@controlvector/cv-agent 1.9.1 → 1.10.0
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 +255 -507
- package/dist/bundle.cjs.map +4 -4
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +104 -109
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/git-safety.d.ts +6 -12
- package/dist/commands/git-safety.d.ts.map +1 -1
- package/dist/commands/git-safety.js +22 -114
- package/dist/commands/git-safety.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +45 -321
- package/dist/commands/setup.js.map +1 -1
- package/dist/utils/api.d.ts +1 -0
- package/dist/utils/api.d.ts.map +1 -1
- package/dist/utils/api.js.map +1 -1
- package/dist/utils/event-queue.d.ts +66 -0
- package/dist/utils/event-queue.d.ts.map +1 -0
- package/dist/utils/event-queue.js +193 -0
- package/dist/utils/event-queue.js.map +1 -0
- package/package.json +1 -1
package/dist/bundle.cjs
CHANGED
|
@@ -959,8 +959,8 @@ var require_command = __commonJS({
|
|
|
959
959
|
"node_modules/commander/lib/command.js"(exports2) {
|
|
960
960
|
var EventEmitter = require("node:events").EventEmitter;
|
|
961
961
|
var childProcess = require("node:child_process");
|
|
962
|
-
var
|
|
963
|
-
var
|
|
962
|
+
var path3 = require("node:path");
|
|
963
|
+
var fs4 = require("node:fs");
|
|
964
964
|
var process3 = require("node:process");
|
|
965
965
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
966
966
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1892,11 +1892,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1892
1892
|
let launchWithNode = false;
|
|
1893
1893
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1894
1894
|
function findFile(baseDir, baseName) {
|
|
1895
|
-
const localBin =
|
|
1896
|
-
if (
|
|
1897
|
-
if (sourceExt.includes(
|
|
1895
|
+
const localBin = path3.resolve(baseDir, baseName);
|
|
1896
|
+
if (fs4.existsSync(localBin)) return localBin;
|
|
1897
|
+
if (sourceExt.includes(path3.extname(baseName))) return void 0;
|
|
1898
1898
|
const foundExt = sourceExt.find(
|
|
1899
|
-
(ext) =>
|
|
1899
|
+
(ext) => fs4.existsSync(`${localBin}${ext}`)
|
|
1900
1900
|
);
|
|
1901
1901
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1902
1902
|
return void 0;
|
|
@@ -1908,21 +1908,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1908
1908
|
if (this._scriptPath) {
|
|
1909
1909
|
let resolvedScriptPath;
|
|
1910
1910
|
try {
|
|
1911
|
-
resolvedScriptPath =
|
|
1911
|
+
resolvedScriptPath = fs4.realpathSync(this._scriptPath);
|
|
1912
1912
|
} catch (err) {
|
|
1913
1913
|
resolvedScriptPath = this._scriptPath;
|
|
1914
1914
|
}
|
|
1915
|
-
executableDir =
|
|
1916
|
-
|
|
1915
|
+
executableDir = path3.resolve(
|
|
1916
|
+
path3.dirname(resolvedScriptPath),
|
|
1917
1917
|
executableDir
|
|
1918
1918
|
);
|
|
1919
1919
|
}
|
|
1920
1920
|
if (executableDir) {
|
|
1921
1921
|
let localFile = findFile(executableDir, executableFile);
|
|
1922
1922
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1923
|
-
const legacyName =
|
|
1923
|
+
const legacyName = path3.basename(
|
|
1924
1924
|
this._scriptPath,
|
|
1925
|
-
|
|
1925
|
+
path3.extname(this._scriptPath)
|
|
1926
1926
|
);
|
|
1927
1927
|
if (legacyName !== this._name) {
|
|
1928
1928
|
localFile = findFile(
|
|
@@ -1933,7 +1933,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1933
1933
|
}
|
|
1934
1934
|
executableFile = localFile || executableFile;
|
|
1935
1935
|
}
|
|
1936
|
-
launchWithNode = sourceExt.includes(
|
|
1936
|
+
launchWithNode = sourceExt.includes(path3.extname(executableFile));
|
|
1937
1937
|
let proc;
|
|
1938
1938
|
if (process3.platform !== "win32") {
|
|
1939
1939
|
if (launchWithNode) {
|
|
@@ -2773,7 +2773,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2773
2773
|
* @return {Command}
|
|
2774
2774
|
*/
|
|
2775
2775
|
nameFromFilename(filename) {
|
|
2776
|
-
this._name =
|
|
2776
|
+
this._name = path3.basename(filename, path3.extname(filename));
|
|
2777
2777
|
return this;
|
|
2778
2778
|
}
|
|
2779
2779
|
/**
|
|
@@ -2787,9 +2787,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2787
2787
|
* @param {string} [path]
|
|
2788
2788
|
* @return {(string|null|Command)}
|
|
2789
2789
|
*/
|
|
2790
|
-
executableDir(
|
|
2791
|
-
if (
|
|
2792
|
-
this._executableDir =
|
|
2790
|
+
executableDir(path4) {
|
|
2791
|
+
if (path4 === void 0) return this._executableDir;
|
|
2792
|
+
this._executableDir = path4;
|
|
2793
2793
|
return this;
|
|
2794
2794
|
}
|
|
2795
2795
|
/**
|
|
@@ -3665,92 +3665,6 @@ 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
|
-
}
|
|
3754
3668
|
function checkBinary(cmd) {
|
|
3755
3669
|
try {
|
|
3756
3670
|
return (0, import_node_child_process.execSync)(`${cmd} 2>&1`, { encoding: "utf8", timeout: 5e3 }).trim().split("\n")[0];
|
|
@@ -3810,20 +3724,38 @@ async function runSetup() {
|
|
|
3810
3724
|
if (!token) {
|
|
3811
3725
|
console.log(" Let's connect you to CV-Hub.");
|
|
3812
3726
|
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}`));
|
|
3813
3730
|
try {
|
|
3814
|
-
const
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
console.log(source_default.
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3731
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
3732
|
+
(0, import_node_child_process.execSync)(`${openCmd} "${tokenUrl}" 2>/dev/null`, { timeout: 5e3 });
|
|
3733
|
+
} catch {
|
|
3734
|
+
console.log(source_default.gray(" (Could not open browser \u2014 copy the URL above)"));
|
|
3735
|
+
}
|
|
3736
|
+
console.log();
|
|
3737
|
+
const readline = await import("node:readline");
|
|
3738
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
3739
|
+
token = await new Promise((resolve) => {
|
|
3740
|
+
rl.question(" Paste your token here: ", (answer) => {
|
|
3741
|
+
rl.close();
|
|
3742
|
+
resolve(answer.trim());
|
|
3743
|
+
});
|
|
3744
|
+
});
|
|
3745
|
+
if (!token) {
|
|
3746
|
+
console.log(source_default.red(" No token provided. Run cva setup again."));
|
|
3825
3747
|
process.exit(1);
|
|
3826
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."));
|
|
3752
|
+
process.exit(1);
|
|
3753
|
+
}
|
|
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);
|
|
3827
3759
|
}
|
|
3828
3760
|
console.log();
|
|
3829
3761
|
console.log(" Claude.ai MCP connector:");
|
|
@@ -3834,84 +3766,18 @@ async function runSetup() {
|
|
|
3834
3766
|
console.log(source_default.gray(' Click "Add Integration" \u2192 "Allow" when prompted.'));
|
|
3835
3767
|
console.log(source_default.gray(" (You can do this later \u2014 setup will continue.)"));
|
|
3836
3768
|
console.log();
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3769
|
+
const cwd = process.cwd();
|
|
3770
|
+
const isGitRepo = (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".git"));
|
|
3771
|
+
const hasCVDir = (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".cv"));
|
|
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);
|
|
3840
3774
|
if (isGitRepo) {
|
|
3841
3775
|
console.log(source_default.green(" \u2713") + ` Git repo found: ${repoName}`);
|
|
3842
3776
|
} else {
|
|
3843
|
-
|
|
3844
|
-
|
|
3845
|
-
console.log("
|
|
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
|
-
}
|
|
3777
|
+
console.log(" Initializing git repository...");
|
|
3778
|
+
(0, import_node_child_process.execSync)("git init && git checkout -b main", { cwd, stdio: "pipe" });
|
|
3779
|
+
console.log(source_default.green(" \u2713") + " Git repo initialized");
|
|
3912
3780
|
}
|
|
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"));
|
|
3915
3781
|
if (!hasClaudeMd) {
|
|
3916
3782
|
const template = `# ${repoName}
|
|
3917
3783
|
|
|
@@ -3938,22 +3804,6 @@ async function runSetup() {
|
|
|
3938
3804
|
try {
|
|
3939
3805
|
const existingRemote = (0, import_node_child_process.execSync)('git remote get-url cv-hub 2>/dev/null || echo ""', { cwd, encoding: "utf8" }).trim();
|
|
3940
3806
|
if (!existingRemote) {
|
|
3941
|
-
try {
|
|
3942
|
-
const controller = new AbortController();
|
|
3943
|
-
const timeout = setTimeout(() => controller.abort(), 15e3);
|
|
3944
|
-
await fetch(`${hubUrl}/api/v1/user/repos`, {
|
|
3945
|
-
method: "POST",
|
|
3946
|
-
headers: {
|
|
3947
|
-
Authorization: `Bearer ${token}`,
|
|
3948
|
-
"Content-Type": "application/json"
|
|
3949
|
-
},
|
|
3950
|
-
body: JSON.stringify({ name: repoName, auto_init: false }),
|
|
3951
|
-
signal: controller.signal
|
|
3952
|
-
});
|
|
3953
|
-
clearTimeout(timeout);
|
|
3954
|
-
console.log(source_default.green(" \u2713") + ` Repo created on CV-Hub: ${username}/${repoName}`);
|
|
3955
|
-
} catch {
|
|
3956
|
-
}
|
|
3957
3807
|
(0, import_node_child_process.execSync)(`git remote add cv-hub ${remoteUrl}`, { cwd, stdio: "pipe" });
|
|
3958
3808
|
console.log(source_default.green(" \u2713") + ` Remote added: cv-hub \u2192 ${remoteUrl}`);
|
|
3959
3809
|
} else {
|
|
@@ -3961,99 +3811,6 @@ async function runSetup() {
|
|
|
3961
3811
|
}
|
|
3962
3812
|
} catch {
|
|
3963
3813
|
}
|
|
3964
|
-
try {
|
|
3965
|
-
const credStorePath = (0, import_node_path.join)((0, import_node_os2.homedir)(), ".git-credentials");
|
|
3966
|
-
const credLine = `https://${username}:${token}@${gitHost}`;
|
|
3967
|
-
let existing = "";
|
|
3968
|
-
try {
|
|
3969
|
-
existing = (0, import_node_fs.readFileSync)(credStorePath, "utf-8");
|
|
3970
|
-
} catch {
|
|
3971
|
-
}
|
|
3972
|
-
if (!existing.includes(gitHost)) {
|
|
3973
|
-
(0, import_node_fs.writeFileSync)(credStorePath, existing + credLine + "\n", { mode: 384 });
|
|
3974
|
-
}
|
|
3975
|
-
(0, import_node_child_process.execSync)(`git config --global credential.helper store`, { stdio: "pipe" });
|
|
3976
|
-
} catch {
|
|
3977
|
-
}
|
|
3978
|
-
try {
|
|
3979
|
-
(0, import_node_child_process.execSync)("git log --oneline -1", { cwd, stdio: "pipe" });
|
|
3980
|
-
const status = (0, import_node_child_process.execSync)("git status --porcelain", { cwd, encoding: "utf8" }).trim();
|
|
3981
|
-
if (status) {
|
|
3982
|
-
(0, import_node_child_process.execSync)("git add -A", { cwd, stdio: "pipe" });
|
|
3983
|
-
(0, import_node_child_process.execSync)('git commit -m "chore: cv-agent setup"', { cwd, stdio: "pipe" });
|
|
3984
|
-
console.log(source_default.green(" \u2713") + " Changes committed");
|
|
3985
|
-
}
|
|
3986
|
-
} catch {
|
|
3987
|
-
try {
|
|
3988
|
-
(0, import_node_child_process.execSync)("git add -A", { cwd, stdio: "pipe" });
|
|
3989
|
-
(0, import_node_child_process.execSync)('git commit -m "Initial commit via cv-agent"', { cwd, stdio: "pipe" });
|
|
3990
|
-
console.log(source_default.green(" \u2713") + " Initial commit created");
|
|
3991
|
-
} catch {
|
|
3992
|
-
}
|
|
3993
|
-
}
|
|
3994
|
-
try {
|
|
3995
|
-
(0, import_node_child_process.execSync)("git push -u cv-hub main 2>&1", { cwd, stdio: "pipe", timeout: 3e4 });
|
|
3996
|
-
console.log(source_default.green(" \u2713") + " Pushed to CV-Hub");
|
|
3997
|
-
} catch {
|
|
3998
|
-
console.log(source_default.gray(" (Push skipped \u2014 you can push later with: git push cv-hub main)"));
|
|
3999
|
-
}
|
|
4000
|
-
}
|
|
4001
|
-
console.log();
|
|
4002
|
-
let agentStatus = "";
|
|
4003
|
-
const pidFile = (0, import_node_path.join)((0, import_node_os2.homedir)(), ".config", "controlvector", "agent.pid");
|
|
4004
|
-
let agentRunning = false;
|
|
4005
|
-
try {
|
|
4006
|
-
const pid = parseInt((0, import_node_fs.readFileSync)(pidFile, "utf-8").trim(), 10);
|
|
4007
|
-
if (pid > 0) {
|
|
4008
|
-
process.kill(pid, 0);
|
|
4009
|
-
agentRunning = true;
|
|
4010
|
-
agentStatus = `running (PID ${pid})`;
|
|
4011
|
-
console.log(source_default.green(" \u2713") + ` Agent already running (PID ${pid})`);
|
|
4012
|
-
}
|
|
4013
|
-
} catch {
|
|
4014
|
-
}
|
|
4015
|
-
if (!agentRunning) {
|
|
4016
|
-
const readline = await import("node:readline");
|
|
4017
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
4018
|
-
const answer = await new Promise((resolve2) => {
|
|
4019
|
-
rl.question(" Start the CV-Agent daemon? (Y/n): ", (a) => {
|
|
4020
|
-
rl.close();
|
|
4021
|
-
resolve2(a.trim().toLowerCase() || "y");
|
|
4022
|
-
});
|
|
4023
|
-
});
|
|
4024
|
-
if (answer === "y" || answer === "yes" || answer === "") {
|
|
4025
|
-
const machName = (0, import_node_os2.hostname)().toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-");
|
|
4026
|
-
console.log(source_default.gray(` Starting agent as "${machName}"...`));
|
|
4027
|
-
try {
|
|
4028
|
-
const { spawn: spawnChild } = await import("node:child_process");
|
|
4029
|
-
const child = spawnChild("cva", [
|
|
4030
|
-
"agent",
|
|
4031
|
-
"--auto-approve",
|
|
4032
|
-
"--machine",
|
|
4033
|
-
machName,
|
|
4034
|
-
"--working-dir",
|
|
4035
|
-
cwd
|
|
4036
|
-
], {
|
|
4037
|
-
detached: true,
|
|
4038
|
-
stdio: "ignore",
|
|
4039
|
-
env: { ...process.env }
|
|
4040
|
-
});
|
|
4041
|
-
child.unref();
|
|
4042
|
-
if (child.pid) {
|
|
4043
|
-
(0, import_node_fs.mkdirSync)((0, import_node_path.join)((0, import_node_os2.homedir)(), ".config", "controlvector"), { recursive: true });
|
|
4044
|
-
(0, import_node_fs.writeFileSync)(pidFile, String(child.pid), { mode: 384 });
|
|
4045
|
-
agentStatus = `running (PID ${child.pid})`;
|
|
4046
|
-
console.log(source_default.green(" \u2713") + ` Agent started (PID ${child.pid}) \u2014 executor "${machName}"`);
|
|
4047
|
-
}
|
|
4048
|
-
} catch (err) {
|
|
4049
|
-
console.log(source_default.yellow(` Could not start agent: ${err.message}`));
|
|
4050
|
-
console.log(source_default.gray(" Start manually with: cva agent --auto-approve"));
|
|
4051
|
-
agentStatus = "not started";
|
|
4052
|
-
}
|
|
4053
|
-
} else {
|
|
4054
|
-
agentStatus = "not started";
|
|
4055
|
-
console.log(source_default.gray(" Start anytime with: cva agent --auto-approve"));
|
|
4056
|
-
}
|
|
4057
3814
|
}
|
|
4058
3815
|
console.log();
|
|
4059
3816
|
console.log(source_default.bold(" Setup Complete"));
|
|
@@ -4061,14 +3818,11 @@ async function runSetup() {
|
|
|
4061
3818
|
console.log(` ${source_default.green("\u2713")} Authenticated as: ${source_default.cyan(username)}`);
|
|
4062
3819
|
console.log(` ${source_default.green("\u2713")} Repository: ${source_default.cyan(repoName)}`);
|
|
4063
3820
|
console.log(` ${source_default.green("\u2713")} CLAUDE.md: present`);
|
|
4064
|
-
if (agentStatus.startsWith("running")) {
|
|
4065
|
-
console.log(` ${source_default.green("\u2713")} Agent daemon: ${source_default.cyan(agentStatus)}`);
|
|
4066
|
-
}
|
|
4067
3821
|
console.log(source_default.gray(" " + "\u2500".repeat(40)));
|
|
4068
3822
|
console.log();
|
|
4069
3823
|
console.log(" What's next:");
|
|
4070
|
-
console.log(
|
|
4071
|
-
console.log(
|
|
3824
|
+
console.log(` ${source_default.cyan("cva agent --auto-approve")} \u2014 Start listening for tasks`);
|
|
3825
|
+
console.log(` Or open Claude.ai and dispatch a task to this repo.`);
|
|
4072
3826
|
console.log();
|
|
4073
3827
|
console.log(source_default.gray(` Dashboard: ${appUrl}`));
|
|
4074
3828
|
console.log();
|
|
@@ -4082,10 +3836,138 @@ function setupCommand() {
|
|
|
4082
3836
|
|
|
4083
3837
|
// src/commands/agent.ts
|
|
4084
3838
|
var import_node_child_process5 = require("node:child_process");
|
|
3839
|
+
var os3 = __toESM(require("node:os"));
|
|
3840
|
+
var path2 = __toESM(require("node:path"));
|
|
3841
|
+
|
|
3842
|
+
// src/utils/event-queue.ts
|
|
3843
|
+
var import_node_fs2 = require("node:fs");
|
|
3844
|
+
var path = __toESM(require("node:path"));
|
|
3845
|
+
var EventQueue = class {
|
|
3846
|
+
buffer = [];
|
|
3847
|
+
sequence = 0;
|
|
3848
|
+
drainPromise = null;
|
|
3849
|
+
closed = false;
|
|
3850
|
+
spillDirty = false;
|
|
3851
|
+
spillPath;
|
|
3852
|
+
poster;
|
|
3853
|
+
maxRetries;
|
|
3854
|
+
baseDelayMs;
|
|
3855
|
+
onError;
|
|
3856
|
+
constructor(opts) {
|
|
3857
|
+
this.spillPath = opts.spillPath;
|
|
3858
|
+
this.poster = opts.poster;
|
|
3859
|
+
this.maxRetries = opts.maxRetries ?? 5;
|
|
3860
|
+
this.baseDelayMs = opts.baseDelayMs ?? 1e3;
|
|
3861
|
+
this.onError = opts.onError;
|
|
3862
|
+
}
|
|
3863
|
+
/** Load any events that were spilled to disk by a previous run. */
|
|
3864
|
+
async loadSpill() {
|
|
3865
|
+
try {
|
|
3866
|
+
const raw = await import_node_fs2.promises.readFile(this.spillPath, "utf8");
|
|
3867
|
+
const lines = raw.split("\n").filter((l) => l.trim().length > 0);
|
|
3868
|
+
for (const line of lines) {
|
|
3869
|
+
const ev = JSON.parse(line);
|
|
3870
|
+
this.buffer.push(ev);
|
|
3871
|
+
if (ev.sequence_number >= this.sequence) {
|
|
3872
|
+
this.sequence = ev.sequence_number + 1;
|
|
3873
|
+
}
|
|
3874
|
+
}
|
|
3875
|
+
} catch (err) {
|
|
3876
|
+
if (err.code !== "ENOENT") throw err;
|
|
3877
|
+
}
|
|
3878
|
+
}
|
|
3879
|
+
/** Assign the next sequence number. Callers that want to stamp manually can read this. */
|
|
3880
|
+
nextSequence() {
|
|
3881
|
+
return this.sequence++;
|
|
3882
|
+
}
|
|
3883
|
+
/** Enqueue an event. Returns the assigned sequence_number. */
|
|
3884
|
+
enqueue(event) {
|
|
3885
|
+
if (this.closed) {
|
|
3886
|
+
throw new Error("EventQueue is closed");
|
|
3887
|
+
}
|
|
3888
|
+
const seq = event.sequence_number ?? this.nextSequence();
|
|
3889
|
+
if (seq >= this.sequence) this.sequence = seq + 1;
|
|
3890
|
+
this.buffer.push({ ...event, sequence_number: seq });
|
|
3891
|
+
this.spillDirty = true;
|
|
3892
|
+
this.drain().catch(() => {
|
|
3893
|
+
});
|
|
3894
|
+
return seq;
|
|
3895
|
+
}
|
|
3896
|
+
/** Drain the buffer, retrying transient failures. Serial — concurrent calls share the same promise. */
|
|
3897
|
+
drain() {
|
|
3898
|
+
if (this.drainPromise) return this.drainPromise;
|
|
3899
|
+
this.drainPromise = this.drainInternal().finally(() => {
|
|
3900
|
+
this.drainPromise = null;
|
|
3901
|
+
});
|
|
3902
|
+
return this.drainPromise;
|
|
3903
|
+
}
|
|
3904
|
+
async drainInternal() {
|
|
3905
|
+
try {
|
|
3906
|
+
while (this.buffer.length > 0) {
|
|
3907
|
+
const ev = this.buffer[0];
|
|
3908
|
+
let delivered = false;
|
|
3909
|
+
for (let attempt = 0; attempt < this.maxRetries; attempt++) {
|
|
3910
|
+
try {
|
|
3911
|
+
await this.poster(ev);
|
|
3912
|
+
delivered = true;
|
|
3913
|
+
break;
|
|
3914
|
+
} catch (err) {
|
|
3915
|
+
this.onError?.(err instanceof Error ? err : new Error(String(err)), ev, attempt);
|
|
3916
|
+
if (attempt < this.maxRetries - 1) {
|
|
3917
|
+
await sleep(this.baseDelayMs * Math.pow(2, attempt));
|
|
3918
|
+
}
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
if (delivered) {
|
|
3922
|
+
this.buffer.shift();
|
|
3923
|
+
this.spillDirty = true;
|
|
3924
|
+
} else {
|
|
3925
|
+
break;
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
} finally {
|
|
3929
|
+
if (this.spillDirty) {
|
|
3930
|
+
await this.writeSpill().catch(() => {
|
|
3931
|
+
});
|
|
3932
|
+
}
|
|
3933
|
+
}
|
|
3934
|
+
}
|
|
3935
|
+
/** Flush: drain remaining events, then persist anything still pending to disk. */
|
|
3936
|
+
async flush() {
|
|
3937
|
+
await this.drain();
|
|
3938
|
+
await this.writeSpill();
|
|
3939
|
+
}
|
|
3940
|
+
/** Mark the queue closed and flush. Safe to call multiple times. */
|
|
3941
|
+
async close() {
|
|
3942
|
+
if (this.closed) return;
|
|
3943
|
+
this.closed = true;
|
|
3944
|
+
await this.flush();
|
|
3945
|
+
}
|
|
3946
|
+
/** Number of events still pending delivery. */
|
|
3947
|
+
size() {
|
|
3948
|
+
return this.buffer.length;
|
|
3949
|
+
}
|
|
3950
|
+
async writeSpill() {
|
|
3951
|
+
try {
|
|
3952
|
+
if (this.buffer.length === 0) {
|
|
3953
|
+
await import_node_fs2.promises.rm(this.spillPath, { force: true });
|
|
3954
|
+
} else {
|
|
3955
|
+
await import_node_fs2.promises.mkdir(path.dirname(this.spillPath), { recursive: true });
|
|
3956
|
+
const body = this.buffer.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
3957
|
+
await import_node_fs2.promises.writeFile(this.spillPath, body, "utf8");
|
|
3958
|
+
}
|
|
3959
|
+
this.spillDirty = false;
|
|
3960
|
+
} catch {
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
};
|
|
3964
|
+
function sleep(ms) {
|
|
3965
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
3966
|
+
}
|
|
4085
3967
|
|
|
4086
3968
|
// src/utils/api.ts
|
|
4087
|
-
async function apiCall(creds, method,
|
|
4088
|
-
const url = `${creds.CV_HUB_API}${
|
|
3969
|
+
async function apiCall(creds, method, path3, body) {
|
|
3970
|
+
const url = `${creds.CV_HUB_API}${path3}`;
|
|
4089
3971
|
const headers = {
|
|
4090
3972
|
"Authorization": `Bearer ${creds.CV_HUB_PAT}`,
|
|
4091
3973
|
"Content-Type": "application/json"
|
|
@@ -4558,78 +4440,11 @@ async function writeConfig(config) {
|
|
|
4558
4440
|
|
|
4559
4441
|
// src/commands/git-safety.ts
|
|
4560
4442
|
var import_node_child_process3 = require("node:child_process");
|
|
4561
|
-
var import_node_fs2 = require("node:fs");
|
|
4562
|
-
var import_node_path2 = require("node:path");
|
|
4563
|
-
var import_node_os3 = require("node:os");
|
|
4564
|
-
var DANGEROUS_PATTERNS = [
|
|
4565
|
-
".claude/",
|
|
4566
|
-
".claude.json",
|
|
4567
|
-
".credentials",
|
|
4568
|
-
".zsh_history",
|
|
4569
|
-
".bash_history",
|
|
4570
|
-
".zsh_sessions/",
|
|
4571
|
-
".ssh/",
|
|
4572
|
-
".gnupg/",
|
|
4573
|
-
".npm/",
|
|
4574
|
-
".config/",
|
|
4575
|
-
".CFUserTextEncoding",
|
|
4576
|
-
"Library/",
|
|
4577
|
-
"Applications/",
|
|
4578
|
-
".Trash/",
|
|
4579
|
-
".DS_Store",
|
|
4580
|
-
"node_modules/",
|
|
4581
|
-
".env",
|
|
4582
|
-
".env.local",
|
|
4583
|
-
".env.production"
|
|
4584
|
-
];
|
|
4585
4443
|
function git(cmd, cwd) {
|
|
4586
4444
|
return (0, import_node_child_process3.execSync)(cmd, { cwd, encoding: "utf8", timeout: 3e4 }).trim();
|
|
4587
4445
|
}
|
|
4588
|
-
function checkWorkspaceSafety(workspaceRoot) {
|
|
4589
|
-
const resolved = (0, import_node_path2.resolve)(workspaceRoot);
|
|
4590
|
-
const home = (0, import_node_path2.resolve)((0, import_node_os3.homedir)());
|
|
4591
|
-
if (resolved === home) {
|
|
4592
|
-
return `Workspace is user HOME directory (${home}). Refusing to auto-commit to prevent indexing personal files.`;
|
|
4593
|
-
}
|
|
4594
|
-
if (home.startsWith(resolved + "/")) {
|
|
4595
|
-
return `Workspace (${resolved}) is a parent of HOME. Refusing to auto-commit.`;
|
|
4596
|
-
}
|
|
4597
|
-
if (!(0, import_node_fs2.existsSync)((0, import_node_path2.join)(resolved, ".git"))) {
|
|
4598
|
-
return `No .git directory in ${resolved}. Not a git repository.`;
|
|
4599
|
-
}
|
|
4600
|
-
return null;
|
|
4601
|
-
}
|
|
4602
|
-
function filterDangerousFiles(statusLines) {
|
|
4603
|
-
const safe = [];
|
|
4604
|
-
const blocked = [];
|
|
4605
|
-
for (const line of statusLines) {
|
|
4606
|
-
const filePath = line.substring(3).trim();
|
|
4607
|
-
const isDangerous = DANGEROUS_PATTERNS.some(
|
|
4608
|
-
(pattern) => filePath.startsWith(pattern) || filePath.includes("/" + pattern)
|
|
4609
|
-
);
|
|
4610
|
-
if (isDangerous) {
|
|
4611
|
-
blocked.push(filePath);
|
|
4612
|
-
} else {
|
|
4613
|
-
safe.push(line);
|
|
4614
|
-
}
|
|
4615
|
-
}
|
|
4616
|
-
return { safe, blocked };
|
|
4617
|
-
}
|
|
4618
4446
|
function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
4619
4447
|
const targetBranch = branch || "main";
|
|
4620
|
-
const safetyError = checkWorkspaceSafety(workspaceRoot);
|
|
4621
|
-
if (safetyError) {
|
|
4622
|
-
console.log(` [git-safety] BLOCKED: ${safetyError}`);
|
|
4623
|
-
return {
|
|
4624
|
-
hadChanges: false,
|
|
4625
|
-
filesAdded: 0,
|
|
4626
|
-
filesModified: 0,
|
|
4627
|
-
filesDeleted: 0,
|
|
4628
|
-
pushed: false,
|
|
4629
|
-
skipped: true,
|
|
4630
|
-
skipReason: safetyError
|
|
4631
|
-
};
|
|
4632
|
-
}
|
|
4633
4448
|
try {
|
|
4634
4449
|
let statusOutput;
|
|
4635
4450
|
try {
|
|
@@ -4637,8 +4452,8 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
|
4637
4452
|
} catch {
|
|
4638
4453
|
return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false, error: "git status failed" };
|
|
4639
4454
|
}
|
|
4640
|
-
const
|
|
4641
|
-
if (
|
|
4455
|
+
const lines = statusOutput.split("\n").filter(Boolean);
|
|
4456
|
+
if (lines.length === 0) {
|
|
4642
4457
|
try {
|
|
4643
4458
|
const unpushed = git(`git log origin/${targetBranch}..HEAD --oneline 2>/dev/null`, workspaceRoot);
|
|
4644
4459
|
if (unpushed) {
|
|
@@ -4650,16 +4465,8 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
|
4650
4465
|
}
|
|
4651
4466
|
return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false };
|
|
4652
4467
|
}
|
|
4653
|
-
const { safe: safeLines, blocked } = filterDangerousFiles(allLines);
|
|
4654
|
-
if (blocked.length > 0) {
|
|
4655
|
-
console.log(` [git-safety] Blocked ${blocked.length} sensitive file(s) from staging: ${blocked.slice(0, 5).join(", ")}${blocked.length > 5 ? "..." : ""}`);
|
|
4656
|
-
}
|
|
4657
|
-
if (safeLines.length === 0) {
|
|
4658
|
-
console.log(` [git-safety] All ${allLines.length} changed files were blocked by safety filter`);
|
|
4659
|
-
return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false };
|
|
4660
|
-
}
|
|
4661
4468
|
let added = 0, modified = 0, deleted = 0;
|
|
4662
|
-
for (const line of
|
|
4469
|
+
for (const line of lines) {
|
|
4663
4470
|
const code = line.substring(0, 2);
|
|
4664
4471
|
if (code.includes("?")) added++;
|
|
4665
4472
|
else if (code.includes("D")) deleted++;
|
|
@@ -4667,15 +4474,9 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
|
4667
4474
|
else added++;
|
|
4668
4475
|
}
|
|
4669
4476
|
console.log(
|
|
4670
|
-
` [git-safety] ${
|
|
4477
|
+
` [git-safety] ${lines.length} uncommitted changes (${added} new, ${modified} modified, ${deleted} deleted) \u2014 committing now`
|
|
4671
4478
|
);
|
|
4672
|
-
|
|
4673
|
-
const filePath = line.substring(3).trim();
|
|
4674
|
-
try {
|
|
4675
|
-
git(`git add -- ${JSON.stringify(filePath)}`, workspaceRoot);
|
|
4676
|
-
} catch {
|
|
4677
|
-
}
|
|
4678
|
-
}
|
|
4479
|
+
git("git add -A", workspaceRoot);
|
|
4679
4480
|
const shortId = taskId.substring(0, 8);
|
|
4680
4481
|
const commitMsg = `task: ${taskTitle} [${shortId}]
|
|
4681
4482
|
|
|
@@ -4737,7 +4538,7 @@ Files: ${added} added, ${modified} modified, ${deleted} deleted`;
|
|
|
4737
4538
|
// src/commands/deploy-manifest.ts
|
|
4738
4539
|
var import_node_child_process4 = require("node:child_process");
|
|
4739
4540
|
var import_node_fs3 = require("node:fs");
|
|
4740
|
-
var
|
|
4541
|
+
var import_node_path2 = require("node:path");
|
|
4741
4542
|
function exec(cmd, cwd, timeoutMs = 3e5, env2) {
|
|
4742
4543
|
try {
|
|
4743
4544
|
const stdout = (0, import_node_child_process4.execSync)(cmd, {
|
|
@@ -4780,7 +4581,7 @@ function getHeadCommit(cwd) {
|
|
|
4780
4581
|
}
|
|
4781
4582
|
}
|
|
4782
4583
|
function loadDeployManifest(workspaceRoot) {
|
|
4783
|
-
const manifestPath = (0,
|
|
4584
|
+
const manifestPath = (0, import_node_path2.join)(workspaceRoot, ".cva", "deploy.json");
|
|
4784
4585
|
if (!(0, import_node_fs3.existsSync)(manifestPath)) return null;
|
|
4785
4586
|
try {
|
|
4786
4587
|
const raw = (0, import_node_fs3.readFileSync)(manifestPath, "utf8");
|
|
@@ -4808,7 +4609,7 @@ async function postTaskDeploy(workspaceRoot, taskId, log) {
|
|
|
4808
4609
|
console.log(` [deploy] Building: ${buildStep.name}`);
|
|
4809
4610
|
log("lifecycle", `Building: ${buildStep.name}`);
|
|
4810
4611
|
const start = Date.now();
|
|
4811
|
-
const cwd = (0,
|
|
4612
|
+
const cwd = (0, import_node_path2.join)(workspaceRoot, buildStep.working_dir || ".");
|
|
4812
4613
|
const timeoutMs = (buildStep.timeout_seconds || 300) * 1e3;
|
|
4813
4614
|
const result = exec(buildStep.command, cwd, timeoutMs);
|
|
4814
4615
|
if (!result.ok) {
|
|
@@ -4899,7 +4700,7 @@ async function rollback(workspaceRoot, targetCommit, manifest, log) {
|
|
|
4899
4700
|
}
|
|
4900
4701
|
if (manifest.build?.steps) {
|
|
4901
4702
|
for (const step of manifest.build.steps) {
|
|
4902
|
-
exec(step.command, (0,
|
|
4703
|
+
exec(step.command, (0, import_node_path2.join)(workspaceRoot, step.working_dir || "."), (step.timeout_seconds || 300) * 1e3);
|
|
4903
4704
|
}
|
|
4904
4705
|
}
|
|
4905
4706
|
if (manifest.service?.restart_command) {
|
|
@@ -5134,8 +4935,9 @@ async function launchAutoApproveMode(prompt, options) {
|
|
|
5134
4935
|
const pendingQuestionIds = [];
|
|
5135
4936
|
let fullOutput = "";
|
|
5136
4937
|
let lastProgressBytes = 0;
|
|
4938
|
+
let totalBytesStreamed = 0;
|
|
5137
4939
|
const runOnce = (inputPrompt, isContinue) => {
|
|
5138
|
-
return new Promise((
|
|
4940
|
+
return new Promise((resolve, reject) => {
|
|
5139
4941
|
const args = isContinue ? ["-p", inputPrompt, "--continue", "--allowedTools", ...ALLOWED_TOOLS] : ["-p", inputPrompt, "--allowedTools", ...ALLOWED_TOOLS];
|
|
5140
4942
|
if (sessionId && !isContinue) {
|
|
5141
4943
|
args.push("--session-id", sessionId);
|
|
@@ -5155,8 +4957,20 @@ async function launchAutoApproveMode(prompt, options) {
|
|
|
5155
4957
|
process.stdout.write(data);
|
|
5156
4958
|
fullOutput += text;
|
|
5157
4959
|
if (fullOutput.length > MAX_OUTPUT_BYTES) {
|
|
4960
|
+
const dropped = fullOutput.length - MAX_OUTPUT_BYTES;
|
|
5158
4961
|
fullOutput = fullOutput.slice(-MAX_OUTPUT_BYTES);
|
|
4962
|
+
options.eventQueue?.enqueue({
|
|
4963
|
+
event_type: "output",
|
|
4964
|
+
content: {
|
|
4965
|
+
chunk: `
|
|
4966
|
+
[... ${dropped} bytes truncated from output buffer ...]
|
|
4967
|
+
`,
|
|
4968
|
+
truncated_bytes: dropped,
|
|
4969
|
+
byte_offset: totalBytesStreamed
|
|
4970
|
+
}
|
|
4971
|
+
});
|
|
5159
4972
|
}
|
|
4973
|
+
totalBytesStreamed += text.length;
|
|
5160
4974
|
if (Date.now() - spawnTime < 1e4) {
|
|
5161
4975
|
const authError = containsAuthError(fullOutput + stderr);
|
|
5162
4976
|
if (authError) {
|
|
@@ -5207,10 +5021,9 @@ ${source_default.red("!")} Claude Code auth failure detected: "${authError}"`);
|
|
|
5207
5021
|
).catch(() => {
|
|
5208
5022
|
});
|
|
5209
5023
|
}
|
|
5210
|
-
|
|
5024
|
+
options.eventQueue?.enqueue({
|
|
5211
5025
|
event_type: "output",
|
|
5212
|
-
content: { chunk, byte_offset:
|
|
5213
|
-
}).catch(() => {
|
|
5026
|
+
content: { chunk, byte_offset: totalBytesStreamed }
|
|
5214
5027
|
});
|
|
5215
5028
|
}
|
|
5216
5029
|
}
|
|
@@ -5234,7 +5047,7 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
|
|
|
5234
5047
|
});
|
|
5235
5048
|
child.on("close", (code, signal) => {
|
|
5236
5049
|
_activeChild = null;
|
|
5237
|
-
|
|
5050
|
+
resolve({
|
|
5238
5051
|
exitCode: signal === "SIGKILL" ? 137 : code ?? 1,
|
|
5239
5052
|
stderr,
|
|
5240
5053
|
output: fullOutput,
|
|
@@ -5273,7 +5086,7 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
|
|
|
5273
5086
|
return result;
|
|
5274
5087
|
}
|
|
5275
5088
|
async function launchRelayMode(prompt, options) {
|
|
5276
|
-
return new Promise((
|
|
5089
|
+
return new Promise((resolve, reject) => {
|
|
5277
5090
|
const child = (0, import_node_child_process5.spawn)("claude", [], {
|
|
5278
5091
|
cwd: options.cwd,
|
|
5279
5092
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -5286,6 +5099,7 @@ async function launchRelayMode(prompt, options) {
|
|
|
5286
5099
|
let lastProgressBytes = 0;
|
|
5287
5100
|
let authFailure = false;
|
|
5288
5101
|
const spawnTime = Date.now();
|
|
5102
|
+
let totalBytesStreamed = 0;
|
|
5289
5103
|
child.stdin?.write(prompt + "\n");
|
|
5290
5104
|
let lastRedirectCheck = Date.now();
|
|
5291
5105
|
let lineBuffer = "";
|
|
@@ -5295,8 +5109,20 @@ async function launchRelayMode(prompt, options) {
|
|
|
5295
5109
|
stdoutBuffer += text;
|
|
5296
5110
|
fullOutput += text;
|
|
5297
5111
|
if (fullOutput.length > MAX_OUTPUT_BYTES) {
|
|
5112
|
+
const dropped = fullOutput.length - MAX_OUTPUT_BYTES;
|
|
5298
5113
|
fullOutput = fullOutput.slice(-MAX_OUTPUT_BYTES);
|
|
5114
|
+
options.eventQueue?.enqueue({
|
|
5115
|
+
event_type: "output",
|
|
5116
|
+
content: {
|
|
5117
|
+
chunk: `
|
|
5118
|
+
[... ${dropped} bytes truncated from output buffer ...]
|
|
5119
|
+
`,
|
|
5120
|
+
truncated_bytes: dropped,
|
|
5121
|
+
byte_offset: totalBytesStreamed
|
|
5122
|
+
}
|
|
5123
|
+
});
|
|
5299
5124
|
}
|
|
5125
|
+
totalBytesStreamed += text.length;
|
|
5300
5126
|
if (Date.now() - spawnTime < 1e4 && !authFailure) {
|
|
5301
5127
|
const authError = containsAuthError(fullOutput + stderr);
|
|
5302
5128
|
if (authError) {
|
|
@@ -5355,10 +5181,9 @@ ${source_default.red("!")} Claude Code auth failure detected: "${authError}"`);
|
|
|
5355
5181
|
{ output_chunk: chunk }
|
|
5356
5182
|
).catch(() => {
|
|
5357
5183
|
});
|
|
5358
|
-
|
|
5184
|
+
options.eventQueue?.enqueue({
|
|
5359
5185
|
event_type: "output",
|
|
5360
|
-
content: { chunk, byte_offset:
|
|
5361
|
-
}).catch(() => {
|
|
5186
|
+
content: { chunk, byte_offset: totalBytesStreamed }
|
|
5362
5187
|
});
|
|
5363
5188
|
}
|
|
5364
5189
|
if (Date.now() - lastRedirectCheck > 1e4) {
|
|
@@ -5466,9 +5291,9 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
|
|
|
5466
5291
|
child.on("close", (code, signal) => {
|
|
5467
5292
|
_activeChild = null;
|
|
5468
5293
|
if (signal === "SIGKILL") {
|
|
5469
|
-
|
|
5294
|
+
resolve({ exitCode: 137, stderr, output: fullOutput, authFailure });
|
|
5470
5295
|
} else {
|
|
5471
|
-
|
|
5296
|
+
resolve({ exitCode: code ?? 1, stderr, output: fullOutput, authFailure });
|
|
5472
5297
|
}
|
|
5473
5298
|
});
|
|
5474
5299
|
child.on("error", (err) => {
|
|
@@ -5569,45 +5394,14 @@ async function runAgent(options) {
|
|
|
5569
5394
|
console.log();
|
|
5570
5395
|
process.exit(1);
|
|
5571
5396
|
}
|
|
5572
|
-
if (process.env.CV_DEBUG) {
|
|
5573
|
-
console.log(source_default.gray(` PATH: ${process.env.PATH}`));
|
|
5574
|
-
console.log(source_default.gray(` HOME: ${process.env.HOME}`));
|
|
5575
|
-
console.log(source_default.gray(` SHELL: ${process.env.SHELL}`));
|
|
5576
|
-
}
|
|
5577
|
-
let claudeBinary = "claude";
|
|
5578
5397
|
try {
|
|
5579
5398
|
(0, import_node_child_process5.execSync)("claude --version", { stdio: "pipe", timeout: 5e3 });
|
|
5580
5399
|
} catch {
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
];
|
|
5587
|
-
let found = false;
|
|
5588
|
-
for (const candidate of candidates) {
|
|
5589
|
-
try {
|
|
5590
|
-
const resolved = (0, import_node_child_process5.execSync)(`ls ${candidate} 2>/dev/null | head -1`, { encoding: "utf8", timeout: 3e3 }).trim();
|
|
5591
|
-
if (resolved) {
|
|
5592
|
-
(0, import_node_child_process5.execSync)(`${resolved} --version`, { stdio: "pipe", timeout: 5e3 });
|
|
5593
|
-
claudeBinary = resolved;
|
|
5594
|
-
found = true;
|
|
5595
|
-
console.log(source_default.yellow("!") + ` Claude Code found at ${resolved} (not on PATH)`);
|
|
5596
|
-
break;
|
|
5597
|
-
}
|
|
5598
|
-
} catch {
|
|
5599
|
-
}
|
|
5600
|
-
}
|
|
5601
|
-
if (!found) {
|
|
5602
|
-
console.log();
|
|
5603
|
-
console.log(source_default.red("Claude Code CLI not found.") + " Install it first:");
|
|
5604
|
-
console.log(` ${source_default.cyan("npm install -g @anthropic-ai/claude-code")}`);
|
|
5605
|
-
console.log();
|
|
5606
|
-
console.log(source_default.gray(` Current PATH: ${process.env.PATH}`));
|
|
5607
|
-
console.log(source_default.gray(" If running via Launch Agent, ensure PATH includes the directory where claude is installed."));
|
|
5608
|
-
console.log();
|
|
5609
|
-
process.exit(1);
|
|
5610
|
-
}
|
|
5400
|
+
console.log();
|
|
5401
|
+
console.log(source_default.red("Claude Code CLI not found.") + " Install it first:");
|
|
5402
|
+
console.log(` ${source_default.cyan("npm install -g @anthropic-ai/claude-code")}`);
|
|
5403
|
+
console.log();
|
|
5404
|
+
process.exit(1);
|
|
5611
5405
|
}
|
|
5612
5406
|
const { env: claudeEnv, usingApiKey } = await getClaudeEnv();
|
|
5613
5407
|
const authCheck = await checkClaudeAuth();
|
|
@@ -5648,64 +5442,6 @@ async function runAgent(options) {
|
|
|
5648
5442
|
console.log();
|
|
5649
5443
|
}
|
|
5650
5444
|
}
|
|
5651
|
-
{
|
|
5652
|
-
const isGitRepo = require("fs").existsSync(require("path").join(workingDir, ".git"));
|
|
5653
|
-
if (!isGitRepo) {
|
|
5654
|
-
console.log(source_default.gray(" Bootstrap: initializing git repo..."));
|
|
5655
|
-
try {
|
|
5656
|
-
(0, import_node_child_process5.execSync)("git init && git checkout -b main", { cwd: workingDir, stdio: "pipe" });
|
|
5657
|
-
} catch {
|
|
5658
|
-
}
|
|
5659
|
-
}
|
|
5660
|
-
const claudeMdPath = require("path").join(workingDir, "CLAUDE.md");
|
|
5661
|
-
if (!require("fs").existsSync(claudeMdPath)) {
|
|
5662
|
-
const name = require("path").basename(workingDir);
|
|
5663
|
-
const template = `# ${name}
|
|
5664
|
-
|
|
5665
|
-
## Overview
|
|
5666
|
-
[Describe your project here]
|
|
5667
|
-
|
|
5668
|
-
## Build & Run
|
|
5669
|
-
[How to build and run this project]
|
|
5670
|
-
`;
|
|
5671
|
-
try {
|
|
5672
|
-
require("fs").writeFileSync(claudeMdPath, template);
|
|
5673
|
-
console.log(source_default.gray(" Bootstrap: created CLAUDE.md"));
|
|
5674
|
-
} catch {
|
|
5675
|
-
}
|
|
5676
|
-
}
|
|
5677
|
-
const cvDir = require("path").join(workingDir, ".cv");
|
|
5678
|
-
if (!require("fs").existsSync(cvDir)) {
|
|
5679
|
-
try {
|
|
5680
|
-
require("fs").mkdirSync(cvDir, { recursive: true });
|
|
5681
|
-
} catch {
|
|
5682
|
-
}
|
|
5683
|
-
}
|
|
5684
|
-
if (creds.CV_HUB_PAT) {
|
|
5685
|
-
try {
|
|
5686
|
-
const existing = (0, import_node_child_process5.execSync)('git remote get-url cv-hub 2>/dev/null || echo ""', {
|
|
5687
|
-
cwd: workingDir,
|
|
5688
|
-
encoding: "utf8",
|
|
5689
|
-
timeout: 5e3
|
|
5690
|
-
}).trim();
|
|
5691
|
-
if (!existing) {
|
|
5692
|
-
const name = require("path").basename(workingDir);
|
|
5693
|
-
const gitHost = "git.hub.controlvector.io";
|
|
5694
|
-
let user = "user";
|
|
5695
|
-
try {
|
|
5696
|
-
const sharedPath = require("path").join(require("os").homedir(), ".config", "controlvector", "credentials.json");
|
|
5697
|
-
const shared = JSON.parse(require("fs").readFileSync(sharedPath, "utf-8"));
|
|
5698
|
-
if (shared.username) user = shared.username;
|
|
5699
|
-
} catch {
|
|
5700
|
-
}
|
|
5701
|
-
const remoteUrl = `https://${gitHost}/${user}/${name}.git`;
|
|
5702
|
-
(0, import_node_child_process5.execSync)(`git remote add cv-hub ${remoteUrl}`, { cwd: workingDir, stdio: "pipe" });
|
|
5703
|
-
console.log(source_default.gray(` Bootstrap: remote cv-hub \u2192 ${remoteUrl}`));
|
|
5704
|
-
}
|
|
5705
|
-
} catch {
|
|
5706
|
-
}
|
|
5707
|
-
}
|
|
5708
|
-
}
|
|
5709
5445
|
let detectedRepoId;
|
|
5710
5446
|
try {
|
|
5711
5447
|
const remoteUrl = (0, import_node_child_process5.execSync)("git remote get-url origin 2>/dev/null", {
|
|
@@ -5889,6 +5625,14 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
5889
5625
|
console.log(source_default.gray(` Git remote: ${remoteInfo.remoteName} -> ${remoteInfo.remoteUrl}`));
|
|
5890
5626
|
}
|
|
5891
5627
|
const prompt = buildClaudePrompt(task);
|
|
5628
|
+
const spillPath = path2.join(os3.tmpdir(), "cva-event-queue", `${task.id}.ndjson`);
|
|
5629
|
+
const eventQueue = new EventQueue({
|
|
5630
|
+
spillPath,
|
|
5631
|
+
poster: async (event) => {
|
|
5632
|
+
await postTaskEvent(creds, task.id, event);
|
|
5633
|
+
}
|
|
5634
|
+
});
|
|
5635
|
+
await eventQueue.loadSpill();
|
|
5892
5636
|
try {
|
|
5893
5637
|
const mode = options.autoApprove ? "auto-approve" : "relay";
|
|
5894
5638
|
console.log(`\u{1F680} ${source_default.bold.green("RUNNING")} \u2014 Launching Claude Code (${mode})...`);
|
|
@@ -5904,7 +5648,8 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
5904
5648
|
taskId: task.id,
|
|
5905
5649
|
executorId: state.executorId,
|
|
5906
5650
|
spawnEnv: claudeEnv,
|
|
5907
|
-
machineName: state.machineName
|
|
5651
|
+
machineName: state.machineName,
|
|
5652
|
+
eventQueue
|
|
5908
5653
|
});
|
|
5909
5654
|
} else {
|
|
5910
5655
|
result = await launchRelayMode(prompt, {
|
|
@@ -5913,7 +5658,8 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
5913
5658
|
executorId: state.executorId,
|
|
5914
5659
|
taskId: task.id,
|
|
5915
5660
|
spawnEnv: claudeEnv,
|
|
5916
|
-
machineName: state.machineName
|
|
5661
|
+
machineName: state.machineName,
|
|
5662
|
+
eventQueue
|
|
5917
5663
|
});
|
|
5918
5664
|
}
|
|
5919
5665
|
console.log(source_default.gray("\n" + "-".repeat(60)));
|
|
@@ -5974,14 +5720,13 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
5974
5720
|
sendTaskLog(creds, state.executorId, task.id, "info", `Deploy manifest error: ${deployErr.message}`);
|
|
5975
5721
|
}
|
|
5976
5722
|
}
|
|
5977
|
-
|
|
5723
|
+
eventQueue.enqueue({
|
|
5978
5724
|
event_type: "completed",
|
|
5979
5725
|
content: {
|
|
5980
5726
|
exit_code: result.exitCode,
|
|
5981
5727
|
duration_seconds: Math.round((Date.now() - startTime) / 1e3),
|
|
5982
5728
|
files_changed: allChangedFiles.length
|
|
5983
5729
|
}
|
|
5984
|
-
}).catch(() => {
|
|
5985
5730
|
});
|
|
5986
5731
|
if (result.exitCode === 0) {
|
|
5987
5732
|
if (allChangedFiles.length > 0) {
|
|
@@ -6006,14 +5751,13 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
6006
5751
|
console.log();
|
|
6007
5752
|
console.log(`\u2705 ${source_default.bold.green("COMPLETED")} \u2014 Duration: ${elapsed}`);
|
|
6008
5753
|
printBanner("COMPLETED", elapsed, allChangedFiles, postGitState.headSha);
|
|
6009
|
-
|
|
5754
|
+
eventQueue.enqueue({
|
|
6010
5755
|
event_type: "output_final",
|
|
6011
5756
|
content: {
|
|
6012
5757
|
output: result.output.slice(-MAX_OUTPUT_FINAL_BYTES),
|
|
6013
5758
|
exit_code: result.exitCode,
|
|
6014
5759
|
duration_seconds: Math.round((Date.now() - startTime) / 1e3)
|
|
6015
5760
|
}
|
|
6016
|
-
}).catch(() => {
|
|
6017
5761
|
});
|
|
6018
5762
|
await withRetry(
|
|
6019
5763
|
() => completeTask(creds, state.executorId, task.id, payload),
|
|
@@ -6102,6 +5846,10 @@ ${source_default.red("!")} Task error: ${err.message}`);
|
|
|
6102
5846
|
} finally {
|
|
6103
5847
|
clearInterval(heartbeatTimer);
|
|
6104
5848
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
5849
|
+
try {
|
|
5850
|
+
await eventQueue.close();
|
|
5851
|
+
} catch {
|
|
5852
|
+
}
|
|
6105
5853
|
state.currentTaskId = null;
|
|
6106
5854
|
state.lastTaskEnd = Date.now();
|
|
6107
5855
|
}
|
|
@@ -6161,7 +5909,7 @@ async function promptForToken() {
|
|
|
6161
5909
|
input: process.stdin,
|
|
6162
5910
|
output: process.stdout
|
|
6163
5911
|
});
|
|
6164
|
-
return new Promise((
|
|
5912
|
+
return new Promise((resolve) => {
|
|
6165
5913
|
rl.question("Enter your CV-Hub PAT token: ", (answer) => {
|
|
6166
5914
|
rl.close();
|
|
6167
5915
|
const token = answer.trim();
|
|
@@ -6169,7 +5917,7 @@ async function promptForToken() {
|
|
|
6169
5917
|
console.log(source_default.red("No token provided."));
|
|
6170
5918
|
process.exit(1);
|
|
6171
5919
|
}
|
|
6172
|
-
|
|
5920
|
+
resolve(token);
|
|
6173
5921
|
});
|
|
6174
5922
|
});
|
|
6175
5923
|
}
|
|
@@ -6448,7 +6196,7 @@ function statusCommand() {
|
|
|
6448
6196
|
|
|
6449
6197
|
// src/index.ts
|
|
6450
6198
|
var program2 = new Command();
|
|
6451
|
-
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.
|
|
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.0" : "1.6.0");
|
|
6452
6200
|
program2.addCommand(setupCommand());
|
|
6453
6201
|
program2.addCommand(agentCommand());
|
|
6454
6202
|
program2.addCommand(authCommand());
|