@controlvector/cv-agent 1.9.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bundle.cjs CHANGED
@@ -3847,10 +3847,10 @@ async function runSetup() {
3847
3847
  console.log(` ${source_default.cyan("a")} \u2014 Initialize a new project here: ${cwd}`);
3848
3848
  console.log(` ${source_default.cyan("b")} \u2014 Clone an existing repo from CV-Hub`);
3849
3849
  console.log();
3850
- const choice = await new Promise((resolve) => {
3850
+ const choice = await new Promise((resolve2) => {
3851
3851
  rl.question(" Choose [a/b]: ", (answer) => {
3852
3852
  rl.close();
3853
- resolve(answer.trim().toLowerCase() || "a");
3853
+ resolve2(answer.trim().toLowerCase() || "a");
3854
3854
  });
3855
3855
  });
3856
3856
  if (choice === "b" && token) {
@@ -3878,10 +3878,10 @@ async function runSetup() {
3878
3878
  });
3879
3879
  console.log();
3880
3880
  const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
3881
- const selection = await new Promise((resolve) => {
3881
+ const selection = await new Promise((resolve2) => {
3882
3882
  rl2.question(` Select a repo [1-${displayRepos.length}]: `, (answer) => {
3883
3883
  rl2.close();
3884
- resolve(answer.trim());
3884
+ resolve2(answer.trim());
3885
3885
  });
3886
3886
  });
3887
3887
  const idx = parseInt(selection, 10) - 1;
@@ -4015,10 +4015,10 @@ async function runSetup() {
4015
4015
  if (!agentRunning) {
4016
4016
  const readline = await import("node:readline");
4017
4017
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
4018
- const answer = await new Promise((resolve) => {
4018
+ const answer = await new Promise((resolve2) => {
4019
4019
  rl.question(" Start the CV-Agent daemon? (Y/n): ", (a) => {
4020
4020
  rl.close();
4021
- resolve(a.trim().toLowerCase() || "y");
4021
+ resolve2(a.trim().toLowerCase() || "y");
4022
4022
  });
4023
4023
  });
4024
4024
  if (answer === "y" || answer === "yes" || answer === "") {
@@ -4558,11 +4558,78 @@ async function writeConfig(config) {
4558
4558
 
4559
4559
  // src/commands/git-safety.ts
4560
4560
  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
+ ];
4561
4585
  function git(cmd, cwd) {
4562
4586
  return (0, import_node_child_process3.execSync)(cmd, { cwd, encoding: "utf8", timeout: 3e4 }).trim();
4563
4587
  }
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
+ }
4564
4618
  function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
4565
4619
  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
+ }
4566
4633
  try {
4567
4634
  let statusOutput;
4568
4635
  try {
@@ -4570,8 +4637,8 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
4570
4637
  } catch {
4571
4638
  return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false, error: "git status failed" };
4572
4639
  }
4573
- const lines = statusOutput.split("\n").filter(Boolean);
4574
- if (lines.length === 0) {
4640
+ const allLines = statusOutput.split("\n").filter(Boolean);
4641
+ if (allLines.length === 0) {
4575
4642
  try {
4576
4643
  const unpushed = git(`git log origin/${targetBranch}..HEAD --oneline 2>/dev/null`, workspaceRoot);
4577
4644
  if (unpushed) {
@@ -4583,8 +4650,16 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
4583
4650
  }
4584
4651
  return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false };
4585
4652
  }
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
+ }
4586
4661
  let added = 0, modified = 0, deleted = 0;
4587
- for (const line of lines) {
4662
+ for (const line of safeLines) {
4588
4663
  const code = line.substring(0, 2);
4589
4664
  if (code.includes("?")) added++;
4590
4665
  else if (code.includes("D")) deleted++;
@@ -4592,9 +4667,15 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
4592
4667
  else added++;
4593
4668
  }
4594
4669
  console.log(
4595
- ` [git-safety] ${lines.length} uncommitted changes (${added} new, ${modified} modified, ${deleted} deleted) \u2014 committing now`
4670
+ ` [git-safety] ${safeLines.length} safe changes (${added} new, ${modified} modified, ${deleted} deleted) \u2014 committing now`
4596
4671
  );
4597
- git("git add -A", workspaceRoot);
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
+ }
4598
4679
  const shortId = taskId.substring(0, 8);
4599
4680
  const commitMsg = `task: ${taskTitle} [${shortId}]
4600
4681
 
@@ -4655,8 +4736,8 @@ Files: ${added} added, ${modified} modified, ${deleted} deleted`;
4655
4736
 
4656
4737
  // src/commands/deploy-manifest.ts
4657
4738
  var import_node_child_process4 = require("node:child_process");
4658
- var import_node_fs2 = require("node:fs");
4659
- var import_node_path2 = require("node:path");
4739
+ var import_node_fs3 = require("node:fs");
4740
+ var import_node_path3 = require("node:path");
4660
4741
  function exec(cmd, cwd, timeoutMs = 3e5, env2) {
4661
4742
  try {
4662
4743
  const stdout = (0, import_node_child_process4.execSync)(cmd, {
@@ -4699,10 +4780,10 @@ function getHeadCommit(cwd) {
4699
4780
  }
4700
4781
  }
4701
4782
  function loadDeployManifest(workspaceRoot) {
4702
- const manifestPath = (0, import_node_path2.join)(workspaceRoot, ".cva", "deploy.json");
4703
- if (!(0, import_node_fs2.existsSync)(manifestPath)) return null;
4783
+ const manifestPath = (0, import_node_path3.join)(workspaceRoot, ".cva", "deploy.json");
4784
+ if (!(0, import_node_fs3.existsSync)(manifestPath)) return null;
4704
4785
  try {
4705
- const raw = (0, import_node_fs2.readFileSync)(manifestPath, "utf8");
4786
+ const raw = (0, import_node_fs3.readFileSync)(manifestPath, "utf8");
4706
4787
  const manifest = JSON.parse(raw);
4707
4788
  if (!manifest.version || manifest.version < 1) {
4708
4789
  console.log(" [deploy] Invalid manifest version");
@@ -4727,7 +4808,7 @@ async function postTaskDeploy(workspaceRoot, taskId, log) {
4727
4808
  console.log(` [deploy] Building: ${buildStep.name}`);
4728
4809
  log("lifecycle", `Building: ${buildStep.name}`);
4729
4810
  const start = Date.now();
4730
- const cwd = (0, import_node_path2.join)(workspaceRoot, buildStep.working_dir || ".");
4811
+ const cwd = (0, import_node_path3.join)(workspaceRoot, buildStep.working_dir || ".");
4731
4812
  const timeoutMs = (buildStep.timeout_seconds || 300) * 1e3;
4732
4813
  const result = exec(buildStep.command, cwd, timeoutMs);
4733
4814
  if (!result.ok) {
@@ -4818,7 +4899,7 @@ async function rollback(workspaceRoot, targetCommit, manifest, log) {
4818
4899
  }
4819
4900
  if (manifest.build?.steps) {
4820
4901
  for (const step of manifest.build.steps) {
4821
- exec(step.command, (0, import_node_path2.join)(workspaceRoot, step.working_dir || "."), (step.timeout_seconds || 300) * 1e3);
4902
+ exec(step.command, (0, import_node_path3.join)(workspaceRoot, step.working_dir || "."), (step.timeout_seconds || 300) * 1e3);
4822
4903
  }
4823
4904
  }
4824
4905
  if (manifest.service?.restart_command) {
@@ -5054,7 +5135,7 @@ async function launchAutoApproveMode(prompt, options) {
5054
5135
  let fullOutput = "";
5055
5136
  let lastProgressBytes = 0;
5056
5137
  const runOnce = (inputPrompt, isContinue) => {
5057
- return new Promise((resolve, reject) => {
5138
+ return new Promise((resolve2, reject) => {
5058
5139
  const args = isContinue ? ["-p", inputPrompt, "--continue", "--allowedTools", ...ALLOWED_TOOLS] : ["-p", inputPrompt, "--allowedTools", ...ALLOWED_TOOLS];
5059
5140
  if (sessionId && !isContinue) {
5060
5141
  args.push("--session-id", sessionId);
@@ -5153,7 +5234,7 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
5153
5234
  });
5154
5235
  child.on("close", (code, signal) => {
5155
5236
  _activeChild = null;
5156
- resolve({
5237
+ resolve2({
5157
5238
  exitCode: signal === "SIGKILL" ? 137 : code ?? 1,
5158
5239
  stderr,
5159
5240
  output: fullOutput,
@@ -5192,7 +5273,7 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
5192
5273
  return result;
5193
5274
  }
5194
5275
  async function launchRelayMode(prompt, options) {
5195
- return new Promise((resolve, reject) => {
5276
+ return new Promise((resolve2, reject) => {
5196
5277
  const child = (0, import_node_child_process5.spawn)("claude", [], {
5197
5278
  cwd: options.cwd,
5198
5279
  stdio: ["pipe", "pipe", "pipe"],
@@ -5385,9 +5466,9 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
5385
5466
  child.on("close", (code, signal) => {
5386
5467
  _activeChild = null;
5387
5468
  if (signal === "SIGKILL") {
5388
- resolve({ exitCode: 137, stderr, output: fullOutput, authFailure });
5469
+ resolve2({ exitCode: 137, stderr, output: fullOutput, authFailure });
5389
5470
  } else {
5390
- resolve({ exitCode: code ?? 1, stderr, output: fullOutput, authFailure });
5471
+ resolve2({ exitCode: code ?? 1, stderr, output: fullOutput, authFailure });
5391
5472
  }
5392
5473
  });
5393
5474
  child.on("error", (err) => {
@@ -5488,14 +5569,45 @@ async function runAgent(options) {
5488
5569
  console.log();
5489
5570
  process.exit(1);
5490
5571
  }
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";
5491
5578
  try {
5492
5579
  (0, import_node_child_process5.execSync)("claude --version", { stdio: "pipe", timeout: 5e3 });
5493
5580
  } catch {
5494
- console.log();
5495
- console.log(source_default.red("Claude Code CLI not found.") + " Install it first:");
5496
- console.log(` ${source_default.cyan("npm install -g @anthropic-ai/claude-code")}`);
5497
- console.log();
5498
- process.exit(1);
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
+ }
5499
5611
  }
5500
5612
  const { env: claudeEnv, usingApiKey } = await getClaudeEnv();
5501
5613
  const authCheck = await checkClaudeAuth();
@@ -6049,7 +6161,7 @@ async function promptForToken() {
6049
6161
  input: process.stdin,
6050
6162
  output: process.stdout
6051
6163
  });
6052
- return new Promise((resolve) => {
6164
+ return new Promise((resolve2) => {
6053
6165
  rl.question("Enter your CV-Hub PAT token: ", (answer) => {
6054
6166
  rl.close();
6055
6167
  const token = answer.trim();
@@ -6057,7 +6169,7 @@ async function promptForToken() {
6057
6169
  console.log(source_default.red("No token provided."));
6058
6170
  process.exit(1);
6059
6171
  }
6060
- resolve(token);
6172
+ resolve2(token);
6061
6173
  });
6062
6174
  });
6063
6175
  }
@@ -6336,7 +6448,7 @@ function statusCommand() {
6336
6448
 
6337
6449
  // src/index.ts
6338
6450
  var program2 = new Command();
6339
- 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.0" : "1.6.0");
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");
6340
6452
  program2.addCommand(setupCommand());
6341
6453
  program2.addCommand(agentCommand());
6342
6454
  program2.addCommand(authCommand());