@blackasteroid/kuma-cli 1.2.0 → 1.2.1
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 +68 -24
- package/dist/index.js +70 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# kuma-cli
|
|
2
2
|
|
|
3
|
-
> CLI for managing [Uptime Kuma](https://github.com/louislam/uptime-kuma) via its native Socket.IO API. No more clicking through the web panel — manage monitors, status pages, and heartbeats
|
|
3
|
+
> CLI for managing [Uptime Kuma](https://github.com/louislam/uptime-kuma) via its native Socket.IO API. No more clicking through the web panel — manage monitors, status pages, and heartbeats from your terminal.
|
|
4
4
|
|
|
5
5
|
## Install
|
|
6
6
|
|
|
@@ -14,7 +14,7 @@ Or use without installing:
|
|
|
14
14
|
npx @blackasteroid/kuma-cli login https://kuma.example.com
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
## Quick
|
|
17
|
+
## Quick start
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
20
|
# 1. Authenticate
|
|
@@ -23,8 +23,8 @@ kuma login https://kuma.example.com
|
|
|
23
23
|
# 2. List monitors
|
|
24
24
|
kuma monitors list
|
|
25
25
|
|
|
26
|
-
# 3.
|
|
27
|
-
kuma monitors
|
|
26
|
+
# 3. Create a monitor
|
|
27
|
+
kuma monitors create --name "My API" --type http --url https://api.example.com
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
## Commands
|
|
@@ -43,46 +43,90 @@ kuma monitors add --name "My API" --type http --url https://api.example.com
|
|
|
43
43
|
|---------|-------------|
|
|
44
44
|
| `kuma monitors list` | List all monitors with status + uptime |
|
|
45
45
|
| `kuma monitors list --json` | Output raw JSON (for scripting) |
|
|
46
|
+
| `kuma monitors list --status down --json` | Filter by status |
|
|
47
|
+
| `kuma monitors list --tag <tag> --json` | Filter by tag |
|
|
46
48
|
| `kuma monitors add` | Add a monitor interactively |
|
|
47
49
|
| `kuma monitors add --name <n> --type http --url <url>` | Add non-interactively |
|
|
50
|
+
| `kuma monitors create --type http --name <n> --url <url>` | Create monitor non-interactively (pipeline-safe) |
|
|
51
|
+
| `kuma monitors create --type push --name <n> --json` | Create push monitor, returns pushToken |
|
|
48
52
|
| `kuma monitors update <id>` | Update name/url/interval of a monitor |
|
|
49
53
|
| `kuma monitors delete <id>` | Delete a monitor (with confirmation) |
|
|
50
54
|
| `kuma monitors delete <id> --force` | Delete without confirmation prompt |
|
|
51
55
|
| `kuma monitors pause <id>` | Pause a monitor |
|
|
52
56
|
| `kuma monitors resume <id>` | Resume a paused monitor |
|
|
57
|
+
| `kuma monitors bulk-pause --tag <tag>` | Pause all monitors matching tag |
|
|
58
|
+
| `kuma monitors bulk-pause --tag <tag> --dry-run` | Preview without touching anything |
|
|
59
|
+
| `kuma monitors bulk-resume --tag <tag>` | Resume all monitors matching tag |
|
|
60
|
+
| `kuma monitors set-notification <id> --notification-id <id>` | Assign notification to monitor |
|
|
53
61
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
| Flag | Description | Default |
|
|
57
|
-
|------|-------------|---------|
|
|
58
|
-
| `--name <name>` | Monitor name | (prompted) |
|
|
59
|
-
| `--type <type>` | Monitor type: `http`, `tcp`, `ping`, `dns`, `push`, ... | (prompted) |
|
|
60
|
-
| `--url <url>` | URL or hostname to monitor | (prompted) |
|
|
61
|
-
| `--interval <seconds>` | Check interval | `60` |
|
|
62
|
-
|
|
63
|
-
#### `monitors update` flags
|
|
62
|
+
### Heartbeats
|
|
64
63
|
|
|
65
|
-
|
|
|
66
|
-
|
|
67
|
-
|
|
|
68
|
-
|
|
|
69
|
-
|
|
|
64
|
+
| Command | Description |
|
|
65
|
+
|---------|-------------|
|
|
66
|
+
| `kuma heartbeat view <monitor-id>` | View last 20 heartbeats |
|
|
67
|
+
| `kuma heartbeat view <monitor-id> --limit 50` | Show last N heartbeats |
|
|
68
|
+
| `kuma heartbeat view <monitor-id> --json` | Output raw JSON |
|
|
69
|
+
| `kuma heartbeat send <push-token>` | Send push heartbeat (no auth needed) |
|
|
70
|
+
| `kuma heartbeat send <push-token> --status down --msg "text"` | Send with status/message |
|
|
70
71
|
|
|
71
|
-
###
|
|
72
|
+
### Notifications
|
|
72
73
|
|
|
73
74
|
| Command | Description |
|
|
74
75
|
|---------|-------------|
|
|
75
|
-
| `kuma
|
|
76
|
-
| `kuma
|
|
77
|
-
| `kuma heartbeat <monitor-id> --json` | Output raw JSON |
|
|
76
|
+
| `kuma notifications list` | List all notification channels |
|
|
77
|
+
| `kuma notifications create --type discord --name <n> --url <webhook>` | Create Discord notification channel |
|
|
78
78
|
|
|
79
|
-
### Status
|
|
79
|
+
### Status pages
|
|
80
80
|
|
|
81
81
|
| Command | Description |
|
|
82
82
|
|---------|-------------|
|
|
83
83
|
| `kuma status-pages list` | List all status pages |
|
|
84
84
|
| `kuma status-pages list --json` | Output raw JSON |
|
|
85
85
|
|
|
86
|
+
## Using with AI agents
|
|
87
|
+
|
|
88
|
+
kuma-cli works well in agent and automation contexts. Every command supports `--json` output and exits non-zero on errors, so you can parse results reliably and short-circuit on failure.
|
|
89
|
+
|
|
90
|
+
Set `KUMA_JSON=1` to force JSON output on all commands — useful when you don't control the call site.
|
|
91
|
+
|
|
92
|
+
**Check what's down:**
|
|
93
|
+
```bash
|
|
94
|
+
kuma monitors list --status down --json
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**Pause/resume around a deploy:**
|
|
98
|
+
```bash
|
|
99
|
+
kuma monitors bulk-pause --tag Production --dry-run # preview first
|
|
100
|
+
kuma monitors bulk-pause --tag Production
|
|
101
|
+
./deploy.sh
|
|
102
|
+
kuma monitors bulk-resume --tag Production
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
**Create a monitor and wire up a notification in one shot:**
|
|
106
|
+
```bash
|
|
107
|
+
MONITOR_ID=$(kuma monitors create --type http --name "my-service" \
|
|
108
|
+
--url https://my-service.com --tag Production --json | jq -r '.data.id')
|
|
109
|
+
kuma monitors set-notification $MONITOR_ID --notification-id 1
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**Push monitor for a GitHub Actions runner:**
|
|
113
|
+
```bash
|
|
114
|
+
# Create the monitor, capture the token
|
|
115
|
+
TOKEN=$(kuma monitors create --type push --name "runner-aang" --json | jq -r '.data.pushToken')
|
|
116
|
+
|
|
117
|
+
# In the workflow:
|
|
118
|
+
- name: Heartbeat
|
|
119
|
+
run: kuma heartbeat send ${{ secrets.RUNNER_PUSH_TOKEN }}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Connect a notification channel to all production monitors:**
|
|
123
|
+
```bash
|
|
124
|
+
NOTIF_ID=$(kuma notifications create --type discord --name "alerts" \
|
|
125
|
+
--url $WEBHOOK --json | jq -r '.data.id')
|
|
126
|
+
kuma monitors list --tag Production --json | jq -r '.[].id' | \
|
|
127
|
+
xargs -I{} kuma monitors set-notification {} --notification-id $NOTIF_ID
|
|
128
|
+
```
|
|
129
|
+
|
|
86
130
|
## Config
|
|
87
131
|
|
|
88
132
|
After login, your session is saved automatically — you won't need to re-authenticate on every command:
|
package/dist/index.js
CHANGED
|
@@ -30366,6 +30366,17 @@ ${source_default.dim("Notes:")}
|
|
|
30366
30366
|
const json = isJsonMode(opts);
|
|
30367
30367
|
try {
|
|
30368
30368
|
const normalizedUrl = url2.replace(/\/$/, "");
|
|
30369
|
+
if (!normalizedUrl.startsWith("https://")) {
|
|
30370
|
+
if (json) {
|
|
30371
|
+
console.log(JSON.stringify({
|
|
30372
|
+
warning: "Connecting over HTTP. Credentials will be transmitted in cleartext. Use HTTPS in production."
|
|
30373
|
+
}));
|
|
30374
|
+
} else {
|
|
30375
|
+
console.warn(source_default.yellow(
|
|
30376
|
+
"\u26A0\uFE0F Warning: connecting over HTTP. Your credentials will be sent in cleartext.\n Use https:// in production environments."
|
|
30377
|
+
));
|
|
30378
|
+
}
|
|
30379
|
+
}
|
|
30369
30380
|
const answers = await prompt([
|
|
30370
30381
|
{
|
|
30371
30382
|
type: "input",
|
|
@@ -31067,9 +31078,13 @@ ${source_default.dim("Examples:")}
|
|
|
31067
31078
|
const config = getConfig();
|
|
31068
31079
|
if (!config) requireAuth(opts);
|
|
31069
31080
|
const json = isJsonMode(opts);
|
|
31081
|
+
const parsedMonitorId = parseInt(monitorId, 10);
|
|
31082
|
+
if (isNaN(parsedMonitorId) || parsedMonitorId <= 0) {
|
|
31083
|
+
handleError(new Error(`Invalid monitor ID: "${monitorId}". Must be a positive integer.`), opts);
|
|
31084
|
+
}
|
|
31070
31085
|
try {
|
|
31071
31086
|
const client = await createAuthenticatedClient(config.url, config.token);
|
|
31072
|
-
const heartbeats = await client.getHeartbeatList(
|
|
31087
|
+
const heartbeats = await client.getHeartbeatList(parsedMonitorId);
|
|
31073
31088
|
client.disconnect();
|
|
31074
31089
|
const limit = parseInt(opts.limit ?? "20", 10);
|
|
31075
31090
|
const recent = heartbeats.slice(-limit).reverse();
|
|
@@ -31119,6 +31134,12 @@ ${source_default.dim("Finding your push token:")}
|
|
|
31119
31134
|
`
|
|
31120
31135
|
).action(async (pushToken, opts) => {
|
|
31121
31136
|
const json = isJsonMode(opts);
|
|
31137
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(pushToken)) {
|
|
31138
|
+
const msg = `Invalid push token format. Tokens must contain only alphanumeric characters, hyphens, and underscores.`;
|
|
31139
|
+
if (json) jsonError(msg, EXIT_CODES.GENERAL);
|
|
31140
|
+
console.error(source_default.red(`\u274C ${msg}`));
|
|
31141
|
+
process.exit(EXIT_CODES.GENERAL);
|
|
31142
|
+
}
|
|
31122
31143
|
const VALID_STATUSES = ["up", "down", "maintenance"];
|
|
31123
31144
|
const statusKey = (opts.status ?? "up").toLowerCase();
|
|
31124
31145
|
if (!VALID_STATUSES.includes(statusKey)) {
|
|
@@ -31324,7 +31345,7 @@ ${source_default.bold(`Upgrading kuma-cli`)} ${source_default.dim(`v${current}`)
|
|
|
31324
31345
|
);
|
|
31325
31346
|
}
|
|
31326
31347
|
try {
|
|
31327
|
-
(0, import_child_process.execSync)(
|
|
31348
|
+
(0, import_child_process.execSync)(`npm install -g @blackasteroid/kuma-cli@${latest}`, {
|
|
31328
31349
|
stdio: json ? "pipe" : "inherit"
|
|
31329
31350
|
});
|
|
31330
31351
|
} catch (err) {
|
|
@@ -31357,6 +31378,27 @@ ${source_default.bold(`Upgrading kuma-cli`)} ${source_default.dim(`v${current}`)
|
|
|
31357
31378
|
}
|
|
31358
31379
|
|
|
31359
31380
|
// src/commands/notifications.ts
|
|
31381
|
+
function resolveSecret(value2) {
|
|
31382
|
+
if (value2 === void 0) return void 0;
|
|
31383
|
+
if (value2.startsWith("$")) {
|
|
31384
|
+
const varName = value2.slice(1);
|
|
31385
|
+
const resolved = process.env[varName];
|
|
31386
|
+
if (!resolved) {
|
|
31387
|
+
return void 0;
|
|
31388
|
+
}
|
|
31389
|
+
return resolved;
|
|
31390
|
+
}
|
|
31391
|
+
if (value2 === "-") {
|
|
31392
|
+
try {
|
|
31393
|
+
const buf = Buffer.alloc(4096);
|
|
31394
|
+
const n = require("fs").readSync(0, buf, 0, buf.length, null);
|
|
31395
|
+
return buf.toString("utf8", 0, n).trim();
|
|
31396
|
+
} catch {
|
|
31397
|
+
return void 0;
|
|
31398
|
+
}
|
|
31399
|
+
}
|
|
31400
|
+
return value2;
|
|
31401
|
+
}
|
|
31360
31402
|
function notificationsCommand(program3) {
|
|
31361
31403
|
const notifications = program3.command("notifications").description("Manage notification channels (Discord, Telegram, webhook, ...)").addHelpText(
|
|
31362
31404
|
"after",
|
|
@@ -31423,14 +31465,18 @@ ${list.length} notification channel(s)`);
|
|
|
31423
31465
|
handleError(err, opts);
|
|
31424
31466
|
}
|
|
31425
31467
|
});
|
|
31426
|
-
notifications.command("create").description("Create a new notification channel").requiredOption("--type <type>", "Notification type: discord, telegram, slack, webhook, ...").requiredOption("--name <name>", "Friendly name for this notification channel").option("--discord-webhook <url>", "Discord webhook URL
|
|
31468
|
+
notifications.command("create").description("Create a new notification channel").requiredOption("--type <type>", "Notification type: discord, telegram, slack, webhook, ...").requiredOption("--name <name>", "Friendly name for this notification channel").option("--discord-webhook <url|$VAR>", "Discord webhook URL \u2014 pass value or env var name like '$DISCORD_WEBHOOK'").option("--discord-username <name>", "Discord bot display name (optional)").option("--telegram-token <token|$VAR>", "Telegram bot token \u2014 pass value or env var name like '$TELEGRAM_TOKEN'").option("--telegram-chat-id <id>", "Telegram chat ID (required for --type telegram)").option("--slack-webhook <url|$VAR>", "Slack webhook URL \u2014 pass value or env var name like '$SLACK_WEBHOOK'").option("--webhook-url <url|$VAR>", "Webhook URL \u2014 pass value or env var name like '$WEBHOOK_URL'").option("--webhook-content-type <type>", "Webhook content type (default: application/json)", "application/json").option("--default", "Enable this notification by default on all new monitors").option("--apply-existing", "Apply this notification to all existing monitors immediately").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
31427
31469
|
"after",
|
|
31428
31470
|
`
|
|
31429
31471
|
${source_default.dim("Examples:")}
|
|
31430
|
-
${source_default.cyan(
|
|
31431
|
-
${source_default.cyan(
|
|
31432
|
-
${source_default.cyan(
|
|
31433
|
-
${source_default.cyan(
|
|
31472
|
+
${source_default.cyan(`kuma notifications create --type discord --name "Alerts" --discord-webhook '$DISCORD_WEBHOOK'`)}
|
|
31473
|
+
${source_default.cyan(`kuma notifications create --type telegram --name "TG" --telegram-token '$TELEGRAM_TOKEN' --telegram-chat-id -100...`)}
|
|
31474
|
+
${source_default.cyan(`kuma notifications create --type webhook --name "My Hook" --webhook-url '$WEBHOOK_URL'`)}
|
|
31475
|
+
${source_default.cyan(`kuma notifications create --type discord --name "Default" --discord-webhook '$DISCORD_WEBHOOK' --default --apply-existing`)}
|
|
31476
|
+
|
|
31477
|
+
${source_default.dim("\u26A0\uFE0F Security: never pass secrets as literal flag values \u2014 use env vars:")}
|
|
31478
|
+
${source_default.cyan("export DISCORD_WEBHOOK=https://discord.com/api/webhooks/...")}
|
|
31479
|
+
${source_default.cyan(`kuma notifications create --type discord --name "Alerts" --discord-webhook '\\$DISCORD_WEBHOOK'`)}
|
|
31434
31480
|
|
|
31435
31481
|
${source_default.dim("Supported types:")}
|
|
31436
31482
|
discord, telegram, slack, webhook, gotify, ntfy, pushover, matrix, mattermost, teams ...
|
|
@@ -31447,32 +31493,36 @@ ${source_default.dim("Supported types:")}
|
|
|
31447
31493
|
active: true,
|
|
31448
31494
|
applyExisting: opts.applyExisting ?? false
|
|
31449
31495
|
};
|
|
31496
|
+
const discordWebhook = resolveSecret(opts.discordWebhook);
|
|
31497
|
+
const telegramToken = resolveSecret(opts.telegramToken);
|
|
31498
|
+
const slackWebhook = resolveSecret(opts.slackWebhook);
|
|
31499
|
+
const webhookUrl = resolveSecret(opts.webhookUrl);
|
|
31450
31500
|
switch (opts.type.toLowerCase()) {
|
|
31451
31501
|
case "discord":
|
|
31452
|
-
if (!
|
|
31453
|
-
handleError(new Error("--discord-webhook is required for --type discord"), opts);
|
|
31502
|
+
if (!discordWebhook) {
|
|
31503
|
+
handleError(new Error("--discord-webhook is required for --type discord (pass value or '$ENV_VAR_NAME')"), opts);
|
|
31454
31504
|
}
|
|
31455
|
-
payload.discordWebhookUrl =
|
|
31505
|
+
payload.discordWebhookUrl = discordWebhook;
|
|
31456
31506
|
if (opts.discordUsername) payload.discordUsername = opts.discordUsername;
|
|
31457
31507
|
break;
|
|
31458
31508
|
case "telegram":
|
|
31459
|
-
if (!
|
|
31509
|
+
if (!telegramToken || !opts.telegramChatId) {
|
|
31460
31510
|
handleError(new Error("--telegram-token and --telegram-chat-id are required for --type telegram"), opts);
|
|
31461
31511
|
}
|
|
31462
|
-
payload.telegramBotToken =
|
|
31512
|
+
payload.telegramBotToken = telegramToken;
|
|
31463
31513
|
payload.telegramChatID = opts.telegramChatId;
|
|
31464
31514
|
break;
|
|
31465
31515
|
case "slack":
|
|
31466
|
-
if (!
|
|
31467
|
-
handleError(new Error("--slack-webhook is required for --type slack"), opts);
|
|
31516
|
+
if (!slackWebhook) {
|
|
31517
|
+
handleError(new Error("--slack-webhook is required for --type slack (pass value or '$ENV_VAR_NAME')"), opts);
|
|
31468
31518
|
}
|
|
31469
|
-
payload.slackwebhookURL =
|
|
31519
|
+
payload.slackwebhookURL = slackWebhook;
|
|
31470
31520
|
break;
|
|
31471
31521
|
case "webhook":
|
|
31472
|
-
if (!
|
|
31473
|
-
handleError(new Error("--webhook-url is required for --type webhook"), opts);
|
|
31522
|
+
if (!webhookUrl) {
|
|
31523
|
+
handleError(new Error("--webhook-url is required for --type webhook (pass value or '$ENV_VAR_NAME')"), opts);
|
|
31474
31524
|
}
|
|
31475
|
-
payload.webhookURL =
|
|
31525
|
+
payload.webhookURL = webhookUrl;
|
|
31476
31526
|
payload.webhookContentType = opts.webhookContentType ?? "application/json";
|
|
31477
31527
|
break;
|
|
31478
31528
|
default:
|
|
@@ -31508,8 +31558,8 @@ ${source_default.dim("Examples:")}
|
|
|
31508
31558
|
if (!config) requireAuth(opts);
|
|
31509
31559
|
const json = isJsonMode(opts);
|
|
31510
31560
|
const notifId = parseInt(id, 10);
|
|
31511
|
-
if (isNaN(notifId)) {
|
|
31512
|
-
handleError(new Error(`Invalid notification ID: ${id}
|
|
31561
|
+
if (isNaN(notifId) || notifId <= 0) {
|
|
31562
|
+
handleError(new Error(`Invalid notification ID: "${id}". Must be a positive integer.`), opts);
|
|
31513
31563
|
}
|
|
31514
31564
|
if (!opts.force && !json) {
|
|
31515
31565
|
const enquirer3 = await Promise.resolve().then(() => __toESM(require_enquirer()));
|