@calebcall/camera-ui-notify 0.7.1 → 0.9.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/CHANGELOG.md CHANGED
@@ -5,6 +5,113 @@ All notable changes to **Notify** are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [Unreleased]
9
+
10
+ ## [0.9.0] - 2026-08-12
11
+
12
+ ### Added
13
+
14
+ - **Video clips are surfaced on every backend.** camera.ui 2.1.6 added `videoUrl` to the
15
+ notification payload — a short MP4 of the recording behind the alert, published when a camera
16
+ (or an episode) has **Video in Push** enabled. Whether a clip is published at all is decided
17
+ there, not here; this is what every backend now does with one by default. None of them is the
18
+ first-party app, so none plays the clip inside the push itself; each offers it as a link the
19
+ phone opens in its own player, and in every case the clip is **added, never substituted** — the
20
+ snapshot keeps its attachment slot and the deep link keeps its click target. (Telegram and
21
+ Discord can go further; see the opt-in below.)
22
+ - **ntfy** — a `Play clip` view action button. ntfy allows one attachment per message and that
23
+ stays with the snapshot, which is the part every client renders inline. Published as a JSON
24
+ `Actions` header so a signed clip URL's commas and semicolons survive.
25
+ - **Telegram** — a second inline-keyboard button below the deep-link button.
26
+ - **Discord** — a `Play clip` link at the end of the embed description, with the target wrapped
27
+ in `<>` so a URL containing a closing paren still parses as one link.
28
+ - **Pushover** — the supplementary `url` (titled `Play clip`) when no deep link has claimed it,
29
+ otherwise a line appended to the message.
30
+ - **Gotify** — a `Play clip: <url>` line appended to the message text.
31
+ - **Grafana** — an anchor in the annotation tooltip (annotations mode); a `video_url`
32
+ annotation alongside `image_url` in alertmanager and IRM modes, for downstream templates.
33
+ - **Generic webhook** — a `videoUrl` field in the JSON payload.
34
+
35
+ - **"Upload video clips" setting for Telegram and Discord** (`telegram_clip` / `discord_clip`) —
36
+ off by default. Both services can carry the video itself and render a real player, so with this
37
+ on the plugin downloads the clip from camera.ui and re-uploads the bytes. Nothing outside your
38
+ network fetches from your server: the plugin is the only client that ever touches the clip URL,
39
+ so this works on an install that isn't reachable from the internet.
40
+ - **Telegram** posts `sendVideo` with `supports_streaming`, so the clip plays while it
41
+ downloads. Telegram carries one media item per message, so the clip replaces the snapshot.
42
+ Capped at the Bot API's own 50 MB upload limit. A silent follow-up costs nothing extra —
43
+ `editMessageCaption` leaves the video in place, so the clip is not re-downloaded for it.
44
+ - **Discord** attaches the clip as `clip.mp4` beside the embed, so the snapshot is kept. Capped
45
+ at 8 MB, leaving headroom under Discord's 10 MB per-request limit for the snapshot in the same
46
+ body. Discord's edit endpoint drops any attachment the request doesn't re-send, so a follow-up
47
+ costs a second download and upload.
48
+ - **Every failure degrades to the link rather than losing the notification**: a clip past the
49
+ cap is not downloaded at all (an over-limit `Content-Length` fails before the body is read), a
50
+ download that errors or stalls retries as an ordinary send, and so does an upload the service
51
+ rejects. Oversize clips are rejected, never truncated — a clipped MP4 is a broken upload, not
52
+ a smaller video. Each fallback is logged with its reason.
53
+
54
+ - **The camera.ui Base URL setting now absolutizes a relative clip URL too**, exactly as it
55
+ already did for deep links. camera.ui publishes `videoUrl` absolute, but a publisher that sends
56
+ a server-relative path would otherwise hand the backends a link no phone could open. Without
57
+ `base_url` set, a relative clip URL is dropped rather than delivered dead — except on the
58
+ generic webhook, whose receiver is a machine that may be able to resolve it, so there it is
59
+ forwarded verbatim.
60
+
61
+ ### Changed
62
+
63
+ - **camera.ui Go SDK bumped to v1.2.24** (from v1.2.22) for `Notification.VideoURL`.
64
+ - **`engines.camera.ui` raised to `>=2.1.6`** (from `>=2.0.24`), the release that publishes
65
+ `videoUrl`. Nothing in this version breaks on an older core — the field is simply absent and
66
+ every backend behaves as it did in 0.8.0 — but the plugin is now versioned against the core it
67
+ is written for.
68
+
69
+ ## [0.8.0] - 2026-08-11
70
+
71
+ ### Fixed
72
+
73
+ - **No more duplicate notifications for AI descriptions.** camera.ui announces a detection, then
74
+ republishes the same notification once the AI description is ready — same `tag`, plus
75
+ `silent: true` to mark it as an update rather than a new alert. Both fields were previously
76
+ ignored, so every detection arrived twice, both times with sound. Now:
77
+ - **Telegram** and **Discord** replace the original message in place (`editMessageText` /
78
+ `editMessageCaption`, `PATCH .../messages/{id}`), so one notification's text improves rather
79
+ than a second one appearing. Message ids are held in memory per tag for 15 minutes; after a
80
+ restart, or if the original was deleted, the update falls back to a new quiet message. Only
81
+ the `silent` follow-up replaces — detection tags repeat across events, so a new alert reusing
82
+ a tag always posts a new message rather than rewriting chat history.
83
+ - **ntfy** (priority `1`), **Gotify** (priority `3`) and **Pushover** (priority `-1`) deliver the
84
+ update without sound or vibration.
85
+ - **Grafana** resolves the update onto the event it already opened: annotations mode patches the
86
+ existing annotation's text, Alertmanager and IRM modes re-send under the same fingerprint so
87
+ the alert updates instead of a second one firing.
88
+ - The **generic webhook** payload forwards the new `silent` field alongside the existing `tag`.
89
+ - `critical` notifications ignore `silent` and always alert, per the SDK contract.
90
+
91
+ - **README no longer documents `grafana_irm_ttl`.** The field was removed from the code in 0.7.1
92
+ when IRM turned out to ignore `endsAt`, but its row survived in the config table — directly
93
+ contradicting the paragraph below it stating that IRM groups do not auto-resolve.
94
+
95
+ ### Added
96
+
97
+ - **"Follow-up updates" setting** (`silent_updates`) — `Deliver quietly (no sound)` (default) or
98
+ `Skip the update entirely`, for anyone who wants exactly one notification per event on the
99
+ backends that can't replace a delivered message. Backends that replace in place still receive
100
+ the update under `skip`, since it adds no entry to the notification list.
101
+
102
+ ### Changed
103
+
104
+ - **camera.ui Go SDK bumped to v1.2.22** (from v1.2.6) for `Notification.Silent`.
105
+ - **Linting consolidated on golangci-lint**, configured by a new `.golangci.yml`. `npm run lint`
106
+ previously chained `staticcheck && golangci-lint run`, so a staticcheck finding short-circuited
107
+ the `&&` and golangci-lint never ran at all. golangci-lint already bundles staticcheck's
108
+ analyzers, so the standalone binary is gone from the script; the config excludes three
109
+ deliberate idioms (`defer resp.Body.Close()`, and nil-`Context` / De Morgan findings in tests)
110
+ and `npm run lint` now reports zero issues.
111
+ - **`package.json` description now lists every backend.** It had said "ntfy, Gotify, or a generic
112
+ webhook" since 0.4.0, omitting Pushover, Telegram, Discord and Grafana. This is the text npm
113
+ shows on the package listing. ([#29](https://github.com/calebcall/camera-ui-notify/issues/29))
114
+
8
115
  ## [0.7.1] - 2026-08-02
9
116
 
10
117
  ### Fixed
package/README.md CHANGED
@@ -45,7 +45,7 @@ Publishes to [ntfy.sh](https://ntfy.sh) or a self-hosted ntfy server.
45
45
  | `topic` | yes | — | The ntfy topic to publish to. |
46
46
  | `token` | no | — | Access token for a protected/self-hosted topic, sent as `Authorization: Bearer <token>`. |
47
47
 
48
- Delivery: `POST {server}/{topic}` with the notification body as the request body, plus `Title`, `Priority` (1–5, from severity), `Click` (deep link, if set), and `Attach`/`Icon` (image URL, if set) headers.
48
+ Delivery: `POST {server}/{topic}` with the notification body as the request body, plus `Title`, `Priority` (1–5, from severity), `Click` (deep link, if set), `Attach`/`Icon` (image URL, if set), and `Actions` (a "Play clip" view button, if a video clip is set) headers.
49
49
 
50
50
  ### Gotify
51
51
 
@@ -56,7 +56,7 @@ Publishes to a self-hosted [Gotify](https://gotify.net) server.
56
56
  | `server` | yes | Base URL of the Gotify server. Trailing `/` trimmed. |
57
57
  | `token` | yes | Gotify application token, used to authenticate published messages. |
58
58
 
59
- Delivery: `POST {server}/message?token={token}` with a JSON body `{title, message, priority}` (priority 0–10, from severity), plus a `client::notification.click` extra for the deep link and a `bigImageUrl` extra when an image URL is set.
59
+ Delivery: `POST {server}/message?token={token}` with a JSON body `{title, message, priority}` (priority 0–10, from severity), plus a `client::notification.click` extra for the deep link and a `bigImageUrl` extra when an image URL is set. A video clip is appended to the message text as a `Play clip: <url>` line.
60
60
 
61
61
  ### Generic webhook
62
62
 
@@ -69,7 +69,7 @@ Delivers to any HTTP endpoint you provide — the fallback for anything without
69
69
  | `headerName` | no | — | Optional custom header name (e.g. for a shared secret). Requires `headerValue` if set. |
70
70
  | `headerValue` | no | — | Value of the custom header. Requires `headerName` if set. |
71
71
 
72
- Delivery: `{method} {url}` with `Content-Type: application/json` and (if configured) the custom header, carrying a JSON body of `{title, subtitle, body, severity, tag, imageUrl, deepLink, data, createdAt, thumbnailBase64}`.
72
+ Delivery: `{method} {url}` with `Content-Type: application/json` and (if configured) the custom header, carrying a JSON body of `{title, subtitle, body, severity, tag, silent, imageUrl, videoUrl, deepLink, data, createdAt, thumbnailBase64}`.
73
73
 
74
74
  ### Pushover
75
75
 
@@ -80,7 +80,7 @@ Hosted push to the [Pushover](https://pushover.net) app.
80
80
  | `token` | yes | Pushover application API token/key. |
81
81
  | `user` | yes | Your Pushover user or group key. |
82
82
 
83
- Delivery: `POST https://api.pushover.net/1/messages.json` with title/message and a priority (Info→0 normal, everything higher→1 high; never emergency). The snapshot **image** is sent as an `attachment`, and an absolute deep link becomes a supplementary `url` titled "Open camera" for a detection or "Open in camera.ui" for anything else.
83
+ Delivery: `POST https://api.pushover.net/1/messages.json` with title/message and a priority (Info→0 normal, everything higher→1 high; never emergency). The snapshot **image** is sent as an `attachment`, and an absolute deep link becomes a supplementary `url` titled "Open camera" for a detection or "Open in camera.ui" for anything else. A video clip takes that `url` slot when no deep link claims it, and otherwise appends a `Play clip: <url>` line to the message.
84
84
 
85
85
  ### Telegram
86
86
 
@@ -90,8 +90,9 @@ Delivers to a chat via a [Telegram bot](https://core.telegram.org/bots).
90
90
  | ------- | -------- | -------------------------------------------------------- |
91
91
  | `token` | yes | Bot token from @BotFather. |
92
92
  | `chat` | yes | Chat ID to deliver to. |
93
+ | `clip` | no | **Upload video clips** — off by default. See [Video clips](#video-clips-video-in-push). |
93
94
 
94
- Delivery: `sendPhoto` (with the snapshot **image** + caption) when a thumbnail is present, otherwise `sendMessage`. An absolute deep link is added as an inline button, labelled "Open camera" when it opens a camera page and "Open in camera.ui" otherwise.
95
+ Delivery: `sendPhoto` (with the snapshot **image** + caption) when a thumbnail is present, otherwise `sendMessage` — or `sendVideo` when clip upload is on and the notification carries a clip. An absolute deep link is added as an inline button, labelled "Open camera" when it opens a camera page and "Open in camera.ui" otherwise; a video clip adds a second "Play clip" button below it, unless it is being uploaded.
95
96
 
96
97
  ### Discord
97
98
 
@@ -100,8 +101,9 @@ Delivers to a channel via a Discord [webhook](https://support.discord.com/hc/en-
100
101
  | Field | Required | Notes |
101
102
  | --------- | -------- | --------------------------------------- |
102
103
  | `webhook` | yes | Channel webhook URL. |
104
+ | `clip` | no | **Upload video clips** — off by default. See [Video clips](#video-clips-video-in-push). |
103
105
 
104
- Delivery: a rich embed (title, body, severity color — blue/yellow/red) with the snapshot **image** attached; an absolute deep link makes the title a link.
106
+ Delivery: a rich embed (title, body, severity color — blue/yellow/red) with the snapshot **image** attached; an absolute deep link makes the title a link, and a video clip is appended to the description as a `Play clip` link — or uploaded as a second attachment when clip upload is on.
105
107
 
106
108
  ### Grafana
107
109
 
@@ -121,7 +123,6 @@ different services, so each has its own connection fields.
121
123
  | `grafana_alertname` | no | alertmanager | `alertname` label. Defaults to `CameraUINotification`. |
122
124
  | `grafana_ttl` | no | alertmanager | Seconds before Alertmanager auto-resolves the alert. Default `900`, minimum `30`. |
123
125
  | `grafana_irm_url` | yes | irm | Inbound webhook URL of an IRM / OnCall integration. The token is in the URL, so it is masked and kept out of every error message. |
124
- | `grafana_irm_ttl` | no | irm | Seconds before the alert is eligible to auto-resolve. Default `300`, minimum `30`. Whether IRM acts on it depends on the integration's templates. |
125
126
 
126
127
  **Annotations** — `POST {server}/api/annotations` with a point-in-time, organization-wide
127
128
  annotation tagged `camera.ui`, `camera:<name>`, `severity:<level>`, plus your extra tags. Surface it
@@ -205,9 +206,97 @@ timer and per-event state, and a restart would strand the group open anyway. Clo
205
206
  > annotations have no image field at all, and alerts carry the URL as an `image_url` annotation
206
207
  > that Grafana itself won't render but downstream notification templates can use.
207
208
 
209
+ > **Video clips:** annotations mode adds a `Play clip` anchor to the annotation tooltip; the
210
+ > alertmanager and IRM modes carry the URL as a `video_url` annotation, alongside `image_url` and
211
+ > on the same terms — Grafana won't render it, downstream templates can use it.
212
+
208
213
  > **Secrets in logs:** transport failures never log the bot token / webhook URL / other
209
214
  > URL-embedded secret — request URLs are redacted from delivery errors.
210
215
 
216
+ ## Follow-up updates (AI descriptions)
217
+
218
+ camera.ui announces a detection immediately, then republishes the same notification a few seconds later once the AI description is ready. Both publishes carry the same `tag` (the collapse key), and the second carries `silent: true`, meaning *this only updates the first one — don't alert again*.
219
+
220
+ Handled per backend, according to what each platform can actually do:
221
+
222
+ | Backend | Follow-up behaviour |
223
+ | --------------- | ------------------------------------------------------------------------------------ |
224
+ | Telegram | **Replaces** the original message (`editMessageText` / `editMessageCaption`). |
225
+ | Discord | **Replaces** the original message (`PATCH .../messages/{id}`). |
226
+ | Grafana | **Revises** the record it already filed — see below. |
227
+ | ntfy | Delivered at priority `1` — visible, no sound or vibration. |
228
+ | Gotify | Delivered at priority `3` — joins the in-app list, raises no system notification. |
229
+ | Pushover | Delivered at priority `-1` (quiet) — no sound or vibration. |
230
+ | Generic webhook | `silent: true` is forwarded in the JSON payload; your endpoint decides. |
231
+
232
+ Grafana revises per mode: **annotations** patches the annotation it created (`PATCH /api/annotations/:id`), so the dashboard keeps one marker at the detection's own timestamp whose text improves. **Alertmanager** and **IRM** re-file under the `event_id` / `alert_uid` the first alert used — that identity is what each surface deduplicates on, so the existing alert or group picks up the description instead of a second one firing. When the publisher supplies its own `Data["eventId"]`, that id is authoritative and the two publishes already share it, so those modes update correctly even across a plugin restart.
233
+
234
+ The replacing backends therefore show **one** notification whose text improves in place. The message id is remembered in memory per tag for 15 minutes; after a plugin restart, or if the original message was deleted, the update is delivered as a new (quiet) message instead of being lost.
235
+
236
+ Only the `silent` follow-up replaces. Detection tags repeat across events (`motion:cam-1` is the same tag every time that camera sees something), so a *new* alert reusing a tag always posts a new message — your chat history is never rewritten by a later event.
237
+
238
+ **Critical alerts ignore `silent`** — a `critical` severity notification always alerts, per the SDK contract.
239
+
240
+ If you would rather never see the follow-up on a backend that can't replace, set **Follow-up updates** to `Skip the update entirely` in the plugin settings. Backends that replace in place still receive it under that setting, since editing adds nothing to the notification list — with the one caveat that a lost message id (restart, deleted message) turns that edit into a new quiet message.
241
+
242
+ ## Video clips ("Video in Push")
243
+
244
+ camera.ui 2.1.6 added `videoUrl` to the notification payload: a short MP4 of the recording that
245
+ triggered the alert, published when the camera — or, for a multi-camera episode, the episode —
246
+ has **Video in Push** switched on under its notification settings. Whether a clip is published at
247
+ all is decided there, per camera, not here — this plugin only forwards what it is handed.
248
+
249
+ ### By default: the clip is a link
250
+
251
+ Nothing this plugin talks to is the first-party mobile app, so no backend plays the clip *inside*
252
+ the push the way an iOS attachment does. What each one can do is offer the clip as a link, opened
253
+ in the phone's own browser or player — already authenticated to your server. The rule is the same
254
+ everywhere: **the clip is added, never substituted.** The snapshot keeps its attachment slot and
255
+ the deep link keeps its click target, because the deep link opens the event in camera.ui, from
256
+ which the recording is one tap away, whereas the clip on its own is a dead end.
257
+
258
+ | Backend | How the clip is surfaced |
259
+ | --------------- | ----------------------------------------------------------------------------------- |
260
+ | ntfy | A `Play clip` **view action button** (the single `Attach` slot stays with the snapshot). |
261
+ | Telegram | A second **inline button** below the deep-link button. |
262
+ | Discord | A `Play clip` link at the end of the embed description. |
263
+ | Pushover | The supplementary `url` when no deep link claims it; otherwise a line in the message. |
264
+ | Gotify | A `Play clip: <url>` line appended to the message text. |
265
+ | Grafana | An anchor in the annotation tooltip; a `video_url` annotation in alertmanager/IRM modes. |
266
+ | Generic webhook | A `videoUrl` field in the JSON payload, forwarded verbatim. |
267
+
268
+ **A linked clip has to be reachable from the phone.** camera.ui publishes the URL absolute; a
269
+ server-relative one is made absolute with the **camera.ui Base URL** setting, the same way deep
270
+ links are. Without that setting a relative clip URL is dropped rather than delivered as a link
271
+ that cannot open — except on the generic webhook, whose receiver is a machine that may well be
272
+ able to resolve it, so there it is forwarded as-is.
273
+
274
+ ### Opt in: upload the clip (Telegram, Discord)
275
+
276
+ Telegram and Discord can carry the video itself, and both render a real player in the chat. Turn
277
+ on **Upload video clips** in the plugin settings for either one and the plugin downloads the clip
278
+ from camera.ui and re-uploads the bytes to the service. Nothing outside your network ever fetches
279
+ from your server — the plugin is the only client that touches the clip URL — so this works for an
280
+ install that isn't reachable from the internet at all.
281
+
282
+ | | Telegram | Discord |
283
+ | --- | --- | --- |
284
+ | Method | `sendVideo`, `supports_streaming` on so it plays while downloading | a `clip.mp4` file attachment beside the embed |
285
+ | Snapshot | **replaced** — Telegram carries one media item per message | **kept**, still rendered inside the embed |
286
+ | Size cap | 50 MB (the Bot API's own upload limit) | 8 MB (Discord allows 10 MB per request on an unboosted server; the headroom is for the snapshot) |
287
+ | Follow-up cost | none — `editMessageCaption` leaves the video in place, so the AI description doesn't re-download it | one more download + upload — Discord's edit drops any attachment the request doesn't re-send |
288
+
289
+ It is off by default because it is the expensive path: the default merely passes a URL along,
290
+ while this moves the whole file twice for every detection. The Discord row above is the one to
291
+ weigh — a busy camera with clip upload on moves each clip **four** times once the AI description
292
+ lands.
293
+
294
+ **Every failure falls back to the link, never to a lost notification.** A clip past the size cap
295
+ is not downloaded at all (a `Content-Length` over the limit fails before the body is read); a
296
+ download that errors or stalls, and an upload the service rejects, both retry immediately as an
297
+ ordinary send with the `Play clip` link. Oversize clips are rejected rather than truncated — a
298
+ clipped MP4 is a broken upload, not a smaller video. Each fallback is logged with its reason.
299
+
211
300
  ## Configuring your target (v1: one active target)
212
301
 
213
302
  There is no "add device" flow. Instead, configure the plugin itself:
@@ -215,7 +304,9 @@ There is no "add device" flow. Instead, configure the plugin itself:
215
304
  1. Open the **Notify** plugin's page in camera.ui (Plugins → Notify).
216
305
  2. In its settings, pick a **Service** (`ntfy`, `Gotify`, `Generic webhook`, `Pushover`, `Telegram`, `Discord`, or `Grafana`) from the dropdown built from the registered backends.
217
306
  3. Fill in that service's fields — only the selected service's fields are shown; the rest are condition-gated out.
218
- 4. Save. The config is validated (`ParseTarget`) the next time a notification is dispatched; `getDevices` then synthesizes one delivery target from it, and notifications from any publisher are delivered there.
307
+ 4. Optionally set **Follow-up updates** (see [Follow-up updates](#follow-up-updates-ai-descriptions)) defaults to delivering the AI description quietly.
308
+ 5. On Telegram or Discord, optionally turn on **Upload video clips** (see [Video clips](#video-clips-video-in-push)) — off by default.
309
+ 6. Save. The config is validated (`ParseTarget`) the next time a notification is dispatched; `getDevices` then synthesizes one delivery target from it, and notifications from any publisher are delivered there.
219
310
 
220
311
  This is a **single, instance-wide target** in v1 — there's no way to register several devices at once. Changing the config replaces the previous target rather than adding to it. Delivery for that one target is a single request per notification (no fan-out to worry about, since there's only one device).
221
312
 
@@ -318,9 +409,24 @@ dispatch logic in `notifier.go` all pick up the new backend automatically.
318
409
 
319
410
  ```bash
320
411
  go test ./src/... # full suite
321
- go test ./src/... -race -count=1 # race detector
412
+ go test ./src/... -race -count=1 # race detector (needs cgo: apt-get install gcc)
413
+ npm run lint # golangci-lint, configured by .golangci.yml
414
+ npm run format # gofmt + go fix
415
+ ```
416
+
417
+ Linting is golangci-lint only. It bundles staticcheck's analyzers (`SA`/`S`/`ST`/`QF`), so running
418
+ the standalone `staticcheck` binary alongside it only duplicates findings — and, being a separate
419
+ tool, it can't read the path-scoped exclusions in `.golangci.yml`.
420
+
421
+ ```bash
422
+ go install github.com/golangci/golangci-lint/v2/cmd/golangci-lint@latest
322
423
  ```
323
424
 
425
+ `.golangci.yml` excludes three deliberate idioms, each scoped as narrowly as the linter allows so
426
+ the check stays live everywhere else: `defer resp.Body.Close()` (errcheck), and — in `_test.go`
427
+ only — passing a nil `Context` to exercise each backend's `ctx == nil` fallback (SA1012) and the
428
+ `!(a <= b && b <= c)` monotonicity assertions (QF1001).
429
+
324
430
  ## License
325
431
 
326
432
  [MIT](./LICENSE.md).
package/bundle.zip CHANGED
Binary file
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "displayName": "Notify",
3
3
  "name": "@calebcall/camera-ui-notify",
4
- "version": "0.7.1",
5
- "description": "A fully-local, multi-backend camera.ui notifier plugin: delivers notifications to ntfy, Gotify, or a generic webhook, entirely on your own hardware with no cloud dependency.",
4
+ "version": "0.9.0",
5
+ "description": "A fully-local, multi-backend camera.ui notifier plugin: delivers notifications to ntfy, Gotify, Pushover, Telegram, Discord, Grafana, or a generic webhook, entirely on your own hardware with no cloud dependency.",
6
6
  "author": "calebcall (https://github.com/calebcall/camera-ui-notify)",
7
7
  "main": "./main.go",
8
8
  "type": "commonjs",
@@ -12,7 +12,7 @@
12
12
  "bundle:dev": "npm run build && cross-env MODE=development cui bundle",
13
13
  "format": "gofmt -w ./src/* && go fix ./src/...",
14
14
  "install-updates": "npm i --save --force && go mod tidy",
15
- "lint": "staticcheck ./src/... && golangci-lint run ./src/...",
15
+ "lint": "golangci-lint run ./src/...",
16
16
  "prepublishOnly": "node -e \"if(!process.env.SAFE_PUBLISH){console.error('Error: Please use @camera.ui/cli to publish the plugin:\\n npm run publish:alpha\\n npm run publish:beta\\n npm run publish:latest\\n');process.exit(1)}\"",
17
17
  "publish:alpha": "npm i --save --force && npm run bundle && cui publish --alpha",
18
18
  "publish:beta": "npm i --save --force && npm run bundle && cui publish --beta",
@@ -25,7 +25,7 @@
25
25
  "url": "https://github.com/calebcall/camera-ui-notify/issues"
26
26
  },
27
27
  "engines": {
28
- "camera.ui": ">=2.0.24",
28
+ "camera.ui": ">=2.1.6",
29
29
  "node": ">=22.0.0"
30
30
  },
31
31
  "homepage": "https://github.com/calebcall/camera-ui-notify/tree/main#readme",
@@ -48,14 +48,14 @@
48
48
  "protocolLevel": 1
49
49
  },
50
50
  "optionalDependencies": {
51
- "@calebcall/camera-ui-notify-darwin-arm64": "0.7.1",
52
- "@calebcall/camera-ui-notify-darwin-amd64": "0.7.1",
53
- "@calebcall/camera-ui-notify-linux-amd64": "0.7.1",
54
- "@calebcall/camera-ui-notify-linux-arm64": "0.7.1",
55
- "@calebcall/camera-ui-notify-windows-amd64": "0.7.1",
56
- "@calebcall/camera-ui-notify-windows-arm64": "0.7.1",
57
- "@calebcall/camera-ui-notify-linux-amd64-musl": "0.7.1",
58
- "@calebcall/camera-ui-notify-linux-arm64-musl": "0.7.1"
51
+ "@calebcall/camera-ui-notify-darwin-arm64": "0.9.0",
52
+ "@calebcall/camera-ui-notify-darwin-amd64": "0.9.0",
53
+ "@calebcall/camera-ui-notify-linux-amd64": "0.9.0",
54
+ "@calebcall/camera-ui-notify-linux-arm64": "0.9.0",
55
+ "@calebcall/camera-ui-notify-windows-amd64": "0.9.0",
56
+ "@calebcall/camera-ui-notify-windows-arm64": "0.9.0",
57
+ "@calebcall/camera-ui-notify-linux-amd64-musl": "0.9.0",
58
+ "@calebcall/camera-ui-notify-linux-arm64-musl": "0.9.0"
59
59
  },
60
60
  "files": [
61
61
  "bundle.zip",