@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.
Files changed (3) hide show
  1. package/README.md +68 -24
  2. package/dist/index.js +70 -20
  3. 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 directly from your terminal.
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 Start
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. Add a monitor
27
- kuma monitors add --name "My API" --type http --url https://api.example.com
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
- #### `monitors add` flags
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
- | Flag | Description |
66
- |------|-------------|
67
- | `--name <name>` | New monitor name |
68
- | `--url <url>` | New URL or hostname |
69
- | `--interval <seconds>` | New check interval |
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
- ### Heartbeats
72
+ ### Notifications
72
73
 
73
74
  | Command | Description |
74
75
  |---------|-------------|
75
- | `kuma heartbeat <monitor-id>` | View last 20 heartbeats for a monitor |
76
- | `kuma heartbeat <monitor-id> --limit 50` | Show last N heartbeats |
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 Pages
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(parseInt(monitorId, 10));
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)("npm install -g @blackasteroid/kuma-cli@latest", {
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 (required for --type discord)").option("--discord-username <name>", "Discord bot display name (optional)").option("--telegram-token <token>", "Telegram bot token (required for --type telegram)").option("--telegram-chat-id <id>", "Telegram chat ID (required for --type telegram)").option("--slack-webhook <url>", "Slack webhook URL (required for --type slack)").option("--webhook-url <url>", "Webhook URL (required for --type webhook)").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(
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('kuma notifications create --type discord --name "Alerts" --discord-webhook https://discord.com/api/webhooks/...')}
31431
- ${source_default.cyan('kuma notifications create --type telegram --name "TG" --telegram-token 123:ABC --telegram-chat-id -100...')}
31432
- ${source_default.cyan('kuma notifications create --type webhook --name "My Hook" --webhook-url https://example.com/hook')}
31433
- ${source_default.cyan('kuma notifications create --type discord --name "Default" --discord-webhook $URL --default --apply-existing')}
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 (!opts.discordWebhook) {
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 = opts.discordWebhook;
31505
+ payload.discordWebhookUrl = discordWebhook;
31456
31506
  if (opts.discordUsername) payload.discordUsername = opts.discordUsername;
31457
31507
  break;
31458
31508
  case "telegram":
31459
- if (!opts.telegramToken || !opts.telegramChatId) {
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 = opts.telegramToken;
31512
+ payload.telegramBotToken = telegramToken;
31463
31513
  payload.telegramChatID = opts.telegramChatId;
31464
31514
  break;
31465
31515
  case "slack":
31466
- if (!opts.slackWebhook) {
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 = opts.slackWebhook;
31519
+ payload.slackwebhookURL = slackWebhook;
31470
31520
  break;
31471
31521
  case "webhook":
31472
- if (!opts.webhookUrl) {
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 = opts.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}`), opts);
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()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blackasteroid/kuma-cli",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "CLI for managing Uptime Kuma via Socket.IO API",
5
5
  "bin": {
6
6
  "kuma": "dist/index.js"