@ayurak/aribot-cli 1.0.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 ADDED
@@ -0,0 +1,66 @@
1
+ # Aribot CLI
2
+
3
+ AI-powered threat modeling from the command line.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install -g aribot-cli
9
+ ```
10
+
11
+ ## Quick Start
12
+
13
+ ```bash
14
+ # Authenticate
15
+ aribot login
16
+
17
+ # Upload and analyze a diagram
18
+ aribot analyze architecture.drawio
19
+
20
+ # List your diagrams
21
+ aribot diagrams
22
+
23
+ # View threats
24
+ aribot threats <diagram-id>
25
+
26
+ # Export report
27
+ aribot export <diagram-id> --format pdf
28
+ ```
29
+
30
+ ## Commands
31
+
32
+ | Command | Description |
33
+ |---------|-------------|
34
+ | `aribot login` | Authenticate with your API key |
35
+ | `aribot logout` | Remove stored credentials |
36
+ | `aribot whoami` | Show current auth status |
37
+ | `aribot diagrams` | List all your diagrams |
38
+ | `aribot analyze <file>` | Upload and analyze a diagram |
39
+ | `aribot threats <id>` | List threats for a diagram |
40
+ | `aribot generate-threats <id>` | Generate AI threats |
41
+ | `aribot export <id>` | Export report (pdf/json/csv) |
42
+
43
+ ## Options
44
+
45
+ ### analyze
46
+ - `-n, --name <name>` - Set diagram name
47
+ - `--auto-threats` - Auto-generate AI threats (default: true)
48
+
49
+ ### threats
50
+ - `-s, --severity <level>` - Filter by severity
51
+
52
+ ### export
53
+ - `-f, --format <format>` - Export format (pdf, json, csv)
54
+ - `-o, --output <file>` - Output file path
55
+
56
+ ## Get API Key
57
+
58
+ Get your API key at [developer.ayurak.com](https://developer.ayurak.com)
59
+
60
+ ## Documentation
61
+
62
+ Full documentation: [developer.ayurak.com/docs](https://developer.ayurak.com/docs)
63
+
64
+ ## License
65
+
66
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Aribot CLI - AI-Powered Threat Modeling
4
+ *
5
+ * Usage:
6
+ * aribot login # Authenticate with API key
7
+ * aribot analyze <diagram> # Analyze a diagram file
8
+ * aribot threats <diagram-id> # List threats for a diagram
9
+ * aribot export <diagram-id> # Export report
10
+ * aribot diagrams # List all diagrams
11
+ */
12
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,290 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Aribot CLI - AI-Powered Threat Modeling
5
+ *
6
+ * Usage:
7
+ * aribot login # Authenticate with API key
8
+ * aribot analyze <diagram> # Analyze a diagram file
9
+ * aribot threats <diagram-id> # List threats for a diagram
10
+ * aribot export <diagram-id> # Export report
11
+ * aribot diagrams # List all diagrams
12
+ */
13
+ var __importDefault = (this && this.__importDefault) || function (mod) {
14
+ return (mod && mod.__esModule) ? mod : { "default": mod };
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ const commander_1 = require("commander");
18
+ const chalk_1 = __importDefault(require("chalk"));
19
+ const ora_1 = __importDefault(require("ora"));
20
+ const conf_1 = __importDefault(require("conf"));
21
+ const inquirer_1 = __importDefault(require("inquirer"));
22
+ const form_data_1 = __importDefault(require("form-data"));
23
+ const fs_1 = __importDefault(require("fs"));
24
+ const path_1 = __importDefault(require("path"));
25
+ const config = new conf_1.default({ projectName: 'aribot-cli' });
26
+ const API_BASE = 'https://api.aribot.ayurak.com';
27
+ const program = new commander_1.Command();
28
+ program
29
+ .name('aribot')
30
+ .description('AI-powered threat modeling CLI by Ayurak')
31
+ .version('1.0.0');
32
+ // Helper to get auth headers
33
+ function getHeaders() {
34
+ const apiKey = config.get('apiKey');
35
+ if (!apiKey) {
36
+ console.error(chalk_1.default.red('Not authenticated. Run: aribot login'));
37
+ process.exit(1);
38
+ }
39
+ return {
40
+ 'Authorization': `Bearer ${apiKey}`,
41
+ 'Content-Type': 'application/json'
42
+ };
43
+ }
44
+ // API request helper
45
+ async function apiRequest(endpoint, options = {}) {
46
+ const fetch = (await import('node-fetch')).default;
47
+ const response = await fetch(`${API_BASE}${endpoint}`, {
48
+ ...options,
49
+ headers: { ...getHeaders(), ...(options.headers || {}) }
50
+ });
51
+ if (!response.ok) {
52
+ throw new Error(`API Error: ${response.status} ${response.statusText}`);
53
+ }
54
+ return response.json();
55
+ }
56
+ // Login command
57
+ program
58
+ .command('login')
59
+ .description('Authenticate with your Aribot API key')
60
+ .action(async () => {
61
+ const { apiKey } = await inquirer_1.default.prompt([{
62
+ type: 'password',
63
+ name: 'apiKey',
64
+ message: 'Enter your Aribot API key:',
65
+ mask: '*'
66
+ }]);
67
+ const spinner = (0, ora_1.default)('Validating API key...').start();
68
+ try {
69
+ const fetch = (await import('node-fetch')).default;
70
+ const response = await fetch(`${API_BASE}/v2/auth/validate/`, {
71
+ headers: { 'Authorization': `Bearer ${apiKey}` }
72
+ });
73
+ if (response.ok) {
74
+ config.set('apiKey', apiKey);
75
+ spinner.succeed(chalk_1.default.green('Authenticated successfully!'));
76
+ console.log(chalk_1.default.dim('API key stored securely.'));
77
+ }
78
+ else {
79
+ spinner.fail(chalk_1.default.red('Invalid API key'));
80
+ }
81
+ }
82
+ catch (error) {
83
+ spinner.fail(chalk_1.default.red('Authentication failed'));
84
+ console.error(error);
85
+ }
86
+ });
87
+ // Logout command
88
+ program
89
+ .command('logout')
90
+ .description('Remove stored credentials')
91
+ .action(() => {
92
+ config.delete('apiKey');
93
+ console.log(chalk_1.default.green('Logged out successfully.'));
94
+ });
95
+ // List diagrams
96
+ program
97
+ .command('diagrams')
98
+ .description('List all your diagrams')
99
+ .option('-l, --limit <number>', 'Number of diagrams to show', '10')
100
+ .action(async (options) => {
101
+ const spinner = (0, ora_1.default)('Fetching diagrams...').start();
102
+ try {
103
+ const data = await apiRequest(`/v2/diagrams/?limit=${options.limit}`);
104
+ spinner.stop();
105
+ console.log(chalk_1.default.bold('\nYour Diagrams:\n'));
106
+ if (!data.results?.length) {
107
+ console.log(chalk_1.default.dim('No diagrams found. Create one at https://portal.aribot.ayurak.com'));
108
+ return;
109
+ }
110
+ data.results.forEach((d) => {
111
+ const status = d.status === 'completed' ? chalk_1.default.green('✓') : chalk_1.default.yellow('⋯');
112
+ console.log(`${status} ${chalk_1.default.cyan(d.id.slice(0, 8))} ${d.name} ${chalk_1.default.dim(d.threats_count + ' threats')}`);
113
+ });
114
+ console.log(chalk_1.default.dim(`\nShowing ${data.results.length} of ${data.count} diagrams`));
115
+ }
116
+ catch (error) {
117
+ spinner.fail('Failed to fetch diagrams');
118
+ console.error(error);
119
+ }
120
+ });
121
+ // Analyze diagram
122
+ program
123
+ .command('analyze <file>')
124
+ .description('Upload and analyze a diagram file')
125
+ .option('-n, --name <name>', 'Diagram name')
126
+ .option('--auto-threats', 'Automatically generate AI threats', true)
127
+ .action(async (file, options) => {
128
+ if (!fs_1.default.existsSync(file)) {
129
+ console.error(chalk_1.default.red(`File not found: ${file}`));
130
+ process.exit(1);
131
+ }
132
+ const spinner = (0, ora_1.default)('Uploading diagram...').start();
133
+ try {
134
+ const fetch = (await import('node-fetch')).default;
135
+ const form = new form_data_1.default();
136
+ form.append('file', fs_1.default.createReadStream(file));
137
+ form.append('name', options.name || path_1.default.basename(file, path_1.default.extname(file)));
138
+ form.append('auto_generate_threats', options.autoThreats ? 'true' : 'false');
139
+ const apiKey = config.get('apiKey');
140
+ const response = await fetch(`${API_BASE}/v2/diagrams/upload-analyze/`, {
141
+ method: 'POST',
142
+ headers: { 'Authorization': `Bearer ${apiKey}` },
143
+ body: form
144
+ });
145
+ if (!response.ok) {
146
+ throw new Error(`Upload failed: ${response.status}`);
147
+ }
148
+ const data = await response.json();
149
+ spinner.succeed('Diagram uploaded!');
150
+ console.log(chalk_1.default.bold('\nDiagram Created:'));
151
+ console.log(` ID: ${chalk_1.default.cyan(data.id)}`);
152
+ console.log(` Name: ${data.name}`);
153
+ console.log(` Status: ${data.status}`);
154
+ if (options.autoThreats) {
155
+ const threatSpinner = (0, ora_1.default)('Generating AI threats...').start();
156
+ // Poll for completion
157
+ let attempts = 0;
158
+ while (attempts < 30) {
159
+ await new Promise(r => setTimeout(r, 2000));
160
+ const status = await apiRequest(`/v2/diagrams/${data.id}/`);
161
+ if (status.status === 'completed') {
162
+ threatSpinner.succeed(`Generated ${status.threats_count} threats`);
163
+ break;
164
+ }
165
+ attempts++;
166
+ }
167
+ }
168
+ console.log(chalk_1.default.dim(`\nView at: https://portal.aribot.ayurak.com/diagrams/${data.id}`));
169
+ }
170
+ catch (error) {
171
+ spinner.fail('Analysis failed');
172
+ console.error(error);
173
+ }
174
+ });
175
+ // List threats
176
+ program
177
+ .command('threats <diagram-id>')
178
+ .description('List threats for a diagram')
179
+ .option('-s, --severity <level>', 'Filter by severity (critical, high, medium, low)')
180
+ .action(async (diagramId, options) => {
181
+ const spinner = (0, ora_1.default)('Fetching threats...').start();
182
+ try {
183
+ let url = `/v2/diagrams/${diagramId}/threats/`;
184
+ if (options.severity) {
185
+ url += `?severity=${options.severity}`;
186
+ }
187
+ const data = await apiRequest(url);
188
+ spinner.stop();
189
+ console.log(chalk_1.default.bold('\nThreats:\n'));
190
+ const severityColors = {
191
+ critical: chalk_1.default.red,
192
+ high: chalk_1.default.yellow,
193
+ medium: chalk_1.default.blue,
194
+ low: chalk_1.default.dim
195
+ };
196
+ data.results?.forEach((t) => {
197
+ const color = severityColors[t.severity] || chalk_1.default.white;
198
+ console.log(`${color(`[${t.severity.toUpperCase()}]`)} ${t.title}`);
199
+ console.log(chalk_1.default.dim(` STRIDE: ${t.stride_category} | ${t.id.slice(0, 8)}`));
200
+ console.log();
201
+ });
202
+ console.log(chalk_1.default.dim(`Total: ${data.count} threats`));
203
+ }
204
+ catch (error) {
205
+ spinner.fail('Failed to fetch threats');
206
+ console.error(error);
207
+ }
208
+ });
209
+ // Export report
210
+ program
211
+ .command('export <diagram-id>')
212
+ .description('Export diagram report')
213
+ .option('-f, --format <format>', 'Export format (pdf, json, csv)', 'json')
214
+ .option('-o, --output <file>', 'Output file path')
215
+ .action(async (diagramId, options) => {
216
+ const spinner = (0, ora_1.default)(`Exporting ${options.format.toUpperCase()} report...`).start();
217
+ try {
218
+ const fetch = (await import('node-fetch')).default;
219
+ const apiKey = config.get('apiKey');
220
+ const response = await fetch(`${API_BASE}/v2/diagrams/${diagramId}/export/?format=${options.format}`, { headers: { 'Authorization': `Bearer ${apiKey}` } });
221
+ if (!response.ok) {
222
+ throw new Error(`Export failed: ${response.status}`);
223
+ }
224
+ const outputPath = options.output || `aribot-report-${diagramId.slice(0, 8)}.${options.format}`;
225
+ if (options.format === 'json') {
226
+ const data = await response.json();
227
+ fs_1.default.writeFileSync(outputPath, JSON.stringify(data, null, 2));
228
+ }
229
+ else {
230
+ const buffer = await response.arrayBuffer();
231
+ fs_1.default.writeFileSync(outputPath, Buffer.from(buffer));
232
+ }
233
+ spinner.succeed(`Report saved to ${chalk_1.default.cyan(outputPath)}`);
234
+ }
235
+ catch (error) {
236
+ spinner.fail('Export failed');
237
+ console.error(error);
238
+ }
239
+ });
240
+ // Generate threats command
241
+ program
242
+ .command('generate-threats <diagram-id>')
243
+ .description('Generate AI threats for an existing diagram')
244
+ .action(async (diagramId) => {
245
+ const spinner = (0, ora_1.default)('Generating AI threats...').start();
246
+ try {
247
+ await apiRequest(`/v2/diagrams/${diagramId}/generate-threats/`, {
248
+ method: 'POST'
249
+ });
250
+ spinner.text = 'Processing...';
251
+ // Poll for completion
252
+ let attempts = 0;
253
+ while (attempts < 30) {
254
+ await new Promise(r => setTimeout(r, 2000));
255
+ const status = await apiRequest(`/v2/diagrams/${diagramId}/`);
256
+ if (status.ai_threats_generated) {
257
+ spinner.succeed(`Generated ${status.threats_count} threats`);
258
+ return;
259
+ }
260
+ attempts++;
261
+ }
262
+ spinner.succeed('Threat generation initiated');
263
+ }
264
+ catch (error) {
265
+ spinner.fail('Failed to generate threats');
266
+ console.error(error);
267
+ }
268
+ });
269
+ // Whoami command
270
+ program
271
+ .command('whoami')
272
+ .description('Show current authentication status')
273
+ .action(async () => {
274
+ const apiKey = config.get('apiKey');
275
+ if (!apiKey) {
276
+ console.log(chalk_1.default.yellow('Not authenticated'));
277
+ console.log(chalk_1.default.dim('Run: aribot login'));
278
+ return;
279
+ }
280
+ try {
281
+ const data = await apiRequest('/v2/auth/me/');
282
+ console.log(chalk_1.default.green('Authenticated as:'));
283
+ console.log(` Email: ${data.email}`);
284
+ console.log(` Company: ${data.company || 'N/A'}`);
285
+ }
286
+ catch {
287
+ console.log(chalk_1.default.yellow('API key stored but validation failed'));
288
+ }
289
+ });
290
+ program.parse();
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@ayurak/aribot-cli",
3
+ "version": "1.0.0",
4
+ "description": "AI-powered threat modeling CLI by Ayurak. Automatically analyze diagrams, generate STRIDE threats, and get security recommendations.",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "aribot": "./dist/cli.js"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "start": "node dist/cli.js",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "keywords": [
15
+ "threat-modeling",
16
+ "security",
17
+ "stride",
18
+ "devsecops",
19
+ "cli",
20
+ "ai",
21
+ "appsec",
22
+ "diagram-analysis",
23
+ "vulnerability",
24
+ "risk-assessment"
25
+ ],
26
+ "author": "Ayurak AI <support@ayurak.com>",
27
+ "license": "MIT",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "https://github.com/ayurak/aribot-cli"
31
+ },
32
+ "homepage": "https://developer.ayurak.com",
33
+ "bugs": {
34
+ "url": "https://github.com/ayurak/aribot-cli/issues"
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "dependencies": {
40
+ "chalk": "^5.3.0",
41
+ "commander": "^12.0.0",
42
+ "conf": "^12.0.0",
43
+ "form-data": "^4.0.0",
44
+ "inquirer": "^9.2.0",
45
+ "node-fetch": "^3.3.0",
46
+ "ora": "^8.0.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/inquirer": "^9.0.9",
50
+ "@types/node": "^20.0.0",
51
+ "typescript": "^5.3.0"
52
+ }
53
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,333 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Aribot CLI - AI-Powered Threat Modeling
4
+ *
5
+ * Usage:
6
+ * aribot login # Authenticate with API key
7
+ * aribot analyze <diagram> # Analyze a diagram file
8
+ * aribot threats <diagram-id> # List threats for a diagram
9
+ * aribot export <diagram-id> # Export report
10
+ * aribot diagrams # List all diagrams
11
+ */
12
+
13
+ import { Command } from 'commander';
14
+ import chalk from 'chalk';
15
+ import ora from 'ora';
16
+ import Conf from 'conf';
17
+ import inquirer from 'inquirer';
18
+ import FormData from 'form-data';
19
+ import fs from 'fs';
20
+ import path from 'path';
21
+
22
+ const config = new Conf({ projectName: 'aribot-cli' });
23
+ const API_BASE = 'https://api.aribot.ayurak.com';
24
+
25
+ const program = new Command();
26
+
27
+ program
28
+ .name('aribot')
29
+ .description('AI-powered threat modeling CLI by Ayurak')
30
+ .version('1.0.0');
31
+
32
+ // Helper to get auth headers
33
+ function getHeaders(): Record<string, string> {
34
+ const apiKey = config.get('apiKey') as string;
35
+ if (!apiKey) {
36
+ console.error(chalk.red('Not authenticated. Run: aribot login'));
37
+ process.exit(1);
38
+ }
39
+ return {
40
+ 'Authorization': `Bearer ${apiKey}`,
41
+ 'Content-Type': 'application/json'
42
+ };
43
+ }
44
+
45
+ // API request helper
46
+ async function apiRequest(endpoint: string, options: any = {}): Promise<any> {
47
+ const fetch = (await import('node-fetch')).default;
48
+ const response = await fetch(`${API_BASE}${endpoint}`, {
49
+ ...options,
50
+ headers: { ...getHeaders(), ...(options.headers || {}) }
51
+ } as any);
52
+
53
+ if (!response.ok) {
54
+ throw new Error(`API Error: ${response.status} ${response.statusText}`);
55
+ }
56
+
57
+ return response.json();
58
+ }
59
+
60
+ // Login command
61
+ program
62
+ .command('login')
63
+ .description('Authenticate with your Aribot API key')
64
+ .action(async () => {
65
+ const { apiKey } = await inquirer.prompt([{
66
+ type: 'password',
67
+ name: 'apiKey',
68
+ message: 'Enter your Aribot API key:',
69
+ mask: '*'
70
+ }]);
71
+
72
+ const spinner = ora('Validating API key...').start();
73
+
74
+ try {
75
+ const fetch = (await import('node-fetch')).default;
76
+ const response = await fetch(`${API_BASE}/v2/auth/validate/`, {
77
+ headers: { 'Authorization': `Bearer ${apiKey}` }
78
+ });
79
+
80
+ if (response.ok) {
81
+ config.set('apiKey', apiKey);
82
+ spinner.succeed(chalk.green('Authenticated successfully!'));
83
+ console.log(chalk.dim('API key stored securely.'));
84
+ } else {
85
+ spinner.fail(chalk.red('Invalid API key'));
86
+ }
87
+ } catch (error) {
88
+ spinner.fail(chalk.red('Authentication failed'));
89
+ console.error(error);
90
+ }
91
+ });
92
+
93
+ // Logout command
94
+ program
95
+ .command('logout')
96
+ .description('Remove stored credentials')
97
+ .action(() => {
98
+ config.delete('apiKey');
99
+ console.log(chalk.green('Logged out successfully.'));
100
+ });
101
+
102
+ // List diagrams
103
+ program
104
+ .command('diagrams')
105
+ .description('List all your diagrams')
106
+ .option('-l, --limit <number>', 'Number of diagrams to show', '10')
107
+ .action(async (options) => {
108
+ const spinner = ora('Fetching diagrams...').start();
109
+
110
+ try {
111
+ const data = await apiRequest(`/v2/diagrams/?limit=${options.limit}`);
112
+ spinner.stop();
113
+
114
+ console.log(chalk.bold('\nYour Diagrams:\n'));
115
+
116
+ if (!data.results?.length) {
117
+ console.log(chalk.dim('No diagrams found. Create one at https://portal.aribot.ayurak.com'));
118
+ return;
119
+ }
120
+
121
+ data.results.forEach((d: any) => {
122
+ const status = d.status === 'completed' ? chalk.green('✓') : chalk.yellow('⋯');
123
+ console.log(`${status} ${chalk.cyan(d.id.slice(0, 8))} ${d.name} ${chalk.dim(d.threats_count + ' threats')}`);
124
+ });
125
+
126
+ console.log(chalk.dim(`\nShowing ${data.results.length} of ${data.count} diagrams`));
127
+ } catch (error) {
128
+ spinner.fail('Failed to fetch diagrams');
129
+ console.error(error);
130
+ }
131
+ });
132
+
133
+ // Analyze diagram
134
+ program
135
+ .command('analyze <file>')
136
+ .description('Upload and analyze a diagram file')
137
+ .option('-n, --name <name>', 'Diagram name')
138
+ .option('--auto-threats', 'Automatically generate AI threats', true)
139
+ .action(async (file, options) => {
140
+ if (!fs.existsSync(file)) {
141
+ console.error(chalk.red(`File not found: ${file}`));
142
+ process.exit(1);
143
+ }
144
+
145
+ const spinner = ora('Uploading diagram...').start();
146
+
147
+ try {
148
+ const fetch = (await import('node-fetch')).default;
149
+ const form = new FormData();
150
+ form.append('file', fs.createReadStream(file));
151
+ form.append('name', options.name || path.basename(file, path.extname(file)));
152
+ form.append('auto_generate_threats', options.autoThreats ? 'true' : 'false');
153
+
154
+ const apiKey = config.get('apiKey') as string;
155
+ const response = await fetch(`${API_BASE}/v2/diagrams/upload-analyze/`, {
156
+ method: 'POST',
157
+ headers: { 'Authorization': `Bearer ${apiKey}` },
158
+ body: form as any
159
+ });
160
+
161
+ if (!response.ok) {
162
+ throw new Error(`Upload failed: ${response.status}`);
163
+ }
164
+
165
+ const data = await response.json() as any;
166
+ spinner.succeed('Diagram uploaded!');
167
+
168
+ console.log(chalk.bold('\nDiagram Created:'));
169
+ console.log(` ID: ${chalk.cyan(data.id)}`);
170
+ console.log(` Name: ${data.name}`);
171
+ console.log(` Status: ${data.status}`);
172
+
173
+ if (options.autoThreats) {
174
+ const threatSpinner = ora('Generating AI threats...').start();
175
+
176
+ // Poll for completion
177
+ let attempts = 0;
178
+ while (attempts < 30) {
179
+ await new Promise(r => setTimeout(r, 2000));
180
+ const status = await apiRequest(`/v2/diagrams/${data.id}/`);
181
+
182
+ if (status.status === 'completed') {
183
+ threatSpinner.succeed(`Generated ${status.threats_count} threats`);
184
+ break;
185
+ }
186
+ attempts++;
187
+ }
188
+ }
189
+
190
+ console.log(chalk.dim(`\nView at: https://portal.aribot.ayurak.com/diagrams/${data.id}`));
191
+ } catch (error) {
192
+ spinner.fail('Analysis failed');
193
+ console.error(error);
194
+ }
195
+ });
196
+
197
+ // List threats
198
+ program
199
+ .command('threats <diagram-id>')
200
+ .description('List threats for a diagram')
201
+ .option('-s, --severity <level>', 'Filter by severity (critical, high, medium, low)')
202
+ .action(async (diagramId, options) => {
203
+ const spinner = ora('Fetching threats...').start();
204
+
205
+ try {
206
+ let url = `/v2/diagrams/${diagramId}/threats/`;
207
+ if (options.severity) {
208
+ url += `?severity=${options.severity}`;
209
+ }
210
+
211
+ const data = await apiRequest(url);
212
+ spinner.stop();
213
+
214
+ console.log(chalk.bold('\nThreats:\n'));
215
+
216
+ const severityColors: Record<string, any> = {
217
+ critical: chalk.red,
218
+ high: chalk.yellow,
219
+ medium: chalk.blue,
220
+ low: chalk.dim
221
+ };
222
+
223
+ data.results?.forEach((t: any) => {
224
+ const color = severityColors[t.severity] || chalk.white;
225
+ console.log(`${color(`[${t.severity.toUpperCase()}]`)} ${t.title}`);
226
+ console.log(chalk.dim(` STRIDE: ${t.stride_category} | ${t.id.slice(0, 8)}`));
227
+ console.log();
228
+ });
229
+
230
+ console.log(chalk.dim(`Total: ${data.count} threats`));
231
+ } catch (error) {
232
+ spinner.fail('Failed to fetch threats');
233
+ console.error(error);
234
+ }
235
+ });
236
+
237
+ // Export report
238
+ program
239
+ .command('export <diagram-id>')
240
+ .description('Export diagram report')
241
+ .option('-f, --format <format>', 'Export format (pdf, json, csv)', 'json')
242
+ .option('-o, --output <file>', 'Output file path')
243
+ .action(async (diagramId, options) => {
244
+ const spinner = ora(`Exporting ${options.format.toUpperCase()} report...`).start();
245
+
246
+ try {
247
+ const fetch = (await import('node-fetch')).default;
248
+ const apiKey = config.get('apiKey') as string;
249
+
250
+ const response = await fetch(
251
+ `${API_BASE}/v2/diagrams/${diagramId}/export/?format=${options.format}`,
252
+ { headers: { 'Authorization': `Bearer ${apiKey}` } }
253
+ );
254
+
255
+ if (!response.ok) {
256
+ throw new Error(`Export failed: ${response.status}`);
257
+ }
258
+
259
+ const outputPath = options.output || `aribot-report-${diagramId.slice(0, 8)}.${options.format}`;
260
+
261
+ if (options.format === 'json') {
262
+ const data = await response.json();
263
+ fs.writeFileSync(outputPath, JSON.stringify(data, null, 2));
264
+ } else {
265
+ const buffer = await response.arrayBuffer();
266
+ fs.writeFileSync(outputPath, Buffer.from(buffer));
267
+ }
268
+
269
+ spinner.succeed(`Report saved to ${chalk.cyan(outputPath)}`);
270
+ } catch (error) {
271
+ spinner.fail('Export failed');
272
+ console.error(error);
273
+ }
274
+ });
275
+
276
+ // Generate threats command
277
+ program
278
+ .command('generate-threats <diagram-id>')
279
+ .description('Generate AI threats for an existing diagram')
280
+ .action(async (diagramId) => {
281
+ const spinner = ora('Generating AI threats...').start();
282
+
283
+ try {
284
+ await apiRequest(`/v2/diagrams/${diagramId}/generate-threats/`, {
285
+ method: 'POST'
286
+ });
287
+
288
+ spinner.text = 'Processing...';
289
+
290
+ // Poll for completion
291
+ let attempts = 0;
292
+ while (attempts < 30) {
293
+ await new Promise(r => setTimeout(r, 2000));
294
+ const status = await apiRequest(`/v2/diagrams/${diagramId}/`);
295
+
296
+ if (status.ai_threats_generated) {
297
+ spinner.succeed(`Generated ${status.threats_count} threats`);
298
+ return;
299
+ }
300
+ attempts++;
301
+ }
302
+
303
+ spinner.succeed('Threat generation initiated');
304
+ } catch (error) {
305
+ spinner.fail('Failed to generate threats');
306
+ console.error(error);
307
+ }
308
+ });
309
+
310
+ // Whoami command
311
+ program
312
+ .command('whoami')
313
+ .description('Show current authentication status')
314
+ .action(async () => {
315
+ const apiKey = config.get('apiKey') as string;
316
+
317
+ if (!apiKey) {
318
+ console.log(chalk.yellow('Not authenticated'));
319
+ console.log(chalk.dim('Run: aribot login'));
320
+ return;
321
+ }
322
+
323
+ try {
324
+ const data = await apiRequest('/v2/auth/me/');
325
+ console.log(chalk.green('Authenticated as:'));
326
+ console.log(` Email: ${data.email}`);
327
+ console.log(` Company: ${data.company || 'N/A'}`);
328
+ } catch {
329
+ console.log(chalk.yellow('API key stored but validation failed'));
330
+ }
331
+ });
332
+
333
+ program.parse();
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true
13
+ },
14
+ "include": ["src/**/*"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }