@controlvector/cv-agent 1.5.0 → 1.7.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
@@ -3036,8 +3036,11 @@ var {
3036
3036
  Help
3037
3037
  } = import_index.default;
3038
3038
 
3039
- // src/commands/agent.ts
3040
- var import_node_child_process4 = require("node:child_process");
3039
+ // src/commands/setup.ts
3040
+ var import_node_child_process = require("node:child_process");
3041
+ var import_node_fs = require("node:fs");
3042
+ var import_node_path = require("node:path");
3043
+ var import_node_os2 = require("node:os");
3041
3044
 
3042
3045
  // node_modules/chalk/source/vendor/ansi-styles/index.js
3043
3046
  var ANSI_BACKGROUND_OFFSET = 10;
@@ -3630,6 +3633,210 @@ function cleanMachineName(name) {
3630
3633
  return name.trim().toLowerCase().replace(/\s+/g, "-");
3631
3634
  }
3632
3635
 
3636
+ // src/commands/setup.ts
3637
+ var SHARED_CRED_DIR = (0, import_node_path.join)((0, import_node_os2.homedir)(), ".config", "controlvector");
3638
+ var SHARED_CRED_PATH = (0, import_node_path.join)(SHARED_CRED_DIR, "credentials.json");
3639
+ function readSharedCreds() {
3640
+ try {
3641
+ const data = JSON.parse((0, import_node_fs.readFileSync)(SHARED_CRED_PATH, "utf-8"));
3642
+ if (data.token && data.hub_url) return data;
3643
+ return null;
3644
+ } catch {
3645
+ return null;
3646
+ }
3647
+ }
3648
+ function writeSharedCreds(creds) {
3649
+ (0, import_node_fs.mkdirSync)(SHARED_CRED_DIR, { recursive: true });
3650
+ (0, import_node_fs.writeFileSync)(SHARED_CRED_PATH, JSON.stringify(creds, null, 2) + "\n", { mode: 384 });
3651
+ }
3652
+ async function validateToken(hubUrl, token) {
3653
+ try {
3654
+ const controller = new AbortController();
3655
+ const timeout = setTimeout(() => controller.abort(), 1e4);
3656
+ const res = await fetch(`${hubUrl}/api/auth/me`, {
3657
+ headers: { Authorization: `Bearer ${token}` },
3658
+ signal: controller.signal
3659
+ });
3660
+ clearTimeout(timeout);
3661
+ if (!res.ok) return null;
3662
+ const data = await res.json();
3663
+ return data.user?.username || data.user?.email || "unknown";
3664
+ } catch {
3665
+ return null;
3666
+ }
3667
+ }
3668
+ function checkBinary(cmd) {
3669
+ try {
3670
+ return (0, import_node_child_process.execSync)(`${cmd} 2>&1`, { encoding: "utf8", timeout: 5e3 }).trim().split("\n")[0];
3671
+ } catch {
3672
+ return null;
3673
+ }
3674
+ }
3675
+ async function runSetup() {
3676
+ const hubUrl = "https://api.hub.controlvector.io";
3677
+ const appUrl = "https://hub.controlvector.io";
3678
+ console.log();
3679
+ console.log(source_default.bold(" CV-Agent Setup"));
3680
+ console.log();
3681
+ console.log(" Checking environment...");
3682
+ const nodeVersion = checkBinary("node --version");
3683
+ const gitVersion = checkBinary("git --version");
3684
+ const claudeVersion = checkBinary("claude --version");
3685
+ console.log(` ${nodeVersion ? source_default.green("\u2713") : source_default.red("\u2717")} Node.js ${nodeVersion || "\u2014 not found"}`);
3686
+ console.log(` ${gitVersion ? source_default.green("\u2713") : source_default.red("\u2717")} Git ${gitVersion || "\u2014 not found"}`);
3687
+ console.log(` ${claudeVersion ? source_default.green("\u2713") : source_default.red("\u2717")} Claude Code ${claudeVersion || "\u2014 not found"}`);
3688
+ console.log();
3689
+ if (!nodeVersion || !gitVersion) {
3690
+ if (!nodeVersion) console.log(source_default.red(" Node.js required: https://nodejs.org"));
3691
+ if (!gitVersion) console.log(source_default.red(" Git required: https://git-scm.com"));
3692
+ process.exit(1);
3693
+ }
3694
+ if (!claudeVersion) {
3695
+ console.log(source_default.yellow(" Claude Code not found. Install with:"));
3696
+ console.log(source_default.cyan(" npm install -g @anthropic-ai/claude-code"));
3697
+ console.log();
3698
+ console.log(source_default.gray(" Continuing setup \u2014 you can install Claude Code later."));
3699
+ console.log();
3700
+ }
3701
+ let username;
3702
+ let token;
3703
+ const shared = readSharedCreds();
3704
+ if (shared) {
3705
+ const user = await validateToken(shared.hub_url, shared.token);
3706
+ if (user) {
3707
+ username = user;
3708
+ token = shared.token;
3709
+ console.log(source_default.green(" \u2713") + ` Authenticated as ${source_default.bold(user)}`);
3710
+ }
3711
+ }
3712
+ if (!token) {
3713
+ const creds = await readCredentials();
3714
+ if (creds.CV_HUB_PAT) {
3715
+ const user = await validateToken(hubUrl, creds.CV_HUB_PAT);
3716
+ if (user) {
3717
+ username = user;
3718
+ token = creds.CV_HUB_PAT;
3719
+ console.log(source_default.green(" \u2713") + ` Authenticated as ${source_default.bold(user)} (from cv-hub credentials)`);
3720
+ writeSharedCreds({ hub_url: hubUrl, token, username, created_at: (/* @__PURE__ */ new Date()).toISOString() });
3721
+ }
3722
+ }
3723
+ }
3724
+ if (!token) {
3725
+ console.log(" Let's connect you to CV-Hub.");
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}`));
3730
+ try {
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."));
3747
+ process.exit(1);
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);
3759
+ }
3760
+ console.log();
3761
+ console.log(" Claude.ai MCP connector:");
3762
+ const mcpUrl = `https://claude.ai/settings/integrations?add_mcp=${encodeURIComponent(`${hubUrl}/mcp`)}`;
3763
+ console.log(source_default.gray(` To connect Claude.ai to CV-Hub, visit:`));
3764
+ console.log(source_default.cyan(` ${mcpUrl}`));
3765
+ console.log();
3766
+ console.log(source_default.gray(' Click "Add Integration" \u2192 "Allow" when prompted.'));
3767
+ console.log(source_default.gray(" (You can do this later \u2014 setup will continue.)"));
3768
+ console.log();
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);
3774
+ if (isGitRepo) {
3775
+ console.log(source_default.green(" \u2713") + ` Git repo found: ${repoName}`);
3776
+ } else {
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");
3780
+ }
3781
+ if (!hasClaudeMd) {
3782
+ const template = `# ${repoName}
3783
+
3784
+ ## Overview
3785
+ [Describe your project here]
3786
+
3787
+ ## Tech Stack
3788
+ [What languages/frameworks does this project use?]
3789
+
3790
+ ## Build & Run
3791
+ [How to build and run this project]
3792
+ `;
3793
+ (0, import_node_fs.writeFileSync)((0, import_node_path.join)(cwd, "CLAUDE.md"), template);
3794
+ console.log(source_default.green(" \u2713") + " CLAUDE.md created");
3795
+ } else {
3796
+ console.log(source_default.green(" \u2713") + " CLAUDE.md present");
3797
+ }
3798
+ if (!hasCVDir) {
3799
+ (0, import_node_fs.mkdirSync)((0, import_node_path.join)(cwd, ".cv"), { recursive: true });
3800
+ }
3801
+ if (token && username) {
3802
+ const gitHost = "git.hub.controlvector.io";
3803
+ const remoteUrl = `https://${gitHost}/${username}/${repoName}.git`;
3804
+ try {
3805
+ const existingRemote = (0, import_node_child_process.execSync)('git remote get-url cv-hub 2>/dev/null || echo ""', { cwd, encoding: "utf8" }).trim();
3806
+ if (!existingRemote) {
3807
+ (0, import_node_child_process.execSync)(`git remote add cv-hub ${remoteUrl}`, { cwd, stdio: "pipe" });
3808
+ console.log(source_default.green(" \u2713") + ` Remote added: cv-hub \u2192 ${remoteUrl}`);
3809
+ } else {
3810
+ console.log(source_default.green(" \u2713") + ` CV-Hub remote exists`);
3811
+ }
3812
+ } catch {
3813
+ }
3814
+ }
3815
+ console.log();
3816
+ console.log(source_default.bold(" Setup Complete"));
3817
+ console.log(source_default.gray(" " + "\u2500".repeat(40)));
3818
+ console.log(` ${source_default.green("\u2713")} Authenticated as: ${source_default.cyan(username)}`);
3819
+ console.log(` ${source_default.green("\u2713")} Repository: ${source_default.cyan(repoName)}`);
3820
+ console.log(` ${source_default.green("\u2713")} CLAUDE.md: present`);
3821
+ console.log(source_default.gray(" " + "\u2500".repeat(40)));
3822
+ console.log();
3823
+ console.log(" What's next:");
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.`);
3826
+ console.log();
3827
+ console.log(source_default.gray(` Dashboard: ${appUrl}`));
3828
+ console.log();
3829
+ }
3830
+ function setupCommand() {
3831
+ const cmd = new Command("setup");
3832
+ cmd.description("Set up CV-Agent \u2014 authentication, repo, and connections (start here)");
3833
+ cmd.action(runSetup);
3834
+ return cmd;
3835
+ }
3836
+
3837
+ // src/commands/agent.ts
3838
+ var import_node_child_process5 = require("node:child_process");
3839
+
3633
3840
  // src/utils/api.ts
3634
3841
  async function apiCall(creds, method, path, body) {
3635
3842
  const url = `${creds.CV_HUB_API}${path}`;
@@ -3643,7 +3850,7 @@ async function apiCall(creds, method, path, body) {
3643
3850
  body: body ? JSON.stringify(body) : void 0
3644
3851
  });
3645
3852
  }
3646
- async function registerExecutor(creds, machineName, workingDir, repositoryId) {
3853
+ async function registerExecutor(creds, machineName, workingDir, repositoryId, metadata) {
3647
3854
  const body = {
3648
3855
  name: `cva:${machineName}`,
3649
3856
  machine_name: machineName,
@@ -3657,6 +3864,11 @@ async function registerExecutor(creds, machineName, workingDir, repositoryId) {
3657
3864
  if (repositoryId) {
3658
3865
  body.repository_id = repositoryId;
3659
3866
  }
3867
+ if (metadata?.role) body.role = metadata.role;
3868
+ if (metadata?.dispatch_guard) body.dispatch_guard = metadata.dispatch_guard;
3869
+ if (metadata?.tags) body.tags = metadata.tags;
3870
+ if (metadata?.owner_project) body.owner_project = metadata.owner_project;
3871
+ if (metadata?.integration) body.integration = metadata.integration;
3660
3872
  const res = await apiCall(creds, "POST", "/api/v1/executors", body);
3661
3873
  if (!res.ok) {
3662
3874
  const err = await res.text();
@@ -3830,10 +4042,10 @@ function parseClaudeCodeOutput(line) {
3830
4042
  }
3831
4043
 
3832
4044
  // src/commands/agent-git.ts
3833
- var import_node_child_process = require("node:child_process");
4045
+ var import_node_child_process2 = require("node:child_process");
3834
4046
  function gitExec(cmd, cwd) {
3835
4047
  try {
3836
- return (0, import_node_child_process.execSync)(cmd, { cwd, encoding: "utf-8", timeout: 1e4 }).trim();
4048
+ return (0, import_node_child_process2.execSync)(cmd, { cwd, encoding: "utf-8", timeout: 1e4 }).trim();
3837
4049
  } catch {
3838
4050
  return null;
3839
4051
  }
@@ -3986,7 +4198,7 @@ function verifyGitRemote(cwd, task, gitHost) {
3986
4198
  const currentRemote = gitExec("git remote get-url origin", cwd);
3987
4199
  if (!currentRemote) {
3988
4200
  try {
3989
- (0, import_node_child_process.execSync)(`git remote add origin ${expectedRemote}`, { cwd, timeout: 5e3 });
4201
+ (0, import_node_child_process2.execSync)(`git remote add origin ${expectedRemote}`, { cwd, timeout: 5e3 });
3990
4202
  } catch {
3991
4203
  }
3992
4204
  return { remoteName: "origin", remoteUrl: expectedRemote };
@@ -3996,7 +4208,7 @@ function verifyGitRemote(cwd, task, gitHost) {
3996
4208
  }
3997
4209
  if (currentRemote.includes(gitHost)) {
3998
4210
  try {
3999
- (0, import_node_child_process.execSync)(`git remote set-url origin ${expectedRemote}`, { cwd, timeout: 5e3 });
4211
+ (0, import_node_child_process2.execSync)(`git remote set-url origin ${expectedRemote}`, { cwd, timeout: 5e3 });
4000
4212
  } catch {
4001
4213
  }
4002
4214
  return { remoteName: "origin", remoteUrl: expectedRemote };
@@ -4004,12 +4216,12 @@ function verifyGitRemote(cwd, task, gitHost) {
4004
4216
  const cvRemote = gitExec("git remote get-url cv-hub", cwd);
4005
4217
  if (!cvRemote) {
4006
4218
  try {
4007
- (0, import_node_child_process.execSync)(`git remote add cv-hub ${expectedRemote}`, { cwd, timeout: 5e3 });
4219
+ (0, import_node_child_process2.execSync)(`git remote add cv-hub ${expectedRemote}`, { cwd, timeout: 5e3 });
4008
4220
  } catch {
4009
4221
  }
4010
4222
  } else if (cvRemote !== expectedRemote) {
4011
4223
  try {
4012
- (0, import_node_child_process.execSync)(`git remote set-url cv-hub ${expectedRemote}`, { cwd, timeout: 5e3 });
4224
+ (0, import_node_child_process2.execSync)(`git remote set-url cv-hub ${expectedRemote}`, { cwd, timeout: 5e3 });
4013
4225
  } catch {
4014
4226
  }
4015
4227
  }
@@ -4072,6 +4284,15 @@ ${source_default.yellow("\u26A0")} ${label} failed, retrying in ${delay}s... (${
4072
4284
  var import_fs2 = require("fs");
4073
4285
  var import_os2 = require("os");
4074
4286
  var import_path2 = require("path");
4287
+ async function readWorkspaceConfig(workspaceRoot) {
4288
+ try {
4289
+ const { readFile } = await import("fs/promises");
4290
+ const content = await readFile((0, import_path2.join)(workspaceRoot, ".cva", "agent.json"), "utf-8");
4291
+ return JSON.parse(content);
4292
+ } catch {
4293
+ return {};
4294
+ }
4295
+ }
4075
4296
  function getConfigPath() {
4076
4297
  return (0, import_path2.join)((0, import_os2.homedir)(), ".config", "cva", "config.json");
4077
4298
  }
@@ -4090,9 +4311,9 @@ async function writeConfig(config) {
4090
4311
  }
4091
4312
 
4092
4313
  // src/commands/git-safety.ts
4093
- var import_node_child_process2 = require("node:child_process");
4314
+ var import_node_child_process3 = require("node:child_process");
4094
4315
  function git(cmd, cwd) {
4095
- return (0, import_node_child_process2.execSync)(cmd, { cwd, encoding: "utf8", timeout: 3e4 }).trim();
4316
+ return (0, import_node_child_process3.execSync)(cmd, { cwd, encoding: "utf8", timeout: 3e4 }).trim();
4096
4317
  }
4097
4318
  function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
4098
4319
  const targetBranch = branch || "main";
@@ -4187,12 +4408,12 @@ Files: ${added} added, ${modified} modified, ${deleted} deleted`;
4187
4408
  }
4188
4409
 
4189
4410
  // src/commands/deploy-manifest.ts
4190
- var import_node_child_process3 = require("node:child_process");
4191
- var import_node_fs = require("node:fs");
4192
- var import_node_path = require("node:path");
4411
+ var import_node_child_process4 = require("node:child_process");
4412
+ var import_node_fs2 = require("node:fs");
4413
+ var import_node_path2 = require("node:path");
4193
4414
  function exec(cmd, cwd, timeoutMs = 3e5, env2) {
4194
4415
  try {
4195
- const stdout = (0, import_node_child_process3.execSync)(cmd, {
4416
+ const stdout = (0, import_node_child_process4.execSync)(cmd, {
4196
4417
  cwd,
4197
4418
  encoding: "utf8",
4198
4419
  timeout: timeoutMs,
@@ -4226,16 +4447,16 @@ async function checkHealth(url, timeoutSeconds) {
4226
4447
  }
4227
4448
  function getHeadCommit(cwd) {
4228
4449
  try {
4229
- return (0, import_node_child_process3.execSync)("git rev-parse HEAD", { cwd, encoding: "utf8", timeout: 5e3 }).trim();
4450
+ return (0, import_node_child_process4.execSync)("git rev-parse HEAD", { cwd, encoding: "utf8", timeout: 5e3 }).trim();
4230
4451
  } catch {
4231
4452
  return null;
4232
4453
  }
4233
4454
  }
4234
4455
  function loadDeployManifest(workspaceRoot) {
4235
- const manifestPath = (0, import_node_path.join)(workspaceRoot, ".cva", "deploy.json");
4236
- if (!(0, import_node_fs.existsSync)(manifestPath)) return null;
4456
+ const manifestPath = (0, import_node_path2.join)(workspaceRoot, ".cva", "deploy.json");
4457
+ if (!(0, import_node_fs2.existsSync)(manifestPath)) return null;
4237
4458
  try {
4238
- const raw = (0, import_node_fs.readFileSync)(manifestPath, "utf8");
4459
+ const raw = (0, import_node_fs2.readFileSync)(manifestPath, "utf8");
4239
4460
  const manifest = JSON.parse(raw);
4240
4461
  if (!manifest.version || manifest.version < 1) {
4241
4462
  console.log(" [deploy] Invalid manifest version");
@@ -4260,7 +4481,7 @@ async function postTaskDeploy(workspaceRoot, taskId, log) {
4260
4481
  console.log(` [deploy] Building: ${buildStep.name}`);
4261
4482
  log("lifecycle", `Building: ${buildStep.name}`);
4262
4483
  const start = Date.now();
4263
- const cwd = (0, import_node_path.join)(workspaceRoot, buildStep.working_dir || ".");
4484
+ const cwd = (0, import_node_path2.join)(workspaceRoot, buildStep.working_dir || ".");
4264
4485
  const timeoutMs = (buildStep.timeout_seconds || 300) * 1e3;
4265
4486
  const result = exec(buildStep.command, cwd, timeoutMs);
4266
4487
  if (!result.ok) {
@@ -4351,7 +4572,7 @@ async function rollback(workspaceRoot, targetCommit, manifest, log) {
4351
4572
  }
4352
4573
  if (manifest.build?.steps) {
4353
4574
  for (const step of manifest.build.steps) {
4354
- exec(step.command, (0, import_node_path.join)(workspaceRoot, step.working_dir || "."), (step.timeout_seconds || 300) * 1e3);
4575
+ exec(step.command, (0, import_node_path2.join)(workspaceRoot, step.working_dir || "."), (step.timeout_seconds || 300) * 1e3);
4355
4576
  }
4356
4577
  }
4357
4578
  if (manifest.service?.restart_command) {
@@ -4381,7 +4602,7 @@ function containsAuthError(text) {
4381
4602
  }
4382
4603
  async function checkClaudeAuth() {
4383
4604
  try {
4384
- const output = (0, import_node_child_process4.execSync)("claude --version 2>&1", {
4605
+ const output = (0, import_node_child_process5.execSync)("claude --version 2>&1", {
4385
4606
  encoding: "utf8",
4386
4607
  timeout: 1e4,
4387
4608
  env: { ...process.env }
@@ -4592,7 +4813,7 @@ async function launchAutoApproveMode(prompt, options) {
4592
4813
  if (sessionId && !isContinue) {
4593
4814
  args.push("--session-id", sessionId);
4594
4815
  }
4595
- const child = (0, import_node_child_process4.spawn)("claude", args, {
4816
+ const child = (0, import_node_child_process5.spawn)("claude", args, {
4596
4817
  cwd: options.cwd,
4597
4818
  stdio: ["inherit", "pipe", "pipe"],
4598
4819
  env: options.spawnEnv || { ...process.env }
@@ -4726,7 +4947,7 @@ ${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
4726
4947
  }
4727
4948
  async function launchRelayMode(prompt, options) {
4728
4949
  return new Promise((resolve, reject) => {
4729
- const child = (0, import_node_child_process4.spawn)("claude", [], {
4950
+ const child = (0, import_node_child_process5.spawn)("claude", [], {
4730
4951
  cwd: options.cwd,
4731
4952
  stdio: ["pipe", "pipe", "pipe"],
4732
4953
  env: options.spawnEnv || { ...process.env }
@@ -4952,19 +5173,19 @@ async function handleSelfUpdate(task, state, creds) {
4952
5173
  let output = "";
4953
5174
  if (source === "npm" || source.startsWith("npm:")) {
4954
5175
  const pkg = source === "npm" ? "@controlvector/cv-agent@latest" : source.replace("npm:", "");
4955
- output = (0, import_node_child_process4.execSync)(`npm install -g ${pkg} 2>&1`, { encoding: "utf8", timeout: 12e4 });
5176
+ output = (0, import_node_child_process5.execSync)(`npm install -g ${pkg} 2>&1`, { encoding: "utf8", timeout: 12e4 });
4956
5177
  } else if (source.startsWith("git:")) {
4957
5178
  const repoPath = source.replace("git:", "");
4958
- output = (0, import_node_child_process4.execSync)(`cd ${repoPath} && git pull && npm install && npm run build && npm link 2>&1`, {
5179
+ output = (0, import_node_child_process5.execSync)(`cd ${repoPath} && git pull && npm install && npm run build && npm link 2>&1`, {
4959
5180
  encoding: "utf8",
4960
5181
  timeout: 3e5
4961
5182
  });
4962
5183
  } else {
4963
- output = (0, import_node_child_process4.execSync)(`npm install -g @controlvector/cv-agent@latest 2>&1`, { encoding: "utf8", timeout: 12e4 });
5184
+ output = (0, import_node_child_process5.execSync)(`npm install -g @controlvector/cv-agent@latest 2>&1`, { encoding: "utf8", timeout: 12e4 });
4964
5185
  }
4965
5186
  let newVersion = "unknown";
4966
5187
  try {
4967
- newVersion = (0, import_node_child_process4.execSync)("cva --version 2>/dev/null || echo unknown", { encoding: "utf8" }).trim();
5188
+ newVersion = (0, import_node_child_process5.execSync)("cva --version 2>/dev/null || echo unknown", { encoding: "utf8" }).trim();
4968
5189
  } catch {
4969
5190
  }
4970
5191
  postTaskEvent(creds, task.id, {
@@ -4990,7 +5211,7 @@ async function handleSelfUpdate(task, state, creds) {
4990
5211
  if (task.input?.constraints?.includes("restart")) {
4991
5212
  console.log(source_default.yellow("Restarting agent with updated binary..."));
4992
5213
  const args = process.argv.slice(1).join(" ");
4993
- (0, import_node_child_process4.execSync)(`nohup cva ${args} > /tmp/cva-restart.log 2>&1 &`, { stdio: "ignore" });
5214
+ (0, import_node_child_process5.execSync)(`nohup cva ${args} > /tmp/cva-restart.log 2>&1 &`, { stdio: "ignore" });
4994
5215
  process.exit(0);
4995
5216
  }
4996
5217
  } catch (err) {
@@ -5022,7 +5243,7 @@ async function runAgent(options) {
5022
5243
  process.exit(1);
5023
5244
  }
5024
5245
  try {
5025
- (0, import_node_child_process4.execSync)("claude --version", { stdio: "pipe", timeout: 5e3 });
5246
+ (0, import_node_child_process5.execSync)("claude --version", { stdio: "pipe", timeout: 5e3 });
5026
5247
  } catch {
5027
5248
  console.log();
5028
5249
  console.log(source_default.red("Claude Code CLI not found.") + " Install it first:");
@@ -5051,7 +5272,7 @@ async function runAgent(options) {
5051
5272
  currentAuthStatus = "api_key_fallback";
5052
5273
  }
5053
5274
  try {
5054
- (0, import_node_child_process4.execSync)("cv --version", { stdio: "pipe", timeout: 5e3 });
5275
+ (0, import_node_child_process5.execSync)("cv --version", { stdio: "pipe", timeout: 5e3 });
5055
5276
  } catch {
5056
5277
  console.log(source_default.yellow("!") + " cv-git CLI not found. Claude Code will fall back to raw git commands.");
5057
5278
  console.log(` Install it: ${source_default.cyan("npm install -g @controlvector/cv-git")}`);
@@ -5071,7 +5292,7 @@ async function runAgent(options) {
5071
5292
  }
5072
5293
  let detectedRepoId;
5073
5294
  try {
5074
- const remoteUrl = (0, import_node_child_process4.execSync)("git remote get-url origin 2>/dev/null", {
5295
+ const remoteUrl = (0, import_node_child_process5.execSync)("git remote get-url origin 2>/dev/null", {
5075
5296
  cwd: workingDir,
5076
5297
  encoding: "utf8",
5077
5298
  timeout: 5e3
@@ -5092,8 +5313,47 @@ async function runAgent(options) {
5092
5313
  }
5093
5314
  } catch {
5094
5315
  }
5316
+ const wsConfig = await readWorkspaceConfig(workingDir);
5317
+ const globalConfig = await readConfig();
5318
+ const role = options.role || wsConfig.role || globalConfig.role || "development";
5319
+ const dispatchGuard = options.dispatchGuard || wsConfig.dispatch_guard || globalConfig.dispatch_guard || "open";
5320
+ const tags = (options.tags?.split(",") || wsConfig.tags || globalConfig.tags)?.map((t) => t.trim()).filter(Boolean);
5321
+ const ownerProject = options.ownerProject || wsConfig.owner_project || globalConfig.owner_project;
5322
+ let integration;
5323
+ if (options.integration || wsConfig.integration) {
5324
+ if (options.integration) {
5325
+ const safeTasks = options.safeTasks?.split(",").map((t) => t.trim()).filter(Boolean);
5326
+ const unsafeTasks = safeTasks ? ["code_change", "deploy", "test", "custom"].filter((t) => !safeTasks.includes(t)) : void 0;
5327
+ integration = {
5328
+ system: options.integration,
5329
+ description: options.integrationDescription || `Integrated into ${options.integration}`,
5330
+ service_port: options.integrationPort ? parseInt(options.integrationPort, 10) : void 0,
5331
+ safe_task_types: safeTasks,
5332
+ unsafe_task_types: unsafeTasks
5333
+ };
5334
+ } else if (wsConfig.integration) {
5335
+ integration = wsConfig.integration;
5336
+ }
5337
+ }
5338
+ const executorMeta = {
5339
+ role,
5340
+ dispatch_guard: dispatchGuard,
5341
+ tags,
5342
+ owner_project: ownerProject,
5343
+ integration
5344
+ };
5345
+ if (role !== "development") {
5346
+ console.log(source_default.yellow(` Role: ${role}`));
5347
+ }
5348
+ if (integration) {
5349
+ console.log(source_default.yellow(` Integration: ${integration.system}${integration.service_port ? ` (port ${integration.service_port})` : ""}`));
5350
+ }
5351
+ if (dispatchGuard !== "open") {
5352
+ const guardIcon = dispatchGuard === "locked" ? "\u{1F512}" : "\u26A0\uFE0F";
5353
+ console.log(source_default.yellow(` Guard: ${guardIcon} ${dispatchGuard}`));
5354
+ }
5095
5355
  const executor = await withRetry(
5096
- () => registerExecutor(creds, machineName, workingDir, detectedRepoId),
5356
+ () => registerExecutor(creds, machineName, workingDir, detectedRepoId, executorMeta),
5097
5357
  "Executor registration"
5098
5358
  );
5099
5359
  const mode = options.autoApprove ? "auto-approve" : "relay";
@@ -5441,6 +5701,14 @@ function agentCommand() {
5441
5701
  cmd.option("--poll-interval <seconds>", "How often to check for tasks, minimum 3 (default: 5)", "5");
5442
5702
  cmd.option("--working-dir <path>", "Working directory for Claude Code (default: current directory)", ".");
5443
5703
  cmd.option("--auto-approve", "Pre-approve all tool permissions (uses -p mode)", false);
5704
+ cmd.option("--role <role>", "Executor role: development, production, ci, staging");
5705
+ cmd.option("--integration <system>", "System this executor is integrated into (e.g. nyx-forge)");
5706
+ cmd.option("--integration-description <desc>", "Description of the integration");
5707
+ cmd.option("--integration-port <port>", "Port the integrated service runs on");
5708
+ cmd.option("--safe-tasks <types>", "Comma-separated task types safe for this executor (e.g. review,research)");
5709
+ cmd.option("--dispatch-guard <guard>", "Dispatch guard: open, confirm, locked (default: open)");
5710
+ cmd.option("--tags <tags>", "Comma-separated tags for this executor");
5711
+ cmd.option("--owner-project <project>", "Project this executor belongs to");
5444
5712
  cmd.action(async (opts) => {
5445
5713
  await runAgent(opts);
5446
5714
  });
@@ -5548,7 +5816,7 @@ function authCommand() {
5548
5816
  }
5549
5817
 
5550
5818
  // src/commands/remote.ts
5551
- var import_node_child_process5 = require("node:child_process");
5819
+ var import_node_child_process6 = require("node:child_process");
5552
5820
  function getGitHost(apiUrl) {
5553
5821
  return apiUrl.replace(/^https?:\/\//, "").replace(/^api\./, "git.");
5554
5822
  }
@@ -5563,16 +5831,16 @@ async function remoteAdd(ownerRepo) {
5563
5831
  }
5564
5832
  const remoteUrl = `https://${gitHost}/${owner}/${repo}.git`;
5565
5833
  try {
5566
- const existing = (0, import_node_child_process5.execSync)("git remote get-url cvhub", { encoding: "utf-8", timeout: 5e3 }).trim();
5834
+ const existing = (0, import_node_child_process6.execSync)("git remote get-url cvhub", { encoding: "utf-8", timeout: 5e3 }).trim();
5567
5835
  if (existing === remoteUrl) {
5568
5836
  console.log(source_default.gray(`Remote 'cvhub' already set to ${remoteUrl}`));
5569
5837
  return;
5570
5838
  }
5571
- (0, import_node_child_process5.execSync)(`git remote set-url cvhub ${remoteUrl}`, { timeout: 5e3 });
5839
+ (0, import_node_child_process6.execSync)(`git remote set-url cvhub ${remoteUrl}`, { timeout: 5e3 });
5572
5840
  console.log(source_default.green("Updated") + ` remote 'cvhub' -> ${remoteUrl}`);
5573
5841
  } catch {
5574
5842
  try {
5575
- (0, import_node_child_process5.execSync)(`git remote add cvhub ${remoteUrl}`, { timeout: 5e3 });
5843
+ (0, import_node_child_process6.execSync)(`git remote add cvhub ${remoteUrl}`, { timeout: 5e3 });
5576
5844
  console.log(source_default.green("Added") + ` remote 'cvhub' -> ${remoteUrl}`);
5577
5845
  } catch (err) {
5578
5846
  console.log(source_default.red("Failed to add remote:") + ` ${err.message}`);
@@ -5764,7 +6032,8 @@ function statusCommand() {
5764
6032
 
5765
6033
  // src/index.ts
5766
6034
  var program2 = new Command();
5767
- program2.name("cva").description("CV-Hub Agent \u2014 bridges Claude Code with CV-Hub task dispatch").version(true ? "1.5.0" : "1.1.0");
6035
+ 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.7.0" : "1.6.0");
6036
+ program2.addCommand(setupCommand());
5768
6037
  program2.addCommand(agentCommand());
5769
6038
  program2.addCommand(authCommand());
5770
6039
  program2.addCommand(remoteCommand());