@controlvector/cv-agent 1.10.0 → 1.10.1
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/bundle.cjs +572 -67
- package/dist/bundle.cjs.map +3 -3
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +126 -7
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/git-safety.d.ts +12 -6
- package/dist/commands/git-safety.d.ts.map +1 -1
- package/dist/commands/git-safety.js +114 -22
- package/dist/commands/git-safety.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +361 -45
- package/dist/commands/setup.js.map +1 -1
- package/dist/utils/api.d.ts +1 -1
- package/dist/utils/api.d.ts.map +1 -1
- package/dist/utils/api.js +20 -2
- package/dist/utils/api.js.map +1 -1
- package/package.json +1 -1
package/dist/bundle.cjs
CHANGED
|
@@ -3665,6 +3665,92 @@ async function validateToken(hubUrl, token) {
|
|
|
3665
3665
|
return null;
|
|
3666
3666
|
}
|
|
3667
3667
|
}
|
|
3668
|
+
var DEVICE_CLIENT_ID = "cv-agent-cli";
|
|
3669
|
+
var DEVICE_SCOPES = "repo:read repo:write profile offline_access";
|
|
3670
|
+
var POLL_INTERVAL_MS = 5e3;
|
|
3671
|
+
var MAX_POLL_ATTEMPTS = 180;
|
|
3672
|
+
async function deviceAuthFlow(hubUrl, appUrl) {
|
|
3673
|
+
const authRes = await fetch(`${hubUrl}/oauth/device/authorize`, {
|
|
3674
|
+
method: "POST",
|
|
3675
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3676
|
+
body: new URLSearchParams({
|
|
3677
|
+
client_id: DEVICE_CLIENT_ID,
|
|
3678
|
+
scope: DEVICE_SCOPES
|
|
3679
|
+
})
|
|
3680
|
+
});
|
|
3681
|
+
if (!authRes.ok) {
|
|
3682
|
+
const err = await authRes.json().catch(() => ({}));
|
|
3683
|
+
throw new Error(err.error_description || `Device auth failed: ${authRes.status}`);
|
|
3684
|
+
}
|
|
3685
|
+
const auth = await authRes.json();
|
|
3686
|
+
console.log(source_default.bold(" \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
3687
|
+
console.log(source_default.bold(" \u2502 CV-Hub Device Authorization \u2502"));
|
|
3688
|
+
console.log(source_default.bold(" \u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524"));
|
|
3689
|
+
console.log(source_default.bold(" \u2502 \u2502"));
|
|
3690
|
+
console.log(source_default.bold(" \u2502 Open this URL in your browser: \u2502"));
|
|
3691
|
+
console.log(source_default.bold(` \u2502 ${source_default.cyan(auth.verification_uri).padEnd(51)}\u2502`));
|
|
3692
|
+
console.log(source_default.bold(" \u2502 \u2502"));
|
|
3693
|
+
console.log(source_default.bold(" \u2502 Then enter this code: \u2502"));
|
|
3694
|
+
console.log(source_default.bold(` \u2502 ${source_default.white.bold(auth.user_code)} \u2502`));
|
|
3695
|
+
console.log(source_default.bold(" \u2502 \u2502"));
|
|
3696
|
+
console.log(source_default.bold(` \u2502 ${source_default.gray(`Expires in ${Math.floor(auth.expires_in / 60)} minutes`).padEnd(51)}\u2502`));
|
|
3697
|
+
console.log(source_default.bold(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
3698
|
+
console.log();
|
|
3699
|
+
try {
|
|
3700
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
3701
|
+
(0, import_node_child_process.execSync)(`${openCmd} "${auth.verification_uri_complete}" 2>/dev/null`, { timeout: 5e3 });
|
|
3702
|
+
console.log(source_default.gray(" Browser opened. Waiting for authorization..."));
|
|
3703
|
+
} catch {
|
|
3704
|
+
console.log(source_default.gray(" Open the URL above in your browser."));
|
|
3705
|
+
}
|
|
3706
|
+
let interval = Math.max(auth.interval * 1e3, POLL_INTERVAL_MS);
|
|
3707
|
+
const expireTime = Date.now() + auth.expires_in * 1e3;
|
|
3708
|
+
for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) {
|
|
3709
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
3710
|
+
if (Date.now() > expireTime) {
|
|
3711
|
+
throw new Error("Authorization timed out");
|
|
3712
|
+
}
|
|
3713
|
+
const remaining = Math.ceil((expireTime - Date.now()) / 1e3);
|
|
3714
|
+
const mins = Math.floor(remaining / 60);
|
|
3715
|
+
const secs = remaining % 60;
|
|
3716
|
+
process.stdout.write(`\r Waiting for authorization... (${mins}:${secs.toString().padStart(2, "0")} remaining) `);
|
|
3717
|
+
const tokenRes = await fetch(`${hubUrl}/oauth/token`, {
|
|
3718
|
+
method: "POST",
|
|
3719
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3720
|
+
body: new URLSearchParams({
|
|
3721
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
3722
|
+
device_code: auth.device_code,
|
|
3723
|
+
client_id: DEVICE_CLIENT_ID
|
|
3724
|
+
})
|
|
3725
|
+
});
|
|
3726
|
+
const tokenData = await tokenRes.json();
|
|
3727
|
+
if (tokenData.access_token) {
|
|
3728
|
+
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
3729
|
+
let uname = "user";
|
|
3730
|
+
try {
|
|
3731
|
+
const userRes = await fetch(`${hubUrl}/oauth/userinfo`, {
|
|
3732
|
+
headers: { Authorization: `Bearer ${tokenData.access_token}` }
|
|
3733
|
+
});
|
|
3734
|
+
if (userRes.ok) {
|
|
3735
|
+
const userInfo = await userRes.json();
|
|
3736
|
+
uname = userInfo.preferred_username || userInfo.name || "user";
|
|
3737
|
+
}
|
|
3738
|
+
} catch {
|
|
3739
|
+
}
|
|
3740
|
+
return { token: tokenData.access_token, username: uname };
|
|
3741
|
+
}
|
|
3742
|
+
if (tokenData.error === "slow_down") {
|
|
3743
|
+
interval += 5e3;
|
|
3744
|
+
} else if (tokenData.error === "access_denied") {
|
|
3745
|
+
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
3746
|
+
throw new Error("Authorization denied by user");
|
|
3747
|
+
} else if (tokenData.error === "expired_token") {
|
|
3748
|
+
process.stdout.write("\r" + " ".repeat(60) + "\r");
|
|
3749
|
+
throw new Error("Authorization expired");
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
throw new Error("Authorization timed out");
|
|
3753
|
+
}
|
|
3668
3754
|
function checkBinary(cmd) {
|
|
3669
3755
|
try {
|
|
3670
3756
|
return (0, import_node_child_process.execSync)(`${cmd} 2>&1`, { encoding: "utf8", timeout: 5e3 }).trim().split("\n")[0];
|
|
@@ -3724,38 +3810,20 @@ async function runSetup() {
|
|
|
3724
3810
|
if (!token) {
|
|
3725
3811
|
console.log(" Let's connect you to CV-Hub.");
|
|
3726
3812
|
console.log();
|
|
3727
|
-
const autoName = `${(0, import_node_os2.hostname)()}-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
3728
|
-
const tokenUrl = `${appUrl}/settings/tokens/new?name=${encodeURIComponent(autoName)}&scopes=agent,repo`;
|
|
3729
|
-
console.log(source_default.gray(` Opening: ${tokenUrl}`));
|
|
3730
3813
|
try {
|
|
3731
|
-
const
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
console.log(source_default.
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
resolve(answer.trim());
|
|
3743
|
-
});
|
|
3744
|
-
});
|
|
3745
|
-
if (!token) {
|
|
3746
|
-
console.log(source_default.red(" No token provided. Run cva setup again."));
|
|
3747
|
-
process.exit(1);
|
|
3748
|
-
}
|
|
3749
|
-
const user = await validateToken(hubUrl, token);
|
|
3750
|
-
if (!user) {
|
|
3751
|
-
console.log(source_default.red(" Token validation failed. Check your token and try again."));
|
|
3814
|
+
const deviceResult = await deviceAuthFlow(hubUrl, appUrl);
|
|
3815
|
+
token = deviceResult.token;
|
|
3816
|
+
username = deviceResult.username;
|
|
3817
|
+
console.log(source_default.green(" \u2713") + ` Authenticated as ${source_default.bold(username)}`);
|
|
3818
|
+
writeSharedCreds({ hub_url: hubUrl, token, username, created_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3819
|
+
await writeCredentialField("CV_HUB_PAT", token);
|
|
3820
|
+
await writeCredentialField("CV_HUB_API", hubUrl);
|
|
3821
|
+
} catch (err) {
|
|
3822
|
+
console.log(source_default.red(` Authentication failed: ${err.message}`));
|
|
3823
|
+
console.log(source_default.gray(" You can retry with: cva setup"));
|
|
3824
|
+
console.log(source_default.gray(" Or manually: cva auth login --token <your-pat>"));
|
|
3752
3825
|
process.exit(1);
|
|
3753
3826
|
}
|
|
3754
|
-
username = user;
|
|
3755
|
-
console.log(source_default.green(" \u2713") + ` Authenticated as ${source_default.bold(user)}`);
|
|
3756
|
-
writeSharedCreds({ hub_url: hubUrl, token, username, created_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
3757
|
-
await writeCredentialField("CV_HUB_PAT", token);
|
|
3758
|
-
await writeCredentialField("CV_HUB_API", hubUrl);
|
|
3759
3827
|
}
|
|
3760
3828
|
console.log();
|
|
3761
3829
|
console.log(" Claude.ai MCP connector:");
|
|
@@ -3766,18 +3834,84 @@ async function runSetup() {
|
|
|
3766
3834
|
console.log(source_default.gray(' Click "Add Integration" \u2192 "Allow" when prompted.'));
|
|
3767
3835
|
console.log(source_default.gray(" (You can do this later \u2014 setup will continue.)"));
|
|
3768
3836
|
console.log();
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
const hasClaudeMd = (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, "CLAUDE.md"));
|
|
3773
|
-
const repoName = (0, import_node_path.basename)(cwd);
|
|
3837
|
+
let cwd = process.cwd();
|
|
3838
|
+
let isGitRepo = (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".git"));
|
|
3839
|
+
let repoName = (0, import_node_path.basename)(cwd);
|
|
3774
3840
|
if (isGitRepo) {
|
|
3775
3841
|
console.log(source_default.green(" \u2713") + ` Git repo found: ${repoName}`);
|
|
3776
3842
|
} else {
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
console.log(
|
|
3843
|
+
const readline = await import("node:readline");
|
|
3844
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3845
|
+
console.log(" No git repository found in this directory.");
|
|
3846
|
+
console.log();
|
|
3847
|
+
console.log(` ${source_default.cyan("a")} \u2014 Initialize a new project here: ${cwd}`);
|
|
3848
|
+
console.log(` ${source_default.cyan("b")} \u2014 Clone an existing repo from CV-Hub`);
|
|
3849
|
+
console.log();
|
|
3850
|
+
const choice = await new Promise((resolve2) => {
|
|
3851
|
+
rl.question(" Choose [a/b]: ", (answer) => {
|
|
3852
|
+
rl.close();
|
|
3853
|
+
resolve2(answer.trim().toLowerCase() || "a");
|
|
3854
|
+
});
|
|
3855
|
+
});
|
|
3856
|
+
if (choice === "b" && token) {
|
|
3857
|
+
try {
|
|
3858
|
+
console.log(source_default.gray(" Fetching your repositories..."));
|
|
3859
|
+
const controller = new AbortController();
|
|
3860
|
+
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
3861
|
+
const res = await fetch(`${hubUrl}/api/v1/repos?limit=50`, {
|
|
3862
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
3863
|
+
signal: controller.signal
|
|
3864
|
+
});
|
|
3865
|
+
clearTimeout(timeout);
|
|
3866
|
+
if (res.ok) {
|
|
3867
|
+
const data = await res.json();
|
|
3868
|
+
const repos = data.repositories || [];
|
|
3869
|
+
if (repos.length === 0) {
|
|
3870
|
+
console.log(source_default.yellow(" No repos found on CV-Hub. Initializing a new project instead."));
|
|
3871
|
+
} else {
|
|
3872
|
+
console.log();
|
|
3873
|
+
console.log(" Your CV-Hub repositories:");
|
|
3874
|
+
const displayRepos = repos.slice(0, 20);
|
|
3875
|
+
displayRepos.forEach((r, i) => {
|
|
3876
|
+
const desc = r.description ? source_default.gray(` \u2014 ${r.description.substring(0, 40)}`) : "";
|
|
3877
|
+
console.log(` ${source_default.cyan(String(i + 1).padStart(2))}. ${r.slug || r.name}${desc}`);
|
|
3878
|
+
});
|
|
3879
|
+
console.log();
|
|
3880
|
+
const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3881
|
+
const selection = await new Promise((resolve2) => {
|
|
3882
|
+
rl2.question(` Select a repo [1-${displayRepos.length}]: `, (answer) => {
|
|
3883
|
+
rl2.close();
|
|
3884
|
+
resolve2(answer.trim());
|
|
3885
|
+
});
|
|
3886
|
+
});
|
|
3887
|
+
const idx = parseInt(selection, 10) - 1;
|
|
3888
|
+
if (idx >= 0 && idx < displayRepos.length) {
|
|
3889
|
+
const repo = displayRepos[idx];
|
|
3890
|
+
const gitHost = "git.hub.controlvector.io";
|
|
3891
|
+
const slug = repo.slug || repo.name;
|
|
3892
|
+
const cloneUrl = `https://${username}:${token}@${gitHost}/${username}/${slug}.git`;
|
|
3893
|
+
console.log(source_default.gray(` Cloning ${username}/${slug}...`));
|
|
3894
|
+
(0, import_node_child_process.execSync)(`git clone ${cloneUrl} ${slug}`, { cwd, stdio: "pipe", timeout: 6e4 });
|
|
3895
|
+
cwd = (0, import_node_path.join)(cwd, slug);
|
|
3896
|
+
process.chdir(cwd);
|
|
3897
|
+
repoName = slug;
|
|
3898
|
+
isGitRepo = true;
|
|
3899
|
+
console.log(source_default.green(" \u2713") + ` Cloned ${username}/${slug}`);
|
|
3900
|
+
}
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
} catch (err) {
|
|
3904
|
+
console.log(source_default.yellow(` Could not fetch repos: ${err.message}. Initializing instead.`));
|
|
3905
|
+
}
|
|
3906
|
+
}
|
|
3907
|
+
if (!isGitRepo) {
|
|
3908
|
+
console.log(" Initializing git repository...");
|
|
3909
|
+
(0, import_node_child_process.execSync)("git init && git checkout -b main", { cwd, stdio: "pipe" });
|
|
3910
|
+
console.log(source_default.green(" \u2713") + " Git repo initialized");
|
|
3911
|
+
}
|
|
3780
3912
|
}
|
|
3913
|
+
const hasClaudeMd = (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, "CLAUDE.md"));
|
|
3914
|
+
const hasCVDir = (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".cv"));
|
|
3781
3915
|
if (!hasClaudeMd) {
|
|
3782
3916
|
const template = `# ${repoName}
|
|
3783
3917
|
|
|
@@ -3795,6 +3929,41 @@ async function runSetup() {
|
|
|
3795
3929
|
} else {
|
|
3796
3930
|
console.log(source_default.green(" \u2713") + " CLAUDE.md present");
|
|
3797
3931
|
}
|
|
3932
|
+
const gitignorePath = (0, import_node_path.join)(cwd, ".gitignore");
|
|
3933
|
+
const CREDENTIAL_PATTERNS = [
|
|
3934
|
+
"# Credentials and secrets (auto-added by cv-agent)",
|
|
3935
|
+
".env",
|
|
3936
|
+
".env.*",
|
|
3937
|
+
".claude/",
|
|
3938
|
+
".claude.json",
|
|
3939
|
+
".credentials*",
|
|
3940
|
+
"*.pem",
|
|
3941
|
+
"*.key",
|
|
3942
|
+
".ssh/",
|
|
3943
|
+
".gnupg/",
|
|
3944
|
+
".npm/",
|
|
3945
|
+
".config/",
|
|
3946
|
+
".zsh_history",
|
|
3947
|
+
".bash_history",
|
|
3948
|
+
".zsh_sessions/",
|
|
3949
|
+
"node_modules/",
|
|
3950
|
+
".DS_Store"
|
|
3951
|
+
];
|
|
3952
|
+
try {
|
|
3953
|
+
let gitignoreContent = "";
|
|
3954
|
+
if ((0, import_node_fs.existsSync)(gitignorePath)) {
|
|
3955
|
+
gitignoreContent = (0, import_node_fs.readFileSync)(gitignorePath, "utf-8");
|
|
3956
|
+
}
|
|
3957
|
+
const missing = CREDENTIAL_PATTERNS.filter((p) => !gitignoreContent.includes(p));
|
|
3958
|
+
if (missing.length > 0) {
|
|
3959
|
+
const addition = (gitignoreContent && !gitignoreContent.endsWith("\n") ? "\n" : "") + missing.join("\n") + "\n";
|
|
3960
|
+
(0, import_node_fs.writeFileSync)(gitignorePath, gitignoreContent + addition);
|
|
3961
|
+
console.log(source_default.green(" \u2713") + " .gitignore updated with credential protection");
|
|
3962
|
+
} else {
|
|
3963
|
+
console.log(source_default.green(" \u2713") + " .gitignore has credential protection");
|
|
3964
|
+
}
|
|
3965
|
+
} catch {
|
|
3966
|
+
}
|
|
3798
3967
|
if (!hasCVDir) {
|
|
3799
3968
|
(0, import_node_fs.mkdirSync)((0, import_node_path.join)(cwd, ".cv"), { recursive: true });
|
|
3800
3969
|
}
|
|
@@ -3804,6 +3973,22 @@ async function runSetup() {
|
|
|
3804
3973
|
try {
|
|
3805
3974
|
const existingRemote = (0, import_node_child_process.execSync)('git remote get-url cv-hub 2>/dev/null || echo ""', { cwd, encoding: "utf8" }).trim();
|
|
3806
3975
|
if (!existingRemote) {
|
|
3976
|
+
try {
|
|
3977
|
+
const controller = new AbortController();
|
|
3978
|
+
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
3979
|
+
await fetch(`${hubUrl}/api/v1/user/repos`, {
|
|
3980
|
+
method: "POST",
|
|
3981
|
+
headers: {
|
|
3982
|
+
Authorization: `Bearer ${token}`,
|
|
3983
|
+
"Content-Type": "application/json"
|
|
3984
|
+
},
|
|
3985
|
+
body: JSON.stringify({ name: repoName, auto_init: false }),
|
|
3986
|
+
signal: controller.signal
|
|
3987
|
+
});
|
|
3988
|
+
clearTimeout(timeout);
|
|
3989
|
+
console.log(source_default.green(" \u2713") + ` Repo created on CV-Hub: ${username}/${repoName}`);
|
|
3990
|
+
} catch {
|
|
3991
|
+
}
|
|
3807
3992
|
(0, import_node_child_process.execSync)(`git remote add cv-hub ${remoteUrl}`, { cwd, stdio: "pipe" });
|
|
3808
3993
|
console.log(source_default.green(" \u2713") + ` Remote added: cv-hub \u2192 ${remoteUrl}`);
|
|
3809
3994
|
} else {
|
|
@@ -3811,6 +3996,99 @@ async function runSetup() {
|
|
|
3811
3996
|
}
|
|
3812
3997
|
} catch {
|
|
3813
3998
|
}
|
|
3999
|
+
try {
|
|
4000
|
+
const credStorePath = (0, import_node_path.join)((0, import_node_os2.homedir)(), ".git-credentials");
|
|
4001
|
+
const credLine = `https://${username}:${token}@${gitHost}`;
|
|
4002
|
+
let existing = "";
|
|
4003
|
+
try {
|
|
4004
|
+
existing = (0, import_node_fs.readFileSync)(credStorePath, "utf-8");
|
|
4005
|
+
} catch {
|
|
4006
|
+
}
|
|
4007
|
+
if (!existing.includes(gitHost)) {
|
|
4008
|
+
(0, import_node_fs.writeFileSync)(credStorePath, existing + credLine + "\n", { mode: 384 });
|
|
4009
|
+
}
|
|
4010
|
+
(0, import_node_child_process.execSync)(`git config --global credential.helper store`, { stdio: "pipe" });
|
|
4011
|
+
} catch {
|
|
4012
|
+
}
|
|
4013
|
+
try {
|
|
4014
|
+
(0, import_node_child_process.execSync)("git log --oneline -1", { cwd, stdio: "pipe" });
|
|
4015
|
+
const status = (0, import_node_child_process.execSync)("git status --porcelain", { cwd, encoding: "utf8" }).trim();
|
|
4016
|
+
if (status) {
|
|
4017
|
+
(0, import_node_child_process.execSync)("git add -A", { cwd, stdio: "pipe" });
|
|
4018
|
+
(0, import_node_child_process.execSync)('git commit -m "chore: cv-agent setup"', { cwd, stdio: "pipe" });
|
|
4019
|
+
console.log(source_default.green(" \u2713") + " Changes committed");
|
|
4020
|
+
}
|
|
4021
|
+
} catch {
|
|
4022
|
+
try {
|
|
4023
|
+
(0, import_node_child_process.execSync)("git add -A", { cwd, stdio: "pipe" });
|
|
4024
|
+
(0, import_node_child_process.execSync)('git commit -m "Initial commit via cv-agent"', { cwd, stdio: "pipe" });
|
|
4025
|
+
console.log(source_default.green(" \u2713") + " Initial commit created");
|
|
4026
|
+
} catch {
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
try {
|
|
4030
|
+
(0, import_node_child_process.execSync)("git push -u cv-hub main 2>&1", { cwd, stdio: "pipe", timeout: 3e4 });
|
|
4031
|
+
console.log(source_default.green(" \u2713") + " Pushed to CV-Hub");
|
|
4032
|
+
} catch {
|
|
4033
|
+
console.log(source_default.gray(" (Push skipped \u2014 you can push later with: git push cv-hub main)"));
|
|
4034
|
+
}
|
|
4035
|
+
}
|
|
4036
|
+
console.log();
|
|
4037
|
+
let agentStatus = "";
|
|
4038
|
+
const pidFile = (0, import_node_path.join)((0, import_node_os2.homedir)(), ".config", "controlvector", "agent.pid");
|
|
4039
|
+
let agentRunning = false;
|
|
4040
|
+
try {
|
|
4041
|
+
const pid = parseInt((0, import_node_fs.readFileSync)(pidFile, "utf-8").trim(), 10);
|
|
4042
|
+
if (pid > 0) {
|
|
4043
|
+
process.kill(pid, 0);
|
|
4044
|
+
agentRunning = true;
|
|
4045
|
+
agentStatus = `running (PID ${pid})`;
|
|
4046
|
+
console.log(source_default.green(" \u2713") + ` Agent already running (PID ${pid})`);
|
|
4047
|
+
}
|
|
4048
|
+
} catch {
|
|
4049
|
+
}
|
|
4050
|
+
if (!agentRunning) {
|
|
4051
|
+
const readline = await import("node:readline");
|
|
4052
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
4053
|
+
const answer = await new Promise((resolve2) => {
|
|
4054
|
+
rl.question(" Start the CV-Agent daemon? (Y/n): ", (a) => {
|
|
4055
|
+
rl.close();
|
|
4056
|
+
resolve2(a.trim().toLowerCase() || "y");
|
|
4057
|
+
});
|
|
4058
|
+
});
|
|
4059
|
+
if (answer === "y" || answer === "yes" || answer === "") {
|
|
4060
|
+
const machName = (0, import_node_os2.hostname)().toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-");
|
|
4061
|
+
console.log(source_default.gray(` Starting agent as "${machName}"...`));
|
|
4062
|
+
try {
|
|
4063
|
+
const { spawn: spawnChild } = await import("node:child_process");
|
|
4064
|
+
const child = spawnChild("cva", [
|
|
4065
|
+
"agent",
|
|
4066
|
+
"--auto-approve",
|
|
4067
|
+
"--machine",
|
|
4068
|
+
machName,
|
|
4069
|
+
"--working-dir",
|
|
4070
|
+
cwd
|
|
4071
|
+
], {
|
|
4072
|
+
detached: true,
|
|
4073
|
+
stdio: "ignore",
|
|
4074
|
+
env: { ...process.env }
|
|
4075
|
+
});
|
|
4076
|
+
child.unref();
|
|
4077
|
+
if (child.pid) {
|
|
4078
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.join)((0, import_node_os2.homedir)(), ".config", "controlvector"), { recursive: true });
|
|
4079
|
+
(0, import_node_fs.writeFileSync)(pidFile, String(child.pid), { mode: 384 });
|
|
4080
|
+
agentStatus = `running (PID ${child.pid})`;
|
|
4081
|
+
console.log(source_default.green(" \u2713") + ` Agent started (PID ${child.pid}) \u2014 executor "${machName}"`);
|
|
4082
|
+
}
|
|
4083
|
+
} catch (err) {
|
|
4084
|
+
console.log(source_default.yellow(` Could not start agent: ${err.message}`));
|
|
4085
|
+
console.log(source_default.gray(" Start manually with: cva agent --auto-approve"));
|
|
4086
|
+
agentStatus = "not started";
|
|
4087
|
+
}
|
|
4088
|
+
} else {
|
|
4089
|
+
agentStatus = "not started";
|
|
4090
|
+
console.log(source_default.gray(" Start anytime with: cva agent --auto-approve"));
|
|
4091
|
+
}
|
|
3814
4092
|
}
|
|
3815
4093
|
console.log();
|
|
3816
4094
|
console.log(source_default.bold(" Setup Complete"));
|
|
@@ -3818,11 +4096,14 @@ async function runSetup() {
|
|
|
3818
4096
|
console.log(` ${source_default.green("\u2713")} Authenticated as: ${source_default.cyan(username)}`);
|
|
3819
4097
|
console.log(` ${source_default.green("\u2713")} Repository: ${source_default.cyan(repoName)}`);
|
|
3820
4098
|
console.log(` ${source_default.green("\u2713")} CLAUDE.md: present`);
|
|
4099
|
+
if (agentStatus.startsWith("running")) {
|
|
4100
|
+
console.log(` ${source_default.green("\u2713")} Agent daemon: ${source_default.cyan(agentStatus)}`);
|
|
4101
|
+
}
|
|
3821
4102
|
console.log(source_default.gray(" " + "\u2500".repeat(40)));
|
|
3822
4103
|
console.log();
|
|
3823
4104
|
console.log(" What's next:");
|
|
3824
|
-
console.log(
|
|
3825
|
-
console.log(`
|
|
4105
|
+
console.log(" Open Claude.ai and try:");
|
|
4106
|
+
console.log(source_default.cyan(` "Create a task in ${repoName} to add a hello world index.html"`));
|
|
3826
4107
|
console.log();
|
|
3827
4108
|
console.log(source_default.gray(` Dashboard: ${appUrl}`));
|
|
3828
4109
|
console.log();
|
|
@@ -3978,7 +4259,7 @@ async function apiCall(creds, method, path3, body) {
|
|
|
3978
4259
|
body: body ? JSON.stringify(body) : void 0
|
|
3979
4260
|
});
|
|
3980
4261
|
}
|
|
3981
|
-
async function registerExecutor(creds, machineName, workingDir, repositoryId, metadata) {
|
|
4262
|
+
async function registerExecutor(creds, machineName, workingDir, repositoryId, metadata, repoOwnerSlug) {
|
|
3982
4263
|
const body = {
|
|
3983
4264
|
name: `cva:${machineName}`,
|
|
3984
4265
|
machine_name: machineName,
|
|
@@ -3997,7 +4278,23 @@ async function registerExecutor(creds, machineName, workingDir, repositoryId, me
|
|
|
3997
4278
|
if (metadata?.tags) body.tags = metadata.tags;
|
|
3998
4279
|
if (metadata?.owner_project) body.owner_project = metadata.owner_project;
|
|
3999
4280
|
if (metadata?.integration) body.integration = metadata.integration;
|
|
4000
|
-
|
|
4281
|
+
let res = await apiCall(creds, "POST", "/api/v1/executors", body);
|
|
4282
|
+
if (res.status === 400 && repoOwnerSlug) {
|
|
4283
|
+
try {
|
|
4284
|
+
const errData = await res.json();
|
|
4285
|
+
const orgs = errData.error?.organizations;
|
|
4286
|
+
if (orgs && orgs.length > 0) {
|
|
4287
|
+
const match = orgs.find(
|
|
4288
|
+
(o) => o.slug.toLowerCase() === repoOwnerSlug.toLowerCase()
|
|
4289
|
+
);
|
|
4290
|
+
if (match) {
|
|
4291
|
+
body.organization_id = match.id;
|
|
4292
|
+
res = await apiCall(creds, "POST", "/api/v1/executors", body);
|
|
4293
|
+
}
|
|
4294
|
+
}
|
|
4295
|
+
} catch {
|
|
4296
|
+
}
|
|
4297
|
+
}
|
|
4001
4298
|
if (!res.ok) {
|
|
4002
4299
|
const err = await res.text();
|
|
4003
4300
|
throw new Error(`Failed to register executor: ${res.status} ${err}`);
|
|
@@ -4440,11 +4737,78 @@ async function writeConfig(config) {
|
|
|
4440
4737
|
|
|
4441
4738
|
// src/commands/git-safety.ts
|
|
4442
4739
|
var import_node_child_process3 = require("node:child_process");
|
|
4740
|
+
var import_node_fs3 = require("node:fs");
|
|
4741
|
+
var import_node_path2 = require("node:path");
|
|
4742
|
+
var import_node_os3 = require("node:os");
|
|
4743
|
+
var DANGEROUS_PATTERNS = [
|
|
4744
|
+
".claude/",
|
|
4745
|
+
".claude.json",
|
|
4746
|
+
".credentials",
|
|
4747
|
+
".zsh_history",
|
|
4748
|
+
".bash_history",
|
|
4749
|
+
".zsh_sessions/",
|
|
4750
|
+
".ssh/",
|
|
4751
|
+
".gnupg/",
|
|
4752
|
+
".npm/",
|
|
4753
|
+
".config/",
|
|
4754
|
+
".CFUserTextEncoding",
|
|
4755
|
+
"Library/",
|
|
4756
|
+
"Applications/",
|
|
4757
|
+
".Trash/",
|
|
4758
|
+
".DS_Store",
|
|
4759
|
+
"node_modules/",
|
|
4760
|
+
".env",
|
|
4761
|
+
".env.local",
|
|
4762
|
+
".env.production"
|
|
4763
|
+
];
|
|
4443
4764
|
function git(cmd, cwd) {
|
|
4444
4765
|
return (0, import_node_child_process3.execSync)(cmd, { cwd, encoding: "utf8", timeout: 3e4 }).trim();
|
|
4445
4766
|
}
|
|
4767
|
+
function checkWorkspaceSafety(workspaceRoot) {
|
|
4768
|
+
const resolved = (0, import_node_path2.resolve)(workspaceRoot);
|
|
4769
|
+
const home = (0, import_node_path2.resolve)((0, import_node_os3.homedir)());
|
|
4770
|
+
if (resolved === home) {
|
|
4771
|
+
return `Workspace is user HOME directory (${home}). Refusing to auto-commit to prevent indexing personal files.`;
|
|
4772
|
+
}
|
|
4773
|
+
if (home.startsWith(resolved + "/")) {
|
|
4774
|
+
return `Workspace (${resolved}) is a parent of HOME. Refusing to auto-commit.`;
|
|
4775
|
+
}
|
|
4776
|
+
if (!(0, import_node_fs3.existsSync)((0, import_node_path2.join)(resolved, ".git"))) {
|
|
4777
|
+
return `No .git directory in ${resolved}. Not a git repository.`;
|
|
4778
|
+
}
|
|
4779
|
+
return null;
|
|
4780
|
+
}
|
|
4781
|
+
function filterDangerousFiles(statusLines) {
|
|
4782
|
+
const safe = [];
|
|
4783
|
+
const blocked = [];
|
|
4784
|
+
for (const line of statusLines) {
|
|
4785
|
+
const filePath = line.substring(3).trim();
|
|
4786
|
+
const isDangerous = DANGEROUS_PATTERNS.some(
|
|
4787
|
+
(pattern) => filePath.startsWith(pattern) || filePath.includes("/" + pattern)
|
|
4788
|
+
);
|
|
4789
|
+
if (isDangerous) {
|
|
4790
|
+
blocked.push(filePath);
|
|
4791
|
+
} else {
|
|
4792
|
+
safe.push(line);
|
|
4793
|
+
}
|
|
4794
|
+
}
|
|
4795
|
+
return { safe, blocked };
|
|
4796
|
+
}
|
|
4446
4797
|
function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
4447
4798
|
const targetBranch = branch || "main";
|
|
4799
|
+
const safetyError = checkWorkspaceSafety(workspaceRoot);
|
|
4800
|
+
if (safetyError) {
|
|
4801
|
+
console.log(` [git-safety] BLOCKED: ${safetyError}`);
|
|
4802
|
+
return {
|
|
4803
|
+
hadChanges: false,
|
|
4804
|
+
filesAdded: 0,
|
|
4805
|
+
filesModified: 0,
|
|
4806
|
+
filesDeleted: 0,
|
|
4807
|
+
pushed: false,
|
|
4808
|
+
skipped: true,
|
|
4809
|
+
skipReason: safetyError
|
|
4810
|
+
};
|
|
4811
|
+
}
|
|
4448
4812
|
try {
|
|
4449
4813
|
let statusOutput;
|
|
4450
4814
|
try {
|
|
@@ -4452,8 +4816,8 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
|
4452
4816
|
} catch {
|
|
4453
4817
|
return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false, error: "git status failed" };
|
|
4454
4818
|
}
|
|
4455
|
-
const
|
|
4456
|
-
if (
|
|
4819
|
+
const allLines = statusOutput.split("\n").filter(Boolean);
|
|
4820
|
+
if (allLines.length === 0) {
|
|
4457
4821
|
try {
|
|
4458
4822
|
const unpushed = git(`git log origin/${targetBranch}..HEAD --oneline 2>/dev/null`, workspaceRoot);
|
|
4459
4823
|
if (unpushed) {
|
|
@@ -4465,8 +4829,16 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
|
4465
4829
|
}
|
|
4466
4830
|
return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false };
|
|
4467
4831
|
}
|
|
4832
|
+
const { safe: safeLines, blocked } = filterDangerousFiles(allLines);
|
|
4833
|
+
if (blocked.length > 0) {
|
|
4834
|
+
console.log(` [git-safety] Blocked ${blocked.length} sensitive file(s) from staging: ${blocked.slice(0, 5).join(", ")}${blocked.length > 5 ? "..." : ""}`);
|
|
4835
|
+
}
|
|
4836
|
+
if (safeLines.length === 0) {
|
|
4837
|
+
console.log(` [git-safety] All ${allLines.length} changed files were blocked by safety filter`);
|
|
4838
|
+
return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false };
|
|
4839
|
+
}
|
|
4468
4840
|
let added = 0, modified = 0, deleted = 0;
|
|
4469
|
-
for (const line of
|
|
4841
|
+
for (const line of safeLines) {
|
|
4470
4842
|
const code = line.substring(0, 2);
|
|
4471
4843
|
if (code.includes("?")) added++;
|
|
4472
4844
|
else if (code.includes("D")) deleted++;
|
|
@@ -4474,9 +4846,15 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
|
4474
4846
|
else added++;
|
|
4475
4847
|
}
|
|
4476
4848
|
console.log(
|
|
4477
|
-
` [git-safety] ${
|
|
4849
|
+
` [git-safety] ${safeLines.length} safe changes (${added} new, ${modified} modified, ${deleted} deleted) \u2014 committing now`
|
|
4478
4850
|
);
|
|
4479
|
-
|
|
4851
|
+
for (const line of safeLines) {
|
|
4852
|
+
const filePath = line.substring(3).trim();
|
|
4853
|
+
try {
|
|
4854
|
+
git(`git add -- ${JSON.stringify(filePath)}`, workspaceRoot);
|
|
4855
|
+
} catch {
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4480
4858
|
const shortId = taskId.substring(0, 8);
|
|
4481
4859
|
const commitMsg = `task: ${taskTitle} [${shortId}]
|
|
4482
4860
|
|
|
@@ -4537,8 +4915,8 @@ Files: ${added} added, ${modified} modified, ${deleted} deleted`;
|
|
|
4537
4915
|
|
|
4538
4916
|
// src/commands/deploy-manifest.ts
|
|
4539
4917
|
var import_node_child_process4 = require("node:child_process");
|
|
4540
|
-
var
|
|
4541
|
-
var
|
|
4918
|
+
var import_node_fs4 = require("node:fs");
|
|
4919
|
+
var import_node_path3 = require("node:path");
|
|
4542
4920
|
function exec(cmd, cwd, timeoutMs = 3e5, env2) {
|
|
4543
4921
|
try {
|
|
4544
4922
|
const stdout = (0, import_node_child_process4.execSync)(cmd, {
|
|
@@ -4581,10 +4959,10 @@ function getHeadCommit(cwd) {
|
|
|
4581
4959
|
}
|
|
4582
4960
|
}
|
|
4583
4961
|
function loadDeployManifest(workspaceRoot) {
|
|
4584
|
-
const manifestPath = (0,
|
|
4585
|
-
if (!(0,
|
|
4962
|
+
const manifestPath = (0, import_node_path3.join)(workspaceRoot, ".cva", "deploy.json");
|
|
4963
|
+
if (!(0, import_node_fs4.existsSync)(manifestPath)) return null;
|
|
4586
4964
|
try {
|
|
4587
|
-
const raw = (0,
|
|
4965
|
+
const raw = (0, import_node_fs4.readFileSync)(manifestPath, "utf8");
|
|
4588
4966
|
const manifest = JSON.parse(raw);
|
|
4589
4967
|
if (!manifest.version || manifest.version < 1) {
|
|
4590
4968
|
console.log(" [deploy] Invalid manifest version");
|
|
@@ -4609,7 +4987,7 @@ async function postTaskDeploy(workspaceRoot, taskId, log) {
|
|
|
4609
4987
|
console.log(` [deploy] Building: ${buildStep.name}`);
|
|
4610
4988
|
log("lifecycle", `Building: ${buildStep.name}`);
|
|
4611
4989
|
const start = Date.now();
|
|
4612
|
-
const cwd = (0,
|
|
4990
|
+
const cwd = (0, import_node_path3.join)(workspaceRoot, buildStep.working_dir || ".");
|
|
4613
4991
|
const timeoutMs = (buildStep.timeout_seconds || 300) * 1e3;
|
|
4614
4992
|
const result = exec(buildStep.command, cwd, timeoutMs);
|
|
4615
4993
|
if (!result.ok) {
|
|
@@ -4700,7 +5078,7 @@ async function rollback(workspaceRoot, targetCommit, manifest, log) {
|
|
|
4700
5078
|
}
|
|
4701
5079
|
if (manifest.build?.steps) {
|
|
4702
5080
|
for (const step of manifest.build.steps) {
|
|
4703
|
-
exec(step.command, (0,
|
|
5081
|
+
exec(step.command, (0, import_node_path3.join)(workspaceRoot, step.working_dir || "."), (step.timeout_seconds || 300) * 1e3);
|
|
4704
5082
|
}
|
|
4705
5083
|
}
|
|
4706
5084
|
if (manifest.service?.restart_command) {
|
|
@@ -4858,6 +5236,22 @@ Target repository: ${task.owner}/${task.repo}
|
|
|
4858
5236
|
`;
|
|
4859
5237
|
prompt += `
|
|
4860
5238
|
|
|
5239
|
+
## Security \u2014 NEVER Commit Secrets
|
|
5240
|
+
`;
|
|
5241
|
+
prompt += `Do NOT add, commit, or push any of the following:
|
|
5242
|
+
`;
|
|
5243
|
+
prompt += `- API keys, tokens, passwords, or credentials
|
|
5244
|
+
`;
|
|
5245
|
+
prompt += `- .env files, .credentials files, .claude/ directory
|
|
5246
|
+
`;
|
|
5247
|
+
prompt += `- SSH keys (.ssh/), GPG keys (.gnupg/)
|
|
5248
|
+
`;
|
|
5249
|
+
prompt += `- Shell history (.zsh_history, .bash_history)
|
|
5250
|
+
`;
|
|
5251
|
+
prompt += `If you need to reference an API key, use an environment variable placeholder.
|
|
5252
|
+
`;
|
|
5253
|
+
prompt += `
|
|
5254
|
+
|
|
4861
5255
|
---
|
|
4862
5256
|
`;
|
|
4863
5257
|
prompt += `When complete, provide a brief summary of what you accomplished.
|
|
@@ -4937,7 +5331,7 @@ async function launchAutoApproveMode(prompt, options) {
|
|
|
4937
5331
|
let lastProgressBytes = 0;
|
|
4938
5332
|
let totalBytesStreamed = 0;
|
|
4939
5333
|
const runOnce = (inputPrompt, isContinue) => {
|
|
4940
|
-
return new Promise((
|
|
5334
|
+
return new Promise((resolve2, reject) => {
|
|
4941
5335
|
const args = isContinue ? ["-p", inputPrompt, "--continue", "--allowedTools", ...ALLOWED_TOOLS] : ["-p", inputPrompt, "--allowedTools", ...ALLOWED_TOOLS];
|
|
4942
5336
|
if (sessionId && !isContinue) {
|
|
4943
5337
|
args.push("--session-id", sessionId);
|
|
@@ -5047,7 +5441,7 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
|
|
|
5047
5441
|
});
|
|
5048
5442
|
child.on("close", (code, signal) => {
|
|
5049
5443
|
_activeChild = null;
|
|
5050
|
-
|
|
5444
|
+
resolve2({
|
|
5051
5445
|
exitCode: signal === "SIGKILL" ? 137 : code ?? 1,
|
|
5052
5446
|
stderr,
|
|
5053
5447
|
output: fullOutput,
|
|
@@ -5086,7 +5480,7 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
|
|
|
5086
5480
|
return result;
|
|
5087
5481
|
}
|
|
5088
5482
|
async function launchRelayMode(prompt, options) {
|
|
5089
|
-
return new Promise((
|
|
5483
|
+
return new Promise((resolve2, reject) => {
|
|
5090
5484
|
const child = (0, import_node_child_process5.spawn)("claude", [], {
|
|
5091
5485
|
cwd: options.cwd,
|
|
5092
5486
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -5291,9 +5685,9 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
|
|
|
5291
5685
|
child.on("close", (code, signal) => {
|
|
5292
5686
|
_activeChild = null;
|
|
5293
5687
|
if (signal === "SIGKILL") {
|
|
5294
|
-
|
|
5688
|
+
resolve2({ exitCode: 137, stderr, output: fullOutput, authFailure });
|
|
5295
5689
|
} else {
|
|
5296
|
-
|
|
5690
|
+
resolve2({ exitCode: code ?? 1, stderr, output: fullOutput, authFailure });
|
|
5297
5691
|
}
|
|
5298
5692
|
});
|
|
5299
5693
|
child.on("error", (err) => {
|
|
@@ -5394,14 +5788,45 @@ async function runAgent(options) {
|
|
|
5394
5788
|
console.log();
|
|
5395
5789
|
process.exit(1);
|
|
5396
5790
|
}
|
|
5791
|
+
if (process.env.CV_DEBUG) {
|
|
5792
|
+
console.log(source_default.gray(` PATH: ${process.env.PATH}`));
|
|
5793
|
+
console.log(source_default.gray(` HOME: ${process.env.HOME}`));
|
|
5794
|
+
console.log(source_default.gray(` SHELL: ${process.env.SHELL}`));
|
|
5795
|
+
}
|
|
5796
|
+
let claudeBinary = "claude";
|
|
5397
5797
|
try {
|
|
5398
5798
|
(0, import_node_child_process5.execSync)("claude --version", { stdio: "pipe", timeout: 5e3 });
|
|
5399
5799
|
} catch {
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5800
|
+
const candidates = [
|
|
5801
|
+
"/usr/local/bin/claude",
|
|
5802
|
+
"/opt/homebrew/bin/claude",
|
|
5803
|
+
`${process.env.HOME}/.npm-global/bin/claude`,
|
|
5804
|
+
`${process.env.HOME}/.nvm/versions/node/*/bin/claude`
|
|
5805
|
+
];
|
|
5806
|
+
let found = false;
|
|
5807
|
+
for (const candidate of candidates) {
|
|
5808
|
+
try {
|
|
5809
|
+
const resolved = (0, import_node_child_process5.execSync)(`ls ${candidate} 2>/dev/null | head -1`, { encoding: "utf8", timeout: 3e3 }).trim();
|
|
5810
|
+
if (resolved) {
|
|
5811
|
+
(0, import_node_child_process5.execSync)(`${resolved} --version`, { stdio: "pipe", timeout: 5e3 });
|
|
5812
|
+
claudeBinary = resolved;
|
|
5813
|
+
found = true;
|
|
5814
|
+
console.log(source_default.yellow("!") + ` Claude Code found at ${resolved} (not on PATH)`);
|
|
5815
|
+
break;
|
|
5816
|
+
}
|
|
5817
|
+
} catch {
|
|
5818
|
+
}
|
|
5819
|
+
}
|
|
5820
|
+
if (!found) {
|
|
5821
|
+
console.log();
|
|
5822
|
+
console.log(source_default.red("Claude Code CLI not found.") + " Install it first:");
|
|
5823
|
+
console.log(` ${source_default.cyan("npm install -g @anthropic-ai/claude-code")}`);
|
|
5824
|
+
console.log();
|
|
5825
|
+
console.log(source_default.gray(` Current PATH: ${process.env.PATH}`));
|
|
5826
|
+
console.log(source_default.gray(" If running via Launch Agent, ensure PATH includes the directory where claude is installed."));
|
|
5827
|
+
console.log();
|
|
5828
|
+
process.exit(1);
|
|
5829
|
+
}
|
|
5405
5830
|
}
|
|
5406
5831
|
const { env: claudeEnv, usingApiKey } = await getClaudeEnv();
|
|
5407
5832
|
const authCheck = await checkClaudeAuth();
|
|
@@ -5442,7 +5867,82 @@ async function runAgent(options) {
|
|
|
5442
5867
|
console.log();
|
|
5443
5868
|
}
|
|
5444
5869
|
}
|
|
5870
|
+
{
|
|
5871
|
+
const isGitRepo = require("fs").existsSync(require("path").join(workingDir, ".git"));
|
|
5872
|
+
if (!isGitRepo) {
|
|
5873
|
+
console.log(source_default.gray(" Bootstrap: initializing git repo..."));
|
|
5874
|
+
try {
|
|
5875
|
+
(0, import_node_child_process5.execSync)("git init && git checkout -b main", { cwd: workingDir, stdio: "pipe" });
|
|
5876
|
+
} catch {
|
|
5877
|
+
}
|
|
5878
|
+
}
|
|
5879
|
+
const claudeMdPath = require("path").join(workingDir, "CLAUDE.md");
|
|
5880
|
+
if (!require("fs").existsSync(claudeMdPath)) {
|
|
5881
|
+
const name = require("path").basename(workingDir);
|
|
5882
|
+
const template = `# ${name}
|
|
5883
|
+
|
|
5884
|
+
## Overview
|
|
5885
|
+
[Describe your project here]
|
|
5886
|
+
|
|
5887
|
+
## Build & Run
|
|
5888
|
+
[How to build and run this project]
|
|
5889
|
+
`;
|
|
5890
|
+
try {
|
|
5891
|
+
require("fs").writeFileSync(claudeMdPath, template);
|
|
5892
|
+
console.log(source_default.gray(" Bootstrap: created CLAUDE.md"));
|
|
5893
|
+
} catch {
|
|
5894
|
+
}
|
|
5895
|
+
}
|
|
5896
|
+
const gitignorePath = require("path").join(workingDir, ".gitignore");
|
|
5897
|
+
try {
|
|
5898
|
+
const fs4 = require("fs");
|
|
5899
|
+
const credPatterns = ".env\n.env.*\n.claude/\n.claude.json\n.credentials*\n*.pem\n*.key\n.ssh/\n.gnupg/\n.npm/\n.config/\n.zsh_history\n.bash_history\nnode_modules/\n.DS_Store\n";
|
|
5900
|
+
let existing = "";
|
|
5901
|
+
try {
|
|
5902
|
+
existing = fs4.readFileSync(gitignorePath, "utf-8");
|
|
5903
|
+
} catch {
|
|
5904
|
+
}
|
|
5905
|
+
if (!existing.includes(".claude/")) {
|
|
5906
|
+
const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
|
|
5907
|
+
fs4.writeFileSync(gitignorePath, existing + prefix + "# Credentials (auto-added by cv-agent)\n" + credPatterns);
|
|
5908
|
+
console.log(source_default.gray(" Bootstrap: .gitignore credential protection added"));
|
|
5909
|
+
}
|
|
5910
|
+
} catch {
|
|
5911
|
+
}
|
|
5912
|
+
const cvDir = require("path").join(workingDir, ".cv");
|
|
5913
|
+
if (!require("fs").existsSync(cvDir)) {
|
|
5914
|
+
try {
|
|
5915
|
+
require("fs").mkdirSync(cvDir, { recursive: true });
|
|
5916
|
+
} catch {
|
|
5917
|
+
}
|
|
5918
|
+
}
|
|
5919
|
+
if (creds.CV_HUB_PAT) {
|
|
5920
|
+
try {
|
|
5921
|
+
const existing = (0, import_node_child_process5.execSync)('git remote get-url cv-hub 2>/dev/null || echo ""', {
|
|
5922
|
+
cwd: workingDir,
|
|
5923
|
+
encoding: "utf8",
|
|
5924
|
+
timeout: 5e3
|
|
5925
|
+
}).trim();
|
|
5926
|
+
if (!existing) {
|
|
5927
|
+
const name = require("path").basename(workingDir);
|
|
5928
|
+
const gitHost = "git.hub.controlvector.io";
|
|
5929
|
+
let user = "user";
|
|
5930
|
+
try {
|
|
5931
|
+
const sharedPath = require("path").join(require("os").homedir(), ".config", "controlvector", "credentials.json");
|
|
5932
|
+
const shared = JSON.parse(require("fs").readFileSync(sharedPath, "utf-8"));
|
|
5933
|
+
if (shared.username) user = shared.username;
|
|
5934
|
+
} catch {
|
|
5935
|
+
}
|
|
5936
|
+
const remoteUrl = `https://${gitHost}/${user}/${name}.git`;
|
|
5937
|
+
(0, import_node_child_process5.execSync)(`git remote add cv-hub ${remoteUrl}`, { cwd: workingDir, stdio: "pipe" });
|
|
5938
|
+
console.log(source_default.gray(` Bootstrap: remote cv-hub \u2192 ${remoteUrl}`));
|
|
5939
|
+
}
|
|
5940
|
+
} catch {
|
|
5941
|
+
}
|
|
5942
|
+
}
|
|
5943
|
+
}
|
|
5445
5944
|
let detectedRepoId;
|
|
5945
|
+
let detectedOwnerSlug;
|
|
5446
5946
|
try {
|
|
5447
5947
|
const remoteUrl = (0, import_node_child_process5.execSync)("git remote get-url origin 2>/dev/null", {
|
|
5448
5948
|
cwd: workingDir,
|
|
@@ -5454,6 +5954,7 @@ async function runAgent(options) {
|
|
|
5454
5954
|
);
|
|
5455
5955
|
if (cvHubMatch) {
|
|
5456
5956
|
const [, repoOwner, repoSlug] = cvHubMatch;
|
|
5957
|
+
detectedOwnerSlug = repoOwner;
|
|
5457
5958
|
try {
|
|
5458
5959
|
const repoData = await resolveRepoId(creds, repoOwner, repoSlug);
|
|
5459
5960
|
if (repoData?.id) {
|
|
@@ -5465,6 +5966,9 @@ async function runAgent(options) {
|
|
|
5465
5966
|
}
|
|
5466
5967
|
} catch {
|
|
5467
5968
|
}
|
|
5969
|
+
if (options.org) {
|
|
5970
|
+
detectedOwnerSlug = options.org;
|
|
5971
|
+
}
|
|
5468
5972
|
const wsConfig = await readWorkspaceConfig(workingDir);
|
|
5469
5973
|
const globalConfig = await readConfig();
|
|
5470
5974
|
const role = options.role || wsConfig.role || globalConfig.role || "development";
|
|
@@ -5505,7 +6009,7 @@ async function runAgent(options) {
|
|
|
5505
6009
|
console.log(source_default.yellow(` Guard: ${guardIcon} ${dispatchGuard}`));
|
|
5506
6010
|
}
|
|
5507
6011
|
const executor = await withRetry(
|
|
5508
|
-
() => registerExecutor(creds, machineName, workingDir, detectedRepoId, executorMeta),
|
|
6012
|
+
() => registerExecutor(creds, machineName, workingDir, detectedRepoId, executorMeta, detectedOwnerSlug),
|
|
5509
6013
|
"Executor registration"
|
|
5510
6014
|
);
|
|
5511
6015
|
const mode = options.autoApprove ? "auto-approve" : "relay";
|
|
@@ -5873,6 +6377,7 @@ function agentCommand() {
|
|
|
5873
6377
|
cmd.option("--dispatch-guard <guard>", "Dispatch guard: open, confirm, locked (default: open)");
|
|
5874
6378
|
cmd.option("--tags <tags>", "Comma-separated tags for this executor");
|
|
5875
6379
|
cmd.option("--owner-project <project>", "Project this executor belongs to");
|
|
6380
|
+
cmd.option("--org <slug>", "Organization slug (auto-detected from repo owner if omitted)");
|
|
5876
6381
|
cmd.action(async (opts) => {
|
|
5877
6382
|
await runAgent(opts);
|
|
5878
6383
|
});
|
|
@@ -5909,7 +6414,7 @@ async function promptForToken() {
|
|
|
5909
6414
|
input: process.stdin,
|
|
5910
6415
|
output: process.stdout
|
|
5911
6416
|
});
|
|
5912
|
-
return new Promise((
|
|
6417
|
+
return new Promise((resolve2) => {
|
|
5913
6418
|
rl.question("Enter your CV-Hub PAT token: ", (answer) => {
|
|
5914
6419
|
rl.close();
|
|
5915
6420
|
const token = answer.trim();
|
|
@@ -5917,7 +6422,7 @@ async function promptForToken() {
|
|
|
5917
6422
|
console.log(source_default.red("No token provided."));
|
|
5918
6423
|
process.exit(1);
|
|
5919
6424
|
}
|
|
5920
|
-
|
|
6425
|
+
resolve2(token);
|
|
5921
6426
|
});
|
|
5922
6427
|
});
|
|
5923
6428
|
}
|
|
@@ -6196,7 +6701,7 @@ function statusCommand() {
|
|
|
6196
6701
|
|
|
6197
6702
|
// src/index.ts
|
|
6198
6703
|
var program2 = new Command();
|
|
6199
|
-
program2.name("cva").description('CV-Hub Agent \u2014 bridges Claude Code with CV-Hub task dispatch.\n\nRun "cva setup" to get started.').version(true ? "1.10.
|
|
6704
|
+
program2.name("cva").description('CV-Hub Agent \u2014 bridges Claude Code with CV-Hub task dispatch.\n\nRun "cva setup" to get started.').version(true ? "1.10.1" : "1.6.0");
|
|
6200
6705
|
program2.addCommand(setupCommand());
|
|
6201
6706
|
program2.addCommand(agentCommand());
|
|
6202
6707
|
program2.addCommand(authCommand());
|