scanwarp 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/dist/api.js +58 -0
- package/dist/commands/init.js +146 -0
- package/dist/commands/logs.js +144 -0
- package/dist/commands/status.js +139 -0
- package/dist/config.js +77 -0
- package/dist/correlator.js +173 -0
- package/dist/detector.js +112 -0
- package/dist/diagnoser.js +159 -0
- package/dist/index.js +19 -0
- package/dist/integrations/mcp.js +87 -0
- package/dist/integrations/notifications.js +46 -0
- package/dist/integrations/stripe.js +25 -0
- package/dist/integrations/supabase.js +44 -0
- package/dist/integrations/vercel.js +90 -0
- package/dist/types.js +2 -0
- package/package.json +57 -0
package/dist/api.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ScanWarpAPI = void 0;
|
|
7
|
+
const axios_1 = __importDefault(require("axios"));
|
|
8
|
+
class ScanWarpAPI {
|
|
9
|
+
client;
|
|
10
|
+
serverUrl;
|
|
11
|
+
constructor(serverUrl = 'http://localhost:3000') {
|
|
12
|
+
this.serverUrl = serverUrl;
|
|
13
|
+
this.client = axios_1.default.create({
|
|
14
|
+
baseURL: serverUrl,
|
|
15
|
+
timeout: 10000,
|
|
16
|
+
headers: {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async createProject(name) {
|
|
22
|
+
try {
|
|
23
|
+
// Check if project exists
|
|
24
|
+
const { data: existing } = await this.client.get('/projects', {
|
|
25
|
+
params: { name },
|
|
26
|
+
});
|
|
27
|
+
if (existing && existing.length > 0) {
|
|
28
|
+
return { id: existing[0].id };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Project doesn't exist, will create
|
|
33
|
+
}
|
|
34
|
+
const { data } = await this.client.post('/projects', { name });
|
|
35
|
+
return data;
|
|
36
|
+
}
|
|
37
|
+
async createMonitor(projectId, url) {
|
|
38
|
+
const { data } = await this.client.post('/monitors', {
|
|
39
|
+
project_id: projectId,
|
|
40
|
+
url,
|
|
41
|
+
check_interval_seconds: 60,
|
|
42
|
+
});
|
|
43
|
+
return data.monitor;
|
|
44
|
+
}
|
|
45
|
+
async testConnection() {
|
|
46
|
+
try {
|
|
47
|
+
const { data } = await this.client.get('/health');
|
|
48
|
+
return data.status === 'ok';
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async getWebhookUrl(path) {
|
|
55
|
+
return `${this.serverUrl}${path}`;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.ScanWarpAPI = ScanWarpAPI;
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.initCommand = initCommand;
|
|
7
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const ora_1 = __importDefault(require("ora"));
|
|
10
|
+
const detector_js_1 = require("../detector.js");
|
|
11
|
+
const api_js_1 = require("../api.js");
|
|
12
|
+
const vercel_js_1 = require("../integrations/vercel.js");
|
|
13
|
+
const stripe_js_1 = require("../integrations/stripe.js");
|
|
14
|
+
const supabase_js_1 = require("../integrations/supabase.js");
|
|
15
|
+
const mcp_js_1 = require("../integrations/mcp.js");
|
|
16
|
+
const notifications_js_1 = require("../integrations/notifications.js");
|
|
17
|
+
const config_js_1 = require("../config.js");
|
|
18
|
+
async function initCommand(options = {}) {
|
|
19
|
+
console.log(chalk_1.default.bold.cyan('\nš ScanWarp Setup\n'));
|
|
20
|
+
console.log(chalk_1.default.gray('Your AI writes your code. ScanWarp keeps it running.\n'));
|
|
21
|
+
// Step 1: Auto-detect project
|
|
22
|
+
const spinner = (0, ora_1.default)('Detecting project...').start();
|
|
23
|
+
const detected = (0, detector_js_1.detectProject)();
|
|
24
|
+
spinner.succeed('Project detected');
|
|
25
|
+
// Print what was found
|
|
26
|
+
console.log(chalk_1.default.bold('\nā Project Details:'));
|
|
27
|
+
if (detected.framework) {
|
|
28
|
+
console.log(chalk_1.default.green(` ā Framework: ${detected.framework}`));
|
|
29
|
+
}
|
|
30
|
+
if (detected.hosting) {
|
|
31
|
+
console.log(chalk_1.default.green(` ā Hosting: ${detected.hosting}`));
|
|
32
|
+
}
|
|
33
|
+
if (detected.services.length > 0) {
|
|
34
|
+
console.log(chalk_1.default.green(` ā Services: ${detected.services.join(', ')}`));
|
|
35
|
+
}
|
|
36
|
+
if (detected.projectName) {
|
|
37
|
+
console.log(chalk_1.default.green(` ā Project: ${detected.projectName}`));
|
|
38
|
+
}
|
|
39
|
+
if (!detected.hasPackageJson) {
|
|
40
|
+
console.log(chalk_1.default.yellow('\nā No package.json found. Are you in a Node.js project?'));
|
|
41
|
+
}
|
|
42
|
+
// Step 2: Get production URL
|
|
43
|
+
const defaultUrl = options.url || (0, detector_js_1.generateDefaultUrl)(detected);
|
|
44
|
+
const { productionUrl } = await inquirer_1.default.prompt([
|
|
45
|
+
{
|
|
46
|
+
type: 'input',
|
|
47
|
+
name: 'productionUrl',
|
|
48
|
+
message: 'What is your production URL?',
|
|
49
|
+
default: defaultUrl,
|
|
50
|
+
validate: (input) => {
|
|
51
|
+
try {
|
|
52
|
+
new URL(input);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return 'Please enter a valid URL (e.g., https://example.com)';
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
]);
|
|
61
|
+
// Validate URL is reachable
|
|
62
|
+
const urlSpinner = (0, ora_1.default)('Checking if URL is reachable...').start();
|
|
63
|
+
try {
|
|
64
|
+
const response = await fetch(productionUrl, { method: 'HEAD' });
|
|
65
|
+
if (response.ok) {
|
|
66
|
+
urlSpinner.succeed(`URL is reachable (${response.status})`);
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
urlSpinner.warn(`URL returned ${response.status} (continuing anyway)`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
urlSpinner.warn('Could not reach URL (continuing anyway)');
|
|
74
|
+
}
|
|
75
|
+
// Step 3: Connect to ScanWarp server
|
|
76
|
+
const serverUrl = options.server || 'http://localhost:3000';
|
|
77
|
+
const api = new api_js_1.ScanWarpAPI(serverUrl);
|
|
78
|
+
const serverSpinner = (0, ora_1.default)('Connecting to ScanWarp server...').start();
|
|
79
|
+
const isConnected = await api.testConnection();
|
|
80
|
+
if (!isConnected) {
|
|
81
|
+
serverSpinner.fail(`Could not connect to ${serverUrl}`);
|
|
82
|
+
console.log(chalk_1.default.yellow('\nā Make sure the ScanWarp server is running.'));
|
|
83
|
+
console.log(chalk_1.default.gray(' Run: cd apps/server && pnpm dev\n'));
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
serverSpinner.succeed(`Connected to ${serverUrl}`);
|
|
87
|
+
// Save server URL to config
|
|
88
|
+
config_js_1.config.setServerUrl(serverUrl);
|
|
89
|
+
// Step 4: Create project and monitor
|
|
90
|
+
const setupSpinner = (0, ora_1.default)('Setting up monitoring...').start();
|
|
91
|
+
try {
|
|
92
|
+
const project = await api.createProject(detected.projectName || 'my-app');
|
|
93
|
+
const monitor = await api.createMonitor(project.id, productionUrl);
|
|
94
|
+
// Save project ID to config
|
|
95
|
+
config_js_1.config.setProjectId(project.id);
|
|
96
|
+
setupSpinner.succeed('Monitoring configured');
|
|
97
|
+
console.log(chalk_1.default.green(`\n ā Project ID: ${project.id}`));
|
|
98
|
+
console.log(chalk_1.default.green(` ā Monitor ID: ${monitor.id}`));
|
|
99
|
+
console.log(chalk_1.default.green(` ā Checking ${productionUrl} every 60 seconds\n`));
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
setupSpinner.fail('Failed to set up monitoring');
|
|
103
|
+
console.error(chalk_1.default.red('\nError:'), error instanceof Error ? error.message : error);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
// Step 5: Provider integrations
|
|
107
|
+
console.log(chalk_1.default.bold('\nš¦ Provider Integrations\n'));
|
|
108
|
+
// Vercel
|
|
109
|
+
if (detected.hosting === 'Vercel' && !options.skipVercel) {
|
|
110
|
+
await (0, vercel_js_1.setupVercel)(api, detected);
|
|
111
|
+
}
|
|
112
|
+
// Stripe
|
|
113
|
+
if (detected.services.includes('Stripe')) {
|
|
114
|
+
await (0, stripe_js_1.setupStripe)(api);
|
|
115
|
+
}
|
|
116
|
+
// Supabase
|
|
117
|
+
if (detected.services.includes('Supabase')) {
|
|
118
|
+
await (0, supabase_js_1.setupSupabase)(api);
|
|
119
|
+
}
|
|
120
|
+
// Step 6: MCP Configuration
|
|
121
|
+
if (!options.skipMcp) {
|
|
122
|
+
console.log(chalk_1.default.bold('\nš¤ MCP Configuration\n'));
|
|
123
|
+
await (0, mcp_js_1.setupMCP)(serverUrl);
|
|
124
|
+
}
|
|
125
|
+
// Step 7: Notifications
|
|
126
|
+
console.log(chalk_1.default.bold('\nš Notifications\n'));
|
|
127
|
+
await (0, notifications_js_1.setupNotifications)();
|
|
128
|
+
// Step 8: Summary
|
|
129
|
+
printSummary(api, productionUrl, detected);
|
|
130
|
+
}
|
|
131
|
+
function printSummary(api, url, _detected) {
|
|
132
|
+
console.log(chalk_1.default.bold.green('\n⨠Setup Complete!\n'));
|
|
133
|
+
console.log(chalk_1.default.bold('Your monitoring is now active:'));
|
|
134
|
+
console.log(chalk_1.default.gray(` ⢠Monitoring: ${url}`));
|
|
135
|
+
console.log(chalk_1.default.gray(` ⢠Check interval: Every 60 seconds`));
|
|
136
|
+
console.log(chalk_1.default.gray(` ⢠Dashboard: ${api.serverUrl}\n`));
|
|
137
|
+
console.log(chalk_1.default.bold('Next steps:'));
|
|
138
|
+
console.log(chalk_1.default.gray(' 1. Deploy your app to trigger monitoring'));
|
|
139
|
+
console.log(chalk_1.default.gray(' 2. Visit the dashboard to see events and incidents'));
|
|
140
|
+
console.log(chalk_1.default.gray(' 3. Check your notifications for any alerts\n'));
|
|
141
|
+
console.log(chalk_1.default.bold('Useful commands:'));
|
|
142
|
+
console.log(chalk_1.default.gray(' ⢠scanwarp status - Check monitoring status'));
|
|
143
|
+
console.log(chalk_1.default.gray(' ⢠scanwarp events - View recent events'));
|
|
144
|
+
console.log(chalk_1.default.gray(' ⢠scanwarp incidents - View open incidents\n'));
|
|
145
|
+
console.log(chalk_1.default.cyan('Happy shipping! š\n'));
|
|
146
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.logsCommand = logsCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const api_js_1 = require("../api.js");
|
|
10
|
+
async function logsCommand(options = {}) {
|
|
11
|
+
const serverUrl = options.server || 'http://localhost:3000';
|
|
12
|
+
const api = new api_js_1.ScanWarpAPI(serverUrl);
|
|
13
|
+
const limit = options.limit || 50;
|
|
14
|
+
if (options.follow) {
|
|
15
|
+
await streamLogs(api, options);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
await fetchLogs(api, { ...options, limit });
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
async function fetchLogs(api, options) {
|
|
22
|
+
const spinner = (0, ora_1.default)('Fetching events...').start();
|
|
23
|
+
try {
|
|
24
|
+
const params = {
|
|
25
|
+
limit: options.limit,
|
|
26
|
+
};
|
|
27
|
+
if (options.type) {
|
|
28
|
+
params.type = options.type;
|
|
29
|
+
}
|
|
30
|
+
if (options.source) {
|
|
31
|
+
params.source = options.source;
|
|
32
|
+
}
|
|
33
|
+
const response = await api.client.get('/events', { params });
|
|
34
|
+
spinner.stop();
|
|
35
|
+
const events = response.data.events;
|
|
36
|
+
if (events.length === 0) {
|
|
37
|
+
console.log(chalk_1.default.yellow('\nNo events found.\n'));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
console.log(chalk_1.default.bold.cyan(`\nš Recent Events (${events.length})\n`));
|
|
41
|
+
// Reverse to show oldest first
|
|
42
|
+
events.reverse();
|
|
43
|
+
for (const event of events) {
|
|
44
|
+
printEvent(event);
|
|
45
|
+
}
|
|
46
|
+
console.log(chalk_1.default.gray(`\nShowing ${events.length} events`));
|
|
47
|
+
console.log(chalk_1.default.gray('Use --follow to stream live events\n'));
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
spinner.fail('Failed to fetch events');
|
|
51
|
+
console.log(chalk_1.default.red(`\nError: ${error instanceof Error ? error.message : 'Unknown error'}\n`));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
async function streamLogs(api, options) {
|
|
56
|
+
console.log(chalk_1.default.bold.cyan('\nš” Streaming events (Ctrl+C to stop)\n'));
|
|
57
|
+
let lastEventId = null;
|
|
58
|
+
let isFirstFetch = true;
|
|
59
|
+
const poll = async () => {
|
|
60
|
+
try {
|
|
61
|
+
const params = {
|
|
62
|
+
limit: 20,
|
|
63
|
+
};
|
|
64
|
+
if (options.type) {
|
|
65
|
+
params.type = options.type;
|
|
66
|
+
}
|
|
67
|
+
if (options.source) {
|
|
68
|
+
params.source = options.source;
|
|
69
|
+
}
|
|
70
|
+
const response = await api.client.get('/events', { params });
|
|
71
|
+
const events = response.data.events;
|
|
72
|
+
// Filter to only show new events
|
|
73
|
+
let newEvents = events;
|
|
74
|
+
if (lastEventId) {
|
|
75
|
+
const lastIndex = events.findIndex((e) => e.id === lastEventId);
|
|
76
|
+
if (lastIndex > -1) {
|
|
77
|
+
newEvents = events.slice(0, lastIndex);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (newEvents.length > 0) {
|
|
81
|
+
if (!isFirstFetch) {
|
|
82
|
+
// Show new events (they come in reverse chronological order)
|
|
83
|
+
for (let i = newEvents.length - 1; i >= 0; i--) {
|
|
84
|
+
printEvent(newEvents[i]);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
// On first fetch, show the most recent event
|
|
89
|
+
printEvent(newEvents[0]);
|
|
90
|
+
isFirstFetch = false;
|
|
91
|
+
}
|
|
92
|
+
lastEventId = newEvents[0].id;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
console.log(chalk_1.default.red(`\nError: ${error instanceof Error ? error.message : 'Unknown error'}`));
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
// Initial fetch
|
|
100
|
+
await poll();
|
|
101
|
+
// Poll every 2 seconds
|
|
102
|
+
const intervalId = setInterval(poll, 2000);
|
|
103
|
+
// Handle Ctrl+C
|
|
104
|
+
process.on('SIGINT', () => {
|
|
105
|
+
clearInterval(intervalId);
|
|
106
|
+
console.log(chalk_1.default.gray('\n\nStopped streaming.\n'));
|
|
107
|
+
process.exit(0);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function printEvent(event) {
|
|
111
|
+
const timestamp = new Date(event.created_at).toLocaleTimeString();
|
|
112
|
+
const severityColor = getSeverityColor(event.severity);
|
|
113
|
+
const typeIcon = getTypeIcon(event.type);
|
|
114
|
+
const sourceTag = chalk_1.default.gray(`[${event.source}]`);
|
|
115
|
+
console.log(`${chalk_1.default.gray(timestamp)} ${typeIcon} ${chalk_1.default[severityColor](event.severity.toUpperCase().padEnd(8))} ${sourceTag} ${event.message}`);
|
|
116
|
+
}
|
|
117
|
+
function getSeverityColor(severity) {
|
|
118
|
+
switch (severity.toLowerCase()) {
|
|
119
|
+
case 'critical':
|
|
120
|
+
return 'red';
|
|
121
|
+
case 'high':
|
|
122
|
+
return 'red';
|
|
123
|
+
case 'medium':
|
|
124
|
+
return 'yellow';
|
|
125
|
+
case 'low':
|
|
126
|
+
return 'blue';
|
|
127
|
+
default:
|
|
128
|
+
return 'gray';
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
function getTypeIcon(type) {
|
|
132
|
+
switch (type.toLowerCase()) {
|
|
133
|
+
case 'error':
|
|
134
|
+
return 'ā';
|
|
135
|
+
case 'down':
|
|
136
|
+
return 'ā';
|
|
137
|
+
case 'up':
|
|
138
|
+
return 'ā';
|
|
139
|
+
case 'slow':
|
|
140
|
+
return 'ā ';
|
|
141
|
+
default:
|
|
142
|
+
return 'ā¢';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.statusCommand = statusCommand;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const ora_1 = __importDefault(require("ora"));
|
|
9
|
+
const api_js_1 = require("../api.js");
|
|
10
|
+
async function statusCommand(options = {}) {
|
|
11
|
+
const serverUrl = options.server || 'http://localhost:3000';
|
|
12
|
+
const api = new api_js_1.ScanWarpAPI(serverUrl);
|
|
13
|
+
const spinner = (0, ora_1.default)('Fetching status...').start();
|
|
14
|
+
try {
|
|
15
|
+
// Fetch monitors and incidents
|
|
16
|
+
const [monitorsResponse, incidentsResponse] = await Promise.all([
|
|
17
|
+
api.client.get('/monitors'),
|
|
18
|
+
api.client.get('/incidents', { params: { status: 'open' } }),
|
|
19
|
+
]);
|
|
20
|
+
spinner.stop();
|
|
21
|
+
const monitors = monitorsResponse.data.monitors;
|
|
22
|
+
const incidents = incidentsResponse.data.incidents;
|
|
23
|
+
// Print header
|
|
24
|
+
console.log(chalk_1.default.bold.cyan('\nš ScanWarp Status\n'));
|
|
25
|
+
// Print monitors
|
|
26
|
+
if (monitors.length === 0) {
|
|
27
|
+
console.log(chalk_1.default.yellow('No monitors configured yet.'));
|
|
28
|
+
console.log(chalk_1.default.gray('Run "scanwarp init" to set up monitoring.\n'));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log(chalk_1.default.bold('Monitors:\n'));
|
|
32
|
+
for (const monitor of monitors) {
|
|
33
|
+
const statusIcon = getStatusIcon(monitor.status);
|
|
34
|
+
const statusColor = getStatusColor(monitor.status);
|
|
35
|
+
const lastChecked = monitor.last_checked_at
|
|
36
|
+
? formatRelativeTime(new Date(monitor.last_checked_at))
|
|
37
|
+
: 'never';
|
|
38
|
+
console.log(` ${statusIcon} ${chalk_1.default[statusColor](monitor.status.toUpperCase().padEnd(7))} ${monitor.url}`);
|
|
39
|
+
console.log(chalk_1.default.gray(` Last checked: ${lastChecked} ⢠Every ${monitor.check_interval_seconds}s`));
|
|
40
|
+
}
|
|
41
|
+
// Print summary
|
|
42
|
+
const upCount = monitors.filter((m) => m.status === 'up').length;
|
|
43
|
+
const downCount = monitors.filter((m) => m.status === 'down').length;
|
|
44
|
+
const unknownCount = monitors.filter((m) => m.status === 'unknown').length;
|
|
45
|
+
console.log(chalk_1.default.bold(`\nSummary: ${monitors.length} total`));
|
|
46
|
+
if (upCount > 0)
|
|
47
|
+
console.log(chalk_1.default.green(` ā ${upCount} up`));
|
|
48
|
+
if (downCount > 0)
|
|
49
|
+
console.log(chalk_1.default.red(` ā ${downCount} down`));
|
|
50
|
+
if (unknownCount > 0)
|
|
51
|
+
console.log(chalk_1.default.gray(` ? ${unknownCount} unknown`));
|
|
52
|
+
// Print active incidents
|
|
53
|
+
if (incidents.length > 0) {
|
|
54
|
+
console.log(chalk_1.default.bold.red(`\nā ${incidents.length} Active Incident${incidents.length > 1 ? 's' : ''}:\n`));
|
|
55
|
+
for (const incident of incidents) {
|
|
56
|
+
const severityIcon = getSeverityIcon(incident.severity);
|
|
57
|
+
const severityColor = getSeverityColor(incident.severity);
|
|
58
|
+
console.log(` ${severityIcon} ${chalk_1.default[severityColor](incident.severity.toUpperCase())} ⢠${incident.status}`);
|
|
59
|
+
if (incident.diagnosis_text) {
|
|
60
|
+
const preview = incident.diagnosis_text.substring(0, 80);
|
|
61
|
+
console.log(chalk_1.default.gray(` ${preview}${incident.diagnosis_text.length > 80 ? '...' : ''}`));
|
|
62
|
+
}
|
|
63
|
+
console.log(chalk_1.default.gray(` Created: ${formatRelativeTime(new Date(incident.created_at))}`));
|
|
64
|
+
console.log(chalk_1.default.gray(` View: scanwarp incidents\n`));
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.log(chalk_1.default.green('\nā No active incidents'));
|
|
69
|
+
}
|
|
70
|
+
console.log(chalk_1.default.gray(`\nServer: ${serverUrl}\n`));
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
spinner.fail('Failed to fetch status');
|
|
74
|
+
if (error instanceof Error && 'code' in error && error.code === 'ECONNREFUSED') {
|
|
75
|
+
console.log(chalk_1.default.red('\nā Could not connect to ScanWarp server'));
|
|
76
|
+
console.log(chalk_1.default.gray(` Server: ${serverUrl}`));
|
|
77
|
+
console.log(chalk_1.default.yellow(' Make sure the server is running.\n'));
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
console.log(chalk_1.default.red(`\nError: ${error instanceof Error ? error.message : 'Unknown error'}\n`));
|
|
81
|
+
}
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
function getStatusIcon(status) {
|
|
86
|
+
switch (status) {
|
|
87
|
+
case 'up':
|
|
88
|
+
return chalk_1.default.green('ā');
|
|
89
|
+
case 'down':
|
|
90
|
+
return chalk_1.default.red('ā');
|
|
91
|
+
default:
|
|
92
|
+
return chalk_1.default.gray('ā');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function getStatusColor(status) {
|
|
96
|
+
switch (status) {
|
|
97
|
+
case 'up':
|
|
98
|
+
return 'green';
|
|
99
|
+
case 'down':
|
|
100
|
+
return 'red';
|
|
101
|
+
default:
|
|
102
|
+
return 'gray';
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
function getSeverityIcon(severity) {
|
|
106
|
+
switch (severity.toLowerCase()) {
|
|
107
|
+
case 'critical':
|
|
108
|
+
return 'šØ';
|
|
109
|
+
case 'warning':
|
|
110
|
+
return 'ā ļø';
|
|
111
|
+
default:
|
|
112
|
+
return 'ā¹ļø';
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function getSeverityColor(severity) {
|
|
116
|
+
switch (severity.toLowerCase()) {
|
|
117
|
+
case 'critical':
|
|
118
|
+
return 'red';
|
|
119
|
+
case 'warning':
|
|
120
|
+
return 'yellow';
|
|
121
|
+
default:
|
|
122
|
+
return 'blue';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
function formatRelativeTime(date) {
|
|
126
|
+
const now = new Date();
|
|
127
|
+
const diffMs = now.getTime() - date.getTime();
|
|
128
|
+
const diffSecs = Math.floor(diffMs / 1000);
|
|
129
|
+
const diffMins = Math.floor(diffSecs / 60);
|
|
130
|
+
const diffHours = Math.floor(diffMins / 60);
|
|
131
|
+
const diffDays = Math.floor(diffHours / 24);
|
|
132
|
+
if (diffSecs < 60)
|
|
133
|
+
return `${diffSecs}s ago`;
|
|
134
|
+
if (diffMins < 60)
|
|
135
|
+
return `${diffMins}m ago`;
|
|
136
|
+
if (diffHours < 24)
|
|
137
|
+
return `${diffHours}h ago`;
|
|
138
|
+
return `${diffDays}d ago`;
|
|
139
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.config = exports.ConfigManager = void 0;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), '.scanwarp');
|
|
11
|
+
const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
|
|
12
|
+
class ConfigManager {
|
|
13
|
+
config = {};
|
|
14
|
+
constructor() {
|
|
15
|
+
this.load();
|
|
16
|
+
}
|
|
17
|
+
ensureConfigDir() {
|
|
18
|
+
if (!fs_1.default.existsSync(CONFIG_DIR)) {
|
|
19
|
+
fs_1.default.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
load() {
|
|
23
|
+
try {
|
|
24
|
+
if (fs_1.default.existsSync(CONFIG_FILE)) {
|
|
25
|
+
const content = fs_1.default.readFileSync(CONFIG_FILE, 'utf-8');
|
|
26
|
+
this.config = JSON.parse(content);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.warn('Failed to load config:', error);
|
|
31
|
+
this.config = {};
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
save() {
|
|
35
|
+
try {
|
|
36
|
+
this.ensureConfigDir();
|
|
37
|
+
fs_1.default.writeFileSync(CONFIG_FILE, JSON.stringify(this.config, null, 2));
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
console.error('Failed to save config:', error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
get(key) {
|
|
44
|
+
return this.config[key];
|
|
45
|
+
}
|
|
46
|
+
set(key, value) {
|
|
47
|
+
this.config[key] = value;
|
|
48
|
+
this.save();
|
|
49
|
+
}
|
|
50
|
+
getAll() {
|
|
51
|
+
return { ...this.config };
|
|
52
|
+
}
|
|
53
|
+
clear() {
|
|
54
|
+
this.config = {};
|
|
55
|
+
this.save();
|
|
56
|
+
}
|
|
57
|
+
getServerUrl() {
|
|
58
|
+
return this.config.serverUrl || 'http://localhost:3000';
|
|
59
|
+
}
|
|
60
|
+
setServerUrl(url) {
|
|
61
|
+
this.set('serverUrl', url);
|
|
62
|
+
}
|
|
63
|
+
getProjectId() {
|
|
64
|
+
return this.config.projectId;
|
|
65
|
+
}
|
|
66
|
+
setProjectId(id) {
|
|
67
|
+
this.set('projectId', id);
|
|
68
|
+
}
|
|
69
|
+
getApiToken() {
|
|
70
|
+
return this.config.apiToken;
|
|
71
|
+
}
|
|
72
|
+
setApiToken(token) {
|
|
73
|
+
this.set('apiToken', token);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
exports.ConfigManager = ConfigManager;
|
|
77
|
+
exports.config = new ConfigManager();
|