vanguard-cli 3.0.0 → 3.1.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.
package/CHANGELOG.md ADDED
@@ -0,0 +1,28 @@
1
+ # Changelog
2
+
3
+ ## 3.1.3 (2026-01-24)
4
+
5
+
6
+ ### Features
7
+
8
+ * enterprise-grade AI resilience with gemini-1.5-flash-latest 🛡️❄️🚀✨ ([3ab3f1d](https://github.com/bazobehram/vanguard/commit/3ab3f1da4866c3e8bb833efd039f43b1dd6e8942))
9
+ * implement scanner resilience and add test suite 🛡️🧪🧬✨ ([20310c3](https://github.com/bazobehram/vanguard/commit/20310c30d2787cbc2dedb0a19d8d2a5d2b308d0d))
10
+ * implement smart throttling and robust 429 recovery logic 🛡️❄️✨ ([4123794](https://github.com/bazobehram/vanguard/commit/4123794b1cced382fb628a1b828a466d83c24a3e))
11
+ * initial public release of Vanguard V3 Enterprise Edition ��️🚀✨ ([2335c1a](https://github.com/bazobehram/vanguard/commit/2335c1aa78a95a871cc600e62dd45431147088b9))
12
+
13
+ ## 3.1.2 (2026-01-24)
14
+
15
+
16
+ ### Features
17
+
18
+ * implement scanner resilience and add test suite 🛡️🧪🧬✨ ([20310c3](https://github.com/bazobehram/vanguard/commit/20310c30d2787cbc2dedb0a19d8d2a5d2b308d0d))
19
+ * implement smart throttling and robust 429 recovery logic 🛡️❄️✨ ([4123794](https://github.com/bazobehram/vanguard/commit/4123794b1cced382fb628a1b828a466d83c24a3e))
20
+ * initial public release of Vanguard V3 Enterprise Edition ��️🚀✨ ([2335c1a](https://github.com/bazobehram/vanguard/commit/2335c1aa78a95a871cc600e62dd45431147088b9))
21
+
22
+ ## 3.1.1 (2026-01-24)
23
+
24
+
25
+ ### Features
26
+
27
+ * implement smart throttling and robust 429 recovery logic 🛡️❄️✨ ([4123794](https://github.com/bazobehram/vanguard/commit/4123794b1cced382fb628a1b828a466d83c24a3e))
28
+ * initial public release of Vanguard V3 Enterprise Edition ��️🚀✨ ([2335c1a](https://github.com/bazobehram/vanguard/commit/2335c1aa78a95a871cc600e62dd45431147088b9))
@@ -14,7 +14,7 @@ export async function runConfigWizard() {
14
14
  name: 'providerSelection',
15
15
  message: 'Select AI Provider:',
16
16
  choices: [
17
- { name: 'Google Gemini 2.0 (Cloud - Fast & Smart)', value: 'gemini' },
17
+ { name: 'Google Gemini (Cloud - Reliable & Smart)', value: 'gemini' },
18
18
  { name: 'Local Ollama (Private - Offline)', value: 'ollama' },
19
19
  ],
20
20
  },
@@ -33,7 +33,7 @@ export async function runConfigWizard() {
33
33
  },
34
34
  ]);
35
35
  config.set('GEMINI_KEY', apiKey);
36
- console.log('✅ Gemini 2.0 configured and saved.');
36
+ console.log('✅ Gemini configured and saved.');
37
37
  } else {
38
38
  const spinner = createSpinner('🔍 Detecting local Ollama models...').start();
39
39
  try {
@@ -37,7 +37,14 @@ export async function handlePull(cmdOptions, programOptions) {
37
37
  }
38
38
 
39
39
  const scanner = new VanguardScanner(programOptions.model, context);
40
- const analysis = await scanner.scan('git_diff', diff);
40
+ let analysis;
41
+ try {
42
+ analysis = await scanner.scan('git_diff', diff, spinner);
43
+ } catch (err) {
44
+ spinner.fail(`AI Audit Failed: ${err.message}`);
45
+ console.log(chalk.yellow('\n💡 Tip: You might be rate-limited. Try again in 60s or run "vanguard config" to switch to local Ollama.'));
46
+ return;
47
+ }
41
48
  spinner.stop();
42
49
 
43
50
  showAlert(analysis);
@@ -107,14 +114,21 @@ export async function handleClone(url, directory, programOptions) {
107
114
  continue;
108
115
  }
109
116
 
110
- const result = await scanner.scan(file, content);
111
- if (result.verdict === 'BLOCK') {
112
- finalVerdict = 'BLOCK';
113
- allThreats.push(
114
- ...result.threats.map((t) => ({ ...t, file: path.relative(tempPath, file) }))
115
- );
116
- } else {
117
- CacheManager.save(file, content, 'SAFE');
117
+ try {
118
+ const result = await scanner.scan(file, content, scanSpinner);
119
+ if (result.verdict === 'BLOCK') {
120
+ finalVerdict = 'BLOCK';
121
+ allThreats.push(
122
+ ...result.threats.map((t) => ({ ...t, file: path.relative(tempPath, file) }))
123
+ );
124
+ } else {
125
+ CacheManager.save(file, content, 'SAFE');
126
+ }
127
+ } catch (err) {
128
+ scanSpinner.fail(`Audit Interrupted: ${err.message}`);
129
+ console.log(chalk.yellow('\n⚠️ Service Error. Part of the codebase remains unverified.'));
130
+ await cleanupSandbox(tempPath);
131
+ return;
118
132
  }
119
133
  }
120
134
 
@@ -10,7 +10,9 @@ import pLimit from 'p-limit';
10
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
11
11
  const THREATS_PATH = path.join(__dirname, '../threats.json');
12
12
 
13
- const limit = pLimit(3); // Enterprise concurrency limit: 3 files at a time
13
+ const limit = pLimit(1); // Conservative concurrency for free tier stability
14
+
15
+ export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
14
16
 
15
17
  export class VanguardScanner {
16
18
  constructor(modelOverride, intelligenceContext = '') {
@@ -19,36 +21,20 @@ export class VanguardScanner {
19
21
  this.intelligenceContext = intelligenceContext;
20
22
  }
21
23
 
22
- /**
23
- * Enterprise Filtering Logic: Skip binaries, images, and massive files.
24
- */
25
24
  shouldSkip(filePath, stats) {
26
25
  const ext = path.extname(filePath).toLowerCase();
27
26
  const binaryExtensions = [
28
- '.exe',
29
- '.dll',
30
- '.so',
31
- '.bin',
32
- '.png',
33
- '.jpg',
34
- '.jpeg',
35
- '.gif',
36
- '.pdf',
37
- '.zip',
38
- '.gz',
27
+ '.exe', '.dll', '.so', '.bin', '.png', '.jpg', '.jpeg', '.gif', '.pdf', '.zip', '.gz',
39
28
  ];
40
-
41
29
  if (binaryExtensions.includes(ext)) return true;
42
- if (stats.size > 100 * 1024) return true; // 100KB limit
30
+ if (stats.size > 100 * 1024) return true;
43
31
  if (filePath.includes('node_modules')) return true;
44
-
45
32
  return false;
46
33
  }
47
34
 
48
35
  async getSystemInstruction() {
49
36
  const threatsData = await fs.readFile(THREATS_PATH, 'utf-8');
50
37
  const threats = JSON.parse(threatsData);
51
-
52
38
  const categoriesListing = threats
53
39
  .map((t, i) => `${i + 1}. **${t.name}:** ${t.description}`)
54
40
  .join('\n');
@@ -62,44 +48,64 @@ ${categoriesListing}
62
48
 
63
49
  ${this.intelligenceContext}
64
50
 
65
- TASK: Combine the code analysis with the latest intelligence and vulnerability data above.
66
- If OSV reports a critical vulnerability or the remote rules flag a new pattern, YOU MUST VERDICT: BLOCK.
67
-
68
51
  JSON OUTPUT RULES:
69
- - risk_score: 0-100 (Be aggressive).
70
- - verdict: "BLOCK" (if risk found) or "SAFE".
71
- - threats: Array of objects: { "file": "filename", "line": number, "threat": "category", "reason": "why" }
72
- - summary: Clear high-level explanation for a developer.
52
+ - risk_score: 0-100.
53
+ - verdict: "BLOCK" or "SAFE".
54
+ - threats: Array of { "file", "line", "threat", "reason" }
55
+ - summary: Clear explanation.
73
56
 
74
57
  RESPONSE FORMAT (JSON ONLY):
75
- { "risk_score": 95, "verdict": "BLOCK", "threats": [{ "file": "lib/utils.js", "line": 42, "threat": "Secret Exfiltration", "reason": "Accesses .env and sends to remote server." }], "summary": "Critical vulnerability found." }
58
+ { "risk_score": 95, "verdict": "BLOCK", "threats": [], "summary": "..." }
76
59
  `;
77
60
  }
78
61
 
79
- async scan(filePath, content) {
80
- return limit(() => this.scanWithRetry(filePath, content));
62
+ async scan(filePath, content, spinner = null) {
63
+ // Random Jitter Throttling (1s - 3s)
64
+ if (this.provider === 'gemini') {
65
+ const jitter = Math.floor(Math.random() * 2000) + 1000;
66
+ await sleep(jitter);
67
+ }
68
+ return limit(() => this.scanWithRetry(filePath, content, spinner));
81
69
  }
82
70
 
83
- async scanWithRetry(filePath, content, retries = 3) {
84
- for (let i = 0; i < retries; i++) {
85
- try {
86
- if (this.provider === 'gemini') {
87
- return await this.scanWithGemini(content);
71
+ async scanWithRetry(filePath, content, spinner, attempt = 1) {
72
+ const maxAttempts = 3;
73
+ try {
74
+ if (this.provider === 'gemini') {
75
+ return await this.scanWithGemini(content);
76
+ } else {
77
+ return await this.scanWithOllama(content);
78
+ }
79
+ } catch (error) {
80
+ const isRateLimit =
81
+ error.message.includes('429') ||
82
+ error.message.includes('Too Many Requests') ||
83
+ error.message.includes('Quota');
84
+
85
+ const isServerErr = error.message.includes('500') || error.message.includes('503');
86
+
87
+ if (isRateLimit && attempt <= maxAttempts) {
88
+ const waitTime = attempt === 1 ? 30 : 60; // Increase wait on second failure
89
+ if (spinner) {
90
+ const originalText = spinner.text;
91
+ for (let i = waitTime; i > 0; i--) {
92
+ spinner.text = `❄️ Quota hit. Cooling down API (${i}s remaining)...`;
93
+ await sleep(1000);
94
+ }
95
+ spinner.text = originalText;
88
96
  } else {
89
- return await this.scanWithOllama(content);
90
- }
91
- } catch (error) {
92
- const isRateLimit =
93
- error.message.includes('429') || error.message.includes('Too Many Requests');
94
- if (isRateLimit && i < retries - 1) {
95
- const delay = Math.pow(2, i) * 1000;
96
- if (config.get('VERBOSE'))
97
- console.log(chalk.yellow(` ⚠ Rate limited. Retrying in ${delay}ms...`));
98
- await new Promise((res) => setTimeout(res, delay));
99
- continue;
97
+ console.log(chalk.yellow(`\n⚠️ Rate limit hit. Cooling down for ${waitTime}s (Attempt ${attempt}/${maxAttempts})...`));
98
+ await sleep(waitTime * 1000);
100
99
  }
101
- throw error;
100
+ return this.scanWithRetry(filePath, content, spinner, attempt + 1);
102
101
  }
102
+
103
+ if (isServerErr && attempt === 1) {
104
+ if (spinner) spinner.text = '🔄 AI Server hiccup. Retrying immediately...';
105
+ return this.scanWithRetry(filePath, content, spinner, attempt + 1);
106
+ }
107
+
108
+ throw error;
103
109
  }
104
110
  }
105
111
 
@@ -108,13 +114,12 @@ RESPONSE FORMAT (JSON ONLY):
108
114
  if (!apiKey) throw new Error('Gemini API Key missing. Run "vanguard config"');
109
115
 
110
116
  const genAI = new GoogleGenerativeAI(apiKey);
111
- const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash-exp' });
117
+ // Use the -latest alias for better cross-region compatibility
118
+ const model = genAI.getGenerativeModel({ model: 'gemini-1.5-flash-latest' });
112
119
  const instruction = await this.getSystemInstruction();
113
120
 
114
121
  const result = await model.generateContent({
115
- contents: [
116
- { role: 'user', parts: [{ text: `${instruction}\n\nFILE CONTENT:\n${content}` }] },
117
- ],
122
+ contents: [{ role: 'user', parts: [{ text: `${instruction}\n\nFILE CONTENT:\n${content}` }] }],
118
123
  });
119
124
 
120
125
  const response = await result.response;
package/package.json CHANGED
@@ -1,50 +1,51 @@
1
- {
2
- "name": "vanguard-cli",
3
- "version": "3.0.0",
4
- "description": "AI-Powered Supply Chain Firewall for Git",
5
- "type": "module",
6
- "bin": {
7
- "vanguard": "./bin/vanguard.js"
8
- },
9
- "scripts": {
10
- "test": "vitest run",
11
- "format": "prettier --write .",
12
- "lint": "eslint .",
13
- "release": "release-it",
14
- "release:dry": "release-it --dry-run"
15
- },
16
- "keywords": [],
17
- "author": "Behram Bazo",
18
- "license": "MIT",
19
- "repository": {
20
- "type": "git",
21
- "url": "git+https://github.com/bazob/vanguard.git"
22
- },
23
- "dependencies": {
24
- "@google/generative-ai": "^0.21.0",
25
- "boxen": "^8.0.1",
26
- "chalk": "^4.1.2",
27
- "cli-table3": "^0.6.5",
28
- "commander": "^13.1.0",
29
- "conf": "^12.0.0",
30
- "crypto-js": "^4.2.0",
31
- "figlet": "^1.8.0",
32
- "inquirer": "^8.2.4",
33
- "node-fetch": "^2.7.0",
34
- "ora": "^8.1.1",
35
- "p-limit": "^7.2.0",
36
- "simple-git": "^3.27.0"
37
- },
38
- "devDependencies": {
39
- "@eslint/js": "^9.39.2",
40
- "@release-it/conventional-changelog": "^10.0.4",
41
- "eslint": "^9.39.2",
42
- "eslint-config-prettier": "^10.1.8",
43
- "eslint-plugin-node": "^11.1.0",
44
- "globals": "^17.1.0",
45
- "nock": "^14.0.10",
46
- "prettier": "^3.8.1",
47
- "release-it": "^19.2.4",
48
- "vitest": "^4.0.18"
49
- }
50
- }
1
+ {
2
+ "name": "vanguard-cli",
3
+ "version": "3.1.3",
4
+ "description": "AI-Powered Supply Chain Firewall for Git",
5
+ "type": "module",
6
+ "bin": {
7
+ "vanguard": "./bin/vanguard.js"
8
+ },
9
+ "scripts": {
10
+ "test": "vitest run",
11
+ "format": "prettier --write .",
12
+ "lint": "eslint .",
13
+ "release": "release-it",
14
+ "release:dry": "release-it --dry-run",
15
+ "ship": "node scripts/ship.js"
16
+ },
17
+ "keywords": [],
18
+ "author": "Behram Bazo",
19
+ "license": "MIT",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/bazob/vanguard.git"
23
+ },
24
+ "dependencies": {
25
+ "@google/generative-ai": "^0.21.0",
26
+ "boxen": "^8.0.1",
27
+ "chalk": "^4.1.2",
28
+ "cli-table3": "^0.6.5",
29
+ "commander": "^13.1.0",
30
+ "conf": "^12.0.0",
31
+ "crypto-js": "^4.2.0",
32
+ "figlet": "^1.8.0",
33
+ "inquirer": "^8.2.4",
34
+ "node-fetch": "^2.7.0",
35
+ "ora": "^8.1.1",
36
+ "p-limit": "^7.2.0",
37
+ "simple-git": "^3.27.0"
38
+ },
39
+ "devDependencies": {
40
+ "@eslint/js": "^9.39.2",
41
+ "@release-it/conventional-changelog": "^10.0.4",
42
+ "eslint": "^9.39.2",
43
+ "eslint-config-prettier": "^10.1.8",
44
+ "eslint-plugin-node": "^11.1.0",
45
+ "globals": "^17.1.0",
46
+ "nock": "^14.0.10",
47
+ "prettier": "^3.8.1",
48
+ "release-it": "^19.2.4",
49
+ "vitest": "^4.0.18"
50
+ }
51
+ }