@jigyasudham/veto 0.8.2 → 0.8.3

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.
Files changed (3) hide show
  1. package/dist/cli.js +106 -46
  2. package/package.json +1 -1
  3. package/src/cli.ts +204 -135
package/dist/cli.js CHANGED
@@ -2,12 +2,12 @@
2
2
  // Veto CLI — entry point for `npx veto init`
3
3
  // Suppress Node experimental warnings (node:sqlite) for clean UX
4
4
  process.removeAllListeners('warning');
5
- import { mkdirSync, existsSync } from 'node:fs';
6
- import { join } from 'node:path';
5
+ import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
6
+ import { join, dirname } from 'node:path';
7
7
  import { homedir } from 'node:os';
8
- const VERSION = '0.8.0';
8
+ const VERSION = '0.8.2';
9
9
  const VETO_DIR = join(homedir(), '.veto');
10
- // Simple inline colors (no chalk needed for the init wizard to avoid ESM issues)
10
+ const HOME = homedir();
11
11
  const c = {
12
12
  bold: (s) => `\x1b[1m${s}\x1b[0m`,
13
13
  green: (s) => `\x1b[32m${s}\x1b[0m`,
@@ -29,6 +29,70 @@ function printBanner() {
29
29
  console.log(c.dim(` v${VERSION}`));
30
30
  console.log('');
31
31
  }
32
+ // Merge veto entry into an existing JSON config file, creating it if needed.
33
+ // Supports both "mcpServers" format (Claude/Gemini/Codex/Cursor/Windsurf)
34
+ // and "servers" format (VS Code).
35
+ function writeVetoConfig(configPath, format) {
36
+ let existing = {};
37
+ if (existsSync(configPath)) {
38
+ try {
39
+ existing = JSON.parse(readFileSync(configPath, 'utf8'));
40
+ }
41
+ catch {
42
+ // Unreadable / invalid JSON — skip to avoid corrupting it
43
+ return 'skipped';
44
+ }
45
+ }
46
+ else {
47
+ mkdirSync(dirname(configPath), { recursive: true });
48
+ }
49
+ const wasEmpty = Object.keys(existing).length === 0;
50
+ if (format === 'mcpServers') {
51
+ const servers = existing.mcpServers ?? {};
52
+ servers['veto'] = { command: 'veto-server' };
53
+ existing.mcpServers = servers;
54
+ }
55
+ else {
56
+ const servers = existing.servers ?? {};
57
+ servers['veto'] = { type: 'stdio', command: 'veto-server' };
58
+ existing.servers = servers;
59
+ }
60
+ writeFileSync(configPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
61
+ return wasEmpty ? 'created' : 'updated';
62
+ }
63
+ // All platforms Veto supports, with their config paths and formats.
64
+ const PLATFORMS = [
65
+ {
66
+ name: 'Claude Code',
67
+ path: join(HOME, '.claude', 'mcp_servers.json'),
68
+ format: 'mcpServers',
69
+ detectionDir: join(HOME, '.claude'),
70
+ },
71
+ {
72
+ name: 'Gemini CLI',
73
+ path: join(HOME, '.gemini', 'settings.json'),
74
+ format: 'mcpServers',
75
+ detectionDir: join(HOME, '.gemini'),
76
+ },
77
+ {
78
+ name: 'Codex CLI',
79
+ path: join(HOME, '.codex', 'config.json'),
80
+ format: 'mcpServers',
81
+ detectionDir: join(HOME, '.codex'),
82
+ },
83
+ {
84
+ name: 'Cursor',
85
+ path: join(HOME, '.cursor', 'mcp.json'),
86
+ format: 'mcpServers',
87
+ detectionDir: join(HOME, '.cursor'),
88
+ },
89
+ {
90
+ name: 'Windsurf',
91
+ path: join(HOME, '.codeium', 'windsurf', 'mcp_config.json'),
92
+ format: 'mcpServers',
93
+ detectionDir: join(HOME, '.codeium', 'windsurf'),
94
+ },
95
+ ];
32
96
  async function initCommand() {
33
97
  printBanner();
34
98
  // 1. Create ~/.veto directory
@@ -39,13 +103,12 @@ async function initCommand() {
39
103
  else {
40
104
  console.log(c.dim(' · ') + `Found existing ${VETO_DIR}`);
41
105
  }
42
- // 2. Initialize SQLite database (imports local.ts which auto-creates tables)
106
+ // 2. Initialize SQLite database
43
107
  process.stdout.write(' · Initializing SQLite database...');
44
108
  const { getDb, getDbPath, saveSession } = await import('./memory/local.js');
45
109
  try {
46
110
  const db = getDb();
47
111
  const dbPath = getDbPath();
48
- // Smoke test: save + retrieve a test session
49
112
  const { session_id } = saveSession({
50
113
  platform: 'claude',
51
114
  summary: 'Veto initialized',
@@ -54,7 +117,6 @@ async function initCommand() {
54
117
  const row = db.prepare('SELECT id FROM sessions WHERE id = ?').get(session_id);
55
118
  if (!row)
56
119
  throw new Error('DB smoke test failed');
57
- // Clean up test row
58
120
  db.prepare('DELETE FROM sessions WHERE id = ?').run(session_id);
59
121
  console.log(c.green(' ✓'));
60
122
  console.log(c.green(' ✓') + ` Database ready at ${dbPath}`);
@@ -65,48 +127,46 @@ async function initCommand() {
65
127
  console.error(c.red(` Error initializing database: ${msg}`));
66
128
  process.exit(1);
67
129
  }
68
- // 3. Print config snippet using the stable "veto-server" bin command
69
- console.log('');
70
- console.log(c.bold(' ┌─ Add Veto to your AI CLI or IDE ───────────────────────────────┐'));
71
- console.log(c.bold(' │') + ' ' + c.bold('│'));
72
- console.log(c.bold(' │') + ' Config file locations: ' + c.bold('│'));
73
- console.log(c.bold(' │') + ' ' + c.bold('│'));
74
- console.log(c.bold(' │') + c.dim(' Claude Code → ~/.claude/mcp_servers.json') + ' ' + c.bold('│'));
75
- console.log(c.bold(' │') + c.dim(' Gemini CLI → ~/.gemini/settings.json') + ' ' + c.bold('│'));
76
- console.log(c.bold(' │') + c.dim(' Codex CLI → ~/.codex/config.json') + ' ' + c.bold('│'));
77
- console.log(c.bold(' │') + c.dim(' Cursor → ~/.cursor/mcp.json') + ' ' + c.bold('│'));
78
- console.log(c.bold(' │') + c.dim(' Windsurf → ~/.codeium/windsurf/mcp_config.json') + ' ' + c.bold('│'));
79
- console.log(c.bold(' │') + ' ' + c.bold('│'));
80
- console.log(c.bold(' │') + ' Claude / Gemini / Codex / Cursor / Windsurf: ' + c.bold('│'));
81
- console.log(c.bold(' │') + ' ' + c.bold('│'));
82
- console.log(c.bold(' │') + c.cyan(' {') + ' ' + c.bold('│'));
83
- console.log(c.bold(' │') + c.cyan(' "mcpServers": {') + ' ' + c.bold('│'));
84
- console.log(c.bold(' │') + c.cyan(' "veto": {') + ' ' + c.bold('│'));
85
- console.log(c.bold(' │') + c.cyan(' "command": "veto-server"') + ' ' + c.bold('│'));
86
- console.log(c.bold(' │') + c.cyan(' }') + ' ' + c.bold('│'));
87
- console.log(c.bold(' │') + c.cyan(' }') + ' ' + c.bold('│'));
88
- console.log(c.bold(' │') + c.cyan(' }') + ' ' + c.bold('│'));
89
- console.log(c.bold(' │') + ' ' + c.bold('│'));
90
- console.log(c.bold(' │') + ' VS Code → .vscode/mcp.json: ' + c.bold('│'));
91
- console.log(c.bold(' │') + ' ' + c.bold('│'));
92
- console.log(c.bold(' │') + c.cyan(' {') + ' ' + c.bold('│'));
93
- console.log(c.bold(' │') + c.cyan(' "servers": {') + ' ' + c.bold('│'));
94
- console.log(c.bold(' │') + c.cyan(' "veto": {') + ' ' + c.bold('│'));
95
- console.log(c.bold(' │') + c.cyan(' "type": "stdio",') + ' ' + c.bold('│'));
96
- console.log(c.bold(' │') + c.cyan(' "command": "veto-server"') + ' ' + c.bold('│'));
97
- console.log(c.bold(' │') + c.cyan(' }') + ' ' + c.bold('│'));
98
- console.log(c.bold(' │') + c.cyan(' }') + ' ' + c.bold('│'));
99
- console.log(c.bold(' │') + c.cyan(' }') + ' ' + c.bold('│'));
100
- console.log(c.bold(' │') + ' ' + c.bold('│'));
101
- console.log(c.bold(' └────────────────────────────────────────────────────────────────┘'));
130
+ // 3. Auto-configure every AI CLI / IDE found on this machine
102
131
  console.log('');
103
- console.log(c.green(' Veto is ready!'));
132
+ console.log(' Configuring all AI tools found on this machine...');
104
133
  console.log('');
105
- console.log(' Next steps:');
106
- console.log(c.dim(' 1.') + ' Add the config above to your AI CLI or IDE config file');
107
- console.log(c.dim(' 2.') + ' Restart the CLI or IDE');
108
- console.log(c.dim(' 3.') + ' Run: veto_status — should return { "status": "running", "version": "' + VERSION + '" }');
134
+ let configured = 0;
135
+ let skipped = 0;
136
+ for (const platform of PLATFORMS) {
137
+ const detected = existsSync(platform.detectionDir);
138
+ if (!detected) {
139
+ console.log(c.dim(' · ') + c.dim(`${platform.name} — not installed, skipping`));
140
+ continue;
141
+ }
142
+ const result = writeVetoConfig(platform.path, platform.format);
143
+ if (result === 'skipped') {
144
+ console.log(c.yellow(' ⚠ ') + `${platform.name} — config unreadable, skipped`);
145
+ skipped++;
146
+ }
147
+ else if (result === 'created') {
148
+ console.log(c.green(' ✓ ') + `${platform.name} — configured`);
149
+ configured++;
150
+ }
151
+ else {
152
+ console.log(c.green(' ✓ ') + `${platform.name} — updated`);
153
+ configured++;
154
+ }
155
+ }
109
156
  console.log('');
157
+ if (configured === 0 && skipped === 0) {
158
+ console.log(c.yellow(' ⚠ No AI tools detected.'));
159
+ console.log(' Install Claude Code, Gemini CLI, or Codex CLI and run veto init again.');
160
+ console.log('');
161
+ }
162
+ else {
163
+ console.log(c.green(` ✓ Veto configured for ${configured} tool${configured !== 1 ? 's' : ''}!`));
164
+ console.log('');
165
+ console.log(' Next steps:');
166
+ console.log(c.dim(' 1.') + ' Restart your AI CLI or IDE');
167
+ console.log(c.dim(' 2.') + ' Run: veto_status — should return { "status": "running", "version": "' + VERSION + '" }');
168
+ console.log('');
169
+ }
110
170
  }
111
171
  // ─── Router ────────────────────────────────────────────────────────────────────
112
172
  const command = process.argv[2] ?? 'init';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jigyasudham/veto",
3
- "version": "0.8.2",
3
+ "version": "0.8.3",
4
4
  "description": "50 agents. 28 skills. 3 AIs. Self-learning. Zero extra cost.",
5
5
  "keywords": [
6
6
  "mcp",
package/src/cli.ts CHANGED
@@ -1,135 +1,204 @@
1
- #!/usr/bin/env node
2
- // Veto CLI — entry point for `npx veto init`
3
-
4
- // Suppress Node experimental warnings (node:sqlite) for clean UX
5
- process.removeAllListeners('warning');
6
-
7
- import { mkdirSync, existsSync } from 'node:fs';
8
- import { join } from 'node:path';
9
- import { homedir } from 'node:os';
10
-
11
- const VERSION = '0.8.0';
12
- const VETO_DIR = join(homedir(), '.veto');
13
-
14
- // Simple inline colors (no chalk needed for the init wizard to avoid ESM issues)
15
- const c = {
16
- bold: (s: string) => `\x1b[1m${s}\x1b[0m`,
17
- green: (s: string) => `\x1b[32m${s}\x1b[0m`,
18
- yellow: (s: string) => `\x1b[33m${s}\x1b[0m`,
19
- cyan: (s: string) => `\x1b[36m${s}\x1b[0m`,
20
- dim: (s: string) => `\x1b[2m${s}\x1b[0m`,
21
- red: (s: string) => `\x1b[31m${s}\x1b[0m`,
22
- };
23
-
24
- function printBanner() {
25
- console.log('');
26
- console.log(c.bold(c.cyan(' ██╗ ██╗███████╗████████╗ ██████╗')));
27
- console.log(c.bold(c.cyan(' ██║ ██║██╔════╝╚══██╔══╝██╔═══██╗')));
28
- console.log(c.bold(c.cyan(' ██║ ██║█████╗ ██║ ██║ ██║')));
29
- console.log(c.bold(c.cyan(' ╚██╗ ██╔╝██╔══╝ ██║ ██║ ██║')));
30
- console.log(c.bold(c.cyan(' ╚████╔╝ ███████╗ ██║ ╚██████╔╝')));
31
- console.log(c.bold(c.cyan(' ╚═══╝ ╚══════╝ ╚═╝ ╚═════╝')));
32
- console.log('');
33
- console.log(c.dim(` 50 agents. 28 skills. 3 AIs. Self-learning. Zero extra cost.`));
34
- console.log(c.dim(` v${VERSION}`));
35
- console.log('');
36
- }
37
-
38
- async function initCommand() {
39
- printBanner();
40
-
41
- // 1. Create ~/.veto directory
42
- if (!existsSync(VETO_DIR)) {
43
- mkdirSync(VETO_DIR, { recursive: true });
44
- console.log(c.green('') + ` Created ${VETO_DIR}`);
45
- } else {
46
- console.log(c.dim(' · ') + `Found existing ${VETO_DIR}`);
47
- }
48
-
49
- // 2. Initialize SQLite database (imports local.ts which auto-creates tables)
50
- process.stdout.write(' · Initializing SQLite database...');
51
- const { getDb, getDbPath, saveSession } = await import('./memory/local.js');
52
-
53
- try {
54
- const db = getDb();
55
- const dbPath = getDbPath();
56
- // Smoke test: save + retrieve a test session
57
- const { session_id } = saveSession({
58
- platform: 'claude',
59
- summary: 'Veto initialized',
60
- context: 'Initial setup via npx veto init',
61
- });
62
- const row = db.prepare('SELECT id FROM sessions WHERE id = ?').get(session_id);
63
- if (!row) throw new Error('DB smoke test failed');
64
- // Clean up test row
65
- db.prepare('DELETE FROM sessions WHERE id = ?').run(session_id);
66
- console.log(c.green(' '));
67
- console.log(c.green(' ✓') + ` Database ready at ${dbPath}`);
68
- } catch (err: unknown) {
69
- console.log(c.red(' ✗'));
70
- const msg = err instanceof Error ? err.message : String(err);
71
- console.error(c.red(` Error initializing database: ${msg}`));
72
- process.exit(1);
73
- }
74
-
75
- // 3. Print config snippet using the stable "veto-server" bin command
76
- console.log('');
77
- console.log(c.bold(' ┌─ Add Veto to your AI CLI or IDE ───────────────────────────────┐'));
78
- console.log(c.bold(' │') + ' ' + c.bold('│'));
79
- console.log(c.bold(' │') + ' Config file locations: ' + c.bold('│'));
80
- console.log(c.bold(' │') + ' ' + c.bold('│'));
81
- console.log(c.bold(' │') + c.dim(' Claude Code → ~/.claude/mcp_servers.json') + ' ' + c.bold('│'));
82
- console.log(c.bold(' │') + c.dim(' Gemini CLI → ~/.gemini/settings.json') + ' ' + c.bold('│'));
83
- console.log(c.bold(' │') + c.dim(' Codex CLI → ~/.codex/config.json') + ' ' + c.bold('│'));
84
- console.log(c.bold(' │') + c.dim(' Cursor → ~/.cursor/mcp.json') + ' ' + c.bold('│'));
85
- console.log(c.bold(' │') + c.dim(' Windsurf → ~/.codeium/windsurf/mcp_config.json') + ' ' + c.bold('│'));
86
- console.log(c.bold(' │') + ' ' + c.bold('│'));
87
- console.log(c.bold(' │') + ' Claude / Gemini / Codex / Cursor / Windsurf: ' + c.bold('│'));
88
- console.log(c.bold(' │') + ' ' + c.bold('│'));
89
- console.log(c.bold(' │') + c.cyan(' {') + ' ' + c.bold('│'));
90
- console.log(c.bold(' │') + c.cyan(' "mcpServers": {') + ' ' + c.bold('│'));
91
- console.log(c.bold(' │') + c.cyan(' "veto": {') + ' ' + c.bold('│'));
92
- console.log(c.bold(' │') + c.cyan(' "command": "veto-server"') + ' ' + c.bold('│'));
93
- console.log(c.bold(' │') + c.cyan(' }') + ' ' + c.bold('│'));
94
- console.log(c.bold(' │') + c.cyan(' }') + ' ' + c.bold('│'));
95
- console.log(c.bold(' │') + c.cyan(' }') + ' ' + c.bold('│'));
96
- console.log(c.bold(' │') + ' ' + c.bold('│'));
97
- console.log(c.bold(' │') + ' VS Code → .vscode/mcp.json: ' + c.bold('│'));
98
- console.log(c.bold(' │') + ' ' + c.bold('│'));
99
- console.log(c.bold(' │') + c.cyan(' {') + ' ' + c.bold('│'));
100
- console.log(c.bold(' │') + c.cyan(' "servers": {') + ' ' + c.bold('│'));
101
- console.log(c.bold(' │') + c.cyan(' "veto": {') + ' ' + c.bold('│'));
102
- console.log(c.bold(' │') + c.cyan(' "type": "stdio",') + ' ' + c.bold('│'));
103
- console.log(c.bold(' │') + c.cyan(' "command": "veto-server"') + ' ' + c.bold('│'));
104
- console.log(c.bold(' │') + c.cyan(' }') + ' ' + c.bold(''));
105
- console.log(c.bold(' │') + c.cyan(' }') + ' ' + c.bold('│'));
106
- console.log(c.bold(' │') + c.cyan(' }') + ' ' + c.bold('│'));
107
- console.log(c.bold(' │') + ' ' + c.bold('│'));
108
- console.log(c.bold(' └────────────────────────────────────────────────────────────────┘'));
109
- console.log('');
110
- console.log(c.green(' ✓ Veto is ready!'));
111
- console.log('');
112
- console.log(' Next steps:');
113
- console.log(c.dim(' 1.') + ' Add the config above to your AI CLI or IDE config file');
114
- console.log(c.dim(' 2.') + ' Restart the CLI or IDE');
115
- console.log(c.dim(' 3.') + ' Run: veto_status — should return { "status": "running", "version": "' + VERSION + '" }');
116
- console.log('');
117
- }
118
-
119
- // ─── Router ────────────────────────────────────────────────────────────────────
120
-
121
- const command = process.argv[2] ?? 'init';
122
-
123
- switch (command) {
124
- case 'init':
125
- initCommand().catch((err) => {
126
- console.error(c.red(`Error: ${err.message}`));
127
- process.exit(1);
128
- });
129
- break;
130
-
131
- default:
132
- console.error(`Unknown command: ${command}`);
133
- console.log('Usage: veto init');
134
- process.exit(1);
135
- }
1
+ #!/usr/bin/env node
2
+ // Veto CLI — entry point for `npx veto init`
3
+
4
+ // Suppress Node experimental warnings (node:sqlite) for clean UX
5
+ process.removeAllListeners('warning');
6
+
7
+ import { mkdirSync, existsSync, readFileSync, writeFileSync } from 'node:fs';
8
+ import { join, dirname } from 'node:path';
9
+ import { homedir } from 'node:os';
10
+
11
+ const VERSION = '0.8.2';
12
+ const VETO_DIR = join(homedir(), '.veto');
13
+ const HOME = homedir();
14
+
15
+ const c = {
16
+ bold: (s: string) => `\x1b[1m${s}\x1b[0m`,
17
+ green: (s: string) => `\x1b[32m${s}\x1b[0m`,
18
+ yellow: (s: string) => `\x1b[33m${s}\x1b[0m`,
19
+ cyan: (s: string) => `\x1b[36m${s}\x1b[0m`,
20
+ dim: (s: string) => `\x1b[2m${s}\x1b[0m`,
21
+ red: (s: string) => `\x1b[31m${s}\x1b[0m`,
22
+ };
23
+
24
+ function printBanner() {
25
+ console.log('');
26
+ console.log(c.bold(c.cyan(' ██╗ ██╗███████╗████████╗ ██████╗')));
27
+ console.log(c.bold(c.cyan(' ██║ ██║██╔════╝╚══██╔══╝██╔═══██╗')));
28
+ console.log(c.bold(c.cyan(' ██║ ██║█████╗ ██║ ██║ ██║')));
29
+ console.log(c.bold(c.cyan(' ╚██╗ ██╔╝██╔══╝ ██║ ██║ ██║')));
30
+ console.log(c.bold(c.cyan(' ╚████╔╝ ███████╗ ██║ ╚██████╔╝')));
31
+ console.log(c.bold(c.cyan(' ╚═══╝ ╚══════╝ ╚═╝ ╚═════╝')));
32
+ console.log('');
33
+ console.log(c.dim(` 50 agents. 28 skills. 3 AIs. Self-learning. Zero extra cost.`));
34
+ console.log(c.dim(` v${VERSION}`));
35
+ console.log('');
36
+ }
37
+
38
+ // Merge veto entry into an existing JSON config file, creating it if needed.
39
+ // Supports both "mcpServers" format (Claude/Gemini/Codex/Cursor/Windsurf)
40
+ // and "servers" format (VS Code).
41
+ function writeVetoConfig(
42
+ configPath: string,
43
+ format: 'mcpServers' | 'servers'
44
+ ): 'created' | 'updated' | 'skipped' {
45
+ let existing: Record<string, unknown> = {};
46
+
47
+ if (existsSync(configPath)) {
48
+ try {
49
+ existing = JSON.parse(readFileSync(configPath, 'utf8'));
50
+ } catch {
51
+ // Unreadable / invalid JSON skip to avoid corrupting it
52
+ return 'skipped';
53
+ }
54
+ } else {
55
+ mkdirSync(dirname(configPath), { recursive: true });
56
+ }
57
+
58
+ const wasEmpty = Object.keys(existing).length === 0;
59
+
60
+ if (format === 'mcpServers') {
61
+ const servers = (existing.mcpServers as Record<string, unknown>) ?? {};
62
+ servers['veto'] = { command: 'veto-server' };
63
+ existing.mcpServers = servers;
64
+ } else {
65
+ const servers = (existing.servers as Record<string, unknown>) ?? {};
66
+ servers['veto'] = { type: 'stdio', command: 'veto-server' };
67
+ existing.servers = servers;
68
+ }
69
+
70
+ writeFileSync(configPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
71
+ return wasEmpty ? 'created' : 'updated';
72
+ }
73
+
74
+ // All platforms Veto supports, with their config paths and formats.
75
+ const PLATFORMS = [
76
+ {
77
+ name: 'Claude Code',
78
+ path: join(HOME, '.claude', 'mcp_servers.json'),
79
+ format: 'mcpServers' as const,
80
+ detectionDir: join(HOME, '.claude'),
81
+ },
82
+ {
83
+ name: 'Gemini CLI',
84
+ path: join(HOME, '.gemini', 'settings.json'),
85
+ format: 'mcpServers' as const,
86
+ detectionDir: join(HOME, '.gemini'),
87
+ },
88
+ {
89
+ name: 'Codex CLI',
90
+ path: join(HOME, '.codex', 'config.json'),
91
+ format: 'mcpServers' as const,
92
+ detectionDir: join(HOME, '.codex'),
93
+ },
94
+ {
95
+ name: 'Cursor',
96
+ path: join(HOME, '.cursor', 'mcp.json'),
97
+ format: 'mcpServers' as const,
98
+ detectionDir: join(HOME, '.cursor'),
99
+ },
100
+ {
101
+ name: 'Windsurf',
102
+ path: join(HOME, '.codeium', 'windsurf', 'mcp_config.json'),
103
+ format: 'mcpServers' as const,
104
+ detectionDir: join(HOME, '.codeium', 'windsurf'),
105
+ },
106
+ ];
107
+
108
+ async function initCommand() {
109
+ printBanner();
110
+
111
+ // 1. Create ~/.veto directory
112
+ if (!existsSync(VETO_DIR)) {
113
+ mkdirSync(VETO_DIR, { recursive: true });
114
+ console.log(c.green(' ') + ` Created ${VETO_DIR}`);
115
+ } else {
116
+ console.log(c.dim(' · ') + `Found existing ${VETO_DIR}`);
117
+ }
118
+
119
+ // 2. Initialize SQLite database
120
+ process.stdout.write(' · Initializing SQLite database...');
121
+ const { getDb, getDbPath, saveSession } = await import('./memory/local.js');
122
+
123
+ try {
124
+ const db = getDb();
125
+ const dbPath = getDbPath();
126
+ const { session_id } = saveSession({
127
+ platform: 'claude',
128
+ summary: 'Veto initialized',
129
+ context: 'Initial setup via npx veto init',
130
+ });
131
+ const row = db.prepare('SELECT id FROM sessions WHERE id = ?').get(session_id);
132
+ if (!row) throw new Error('DB smoke test failed');
133
+ db.prepare('DELETE FROM sessions WHERE id = ?').run(session_id);
134
+ console.log(c.green(' ✓'));
135
+ console.log(c.green(' ✓') + ` Database ready at ${dbPath}`);
136
+ } catch (err: unknown) {
137
+ console.log(c.red(' ✗'));
138
+ const msg = err instanceof Error ? err.message : String(err);
139
+ console.error(c.red(` Error initializing database: ${msg}`));
140
+ process.exit(1);
141
+ }
142
+
143
+ // 3. Auto-configure every AI CLI / IDE found on this machine
144
+ console.log('');
145
+ console.log(' Configuring all AI tools found on this machine...');
146
+ console.log('');
147
+
148
+ let configured = 0;
149
+ let skipped = 0;
150
+
151
+ for (const platform of PLATFORMS) {
152
+ const detected = existsSync(platform.detectionDir);
153
+ if (!detected) {
154
+ console.log(c.dim(' · ') + c.dim(`${platform.name} — not installed, skipping`));
155
+ continue;
156
+ }
157
+
158
+ const result = writeVetoConfig(platform.path, platform.format);
159
+
160
+ if (result === 'skipped') {
161
+ console.log(c.yellow(' ⚠ ') + `${platform.name} — config unreadable, skipped`);
162
+ skipped++;
163
+ } else if (result === 'created') {
164
+ console.log(c.green(' ✓ ') + `${platform.name} — configured`);
165
+ configured++;
166
+ } else {
167
+ console.log(c.green(' ✓ ') + `${platform.name} — updated`);
168
+ configured++;
169
+ }
170
+ }
171
+
172
+ console.log('');
173
+
174
+ if (configured === 0 && skipped === 0) {
175
+ console.log(c.yellow(' ⚠ No AI tools detected.'));
176
+ console.log(' Install Claude Code, Gemini CLI, or Codex CLI and run veto init again.');
177
+ console.log('');
178
+ } else {
179
+ console.log(c.green(` ✓ Veto configured for ${configured} tool${configured !== 1 ? 's' : ''}!`));
180
+ console.log('');
181
+ console.log(' Next steps:');
182
+ console.log(c.dim(' 1.') + ' Restart your AI CLI or IDE');
183
+ console.log(c.dim(' 2.') + ' Run: veto_status — should return { "status": "running", "version": "' + VERSION + '" }');
184
+ console.log('');
185
+ }
186
+ }
187
+
188
+ // ─── Router ────────────────────────────────────────────────────────────────────
189
+
190
+ const command = process.argv[2] ?? 'init';
191
+
192
+ switch (command) {
193
+ case 'init':
194
+ initCommand().catch((err) => {
195
+ console.error(c.red(`Error: ${err.message}`));
196
+ process.exit(1);
197
+ });
198
+ break;
199
+
200
+ default:
201
+ console.error(`Unknown command: ${command}`);
202
+ console.log('Usage: veto init');
203
+ process.exit(1);
204
+ }