watchfor 0.4.0 → 0.7.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0bbe1b3093561c22831a65a54fe793e3940c44a06b74477890606ed68de080e1
4
- data.tar.gz: 9f58677f13e0ff368293bae5a040fb18f43f576f40ecff320424592ebabb5232
3
+ metadata.gz: 0fa3724c68935a0f14be699f9b232640b29b75811132dcf9e96cdec986d17132
4
+ data.tar.gz: 27b90a62ac79058fb401d4c0f75e7fbb59bc0c5622cb33b41fed8bb71ea49d2d
5
5
  SHA512:
6
- metadata.gz: 9e78697d7e326e57e419aebbbb5c5a38071443cfe2c61c5dd204613a7b92253d0801fb7dc18c91507ae2f8c687f0465821968f74edff781ba1a93bbdd303f1f6
7
- data.tar.gz: 6ad9649aabcc6d2bc8374d39ea6fecd4e38f03cf44005f644ce6d2767d98b27a99f1b4392ffb755c3ed4df094eee13aa725b015fc7ebbbcb0d4c4142ecab58e6
6
+ metadata.gz: 8fdfa7930ac0000827e8f5325e08bd5e99a10ec8ef0409b04144273e284cd16cbb6f1c9f37aaa2fda22df74998711c860bf1b93ac4e53136ca5acd5cd4daf114
7
+ data.tar.gz: 0a326ed15feab7394d1ab2f159cdab8a3bc2f402f3b47c0302dc8b1b5868ccfbe749860ca5ac70f83f5130d8f21a6248f4370df738f33a8cd220a73f05c016f4
data/README.md CHANGED
@@ -32,10 +32,52 @@ mon = wf.monitors.create(
32
32
  )
33
33
 
34
34
  wf.incidents.list(status: "firing")["data"].each { |i| puts i["message"] }
35
+
36
+ # False positive? Keep the resolved incident on record but drop it from
37
+ # uptime, SLA, reports and the status page (include undoes it)
38
+ wf.incidents.exclude(19351, reason: "Probe-side DNS hiccup")
39
+
40
+ # Irreversible: wipe a monitor's checks and incidents; uptime restarts "since reset"
41
+ wf.monitors.reset(mon["id"])
42
+
43
+ # Older checks: pass next_cursor back as cursor (keep the same hours/success)
44
+ page = wf.monitors.checks(mon["id"], hours: 24, limit: 100)
45
+ wf.monitors.checks(mon["id"], hours: 24, limit: 100, cursor: page["next_cursor"]) if page["next_cursor"]
46
+
47
+ # Delete a contact group that alert rules still use (409 without force)
48
+ wf.contact_groups.delete("<group-id>", force: true)
49
+ ```
50
+
51
+ ### Live diagnostics
52
+
53
+ Run any of 18 checks from WatchFor's probe fleet against **any public
54
+ target**, monitored or not — this measures right now, rather than reading
55
+ what WatchFor recorded:
56
+
57
+ ```ruby
58
+ # What can I run, and how much budget is left?
59
+ catalog = wf.diagnostics.list
60
+
61
+ # One check, optionally from a location you choose (plan-gated)
62
+ dns = wf.diagnostics.run("dns-lookup", "example.com",
63
+ options: { recordType: "A", resolver: "8.8.8.8" })
64
+ # A closed port / NXDOMAIN / failed handshake does NOT raise:
65
+ puts "finding: #{dns['error']}" unless dns["success"]
66
+ puts "runs left this hour: #{dns['remaining']}"
67
+
68
+ # The whole picture in one call: DNS + propagation + TLS + HTTP + ping
69
+ # from up to 3 regions, aggregated into one verdict
70
+ report = wf.diagnostics.diagnose_target("example.com")
71
+ puts "#{report['verdict']['status']}: #{report['verdict']['summary']}"
35
72
  ```
36
73
 
74
+ Runs need a `write` key (a probe sends real traffic from WatchFor's IPs)
75
+ and spend the same per-plan hourly allowance as the dashboard Toolbox.
76
+ Details: <https://watchfor.io/docs/api/diagnostics>
77
+
37
78
  Namespaces: `wf.monitors`, `wf.alert_rules`, `wf.incidents`,
38
- `wf.maintenance_windows`, `wf.contacts`, `wf.contact_groups`. Top-level:
79
+ `wf.maintenance_windows`, `wf.contacts`, `wf.contact_groups`,
80
+ `wf.diagnostics`. Top-level:
39
81
  `wf.summary`, `wf.plan`, `wf.me`, `wf.locations`, `wf.monitor_types`,
40
82
  `wf.incident_stats(period)`, `wf.notifications`, `wf.activity`. Errors raise
41
83
  `Watchfor::Error` with `#status`, `#code`, `#message`.
@@ -47,7 +89,7 @@ export WATCHFOR_API_KEY=wf_live_...
47
89
  watchfor summary
48
90
  watchfor monitors
49
91
  watchfor incidents
50
- watchfor checks <monitor_id>
92
+ watchfor checks <monitor_id> [--cursor <next_cursor>]
51
93
  watchfor report --period 30d
52
94
  ```
53
95
 
data/exe/watchfor CHANGED
@@ -31,10 +31,13 @@ begin
31
31
  when "locations" then wf.locations
32
32
  when "monitor-types" then wf.monitor_types
33
33
  when "incidents" then wf.incidents.list
34
- when "checks" then wf.monitors.checks(args.shift)
34
+ when "checks"
35
+ monitor_id = args.shift
36
+ cursor = args.include?("--cursor") ? args[args.index("--cursor") + 1] : nil
37
+ wf.monitors.checks(monitor_id, cursor: cursor)
35
38
  when "report" then wf.report(period: args.include?("--period") ? args[args.index("--period") + 1] : nil)
36
39
  else
37
- warn "Usage: watchfor [summary|plan|monitors|locations|monitor-types|incidents|checks <monitor_id>|report [--period 7d|30d]]"
40
+ warn "Usage: watchfor [summary|plan|monitors|locations|monitor-types|incidents|checks <monitor_id> [--cursor c]|report [--period 7d|30d]]"
38
41
  exit 2
39
42
  end
40
43
  puts JSON.pretty_generate(result)
@@ -26,7 +26,8 @@ module Watchfor
26
26
  # wf.monitors.list["data"].each { |m| puts "#{m['name']} #{m['status']}" }
27
27
  class Client
28
28
  attr_reader :monitors, :alert_rules, :incidents,
29
- :maintenance_windows, :contacts, :contact_groups
29
+ :maintenance_windows, :contacts, :contact_groups,
30
+ :diagnostics
30
31
 
31
32
  def initialize(api_key:, base_url: DEFAULT_BASE_URL, timeout: 30)
32
33
  raise ArgumentError, "api_key is required" if api_key.nil? || api_key.empty?
@@ -40,6 +41,7 @@ module Watchfor
40
41
  @maintenance_windows = MaintenanceWindows.new(self)
41
42
  @contacts = Contacts.new(self)
42
43
  @contact_groups = ContactGroups.new(self)
44
+ @diagnostics = Diagnostics.new(self)
43
45
  end
44
46
 
45
47
  # Core request. +query+ and +body+ are optional hashes.
@@ -113,15 +115,26 @@ module Watchfor
113
115
 
114
116
  class Monitors < Namespace
115
117
  def list(**query) = @c.request("GET", "/monitors", query: query)
118
+ # The monitor plus available_metrics (alert metrics valid for its type).
116
119
  def get(id) = @c.request("GET", "/monitors/#{id}")
117
120
  def create(body, idempotency_key: nil) = @c.request("POST", "/monitors", body: body, idempotency_key: idempotency_key)
118
121
  def update(id, body) = @c.request("PATCH", "/monitors/#{id}", body: body)
119
122
  def delete(id) = @c.request("DELETE", "/monitors/#{id}")
120
123
  def pause(id) = @c.request("POST", "/monitors/#{id}/pause")
121
124
  def resume(id) = @c.request("POST", "/monitors/#{id}/resume")
125
+ # Dispatch one out-of-schedule check now (202); the result lands in #checks moments later.
122
126
  def check_now(id) = @c.request("POST", "/monitors/#{id}/check-now")
127
+ # IRREVERSIBLE: deletes every check, incident, note and post-mortem of the
128
+ # monitor and starts uptime over ("since reset"); the monitor and its
129
+ # configuration, alert rules, tags and locations stay.
130
+ def reset(id) = @c.request("POST", "/monitors/#{id}/reset")
123
131
  def uptime(id, period = nil) = @c.request("GET", "/monitors/#{id}/uptime", query: { period: period })
124
- def checks(id, **query) = @c.request("GET", "/monitors/#{id}/checks", query: query)
132
+ # Recent probe results, newest first. Page with the response's next_cursor -> cursor:
133
+ # (keep the same hours/success); "locations" is the per-location breakdown for the whole window.
134
+ def checks(id, hours: nil, limit: nil, success: nil, cursor: nil, **query)
135
+ @c.request("GET", "/monitors/#{id}/checks",
136
+ query: { hours: hours, limit: limit, success: success, cursor: cursor, **query })
137
+ end
125
138
  end
126
139
 
127
140
  class AlertRules < Namespace
@@ -135,8 +148,14 @@ module Watchfor
135
148
  class Incidents < Namespace
136
149
  def list(**query) = @c.request("GET", "/incidents", query: query)
137
150
  def get(id) = @c.request("GET", "/incidents/#{id}")
151
+ # Every action returns the full, updated incident.
138
152
  def acknowledge(id) = @c.request("POST", "/incidents/#{id}/acknowledge")
139
153
  def resolve(id) = @c.request("POST", "/incidents/#{id}/resolve")
154
+ # Mark a RESOLVED incident as a false positive: kept on record, left out of
155
+ # uptime, SLA, reports and the public status page (409 while still open).
156
+ def exclude(id, reason: nil) = @c.request("POST", "/incidents/#{id}/exclude", body: reason.nil? ? {} : { reason: reason })
157
+ # Undo #exclude - the incident counts as downtime again.
158
+ def include(id) = @c.request("POST", "/incidents/#{id}/include")
140
159
  end
141
160
 
142
161
  class MaintenanceWindows < Namespace
@@ -160,7 +179,48 @@ module Watchfor
160
179
  def get(id) = @c.request("GET", "/contact-groups/#{id}")
161
180
  def create(body, idempotency_key: nil) = @c.request("POST", "/contact-groups", body: body, idempotency_key: idempotency_key)
162
181
  def update(id, body) = @c.request("PATCH", "/contact-groups/#{id}", body: body)
163
- def delete(id) = @c.request("DELETE", "/contact-groups/#{id}")
182
+ # 409 for the default group, and 409 while alert rules, monitors or notification channels
183
+ # still route through it; force: true deletes anyway (references are removed; affected
184
+ # alert rules fall back to their monitor's notification groups).
185
+ def delete(id, force: false) = @c.request("DELETE", "/contact-groups/#{id}", query: force ? { force: true } : nil)
186
+ end
187
+
188
+ # Live checks run from WatchFor's probe fleet against any public target,
189
+ # monitored or not — these MEASURE right now, unlike the rest of the
190
+ # client, which reads what WatchFor already recorded.
191
+ #
192
+ # +run+ and +diagnose_target+ need a write-scope key: a probe sends real
193
+ # traffic from WatchFor's IPs and spends a metered, per-plan allowance
194
+ # (the same hourly budget the dashboard Toolbox spends). Every response
195
+ # carries "remaining" and "distinct_targets" so a long investigation can
196
+ # pace itself. A check that ran and found a problem (closed port,
197
+ # NXDOMAIN, failed handshake) returns normally with "success" => false;
198
+ # only calls that could not run raise.
199
+ class Diagnostics < Namespace
200
+ # Every check, the question it answers, its options and this
201
+ # organization's remaining allowance. Read scope.
202
+ def list = @c.request("GET", "/diagnostics")
203
+
204
+ # Run one check (dns-lookup, ping, tls-grade, ...). +location_id+ is
205
+ # honoured on plans whose location_select is true; otherwise the
206
+ # nearest probe answers and the response says which. Write scope.
207
+ def run(slug, target, options: nil, location_id: nil, idempotency_key: nil)
208
+ body = { target: target }
209
+ body[:options] = options unless options.nil?
210
+ body[:location_id] = location_id unless location_id.nil?
211
+ @c.request("POST", "/diagnostics/#{slug}", body: body, idempotency_key: idempotency_key)
212
+ end
213
+
214
+ # DNS, propagation, TLS grade, HTTP headers and ping from up to 3
215
+ # regions in one call, aggregated into a single verdict. Each
216
+ # underlying check spends its own allowance; needs a plan with
217
+ # location selection. Write scope.
218
+ def diagnose_target(target, max_locations: nil, location_ids: nil, idempotency_key: nil)
219
+ body = { target: target }
220
+ body[:max_locations] = max_locations unless max_locations.nil?
221
+ body[:location_ids] = location_ids unless location_ids.nil?
222
+ @c.request("POST", "/diagnostics/diagnose-target", body: body, idempotency_key: idempotency_key)
223
+ end
164
224
  end
165
225
  end
166
226
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Watchfor
4
- VERSION = "0.4.0"
4
+ VERSION = "0.7.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: watchfor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - WatchFor
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-09-03 00:00:00.000000000 Z
11
+ date: 2026-09-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Zero-dependency Ruby client for the WatchFor REST API (monitors, alert
14
14
  rules, incidents, maintenance windows) plus a `watchfor` CLI.