@controlvector/cv-agent 1.9.2 → 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 -574
- package/dist/bundle.cjs.map +4 -4
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +104 -133
- 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 -361
- 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
|
|
|
@@ -3929,41 +3795,6 @@ async function runSetup() {
|
|
|
3929
3795
|
} else {
|
|
3930
3796
|
console.log(source_default.green(" \u2713") + " CLAUDE.md present");
|
|
3931
3797
|
}
|
|
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
|
-
}
|
|
3967
3798
|
if (!hasCVDir) {
|
|
3968
3799
|
(0, import_node_fs.mkdirSync)((0, import_node_path.join)(cwd, ".cv"), { recursive: true });
|
|
3969
3800
|
}
|
|
@@ -3973,22 +3804,6 @@ async function runSetup() {
|
|
|
3973
3804
|
try {
|
|
3974
3805
|
const existingRemote = (0, import_node_child_process.execSync)('git remote get-url cv-hub 2>/dev/null || echo ""', { cwd, encoding: "utf8" }).trim();
|
|
3975
3806
|
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
|
-
}
|
|
3992
3807
|
(0, import_node_child_process.execSync)(`git remote add cv-hub ${remoteUrl}`, { cwd, stdio: "pipe" });
|
|
3993
3808
|
console.log(source_default.green(" \u2713") + ` Remote added: cv-hub \u2192 ${remoteUrl}`);
|
|
3994
3809
|
} else {
|
|
@@ -3996,99 +3811,6 @@ async function runSetup() {
|
|
|
3996
3811
|
}
|
|
3997
3812
|
} catch {
|
|
3998
3813
|
}
|
|
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
|
-
}
|
|
4092
3814
|
}
|
|
4093
3815
|
console.log();
|
|
4094
3816
|
console.log(source_default.bold(" Setup Complete"));
|
|
@@ -4096,14 +3818,11 @@ async function runSetup() {
|
|
|
4096
3818
|
console.log(` ${source_default.green("\u2713")} Authenticated as: ${source_default.cyan(username)}`);
|
|
4097
3819
|
console.log(` ${source_default.green("\u2713")} Repository: ${source_default.cyan(repoName)}`);
|
|
4098
3820
|
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
|
-
}
|
|
4102
3821
|
console.log(source_default.gray(" " + "\u2500".repeat(40)));
|
|
4103
3822
|
console.log();
|
|
4104
3823
|
console.log(" What's next:");
|
|
4105
|
-
console.log(
|
|
4106
|
-
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.`);
|
|
4107
3826
|
console.log();
|
|
4108
3827
|
console.log(source_default.gray(` Dashboard: ${appUrl}`));
|
|
4109
3828
|
console.log();
|
|
@@ -4117,10 +3836,138 @@ function setupCommand() {
|
|
|
4117
3836
|
|
|
4118
3837
|
// src/commands/agent.ts
|
|
4119
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
|
+
}
|
|
4120
3967
|
|
|
4121
3968
|
// src/utils/api.ts
|
|
4122
|
-
async function apiCall(creds, method,
|
|
4123
|
-
const url = `${creds.CV_HUB_API}${
|
|
3969
|
+
async function apiCall(creds, method, path3, body) {
|
|
3970
|
+
const url = `${creds.CV_HUB_API}${path3}`;
|
|
4124
3971
|
const headers = {
|
|
4125
3972
|
"Authorization": `Bearer ${creds.CV_HUB_PAT}`,
|
|
4126
3973
|
"Content-Type": "application/json"
|
|
@@ -4593,78 +4440,11 @@ async function writeConfig(config) {
|
|
|
4593
4440
|
|
|
4594
4441
|
// src/commands/git-safety.ts
|
|
4595
4442
|
var import_node_child_process3 = require("node:child_process");
|
|
4596
|
-
var import_node_fs2 = require("node:fs");
|
|
4597
|
-
var import_node_path2 = require("node:path");
|
|
4598
|
-
var import_node_os3 = require("node:os");
|
|
4599
|
-
var DANGEROUS_PATTERNS = [
|
|
4600
|
-
".claude/",
|
|
4601
|
-
".claude.json",
|
|
4602
|
-
".credentials",
|
|
4603
|
-
".zsh_history",
|
|
4604
|
-
".bash_history",
|
|
4605
|
-
".zsh_sessions/",
|
|
4606
|
-
".ssh/",
|
|
4607
|
-
".gnupg/",
|
|
4608
|
-
".npm/",
|
|
4609
|
-
".config/",
|
|
4610
|
-
".CFUserTextEncoding",
|
|
4611
|
-
"Library/",
|
|
4612
|
-
"Applications/",
|
|
4613
|
-
".Trash/",
|
|
4614
|
-
".DS_Store",
|
|
4615
|
-
"node_modules/",
|
|
4616
|
-
".env",
|
|
4617
|
-
".env.local",
|
|
4618
|
-
".env.production"
|
|
4619
|
-
];
|
|
4620
4443
|
function git(cmd, cwd) {
|
|
4621
4444
|
return (0, import_node_child_process3.execSync)(cmd, { cwd, encoding: "utf8", timeout: 3e4 }).trim();
|
|
4622
4445
|
}
|
|
4623
|
-
function checkWorkspaceSafety(workspaceRoot) {
|
|
4624
|
-
const resolved = (0, import_node_path2.resolve)(workspaceRoot);
|
|
4625
|
-
const home = (0, import_node_path2.resolve)((0, import_node_os3.homedir)());
|
|
4626
|
-
if (resolved === home) {
|
|
4627
|
-
return `Workspace is user HOME directory (${home}). Refusing to auto-commit to prevent indexing personal files.`;
|
|
4628
|
-
}
|
|
4629
|
-
if (home.startsWith(resolved + "/")) {
|
|
4630
|
-
return `Workspace (${resolved}) is a parent of HOME. Refusing to auto-commit.`;
|
|
4631
|
-
}
|
|
4632
|
-
if (!(0, import_node_fs2.existsSync)((0, import_node_path2.join)(resolved, ".git"))) {
|
|
4633
|
-
return `No .git directory in ${resolved}. Not a git repository.`;
|
|
4634
|
-
}
|
|
4635
|
-
return null;
|
|
4636
|
-
}
|
|
4637
|
-
function filterDangerousFiles(statusLines) {
|
|
4638
|
-
const safe = [];
|
|
4639
|
-
const blocked = [];
|
|
4640
|
-
for (const line of statusLines) {
|
|
4641
|
-
const filePath = line.substring(3).trim();
|
|
4642
|
-
const isDangerous = DANGEROUS_PATTERNS.some(
|
|
4643
|
-
(pattern) => filePath.startsWith(pattern) || filePath.includes("/" + pattern)
|
|
4644
|
-
);
|
|
4645
|
-
if (isDangerous) {
|
|
4646
|
-
blocked.push(filePath);
|
|
4647
|
-
} else {
|
|
4648
|
-
safe.push(line);
|
|
4649
|
-
}
|
|
4650
|
-
}
|
|
4651
|
-
return { safe, blocked };
|
|
4652
|
-
}
|
|
4653
4446
|
function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
4654
4447
|
const targetBranch = branch || "main";
|
|
4655
|
-
const safetyError = checkWorkspaceSafety(workspaceRoot);
|
|
4656
|
-
if (safetyError) {
|
|
4657
|
-
console.log(` [git-safety] BLOCKED: ${safetyError}`);
|
|
4658
|
-
return {
|
|
4659
|
-
hadChanges: false,
|
|
4660
|
-
filesAdded: 0,
|
|
4661
|
-
filesModified: 0,
|
|
4662
|
-
filesDeleted: 0,
|
|
4663
|
-
pushed: false,
|
|
4664
|
-
skipped: true,
|
|
4665
|
-
skipReason: safetyError
|
|
4666
|
-
};
|
|
4667
|
-
}
|
|
4668
4448
|
try {
|
|
4669
4449
|
let statusOutput;
|
|
4670
4450
|
try {
|
|
@@ -4672,8 +4452,8 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
|
4672
4452
|
} catch {
|
|
4673
4453
|
return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false, error: "git status failed" };
|
|
4674
4454
|
}
|
|
4675
|
-
const
|
|
4676
|
-
if (
|
|
4455
|
+
const lines = statusOutput.split("\n").filter(Boolean);
|
|
4456
|
+
if (lines.length === 0) {
|
|
4677
4457
|
try {
|
|
4678
4458
|
const unpushed = git(`git log origin/${targetBranch}..HEAD --oneline 2>/dev/null`, workspaceRoot);
|
|
4679
4459
|
if (unpushed) {
|
|
@@ -4685,16 +4465,8 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
|
4685
4465
|
}
|
|
4686
4466
|
return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false };
|
|
4687
4467
|
}
|
|
4688
|
-
const { safe: safeLines, blocked } = filterDangerousFiles(allLines);
|
|
4689
|
-
if (blocked.length > 0) {
|
|
4690
|
-
console.log(` [git-safety] Blocked ${blocked.length} sensitive file(s) from staging: ${blocked.slice(0, 5).join(", ")}${blocked.length > 5 ? "..." : ""}`);
|
|
4691
|
-
}
|
|
4692
|
-
if (safeLines.length === 0) {
|
|
4693
|
-
console.log(` [git-safety] All ${allLines.length} changed files were blocked by safety filter`);
|
|
4694
|
-
return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false };
|
|
4695
|
-
}
|
|
4696
4468
|
let added = 0, modified = 0, deleted = 0;
|
|
4697
|
-
for (const line of
|
|
4469
|
+
for (const line of lines) {
|
|
4698
4470
|
const code = line.substring(0, 2);
|
|
4699
4471
|
if (code.includes("?")) added++;
|
|
4700
4472
|
else if (code.includes("D")) deleted++;
|
|
@@ -4702,15 +4474,9 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
|
|
|
4702
4474
|
else added++;
|
|
4703
4475
|
}
|
|
4704
4476
|
console.log(
|
|
4705
|
-
` [git-safety] ${
|
|
4477
|
+
` [git-safety] ${lines.length} uncommitted changes (${added} new, ${modified} modified, ${deleted} deleted) \u2014 committing now`
|
|
4706
4478
|
);
|
|
4707
|
-
|
|
4708
|
-
const filePath = line.substring(3).trim();
|
|
4709
|
-
try {
|
|
4710
|
-
git(`git add -- ${JSON.stringify(filePath)}`, workspaceRoot);
|
|
4711
|
-
} catch {
|
|
4712
|
-
}
|
|
4713
|
-
}
|
|
4479
|
+
git("git add -A", workspaceRoot);
|
|
4714
4480
|
const shortId = taskId.substring(0, 8);
|
|
4715
4481
|
const commitMsg = `task: ${taskTitle} [${shortId}]
|
|
4716
4482
|
|
|
@@ -4772,7 +4538,7 @@ Files: ${added} added, ${modified} modified, ${deleted} deleted`;
|
|
|
4772
4538
|
// src/commands/deploy-manifest.ts
|
|
4773
4539
|
var import_node_child_process4 = require("node:child_process");
|
|
4774
4540
|
var import_node_fs3 = require("node:fs");
|
|
4775
|
-
var
|
|
4541
|
+
var import_node_path2 = require("node:path");
|
|
4776
4542
|
function exec(cmd, cwd, timeoutMs = 3e5, env2) {
|
|
4777
4543
|
try {
|
|
4778
4544
|
const stdout = (0, import_node_child_process4.execSync)(cmd, {
|
|
@@ -4815,7 +4581,7 @@ function getHeadCommit(cwd) {
|
|
|
4815
4581
|
}
|
|
4816
4582
|
}
|
|
4817
4583
|
function loadDeployManifest(workspaceRoot) {
|
|
4818
|
-
const manifestPath = (0,
|
|
4584
|
+
const manifestPath = (0, import_node_path2.join)(workspaceRoot, ".cva", "deploy.json");
|
|
4819
4585
|
if (!(0, import_node_fs3.existsSync)(manifestPath)) return null;
|
|
4820
4586
|
try {
|
|
4821
4587
|
const raw = (0, import_node_fs3.readFileSync)(manifestPath, "utf8");
|
|
@@ -4843,7 +4609,7 @@ async function postTaskDeploy(workspaceRoot, taskId, log) {
|
|
|
4843
4609
|
console.log(` [deploy] Building: ${buildStep.name}`);
|
|
4844
4610
|
log("lifecycle", `Building: ${buildStep.name}`);
|
|
4845
4611
|
const start = Date.now();
|
|
4846
|
-
const cwd = (0,
|
|
4612
|
+
const cwd = (0, import_node_path2.join)(workspaceRoot, buildStep.working_dir || ".");
|
|
4847
4613
|
const timeoutMs = (buildStep.timeout_seconds || 300) * 1e3;
|
|
4848
4614
|
const result = exec(buildStep.command, cwd, timeoutMs);
|
|
4849
4615
|
if (!result.ok) {
|
|
@@ -4934,7 +4700,7 @@ async function rollback(workspaceRoot, targetCommit, manifest, log) {
|
|
|
4934
4700
|
}
|
|
4935
4701
|
if (manifest.build?.steps) {
|
|
4936
4702
|
for (const step of manifest.build.steps) {
|
|
4937
|
-
exec(step.command, (0,
|
|
4703
|
+
exec(step.command, (0, import_node_path2.join)(workspaceRoot, step.working_dir || "."), (step.timeout_seconds || 300) * 1e3);
|
|
4938
4704
|
}
|
|
4939
4705
|
}
|
|
4940
4706
|
if (manifest.service?.restart_command) {
|
|
@@ -5092,22 +4858,6 @@ Target repository: ${task.owner}/${task.repo}
|
|
|
5092
4858
|
`;
|
|
5093
4859
|
prompt += `
|
|
5094
4860
|
|
|
5095
|
-
## Security \u2014 NEVER Commit Secrets
|
|
5096
|
-
`;
|
|
5097
|
-
prompt += `Do NOT add, commit, or push any of the following:
|
|
5098
|
-
`;
|
|
5099
|
-
prompt += `- API keys, tokens, passwords, or credentials
|
|
5100
|
-
`;
|
|
5101
|
-
prompt += `- .env files, .credentials files, .claude/ directory
|
|
5102
|
-
`;
|
|
5103
|
-
prompt += `- SSH keys (.ssh/), GPG keys (.gnupg/)
|
|
5104
|
-
`;
|
|
5105
|
-
prompt += `- Shell history (.zsh_history, .bash_history)
|
|
5106
|
-
`;
|
|
5107
|
-
prompt += `If you need to reference an API key, use an environment variable placeholder.
|
|
5108
|
-
`;
|
|
5109
|
-
prompt += `
|
|
5110
|
-
|
|
5111
4861
|
---
|
|
5112
4862
|
`;
|
|
5113
4863
|
prompt += `When complete, provide a brief summary of what you accomplished.
|
|
@@ -5185,8 +4935,9 @@ async function launchAutoApproveMode(prompt, options) {
|
|
|
5185
4935
|
const pendingQuestionIds = [];
|
|
5186
4936
|
let fullOutput = "";
|
|
5187
4937
|
let lastProgressBytes = 0;
|
|
4938
|
+
let totalBytesStreamed = 0;
|
|
5188
4939
|
const runOnce = (inputPrompt, isContinue) => {
|
|
5189
|
-
return new Promise((
|
|
4940
|
+
return new Promise((resolve, reject) => {
|
|
5190
4941
|
const args = isContinue ? ["-p", inputPrompt, "--continue", "--allowedTools", ...ALLOWED_TOOLS] : ["-p", inputPrompt, "--allowedTools", ...ALLOWED_TOOLS];
|
|
5191
4942
|
if (sessionId && !isContinue) {
|
|
5192
4943
|
args.push("--session-id", sessionId);
|
|
@@ -5206,8 +4957,20 @@ async function launchAutoApproveMode(prompt, options) {
|
|
|
5206
4957
|
process.stdout.write(data);
|
|
5207
4958
|
fullOutput += text;
|
|
5208
4959
|
if (fullOutput.length > MAX_OUTPUT_BYTES) {
|
|
4960
|
+
const dropped = fullOutput.length - MAX_OUTPUT_BYTES;
|
|
5209
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
|
+
});
|
|
5210
4972
|
}
|
|
4973
|
+
totalBytesStreamed += text.length;
|
|
5211
4974
|
if (Date.now() - spawnTime < 1e4) {
|
|
5212
4975
|
const authError = containsAuthError(fullOutput + stderr);
|
|
5213
4976
|
if (authError) {
|
|
@@ -5258,10 +5021,9 @@ ${source_default.red("!")} Claude Code auth failure detected: "${authError}"`);
|
|
|
5258
5021
|
).catch(() => {
|
|
5259
5022
|
});
|
|
5260
5023
|
}
|
|
5261
|
-
|
|
5024
|
+
options.eventQueue?.enqueue({
|
|
5262
5025
|
event_type: "output",
|
|
5263
|
-
content: { chunk, byte_offset:
|
|
5264
|
-
}).catch(() => {
|
|
5026
|
+
content: { chunk, byte_offset: totalBytesStreamed }
|
|
5265
5027
|
});
|
|
5266
5028
|
}
|
|
5267
5029
|
}
|
|
@@ -5285,7 +5047,7 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
|
|
|
5285
5047
|
});
|
|
5286
5048
|
child.on("close", (code, signal) => {
|
|
5287
5049
|
_activeChild = null;
|
|
5288
|
-
|
|
5050
|
+
resolve({
|
|
5289
5051
|
exitCode: signal === "SIGKILL" ? 137 : code ?? 1,
|
|
5290
5052
|
stderr,
|
|
5291
5053
|
output: fullOutput,
|
|
@@ -5324,7 +5086,7 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
|
|
|
5324
5086
|
return result;
|
|
5325
5087
|
}
|
|
5326
5088
|
async function launchRelayMode(prompt, options) {
|
|
5327
|
-
return new Promise((
|
|
5089
|
+
return new Promise((resolve, reject) => {
|
|
5328
5090
|
const child = (0, import_node_child_process5.spawn)("claude", [], {
|
|
5329
5091
|
cwd: options.cwd,
|
|
5330
5092
|
stdio: ["pipe", "pipe", "pipe"],
|
|
@@ -5337,6 +5099,7 @@ async function launchRelayMode(prompt, options) {
|
|
|
5337
5099
|
let lastProgressBytes = 0;
|
|
5338
5100
|
let authFailure = false;
|
|
5339
5101
|
const spawnTime = Date.now();
|
|
5102
|
+
let totalBytesStreamed = 0;
|
|
5340
5103
|
child.stdin?.write(prompt + "\n");
|
|
5341
5104
|
let lastRedirectCheck = Date.now();
|
|
5342
5105
|
let lineBuffer = "";
|
|
@@ -5346,8 +5109,20 @@ async function launchRelayMode(prompt, options) {
|
|
|
5346
5109
|
stdoutBuffer += text;
|
|
5347
5110
|
fullOutput += text;
|
|
5348
5111
|
if (fullOutput.length > MAX_OUTPUT_BYTES) {
|
|
5112
|
+
const dropped = fullOutput.length - MAX_OUTPUT_BYTES;
|
|
5349
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
|
+
});
|
|
5350
5124
|
}
|
|
5125
|
+
totalBytesStreamed += text.length;
|
|
5351
5126
|
if (Date.now() - spawnTime < 1e4 && !authFailure) {
|
|
5352
5127
|
const authError = containsAuthError(fullOutput + stderr);
|
|
5353
5128
|
if (authError) {
|
|
@@ -5406,10 +5181,9 @@ ${source_default.red("!")} Claude Code auth failure detected: "${authError}"`);
|
|
|
5406
5181
|
{ output_chunk: chunk }
|
|
5407
5182
|
).catch(() => {
|
|
5408
5183
|
});
|
|
5409
|
-
|
|
5184
|
+
options.eventQueue?.enqueue({
|
|
5410
5185
|
event_type: "output",
|
|
5411
|
-
content: { chunk, byte_offset:
|
|
5412
|
-
}).catch(() => {
|
|
5186
|
+
content: { chunk, byte_offset: totalBytesStreamed }
|
|
5413
5187
|
});
|
|
5414
5188
|
}
|
|
5415
5189
|
if (Date.now() - lastRedirectCheck > 1e4) {
|
|
@@ -5517,9 +5291,9 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
|
|
|
5517
5291
|
child.on("close", (code, signal) => {
|
|
5518
5292
|
_activeChild = null;
|
|
5519
5293
|
if (signal === "SIGKILL") {
|
|
5520
|
-
|
|
5294
|
+
resolve({ exitCode: 137, stderr, output: fullOutput, authFailure });
|
|
5521
5295
|
} else {
|
|
5522
|
-
|
|
5296
|
+
resolve({ exitCode: code ?? 1, stderr, output: fullOutput, authFailure });
|
|
5523
5297
|
}
|
|
5524
5298
|
});
|
|
5525
5299
|
child.on("error", (err) => {
|
|
@@ -5620,45 +5394,14 @@ async function runAgent(options) {
|
|
|
5620
5394
|
console.log();
|
|
5621
5395
|
process.exit(1);
|
|
5622
5396
|
}
|
|
5623
|
-
if (process.env.CV_DEBUG) {
|
|
5624
|
-
console.log(source_default.gray(` PATH: ${process.env.PATH}`));
|
|
5625
|
-
console.log(source_default.gray(` HOME: ${process.env.HOME}`));
|
|
5626
|
-
console.log(source_default.gray(` SHELL: ${process.env.SHELL}`));
|
|
5627
|
-
}
|
|
5628
|
-
let claudeBinary = "claude";
|
|
5629
5397
|
try {
|
|
5630
5398
|
(0, import_node_child_process5.execSync)("claude --version", { stdio: "pipe", timeout: 5e3 });
|
|
5631
5399
|
} catch {
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
];
|
|
5638
|
-
let found = false;
|
|
5639
|
-
for (const candidate of candidates) {
|
|
5640
|
-
try {
|
|
5641
|
-
const resolved = (0, import_node_child_process5.execSync)(`ls ${candidate} 2>/dev/null | head -1`, { encoding: "utf8", timeout: 3e3 }).trim();
|
|
5642
|
-
if (resolved) {
|
|
5643
|
-
(0, import_node_child_process5.execSync)(`${resolved} --version`, { stdio: "pipe", timeout: 5e3 });
|
|
5644
|
-
claudeBinary = resolved;
|
|
5645
|
-
found = true;
|
|
5646
|
-
console.log(source_default.yellow("!") + ` Claude Code found at ${resolved} (not on PATH)`);
|
|
5647
|
-
break;
|
|
5648
|
-
}
|
|
5649
|
-
} catch {
|
|
5650
|
-
}
|
|
5651
|
-
}
|
|
5652
|
-
if (!found) {
|
|
5653
|
-
console.log();
|
|
5654
|
-
console.log(source_default.red("Claude Code CLI not found.") + " Install it first:");
|
|
5655
|
-
console.log(` ${source_default.cyan("npm install -g @anthropic-ai/claude-code")}`);
|
|
5656
|
-
console.log();
|
|
5657
|
-
console.log(source_default.gray(` Current PATH: ${process.env.PATH}`));
|
|
5658
|
-
console.log(source_default.gray(" If running via Launch Agent, ensure PATH includes the directory where claude is installed."));
|
|
5659
|
-
console.log();
|
|
5660
|
-
process.exit(1);
|
|
5661
|
-
}
|
|
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);
|
|
5662
5405
|
}
|
|
5663
5406
|
const { env: claudeEnv, usingApiKey } = await getClaudeEnv();
|
|
5664
5407
|
const authCheck = await checkClaudeAuth();
|
|
@@ -5699,80 +5442,6 @@ async function runAgent(options) {
|
|
|
5699
5442
|
console.log();
|
|
5700
5443
|
}
|
|
5701
5444
|
}
|
|
5702
|
-
{
|
|
5703
|
-
const isGitRepo = require("fs").existsSync(require("path").join(workingDir, ".git"));
|
|
5704
|
-
if (!isGitRepo) {
|
|
5705
|
-
console.log(source_default.gray(" Bootstrap: initializing git repo..."));
|
|
5706
|
-
try {
|
|
5707
|
-
(0, import_node_child_process5.execSync)("git init && git checkout -b main", { cwd: workingDir, stdio: "pipe" });
|
|
5708
|
-
} catch {
|
|
5709
|
-
}
|
|
5710
|
-
}
|
|
5711
|
-
const claudeMdPath = require("path").join(workingDir, "CLAUDE.md");
|
|
5712
|
-
if (!require("fs").existsSync(claudeMdPath)) {
|
|
5713
|
-
const name = require("path").basename(workingDir);
|
|
5714
|
-
const template = `# ${name}
|
|
5715
|
-
|
|
5716
|
-
## Overview
|
|
5717
|
-
[Describe your project here]
|
|
5718
|
-
|
|
5719
|
-
## Build & Run
|
|
5720
|
-
[How to build and run this project]
|
|
5721
|
-
`;
|
|
5722
|
-
try {
|
|
5723
|
-
require("fs").writeFileSync(claudeMdPath, template);
|
|
5724
|
-
console.log(source_default.gray(" Bootstrap: created CLAUDE.md"));
|
|
5725
|
-
} catch {
|
|
5726
|
-
}
|
|
5727
|
-
}
|
|
5728
|
-
const gitignorePath = require("path").join(workingDir, ".gitignore");
|
|
5729
|
-
try {
|
|
5730
|
-
const fs3 = require("fs");
|
|
5731
|
-
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";
|
|
5732
|
-
let existing = "";
|
|
5733
|
-
try {
|
|
5734
|
-
existing = fs3.readFileSync(gitignorePath, "utf-8");
|
|
5735
|
-
} catch {
|
|
5736
|
-
}
|
|
5737
|
-
if (!existing.includes(".claude/")) {
|
|
5738
|
-
const prefix = existing && !existing.endsWith("\n") ? "\n" : "";
|
|
5739
|
-
fs3.writeFileSync(gitignorePath, existing + prefix + "# Credentials (auto-added by cv-agent)\n" + credPatterns);
|
|
5740
|
-
console.log(source_default.gray(" Bootstrap: .gitignore credential protection added"));
|
|
5741
|
-
}
|
|
5742
|
-
} catch {
|
|
5743
|
-
}
|
|
5744
|
-
const cvDir = require("path").join(workingDir, ".cv");
|
|
5745
|
-
if (!require("fs").existsSync(cvDir)) {
|
|
5746
|
-
try {
|
|
5747
|
-
require("fs").mkdirSync(cvDir, { recursive: true });
|
|
5748
|
-
} catch {
|
|
5749
|
-
}
|
|
5750
|
-
}
|
|
5751
|
-
if (creds.CV_HUB_PAT) {
|
|
5752
|
-
try {
|
|
5753
|
-
const existing = (0, import_node_child_process5.execSync)('git remote get-url cv-hub 2>/dev/null || echo ""', {
|
|
5754
|
-
cwd: workingDir,
|
|
5755
|
-
encoding: "utf8",
|
|
5756
|
-
timeout: 5e3
|
|
5757
|
-
}).trim();
|
|
5758
|
-
if (!existing) {
|
|
5759
|
-
const name = require("path").basename(workingDir);
|
|
5760
|
-
const gitHost = "git.hub.controlvector.io";
|
|
5761
|
-
let user = "user";
|
|
5762
|
-
try {
|
|
5763
|
-
const sharedPath = require("path").join(require("os").homedir(), ".config", "controlvector", "credentials.json");
|
|
5764
|
-
const shared = JSON.parse(require("fs").readFileSync(sharedPath, "utf-8"));
|
|
5765
|
-
if (shared.username) user = shared.username;
|
|
5766
|
-
} catch {
|
|
5767
|
-
}
|
|
5768
|
-
const remoteUrl = `https://${gitHost}/${user}/${name}.git`;
|
|
5769
|
-
(0, import_node_child_process5.execSync)(`git remote add cv-hub ${remoteUrl}`, { cwd: workingDir, stdio: "pipe" });
|
|
5770
|
-
console.log(source_default.gray(` Bootstrap: remote cv-hub \u2192 ${remoteUrl}`));
|
|
5771
|
-
}
|
|
5772
|
-
} catch {
|
|
5773
|
-
}
|
|
5774
|
-
}
|
|
5775
|
-
}
|
|
5776
5445
|
let detectedRepoId;
|
|
5777
5446
|
try {
|
|
5778
5447
|
const remoteUrl = (0, import_node_child_process5.execSync)("git remote get-url origin 2>/dev/null", {
|
|
@@ -5956,6 +5625,14 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
5956
5625
|
console.log(source_default.gray(` Git remote: ${remoteInfo.remoteName} -> ${remoteInfo.remoteUrl}`));
|
|
5957
5626
|
}
|
|
5958
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();
|
|
5959
5636
|
try {
|
|
5960
5637
|
const mode = options.autoApprove ? "auto-approve" : "relay";
|
|
5961
5638
|
console.log(`\u{1F680} ${source_default.bold.green("RUNNING")} \u2014 Launching Claude Code (${mode})...`);
|
|
@@ -5971,7 +5648,8 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
5971
5648
|
taskId: task.id,
|
|
5972
5649
|
executorId: state.executorId,
|
|
5973
5650
|
spawnEnv: claudeEnv,
|
|
5974
|
-
machineName: state.machineName
|
|
5651
|
+
machineName: state.machineName,
|
|
5652
|
+
eventQueue
|
|
5975
5653
|
});
|
|
5976
5654
|
} else {
|
|
5977
5655
|
result = await launchRelayMode(prompt, {
|
|
@@ -5980,7 +5658,8 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
5980
5658
|
executorId: state.executorId,
|
|
5981
5659
|
taskId: task.id,
|
|
5982
5660
|
spawnEnv: claudeEnv,
|
|
5983
|
-
machineName: state.machineName
|
|
5661
|
+
machineName: state.machineName,
|
|
5662
|
+
eventQueue
|
|
5984
5663
|
});
|
|
5985
5664
|
}
|
|
5986
5665
|
console.log(source_default.gray("\n" + "-".repeat(60)));
|
|
@@ -6041,14 +5720,13 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
6041
5720
|
sendTaskLog(creds, state.executorId, task.id, "info", `Deploy manifest error: ${deployErr.message}`);
|
|
6042
5721
|
}
|
|
6043
5722
|
}
|
|
6044
|
-
|
|
5723
|
+
eventQueue.enqueue({
|
|
6045
5724
|
event_type: "completed",
|
|
6046
5725
|
content: {
|
|
6047
5726
|
exit_code: result.exitCode,
|
|
6048
5727
|
duration_seconds: Math.round((Date.now() - startTime) / 1e3),
|
|
6049
5728
|
files_changed: allChangedFiles.length
|
|
6050
5729
|
}
|
|
6051
|
-
}).catch(() => {
|
|
6052
5730
|
});
|
|
6053
5731
|
if (result.exitCode === 0) {
|
|
6054
5732
|
if (allChangedFiles.length > 0) {
|
|
@@ -6073,14 +5751,13 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
6073
5751
|
console.log();
|
|
6074
5752
|
console.log(`\u2705 ${source_default.bold.green("COMPLETED")} \u2014 Duration: ${elapsed}`);
|
|
6075
5753
|
printBanner("COMPLETED", elapsed, allChangedFiles, postGitState.headSha);
|
|
6076
|
-
|
|
5754
|
+
eventQueue.enqueue({
|
|
6077
5755
|
event_type: "output_final",
|
|
6078
5756
|
content: {
|
|
6079
5757
|
output: result.output.slice(-MAX_OUTPUT_FINAL_BYTES),
|
|
6080
5758
|
exit_code: result.exitCode,
|
|
6081
5759
|
duration_seconds: Math.round((Date.now() - startTime) / 1e3)
|
|
6082
5760
|
}
|
|
6083
|
-
}).catch(() => {
|
|
6084
5761
|
});
|
|
6085
5762
|
await withRetry(
|
|
6086
5763
|
() => completeTask(creds, state.executorId, task.id, payload),
|
|
@@ -6169,6 +5846,10 @@ ${source_default.red("!")} Task error: ${err.message}`);
|
|
|
6169
5846
|
} finally {
|
|
6170
5847
|
clearInterval(heartbeatTimer);
|
|
6171
5848
|
if (timeoutTimer) clearTimeout(timeoutTimer);
|
|
5849
|
+
try {
|
|
5850
|
+
await eventQueue.close();
|
|
5851
|
+
} catch {
|
|
5852
|
+
}
|
|
6172
5853
|
state.currentTaskId = null;
|
|
6173
5854
|
state.lastTaskEnd = Date.now();
|
|
6174
5855
|
}
|
|
@@ -6228,7 +5909,7 @@ async function promptForToken() {
|
|
|
6228
5909
|
input: process.stdin,
|
|
6229
5910
|
output: process.stdout
|
|
6230
5911
|
});
|
|
6231
|
-
return new Promise((
|
|
5912
|
+
return new Promise((resolve) => {
|
|
6232
5913
|
rl.question("Enter your CV-Hub PAT token: ", (answer) => {
|
|
6233
5914
|
rl.close();
|
|
6234
5915
|
const token = answer.trim();
|
|
@@ -6236,7 +5917,7 @@ async function promptForToken() {
|
|
|
6236
5917
|
console.log(source_default.red("No token provided."));
|
|
6237
5918
|
process.exit(1);
|
|
6238
5919
|
}
|
|
6239
|
-
|
|
5920
|
+
resolve(token);
|
|
6240
5921
|
});
|
|
6241
5922
|
});
|
|
6242
5923
|
}
|
|
@@ -6515,7 +6196,7 @@ function statusCommand() {
|
|
|
6515
6196
|
|
|
6516
6197
|
// src/index.ts
|
|
6517
6198
|
var program2 = new Command();
|
|
6518
|
-
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");
|
|
6519
6200
|
program2.addCommand(setupCommand());
|
|
6520
6201
|
program2.addCommand(agentCommand());
|
|
6521
6202
|
program2.addCommand(authCommand());
|