@controlvector/cv-agent 1.7.1 → 1.9.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
@@ -3665,6 +3665,92 @@ 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
+ }
3668
3754
  function checkBinary(cmd) {
3669
3755
  try {
3670
3756
  return (0, import_node_child_process.execSync)(`${cmd} 2>&1`, { encoding: "utf8", timeout: 5e3 }).trim().split("\n")[0];
@@ -3724,38 +3810,20 @@ async function runSetup() {
3724
3810
  if (!token) {
3725
3811
  console.log(" Let's connect you to CV-Hub.");
3726
3812
  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
3813
  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."));
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>"));
3752
3825
  process.exit(1);
3753
3826
  }
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
3827
  }
3760
3828
  console.log();
3761
3829
  console.log(" Claude.ai MCP connector:");
@@ -3766,18 +3834,84 @@ async function runSetup() {
3766
3834
  console.log(source_default.gray(' Click "Add Integration" \u2192 "Allow" when prompted.'));
3767
3835
  console.log(source_default.gray(" (You can do this later \u2014 setup will continue.)"));
3768
3836
  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);
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);
3774
3840
  if (isGitRepo) {
3775
3841
  console.log(source_default.green(" \u2713") + ` Git repo found: ${repoName}`);
3776
3842
  } 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");
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((resolve) => {
3851
+ rl.question(" Choose [a/b]: ", (answer) => {
3852
+ rl.close();
3853
+ resolve(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((resolve) => {
3882
+ rl2.question(` Select a repo [1-${displayRepos.length}]: `, (answer) => {
3883
+ rl2.close();
3884
+ resolve(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
+ }
3780
3912
  }
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"));
3781
3915
  if (!hasClaudeMd) {
3782
3916
  const template = `# ${repoName}
3783
3917
 
@@ -3827,6 +3961,20 @@ async function runSetup() {
3827
3961
  }
3828
3962
  } catch {
3829
3963
  }
3964
+ try {
3965
+ const credStorePath = (0, import_node_path.join)((0, import_node_os2.homedir)(), ".git-credentials");
3966
+ const credLine = `https://${username}:${token}@${gitHost}`;
3967
+ let existing = "";
3968
+ try {
3969
+ existing = (0, import_node_fs.readFileSync)(credStorePath, "utf-8");
3970
+ } catch {
3971
+ }
3972
+ if (!existing.includes(gitHost)) {
3973
+ (0, import_node_fs.writeFileSync)(credStorePath, existing + credLine + "\n", { mode: 384 });
3974
+ }
3975
+ (0, import_node_child_process.execSync)(`git config --global credential.helper store`, { stdio: "pipe" });
3976
+ } catch {
3977
+ }
3830
3978
  try {
3831
3979
  (0, import_node_child_process.execSync)("git log --oneline -1", { cwd, stdio: "pipe" });
3832
3980
  const status = (0, import_node_child_process.execSync)("git status --porcelain", { cwd, encoding: "utf8" }).trim();
@@ -3851,16 +3999,76 @@ async function runSetup() {
3851
3999
  }
3852
4000
  }
3853
4001
  console.log();
4002
+ let agentStatus = "";
4003
+ const pidFile = (0, import_node_path.join)((0, import_node_os2.homedir)(), ".config", "controlvector", "agent.pid");
4004
+ let agentRunning = false;
4005
+ try {
4006
+ const pid = parseInt((0, import_node_fs.readFileSync)(pidFile, "utf-8").trim(), 10);
4007
+ if (pid > 0) {
4008
+ process.kill(pid, 0);
4009
+ agentRunning = true;
4010
+ agentStatus = `running (PID ${pid})`;
4011
+ console.log(source_default.green(" \u2713") + ` Agent already running (PID ${pid})`);
4012
+ }
4013
+ } catch {
4014
+ }
4015
+ if (!agentRunning) {
4016
+ const readline = await import("node:readline");
4017
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
4018
+ const answer = await new Promise((resolve) => {
4019
+ rl.question(" Start the CV-Agent daemon? (Y/n): ", (a) => {
4020
+ rl.close();
4021
+ resolve(a.trim().toLowerCase() || "y");
4022
+ });
4023
+ });
4024
+ if (answer === "y" || answer === "yes" || answer === "") {
4025
+ const machName = (0, import_node_os2.hostname)().toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/-+/g, "-");
4026
+ console.log(source_default.gray(` Starting agent as "${machName}"...`));
4027
+ try {
4028
+ const { spawn: spawnChild } = await import("node:child_process");
4029
+ const child = spawnChild("cva", [
4030
+ "agent",
4031
+ "--auto-approve",
4032
+ "--machine",
4033
+ machName,
4034
+ "--working-dir",
4035
+ cwd
4036
+ ], {
4037
+ detached: true,
4038
+ stdio: "ignore",
4039
+ env: { ...process.env }
4040
+ });
4041
+ child.unref();
4042
+ if (child.pid) {
4043
+ (0, import_node_fs.mkdirSync)((0, import_node_path.join)((0, import_node_os2.homedir)(), ".config", "controlvector"), { recursive: true });
4044
+ (0, import_node_fs.writeFileSync)(pidFile, String(child.pid), { mode: 384 });
4045
+ agentStatus = `running (PID ${child.pid})`;
4046
+ console.log(source_default.green(" \u2713") + ` Agent started (PID ${child.pid}) \u2014 executor "${machName}"`);
4047
+ }
4048
+ } catch (err) {
4049
+ console.log(source_default.yellow(` Could not start agent: ${err.message}`));
4050
+ console.log(source_default.gray(" Start manually with: cva agent --auto-approve"));
4051
+ agentStatus = "not started";
4052
+ }
4053
+ } else {
4054
+ agentStatus = "not started";
4055
+ console.log(source_default.gray(" Start anytime with: cva agent --auto-approve"));
4056
+ }
4057
+ }
4058
+ console.log();
3854
4059
  console.log(source_default.bold(" Setup Complete"));
3855
4060
  console.log(source_default.gray(" " + "\u2500".repeat(40)));
3856
4061
  console.log(` ${source_default.green("\u2713")} Authenticated as: ${source_default.cyan(username)}`);
3857
4062
  console.log(` ${source_default.green("\u2713")} Repository: ${source_default.cyan(repoName)}`);
3858
4063
  console.log(` ${source_default.green("\u2713")} CLAUDE.md: present`);
4064
+ if (agentStatus.startsWith("running")) {
4065
+ console.log(` ${source_default.green("\u2713")} Agent daemon: ${source_default.cyan(agentStatus)}`);
4066
+ }
3859
4067
  console.log(source_default.gray(" " + "\u2500".repeat(40)));
3860
4068
  console.log();
3861
4069
  console.log(" What's next:");
3862
- console.log(` ${source_default.cyan("cva agent --auto-approve")} \u2014 Start listening for tasks`);
3863
- console.log(` Or open Claude.ai and dispatch a task to this repo.`);
4070
+ console.log(" Open Claude.ai and try:");
4071
+ console.log(source_default.cyan(` "Create a task in ${repoName} to add a hello world index.html"`));
3864
4072
  console.log();
3865
4073
  console.log(source_default.gray(` Dashboard: ${appUrl}`));
3866
4074
  console.log();
@@ -5328,6 +5536,64 @@ async function runAgent(options) {
5328
5536
  console.log();
5329
5537
  }
5330
5538
  }
5539
+ {
5540
+ const isGitRepo = require("fs").existsSync(require("path").join(workingDir, ".git"));
5541
+ if (!isGitRepo) {
5542
+ console.log(source_default.gray(" Bootstrap: initializing git repo..."));
5543
+ try {
5544
+ (0, import_node_child_process5.execSync)("git init && git checkout -b main", { cwd: workingDir, stdio: "pipe" });
5545
+ } catch {
5546
+ }
5547
+ }
5548
+ const claudeMdPath = require("path").join(workingDir, "CLAUDE.md");
5549
+ if (!require("fs").existsSync(claudeMdPath)) {
5550
+ const name = require("path").basename(workingDir);
5551
+ const template = `# ${name}
5552
+
5553
+ ## Overview
5554
+ [Describe your project here]
5555
+
5556
+ ## Build & Run
5557
+ [How to build and run this project]
5558
+ `;
5559
+ try {
5560
+ require("fs").writeFileSync(claudeMdPath, template);
5561
+ console.log(source_default.gray(" Bootstrap: created CLAUDE.md"));
5562
+ } catch {
5563
+ }
5564
+ }
5565
+ const cvDir = require("path").join(workingDir, ".cv");
5566
+ if (!require("fs").existsSync(cvDir)) {
5567
+ try {
5568
+ require("fs").mkdirSync(cvDir, { recursive: true });
5569
+ } catch {
5570
+ }
5571
+ }
5572
+ if (creds.CV_HUB_PAT) {
5573
+ try {
5574
+ const existing = (0, import_node_child_process5.execSync)('git remote get-url cv-hub 2>/dev/null || echo ""', {
5575
+ cwd: workingDir,
5576
+ encoding: "utf8",
5577
+ timeout: 5e3
5578
+ }).trim();
5579
+ if (!existing) {
5580
+ const name = require("path").basename(workingDir);
5581
+ const gitHost = "git.hub.controlvector.io";
5582
+ let user = "user";
5583
+ try {
5584
+ const sharedPath = require("path").join(require("os").homedir(), ".config", "controlvector", "credentials.json");
5585
+ const shared = JSON.parse(require("fs").readFileSync(sharedPath, "utf-8"));
5586
+ if (shared.username) user = shared.username;
5587
+ } catch {
5588
+ }
5589
+ const remoteUrl = `https://${gitHost}/${user}/${name}.git`;
5590
+ (0, import_node_child_process5.execSync)(`git remote add cv-hub ${remoteUrl}`, { cwd: workingDir, stdio: "pipe" });
5591
+ console.log(source_default.gray(` Bootstrap: remote cv-hub \u2192 ${remoteUrl}`));
5592
+ }
5593
+ } catch {
5594
+ }
5595
+ }
5596
+ }
5331
5597
  let detectedRepoId;
5332
5598
  try {
5333
5599
  const remoteUrl = (0, import_node_child_process5.execSync)("git remote get-url origin 2>/dev/null", {
@@ -6070,7 +6336,7 @@ function statusCommand() {
6070
6336
 
6071
6337
  // src/index.ts
6072
6338
  var program2 = new Command();
6073
- 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.1" : "1.6.0");
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");
6074
6340
  program2.addCommand(setupCommand());
6075
6341
  program2.addCommand(agentCommand());
6076
6342
  program2.addCommand(authCommand());