@blackasteroid/kuma-cli 1.1.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 +484 -40
- 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
|
@@ -27808,10 +27808,16 @@ var KumaClient = class {
|
|
|
27808
27808
|
// BUG-01 fix: addMonitor uses callback, not a separate event
|
|
27809
27809
|
// BUG-03 fix: include required fields accepted_statuscodes, maxretries, retryInterval
|
|
27810
27810
|
async addMonitor(monitor) {
|
|
27811
|
+
const autoToken = monitor.type === "push" && !monitor.pushToken ? Array.from(crypto.getRandomValues(new Uint8Array(24))).map((b) => b.toString(16).padStart(2, "0")).join("") : void 0;
|
|
27811
27812
|
const payload = {
|
|
27812
27813
|
accepted_statuscodes: ["200-299"],
|
|
27813
27814
|
maxretries: 1,
|
|
27814
27815
|
retryInterval: 60,
|
|
27816
|
+
conditions: [],
|
|
27817
|
+
rabbitmqNodes: [],
|
|
27818
|
+
kafkaProducerBrokers: [],
|
|
27819
|
+
kafkaProducerSaslOptions: { mechanism: "none" },
|
|
27820
|
+
...autoToken ? { pushToken: autoToken } : {},
|
|
27815
27821
|
...monitor
|
|
27816
27822
|
};
|
|
27817
27823
|
return new Promise((resolve, reject) => {
|
|
@@ -27828,7 +27834,11 @@ var KumaClient = class {
|
|
|
27828
27834
|
reject(new Error(result.msg ?? "Failed to add monitor"));
|
|
27829
27835
|
return;
|
|
27830
27836
|
}
|
|
27831
|
-
resolve({
|
|
27837
|
+
resolve({
|
|
27838
|
+
id: result.monitorID,
|
|
27839
|
+
// Return the token we generated so the caller has it immediately
|
|
27840
|
+
pushToken: payload.pushToken
|
|
27841
|
+
});
|
|
27832
27842
|
}
|
|
27833
27843
|
);
|
|
27834
27844
|
});
|
|
@@ -27969,6 +27979,50 @@ var KumaClient = class {
|
|
|
27969
27979
|
});
|
|
27970
27980
|
}
|
|
27971
27981
|
// ---------------------------------------------------------------------------
|
|
27982
|
+
// Tags
|
|
27983
|
+
// ---------------------------------------------------------------------------
|
|
27984
|
+
/** Get all tags defined in Kuma. Callback-based event. */
|
|
27985
|
+
async getTags() {
|
|
27986
|
+
return new Promise((resolve, reject) => {
|
|
27987
|
+
const timer = setTimeout(() => reject(new Error("getTags timeout")), 1e4);
|
|
27988
|
+
this.socket.emit(
|
|
27989
|
+
"getTags",
|
|
27990
|
+
(result) => {
|
|
27991
|
+
clearTimeout(timer);
|
|
27992
|
+
if (!result.ok) {
|
|
27993
|
+
reject(new Error(result.msg ?? "Failed to fetch tags"));
|
|
27994
|
+
return;
|
|
27995
|
+
}
|
|
27996
|
+
resolve(result.tags ?? []);
|
|
27997
|
+
}
|
|
27998
|
+
);
|
|
27999
|
+
});
|
|
28000
|
+
}
|
|
28001
|
+
/**
|
|
28002
|
+
* Add a tag to a monitor.
|
|
28003
|
+
* socket.emit("addMonitorTag", tagID, monitorID, value, callback)
|
|
28004
|
+
* value is a user-defined label string (can be empty "").
|
|
28005
|
+
*/
|
|
28006
|
+
async addMonitorTag(tagId, monitorId, value2 = "") {
|
|
28007
|
+
return new Promise((resolve, reject) => {
|
|
28008
|
+
const timer = setTimeout(() => reject(new Error("addMonitorTag timeout")), 1e4);
|
|
28009
|
+
this.socket.emit(
|
|
28010
|
+
"addMonitorTag",
|
|
28011
|
+
tagId,
|
|
28012
|
+
monitorId,
|
|
28013
|
+
value2,
|
|
28014
|
+
(result) => {
|
|
28015
|
+
clearTimeout(timer);
|
|
28016
|
+
if (!result.ok) {
|
|
28017
|
+
reject(new Error(result.msg ?? "Failed to add tag to monitor"));
|
|
28018
|
+
return;
|
|
28019
|
+
}
|
|
28020
|
+
resolve();
|
|
28021
|
+
}
|
|
28022
|
+
);
|
|
28023
|
+
});
|
|
28024
|
+
}
|
|
28025
|
+
// ---------------------------------------------------------------------------
|
|
27972
28026
|
// Notifications
|
|
27973
28027
|
// ---------------------------------------------------------------------------
|
|
27974
28028
|
/**
|
|
@@ -28072,6 +28126,45 @@ var KumaClient = class {
|
|
|
28072
28126
|
);
|
|
28073
28127
|
});
|
|
28074
28128
|
}
|
|
28129
|
+
// ---------------------------------------------------------------------------
|
|
28130
|
+
// Bulk operations
|
|
28131
|
+
// ---------------------------------------------------------------------------
|
|
28132
|
+
/**
|
|
28133
|
+
* Pause all monitors matching a filter function.
|
|
28134
|
+
* Returns a list of { id, name, ok, error? } results.
|
|
28135
|
+
*/
|
|
28136
|
+
async bulkPause(filter) {
|
|
28137
|
+
const monitorMap = await this.getMonitorList();
|
|
28138
|
+
const targets = Object.values(monitorMap).filter(filter);
|
|
28139
|
+
const results = [];
|
|
28140
|
+
for (const m of targets) {
|
|
28141
|
+
try {
|
|
28142
|
+
await this.pauseMonitor(m.id);
|
|
28143
|
+
results.push({ id: m.id, name: m.name, ok: true });
|
|
28144
|
+
} catch (e) {
|
|
28145
|
+
results.push({ id: m.id, name: m.name, ok: false, error: e.message });
|
|
28146
|
+
}
|
|
28147
|
+
}
|
|
28148
|
+
return results;
|
|
28149
|
+
}
|
|
28150
|
+
/**
|
|
28151
|
+
* Resume all monitors matching a filter function.
|
|
28152
|
+
* Returns a list of { id, name, ok, error? } results.
|
|
28153
|
+
*/
|
|
28154
|
+
async bulkResume(filter) {
|
|
28155
|
+
const monitorMap = await this.getMonitorList();
|
|
28156
|
+
const targets = Object.values(monitorMap).filter(filter);
|
|
28157
|
+
const results = [];
|
|
28158
|
+
for (const m of targets) {
|
|
28159
|
+
try {
|
|
28160
|
+
await this.resumeMonitor(m.id);
|
|
28161
|
+
results.push({ id: m.id, name: m.name, ok: true });
|
|
28162
|
+
} catch (e) {
|
|
28163
|
+
results.push({ id: m.id, name: m.name, ok: false, error: e.message });
|
|
28164
|
+
}
|
|
28165
|
+
}
|
|
28166
|
+
return results;
|
|
28167
|
+
}
|
|
28075
28168
|
disconnect() {
|
|
28076
28169
|
this.socket.disconnect();
|
|
28077
28170
|
}
|
|
@@ -30204,9 +30297,9 @@ function isJsonMode(opts) {
|
|
|
30204
30297
|
const env3 = process.env["KUMA_JSON"];
|
|
30205
30298
|
return env3 === "1" || env3 === "true" || env3 === "yes";
|
|
30206
30299
|
}
|
|
30207
|
-
function jsonOut(data) {
|
|
30300
|
+
function jsonOut(data, exitCode = 0) {
|
|
30208
30301
|
console.log(JSON.stringify({ ok: true, data }, null, 2));
|
|
30209
|
-
process.exit(
|
|
30302
|
+
process.exit(exitCode);
|
|
30210
30303
|
}
|
|
30211
30304
|
function jsonError(message, code = 1) {
|
|
30212
30305
|
console.log(JSON.stringify({ ok: false, error: message, code }, null, 2));
|
|
@@ -30273,6 +30366,17 @@ ${source_default.dim("Notes:")}
|
|
|
30273
30366
|
const json = isJsonMode(opts);
|
|
30274
30367
|
try {
|
|
30275
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
|
+
}
|
|
30276
30380
|
const answers = await prompt([
|
|
30277
30381
|
{
|
|
30278
30382
|
type: "input",
|
|
@@ -30339,6 +30443,12 @@ ${source_default.dim("Examples:")}
|
|
|
30339
30443
|
// src/commands/monitors.ts
|
|
30340
30444
|
var import_enquirer2 = __toESM(require_enquirer());
|
|
30341
30445
|
var { prompt: prompt2 } = import_enquirer2.default;
|
|
30446
|
+
function collect(val, prev) {
|
|
30447
|
+
return [...prev, val];
|
|
30448
|
+
}
|
|
30449
|
+
function collectInt(val, prev) {
|
|
30450
|
+
return [...prev, parseInt(val, 10)];
|
|
30451
|
+
}
|
|
30342
30452
|
var MONITOR_TYPES = [
|
|
30343
30453
|
"http",
|
|
30344
30454
|
"tcp",
|
|
@@ -30513,6 +30623,90 @@ ${source_default.dim("Examples:")}
|
|
|
30513
30623
|
}
|
|
30514
30624
|
}
|
|
30515
30625
|
);
|
|
30626
|
+
monitors.command("create").description("Create a monitor non-interactively \u2014 designed for CI/CD pipelines").requiredOption("--name <name>", "Monitor display name").requiredOption("--type <type>", "Monitor type: http, tcp, ping, dns, push, ...").option("--url <url>", "URL or hostname to monitor").option("--interval <seconds>", "Check interval in seconds (default: 60)", "60").option("--tag <tag>", "Assign a tag by name (repeatable \u2014 must already exist in Kuma)", collect, []).option("--notification-id <id>", "Assign a notification channel by ID (repeatable)", collectInt, []).option("--json", "Output as JSON ({ ok, data }) \u2014 prints monitor ID and pushToken to stdout").addHelpText(
|
|
30627
|
+
"after",
|
|
30628
|
+
`
|
|
30629
|
+
${source_default.dim("Examples:")}
|
|
30630
|
+
${source_default.cyan('kuma monitors create --type http --name "habitu.ar" --url https://habitu.ar')}
|
|
30631
|
+
${source_default.cyan('kuma monitors create --type http --name "My API" --url https://api.example.com --tag Production --tag BlackAsteroid')}
|
|
30632
|
+
${source_default.cyan(`kuma monitors create --type push --name "GH Runner" --json | jq '.data.pushToken'`)}
|
|
30633
|
+
${source_default.cyan('kuma monitors create --type tcp --name "DB" --url db.host:5432 --interval 30 --notification-id 1')}
|
|
30634
|
+
|
|
30635
|
+
${source_default.dim("Full pipeline (deploy \u2192 monitor \u2192 heartbeat):")}
|
|
30636
|
+
${source_default.cyan('RESULT=$(kuma monitors create --type push --name "runner" --json)')}
|
|
30637
|
+
${source_default.cyan("PUSH_TOKEN=$(echo $RESULT | jq -r '.data.pushToken')")}
|
|
30638
|
+
${source_default.cyan('kuma heartbeat send $PUSH_TOKEN --msg "Alive"')}
|
|
30639
|
+
`
|
|
30640
|
+
).action(async (opts) => {
|
|
30641
|
+
const config = getConfig();
|
|
30642
|
+
if (!config) requireAuth(opts);
|
|
30643
|
+
const json = isJsonMode(opts);
|
|
30644
|
+
const interval = parseInt(opts.interval ?? "60", 10);
|
|
30645
|
+
if (["http", "keyword", "tcp", "ping", "dns"].includes(opts.type) && !opts.url) {
|
|
30646
|
+
handleError(new Error(`--url is required for monitor type "${opts.type}"`), opts);
|
|
30647
|
+
}
|
|
30648
|
+
try {
|
|
30649
|
+
const client = await createAuthenticatedClient(config.url, config.token);
|
|
30650
|
+
const result = await client.addMonitor({
|
|
30651
|
+
name: opts.name,
|
|
30652
|
+
type: opts.type,
|
|
30653
|
+
url: opts.url,
|
|
30654
|
+
interval
|
|
30655
|
+
});
|
|
30656
|
+
const monitorId = result.id;
|
|
30657
|
+
let pushToken = result.pushToken ?? null;
|
|
30658
|
+
const tagWarnings = [];
|
|
30659
|
+
if (opts.tag.length > 0) {
|
|
30660
|
+
const allTags = await client.getTags();
|
|
30661
|
+
const tagMap = new Map(allTags.map((t) => [t.name.toLowerCase(), t]));
|
|
30662
|
+
for (const tagName of opts.tag) {
|
|
30663
|
+
const found = tagMap.get(tagName.toLowerCase());
|
|
30664
|
+
if (!found) {
|
|
30665
|
+
const warn2 = `Tag "${tagName}" not found \u2014 skipping. Create it in the Kuma UI first.`;
|
|
30666
|
+
tagWarnings.push(warn2);
|
|
30667
|
+
if (!json) {
|
|
30668
|
+
console.warn(source_default.yellow(`\u26A0\uFE0F ${warn2}`));
|
|
30669
|
+
}
|
|
30670
|
+
continue;
|
|
30671
|
+
}
|
|
30672
|
+
await client.addMonitorTag(found.id, monitorId);
|
|
30673
|
+
}
|
|
30674
|
+
}
|
|
30675
|
+
if (opts.notificationId.length > 0) {
|
|
30676
|
+
const monitorMap = await client.getMonitorList();
|
|
30677
|
+
for (const notifId of opts.notificationId) {
|
|
30678
|
+
await client.setMonitorNotification(monitorId, notifId, true, monitorMap);
|
|
30679
|
+
}
|
|
30680
|
+
}
|
|
30681
|
+
client.disconnect();
|
|
30682
|
+
if (json) {
|
|
30683
|
+
const data = {
|
|
30684
|
+
id: monitorId,
|
|
30685
|
+
name: opts.name,
|
|
30686
|
+
type: opts.type,
|
|
30687
|
+
url: opts.url ?? null,
|
|
30688
|
+
interval
|
|
30689
|
+
};
|
|
30690
|
+
if (pushToken) data.pushToken = pushToken;
|
|
30691
|
+
if (tagWarnings.length > 0) data.warnings = tagWarnings;
|
|
30692
|
+
jsonOut(data, tagWarnings.length > 0 ? 1 : 0);
|
|
30693
|
+
}
|
|
30694
|
+
success(`Monitor "${opts.name}" created (ID: ${monitorId})`);
|
|
30695
|
+
if (pushToken) {
|
|
30696
|
+
console.log(` Push token: ${source_default.cyan(pushToken)}`);
|
|
30697
|
+
console.log(` Push URL: ${source_default.dim(`${config.url}/api/push/${pushToken}`)}`);
|
|
30698
|
+
}
|
|
30699
|
+
if (opts.tag.length > 0) {
|
|
30700
|
+
const applied = opts.tag.filter((t) => !tagWarnings.some((w) => w.includes(t)));
|
|
30701
|
+
if (applied.length > 0) console.log(` Tags: ${applied.join(", ")}`);
|
|
30702
|
+
}
|
|
30703
|
+
if (tagWarnings.length > 0) {
|
|
30704
|
+
process.exit(1);
|
|
30705
|
+
}
|
|
30706
|
+
} catch (err) {
|
|
30707
|
+
handleError(err, opts);
|
|
30708
|
+
}
|
|
30709
|
+
});
|
|
30516
30710
|
monitors.command("update <id>").description("Update the name, URL, interval, or active state of a monitor").option("--name <name>", "Set a new display name").option("--url <url>", "Set a new URL or hostname").option("--interval <seconds>", "Set a new check interval (seconds)").option("--active", "Resume the monitor (mark as active)").option("--no-active", "Pause the monitor (mark as inactive)").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
30517
30711
|
"after",
|
|
30518
30712
|
`
|
|
@@ -30684,6 +30878,137 @@ ${source_default.dim("Examples:")}
|
|
|
30684
30878
|
handleError(err, opts);
|
|
30685
30879
|
}
|
|
30686
30880
|
});
|
|
30881
|
+
monitors.command("bulk-pause").description("Pause all monitors matching a tag or status filter").option("--tag <tag>", "Pause all monitors with this tag").option("--status <status>", "Pause all monitors with this status: up, down, pending, maintenance").option("--dry-run", "Preview which monitors would be paused without pausing them").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
30882
|
+
"after",
|
|
30883
|
+
`
|
|
30884
|
+
${source_default.dim("Examples:")}
|
|
30885
|
+
${source_default.cyan("kuma monitors bulk-pause --tag Production")} Pause all Production monitors
|
|
30886
|
+
${source_default.cyan("kuma monitors bulk-pause --tag Production --dry-run")} Preview without pausing
|
|
30887
|
+
${source_default.cyan("kuma monitors bulk-pause --tag Production --json")} Machine-readable results
|
|
30888
|
+
|
|
30889
|
+
${source_default.dim("CI/CD usage:")}
|
|
30890
|
+
${source_default.cyan("kuma monitors bulk-pause --tag Production && ./deploy.sh && kuma monitors bulk-resume --tag Production")}
|
|
30891
|
+
`
|
|
30892
|
+
).action(async (opts) => {
|
|
30893
|
+
const config = getConfig();
|
|
30894
|
+
if (!config) requireAuth(opts);
|
|
30895
|
+
const json = isJsonMode(opts);
|
|
30896
|
+
if (!opts.tag && !opts.status) {
|
|
30897
|
+
handleError(new Error("At least one of --tag or --status is required"), opts);
|
|
30898
|
+
}
|
|
30899
|
+
const STATUS_MAP = { down: 0, up: 1, pending: 2, maintenance: 3 };
|
|
30900
|
+
try {
|
|
30901
|
+
const client = await createAuthenticatedClient(config.url, config.token);
|
|
30902
|
+
const monitorMap = await client.getMonitorList();
|
|
30903
|
+
const all = Object.values(monitorMap);
|
|
30904
|
+
let targets = all;
|
|
30905
|
+
if (opts.tag) {
|
|
30906
|
+
const tagName = opts.tag.toLowerCase();
|
|
30907
|
+
targets = targets.filter(
|
|
30908
|
+
(m) => Array.isArray(m.tags) && m.tags.some((t) => t.name.toLowerCase() === tagName)
|
|
30909
|
+
);
|
|
30910
|
+
}
|
|
30911
|
+
if (opts.status) {
|
|
30912
|
+
const statusNum = STATUS_MAP[opts.status.toLowerCase()];
|
|
30913
|
+
if (statusNum === void 0) {
|
|
30914
|
+
client.disconnect();
|
|
30915
|
+
handleError(new Error(`Invalid status "${opts.status}". Valid: up, down, pending, maintenance`), opts);
|
|
30916
|
+
}
|
|
30917
|
+
targets = targets.filter((m) => m.heartbeat?.status === statusNum);
|
|
30918
|
+
}
|
|
30919
|
+
if (targets.length === 0) {
|
|
30920
|
+
client.disconnect();
|
|
30921
|
+
if (json) jsonOut({ affected: 0, results: [] });
|
|
30922
|
+
console.log("No monitors matched the given filters.");
|
|
30923
|
+
return;
|
|
30924
|
+
}
|
|
30925
|
+
if (opts.dryRun) {
|
|
30926
|
+
client.disconnect();
|
|
30927
|
+
const preview = targets.map((m) => ({ id: m.id, name: m.name }));
|
|
30928
|
+
if (json) jsonOut({ dryRun: true, affected: targets.length, monitors: preview });
|
|
30929
|
+
console.log(source_default.yellow(`Dry run \u2014 would pause ${targets.length} monitor(s):`));
|
|
30930
|
+
preview.forEach((m) => console.log(` ${source_default.dim(String(m.id).padStart(4))} ${m.name}`));
|
|
30931
|
+
return;
|
|
30932
|
+
}
|
|
30933
|
+
const results = await client.bulkPause((m) => targets.some((t) => t.id === m.id));
|
|
30934
|
+
client.disconnect();
|
|
30935
|
+
const failed = results.filter((r) => !r.ok);
|
|
30936
|
+
if (json) {
|
|
30937
|
+
jsonOut({ affected: results.length, failed: failed.length, results });
|
|
30938
|
+
}
|
|
30939
|
+
console.log(`Paused ${results.length - failed.length}/${results.length} monitor(s)`);
|
|
30940
|
+
if (failed.length > 0) {
|
|
30941
|
+
failed.forEach((r) => error(` Monitor ${r.id} (${r.name}): ${r.error}`));
|
|
30942
|
+
process.exit(1);
|
|
30943
|
+
}
|
|
30944
|
+
} catch (err) {
|
|
30945
|
+
handleError(err, opts);
|
|
30946
|
+
}
|
|
30947
|
+
});
|
|
30948
|
+
monitors.command("bulk-resume").description("Resume all monitors matching a tag or status filter").option("--tag <tag>", "Resume all monitors with this tag").option("--status <status>", "Resume all monitors with this status: up, down, pending, maintenance").option("--dry-run", "Preview which monitors would be resumed without resuming them").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
30949
|
+
"after",
|
|
30950
|
+
`
|
|
30951
|
+
${source_default.dim("Examples:")}
|
|
30952
|
+
${source_default.cyan("kuma monitors bulk-resume --tag Production")}
|
|
30953
|
+
${source_default.cyan("kuma monitors bulk-resume --tag Production --dry-run")}
|
|
30954
|
+
${source_default.cyan("kuma monitors bulk-resume --tag Production --json")}
|
|
30955
|
+
`
|
|
30956
|
+
).action(async (opts) => {
|
|
30957
|
+
const config = getConfig();
|
|
30958
|
+
if (!config) requireAuth(opts);
|
|
30959
|
+
const json = isJsonMode(opts);
|
|
30960
|
+
if (!opts.tag && !opts.status) {
|
|
30961
|
+
handleError(new Error("At least one of --tag or --status is required"), opts);
|
|
30962
|
+
}
|
|
30963
|
+
const STATUS_MAP = { down: 0, up: 1, pending: 2, maintenance: 3 };
|
|
30964
|
+
try {
|
|
30965
|
+
const client = await createAuthenticatedClient(config.url, config.token);
|
|
30966
|
+
const monitorMap = await client.getMonitorList();
|
|
30967
|
+
const all = Object.values(monitorMap);
|
|
30968
|
+
let targets = all;
|
|
30969
|
+
if (opts.tag) {
|
|
30970
|
+
const tagName = opts.tag.toLowerCase();
|
|
30971
|
+
targets = targets.filter(
|
|
30972
|
+
(m) => Array.isArray(m.tags) && m.tags.some((t) => t.name.toLowerCase() === tagName)
|
|
30973
|
+
);
|
|
30974
|
+
}
|
|
30975
|
+
if (opts.status) {
|
|
30976
|
+
const statusNum = STATUS_MAP[opts.status.toLowerCase()];
|
|
30977
|
+
if (statusNum === void 0) {
|
|
30978
|
+
client.disconnect();
|
|
30979
|
+
handleError(new Error(`Invalid status "${opts.status}". Valid: up, down, pending, maintenance`), opts);
|
|
30980
|
+
}
|
|
30981
|
+
targets = targets.filter((m) => m.heartbeat?.status === statusNum);
|
|
30982
|
+
}
|
|
30983
|
+
if (targets.length === 0) {
|
|
30984
|
+
client.disconnect();
|
|
30985
|
+
if (json) jsonOut({ affected: 0, results: [] });
|
|
30986
|
+
console.log("No monitors matched the given filters.");
|
|
30987
|
+
return;
|
|
30988
|
+
}
|
|
30989
|
+
if (opts.dryRun) {
|
|
30990
|
+
client.disconnect();
|
|
30991
|
+
const preview = targets.map((m) => ({ id: m.id, name: m.name }));
|
|
30992
|
+
if (json) jsonOut({ dryRun: true, affected: targets.length, monitors: preview });
|
|
30993
|
+
console.log(source_default.yellow(`Dry run \u2014 would resume ${targets.length} monitor(s):`));
|
|
30994
|
+
preview.forEach((m) => console.log(` ${source_default.dim(String(m.id).padStart(4))} ${m.name}`));
|
|
30995
|
+
return;
|
|
30996
|
+
}
|
|
30997
|
+
const results = await client.bulkResume((m) => targets.some((t) => t.id === m.id));
|
|
30998
|
+
client.disconnect();
|
|
30999
|
+
const failed = results.filter((r) => !r.ok);
|
|
31000
|
+
if (json) {
|
|
31001
|
+
jsonOut({ affected: results.length, failed: failed.length, results });
|
|
31002
|
+
}
|
|
31003
|
+
console.log(`Resumed ${results.length - failed.length}/${results.length} monitor(s)`);
|
|
31004
|
+
if (failed.length > 0) {
|
|
31005
|
+
failed.forEach((r) => error(` Monitor ${r.id} (${r.name}): ${r.error}`));
|
|
31006
|
+
process.exit(1);
|
|
31007
|
+
}
|
|
31008
|
+
} catch (err) {
|
|
31009
|
+
handleError(err, opts);
|
|
31010
|
+
}
|
|
31011
|
+
});
|
|
30687
31012
|
monitors.command("set-notification <id>").description("Assign or remove a notification channel from a monitor").requiredOption("--notification-id <nid>", "ID of the notification channel to assign").option("--remove", "Remove the notification instead of assigning it").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
30688
31013
|
"after",
|
|
30689
31014
|
`
|
|
@@ -30730,27 +31055,36 @@ ${source_default.dim("Bulk assign via pipe:")}
|
|
|
30730
31055
|
|
|
30731
31056
|
// src/commands/heartbeat.ts
|
|
30732
31057
|
function heartbeatCommand(program3) {
|
|
30733
|
-
program3.command("heartbeat
|
|
31058
|
+
const hb = program3.command("heartbeat").description("View heartbeat history or send push heartbeats to monitors").addHelpText(
|
|
31059
|
+
"after",
|
|
31060
|
+
`
|
|
31061
|
+
${source_default.dim("Subcommands:")}
|
|
31062
|
+
${source_default.cyan("heartbeat view <monitor-id>")} View recent heartbeats for a monitor
|
|
31063
|
+
${source_default.cyan("heartbeat send <push-token>")} Send a push heartbeat (for scripts / GitHub Actions)
|
|
31064
|
+
|
|
31065
|
+
${source_default.dim("Run")} ${source_default.cyan("kuma heartbeat <subcommand> --help")} ${source_default.dim("for examples.")}
|
|
31066
|
+
`
|
|
31067
|
+
);
|
|
31068
|
+
hb.command("view <monitor-id>").description("View recent heartbeats (check results) for a monitor").option("--limit <n>", "Maximum number of heartbeats to display (default: 20)", "20").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
30734
31069
|
"after",
|
|
30735
31070
|
`
|
|
30736
31071
|
${source_default.dim("Examples:")}
|
|
30737
|
-
${source_default.cyan("kuma heartbeat 42")}
|
|
30738
|
-
${source_default.cyan("kuma heartbeat 42 --limit 50")}
|
|
30739
|
-
${source_default.cyan("kuma heartbeat 42 --json")}
|
|
30740
|
-
${source_default.cyan("kuma heartbeat 42 --json | jq '.data[] | select(.status == 0)'")}
|
|
31072
|
+
${source_default.cyan("kuma heartbeat view 42")}
|
|
31073
|
+
${source_default.cyan("kuma heartbeat view 42 --limit 50")}
|
|
31074
|
+
${source_default.cyan("kuma heartbeat view 42 --json")}
|
|
31075
|
+
${source_default.cyan("kuma heartbeat view 42 --json | jq '.data[] | select(.status == 0)'")}
|
|
30741
31076
|
`
|
|
30742
31077
|
).action(async (monitorId, opts) => {
|
|
30743
31078
|
const config = getConfig();
|
|
30744
31079
|
if (!config) requireAuth(opts);
|
|
30745
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
|
+
}
|
|
30746
31085
|
try {
|
|
30747
|
-
const client = await createAuthenticatedClient(
|
|
30748
|
-
|
|
30749
|
-
config.token
|
|
30750
|
-
);
|
|
30751
|
-
const heartbeats = await client.getHeartbeatList(
|
|
30752
|
-
parseInt(monitorId, 10)
|
|
30753
|
-
);
|
|
31086
|
+
const client = await createAuthenticatedClient(config.url, config.token);
|
|
31087
|
+
const heartbeats = await client.getHeartbeatList(parsedMonitorId);
|
|
30754
31088
|
client.disconnect();
|
|
30755
31089
|
const limit = parseInt(opts.limit ?? "20", 10);
|
|
30756
31090
|
const recent = heartbeats.slice(-limit).reverse();
|
|
@@ -30762,12 +31096,12 @@ ${source_default.dim("Examples:")}
|
|
|
30762
31096
|
return;
|
|
30763
31097
|
}
|
|
30764
31098
|
const table = createTable(["Time", "Status", "Ping", "Message"]);
|
|
30765
|
-
recent.forEach((
|
|
31099
|
+
recent.forEach((hb2) => {
|
|
30766
31100
|
table.push([
|
|
30767
|
-
formatDate(
|
|
30768
|
-
statusLabel(
|
|
30769
|
-
formatPing(
|
|
30770
|
-
|
|
31101
|
+
formatDate(hb2.time),
|
|
31102
|
+
statusLabel(hb2.status),
|
|
31103
|
+
formatPing(hb2.ping),
|
|
31104
|
+
hb2.msg ?? "\u2014"
|
|
30771
31105
|
]);
|
|
30772
31106
|
});
|
|
30773
31107
|
console.log(table.toString());
|
|
@@ -30777,6 +31111,87 @@ Showing last ${recent.length} heartbeat(s)`);
|
|
|
30777
31111
|
handleError(err, opts);
|
|
30778
31112
|
}
|
|
30779
31113
|
});
|
|
31114
|
+
hb.command("send <push-token>").description("Send a push heartbeat to a Kuma push monitor (for scripts and GitHub Actions)").option("--status <status>", "Heartbeat status: up, down, maintenance (default: up)").option("--msg <message>", "Optional status message").option("--ping <ms>", "Optional response time in milliseconds").option("--url <url>", "Kuma base URL (defaults to saved login URL)").option("--json", "Output as JSON ({ ok, data })").addHelpText(
|
|
31115
|
+
"after",
|
|
31116
|
+
`
|
|
31117
|
+
${source_default.dim("Examples:")}
|
|
31118
|
+
${source_default.cyan("kuma heartbeat send abc123")}
|
|
31119
|
+
${source_default.cyan('kuma heartbeat send abc123 --status down --msg "Job failed"')}
|
|
31120
|
+
${source_default.cyan('kuma heartbeat send abc123 --msg "Deploy complete" --ping 42')}
|
|
31121
|
+
${source_default.cyan("kuma heartbeat send abc123 --json")}
|
|
31122
|
+
|
|
31123
|
+
${source_default.dim("GitHub Actions usage:")}
|
|
31124
|
+
${source_default.cyan("- name: Heartbeat")}
|
|
31125
|
+
${source_default.cyan(" if: always()")}
|
|
31126
|
+
${source_default.cyan(" run: kuma heartbeat send ${{ secrets.RUNNER_PUSH_TOKEN }} --status ${{ job.status == 'success' && 'up' || 'down' }}")}
|
|
31127
|
+
|
|
31128
|
+
${source_default.dim("Finding your push token:")}
|
|
31129
|
+
Create a "Push" monitor in Kuma UI. The push URL is:
|
|
31130
|
+
https://kuma.example.com/api/push/<token>
|
|
31131
|
+
Use only the <token> part.
|
|
31132
|
+
|
|
31133
|
+
Or get it from CLI: kuma monitors create --type push --name "my-runner" --json | jq '.data.pushToken'
|
|
31134
|
+
`
|
|
31135
|
+
).action(async (pushToken, opts) => {
|
|
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
|
+
}
|
|
31143
|
+
const VALID_STATUSES = ["up", "down", "maintenance"];
|
|
31144
|
+
const statusKey = (opts.status ?? "up").toLowerCase();
|
|
31145
|
+
if (!VALID_STATUSES.includes(statusKey)) {
|
|
31146
|
+
const msg = `Invalid status "${opts.status}". Valid: up, down, maintenance`;
|
|
31147
|
+
if (json) jsonError(msg, EXIT_CODES.GENERAL);
|
|
31148
|
+
console.error(source_default.red(`\u274C ${msg}`));
|
|
31149
|
+
process.exit(EXIT_CODES.GENERAL);
|
|
31150
|
+
}
|
|
31151
|
+
let baseUrl = opts.url;
|
|
31152
|
+
if (!baseUrl) {
|
|
31153
|
+
const config = getConfig();
|
|
31154
|
+
if (!config) {
|
|
31155
|
+
const msg = "No --url specified and not logged in. Run: kuma login <url> or pass --url";
|
|
31156
|
+
if (json) jsonError(msg, EXIT_CODES.AUTH);
|
|
31157
|
+
console.error(source_default.red(`\u274C ${msg}`));
|
|
31158
|
+
process.exit(EXIT_CODES.AUTH);
|
|
31159
|
+
}
|
|
31160
|
+
baseUrl = config.url;
|
|
31161
|
+
}
|
|
31162
|
+
const pushUrl = new URL(`${baseUrl.replace(/\/$/, "")}/api/push/${pushToken}`);
|
|
31163
|
+
pushUrl.searchParams.set("status", statusKey);
|
|
31164
|
+
if (opts.msg) pushUrl.searchParams.set("msg", opts.msg);
|
|
31165
|
+
if (opts.ping) pushUrl.searchParams.set("ping", opts.ping);
|
|
31166
|
+
try {
|
|
31167
|
+
const res = await fetch(pushUrl.toString(), {
|
|
31168
|
+
signal: AbortSignal.timeout(1e4)
|
|
31169
|
+
});
|
|
31170
|
+
if (!res.ok) {
|
|
31171
|
+
const body = await res.text().catch(() => "");
|
|
31172
|
+
const msg = `Push failed (HTTP ${res.status}): ${body || res.statusText}`;
|
|
31173
|
+
if (json) jsonError(msg, EXIT_CODES.GENERAL);
|
|
31174
|
+
console.error(source_default.red(`\u274C ${msg}`));
|
|
31175
|
+
process.exit(EXIT_CODES.GENERAL);
|
|
31176
|
+
}
|
|
31177
|
+
const data = await res.json().catch(() => ({ ok: true }));
|
|
31178
|
+
if (data.ok === false) {
|
|
31179
|
+
const msg = data.msg ?? "Kuma rejected the push heartbeat";
|
|
31180
|
+
if (json) jsonError(msg, EXIT_CODES.GENERAL);
|
|
31181
|
+
console.error(source_default.red(`\u274C ${msg}`));
|
|
31182
|
+
process.exit(EXIT_CODES.GENERAL);
|
|
31183
|
+
}
|
|
31184
|
+
if (json) {
|
|
31185
|
+
jsonOut({ pushToken, status: statusKey, msg: opts.msg ?? null });
|
|
31186
|
+
}
|
|
31187
|
+
success(`Push heartbeat sent (${statusKey}${opts.msg ? ` \u2014 ${opts.msg}` : ""})`);
|
|
31188
|
+
} catch (e) {
|
|
31189
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
31190
|
+
if (json) jsonError(msg, EXIT_CODES.CONNECTION);
|
|
31191
|
+
console.error(source_default.red(`\u274C ${msg}`));
|
|
31192
|
+
process.exit(EXIT_CODES.CONNECTION);
|
|
31193
|
+
}
|
|
31194
|
+
});
|
|
30780
31195
|
}
|
|
30781
31196
|
|
|
30782
31197
|
// src/commands/status-pages.ts
|
|
@@ -30930,7 +31345,7 @@ ${source_default.bold(`Upgrading kuma-cli`)} ${source_default.dim(`v${current}`)
|
|
|
30930
31345
|
);
|
|
30931
31346
|
}
|
|
30932
31347
|
try {
|
|
30933
|
-
(0, import_child_process.execSync)(
|
|
31348
|
+
(0, import_child_process.execSync)(`npm install -g @blackasteroid/kuma-cli@${latest}`, {
|
|
30934
31349
|
stdio: json ? "pipe" : "inherit"
|
|
30935
31350
|
});
|
|
30936
31351
|
} catch (err) {
|
|
@@ -30963,6 +31378,27 @@ ${source_default.bold(`Upgrading kuma-cli`)} ${source_default.dim(`v${current}`)
|
|
|
30963
31378
|
}
|
|
30964
31379
|
|
|
30965
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
|
+
}
|
|
30966
31402
|
function notificationsCommand(program3) {
|
|
30967
31403
|
const notifications = program3.command("notifications").description("Manage notification channels (Discord, Telegram, webhook, ...)").addHelpText(
|
|
30968
31404
|
"after",
|
|
@@ -31029,14 +31465,18 @@ ${list.length} notification channel(s)`);
|
|
|
31029
31465
|
handleError(err, opts);
|
|
31030
31466
|
}
|
|
31031
31467
|
});
|
|
31032
|
-
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(
|
|
31033
31469
|
"after",
|
|
31034
31470
|
`
|
|
31035
31471
|
${source_default.dim("Examples:")}
|
|
31036
|
-
${source_default.cyan(
|
|
31037
|
-
${source_default.cyan(
|
|
31038
|
-
${source_default.cyan(
|
|
31039
|
-
${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'`)}
|
|
31040
31480
|
|
|
31041
31481
|
${source_default.dim("Supported types:")}
|
|
31042
31482
|
discord, telegram, slack, webhook, gotify, ntfy, pushover, matrix, mattermost, teams ...
|
|
@@ -31053,32 +31493,36 @@ ${source_default.dim("Supported types:")}
|
|
|
31053
31493
|
active: true,
|
|
31054
31494
|
applyExisting: opts.applyExisting ?? false
|
|
31055
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);
|
|
31056
31500
|
switch (opts.type.toLowerCase()) {
|
|
31057
31501
|
case "discord":
|
|
31058
|
-
if (!
|
|
31059
|
-
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);
|
|
31060
31504
|
}
|
|
31061
|
-
payload.discordWebhookUrl =
|
|
31505
|
+
payload.discordWebhookUrl = discordWebhook;
|
|
31062
31506
|
if (opts.discordUsername) payload.discordUsername = opts.discordUsername;
|
|
31063
31507
|
break;
|
|
31064
31508
|
case "telegram":
|
|
31065
|
-
if (!
|
|
31509
|
+
if (!telegramToken || !opts.telegramChatId) {
|
|
31066
31510
|
handleError(new Error("--telegram-token and --telegram-chat-id are required for --type telegram"), opts);
|
|
31067
31511
|
}
|
|
31068
|
-
payload.telegramBotToken =
|
|
31512
|
+
payload.telegramBotToken = telegramToken;
|
|
31069
31513
|
payload.telegramChatID = opts.telegramChatId;
|
|
31070
31514
|
break;
|
|
31071
31515
|
case "slack":
|
|
31072
|
-
if (!
|
|
31073
|
-
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);
|
|
31074
31518
|
}
|
|
31075
|
-
payload.slackwebhookURL =
|
|
31519
|
+
payload.slackwebhookURL = slackWebhook;
|
|
31076
31520
|
break;
|
|
31077
31521
|
case "webhook":
|
|
31078
|
-
if (!
|
|
31079
|
-
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);
|
|
31080
31524
|
}
|
|
31081
|
-
payload.webhookURL =
|
|
31525
|
+
payload.webhookURL = webhookUrl;
|
|
31082
31526
|
payload.webhookContentType = opts.webhookContentType ?? "application/json";
|
|
31083
31527
|
break;
|
|
31084
31528
|
default:
|
|
@@ -31114,8 +31558,8 @@ ${source_default.dim("Examples:")}
|
|
|
31114
31558
|
if (!config) requireAuth(opts);
|
|
31115
31559
|
const json = isJsonMode(opts);
|
|
31116
31560
|
const notifId = parseInt(id, 10);
|
|
31117
|
-
if (isNaN(notifId)) {
|
|
31118
|
-
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);
|
|
31119
31563
|
}
|
|
31120
31564
|
if (!opts.force && !json) {
|
|
31121
31565
|
const enquirer3 = await Promise.resolve().then(() => __toESM(require_enquirer()));
|
|
@@ -31160,7 +31604,7 @@ ${source_default.bold("Quick Start:")}
|
|
|
31160
31604
|
${source_default.cyan("kuma login https://kuma.example.com")} Authenticate (saves session)
|
|
31161
31605
|
${source_default.cyan("kuma monitors list")} List all monitors + status
|
|
31162
31606
|
${source_default.cyan('kuma monitors add --name "My API" --type http --url https://api.example.com')}
|
|
31163
|
-
${source_default.cyan("kuma heartbeat 42")}
|
|
31607
|
+
${source_default.cyan("kuma heartbeat view 42")} View recent heartbeats for monitor 42
|
|
31164
31608
|
${source_default.cyan("kuma logout")} Clear saved session
|
|
31165
31609
|
|
|
31166
31610
|
${source_default.bold("JSON / scripting mode:")}
|