@continum/cli 0.1.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/README.md +481 -0
- package/SETUP.md +517 -0
- package/dist/api/client.d.ts +17 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +70 -0
- package/dist/api/client.js.map +1 -0
- package/dist/commands/init.d.ts +4 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +104 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +2 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +217 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/patterns.d.ts +3 -0
- package/dist/commands/patterns.d.ts.map +1 -0
- package/dist/commands/patterns.js +67 -0
- package/dist/commands/patterns.js.map +1 -0
- package/dist/commands/scan.d.ts +11 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +219 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +61 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/uninstall.d.ts +2 -0
- package/dist/commands/uninstall.d.ts.map +1 -0
- package/dist/commands/uninstall.js +87 -0
- package/dist/commands/uninstall.js.map +1 -0
- package/dist/config/default-config.d.ts +3 -0
- package/dist/config/default-config.d.ts.map +1 -0
- package/dist/config/default-config.js +25 -0
- package/dist/config/default-config.js.map +1 -0
- package/dist/config/loader.d.ts +11 -0
- package/dist/config/loader.d.ts.map +1 -0
- package/dist/config/loader.js +96 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/git/git-utils.d.ts +8 -0
- package/dist/git/git-utils.d.ts.map +1 -0
- package/dist/git/git-utils.js +130 -0
- package/dist/git/git-utils.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/scanner/local-scan.d.ts +15 -0
- package/dist/scanner/local-scan.d.ts.map +1 -0
- package/dist/scanner/local-scan.js +227 -0
- package/dist/scanner/local-scan.js.map +1 -0
- package/dist/scanner/pattern-updater.d.ts +12 -0
- package/dist/scanner/pattern-updater.d.ts.map +1 -0
- package/dist/scanner/pattern-updater.js +110 -0
- package/dist/scanner/pattern-updater.js.map +1 -0
- package/dist/scanner/patterns.d.ts +5 -0
- package/dist/scanner/patterns.d.ts.map +1 -0
- package/dist/scanner/patterns.js +145 -0
- package/dist/scanner/patterns.js.map +1 -0
- package/dist/types.d.ts +59 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +40 -0
- package/src/api/client.ts +77 -0
- package/src/commands/init.ts +113 -0
- package/src/commands/login.ts +205 -0
- package/src/commands/patterns.ts +68 -0
- package/src/commands/scan.ts +257 -0
- package/src/commands/status.ts +57 -0
- package/src/commands/uninstall.ts +55 -0
- package/src/config/default-config.ts +23 -0
- package/src/config/loader.ts +67 -0
- package/src/git/git-utils.ts +95 -0
- package/src/index.ts +72 -0
- package/src/scanner/local-scan.ts +222 -0
- package/src/scanner/pattern-updater.ts +94 -0
- package/src/scanner/patterns.ts +156 -0
- package/src/types.ts +64 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import fetch from 'node-fetch';
|
|
2
|
+
import { Pattern, ApprovePatternDto } from '../types';
|
|
3
|
+
|
|
4
|
+
export class ContinumApiClient {
|
|
5
|
+
private apiUrl: string;
|
|
6
|
+
private apiKey: string;
|
|
7
|
+
|
|
8
|
+
constructor(apiUrl: string, apiKey: string) {
|
|
9
|
+
this.apiUrl = apiUrl.replace(/\/$/, ''); // Remove trailing slash
|
|
10
|
+
this.apiKey = apiKey;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async approvePattern(dto: ApprovePatternDto): Promise<{ success: boolean; patternId: string }> {
|
|
14
|
+
const response = await fetch(`${this.apiUrl}/patterns/approve`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
'x-continum-key': this.apiKey,
|
|
18
|
+
'Content-Type': 'application/json'
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify(dto)
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
if (!response.ok) {
|
|
24
|
+
const error = await response.text();
|
|
25
|
+
throw new Error(`Failed to approve pattern: ${error}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return response.json() as Promise<{ success: boolean; patternId: string }>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async getPatternLibrary(): Promise<Pattern[]> {
|
|
32
|
+
const response = await fetch(`${this.apiUrl}/patterns/library`, {
|
|
33
|
+
method: 'GET',
|
|
34
|
+
headers: {
|
|
35
|
+
'x-continum-key': this.apiKey,
|
|
36
|
+
'Content-Type': 'application/json'
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (!response.ok) {
|
|
41
|
+
const error = await response.text();
|
|
42
|
+
throw new Error(`Failed to fetch patterns: ${error}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return response.json() as Promise<Pattern[]>;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async sendSandboxAudit(diff: string, sandbox: string): Promise<void> {
|
|
49
|
+
// Fire and forget - don't wait for response
|
|
50
|
+
fetch(`${this.apiUrl}/patterns/audit`, {
|
|
51
|
+
method: 'POST',
|
|
52
|
+
headers: {
|
|
53
|
+
'x-continum-key': this.apiKey,
|
|
54
|
+
'Content-Type': 'application/json'
|
|
55
|
+
},
|
|
56
|
+
body: JSON.stringify({ diff, sandbox })
|
|
57
|
+
}).catch(() => {
|
|
58
|
+
// Silent fail - this is background audit
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async testConnection(): Promise<{ success: boolean; customer: string }> {
|
|
63
|
+
const response = await fetch(`${this.apiUrl}/patterns/status`, {
|
|
64
|
+
method: 'GET',
|
|
65
|
+
headers: {
|
|
66
|
+
'x-continum-key': this.apiKey,
|
|
67
|
+
'Content-Type': 'application/json'
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
throw new Error('Failed to connect to Continum API');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return response.json() as Promise<{ success: boolean; customer: string }>;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import prompts from 'prompts';
|
|
3
|
+
import { loadConfig, saveConfig, saveCredentials, loadCredentials } from '../config/loader';
|
|
4
|
+
import { installPreCommitHook, isGitRepository, hasPreCommitHook } from '../git/git-utils';
|
|
5
|
+
import { ContinumApiClient } from '../api/client';
|
|
6
|
+
import { DEFAULT_CONFIG } from '../config/default-config';
|
|
7
|
+
|
|
8
|
+
export async function initCommand(options: { silent?: boolean }): Promise<void> {
|
|
9
|
+
if (!options.silent) {
|
|
10
|
+
console.log(chalk.blue.bold('\n🛡️ Continum CLI Setup\n'));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Check if in git repository
|
|
14
|
+
if (!isGitRepository()) {
|
|
15
|
+
console.log(chalk.red('✗ Not a git repository'));
|
|
16
|
+
console.log(chalk.gray(' Run this command in a git repository'));
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Check if already initialized
|
|
21
|
+
const existingConfig = loadConfig();
|
|
22
|
+
const hasHook = hasPreCommitHook();
|
|
23
|
+
|
|
24
|
+
if (existingConfig && hasHook && !options.silent) {
|
|
25
|
+
console.log(chalk.yellow('⚠️ Continum is already initialized in this repository'));
|
|
26
|
+
const { reinit } = await prompts({
|
|
27
|
+
type: 'confirm',
|
|
28
|
+
name: 'reinit',
|
|
29
|
+
message: 'Reinitialize?',
|
|
30
|
+
initial: false
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (!reinit) {
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check for credentials - must be logged in first
|
|
39
|
+
const credentials = loadCredentials();
|
|
40
|
+
|
|
41
|
+
if (!credentials.apiUrl || !credentials.apiKey) {
|
|
42
|
+
console.log(chalk.red('✗ Not logged in to Continum\n'));
|
|
43
|
+
console.log(chalk.gray('Please run `continum login` first to authenticate.\n'));
|
|
44
|
+
console.log(chalk.blue(' $ continum login\n'));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Test connection
|
|
49
|
+
if (!options.silent) {
|
|
50
|
+
console.log(chalk.gray('\nTesting connection...'));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const client = new ContinumApiClient(credentials.apiUrl!, credentials.apiKey!);
|
|
55
|
+
const status = await client.testConnection();
|
|
56
|
+
|
|
57
|
+
if (!options.silent) {
|
|
58
|
+
console.log(chalk.green(`✓ Connected to Continum (${status.customer})`));
|
|
59
|
+
}
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.log(chalk.red('✗ Failed to connect to Continum API'));
|
|
62
|
+
console.log(chalk.gray(` ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Get sandbox configuration
|
|
67
|
+
let sandbox = existingConfig?.sandbox || 'default';
|
|
68
|
+
|
|
69
|
+
if (!options.silent) {
|
|
70
|
+
const sandboxResponse = await prompts({
|
|
71
|
+
type: 'text',
|
|
72
|
+
name: 'sandbox',
|
|
73
|
+
message: 'Sandbox name:',
|
|
74
|
+
initial: sandbox
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (sandboxResponse.sandbox) {
|
|
78
|
+
sandbox = sandboxResponse.sandbox;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Create config file
|
|
83
|
+
const config = {
|
|
84
|
+
...DEFAULT_CONFIG,
|
|
85
|
+
sandbox,
|
|
86
|
+
apiUrl: credentials.apiUrl,
|
|
87
|
+
apiKey: credentials.apiKey
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
saveConfig(config);
|
|
91
|
+
|
|
92
|
+
if (!options.silent) {
|
|
93
|
+
console.log(chalk.green('✓ .continum.json created'));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Install git hook
|
|
97
|
+
try {
|
|
98
|
+
installPreCommitHook();
|
|
99
|
+
if (!options.silent) {
|
|
100
|
+
console.log(chalk.green('✓ Pre-commit hook installed'));
|
|
101
|
+
}
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.log(chalk.red('✗ Failed to install pre-commit hook'));
|
|
104
|
+
console.log(chalk.gray(` ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!options.silent) {
|
|
109
|
+
console.log(chalk.blue.bold('\n✓ Continum is ready!\n'));
|
|
110
|
+
console.log(chalk.gray('Every commit will now be scanned locally.'));
|
|
111
|
+
console.log(chalk.gray('Violations are also audited by the Continum sandbox.\n'));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { createServer } from 'http';
|
|
4
|
+
import { parse } from 'url';
|
|
5
|
+
import { saveCredentials } from '../config/loader';
|
|
6
|
+
import { ContinumApiClient } from '../api/client';
|
|
7
|
+
|
|
8
|
+
const CALLBACK_PORT = 8765;
|
|
9
|
+
const CALLBACK_PATH = '/cli/callback';
|
|
10
|
+
|
|
11
|
+
interface AuthResponse {
|
|
12
|
+
apiKey: string;
|
|
13
|
+
apiUrl: string;
|
|
14
|
+
customer: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function loginCommand(): Promise<void> {
|
|
18
|
+
console.log(chalk.blue.bold('\n🛡️ Continum CLI Login\n'));
|
|
19
|
+
|
|
20
|
+
const apiUrl = process.env.CONTINUM_API_URL || 'https://api.continum.dev';
|
|
21
|
+
const consoleUrl = process.env.CONTINUM_CONSOLE_URL || 'https://console.continum.dev';
|
|
22
|
+
|
|
23
|
+
// Generate a random state for security
|
|
24
|
+
const state = generateRandomState();
|
|
25
|
+
|
|
26
|
+
// Start local server to receive callback
|
|
27
|
+
const authPromise = startCallbackServer(state);
|
|
28
|
+
|
|
29
|
+
// Build auth URL
|
|
30
|
+
const authUrl = `${consoleUrl}/cli/auth?state=${state}&port=${CALLBACK_PORT}`;
|
|
31
|
+
|
|
32
|
+
console.log(chalk.gray('Opening browser for authentication...\n'));
|
|
33
|
+
console.log(chalk.gray(`If browser doesn't open, visit:\n${authUrl}\n`));
|
|
34
|
+
|
|
35
|
+
// Open browser
|
|
36
|
+
const open = await import('open');
|
|
37
|
+
await open.default(authUrl);
|
|
38
|
+
|
|
39
|
+
const spinner = ora('Waiting for authentication...').start();
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// Wait for callback
|
|
43
|
+
const authResponse = await authPromise;
|
|
44
|
+
spinner.succeed('Authentication successful!');
|
|
45
|
+
|
|
46
|
+
// Save credentials
|
|
47
|
+
saveCredentials(authResponse.apiUrl || apiUrl, authResponse.apiKey);
|
|
48
|
+
|
|
49
|
+
// Test connection
|
|
50
|
+
console.log(chalk.gray('\nTesting connection...'));
|
|
51
|
+
const client = new ContinumApiClient(authResponse.apiUrl || apiUrl, authResponse.apiKey);
|
|
52
|
+
const status = await client.testConnection();
|
|
53
|
+
|
|
54
|
+
console.log(chalk.green(`✓ Connected to Continum (${status.customer})`));
|
|
55
|
+
console.log(chalk.blue.bold('\n✓ Login complete!\n'));
|
|
56
|
+
console.log(chalk.gray('You can now run `continum init` in your project.\n'));
|
|
57
|
+
|
|
58
|
+
process.exit(0);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
spinner.fail('Authentication failed');
|
|
61
|
+
console.log(chalk.red(`\n✗ ${error instanceof Error ? error.message : 'Unknown error'}\n`));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function generateRandomState(): string {
|
|
67
|
+
return Math.random().toString(36).substring(2, 15) +
|
|
68
|
+
Math.random().toString(36).substring(2, 15);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function startCallbackServer(expectedState: string): Promise<AuthResponse> {
|
|
72
|
+
return new Promise((resolve, reject) => {
|
|
73
|
+
const timeout = setTimeout(() => {
|
|
74
|
+
server.close();
|
|
75
|
+
reject(new Error('Authentication timeout (5 minutes)'));
|
|
76
|
+
}, 5 * 60 * 1000); // 5 minutes
|
|
77
|
+
|
|
78
|
+
const server = createServer((req, res) => {
|
|
79
|
+
const parsedUrl = parse(req.url || '', true);
|
|
80
|
+
|
|
81
|
+
if (parsedUrl.pathname === CALLBACK_PATH) {
|
|
82
|
+
const { state, apiKey, apiUrl, customer, error } = parsedUrl.query;
|
|
83
|
+
|
|
84
|
+
// Check for errors
|
|
85
|
+
if (error) {
|
|
86
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
87
|
+
res.end(`
|
|
88
|
+
<!DOCTYPE html>
|
|
89
|
+
<html>
|
|
90
|
+
<head>
|
|
91
|
+
<title>Authentication Failed</title>
|
|
92
|
+
<style>
|
|
93
|
+
body { font-family: system-ui; max-width: 600px; margin: 100px auto; text-align: center; }
|
|
94
|
+
.error { color: #dc2626; font-size: 24px; margin-bottom: 20px; }
|
|
95
|
+
.message { color: #6b7280; }
|
|
96
|
+
</style>
|
|
97
|
+
</head>
|
|
98
|
+
<body>
|
|
99
|
+
<div class="error">❌ Authentication Failed</div>
|
|
100
|
+
<div class="message">${error}</div>
|
|
101
|
+
<p>You can close this window and try again.</p>
|
|
102
|
+
</body>
|
|
103
|
+
</html>
|
|
104
|
+
`);
|
|
105
|
+
clearTimeout(timeout);
|
|
106
|
+
server.close();
|
|
107
|
+
reject(new Error(error as string));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Verify state
|
|
112
|
+
if (state !== expectedState) {
|
|
113
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
114
|
+
res.end(`
|
|
115
|
+
<!DOCTYPE html>
|
|
116
|
+
<html>
|
|
117
|
+
<head>
|
|
118
|
+
<title>Authentication Failed</title>
|
|
119
|
+
<style>
|
|
120
|
+
body { font-family: system-ui; max-width: 600px; margin: 100px auto; text-align: center; }
|
|
121
|
+
.error { color: #dc2626; font-size: 24px; margin-bottom: 20px; }
|
|
122
|
+
</style>
|
|
123
|
+
</head>
|
|
124
|
+
<body>
|
|
125
|
+
<div class="error">❌ Invalid State</div>
|
|
126
|
+
<p>Security verification failed. Please try again.</p>
|
|
127
|
+
</body>
|
|
128
|
+
</html>
|
|
129
|
+
`);
|
|
130
|
+
clearTimeout(timeout);
|
|
131
|
+
server.close();
|
|
132
|
+
reject(new Error('Invalid state parameter'));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Success
|
|
137
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
138
|
+
res.end(`
|
|
139
|
+
<!DOCTYPE html>
|
|
140
|
+
<html>
|
|
141
|
+
<head>
|
|
142
|
+
<title>Authentication Successful</title>
|
|
143
|
+
<style>
|
|
144
|
+
body {
|
|
145
|
+
font-family: system-ui;
|
|
146
|
+
max-width: 600px;
|
|
147
|
+
margin: 100px auto;
|
|
148
|
+
text-align: center;
|
|
149
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
150
|
+
color: white;
|
|
151
|
+
padding: 40px;
|
|
152
|
+
}
|
|
153
|
+
.success { font-size: 48px; margin-bottom: 20px; }
|
|
154
|
+
.title { font-size: 32px; font-weight: bold; margin-bottom: 10px; }
|
|
155
|
+
.message { font-size: 18px; opacity: 0.9; }
|
|
156
|
+
.close {
|
|
157
|
+
margin-top: 30px;
|
|
158
|
+
padding: 12px 24px;
|
|
159
|
+
background: white;
|
|
160
|
+
color: #667eea;
|
|
161
|
+
border: none;
|
|
162
|
+
border-radius: 6px;
|
|
163
|
+
font-size: 16px;
|
|
164
|
+
cursor: pointer;
|
|
165
|
+
font-weight: 600;
|
|
166
|
+
}
|
|
167
|
+
.close:hover { background: #f3f4f6; }
|
|
168
|
+
</style>
|
|
169
|
+
</head>
|
|
170
|
+
<body>
|
|
171
|
+
<div class="success">✓</div>
|
|
172
|
+
<div class="title">Authentication Successful!</div>
|
|
173
|
+
<div class="message">You can now close this window and return to your terminal.</div>
|
|
174
|
+
<button class="close" onclick="window.close()">Close Window</button>
|
|
175
|
+
<script>
|
|
176
|
+
// Auto-close after 3 seconds
|
|
177
|
+
setTimeout(() => window.close(), 3000);
|
|
178
|
+
</script>
|
|
179
|
+
</body>
|
|
180
|
+
</html>
|
|
181
|
+
`);
|
|
182
|
+
|
|
183
|
+
clearTimeout(timeout);
|
|
184
|
+
server.close();
|
|
185
|
+
resolve({
|
|
186
|
+
apiKey: apiKey as string,
|
|
187
|
+
apiUrl: apiUrl as string,
|
|
188
|
+
customer: customer as string,
|
|
189
|
+
});
|
|
190
|
+
} else {
|
|
191
|
+
res.writeHead(404);
|
|
192
|
+
res.end('Not found');
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
server.listen(CALLBACK_PORT, () => {
|
|
197
|
+
// Server started
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
server.on('error', (err) => {
|
|
201
|
+
clearTimeout(timeout);
|
|
202
|
+
reject(new Error(`Failed to start callback server: ${err.message}`));
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { loadCredentials } from '../config/loader';
|
|
3
|
+
import { ContinumApiClient } from '../api/client';
|
|
4
|
+
import { PatternUpdater } from '../scanner/pattern-updater';
|
|
5
|
+
|
|
6
|
+
export async function patternsUpdateCommand(): Promise<void> {
|
|
7
|
+
const credentials = loadCredentials();
|
|
8
|
+
|
|
9
|
+
if (!credentials.apiUrl || !credentials.apiKey) {
|
|
10
|
+
console.log(chalk.red('✗ No API credentials configured'));
|
|
11
|
+
console.log(chalk.gray(' Run `continum init` to set up credentials'));
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
console.log(chalk.blue('Updating patterns...'));
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const client = new ContinumApiClient(credentials.apiUrl, credentials.apiKey);
|
|
19
|
+
const updater = new PatternUpdater(client);
|
|
20
|
+
await updater.updatePatterns(true);
|
|
21
|
+
|
|
22
|
+
const patterns = updater.loadPatterns();
|
|
23
|
+
console.log(chalk.green(`✓ Updated ${patterns.length} patterns`));
|
|
24
|
+
} catch (error) {
|
|
25
|
+
console.log(chalk.red('✗ Failed to update patterns'));
|
|
26
|
+
console.log(chalk.gray(` ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function patternsListCommand(): Promise<void> {
|
|
32
|
+
const credentials = loadCredentials();
|
|
33
|
+
|
|
34
|
+
if (!credentials.apiUrl || !credentials.apiKey) {
|
|
35
|
+
console.log(chalk.red('✗ No API credentials configured'));
|
|
36
|
+
console.log(chalk.gray(' Run `continum init` to set up credentials'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
const client = new ContinumApiClient(credentials.apiUrl, credentials.apiKey);
|
|
42
|
+
const updater = new PatternUpdater(client);
|
|
43
|
+
const patterns = updater.loadPatterns();
|
|
44
|
+
|
|
45
|
+
console.log(chalk.blue.bold('\n🛡️ Pattern Library\n'));
|
|
46
|
+
|
|
47
|
+
const byType: { [key: string]: typeof patterns } = {};
|
|
48
|
+
patterns.forEach(p => {
|
|
49
|
+
if (!byType[p.patternType]) {
|
|
50
|
+
byType[p.patternType] = [];
|
|
51
|
+
}
|
|
52
|
+
byType[p.patternType].push(p);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
for (const [type, typePatterns] of Object.entries(byType)) {
|
|
56
|
+
console.log(chalk.bold(`\n${type} (${typePatterns.length})`));
|
|
57
|
+
typePatterns.forEach(p => {
|
|
58
|
+
console.log(chalk.gray(` ${p.description}`));
|
|
59
|
+
console.log(chalk.gray(` Pattern: ${p.pattern}`));
|
|
60
|
+
console.log(chalk.gray(` Severity: ${p.severity}\n`));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.log(chalk.red('✗ Failed to load patterns'));
|
|
65
|
+
console.log(chalk.gray(` ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|