vanguard-cli 3.1.3 ā 3.1.7
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/RECOVERY_GUIDE.md +36 -0
- package/bin/vanguard.js +33 -5
- package/lib/commands/config.js +8 -3
- package/lib/commands/integrate.js +123 -0
- package/lib/commands/scan.js +1 -0
- package/lib/services/git.js +20 -17
- package/lib/services/scanner.js +3 -3
- package/package.json +3 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Enterprise Publication Recovery Guide š”ļøššļø
|
|
2
|
+
|
|
3
|
+
If you see an `ENEEDAUTH` or `Access token expired` error when publishing to NPM (like the one you saw on the homeserver), follow these steps to restore your automated shipping pipeline.
|
|
4
|
+
|
|
5
|
+
## 1. Refresh Your Session š
|
|
6
|
+
Your CLI session for NPM has likely expired. Run this in your terminal to log back in:
|
|
7
|
+
```bash
|
|
8
|
+
npm login
|
|
9
|
+
```
|
|
10
|
+
*Note: This will likely open a browser windows or ask for your 2FA Passkey signature.*
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 2. Using a Permanent Token (Recommended) š
|
|
15
|
+
To avoid Passkey/Browser prompts on your remote servers, use a **Granular Access Token**:
|
|
16
|
+
|
|
17
|
+
1. **Generate Token**: Go to [NPM Tokens](https://www.npmjs.com/settings/tokens/granular-access-tokens/new).
|
|
18
|
+
2. **Permissions**: Select "Read and write" for the specific package.
|
|
19
|
+
3. **Bypass 2FA**: **CHECK THIS BOX** in the NPM settings for the token.
|
|
20
|
+
4. **Set Environment**: On your server, add this to your `.bashrc` or `.env`:
|
|
21
|
+
```bash
|
|
22
|
+
export NPM_TOKEN="your_token_here"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 3. Finishing the Vanguard V3.1.5 Release š
|
|
28
|
+
I have already versioned and pushed the codes to GitHub. To finish the live NPM update, simply run:
|
|
29
|
+
```bash
|
|
30
|
+
npm run ship
|
|
31
|
+
```
|
|
32
|
+
- Select **"Patch"**.
|
|
33
|
+
- When asked about 2FA, select **"No"** (after you've logged in or set the token).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
š”ļø **Vanguard is perfectly synchronized and ready for the world.**
|
package/bin/vanguard.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
5
|
import { runConfigWizard } from '../lib/commands/config.js';
|
|
6
|
+
import { runIntegrate } from '../lib/commands/integrate.js';
|
|
6
7
|
import { handlePull, handleClone } from '../lib/commands/scan.js';
|
|
7
8
|
import { showBanner, showFooter } from '../lib/utils/ui.js';
|
|
8
9
|
import config from '../lib/utils/config.js';
|
|
@@ -10,6 +11,21 @@ import config from '../lib/utils/config.js';
|
|
|
10
11
|
const program = new Command();
|
|
11
12
|
|
|
12
13
|
async function handleAction(actionName, logicFn) {
|
|
14
|
+
const isConfigured =
|
|
15
|
+
(config.get('AI_PROVIDER') === 'gemini' && config.get('GEMINI_KEY')) ||
|
|
16
|
+
(config.get('AI_PROVIDER') === 'ollama');
|
|
17
|
+
|
|
18
|
+
if (!isConfigured) {
|
|
19
|
+
showBanner();
|
|
20
|
+
console.log(chalk.yellow(`š”ļø Vanguard V3 is not fully configured. Starting setup...\n`));
|
|
21
|
+
await runConfigWizard();
|
|
22
|
+
// After wizard, check again
|
|
23
|
+
if (!config.get('GEMINI_KEY') && config.get('AI_PROVIDER') === 'gemini') {
|
|
24
|
+
console.log(chalk.red('\nā Configuration incomplete. Please provide a Gemini API key.'));
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
13
29
|
showBanner();
|
|
14
30
|
const options = program.opts();
|
|
15
31
|
config.set('VERBOSE', !!options.verbose);
|
|
@@ -28,11 +44,6 @@ async function handleAction(actionName, logicFn) {
|
|
|
28
44
|
return;
|
|
29
45
|
}
|
|
30
46
|
|
|
31
|
-
if (config.get('AI_PROVIDER') === 'gemini' && !config.get('GEMINI_KEY')) {
|
|
32
|
-
console.log('ā Gemini not configured.');
|
|
33
|
-
await runConfigWizard();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
47
|
await logicFn();
|
|
37
48
|
showFooter();
|
|
38
49
|
}
|
|
@@ -46,6 +57,8 @@ program
|
|
|
46
57
|
.option('--clear-cache', 'Flush local audit cache');
|
|
47
58
|
|
|
48
59
|
program.command('config').description('Configure AI providers').action(runConfigWizard);
|
|
60
|
+
program.command('integrate').description('Install "Invisible Protection" shell wrapper').action(runIntegrate);
|
|
61
|
+
|
|
49
62
|
|
|
50
63
|
program
|
|
51
64
|
.command('pull')
|
|
@@ -65,3 +78,18 @@ program
|
|
|
65
78
|
});
|
|
66
79
|
|
|
67
80
|
program.parse(process.argv);
|
|
81
|
+
|
|
82
|
+
// Default action: if no command provided, check config
|
|
83
|
+
if (!process.argv.slice(2).length) {
|
|
84
|
+
const isConfigured =
|
|
85
|
+
(config.get('AI_PROVIDER') === 'gemini' && config.get('GEMINI_KEY')) ||
|
|
86
|
+
(config.get('AI_PROVIDER') === 'ollama');
|
|
87
|
+
|
|
88
|
+
if (!isConfigured) {
|
|
89
|
+
showBanner();
|
|
90
|
+
console.log(chalk.yellow(`š”ļø Vanguard V3 is not fully configured. Starting setup...\n`));
|
|
91
|
+
await runConfigWizard();
|
|
92
|
+
} else {
|
|
93
|
+
program.help();
|
|
94
|
+
}
|
|
95
|
+
}
|
package/lib/commands/config.js
CHANGED
|
@@ -6,7 +6,8 @@ import { showBanner } from '../utils/ui.js';
|
|
|
6
6
|
|
|
7
7
|
export async function runConfigWizard() {
|
|
8
8
|
showBanner();
|
|
9
|
-
console.log('š°ļø Vanguard Configuration Wizard
|
|
9
|
+
console.log('š°ļø Vanguard Configuration Wizard');
|
|
10
|
+
console.log(chalk.dim(`š Config Path: ${config.path}\n`));
|
|
10
11
|
|
|
11
12
|
const { providerSelection } = await inquirer.prompt([
|
|
12
13
|
{
|
|
@@ -29,10 +30,14 @@ export async function runConfigWizard() {
|
|
|
29
30
|
name: 'apiKey',
|
|
30
31
|
message: 'Enter your Gemini API Key:',
|
|
31
32
|
default: config.get('GEMINI_KEY'),
|
|
32
|
-
validate: (val) =>
|
|
33
|
+
validate: (val) => {
|
|
34
|
+
if (val.length === 0) return 'Key is required';
|
|
35
|
+
if (val.startsWith('sk-')) return 'ā ļø That looks like an OpenAI key. Gemini keys usually start with "AIza"';
|
|
36
|
+
return true;
|
|
37
|
+
},
|
|
33
38
|
},
|
|
34
39
|
]);
|
|
35
|
-
config.set('GEMINI_KEY', apiKey);
|
|
40
|
+
config.set('GEMINI_KEY', apiKey.trim());
|
|
36
41
|
console.log('ā
Gemini configured and saved.');
|
|
37
42
|
} else {
|
|
38
43
|
const spinner = createSpinner('š Detecting local Ollama models...').start();
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { createSpinner } from '../utils/spinner.js';
|
|
6
|
+
|
|
7
|
+
const SCRIPTS = {
|
|
8
|
+
bash: {
|
|
9
|
+
file: '.bashrc',
|
|
10
|
+
template: `
|
|
11
|
+
# --- VANGUARD PROTECTION START ---
|
|
12
|
+
git() {
|
|
13
|
+
if [ "$1" = "clone" ]; then
|
|
14
|
+
vanguard clone "$@"
|
|
15
|
+
else
|
|
16
|
+
command git "$@"
|
|
17
|
+
fi
|
|
18
|
+
}
|
|
19
|
+
# --- VANGUARD PROTECTION END ---
|
|
20
|
+
`
|
|
21
|
+
},
|
|
22
|
+
zsh: {
|
|
23
|
+
file: '.zshrc',
|
|
24
|
+
template: `
|
|
25
|
+
# --- VANGUARD PROTECTION START ---
|
|
26
|
+
git() {
|
|
27
|
+
if [ "$1" = "clone" ]; then
|
|
28
|
+
vanguard clone "$@"
|
|
29
|
+
else
|
|
30
|
+
command git "$@"
|
|
31
|
+
fi
|
|
32
|
+
}
|
|
33
|
+
# --- VANGUARD PROTECTION END ---
|
|
34
|
+
`
|
|
35
|
+
},
|
|
36
|
+
fish: {
|
|
37
|
+
file: '.config/fish/config.fish',
|
|
38
|
+
template: `
|
|
39
|
+
# --- VANGUARD PROTECTION START ---
|
|
40
|
+
function git
|
|
41
|
+
if test "$argv[1]" = "clone"
|
|
42
|
+
vanguard clone $argv[2..-1]
|
|
43
|
+
else
|
|
44
|
+
command git $argv
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
# --- VANGUARD PROTECTION END ---
|
|
48
|
+
`
|
|
49
|
+
},
|
|
50
|
+
powershell: {
|
|
51
|
+
file: 'Documents/PowerShell/Microsoft.PowerShell_profile.ps1',
|
|
52
|
+
template: `
|
|
53
|
+
# --- VANGUARD PROTECTION START ---
|
|
54
|
+
function git {
|
|
55
|
+
if ($args[0] -eq "clone") {
|
|
56
|
+
vanguard clone @(if ($args.Length -gt 1) { $args[1..($args.Length-1)] })
|
|
57
|
+
} else {
|
|
58
|
+
& (Get-Command git -CommandType Application) @args
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
# --- VANGUARD PROTECTION END ---
|
|
62
|
+
`
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export async function runIntegrate() {
|
|
67
|
+
console.log(chalk.bold('š Vanguard Shell Integrator\n'));
|
|
68
|
+
const spinner = createSpinner('š Detecting shells and profiles...').start();
|
|
69
|
+
|
|
70
|
+
const home = os.homedir();
|
|
71
|
+
let detected = [];
|
|
72
|
+
|
|
73
|
+
// Check Unix shells
|
|
74
|
+
for (const [shell, info] of Object.entries(SCRIPTS)) {
|
|
75
|
+
if (shell === 'powershell') continue;
|
|
76
|
+
const profilePath = path.join(home, info.file);
|
|
77
|
+
if (await fs.pathExists(profilePath)) {
|
|
78
|
+
detected.push({ name: shell, path: profilePath, info });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Check PowerShell
|
|
83
|
+
const psProfile = path.join(home, SCRIPTS.powershell.file);
|
|
84
|
+
if (await fs.pathExists(psProfile) || os.platform() === 'win32') {
|
|
85
|
+
detected.push({ name: 'powershell', path: psProfile, info: SCRIPTS.powershell });
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (detected.length === 0) {
|
|
89
|
+
spinner.fail('No supported shell profiles detected.');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
spinner.succeed(`Detected ${detected.length} shell profiles.`);
|
|
94
|
+
|
|
95
|
+
for (const shell of detected) {
|
|
96
|
+
const profilePath = shell.path;
|
|
97
|
+
const backupPath = `${profilePath}.backup`;
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
// Backup
|
|
101
|
+
if (await fs.pathExists(profilePath)) {
|
|
102
|
+
await fs.copy(profilePath, backupPath);
|
|
103
|
+
console.log(chalk.dim(`š¾ Backup created: ${backupPath}`));
|
|
104
|
+
} else {
|
|
105
|
+
await fs.ensureDir(path.dirname(profilePath));
|
|
106
|
+
await fs.writeFile(profilePath, '');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const content = await fs.readFile(profilePath, 'utf-8');
|
|
110
|
+
if (content.includes('VANGUARD PROTECTION START')) {
|
|
111
|
+
console.log(chalk.yellow(`ā© ${shell.name} already integrated. Skipping.`));
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await fs.appendFile(profilePath, shell.info.template);
|
|
116
|
+
console.log(chalk.green(`ā
Integrated with ${shell.name} (${profilePath})`));
|
|
117
|
+
} catch (err) {
|
|
118
|
+
console.log(chalk.red(`ā Failed to integrate with ${shell.name}: ${err.message}`));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
console.log(chalk.bold('\n⨠Invisible Protection Active! Restart your terminal to apply. š”ļø'));
|
|
123
|
+
}
|
package/lib/commands/scan.js
CHANGED
|
@@ -142,6 +142,7 @@ export async function handleClone(url, directory, programOptions) {
|
|
|
142
142
|
summary: 'Enterprise Deep Audit identified multiple high-risk vectors.',
|
|
143
143
|
});
|
|
144
144
|
await cleanupSandbox(tempPath);
|
|
145
|
+
console.log(chalk.red.bold('\nš”ļø Malware Detected! Clone aborted. Your disk is safe.'));
|
|
145
146
|
} else {
|
|
146
147
|
console.log(chalk.green(`\nā
Deep Audit Passed. Finalizing clone to ${targetPath}...`));
|
|
147
148
|
await finalizeClone(tempPath, targetPath);
|
package/lib/services/git.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import simpleGit from 'simple-git';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import fs from 'fs
|
|
3
|
+
import fs from 'fs-extra';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import { nanoid } from 'nanoid';
|
|
4
6
|
|
|
5
7
|
|
|
6
8
|
export const git = simpleGit();
|
|
@@ -28,14 +30,15 @@ export async function mergeUpstream() {
|
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
/**
|
|
31
|
-
* Clones into a temporary sandbox.
|
|
33
|
+
* Clones into a temporary sandbox in system temp directory.
|
|
32
34
|
*/
|
|
33
35
|
export async function createSandboxClone(url) {
|
|
34
|
-
const
|
|
36
|
+
const randomId = nanoid(8);
|
|
37
|
+
const tempPath = path.join(os.tmpdir(), `vanguard_${randomId}`);
|
|
35
38
|
|
|
36
|
-
//
|
|
37
|
-
await fs.
|
|
38
|
-
await fs.
|
|
39
|
+
// Ensure fresh directory
|
|
40
|
+
await fs.remove(tempPath);
|
|
41
|
+
await fs.ensureDir(tempPath);
|
|
39
42
|
|
|
40
43
|
const tempGit = simpleGit(tempPath);
|
|
41
44
|
await tempGit.clone(url, '.');
|
|
@@ -44,23 +47,23 @@ export async function createSandboxClone(url) {
|
|
|
44
47
|
|
|
45
48
|
/**
|
|
46
49
|
* Moves files from sandbox to target and cleans up.
|
|
47
|
-
*
|
|
50
|
+
* Uses fs-extra to handle cross-partition moves safely.
|
|
48
51
|
*/
|
|
49
52
|
export async function finalizeClone(tempPath, targetPath) {
|
|
50
53
|
const absTemp = path.resolve(tempPath);
|
|
51
54
|
const absTarget = path.resolve(targetPath);
|
|
52
55
|
|
|
53
56
|
try {
|
|
54
|
-
|
|
57
|
+
// Ensure parent directory of target exists
|
|
58
|
+
await fs.ensureDir(path.dirname(absTarget));
|
|
59
|
+
|
|
60
|
+
// Use fs-extra move which handles cross-device moves automatically
|
|
61
|
+
await fs.move(absTemp, absTarget, { overwrite: true });
|
|
55
62
|
} catch (e) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
await fs.rm(absTemp, { recursive: true, force: true });
|
|
61
|
-
} else {
|
|
62
|
-
throw e;
|
|
63
|
-
}
|
|
63
|
+
throw new Error(`Failed to finalize clone: ${e.message}`);
|
|
64
|
+
} finally {
|
|
65
|
+
// Final cleanup attempt
|
|
66
|
+
await cleanupSandbox(tempPath);
|
|
64
67
|
}
|
|
65
68
|
}
|
|
66
69
|
|
|
@@ -68,5 +71,5 @@ export async function finalizeClone(tempPath, targetPath) {
|
|
|
68
71
|
* Completely removes the sandbox.
|
|
69
72
|
*/
|
|
70
73
|
export async function cleanupSandbox(tempPath) {
|
|
71
|
-
await fs.
|
|
74
|
+
await fs.remove(tempPath).catch(() => { });
|
|
72
75
|
}
|
package/lib/services/scanner.js
CHANGED
|
@@ -113,9 +113,9 @@ RESPONSE FORMAT (JSON ONLY):
|
|
|
113
113
|
const apiKey = config.get('GEMINI_KEY');
|
|
114
114
|
if (!apiKey) throw new Error('Gemini API Key missing. Run "vanguard config"');
|
|
115
115
|
|
|
116
|
-
const genAI = new GoogleGenerativeAI(apiKey);
|
|
117
|
-
// Use the
|
|
118
|
-
const model = genAI.getGenerativeModel({ model: 'gemini-
|
|
116
|
+
const genAI = new GoogleGenerativeAI(apiKey.trim());
|
|
117
|
+
// Use the latest stable pro flash model for the best performance/quota ratio
|
|
118
|
+
const model = genAI.getGenerativeModel({ model: 'gemini-2.0-flash' });
|
|
119
119
|
const instruction = await this.getSystemInstruction();
|
|
120
120
|
|
|
121
121
|
const result = await model.generateContent({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vanguard-cli",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.7",
|
|
4
4
|
"description": "AI-Powered Supply Chain Firewall for Git",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -30,7 +30,9 @@
|
|
|
30
30
|
"conf": "^12.0.0",
|
|
31
31
|
"crypto-js": "^4.2.0",
|
|
32
32
|
"figlet": "^1.8.0",
|
|
33
|
+
"fs-extra": "^11.3.3",
|
|
33
34
|
"inquirer": "^8.2.4",
|
|
35
|
+
"nanoid": "^5.1.6",
|
|
34
36
|
"node-fetch": "^2.7.0",
|
|
35
37
|
"ora": "^8.1.1",
|
|
36
38
|
"p-limit": "^7.2.0",
|