@uptimerobot/cli 0.3.0 → 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/README.md CHANGED
@@ -36,6 +36,13 @@ uptimerobot --version
36
36
  uptimerobot --help
37
37
  ```
38
38
 
39
+ `--version` prints one human-readable line. The `version` command reports the same facts through the normal output flags, so a script asserting a minimum version reads a field instead of parsing a sentence:
40
+
41
+ ```sh
42
+ uptimerobot version --json
43
+ # {"version":"1.0.0","platform":"darwin-arm64","node":"24.12.0"}
44
+ ```
45
+
39
46
  ## AI agent skills
40
47
 
41
48
  Install the [UptimeRobot AI skills](https://github.com/uptimerobot/ai) for supported coding agents:
@@ -123,6 +130,30 @@ uptimerobot monitors update 797054213 --set interval=120
123
130
  uptimerobot monitors create http --body - < monitor.json
124
131
  ```
125
132
 
133
+ `--body` is a base document, and typed flags and `--set` are applied on top of it. Those two are peers, so giving the same field a different value with both is an error rather than a silent win for one of them:
134
+
135
+ ```sh
136
+ uptimerobot monitors create http --name checkout-api --set friendlyName=checkout
137
+ # INVALID_INPUT: friendlyName received conflicting values:
138
+ # --name checkout-api and --set friendlyName=checkout. Pass only one.
139
+ ```
140
+
141
+ ## Heartbeat monitors
142
+
143
+ A heartbeat monitor stays silent until something pings it, so normalized output carries a derived `pingUrl` — the address that job should call. Both it and the `url` it is built from are secrets, so both are redacted unless you ask for them:
144
+
145
+ ```sh
146
+ uptimerobot monitors get 803704830 --json
147
+ # {"type":"HEARTBEAT","id":803704830,"url":"[REDACTED]","pingUrl":"[REDACTED]"}
148
+
149
+ uptimerobot monitors get 803704830 --json --reveal-secrets
150
+ # {"type":"HEARTBEAT","id":803704830,"url":"997c8970…","pingUrl":"https://heartbeat.uptimerobot.com/m803704830-997c8970…"}
151
+ ```
152
+
153
+ Reach for `--reveal-secrets` when wiring up the job that pings it, which is normally once per monitor. Anyone holding the ping URL can report the heartbeat on your behalf, so a leaked one keeps a dead service looking alive with no alert raised. `url` on every other monitor type is a target address, not a secret, and is never redacted.
154
+
155
+ The URL is composed from the monitor's `id` and `url`, where `url` carries the monitor's token rather than a target address. Set `UPTIMEROBOT_HEARTBEAT_URL` to point at a different host. The field is derived by the CLI, not returned by the API, so `--raw` does not include it.
156
+
126
157
  ## Validate before sending
127
158
 
128
159
  Inspect a monitor request contract or print a validated example without credentials or network access:
@@ -167,15 +198,29 @@ The exit code identifies the failure:
167
198
  | --------- | ----------------------------------------------------------------------------------- |
168
199
  | `0` | Success |
169
200
  | `1` | Generic failure, including a bulk operation where every item failed (`BULK_FAILED`) |
170
- | `2` | Invalid input |
201
+ | `2` | Invalid input, rejected either locally or by the API (HTTP 400 or 422) |
171
202
  | `3` | Bulk operation where some items failed (`BULK_PARTIAL_FAILURE`) |
172
203
  | `4` | Unauthenticated (HTTP 401) |
173
204
  | `5` | Forbidden (HTTP 403) |
174
205
  | `6` | Not found (HTTP 404) |
175
206
  | `7` | Rate limited (HTTP 429) |
207
+ | `8` | Conflict with the current state (HTTP 409) |
208
+
209
+ Exit code `2` covers both a request the CLI rejected before sending and one the API rejected as invalid, so it always means the input is wrong. Along with `8`, it says the request will not succeed as sent and should be changed rather than repeated; `1` and `7` are the failures a plain retry may clear.
210
+
211
+ The error envelope's `code` is `HTTP_<status>` whenever `status` is present, and a CLI symbol such as `INVALID_INPUT` or `AUTH_REQUIRED` otherwise, so `status` distinguishes a failure the API reported from one the CLI raised. When the API supplies an error code of its own it is passed through verbatim as `apiCode` and is never interpreted; it is not part of the CLI's contract, because the API does not currently document those codes:
212
+
213
+ ```sh
214
+ uptimerobot monitors get 999999999 --json
215
+ # {"error":{"apiCode":"000-004","code":"HTTP_404","message":"Monitor not found","status":404}}
216
+ ```
217
+
218
+ Branch on `code` or `status`. Read `apiCode` only for diagnostics, and use `--raw` for the untouched API response.
176
219
 
177
220
  `monitors bulk pause`, `monitors bulk start`, and `monitors bulk update` report per-monitor outcomes in an HTTP 201 response, so a failure is only visible in the body. The CLI inspects it and exits non-zero, listing the failed entries in the error envelope's `details`.
178
221
 
222
+ Those three commands report acceptance rather than completion. The API answers `success` for every matched monitor as soon as the write is accepted; it carries no operation id, and nothing in the response distinguishes queued from applied. Exit code `0` therefore means the request was accepted, not that the monitors have changed. A pipeline that pauses monitors before a deploy should confirm with `uptimerobot monitors get <id>` before it continues.
223
+
179
224
  ## Safety
180
225
 
181
226
  Destructive commands prompt in an interactive terminal. Agents and non-interactive processes must pass `--confirm` explicitly:
package/bin/run.js CHANGED
@@ -13,6 +13,19 @@ if (!nodeVersionSupported) {
13
13
  process.exit(1);
14
14
  }
15
15
 
16
+ /**
17
+ * Resolves when a reader closes stdout before the CLI is done writing to it,
18
+ * which is what `| head`, `| head -c`, and `| grep -q` do. Every later write
19
+ * then fails with EPIPE. Without this listener Node reports the stream error
20
+ * as unhandled; with it, the broken pipe is simply the end of the output.
21
+ */
22
+ const stdoutClosedEarly = new Promise((resolve) => {
23
+ process.stdout.on('error', (error) => {
24
+ if (error.code === 'EPIPE') resolve();
25
+ else throw error;
26
+ });
27
+ });
28
+
16
29
  const { flush, handle, run } = await import('@oclif/core');
17
30
  const { commandSuggestion } = await import('../dist/lib/command-suggestions.js');
18
31
  const { isUnknownCommandMessage, withConfiguredCommandId } =
@@ -20,11 +33,20 @@ const { isUnknownCommandMessage, withConfiguredCommandId } =
20
33
  const { errorEnvelope, exitCodeForError, machineOutputRequested, wasErrorHandled } =
21
34
  await import('../dist/lib/cli-errors.js');
22
35
 
36
+ /**
37
+ * oclif's flush() waits for a `drain` that a broken pipe never emits, so on
38
+ * EPIPE it never settles: Node then printed `Detected unsettled top-level
39
+ * await` on stderr and exited 13. Racing the two keeps stderr meaningful and
40
+ * lets the normal exit path decide the status, which is 0 for a reader that
41
+ * stopped listening and unchanged for a command that had already failed.
42
+ */
43
+ const flushOutput = () => Promise.race([flush(), stdoutClosedEarly]);
44
+
23
45
  try {
24
46
  await run(process.argv.slice(2), import.meta.url);
25
- await flush();
47
+ await flushOutput();
26
48
  } catch (error) {
27
- await flush();
49
+ await flushOutput();
28
50
  if (typeof error === 'object' && error !== null && isUnknownCommandMessage(error.message ?? '')) {
29
51
  // oclif names the command with its colon-joined internal id; report it the
30
52
  // way the CLI accepts it, so the message is a syntax the user can retype.
@@ -7,19 +7,38 @@ export function normalizeApiError(status, payload) {
7
7
  ? rawMessage
8
8
  : `UptimeRobot API request failed with HTTP ${status}.`;
9
9
  return {
10
- code: typeof object.code === 'string' ? object.code : `HTTP_${status}`,
10
+ // Derived from the transport status, never from the body: an upstream code
11
+ // is optional, undocumented, and absent from openapi.yaml, so reporting it
12
+ // as `code` left the vocabulary depending on which endpoint answered. The
13
+ // raw value is kept beside it, verbatim and uninterpreted.
14
+ ...(typeof object.code === 'string' ? { apiCode: object.code } : {}),
15
+ code: `HTTP_${status}`,
11
16
  ...(object.details !== undefined ? { details: object.details } : {}),
12
17
  message,
13
18
  status,
14
19
  };
15
20
  }
21
+ /**
22
+ * Maps an HTTP status onto the CLI's exit code contract. A request the server
23
+ * rejected as invalid exits 2, the same code locally caught invalid input
24
+ * produces, so a caller can tell "the request was wrong, do not retry it" from
25
+ * a generic failure that retrying may clear. Conflicts get their own code
26
+ * because the request is well formed and only the current server state refuses
27
+ * it, which a caller resolves differently.
28
+ */
16
29
  export function exitCodeFor(status) {
30
+ if (status === 400)
31
+ return 2;
17
32
  if (status === 401)
18
33
  return 4;
19
34
  if (status === 403)
20
35
  return 5;
21
36
  if (status === 404)
22
37
  return 6;
38
+ if (status === 409)
39
+ return 8;
40
+ if (status === 422)
41
+ return 2;
23
42
  if (status === 429)
24
43
  return 7;
25
44
  return 1;
@@ -0,0 +1,44 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { BaseCommand } from '../lib/base-command.js';
3
+ import { detectInvocationMode } from '../lib/invocation.js';
4
+ import { formatOutput } from '../output/renderer.js';
5
+ import { resolveFormat } from '../output/resolve-format.js';
6
+ export default class Version extends BaseCommand {
7
+ // The bare --version flag is handled by oclif core before any command runs,
8
+ // so it always prints one human sentence. This command reports the same
9
+ // facts as separate fields, which is what a version assertion in CI needs.
10
+ static description = 'Report the CLI version, the platform it runs on, and the Node.js runtime executing it. Unlike the --version flag, this command honours the output flags, so a caller can read the version as a field instead of parsing a sentence.';
11
+ static examples = ['<%= config.bin %> version --json'];
12
+ static flags = {
13
+ format: Flags.string({
14
+ description: 'Output format; json is normalized and jsonl emits one resource per line',
15
+ options: ['json', 'jsonl', 'table', 'plain'],
16
+ }),
17
+ json: Flags.boolean({
18
+ description: 'Shorthand for --format json; emit normalized structured JSON',
19
+ }),
20
+ };
21
+ static summary = 'Show the CLI version';
22
+ async run() {
23
+ const { flags } = await this.parse(Version);
24
+ if (flags.json === true && flags.format !== undefined) {
25
+ return this.fail({ code: 'INVALID_INPUT', message: '--json cannot be combined with --format.' }, 2);
26
+ }
27
+ const info = {
28
+ version: this.config.version,
29
+ platform: `${this.config.platform}-${this.config.arch}`,
30
+ node: process.versions.node,
31
+ };
32
+ const format = resolveFormat(flags, detectInvocationMode(false));
33
+ if (format === 'json' || format === 'jsonl') {
34
+ this.log(JSON.stringify(info));
35
+ return;
36
+ }
37
+ // A single record has no collection to render, so it is passed as a
38
+ // one-row collection to get the table headings and the tab-separated
39
+ // plain row every other command produces.
40
+ const output = formatOutput(format, [info]);
41
+ if (output !== undefined)
42
+ this.log(output);
43
+ }
44
+ }
@@ -354,6 +354,15 @@ export const operations = {
354
354
  "example": "Investigating the issue...",
355
355
  "maxLength": 10000,
356
356
  "type": "string"
357
+ },
358
+ {
359
+ "flag": "publish-on-status-page",
360
+ "path": "publishOnStatusPage",
361
+ "required": false,
362
+ "default": false,
363
+ "description": "Whether the comment is published on the status page",
364
+ "example": true,
365
+ "type": "boolean"
357
366
  }
358
367
  ]
359
368
  },
@@ -472,6 +481,15 @@ export const operations = {
472
481
  "example": "Issue has been identified and fix is in progress...",
473
482
  "maxLength": 10000,
474
483
  "type": "string"
484
+ },
485
+ {
486
+ "flag": "publish-on-status-page",
487
+ "path": "publishOnStatusPage",
488
+ "required": false,
489
+ "default": false,
490
+ "description": "Whether the comment is published on the status page",
491
+ "example": true,
492
+ "type": "boolean"
475
493
  }
476
494
  ]
477
495
  },
@@ -12,6 +12,7 @@ export const HANDWRITTEN_COMMANDS = [
12
12
  'help',
13
13
  'monitors schema',
14
14
  'skills install',
15
+ 'version',
15
16
  ];
16
17
  const commands = [
17
18
  ...Object.keys(operations).map((commandId) => commandId.replaceAll(':', ' ')),
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The three bulk endpoints answer with a per-monitor `success` that reports
3
+ * acceptance, not application: there is no operation id to poll and nothing
4
+ * distinguishes queued from applied, so a caller treating exit 0 as "done" may
5
+ * act on monitors still in their old state. A stale-cache defect that made the
6
+ * gap tens of seconds wide was fixed in UPT-3557, so no duration is quoted —
7
+ * the absent completion signal is the part that is structural.
8
+ */
9
+ const BULK_DELAY_NOTE = 'Reports acceptance, not completion. The API answers "success" for every matched monitor as soon as the write is accepted; it carries no operation id, and nothing in the response distinguishes queued from applied. Confirm with `uptimerobot monitors get <id>` before anything depends on the new state.';
10
+ /**
11
+ * Editorial notes appended to a command's help description. `src/generated` is
12
+ * a projection of openapi/openapi.yaml and is rewritten by
13
+ * `pnpm openapi:generate`, so behaviour the published contract omits belongs
14
+ * here, alongside the request curation in request-curation.ts.
15
+ */
16
+ const DESCRIPTION_NOTES = {
17
+ 'monitors:bulk:pause': BULK_DELAY_NOTE,
18
+ 'monitors:bulk:start': BULK_DELAY_NOTE,
19
+ 'monitors:bulk:update': BULK_DELAY_NOTE,
20
+ };
21
+ export function curatedCommandDescription(operation) {
22
+ const description = operation.description ?? operation.summary;
23
+ const note = DESCRIPTION_NOTES[operation.commandId];
24
+ return note === undefined ? description : `${description}\n\n${note}`;
25
+ }
@@ -13,6 +13,8 @@ import { InputValidationError, validateInput } from '../runtime/validate-input.j
13
13
  import { AuthenticationError, resolveApiKey } from './auth.js';
14
14
  import { BaseCommand } from './base-command.js';
15
15
  import { flagName } from './flag-name.js';
16
+ import { curatedCommandDescription } from './help-curation.js';
17
+ import { withDerivedFields } from './response-curation.js';
16
18
  import { detectInvocationMode, isCI, requestConfirmation } from './invocation.js';
17
19
  import { enrichOperationParserError } from './operation-parser-error.js';
18
20
  import { redactSecrets } from './redact-secrets.js';
@@ -57,6 +59,7 @@ export function createOperationCommand(operation, options = {}) {
57
59
  }
58
60
  : {};
59
61
  const defaultColumns = columnsFor(operation.commandId);
62
+ const description = curatedCommandDescription(operation);
60
63
  const bodyFieldFlags = Object.fromEntries((operation.requestBodyFields ?? []).map((field) => [
61
64
  field.flag,
62
65
  flagForBodyField(curatedRequestBodyField(operation, field)),
@@ -102,10 +105,10 @@ export function createOperationCommand(operation, options = {}) {
102
105
  class OperationCommand extends BaseCommand {
103
106
  static args = commandArgs;
104
107
  static description = options.canonicalCommand
105
- ? `${operation.description ?? operation.summary}
108
+ ? `${description}
106
109
 
107
110
  Canonical command: ${options.canonicalCommand}`
108
- : (operation.description ?? operation.summary);
111
+ : description;
109
112
  static examples = examplesFor(operation);
110
113
  static flags = commandFlags;
111
114
  static summary = operation.summary;
@@ -254,7 +257,8 @@ Canonical command: ${options.canonicalCommand}`
254
257
  const apiError = normalizeApiError(response.status, payload);
255
258
  return this.fail(apiError, exitCodeFor(response.status));
256
259
  }
257
- const normalized = flags.raw === true ? payload : normalizeResult(payload);
260
+ // --raw is the untouched API response, so derived fields stay out of it.
261
+ const normalized = flags.raw === true ? payload : withDerivedFields(normalizeResult(payload));
258
262
  // Redacted before rendering, so column selection, truncation, and JSONL
259
263
  // splitting all operate on the redacted values. --raw stays untouched.
260
264
  const redaction = flags.raw === true || flags['reveal-secrets'] === true
@@ -17,15 +17,30 @@ function redactValue(value, path, redacted) {
17
17
  }
18
18
  if (!isRecord(value))
19
19
  return value;
20
+ const contextual = contextualSensitiveKeys(value);
20
21
  return Object.fromEntries(Object.entries(value).map(([key, child]) => {
21
22
  const childPath = [...path, key];
22
- if (isSensitiveFieldName(key)) {
23
+ if (isSensitiveFieldName(key) || contextual.has(key)) {
23
24
  redacted.push(formatPath(childPath));
24
25
  return [key, REDACTED];
25
26
  }
26
27
  return [key, redactValue(child, childPath, redacted)];
27
28
  }));
28
29
  }
30
+ const NO_KEYS = new Set();
31
+ const HEARTBEAT_SECRET_KEYS = new Set(['pingUrl', 'url']);
32
+ /**
33
+ * Fields that carry a secret because of the record they sit in rather than what
34
+ * they are called. A heartbeat monitor's `url` is the token its ping endpoint
35
+ * is built from, and `pingUrl` is that endpoint: anyone holding either can
36
+ * report the heartbeat, so a leak keeps a dead service looking alive with no
37
+ * alert and nothing on the monitor to show for it. Every other monitor type
38
+ * uses `url` for a target address, which is not a secret — hence the type
39
+ * check rather than a name added to sensitive-fields.ts.
40
+ */
41
+ function contextualSensitiveKeys(record) {
42
+ return record.type === 'HEARTBEAT' ? HEARTBEAT_SECRET_KEYS : NO_KEYS;
43
+ }
29
44
  function formatPath(segments) {
30
45
  return segments.reduce((path, segment) => /^\d+$/.test(segment) ? `${path}[${segment}]` : path ? `${path}.${segment}` : segment, '');
31
46
  }
@@ -44,13 +44,17 @@ export async function buildRequestBody(operation, flags, headers) {
44
44
  return undefined;
45
45
  }
46
46
  const objectBody = toObject(bodyValue);
47
+ const typedAssignments = new Map();
47
48
  for (const field of bodyFields) {
48
49
  const value = flags[field.flag];
49
- if (value !== undefined)
50
- assignPath(objectBody, field.path, parseBodyField(field, value));
50
+ if (value === undefined)
51
+ continue;
52
+ const parsed = parseBodyField(field, value);
53
+ assignPath(objectBody, field.path, parsed);
54
+ typedAssignments.set(field.path, { flag: field.flag, value: parsed });
51
55
  }
52
56
  for (const assignment of assignments)
53
- assignValue(objectBody, assignment);
57
+ assignValue(objectBody, assignment, typedAssignments);
54
58
  for (const [path, value] of Object.entries(operation.requestBodyDefaults ?? {})) {
55
59
  const supplied = valueAtPath(objectBody, path);
56
60
  if (supplied !== undefined && supplied !== value) {
@@ -111,9 +115,41 @@ function toObject(value) {
111
115
  }
112
116
  return structuredClone(value);
113
117
  }
114
- function assignValue(target, assignment) {
118
+ function assignValue(target, assignment, typedAssignments) {
115
119
  const [path, rawValue] = splitAssignment(assignment, '--set');
116
- assignPath(target, path, parseValue(rawValue));
120
+ const value = parseValue(rawValue);
121
+ // --body is a base that both typed flags and --set layer onto, but those two
122
+ // are peers: when they name the same field nothing says which one was meant,
123
+ // so the old last-one-wins silently discarded the typed flag.
124
+ const typed = typedAssignments.get(path);
125
+ if (typed !== undefined && !sameValue(typed.value, value)) {
126
+ throw new RequestInputError(path, 'a single value', `${path} received conflicting values: --${typed.flag} ${describeValue(typed.value)} and --set ${path}=${describeValue(value)}. Pass only one.`);
127
+ }
128
+ assignPath(target, path, value);
129
+ }
130
+ /**
131
+ * Structural equality, so the same object written two ways is not a conflict:
132
+ * a typed flag carrying `{"a":1,"b":2}` and `--set` carrying `{"b":2,"a":1}`
133
+ * say the same thing, and refusing them would be a false positive on the very
134
+ * case the conflict check is meant to allow through.
135
+ */
136
+ function sameValue(left, right) {
137
+ return canonicalJson(left) === canonicalJson(right);
138
+ }
139
+ function canonicalJson(value) {
140
+ return JSON.stringify(withSortedKeys(value ?? null));
141
+ }
142
+ function withSortedKeys(value) {
143
+ if (Array.isArray(value))
144
+ return value.map(withSortedKeys);
145
+ if (!isPlainRecord(value))
146
+ return value;
147
+ return Object.fromEntries(Object.keys(value)
148
+ .sort()
149
+ .map((key) => [key, withSortedKeys(value[key])]));
150
+ }
151
+ function describeValue(value) {
152
+ return typeof value === 'string' ? value : JSON.stringify(value);
117
153
  }
118
154
  function splitAssignment(value, flag) {
119
155
  const separator = value.indexOf('=');
@@ -0,0 +1,43 @@
1
+ import { isPlainRecord } from './objects.js';
2
+ /**
3
+ * The heartbeat host the ping URL is built against. Overridable because the
4
+ * host is environment-specific and is not part of the published API contract.
5
+ */
6
+ const DEFAULT_HEARTBEAT_HOST = 'https://heartbeat.uptimerobot.com';
7
+ /**
8
+ * A heartbeat monitor is inert until something pings its URL, and the API
9
+ * returns the ingredients without ever naming the result: `url` carries the
10
+ * token and the ping endpoint is `<host>/m<id>-<token>`. That composition is
11
+ * how the dashboard builds the URL it shows, but it is documented nowhere, so
12
+ * an API consumer cannot discover it. Deriving it here lets a heartbeat be
13
+ * created and wired up without a trip to the dashboard.
14
+ *
15
+ * Derived rather than authoritative: UPT-3726 asks the API to return the URL
16
+ * itself, and this should give way to that field once it exists.
17
+ */
18
+ export function withDerivedFields(payload, env = process.env) {
19
+ if (Array.isArray(payload))
20
+ return payload.map((item) => withDerivedFields(item, env));
21
+ if (!isPlainRecord(payload))
22
+ return payload;
23
+ if (Array.isArray(payload.items)) {
24
+ return { ...payload, items: payload.items.map((item) => withDerivedFields(item, env)) };
25
+ }
26
+ // An API-supplied value wins: this composition exists only while the contract
27
+ // has none, and must not shadow the real field when UPT-3726 lands.
28
+ if (typeof payload.pingUrl === 'string')
29
+ return payload;
30
+ const pingUrl = heartbeatPingUrl(payload, env);
31
+ return pingUrl === undefined ? payload : { ...payload, pingUrl };
32
+ }
33
+ function heartbeatPingUrl(monitor, env) {
34
+ if (monitor.type !== 'HEARTBEAT')
35
+ return undefined;
36
+ const { id, url } = monitor;
37
+ if (typeof url !== 'string' || url.length === 0)
38
+ return undefined;
39
+ if (typeof id !== 'number' && typeof id !== 'string')
40
+ return undefined;
41
+ const host = (env.UPTIMEROBOT_HEARTBEAT_URL ?? DEFAULT_HEARTBEAT_HOST).replace(/\/+$/, '');
42
+ return `${host}/m${id}-${url}`;
43
+ }
@@ -2300,6 +2300,8 @@ components:
2300
2300
  exclusiveMinimum: false
2301
2301
  comment:
2302
2302
  type: string
2303
+ publishOnStatusPage:
2304
+ type: boolean
2303
2305
  created:
2304
2306
  oneOf:
2305
2307
  - {}
@@ -2317,6 +2319,7 @@ components:
2317
2319
  required:
2318
2320
  - id
2319
2321
  - comment
2322
+ - publishOnStatusPage
2320
2323
  - created
2321
2324
  - user
2322
2325
  required:
@@ -2508,6 +2511,11 @@ components:
2508
2511
  maxLength: 10000
2509
2512
  description: Updated content of the comment
2510
2513
  example: Issue has been identified and fix is in progress...
2514
+ publishOnStatusPage:
2515
+ type: boolean
2516
+ description: Whether the comment is published on the status page
2517
+ example: true
2518
+ default: false
2511
2519
  required:
2512
2520
  - content
2513
2521
  IncidentCommentDto:
@@ -2519,6 +2527,8 @@ components:
2519
2527
  exclusiveMinimum: false
2520
2528
  comment:
2521
2529
  type: string
2530
+ publishOnStatusPage:
2531
+ type: boolean
2522
2532
  created:
2523
2533
  oneOf:
2524
2534
  - {}
@@ -2536,6 +2546,7 @@ components:
2536
2546
  required:
2537
2547
  - id
2538
2548
  - comment
2549
+ - publishOnStatusPage
2539
2550
  - created
2540
2551
  - user
2541
2552
  CreateIncidentCommentRequestDto:
@@ -2546,6 +2557,11 @@ components:
2546
2557
  maxLength: 10000
2547
2558
  description: Content of the comment
2548
2559
  example: Investigating the issue...
2560
+ publishOnStatusPage:
2561
+ type: boolean
2562
+ description: Whether the comment is published on the status page
2563
+ example: true
2564
+ default: false
2549
2565
  required:
2550
2566
  - content
2551
2567
  CreateMonitorGroupDto:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uptimerobot/cli",
3
- "version": "0.3.0",
3
+ "version": "1.0.0",
4
4
  "description": "Official UptimeRobot CLI — manage uptime monitors, incidents, status pages, and alert contacts from your terminal, scripts, or CI.",
5
5
  "keywords": [
6
6
  "uptimerobot",
@@ -109,7 +109,7 @@
109
109
  "description": "Manage monitors and statistics",
110
110
  "subtopics": {
111
111
  "bulk": {
112
- "description": "Run bulk monitor actions"
112
+ "description": "Run bulk monitor actions\n\nPause, start, and update report acceptance, not completion: the API answers success as soon as the write is accepted and carries no operation id. Confirm with `uptimerobot monitors get <id>` before depending on the new state."
113
113
  },
114
114
  "create": {
115
115
  "description": "Create a monitor of a given type"