@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 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 path = require("node:path");
963
- var fs3 = require("node:fs");
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 = path.resolve(baseDir, baseName);
1896
- if (fs3.existsSync(localBin)) return localBin;
1897
- if (sourceExt.includes(path.extname(baseName))) return void 0;
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) => fs3.existsSync(`${localBin}${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 = fs3.realpathSync(this._scriptPath);
1911
+ resolvedScriptPath = fs4.realpathSync(this._scriptPath);
1912
1912
  } catch (err) {
1913
1913
  resolvedScriptPath = this._scriptPath;
1914
1914
  }
1915
- executableDir = path.resolve(
1916
- path.dirname(resolvedScriptPath),
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 = path.basename(
1923
+ const legacyName = path3.basename(
1924
1924
  this._scriptPath,
1925
- path.extname(this._scriptPath)
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(path.extname(executableFile));
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 = path.basename(filename, path.extname(filename));
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(path2) {
2791
- if (path2 === void 0) return this._executableDir;
2792
- this._executableDir = path2;
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 deviceResult = await deviceAuthFlow(hubUrl, appUrl);
3815
- token = deviceResult.token;
3816
- username = deviceResult.username;
3817
- console.log(source_default.green(" \u2713") + ` Authenticated as ${source_default.bold(username)}`);
3818
- writeSharedCreds({ hub_url: hubUrl, token, username, created_at: (/* @__PURE__ */ new Date()).toISOString() });
3819
- await writeCredentialField("CV_HUB_PAT", token);
3820
- await writeCredentialField("CV_HUB_API", hubUrl);
3821
- } catch (err) {
3822
- console.log(source_default.red(` Authentication failed: ${err.message}`));
3823
- console.log(source_default.gray(" You can retry with: cva setup"));
3824
- console.log(source_default.gray(" Or manually: cva auth login --token <your-pat>"));
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
- let cwd = process.cwd();
3838
- let isGitRepo = (0, import_node_fs.existsSync)((0, import_node_path.join)(cwd, ".git"));
3839
- let repoName = (0, import_node_path.basename)(cwd);
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
- const readline = await import("node:readline");
3844
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
3845
- console.log(" No git repository found in this directory.");
3846
- console.log();
3847
- console.log(` ${source_default.cyan("a")} \u2014 Initialize a new project here: ${cwd}`);
3848
- console.log(` ${source_default.cyan("b")} \u2014 Clone an existing repo from CV-Hub`);
3849
- console.log();
3850
- const choice = await new Promise((resolve2) => {
3851
- rl.question(" Choose [a/b]: ", (answer) => {
3852
- rl.close();
3853
- resolve2(answer.trim().toLowerCase() || "a");
3854
- });
3855
- });
3856
- if (choice === "b" && token) {
3857
- try {
3858
- console.log(source_default.gray(" Fetching your repositories..."));
3859
- const controller = new AbortController();
3860
- const timeout = setTimeout(() => controller.abort(), 15e3);
3861
- const res = await fetch(`${hubUrl}/api/v1/repos?limit=50`, {
3862
- headers: { Authorization: `Bearer ${token}` },
3863
- signal: controller.signal
3864
- });
3865
- clearTimeout(timeout);
3866
- if (res.ok) {
3867
- const data = await res.json();
3868
- const repos = data.repositories || [];
3869
- if (repos.length === 0) {
3870
- console.log(source_default.yellow(" No repos found on CV-Hub. Initializing a new project instead."));
3871
- } else {
3872
- console.log();
3873
- console.log(" Your CV-Hub repositories:");
3874
- const displayRepos = repos.slice(0, 20);
3875
- displayRepos.forEach((r, i) => {
3876
- const desc = r.description ? source_default.gray(` \u2014 ${r.description.substring(0, 40)}`) : "";
3877
- console.log(` ${source_default.cyan(String(i + 1).padStart(2))}. ${r.slug || r.name}${desc}`);
3878
- });
3879
- console.log();
3880
- const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
3881
- const selection = await new Promise((resolve2) => {
3882
- rl2.question(` Select a repo [1-${displayRepos.length}]: `, (answer) => {
3883
- rl2.close();
3884
- resolve2(answer.trim());
3885
- });
3886
- });
3887
- const idx = parseInt(selection, 10) - 1;
3888
- if (idx >= 0 && idx < displayRepos.length) {
3889
- const repo = displayRepos[idx];
3890
- const gitHost = "git.hub.controlvector.io";
3891
- const slug = repo.slug || repo.name;
3892
- const cloneUrl = `https://${username}:${token}@${gitHost}/${username}/${slug}.git`;
3893
- console.log(source_default.gray(` Cloning ${username}/${slug}...`));
3894
- (0, import_node_child_process.execSync)(`git clone ${cloneUrl} ${slug}`, { cwd, stdio: "pipe", timeout: 6e4 });
3895
- cwd = (0, import_node_path.join)(cwd, slug);
3896
- process.chdir(cwd);
3897
- repoName = slug;
3898
- isGitRepo = true;
3899
- console.log(source_default.green(" \u2713") + ` Cloned ${username}/${slug}`);
3900
- }
3901
- }
3902
- }
3903
- } catch (err) {
3904
- console.log(source_default.yellow(` Could not fetch repos: ${err.message}. Initializing instead.`));
3905
- }
3906
- }
3907
- if (!isGitRepo) {
3908
- console.log(" Initializing git repository...");
3909
- (0, import_node_child_process.execSync)("git init && git checkout -b main", { cwd, stdio: "pipe" });
3910
- console.log(source_default.green(" \u2713") + " Git repo initialized");
3911
- }
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(" Open Claude.ai and try:");
4071
- console.log(source_default.cyan(` "Create a task in ${repoName} to add a hello world index.html"`));
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, path, body) {
4088
- const url = `${creds.CV_HUB_API}${path}`;
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 allLines = statusOutput.split("\n").filter(Boolean);
4641
- if (allLines.length === 0) {
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 safeLines) {
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] ${safeLines.length} safe changes (${added} new, ${modified} modified, ${deleted} deleted) \u2014 committing now`
4477
+ ` [git-safety] ${lines.length} uncommitted changes (${added} new, ${modified} modified, ${deleted} deleted) \u2014 committing now`
4671
4478
  );
4672
- for (const line of safeLines) {
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 import_node_path3 = require("node:path");
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, import_node_path3.join)(workspaceRoot, ".cva", "deploy.json");
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, import_node_path3.join)(workspaceRoot, buildStep.working_dir || ".");
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, import_node_path3.join)(workspaceRoot, step.working_dir || "."), (step.timeout_seconds || 300) * 1e3);
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((resolve2, reject) => {
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
- postTaskEvent(options.creds, options.taskId, {
5024
+ options.eventQueue?.enqueue({
5211
5025
  event_type: "output",
5212
- content: { chunk, byte_offset: lastProgressBytes }
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
- resolve2({
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((resolve2, reject) => {
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
- postTaskEvent(options.creds, options.taskId, {
5184
+ options.eventQueue?.enqueue({
5359
5185
  event_type: "output",
5360
- content: { chunk, byte_offset: lastProgressBytes }
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
- resolve2({ exitCode: 137, stderr, output: fullOutput, authFailure });
5294
+ resolve({ exitCode: 137, stderr, output: fullOutput, authFailure });
5470
5295
  } else {
5471
- resolve2({ exitCode: code ?? 1, stderr, output: fullOutput, authFailure });
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
- const candidates = [
5582
- "/usr/local/bin/claude",
5583
- "/opt/homebrew/bin/claude",
5584
- `${process.env.HOME}/.npm-global/bin/claude`,
5585
- `${process.env.HOME}/.nvm/versions/node/*/bin/claude`
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
- postTaskEvent(creds, task.id, {
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
- postTaskEvent(creds, task.id, {
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((resolve2) => {
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
- resolve2(token);
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.9.1" : "1.6.0");
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());