@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 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
 
@@ -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(" Open Claude.ai and try:");
4106
- 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.`);
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, path, body) {
4123
- const url = `${creds.CV_HUB_API}${path}`;
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 allLines = statusOutput.split("\n").filter(Boolean);
4676
- if (allLines.length === 0) {
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 safeLines) {
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] ${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`
4706
4478
  );
4707
- for (const line of safeLines) {
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 import_node_path3 = require("node:path");
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, import_node_path3.join)(workspaceRoot, ".cva", "deploy.json");
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, import_node_path3.join)(workspaceRoot, buildStep.working_dir || ".");
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, 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);
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((resolve2, reject) => {
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
- postTaskEvent(options.creds, options.taskId, {
5024
+ options.eventQueue?.enqueue({
5262
5025
  event_type: "output",
5263
- content: { chunk, byte_offset: lastProgressBytes }
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
- resolve2({
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((resolve2, reject) => {
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
- postTaskEvent(options.creds, options.taskId, {
5184
+ options.eventQueue?.enqueue({
5410
5185
  event_type: "output",
5411
- content: { chunk, byte_offset: lastProgressBytes }
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
- resolve2({ exitCode: 137, stderr, output: fullOutput, authFailure });
5294
+ resolve({ exitCode: 137, stderr, output: fullOutput, authFailure });
5521
5295
  } else {
5522
- resolve2({ exitCode: code ?? 1, stderr, output: fullOutput, authFailure });
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
- const candidates = [
5633
- "/usr/local/bin/claude",
5634
- "/opt/homebrew/bin/claude",
5635
- `${process.env.HOME}/.npm-global/bin/claude`,
5636
- `${process.env.HOME}/.nvm/versions/node/*/bin/claude`
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
- postTaskEvent(creds, task.id, {
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
- postTaskEvent(creds, task.id, {
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((resolve2) => {
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
- resolve2(token);
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.9.2" : "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");
6519
6200
  program2.addCommand(setupCommand());
6520
6201
  program2.addCommand(agentCommand());
6521
6202
  program2.addCommand(authCommand());