@ktmcp-cli/alertersystem 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/AGENT.md ADDED
@@ -0,0 +1,40 @@
1
+ # Alerter System CLI - AI Agent Guide
2
+
3
+ This CLI provides programmatic access to the Alerter System Monitoring API.
4
+
5
+ ## Quick Start for AI Agents
6
+
7
+ ```bash
8
+ alertersystem config set --token YOUR_TOKEN
9
+ alertersystem alerts list
10
+ alertersystem monitors list
11
+ ```
12
+
13
+ ## Available Commands
14
+
15
+ ### config
16
+ - `alertersystem config set --token <token>` - Set bearer token
17
+ - `alertersystem config get <key>` - Get config value
18
+ - `alertersystem config list` - Show all config
19
+
20
+ ### alerts
21
+ - `alertersystem alerts list [--status open|acknowledged|resolved]` - List alerts
22
+ - `alertersystem alerts get <alert-id>` - Get alert details
23
+ - `alertersystem alerts acknowledge <alert-id>` - Acknowledge alert
24
+ - `alertersystem alerts resolve <alert-id>` - Resolve alert
25
+
26
+ ### monitors
27
+ - `alertersystem monitors list` - List all monitors
28
+ - `alertersystem monitors get <monitor-id>` - Get monitor details
29
+ - `alertersystem monitors create --name <name> --url <url> [--interval <seconds>] [--type http|https|tcp|ping]` - Create monitor
30
+ - `alertersystem monitors delete <monitor-id>` - Delete monitor
31
+
32
+ ### notifications
33
+ - `alertersystem notifications list` - List all notifications
34
+ - `alertersystem notifications get <notification-id>` - Get notification details
35
+
36
+ ## Tips for Agents
37
+
38
+ - All commands support `--json` for machine-readable output
39
+ - Filter critical alerts: `alertersystem alerts list --json | jq '.[] | select(.severity == "critical")'`
40
+ - Acknowledge all open alerts: `alertersystem alerts list --status open --json | jq -r '.[].id' | xargs -I{} alertersystem alerts acknowledge {}`
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 KTMCP
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,133 @@
1
+ > "Six months ago, everyone was talking about MCPs. And I was like, screw MCPs. Every MCP would be better as a CLI."
2
+ >
3
+ > — [Peter Steinberger](https://twitter.com/steipete), Founder of OpenClaw
4
+ > [Watch on YouTube (~2:39:00)](https://www.youtube.com/@lexfridman) | [Lex Fridman Podcast #491](https://lexfridman.com/peter-steinberger/)
5
+
6
+ # Alerter System CLI
7
+
8
+ Production-ready CLI for the [Alerter System](https://alertersystem.com) Monitoring API. Manage alerts, monitors, and notifications directly from your terminal.
9
+
10
+ > **Disclaimer**: This is an unofficial CLI tool and is not affiliated with, endorsed by, or supported by Alerter System.
11
+
12
+ ## Installation
13
+
14
+ ```bash
15
+ npm install -g @ktmcp-cli/alertersystem
16
+ ```
17
+
18
+ ## Configuration
19
+
20
+ ```bash
21
+ alertersystem config set --token YOUR_BEARER_TOKEN
22
+ ```
23
+
24
+ Get your API token at [alertersystem.com](https://alertersystem.com).
25
+
26
+ ## Usage
27
+
28
+ ### Configuration
29
+
30
+ ```bash
31
+ # Set bearer token
32
+ alertersystem config set --token YOUR_BEARER_TOKEN
33
+
34
+ # Show configuration
35
+ alertersystem config list
36
+
37
+ # Get a specific config value
38
+ alertersystem config get token
39
+ ```
40
+
41
+ ### Alerts
42
+
43
+ ```bash
44
+ # List all alerts
45
+ alertersystem alerts list
46
+
47
+ # Filter by status
48
+ alertersystem alerts list --status open
49
+ alertersystem alerts list --status acknowledged
50
+ alertersystem alerts list --status resolved
51
+
52
+ # Get a specific alert
53
+ alertersystem alerts get ALERT_ID
54
+
55
+ # Acknowledge an alert
56
+ alertersystem alerts acknowledge ALERT_ID
57
+
58
+ # Resolve an alert
59
+ alertersystem alerts resolve ALERT_ID
60
+ ```
61
+
62
+ ### Monitors
63
+
64
+ ```bash
65
+ # List all monitors
66
+ alertersystem monitors list
67
+
68
+ # Get a specific monitor
69
+ alertersystem monitors get MONITOR_ID
70
+
71
+ # Create a new monitor
72
+ alertersystem monitors create \
73
+ --name "My Website" \
74
+ --url https://example.com \
75
+ --interval 60 \
76
+ --type https
77
+
78
+ # Delete a monitor
79
+ alertersystem monitors delete MONITOR_ID
80
+ ```
81
+
82
+ ### Notifications
83
+
84
+ ```bash
85
+ # List all notifications
86
+ alertersystem notifications list
87
+
88
+ # Get a specific notification
89
+ alertersystem notifications get NOTIFICATION_ID
90
+ ```
91
+
92
+ ### JSON Output
93
+
94
+ All commands support `--json` for machine-readable output:
95
+
96
+ ```bash
97
+ # List open alerts as JSON
98
+ alertersystem alerts list --status open --json
99
+
100
+ # Pipe to jq
101
+ alertersystem alerts list --json | jq '.[] | select(.severity == "critical") | {id, name}'
102
+
103
+ # Get monitor status
104
+ alertersystem monitors list --json | jq '.[] | {name, status, uptime}'
105
+ ```
106
+
107
+ ## Examples
108
+
109
+ ```bash
110
+ # Morning ops check: see all open alerts
111
+ alertersystem alerts list --status open
112
+
113
+ # Acknowledge all critical alerts
114
+ alertersystem alerts list --json | jq -r '.[] | select(.severity == "critical") | .id' | \
115
+ xargs -I{} alertersystem alerts acknowledge {}
116
+
117
+ # Create a monitor for your API
118
+ alertersystem monitors create --name "Production API" --url https://api.example.com/health --interval 30
119
+
120
+ # Check monitor uptime
121
+ alertersystem monitors list --json | jq '.[] | {name, uptime: .uptime}'
122
+
123
+ # Count unread notifications
124
+ alertersystem notifications list --json | jq '[.[] | select(.read == false)] | length'
125
+ ```
126
+
127
+ ## License
128
+
129
+ MIT
130
+
131
+ ---
132
+
133
+ Part of the [KTMCP CLI](https://killthemcp.com) project — replacing MCPs with simple, composable CLIs.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../src/index.js';
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@ktmcp-cli/alertersystem",
3
+ "version": "1.0.0",
4
+ "description": "Production-ready CLI for Alerter System Monitoring API - Kill The MCP",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "alertersystem": "bin/alertersystem.js"
9
+ },
10
+ "keywords": ["alertersystem", "monitoring", "alerts", "cli", "api", "ktmcp"],
11
+ "author": "KTMCP",
12
+ "license": "MIT",
13
+ "dependencies": {
14
+ "commander": "^12.0.0",
15
+ "axios": "^1.6.7",
16
+ "chalk": "^5.3.0",
17
+ "ora": "^8.0.1",
18
+ "conf": "^12.0.0"
19
+ },
20
+ "engines": { "node": ">=18.0.0" },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/ktmcp-cli/alertersystem.git"
24
+ },
25
+ "homepage": "https://killthemcp.com/alertersystem-cli",
26
+ "bugs": { "url": "https://github.com/ktmcp-cli/alertersystem/issues" }
27
+ }
package/src/api.js ADDED
@@ -0,0 +1,138 @@
1
+ import axios from 'axios';
2
+ import { getConfig } from './config.js';
3
+
4
+ const BASE_URL = 'https://api.alertersystem.com';
5
+
6
+ function getToken() {
7
+ const token = getConfig('token');
8
+ if (!token) {
9
+ throw new Error('Bearer token not configured. Run: alertersystem config set token YOUR_TOKEN');
10
+ }
11
+ return token;
12
+ }
13
+
14
+ function handleApiError(error) {
15
+ if (error.response) {
16
+ const status = error.response.status;
17
+ const data = error.response.data;
18
+ if (status === 401 || status === 403) {
19
+ throw new Error('Authentication failed. Check your token: alertersystem config set token YOUR_TOKEN');
20
+ } else if (status === 429) {
21
+ throw new Error('Rate limit exceeded. Please wait before retrying.');
22
+ } else if (status === 404) {
23
+ throw new Error('Resource not found.');
24
+ } else {
25
+ const message = data?.error || data?.message || JSON.stringify(data);
26
+ throw new Error(`API Error (${status}): ${message}`);
27
+ }
28
+ } else if (error.request) {
29
+ throw new Error('No response from Alerter System API. Check your internet connection.');
30
+ } else {
31
+ throw error;
32
+ }
33
+ }
34
+
35
+ function getHeaders() {
36
+ return {
37
+ 'Authorization': `Bearer ${getToken()}`,
38
+ 'Content-Type': 'application/json',
39
+ 'Accept': 'application/json'
40
+ };
41
+ }
42
+
43
+ async function apiGet(path, params = {}) {
44
+ try {
45
+ const response = await axios.get(`${BASE_URL}${path}`, {
46
+ headers: getHeaders(),
47
+ params
48
+ });
49
+ return response.data;
50
+ } catch (error) {
51
+ handleApiError(error);
52
+ }
53
+ }
54
+
55
+ async function apiPost(path, data = {}) {
56
+ try {
57
+ const response = await axios.post(`${BASE_URL}${path}`, data, {
58
+ headers: getHeaders()
59
+ });
60
+ return response.data;
61
+ } catch (error) {
62
+ handleApiError(error);
63
+ }
64
+ }
65
+
66
+ async function apiDelete(path) {
67
+ try {
68
+ const response = await axios.delete(`${BASE_URL}${path}`, {
69
+ headers: getHeaders()
70
+ });
71
+ return response.data;
72
+ } catch (error) {
73
+ handleApiError(error);
74
+ }
75
+ }
76
+
77
+ async function apiPatch(path, data = {}) {
78
+ try {
79
+ const response = await axios.patch(`${BASE_URL}${path}`, data, {
80
+ headers: getHeaders()
81
+ });
82
+ return response.data;
83
+ } catch (error) {
84
+ handleApiError(error);
85
+ }
86
+ }
87
+
88
+ // ============================================================
89
+ // ALERTS
90
+ // ============================================================
91
+
92
+ export async function listAlerts(params = {}) {
93
+ return await apiGet('/alerts', params);
94
+ }
95
+
96
+ export async function getAlert(alertId) {
97
+ return await apiGet(`/alerts/${alertId}`);
98
+ }
99
+
100
+ export async function acknowledgeAlert(alertId) {
101
+ return await apiPatch(`/alerts/${alertId}/acknowledge`, {});
102
+ }
103
+
104
+ export async function resolveAlert(alertId) {
105
+ return await apiPatch(`/alerts/${alertId}/resolve`, {});
106
+ }
107
+
108
+ // ============================================================
109
+ // MONITORS
110
+ // ============================================================
111
+
112
+ export async function listMonitors(params = {}) {
113
+ return await apiGet('/monitors', params);
114
+ }
115
+
116
+ export async function getMonitor(monitorId) {
117
+ return await apiGet(`/monitors/${monitorId}`);
118
+ }
119
+
120
+ export async function createMonitor({ name, url, interval, type }) {
121
+ return await apiPost('/monitors', { name, url, interval, type });
122
+ }
123
+
124
+ export async function deleteMonitor(monitorId) {
125
+ return await apiDelete(`/monitors/${monitorId}`);
126
+ }
127
+
128
+ // ============================================================
129
+ // NOTIFICATIONS
130
+ // ============================================================
131
+
132
+ export async function listNotifications(params = {}) {
133
+ return await apiGet('/notifications', params);
134
+ }
135
+
136
+ export async function getNotification(notificationId) {
137
+ return await apiGet(`/notifications/${notificationId}`);
138
+ }
package/src/config.js ADDED
@@ -0,0 +1,19 @@
1
+ import Conf from 'conf';
2
+
3
+ const config = new Conf({ projectName: '@ktmcp-cli/alertersystem' });
4
+
5
+ export function getConfig(key) {
6
+ return config.get(key);
7
+ }
8
+
9
+ export function setConfig(key, value) {
10
+ config.set(key, value);
11
+ }
12
+
13
+ export function isConfigured() {
14
+ return !!config.get('token');
15
+ }
16
+
17
+ export function getAllConfig() {
18
+ return config.store;
19
+ }
package/src/index.js ADDED
@@ -0,0 +1,405 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { getConfig, setConfig, isConfigured, getAllConfig } from './config.js';
5
+ import {
6
+ listAlerts,
7
+ getAlert,
8
+ acknowledgeAlert,
9
+ resolveAlert,
10
+ listMonitors,
11
+ getMonitor,
12
+ createMonitor,
13
+ deleteMonitor,
14
+ listNotifications,
15
+ getNotification
16
+ } from './api.js';
17
+
18
+ const program = new Command();
19
+
20
+ // ============================================================
21
+ // Helpers
22
+ // ============================================================
23
+
24
+ function printSuccess(message) {
25
+ console.log(chalk.green('✓') + ' ' + message);
26
+ }
27
+
28
+ function printError(message) {
29
+ console.error(chalk.red('✗') + ' ' + message);
30
+ }
31
+
32
+ function printTable(data, columns) {
33
+ if (!data || data.length === 0) {
34
+ console.log(chalk.yellow('No results found.'));
35
+ return;
36
+ }
37
+ const widths = {};
38
+ columns.forEach(col => {
39
+ widths[col.key] = col.label.length;
40
+ data.forEach(row => {
41
+ const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
42
+ if (val.length > widths[col.key]) widths[col.key] = val.length;
43
+ });
44
+ widths[col.key] = Math.min(widths[col.key], 40);
45
+ });
46
+ const header = columns.map(col => col.label.padEnd(widths[col.key])).join(' ');
47
+ console.log(chalk.bold(chalk.cyan(header)));
48
+ console.log(chalk.dim('─'.repeat(header.length)));
49
+ data.forEach(row => {
50
+ const line = columns.map(col => {
51
+ const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
52
+ return val.substring(0, widths[col.key]).padEnd(widths[col.key]);
53
+ }).join(' ');
54
+ console.log(line);
55
+ });
56
+ console.log(chalk.dim(`\n${data.length} result(s)`));
57
+ }
58
+
59
+ function printJson(data) {
60
+ console.log(JSON.stringify(data, null, 2));
61
+ }
62
+
63
+ async function withSpinner(message, fn) {
64
+ const spinner = ora(message).start();
65
+ try {
66
+ const result = await fn();
67
+ spinner.stop();
68
+ return result;
69
+ } catch (error) {
70
+ spinner.stop();
71
+ throw error;
72
+ }
73
+ }
74
+
75
+ function requireAuth() {
76
+ if (!isConfigured()) {
77
+ printError('Alerter System token not configured.');
78
+ console.log('\nRun the following to configure:');
79
+ console.log(chalk.cyan(' alertersystem config set token YOUR_BEARER_TOKEN'));
80
+ process.exit(1);
81
+ }
82
+ }
83
+
84
+ function alertStatusColor(status) {
85
+ switch ((status || '').toLowerCase()) {
86
+ case 'open': case 'firing': case 'critical': return chalk.red(status);
87
+ case 'acknowledged': return chalk.yellow(status);
88
+ case 'resolved': case 'closed': return chalk.green(status);
89
+ default: return status;
90
+ }
91
+ }
92
+
93
+ function monitorStatusColor(status) {
94
+ switch ((status || '').toLowerCase()) {
95
+ case 'up': case 'ok': case 'operational': return chalk.green(status);
96
+ case 'down': case 'error': case 'critical': return chalk.red(status);
97
+ case 'degraded': case 'warning': return chalk.yellow(status);
98
+ default: return status;
99
+ }
100
+ }
101
+
102
+ // ============================================================
103
+ // Program metadata
104
+ // ============================================================
105
+
106
+ program
107
+ .name('alertersystem')
108
+ .description(chalk.bold('Alerter System CLI') + ' - System monitoring from your terminal')
109
+ .version('1.0.0');
110
+
111
+ // ============================================================
112
+ // CONFIG
113
+ // ============================================================
114
+
115
+ const configCmd = program.command('config').description('Manage CLI configuration');
116
+
117
+ configCmd
118
+ .command('set')
119
+ .description('Set configuration values')
120
+ .option('--token <token>', 'Alerter System Bearer token')
121
+ .action((options) => {
122
+ if (options.token) {
123
+ setConfig('token', options.token);
124
+ printSuccess('Token set');
125
+ } else {
126
+ printError('No options provided. Use --token');
127
+ }
128
+ });
129
+
130
+ configCmd
131
+ .command('get')
132
+ .description('Get a configuration value')
133
+ .argument('<key>', 'Config key to retrieve')
134
+ .action((key) => {
135
+ const value = getConfig(key);
136
+ if (value === undefined || value === null || value === '') {
137
+ console.log(chalk.yellow(`${key}: not set`));
138
+ } else {
139
+ const masked = key.toLowerCase().includes('token') ? '*'.repeat(8) : value;
140
+ console.log(`${key}: ${chalk.green(masked)}`);
141
+ }
142
+ });
143
+
144
+ configCmd
145
+ .command('list')
146
+ .description('List all configuration values')
147
+ .action(() => {
148
+ const all = getAllConfig();
149
+ console.log(chalk.bold('\nAlerter System CLI Configuration\n'));
150
+ console.log('Token:', all.token ? chalk.green('*'.repeat(8)) : chalk.red('not set'));
151
+ console.log('');
152
+ });
153
+
154
+ // ============================================================
155
+ // ALERTS
156
+ // ============================================================
157
+
158
+ const alertsCmd = program.command('alerts').description('Manage system alerts');
159
+
160
+ alertsCmd
161
+ .command('list')
162
+ .description('List all alerts')
163
+ .option('--status <status>', 'Filter by status (open|acknowledged|resolved)')
164
+ .option('--json', 'Output as JSON')
165
+ .action(async (options) => {
166
+ requireAuth();
167
+ try {
168
+ const params = options.status ? { status: options.status } : {};
169
+ const data = await withSpinner('Fetching alerts...', () => listAlerts(params));
170
+ if (options.json) { printJson(data); return; }
171
+ const list = Array.isArray(data) ? data : (data?.alerts || data?.data || []);
172
+ printTable(list, [
173
+ { key: 'id', label: 'ID' },
174
+ { key: 'name', label: 'Name' },
175
+ { key: 'severity', label: 'Severity', format: (v) => v ? chalk.bold(v) : 'N/A' },
176
+ { key: 'status', label: 'Status', format: (v) => alertStatusColor(v) },
177
+ { key: 'monitor', label: 'Monitor', format: (v, row) => v || row.monitor_name || 'N/A' },
178
+ { key: 'created_at', label: 'Created' }
179
+ ]);
180
+ } catch (error) {
181
+ printError(error.message);
182
+ process.exit(1);
183
+ }
184
+ });
185
+
186
+ alertsCmd
187
+ .command('get <alert-id>')
188
+ .description('Get details of a specific alert')
189
+ .option('--json', 'Output as JSON')
190
+ .action(async (alertId, options) => {
191
+ requireAuth();
192
+ try {
193
+ const alert = await withSpinner('Fetching alert...', () => getAlert(alertId));
194
+ if (options.json) { printJson(alert); return; }
195
+ console.log(chalk.bold('\nAlert Details\n'));
196
+ console.log('ID: ', chalk.cyan(alert.id || alertId));
197
+ console.log('Name: ', chalk.bold(alert.name || 'N/A'));
198
+ console.log('Status: ', alertStatusColor(alert.status));
199
+ console.log('Severity: ', alert.severity || 'N/A');
200
+ console.log('Monitor: ', alert.monitor || alert.monitor_name || 'N/A');
201
+ console.log('Message: ', alert.message || alert.description || 'N/A');
202
+ console.log('Created: ', alert.created_at || 'N/A');
203
+ if (alert.resolved_at) console.log('Resolved: ', alert.resolved_at);
204
+ console.log('');
205
+ } catch (error) {
206
+ printError(error.message);
207
+ process.exit(1);
208
+ }
209
+ });
210
+
211
+ alertsCmd
212
+ .command('acknowledge <alert-id>')
213
+ .description('Acknowledge an alert')
214
+ .option('--json', 'Output as JSON')
215
+ .action(async (alertId, options) => {
216
+ requireAuth();
217
+ try {
218
+ const result = await withSpinner('Acknowledging alert...', () => acknowledgeAlert(alertId));
219
+ if (options.json) { printJson(result); return; }
220
+ printSuccess(`Alert ${chalk.cyan(alertId)} acknowledged`);
221
+ } catch (error) {
222
+ printError(error.message);
223
+ process.exit(1);
224
+ }
225
+ });
226
+
227
+ alertsCmd
228
+ .command('resolve <alert-id>')
229
+ .description('Resolve an alert')
230
+ .option('--json', 'Output as JSON')
231
+ .action(async (alertId, options) => {
232
+ requireAuth();
233
+ try {
234
+ const result = await withSpinner('Resolving alert...', () => resolveAlert(alertId));
235
+ if (options.json) { printJson(result); return; }
236
+ printSuccess(`Alert ${chalk.cyan(alertId)} resolved`);
237
+ } catch (error) {
238
+ printError(error.message);
239
+ process.exit(1);
240
+ }
241
+ });
242
+
243
+ // ============================================================
244
+ // MONITORS
245
+ // ============================================================
246
+
247
+ const monitorsCmd = program.command('monitors').description('Manage uptime monitors');
248
+
249
+ monitorsCmd
250
+ .command('list')
251
+ .description('List all monitors')
252
+ .option('--json', 'Output as JSON')
253
+ .action(async (options) => {
254
+ requireAuth();
255
+ try {
256
+ const data = await withSpinner('Fetching monitors...', () => listMonitors());
257
+ if (options.json) { printJson(data); return; }
258
+ const list = Array.isArray(data) ? data : (data?.monitors || data?.data || []);
259
+ printTable(list, [
260
+ { key: 'id', label: 'ID' },
261
+ { key: 'name', label: 'Name' },
262
+ { key: 'url', label: 'URL' },
263
+ { key: 'type', label: 'Type' },
264
+ { key: 'status', label: 'Status', format: (v) => monitorStatusColor(v) },
265
+ { key: 'interval', label: 'Interval', format: (v) => v ? `${v}s` : 'N/A' }
266
+ ]);
267
+ } catch (error) {
268
+ printError(error.message);
269
+ process.exit(1);
270
+ }
271
+ });
272
+
273
+ monitorsCmd
274
+ .command('get <monitor-id>')
275
+ .description('Get details of a specific monitor')
276
+ .option('--json', 'Output as JSON')
277
+ .action(async (monitorId, options) => {
278
+ requireAuth();
279
+ try {
280
+ const monitor = await withSpinner('Fetching monitor...', () => getMonitor(monitorId));
281
+ if (options.json) { printJson(monitor); return; }
282
+ console.log(chalk.bold('\nMonitor Details\n'));
283
+ console.log('ID: ', chalk.cyan(monitor.id || monitorId));
284
+ console.log('Name: ', chalk.bold(monitor.name || 'N/A'));
285
+ console.log('URL: ', monitor.url || 'N/A');
286
+ console.log('Type: ', monitor.type || 'N/A');
287
+ console.log('Status: ', monitorStatusColor(monitor.status));
288
+ console.log('Interval: ', monitor.interval ? `${monitor.interval}s` : 'N/A');
289
+ console.log('Created: ', monitor.created_at || 'N/A');
290
+ if (monitor.last_checked) console.log('Last Check: ', monitor.last_checked);
291
+ if (monitor.uptime) console.log('Uptime: ', `${monitor.uptime}%`);
292
+ console.log('');
293
+ } catch (error) {
294
+ printError(error.message);
295
+ process.exit(1);
296
+ }
297
+ });
298
+
299
+ monitorsCmd
300
+ .command('create')
301
+ .description('Create a new uptime monitor')
302
+ .requiredOption('--name <name>', 'Monitor name')
303
+ .requiredOption('--url <url>', 'URL to monitor')
304
+ .option('--interval <seconds>', 'Check interval in seconds', '60')
305
+ .option('--type <type>', 'Monitor type (http|https|tcp|ping)', 'https')
306
+ .option('--json', 'Output as JSON')
307
+ .action(async (options) => {
308
+ requireAuth();
309
+ try {
310
+ const monitor = await withSpinner('Creating monitor...', () =>
311
+ createMonitor({
312
+ name: options.name,
313
+ url: options.url,
314
+ interval: parseInt(options.interval),
315
+ type: options.type
316
+ })
317
+ );
318
+ if (options.json) { printJson(monitor); return; }
319
+ printSuccess(`Monitor created: ${chalk.bold(options.name)}`);
320
+ console.log('Monitor ID:', chalk.cyan(monitor.id || 'N/A'));
321
+ console.log('URL: ', options.url);
322
+ console.log('Interval: ', `${options.interval}s`);
323
+ console.log('');
324
+ } catch (error) {
325
+ printError(error.message);
326
+ process.exit(1);
327
+ }
328
+ });
329
+
330
+ monitorsCmd
331
+ .command('delete <monitor-id>')
332
+ .description('Delete a monitor')
333
+ .option('--json', 'Output as JSON')
334
+ .action(async (monitorId, options) => {
335
+ requireAuth();
336
+ try {
337
+ const result = await withSpinner('Deleting monitor...', () => deleteMonitor(monitorId));
338
+ if (options.json) { printJson(result || { deleted: true }); return; }
339
+ printSuccess(`Monitor ${chalk.cyan(monitorId)} deleted`);
340
+ } catch (error) {
341
+ printError(error.message);
342
+ process.exit(1);
343
+ }
344
+ });
345
+
346
+ // ============================================================
347
+ // NOTIFICATIONS
348
+ // ============================================================
349
+
350
+ const notificationsCmd = program.command('notifications').description('View notifications');
351
+
352
+ notificationsCmd
353
+ .command('list')
354
+ .description('List all notifications')
355
+ .option('--json', 'Output as JSON')
356
+ .action(async (options) => {
357
+ requireAuth();
358
+ try {
359
+ const data = await withSpinner('Fetching notifications...', () => listNotifications());
360
+ if (options.json) { printJson(data); return; }
361
+ const list = Array.isArray(data) ? data : (data?.notifications || data?.data || []);
362
+ printTable(list, [
363
+ { key: 'id', label: 'ID' },
364
+ { key: 'type', label: 'Type' },
365
+ { key: 'message', label: 'Message' },
366
+ { key: 'read', label: 'Read', format: (v) => v ? chalk.dim('Yes') : chalk.bold('No') },
367
+ { key: 'created_at', label: 'Created' }
368
+ ]);
369
+ } catch (error) {
370
+ printError(error.message);
371
+ process.exit(1);
372
+ }
373
+ });
374
+
375
+ notificationsCmd
376
+ .command('get <notification-id>')
377
+ .description('Get details of a specific notification')
378
+ .option('--json', 'Output as JSON')
379
+ .action(async (notificationId, options) => {
380
+ requireAuth();
381
+ try {
382
+ const notification = await withSpinner('Fetching notification...', () => getNotification(notificationId));
383
+ if (options.json) { printJson(notification); return; }
384
+ console.log(chalk.bold('\nNotification Details\n'));
385
+ console.log('ID: ', chalk.cyan(notification.id || notificationId));
386
+ console.log('Type: ', notification.type || 'N/A');
387
+ console.log('Message: ', notification.message || 'N/A');
388
+ console.log('Read: ', notification.read ? chalk.dim('Yes') : chalk.bold('No'));
389
+ console.log('Created: ', notification.created_at || 'N/A');
390
+ console.log('');
391
+ } catch (error) {
392
+ printError(error.message);
393
+ process.exit(1);
394
+ }
395
+ });
396
+
397
+ // ============================================================
398
+ // Parse
399
+ // ============================================================
400
+
401
+ program.parse(process.argv);
402
+
403
+ if (process.argv.length <= 2) {
404
+ program.help();
405
+ }