@controlvector/cv-agent 1.9.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,106 +6,30 @@
6
6
  * Ensures all changes are committed and pushed, because Claude Code
7
7
  * cannot be trusted to do this reliably.
8
8
  *
9
- * SAFETY GUARDS (Bug 3 fix, 2026-04-11):
10
- * - REFUSES to run if workspace is user's HOME directory
11
- * - REFUSES to run if workspace has no .git directory
12
- * - Filters out sensitive files from staging (.claude/, .zsh_history, etc.)
13
- * - Logs warnings instead of silently committing dangerous paths
9
+ * Evidence: ANAX Artifact Registry task (2026-04-03) — Claude Code wrote
10
+ * an entire module and didn't git-add a single file.
14
11
  */
15
12
  Object.defineProperty(exports, "__esModule", { value: true });
16
13
  exports.gitSafetyNet = gitSafetyNet;
17
14
  const node_child_process_1 = require("node:child_process");
18
- const node_fs_1 = require("node:fs");
19
- const node_path_1 = require("node:path");
20
- const node_os_1 = require("node:os");
21
- /** Files/directories that must NEVER be committed by the safety net */
22
- const DANGEROUS_PATTERNS = [
23
- '.claude/',
24
- '.claude.json',
25
- '.credentials',
26
- '.zsh_history',
27
- '.bash_history',
28
- '.zsh_sessions/',
29
- '.ssh/',
30
- '.gnupg/',
31
- '.npm/',
32
- '.config/',
33
- '.CFUserTextEncoding',
34
- 'Library/',
35
- 'Applications/',
36
- '.Trash/',
37
- '.DS_Store',
38
- 'node_modules/',
39
- '.env',
40
- '.env.local',
41
- '.env.production',
42
- ];
43
- function git(cmd, cwd) {
44
- return (0, node_child_process_1.execSync)(cmd, { cwd, encoding: 'utf8', timeout: 30_000 }).trim();
45
- }
46
- /**
47
- * Check if a workspace is safe for auto-commit operations.
48
- * Returns an error string if unsafe, null if safe.
49
- */
50
- function checkWorkspaceSafety(workspaceRoot) {
51
- const resolved = (0, node_path_1.resolve)(workspaceRoot);
52
- const home = (0, node_path_1.resolve)((0, node_os_1.homedir)());
53
- // NEVER auto-commit from user's HOME directory
54
- if (resolved === home) {
55
- return `Workspace is user HOME directory (${home}). Refusing to auto-commit to prevent indexing personal files.`;
56
- }
57
- // NEVER auto-commit from a parent of HOME (e.g., /Users or /)
58
- if (home.startsWith(resolved + '/')) {
59
- return `Workspace (${resolved}) is a parent of HOME. Refusing to auto-commit.`;
60
- }
61
- // Must have a .git directory
62
- if (!(0, node_fs_1.existsSync)((0, node_path_1.join)(resolved, '.git'))) {
63
- return `No .git directory in ${resolved}. Not a git repository.`;
64
- }
65
- return null;
66
- }
67
15
  /**
68
- * Filter out dangerous files from git status output.
69
- * Returns only the safe lines.
16
+ * Execute a git command, return stdout or throw.
70
17
  */
71
- function filterDangerousFiles(statusLines) {
72
- const safe = [];
73
- const blocked = [];
74
- for (const line of statusLines) {
75
- const filePath = line.substring(3).trim(); // Remove status prefix (e.g., "?? " or " M ")
76
- const isDangerous = DANGEROUS_PATTERNS.some(pattern => filePath.startsWith(pattern) || filePath.includes('/' + pattern));
77
- if (isDangerous) {
78
- blocked.push(filePath);
79
- }
80
- else {
81
- safe.push(line);
82
- }
83
- }
84
- return { safe, blocked };
18
+ function git(cmd, cwd) {
19
+ return (0, node_child_process_1.execSync)(cmd, { cwd, encoding: 'utf8', timeout: 30_000 }).trim();
85
20
  }
86
21
  /**
87
22
  * Ensure all changes are committed and pushed after Claude Code exits.
88
23
  *
89
- * Safety guards:
90
- * - Refuses if workspace is HOME directory
91
- * - Refuses if no .git directory exists
92
- * - Filters out sensitive files (.claude/, .zsh_history, etc.)
93
- * - Only stages files that pass the safety filter
24
+ * 1. Check for uncommitted changes (untracked, modified, deleted)
25
+ * 2. If any: git add -A, commit with task metadata, push
26
+ * 3. If no local changes but unpushed commits: push them
27
+ * 4. Return structured result for task reporting
94
28
  */
95
29
  function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
96
30
  const targetBranch = branch || 'main';
97
- // ── Safety check: refuse to run in dangerous directories ──────────
98
- const safetyError = checkWorkspaceSafety(workspaceRoot);
99
- if (safetyError) {
100
- console.log(` [git-safety] BLOCKED: ${safetyError}`);
101
- return {
102
- hadChanges: false, filesAdded: 0, filesModified: 0,
103
- filesDeleted: 0, pushed: false, skipped: true,
104
- skipReason: safetyError,
105
- };
106
- }
107
31
  try {
108
- // 1. Check for uncommitted changes
32
+ // 1. Check for ANY uncommitted changes
109
33
  let statusOutput;
110
34
  try {
111
35
  statusOutput = git('git status --porcelain', workspaceRoot);
@@ -113,8 +37,8 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
113
37
  catch {
114
38
  return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false, error: 'git status failed' };
115
39
  }
116
- const allLines = statusOutput.split('\n').filter(Boolean);
117
- if (allLines.length === 0) {
40
+ const lines = statusOutput.split('\n').filter(Boolean);
41
+ if (lines.length === 0) {
118
42
  // No local changes — check for unpushed commits
119
43
  try {
120
44
  const unpushed = git(`git log origin/${targetBranch}..HEAD --oneline 2>/dev/null`, workspaceRoot);
@@ -125,22 +49,13 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
125
49
  }
126
50
  }
127
51
  catch {
128
- // No remote tracking or push failed
52
+ // No remote tracking or push failed — not critical
129
53
  }
130
54
  return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false };
131
55
  }
132
- // 2. Filter out dangerous files
133
- const { safe: safeLines, blocked } = filterDangerousFiles(allLines);
134
- if (blocked.length > 0) {
135
- console.log(` [git-safety] Blocked ${blocked.length} sensitive file(s) from staging: ${blocked.slice(0, 5).join(', ')}${blocked.length > 5 ? '...' : ''}`);
136
- }
137
- if (safeLines.length === 0) {
138
- console.log(` [git-safety] All ${allLines.length} changed files were blocked by safety filter`);
139
- return { hadChanges: false, filesAdded: 0, filesModified: 0, filesDeleted: 0, pushed: false };
140
- }
141
- // 3. Parse safe changes
56
+ // 2. Parse what Claude Code left behind
142
57
  let added = 0, modified = 0, deleted = 0;
143
- for (const line of safeLines) {
58
+ for (const line of lines) {
144
59
  const code = line.substring(0, 2);
145
60
  if (code.includes('?'))
146
61
  added++;
@@ -149,21 +64,13 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
149
64
  else if (code.includes('M') || code.includes('A'))
150
65
  modified++;
151
66
  else
152
- added++;
67
+ added++; // Treat unknown status as added
153
68
  }
154
- console.log(` [git-safety] ${safeLines.length} safe changes ` +
69
+ console.log(` [git-safety] ${lines.length} uncommitted changes ` +
155
70
  `(${added} new, ${modified} modified, ${deleted} deleted) — committing now`);
156
- // 4. Stage only safe files (NOT git add -A)
157
- for (const line of safeLines) {
158
- const filePath = line.substring(3).trim();
159
- try {
160
- git(`git add -- ${JSON.stringify(filePath)}`, workspaceRoot);
161
- }
162
- catch {
163
- // Individual file add failed — skip it
164
- }
165
- }
166
- // 5. Commit with task metadata
71
+ // 3. Stage everything
72
+ git('git add -A', workspaceRoot);
73
+ // 4. Commit with task metadata
167
74
  const shortId = taskId.substring(0, 8);
168
75
  const commitMsg = `task: ${taskTitle} [${shortId}]\n\nAuto-committed by cv-agent git safety net.\nTask ID: ${taskId}\nFiles: ${added} added, ${modified} modified, ${deleted} deleted`;
169
76
  let commitSha;
@@ -173,6 +80,7 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
173
80
  commitSha = shaMatch ? shaMatch[1] : undefined;
174
81
  }
175
82
  catch (e) {
83
+ // Commit may fail if there's nothing to commit after add
176
84
  if (!e.message?.includes('nothing to commit')) {
177
85
  return {
178
86
  hadChanges: true, filesAdded: added, filesModified: modified,
@@ -180,7 +88,7 @@ function gitSafetyNet(workspaceRoot, taskTitle, taskId, branch) {
180
88
  };
181
89
  }
182
90
  }
183
- // 6. Push
91
+ // 5. Push
184
92
  try {
185
93
  git(`git push origin ${targetBranch}`, workspaceRoot);
186
94
  console.log(` [git-safety] Committed and pushed: ${commitSha || 'ok'}`);
@@ -1 +1 @@
1
- {"version":3,"file":"git-safety.js","sourceRoot":"","sources":["../../src/commands/git-safety.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"git-safety.js","sourceRoot":"","sources":["../../src/commands/git-safety.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsjBpC,wBAAgB,YAAY,IAAI,OAAO,CAKtC"}
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAmQpC,wBAAgB,YAAY,IAAI,OAAO,CAKtC"}
@@ -60,104 +60,6 @@ async function validateToken(hubUrl, token) {
60
60
  }
61
61
  }
62
62
  // ============================================================================
63
- // OAuth Device Authorization Flow (RFC 8628)
64
- // ============================================================================
65
- const DEVICE_CLIENT_ID = 'cv-agent-cli';
66
- const DEVICE_SCOPES = 'repo:read repo:write profile offline_access';
67
- const POLL_INTERVAL_MS = 5000;
68
- const MAX_POLL_ATTEMPTS = 180; // 15 minutes
69
- async function deviceAuthFlow(hubUrl, appUrl) {
70
- // Step 1: Request device authorization
71
- const authRes = await fetch(`${hubUrl}/oauth/device/authorize`, {
72
- method: 'POST',
73
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
74
- body: new URLSearchParams({
75
- client_id: DEVICE_CLIENT_ID,
76
- scope: DEVICE_SCOPES,
77
- }),
78
- });
79
- if (!authRes.ok) {
80
- const err = await authRes.json().catch(() => ({}));
81
- throw new Error(err.error_description || `Device auth failed: ${authRes.status}`);
82
- }
83
- const auth = await authRes.json();
84
- // Step 2: Display code and open browser
85
- console.log(chalk_1.default.bold(' ┌──────────────────────────────────────────┐'));
86
- console.log(chalk_1.default.bold(' │ CV-Hub Device Authorization │'));
87
- console.log(chalk_1.default.bold(' ├──────────────────────────────────────────┤'));
88
- console.log(chalk_1.default.bold(' │ │'));
89
- console.log(chalk_1.default.bold(' │ Open this URL in your browser: │'));
90
- console.log(chalk_1.default.bold(` │ ${chalk_1.default.cyan(auth.verification_uri).padEnd(51)}│`));
91
- console.log(chalk_1.default.bold(' │ │'));
92
- console.log(chalk_1.default.bold(' │ Then enter this code: │'));
93
- console.log(chalk_1.default.bold(` │ ${chalk_1.default.white.bold(auth.user_code)} │`));
94
- console.log(chalk_1.default.bold(' │ │'));
95
- console.log(chalk_1.default.bold(` │ ${chalk_1.default.gray(`Expires in ${Math.floor(auth.expires_in / 60)} minutes`).padEnd(51)}│`));
96
- console.log(chalk_1.default.bold(' └──────────────────────────────────────────┘'));
97
- console.log();
98
- // Try to open browser
99
- try {
100
- const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
101
- (0, node_child_process_1.execSync)(`${openCmd} "${auth.verification_uri_complete}" 2>/dev/null`, { timeout: 5000 });
102
- console.log(chalk_1.default.gray(' Browser opened. Waiting for authorization...'));
103
- }
104
- catch {
105
- console.log(chalk_1.default.gray(' Open the URL above in your browser.'));
106
- }
107
- // Step 3: Poll for token
108
- let interval = Math.max(auth.interval * 1000, POLL_INTERVAL_MS);
109
- const expireTime = Date.now() + auth.expires_in * 1000;
110
- for (let attempt = 0; attempt < MAX_POLL_ATTEMPTS; attempt++) {
111
- await new Promise(r => setTimeout(r, interval));
112
- if (Date.now() > expireTime) {
113
- throw new Error('Authorization timed out');
114
- }
115
- const remaining = Math.ceil((expireTime - Date.now()) / 1000);
116
- const mins = Math.floor(remaining / 60);
117
- const secs = remaining % 60;
118
- process.stdout.write(`\r Waiting for authorization... (${mins}:${secs.toString().padStart(2, '0')} remaining) `);
119
- const tokenRes = await fetch(`${hubUrl}/oauth/token`, {
120
- method: 'POST',
121
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
122
- body: new URLSearchParams({
123
- grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
124
- device_code: auth.device_code,
125
- client_id: DEVICE_CLIENT_ID,
126
- }),
127
- });
128
- const tokenData = await tokenRes.json();
129
- if (tokenData.access_token) {
130
- process.stdout.write('\r' + ' '.repeat(60) + '\r');
131
- // Get username
132
- let uname = 'user';
133
- try {
134
- const userRes = await fetch(`${hubUrl}/oauth/userinfo`, {
135
- headers: { Authorization: `Bearer ${tokenData.access_token}` },
136
- });
137
- if (userRes.ok) {
138
- const userInfo = await userRes.json();
139
- uname = userInfo.preferred_username || userInfo.name || 'user';
140
- }
141
- }
142
- catch { /* use default */ }
143
- return { token: tokenData.access_token, username: uname };
144
- }
145
- if (tokenData.error === 'slow_down') {
146
- interval += 5000;
147
- }
148
- else if (tokenData.error === 'access_denied') {
149
- process.stdout.write('\r' + ' '.repeat(60) + '\r');
150
- throw new Error('Authorization denied by user');
151
- }
152
- else if (tokenData.error === 'expired_token') {
153
- process.stdout.write('\r' + ' '.repeat(60) + '\r');
154
- throw new Error('Authorization expired');
155
- }
156
- // authorization_pending → keep polling
157
- }
158
- throw new Error('Authorization timed out');
159
- }
160
- // ============================================================================
161
63
  // Preflight Checks
162
64
  // ============================================================================
163
65
  function checkBinary(cmd) {
@@ -230,25 +132,44 @@ async function runSetup() {
230
132
  if (!token) {
231
133
  console.log(' Let\'s connect you to CV-Hub.');
232
134
  console.log();
233
- // Use OAuth Device Authorization flow (RFC 8628)
234
- // Same flow as cv-git: CLI gets a code, user approves in browser, CLI gets token
135
+ const autoName = `${(0, node_os_1.hostname)()}-${new Date().toISOString().slice(0, 10)}`;
136
+ const tokenUrl = `${appUrl}/settings/tokens/new?name=${encodeURIComponent(autoName)}&scopes=agent,repo`;
137
+ console.log(chalk_1.default.gray(` Opening: ${tokenUrl}`));
138
+ // Try to open browser
235
139
  try {
236
- const deviceResult = await deviceAuthFlow(hubUrl, appUrl);
237
- token = deviceResult.token;
238
- username = deviceResult.username;
239
- console.log(chalk_1.default.green(' ✓') + ` Authenticated as ${chalk_1.default.bold(username)}`);
240
- // Save to shared credentials
241
- writeSharedCreds({ hub_url: hubUrl, token, username, created_at: new Date().toISOString() });
242
- // Also save to cv-hub credentials for backward compatibility
243
- await (0, credentials_js_1.writeCredentialField)('CV_HUB_PAT', token);
244
- await (0, credentials_js_1.writeCredentialField)('CV_HUB_API', hubUrl);
140
+ const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
141
+ (0, node_child_process_1.execSync)(`${openCmd} "${tokenUrl}" 2>/dev/null`, { timeout: 5000 });
142
+ }
143
+ catch {
144
+ console.log(chalk_1.default.gray(' (Could not open browser — copy the URL above)'));
245
145
  }
246
- catch (err) {
247
- console.log(chalk_1.default.red(` Authentication failed: ${err.message}`));
248
- console.log(chalk_1.default.gray(' You can retry with: cva setup'));
249
- console.log(chalk_1.default.gray(' Or manually: cva auth login --token <your-pat>'));
146
+ console.log();
147
+ // Prompt for token
148
+ const readline = await import('node:readline');
149
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
150
+ token = await new Promise((resolve) => {
151
+ rl.question(' Paste your token here: ', (answer) => {
152
+ rl.close();
153
+ resolve(answer.trim());
154
+ });
155
+ });
156
+ if (!token) {
157
+ console.log(chalk_1.default.red(' No token provided. Run cva setup again.'));
158
+ process.exit(1);
159
+ }
160
+ // Validate
161
+ const user = await validateToken(hubUrl, token);
162
+ if (!user) {
163
+ console.log(chalk_1.default.red(' Token validation failed. Check your token and try again.'));
250
164
  process.exit(1);
251
165
  }
166
+ username = user;
167
+ console.log(chalk_1.default.green(' ✓') + ` Authenticated as ${chalk_1.default.bold(user)}`);
168
+ // Save to shared credentials
169
+ writeSharedCreds({ hub_url: hubUrl, token, username, created_at: new Date().toISOString() });
170
+ // Also save to cv-hub credentials for backward compatibility
171
+ await (0, credentials_js_1.writeCredentialField)('CV_HUB_PAT', token);
172
+ await (0, credentials_js_1.writeCredentialField)('CV_HUB_API', hubUrl);
252
173
  }
253
174
  console.log();
254
175
  // ── Step 3: Claude.ai MCP Connector ────────────────────────────────
@@ -261,90 +182,19 @@ async function runSetup() {
261
182
  console.log(chalk_1.default.gray(' (You can do this later — setup will continue.)'));
262
183
  console.log();
263
184
  // ── Step 4: Repository Setup ───────────────────────────────────────
264
- let cwd = process.cwd();
265
- let isGitRepo = (0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, '.git'));
266
- let repoName = (0, node_path_1.basename)(cwd);
185
+ const cwd = process.cwd();
186
+ const isGitRepo = (0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, '.git'));
187
+ const hasCVDir = (0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, '.cv'));
188
+ const hasClaudeMd = (0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, 'CLAUDE.md'));
189
+ const repoName = (0, node_path_1.basename)(cwd);
267
190
  if (isGitRepo) {
268
191
  console.log(chalk_1.default.green(' ✓') + ` Git repo found: ${repoName}`);
269
192
  }
270
193
  else {
271
- // Offer options: init, clone, or change dir
272
- const readline = await import('node:readline');
273
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
274
- console.log(' No git repository found in this directory.');
275
- console.log();
276
- console.log(` ${chalk_1.default.cyan('a')} — Initialize a new project here: ${cwd}`);
277
- console.log(` ${chalk_1.default.cyan('b')} — Clone an existing repo from CV-Hub`);
278
- console.log();
279
- const choice = await new Promise((resolve) => {
280
- rl.question(' Choose [a/b]: ', (answer) => {
281
- rl.close();
282
- resolve(answer.trim().toLowerCase() || 'a');
283
- });
284
- });
285
- if (choice === 'b' && token) {
286
- // Clone from CV-Hub
287
- try {
288
- console.log(chalk_1.default.gray(' Fetching your repositories...'));
289
- const controller = new AbortController();
290
- const timeout = setTimeout(() => controller.abort(), 15_000);
291
- const res = await fetch(`${hubUrl}/api/v1/repos?limit=50`, {
292
- headers: { Authorization: `Bearer ${token}` },
293
- signal: controller.signal,
294
- });
295
- clearTimeout(timeout);
296
- if (res.ok) {
297
- const data = await res.json();
298
- const repos = data.repositories || [];
299
- if (repos.length === 0) {
300
- console.log(chalk_1.default.yellow(' No repos found on CV-Hub. Initializing a new project instead.'));
301
- }
302
- else {
303
- console.log();
304
- console.log(' Your CV-Hub repositories:');
305
- const displayRepos = repos.slice(0, 20);
306
- displayRepos.forEach((r, i) => {
307
- const desc = r.description ? chalk_1.default.gray(` — ${r.description.substring(0, 40)}`) : '';
308
- console.log(` ${chalk_1.default.cyan(String(i + 1).padStart(2))}. ${r.slug || r.name}${desc}`);
309
- });
310
- console.log();
311
- const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
312
- const selection = await new Promise((resolve) => {
313
- rl2.question(` Select a repo [1-${displayRepos.length}]: `, (answer) => {
314
- rl2.close();
315
- resolve(answer.trim());
316
- });
317
- });
318
- const idx = parseInt(selection, 10) - 1;
319
- if (idx >= 0 && idx < displayRepos.length) {
320
- const repo = displayRepos[idx];
321
- const gitHost = 'git.hub.controlvector.io';
322
- const slug = repo.slug || repo.name;
323
- const cloneUrl = `https://${username}:${token}@${gitHost}/${username}/${slug}.git`;
324
- console.log(chalk_1.default.gray(` Cloning ${username}/${slug}...`));
325
- (0, node_child_process_1.execSync)(`git clone ${cloneUrl} ${slug}`, { cwd, stdio: 'pipe', timeout: 60_000 });
326
- cwd = (0, node_path_1.join)(cwd, slug);
327
- process.chdir(cwd);
328
- repoName = slug;
329
- isGitRepo = true;
330
- console.log(chalk_1.default.green(' ✓') + ` Cloned ${username}/${slug}`);
331
- }
332
- }
333
- }
334
- }
335
- catch (err) {
336
- console.log(chalk_1.default.yellow(` Could not fetch repos: ${err.message}. Initializing instead.`));
337
- }
338
- }
339
- // If we haven't cloned, init a new repo
340
- if (!isGitRepo) {
341
- console.log(' Initializing git repository...');
342
- (0, node_child_process_1.execSync)('git init && git checkout -b main', { cwd, stdio: 'pipe' });
343
- console.log(chalk_1.default.green(' ✓') + ' Git repo initialized');
344
- }
194
+ console.log(' Initializing git repository...');
195
+ (0, node_child_process_1.execSync)('git init && git checkout -b main', { cwd, stdio: 'pipe' });
196
+ console.log(chalk_1.default.green(' ✓') + ' Git repo initialized');
345
197
  }
346
- const hasClaudeMd = (0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, 'CLAUDE.md'));
347
- const hasCVDir = (0, node_fs_1.existsSync)((0, node_path_1.join)(cwd, '.cv'));
348
198
  if (!hasClaudeMd) {
349
199
  const template = `# ${repoName}\n\n## Overview\n[Describe your project here]\n\n## Tech Stack\n[What languages/frameworks does this project use?]\n\n## Build & Run\n[How to build and run this project]\n`;
350
200
  (0, node_fs_1.writeFileSync)((0, node_path_1.join)(cwd, 'CLAUDE.md'), template);
@@ -363,25 +213,6 @@ async function runSetup() {
363
213
  try {
364
214
  const existingRemote = (0, node_child_process_1.execSync)('git remote get-url cv-hub 2>/dev/null || echo ""', { cwd, encoding: 'utf8' }).trim();
365
215
  if (!existingRemote) {
366
- // Create repo on CV-Hub (non-fatal if it already exists or fails)
367
- try {
368
- const controller = new AbortController();
369
- const timeout = setTimeout(() => controller.abort(), 15_000);
370
- await fetch(`${hubUrl}/api/v1/user/repos`, {
371
- method: 'POST',
372
- headers: {
373
- Authorization: `Bearer ${token}`,
374
- 'Content-Type': 'application/json',
375
- },
376
- body: JSON.stringify({ name: repoName, auto_init: false }),
377
- signal: controller.signal,
378
- });
379
- clearTimeout(timeout);
380
- console.log(chalk_1.default.green(' ✓') + ` Repo created on CV-Hub: ${username}/${repoName}`);
381
- }
382
- catch {
383
- // May already exist — that's fine
384
- }
385
216
  (0, node_child_process_1.execSync)(`git remote add cv-hub ${remoteUrl}`, { cwd, stdio: 'pipe' });
386
217
  console.log(chalk_1.default.green(' ✓') + ` Remote added: cv-hub → ${remoteUrl}`);
387
218
  }
@@ -392,126 +223,19 @@ async function runSetup() {
392
223
  catch {
393
224
  // Remote setup non-fatal
394
225
  }
395
- // Configure git credentials for CV-Hub pushes
396
- try {
397
- const credStorePath = (0, node_path_1.join)((0, node_os_1.homedir)(), '.git-credentials');
398
- const credLine = `https://${username}:${token}@${gitHost}`;
399
- let existing = '';
400
- try {
401
- existing = (0, node_fs_1.readFileSync)(credStorePath, 'utf-8');
402
- }
403
- catch { /* doesn't exist yet */ }
404
- if (!existing.includes(gitHost)) {
405
- (0, node_fs_1.writeFileSync)(credStorePath, existing + credLine + '\n', { mode: 0o600 });
406
- }
407
- (0, node_child_process_1.execSync)(`git config --global credential.helper store`, { stdio: 'pipe' });
408
- }
409
- catch {
410
- // Non-fatal
411
- }
412
- // Initial commit if repo has no commits
413
- try {
414
- (0, node_child_process_1.execSync)('git log --oneline -1', { cwd, stdio: 'pipe' });
415
- // Has commits — check for uncommitted new files
416
- const status = (0, node_child_process_1.execSync)('git status --porcelain', { cwd, encoding: 'utf8' }).trim();
417
- if (status) {
418
- (0, node_child_process_1.execSync)('git add -A', { cwd, stdio: 'pipe' });
419
- (0, node_child_process_1.execSync)('git commit -m "chore: cv-agent setup"', { cwd, stdio: 'pipe' });
420
- console.log(chalk_1.default.green(' ✓') + ' Changes committed');
421
- }
422
- }
423
- catch {
424
- // No commits yet — make initial commit
425
- try {
426
- (0, node_child_process_1.execSync)('git add -A', { cwd, stdio: 'pipe' });
427
- (0, node_child_process_1.execSync)('git commit -m "Initial commit via cv-agent"', { cwd, stdio: 'pipe' });
428
- console.log(chalk_1.default.green(' ✓') + ' Initial commit created');
429
- }
430
- catch { /* empty repo with nothing to commit */ }
431
- }
432
- // Push to CV-Hub
433
- try {
434
- (0, node_child_process_1.execSync)('git push -u cv-hub main 2>&1', { cwd, stdio: 'pipe', timeout: 30_000 });
435
- console.log(chalk_1.default.green(' ✓') + ' Pushed to CV-Hub');
436
- }
437
- catch {
438
- // Push may fail if repo doesn't exist yet or auth issue — non-fatal
439
- console.log(chalk_1.default.gray(' (Push skipped — you can push later with: git push cv-hub main)'));
440
- }
441
- }
442
- console.log();
443
- // ── Step 5: Agent Daemon ────────────────────────────────────────────
444
- let agentStatus = '';
445
- const pidFile = (0, node_path_1.join)((0, node_os_1.homedir)(), '.config', 'controlvector', 'agent.pid');
446
- // Check if agent is already running
447
- let agentRunning = false;
448
- try {
449
- const pid = parseInt((0, node_fs_1.readFileSync)(pidFile, 'utf-8').trim(), 10);
450
- if (pid > 0) {
451
- process.kill(pid, 0); // throws if not running
452
- agentRunning = true;
453
- agentStatus = `running (PID ${pid})`;
454
- console.log(chalk_1.default.green(' ✓') + ` Agent already running (PID ${pid})`);
455
- }
456
- }
457
- catch {
458
- // PID file missing or process dead
459
- }
460
- if (!agentRunning) {
461
- const readline = await import('node:readline');
462
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
463
- const answer = await new Promise((resolve) => {
464
- rl.question(' Start the CV-Agent daemon? (Y/n): ', (a) => {
465
- rl.close();
466
- resolve(a.trim().toLowerCase() || 'y');
467
- });
468
- });
469
- if (answer === 'y' || answer === 'yes' || answer === '') {
470
- const machName = (0, node_os_1.hostname)().toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-');
471
- console.log(chalk_1.default.gray(` Starting agent as "${machName}"...`));
472
- try {
473
- const { spawn: spawnChild } = await import('node:child_process');
474
- const child = spawnChild('cva', [
475
- 'agent', '--auto-approve', '--machine', machName, '--working-dir', cwd,
476
- ], {
477
- detached: true,
478
- stdio: 'ignore',
479
- env: { ...process.env },
480
- });
481
- child.unref();
482
- if (child.pid) {
483
- (0, node_fs_1.mkdirSync)((0, node_path_1.join)((0, node_os_1.homedir)(), '.config', 'controlvector'), { recursive: true });
484
- (0, node_fs_1.writeFileSync)(pidFile, String(child.pid), { mode: 0o600 });
485
- agentStatus = `running (PID ${child.pid})`;
486
- console.log(chalk_1.default.green(' ✓') + ` Agent started (PID ${child.pid}) — executor "${machName}"`);
487
- }
488
- }
489
- catch (err) {
490
- console.log(chalk_1.default.yellow(` Could not start agent: ${err.message}`));
491
- console.log(chalk_1.default.gray(' Start manually with: cva agent --auto-approve'));
492
- agentStatus = 'not started';
493
- }
494
- }
495
- else {
496
- agentStatus = 'not started';
497
- console.log(chalk_1.default.gray(' Start anytime with: cva agent --auto-approve'));
498
- }
499
226
  }
500
227
  console.log();
501
- // ── Step 6: Summary ────────────────────────────────────────────────
228
+ // ── Step 5: Summary ────────────────────────────────────────────────
502
229
  console.log(chalk_1.default.bold(' Setup Complete'));
503
230
  console.log(chalk_1.default.gray(' ' + '─'.repeat(40)));
504
231
  console.log(` ${chalk_1.default.green('✓')} Authenticated as: ${chalk_1.default.cyan(username)}`);
505
232
  console.log(` ${chalk_1.default.green('✓')} Repository: ${chalk_1.default.cyan(repoName)}`);
506
233
  console.log(` ${chalk_1.default.green('✓')} CLAUDE.md: present`);
507
- if (agentStatus.startsWith('running')) {
508
- console.log(` ${chalk_1.default.green('✓')} Agent daemon: ${chalk_1.default.cyan(agentStatus)}`);
509
- }
510
234
  console.log(chalk_1.default.gray(' ' + '─'.repeat(40)));
511
235
  console.log();
512
236
  console.log(' What\'s next:');
513
- console.log(' Open Claude.ai and try:');
514
- console.log(chalk_1.default.cyan(` "Create a task in ${repoName} to add a hello world index.html"`));
237
+ console.log(` ${chalk_1.default.cyan('cva agent --auto-approve')} — Start listening for tasks`);
238
+ console.log(` Or open Claude.ai and dispatch a task to this repo.`);
515
239
  console.log();
516
240
  console.log(chalk_1.default.gray(` Dashboard: ${appUrl}`));
517
241
  console.log();