notiformer 1.1.0 → 1.1.2

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
@@ -1,10 +1,10 @@
1
1
  # notiformer
2
2
 
3
- Real-time push notifications and feature gates for your code.
3
+ Real-time push notifications, approval gates, and feature flags for your code.
4
4
 
5
- Know the moment something important happens in your appa new sale, an error, a spike in costs. Get notified on your phone via the **Notiformer app** (iOS & Android), or in your inbox.
5
+ Pause your AI agent, ask your phone, and continueor fire a one-line alert the moment something important happens.
6
6
 
7
- > **Works everywhere.** Use in Node.js, Express, Next.js API routes, serverless functions, Python, Go, or any HTTP client.
7
+ > **Works everywhere.** Node.js, Express, Next.js, serverless functions, Python, Go, or any HTTP client.
8
8
 
9
9
  ---
10
10
 
@@ -25,89 +25,216 @@ const n = new Notiformer({
25
25
  apiKey: "ntf_live_...", // from app.notiformer.com/projects
26
26
  });
27
27
 
28
+ // 🛑 Block and wait for approval
29
+ const { approved } = await n.ask({
30
+ message: `Deploy v2 to production?`,
31
+ context: "Build #442 · 3 services affected",
32
+ timeout: 300,
33
+ });
34
+ if (approved) await deploy();
35
+
36
+ // 🔀 Block and wait for a choice from multiple options
37
+ const { selected } = await n.select({
38
+ message: "How to handle the failed payment?",
39
+ options: [
40
+ { value: "retry", label: "🔄 Retry in 1 hour" },
41
+ { value: "notify", label: "📧 Notify the customer" },
42
+ { value: "cancel", label: "✕ Cancel the order", isDestructive: true },
43
+ ],
44
+ fallback: "notify",
45
+ timeout: 300,
46
+ });
47
+ if (selected === "retry") await scheduleRetry();
48
+
49
+ // 🔔 Fire-and-forget alert
28
50
  await n.event({
29
- channel: "payments", // groups related events — auto-created on first use
30
- event: "payment_success", // machine-readable event name
31
- description: "$49.00 — john@example.com", // optional human-readable detail
32
- icon: "💳", // optional emoji shown in the feed and notification
33
- tags: { userId: "usr_123", plan: "pro" }, // optional key-value metadata
34
- value: "$49.00", // optional value highlighted in the feed
35
- notify: true, // true → push notification | false → silent log only
36
- recipients: ["you@company.com"], // optional — notify specific people only (see below)
51
+ channel: "payments",
52
+ event: "payment_success",
53
+ description: "$49.00 — john@example.com",
54
+ icon: "💳",
55
+ value: "$49.00",
56
+ notify: true,
37
57
  });
58
+
59
+ // 🚦 Feature gate check (cached 30s)
60
+ if (await n.gate("new-checkout-flow")) {
61
+ return newCheckout(req);
62
+ }
38
63
  ```
39
64
 
40
- > **Tip:** Use `apiKey: 'ntf_live_test'` to get started without a real key. The SDK will print setup instructions in your console and skip all API calls.
65
+ > **Tip:** Use `apiKey: 'ntf_live_test'` to get started without a real key. The SDK prints setup instructions and skips all API calls.
41
66
 
42
67
  ---
43
68
 
44
69
  ## Get your API key
45
70
 
46
- 1. Go to [app.notiformer.com](https://app.notiformer.com) and create an account (free)
71
+ 1. Go to [app.notiformer.com](https://app.notiformer.com) and create a free account
47
72
  2. Create a project
48
73
  3. Copy the API key from the project overview
49
- 4. Replace `'ntf_live_test'` in your code with your real key
50
74
 
51
75
  ---
52
76
 
53
- ## `event()` — Send an event
77
+ ## Configuration
54
78
 
55
79
  ```ts
56
- await n.event({
57
- channel: "payments", // required — string, lowercase, a-z 0-9 - _
58
- event: "payment_success", // requiredmachine-readable event name
80
+ const n = new Notiformer({
81
+ apiKey: "ntf_live_...", // required
82
+ silent: false, // optionaltrue = no API calls (great for local dev)
83
+ throwOnError: false, // optional — true = throws instead of returning null
84
+ onError: (err) => {
85
+ // optional — called on any failure
86
+ Sentry.captureException(err);
87
+ },
88
+ });
59
89
 
60
- description: "$49.00 john@example.com", // optional
61
- icon: "💳", // optional — emoji
62
- tags: { plan: "pro", userId: "123" }, // optional — displayed in the event feed
63
- value: "$49.00", // optional — highlighted value in the feed
64
- notify: true, // optional — default: true
65
- recipients: ["alice@company.com"], // optional — see "Targeting specific people"
90
+ // Silence in local development
91
+ const n = new Notiformer({
92
+ apiKey: process.env.NOTIFORMER_API_KEY!,
93
+ silent: process.env.NODE_ENV !== "production",
66
94
  });
67
95
  ```
68
96
 
69
- ### `notify`
97
+ ---
70
98
 
71
- | Value | Behaviour |
72
- | ---------------- | -------------------------------------------------------------- |
73
- | `true` (default) | Sends push notification to subscribed members |
74
- | `false` | Stores the event silently for analytics — no notification sent |
99
+ ## `ask()` — Approval gate (binary)
75
100
 
76
- ### Return value
101
+ Pause your code and wait for a **human to approve or deny** from the Notiformer app. The `Promise` resolves when you respond, or when the timeout expires.
102
+
103
+ > **This is a blocking call.** Your agent stops at `await n.ask()` and waits.
77
104
 
78
105
  ```ts
79
- const result = await n.event({ ... });
80
- // result is null if the call failed (never throws by default)
81
- // result.rateLimited === true if you exceeded 60 events/minute (event is stored, notification skipped)
106
+ const { approved, timedOut, respondedAt } = await n.ask({
107
+ message: "Send campaign to 3,241 users?", // required shown as notification title
108
+ context: "Campaign: Black Friday · segment A", // optional shown in notification body
109
+ details: "Full changelog:\n• Fix auth bug", // optional — shown in app detail screen, supports \n
110
+ timeout: 300, // optional — seconds to wait (default: 300)
111
+ fallback: "deny", // optional — 'deny' | 'approve' on timeout (default: 'deny')
112
+ });
113
+
114
+ if (approved) {
115
+ await sendEmails();
116
+ } else {
117
+ console.log(timedOut ? "Timed out" : "Denied");
118
+ }
82
119
  ```
83
120
 
84
- `event()` **never throws by default.** A failed notification will never crash your app. If you prefer it to throw, pass `throwOnError: true` in the config.
121
+ ### Return value
122
+
123
+ | Field | Type | Description |
124
+ | ------------- | ---------------- | ----------------------------------------------------- |
125
+ | `approved` | `boolean` | `true` if the user tapped Approve |
126
+ | `timedOut` | `boolean` | `true` if nobody responded before the timeout |
127
+ | `respondedAt` | `string \| null` | ISO timestamp of the response, or `null` if timed out |
128
+
129
+ ### Plan limits
130
+
131
+ | Plan | Gates/month | Max timeout |
132
+ | -------- | ----------------- | ----------- |
133
+ | Free | — (not available) | — |
134
+ | Starter | 200 | 5 min |
135
+ | Pro | 2,000 | 15 min |
136
+ | Business | 20,000 | 60 min |
85
137
 
86
138
  ---
87
139
 
88
- ## Targeting specific people `recipients`
140
+ ## `select()` Approval gate (multi-option)
89
141
 
90
- By default, when `notify: true`, all members of the project who are subscribed to that channel receive a notification.
142
+ Like `ask()`, but instead of Approve/Deny the user picks from **2–6 custom options**. Returns the `value` string of the chosen option.
91
143
 
92
- You can override this and target specific people by email:
144
+ Uses the same monthly quota as `ask()`.
145
+
146
+ ```ts
147
+ const { selected, timedOut, respondedAt } = await n.select({
148
+ message: "How should the agent handle the error?", // required
149
+ options: [
150
+ // required — min 2, max 6
151
+ { value: "retry", label: "🔄 Retry the request" },
152
+ { value: "skip", label: "⏭ Skip and continue" },
153
+ { value: "stop", label: "🛑 Stop the pipeline", isDestructive: true },
154
+ ],
155
+ context: "Step 4/10 failed — HTTP 503 from payments API", // optional
156
+ details: "Error: Connection timeout\nEndpoint: /v2/charge\nRetries: 3",
157
+ timeout: 300, // optional — seconds (default: 300)
158
+ fallback: "skip", // optional — value to return on timeout (must match an option)
159
+ });
160
+
161
+ if (selected === "retry") await retryStep();
162
+ if (selected === "skip") await nextStep();
163
+ if (selected === "stop") await abort();
164
+ ```
165
+
166
+ ### Return value
167
+
168
+ | Field | Type | Description |
169
+ | ------------- | ---------------- | ------------------------------------------------------------------------------------------------ |
170
+ | `selected` | `string \| null` | The value of the chosen option, or the fallback on timeout. `null` if timed out with no fallback |
171
+ | `timedOut` | `boolean` | `true` if nobody responded in time |
172
+ | `respondedAt` | `string \| null` | ISO timestamp of the response |
173
+
174
+ ### Option shape
175
+
176
+ ```ts
177
+ interface SelectOption {
178
+ value: string; // returned in result.selected — max 50 chars
179
+ label: string; // button text in the app — max 80 chars, supports emoji
180
+ isDestructive?: boolean; // if true, button is styled in red
181
+ }
182
+ ```
183
+
184
+ ### Validation rules (enforced at call time)
185
+
186
+ - At least 2 options, maximum 6
187
+ - `fallback` must match one of the option `value` strings exactly
188
+ - Each `value` max 50 chars, each `label` max 80 chars
189
+
190
+ ---
191
+
192
+ ## `event()` — Fire-and-forget alert
193
+
194
+ Send an event notification. Never throws by default.
93
195
 
94
196
  ```ts
95
197
  await n.event({
96
- channel: "payments",
97
- event: "large_order",
98
- description: "$2,400enterprise@client.com",
99
- notify: true,
100
- recipients: ["cto@company.com", "billing@company.com"], // only these two are notified
198
+ channel: "payments", // required — auto-created on first use
199
+ event: "payment_success", // required — machine-readable event name
200
+ description: "$49.00john@co.com", // optional — shown in notification + feed
201
+ icon: "💳", // optional — emoji
202
+ tags: { plan: "pro" }, // optional metadata shown in feed
203
+ value: "$49.00", // optional — highlighted value in feed
204
+ notify: true, // optional — true = push notification (default)
205
+ recipients: ["you@company.com"], // optional — notify specific people only
101
206
  });
102
207
  ```
103
208
 
104
- **How it works:**
209
+ ### `notify` behaviour
210
+
211
+ | Value | Behaviour |
212
+ | ---------------- | ------------------------------------------- |
213
+ | `true` (default) | Sends push to subscribed members |
214
+ | `false` | Stores the event silently — no notification |
215
+
216
+ ### Return value
217
+
218
+ ```ts
219
+ const result = await n.event({ ... });
220
+ // result is null if the call failed (never throws by default)
221
+ // result.id → event ID
222
+ // result.createdAt → ISO timestamp
223
+ // result.rateLimited → true if >60 events/min (event stored, notification skipped)
224
+ ```
225
+
226
+ ### Recipients
105
227
 
106
- - If a recipient has a Notiformer account linked to that email and has installed the app, they receive a **push notification**
107
- - Email notifications are **coming soon** — recipients will receive emails once this feature launches
108
- - If you don't specify `recipients`, all project members subscribed to the channel are notified
228
+ By default, all project members subscribed to the channel are notified. Use `recipients` to target specific people:
109
229
 
110
- **Plan limits for `recipients`:**
230
+ ```ts
231
+ await n.event({
232
+ channel: "sales",
233
+ event: "enterprise_signup",
234
+ notify: true,
235
+ recipients: ["cto@company.com"], // only this person gets the push
236
+ });
237
+ ```
111
238
 
112
239
  | Plan | Max recipients per event |
113
240
  | -------- | ------------------------ |
@@ -116,230 +243,189 @@ await n.event({
116
243
  | Pro | 10 |
117
244
  | Business | 30 |
118
245
 
119
- > Members are managed from the project dashboard at [app.notiformer.com](https://app.notiformer.com/projects).
246
+ ### Monthly event quotas
247
+
248
+ | Plan | Events/month |
249
+ | -------- | ------------ |
250
+ | Free | 500 |
251
+ | Starter | 20,000 |
252
+ | Pro | 75,000 |
253
+ | Business | 300,000 |
254
+
255
+ Rate limit: 60 events/minute per project. If exceeded, the event is stored but no notification is sent (`rateLimited: true` in the response).
120
256
 
121
257
  ---
122
258
 
123
- ## `gate()` — Feature gates
259
+ ## `gate()` — Feature flags
124
260
 
125
- Toggle features in your code remotely from the Notiformer dashboard — no redeploy needed.
261
+ Toggle features remotely from the dashboard — no redeploy needed. Results are **cached locally for 30 seconds** by default.
262
+
263
+ > Available on **Pro and Business** plans only.
126
264
 
127
265
  ```ts
128
266
  const isEnabled = await n.gate("new-checkout-flow");
129
267
 
130
268
  if (isEnabled) {
131
- // new behaviour
269
+ return newCheckout(req);
132
270
  } else {
133
- // old behaviour
271
+ return legacyCheckout(req);
134
272
  }
135
273
  ```
136
274
 
137
- Gates are **cached locally for 30 seconds** by default to avoid hammering the API on every request.
138
-
139
275
  ### Options
140
276
 
141
277
  ```ts
142
278
  const isEnabled = await n.gate("my-gate", {
143
- fallback: false, // optional — returned if the gate can't be fetched (default: false)
144
- cacheTtl: 60, // optional cache duration in seconds (default: 30)
279
+ fallback: false, // returned if the gate can't be fetched (default: false)
280
+ cacheTtl: 60, // local cache in seconds (default: 30)
145
281
  });
146
- ```
147
282
 
148
- ### Full gate result
149
-
150
- ```ts
283
+ // Full result with metadata
151
284
  const result = await n.gateDetails("my-gate");
152
- // { key: 'my-gate', enabled: true, cached: false, fetchedAt: '2025-...' }
153
- ```
154
-
155
- ### Clear the cache
285
+ // { key: 'my-gate', enabled: true, cached: false, fetchedAt: '...' }
156
286
 
157
- ```ts
158
- n.clearGateCache("my-gate"); // clear a specific gate
287
+ // Cache management
288
+ n.clearGateCache("my-gate"); // clear one gate
159
289
  n.clearGateCache(); // clear all gates
160
290
  ```
161
291
 
292
+ | Plan | Gate limit per project |
293
+ | -------- | ---------------------- |
294
+ | Free | Not available |
295
+ | Starter | Not available |
296
+ | Pro | 50 gates |
297
+ | Business | 500 gates |
298
+
162
299
  ---
163
300
 
164
- ## Configuration
301
+ ## When quota is exceeded
302
+
303
+ When you hit your monthly limit, the API returns HTTP 429 with an `upgradeUrl` field pointing to a direct Stripe Checkout for the next plan:
165
304
 
166
305
  ```ts
306
+ // The SDK logs this automatically:
307
+ // [notiformer] Plan limit reached. Upgrade your plan to continue:
308
+ // → https://api.notiformer.com/v1/upgrade?plan=pro&uid=...&email=...
309
+
310
+ // You can also handle it yourself:
167
311
  const n = new Notiformer({
168
- apiKey: "ntf_live_...", // required — from app.notiformer.com/projects
169
- silent: false, // optional — true = no API calls (great for local dev)
170
- throwOnError: false, // optional — true = throws instead of returning null
312
+ apiKey: "ntf_live_...",
171
313
  onError: (err) => {
172
- // optional — called when any call fails
173
- Sentry.captureException(err);
314
+ if (err.message.includes("quota")) {
315
+ notifyAdmin("Notiformer quota exceeded");
316
+ }
174
317
  },
175
318
  });
176
319
  ```
177
320
 
178
- ### Silence in local development
321
+ ---
322
+
323
+ ## REST API
324
+
325
+ All SDK methods wrap REST endpoints. Use them from any language:
179
326
 
180
- ```ts
181
- const n = new Notiformer({
182
- apiKey: process.env.NOTIFORMER_API_KEY!,
183
- silent: process.env.NODE_ENV !== "production", // no calls in dev/test
184
- });
327
+ ```
328
+ POST https://api.notiformer.com/v1/events — n.event()
329
+ POST https://api.notiformer.com/v1/ask — n.ask() create
330
+ GET https://api.notiformer.com/v1/ask/:id — n.ask() poll
331
+ POST https://api.notiformer.com/v1/select — n.select() create
332
+ GET https://api.notiformer.com/v1/select/:id — n.select() poll
333
+ GET https://api.notiformer.com/v1/gates/:key — n.gate()
334
+ GET https://api.notiformer.com/v1/health — status check
185
335
  ```
186
336
 
187
- ---
337
+ All endpoints require `Authorization: Bearer ntf_live_...`.
188
338
 
189
- ## Rate limits & quotas
339
+ ### Python example
190
340
 
191
- | Limit | Value |
192
- | ------------------------------- | ------- |
193
- | Events per minute (per project) | 60 |
194
- | Events per month (Free plan) | 100 |
195
- | Events per month (Starter) | 5,000 |
196
- | Events per month (Pro) | 75,000 |
197
- | Events per month (Business) | 300,000 |
341
+ ```python
342
+ import requests, time
198
343
 
199
- If you exceed **60 events/minute**: the event is stored and visible in your feed, but no notification is sent. The API response includes `rateLimited: true`.
344
+ NF_KEY = "ntf_live_..."
345
+ HEADERS = {"Authorization": f"Bearer {NF_KEY}"}
200
346
 
201
- If you exceed your **monthly quota**: the event is rejected with HTTP 429. Upgrade your plan at [app.notiformer.com/settings](https://app.notiformer.com/settings).
347
+ # Create an approval request
348
+ r = requests.post(
349
+ "https://api.notiformer.com/v1/ask",
350
+ headers=HEADERS,
351
+ json={"message": "Delete 500 rows?", "timeout": 300}
352
+ )
353
+ ask_id = r.json()["id"]
354
+
355
+ # Poll until resolved
356
+ while True:
357
+ poll = requests.get(
358
+ f"https://api.notiformer.com/v1/ask/{ask_id}",
359
+ headers=HEADERS
360
+ ).json()
361
+ if poll["status"] != "pending":
362
+ break
363
+ time.sleep(2)
364
+
365
+ if poll["status"] == "approved":
366
+ db.execute(delete_query)
367
+ ```
202
368
 
203
369
  ---
204
370
 
205
371
  ## Common patterns
206
372
 
207
- ### Alert on payment success
373
+ ### AI agent guard
208
374
 
209
375
  ```ts
210
- await n.event({
211
- channel: "payments",
212
- event: "payment_success",
213
- description: `${amount} ${user.email}`,
214
- icon: "💳",
215
- value: amount,
216
- notify: true,
376
+ // Stop the agent before any destructive action
377
+ const { approved } = await n.ask({
378
+ message: `Agent wants to delete ${count} records`,
379
+ context: `Table: ${table} · Environment: production`,
380
+ details: `WHERE clause: ${query}\nEstimated rows: ${count}`,
381
+ timeout: 120,
382
+ fallback: "deny",
217
383
  });
384
+ if (!approved) throw new Error("Action denied by human");
218
385
  ```
219
386
 
220
- ### Alert on unhandled errors
387
+ ### Multi-step decision
221
388
 
222
389
  ```ts
223
- // Express error middleware
224
- app.use(async (err, req, res, next) => {
225
- await n.event({
226
- channel: "errors",
227
- event: "unhandled_error",
228
- description: err.message,
229
- icon: "🔴",
230
- tags: { path: req.path, method: req.method },
231
- notify: true,
232
- });
233
- res.status(500).json({ error: "Internal server error" });
390
+ const { selected } = await n.select({
391
+ message: `Build #${build.id} failed at step ${step}`,
392
+ options: [
393
+ { value: "retry", label: "🔄 Retry from this step" },
394
+ { value: "restart", label: "↩ Restart from scratch" },
395
+ { value: "abort", label: "🛑 Abort pipeline", isDestructive: true },
396
+ ],
397
+ fallback: "abort",
398
+ timeout: 600,
234
399
  });
235
400
  ```
236
401
 
237
- ### Silent analytics (no notification)
402
+ ### Silent analytics
238
403
 
239
404
  ```ts
240
405
  await n.event({
241
406
  channel: "analytics",
242
407
  event: "page_view",
243
408
  tags: { path: req.path, userId: session.userId },
244
- notify: false, // stored in feed, no push notification sent
409
+ notify: false, // stored in feed, no push
245
410
  });
246
411
  ```
247
412
 
248
- ### Feature gate in an API route
249
-
250
- ```ts
251
- // Next.js API route
252
- export async function POST(req: Request) {
253
- const useNewFlow = await n.gate("new-checkout-flow");
254
-
255
- if (useNewFlow) {
256
- return newCheckoutHandler(req);
257
- }
258
- return legacyCheckoutHandler(req);
259
- }
260
- ```
261
-
262
- ### Notify a specific person
413
+ ### Error alert with context
263
414
 
264
415
  ```ts
265
- // Only notify the CTO for large orders
266
- await n.event({
267
- channel: "sales",
268
- event: "enterprise_signup",
269
- description: `${company} — ${mrr}/mo`,
270
- icon: "🏢",
271
- value: mrr,
272
- notify: true,
273
- recipients: ["cto@yourcompany.com"], // only they get the push
416
+ app.use(async (err, req, res, next) => {
417
+ await n.event({
418
+ channel: "errors",
419
+ event: "unhandled_error",
420
+ description: err.message,
421
+ icon: "🔴",
422
+ tags: { path: req.path, method: req.method, status: 500 },
423
+ notify: true,
424
+ });
425
+ res.status(500).json({ error: "Internal server error" });
274
426
  });
275
427
  ```
276
428
 
277
- ### Using with Python (REST API)
278
-
279
- Notiformer works from any language via the REST API:
280
-
281
- ```python
282
- import requests
283
-
284
- requests.post(
285
- 'https://api.notiformer.com/v1/events',
286
- headers={ 'Authorization': f'Bearer {NF_KEY}' },
287
- json={
288
- 'channel': 'ai-costs',
289
- 'event': 'cost_spike',
290
- 'description': '🤖 $180 spent in 10 min',
291
- 'notify': True,
292
- 'recipients': ['cto@company.com'], # optional
293
- }
294
- )
295
- ```
296
-
297
- ---
298
-
299
- ## Receiving notifications
300
-
301
- Push notifications are delivered via the **Notiformer app**, available for iOS and Android.
302
-
303
- 1. Download the Notiformer app from the App Store or Google Play
304
- 2. Sign in with your Notiformer account
305
- 3. You'll automatically receive push notifications for projects you own or are a member of
306
- 4. Manage which channels you're subscribed to from the app settings
307
-
308
- > **Email notifications** are in development and coming soon. Sign up at [notiformer.com](https://notiformer.com) to be notified when they launch.
309
-
310
- ---
311
-
312
- ## REST API
313
-
314
- The same `event()` call maps to this endpoint:
315
-
316
- ```
317
- POST https://api.notiformer.com/v1/events
318
- Authorization: Bearer ntf_live_...
319
- Content-Type: application/json
320
-
321
- {
322
- "channel": "payments",
323
- "event": "payment_success",
324
- "description": "$49.00 — john@example.com",
325
- "icon": "💳",
326
- "tags": { "plan": "pro" },
327
- "value": "$49.00",
328
- "notify": true,
329
- "recipients": ["you@company.com"]
330
- }
331
- ```
332
-
333
- Response:
334
-
335
- ```json
336
- {
337
- "id": "evt_abc123",
338
- "createdAt": "2025-01-15T10:30:00.000Z",
339
- "rateLimited": false
340
- }
341
- ```
342
-
343
429
  ---
344
430
 
345
431
  ## Requirements
@@ -355,3 +441,4 @@ Response:
355
441
  - **Docs:** [docs.notiformer.com](https://docs.notiformer.com)
356
442
  - **Status:** [status.notiformer.com](https://status.notiformer.com)
357
443
  - **Pricing:** [notiformer.com/#pricing](https://notiformer.com/#pricing)
444
+ - **npm:** [npmjs.com/package/notiformer](https://www.npmjs.com/package/notiformer)
package/dist/index.d.ts CHANGED
@@ -8,19 +8,26 @@
8
8
  *
9
9
  * const n = new Notiformer({ apiKey: 'ntf_live_...' });
10
10
  *
11
- * // Notify everyone subscribed to the 'payments' channel
11
+ * // Fire-and-forget alert
12
12
  * await n.event({ channel: 'payments', event: 'new_sale', notify: true });
13
13
  *
14
- * // Notify specific people only
15
- * await n.event({
16
- * channel: 'payments',
17
- * event: 'refund_requested',
18
- * notify: true,
19
- * recipients: ['admin@company.com', 'billing@company.com'],
14
+ * // Block and wait for approval
15
+ * const { approved } = await n.ask({ message: 'Deploy to prod?' });
16
+ *
17
+ * // Block and wait for a choice from multiple options
18
+ * const { selected } = await n.select({
19
+ * message: 'How to handle the failed payment?',
20
+ * options: [
21
+ * { value: 'retry', label: '🔄 Retry in 1 hour' },
22
+ * { value: 'notify', label: '📧 Notify customer' },
23
+ * { value: 'cancel', label: '✕ Cancel order', isDestructive: true },
24
+ * ],
25
+ * fallback: 'notify',
20
26
  * });
21
27
  */
22
- import type { NotiformerConfig, EventPayload, EventResponse, GateOptions, GateResult, AskPayload, AskResult } from "./types";
23
- export type { NotiformerConfig, EventPayload, EventResponse, GateOptions, GateResult, AskPayload, AskResult, };
28
+ import type { NotiformerConfig, EventPayload, EventResponse, GateOptions, GateResult, AskPayload, AskResult, SelectPayload, SelectResult } from "./types";
29
+ export type { NotiformerConfig, EventPayload, EventResponse, GateOptions, GateResult, AskPayload, AskResult, SelectPayload, SelectResult, };
30
+ export type { SelectOption } from "./types";
24
31
  export declare class Notiformer {
25
32
  private readonly apiKey;
26
33
  private readonly baseUrl;
@@ -34,49 +41,75 @@ export declare class Notiformer {
34
41
  constructor(config: NotiformerConfig);
35
42
  /**
36
43
  * Send an event and optionally notify recipients.
37
- *
38
- * - Without `recipients`: notifies the project owner + all members
39
- * subscribed to the event's channel.
40
- * - With `recipients`: notifies only the specified email addresses
41
- * (must be project members). Overrides the default behaviour.
42
- *
43
44
  * Never throws by default — returns null if the call fails.
44
45
  */
45
46
  event(payload: EventPayload): Promise<EventResponse | null>;
46
47
  /**
47
48
  * Check whether a feature gate is enabled.
48
49
  * Results are cached locally for 30 seconds by default.
49
- * Returns false if the call fails.
50
+ * Returns false if the call fails or the plan doesn't support gates.
50
51
  */
51
52
  gate(key: string, options?: GateOptions): Promise<boolean>;
52
53
  gateDetails(key: string, options?: GateOptions): Promise<GateResult>;
53
54
  clearGateCache(key?: string): void;
54
55
  /**
55
- * Pause execution and wait for a human to approve or deny from the Notiformer app.
56
+ * Pause execution and wait for a human to approve or deny.
56
57
  *
57
- * Your agent stops here. A push notification is sent to your phone with
58
- * Approve and Deny buttons. The method resolves when you respond,
59
- * or when the timeout expires.
58
+ * A push notification is sent to the Notiformer app with Approve
59
+ * and Deny buttons. The Promise resolves when you respond or when
60
+ * the timeout expires.
60
61
  *
61
- * Never throws by default — returns { approved: false, timedOut: false } on error.
62
+ * Never throws by default.
62
63
  *
63
64
  * @example
64
65
  * const { approved } = await n.ask({
65
66
  * message: `Send campaign to ${count} users?`,
66
67
  * context: `Campaign: Black Friday · ${count} recipients`,
67
- * details: `CHANGES\n• Segment: paying users only\n• Template: bf-2026\n\nROLLBACK\nSet campaign.active = false`,
68
68
  * timeout: 300,
69
69
  * });
70
70
  * if (approved) await sendEmails();
71
71
  */
72
72
  ask(payload: AskPayload): Promise<AskResult>;
73
+ /**
74
+ * Pause execution and wait for a human to choose from multiple options.
75
+ *
76
+ * Unlike ask() which returns a boolean, select() returns the `value`
77
+ * string of the chosen option. Use it when you need more than two actions.
78
+ *
79
+ * Uses the same monthly quota as ask().
80
+ * Never throws by default.
81
+ *
82
+ * @example
83
+ * const { selected } = await n.select({
84
+ * message: 'How should I handle the failed payment?',
85
+ * options: [
86
+ * { value: 'retry', label: '🔄 Retry in 1 hour' },
87
+ * { value: 'notify', label: '📧 Notify the customer' },
88
+ * { value: 'cancel', label: '✕ Cancel the order', isDestructive: true },
89
+ * ],
90
+ * fallback: 'notify', // returned automatically on timeout
91
+ * timeout: 300,
92
+ * });
93
+ *
94
+ * if (selected === 'retry') await scheduleRetry();
95
+ * if (selected === 'notify') await sendEmail();
96
+ * if (selected === 'cancel') await cancelOrder();
97
+ */
98
+ select(payload: SelectPayload): Promise<SelectResult>;
73
99
  private pollAsk;
100
+ private pollSelect;
74
101
  private sleep;
102
+ /**
103
+ * If the API response includes an upgradeUrl (quota exceeded),
104
+ * print a visible warning in the developer's console.
105
+ */
106
+ private warnUpgrade;
75
107
  private failAsk;
108
+ private failSelect;
109
+ private fail;
76
110
  private post;
77
111
  private get;
78
112
  private request;
79
- private fail;
80
113
  private friendlyNetworkError;
81
114
  }
82
115
  export default Notiformer;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,UAAU,EACV,UAAU,EACV,SAAS,EACV,MAAM,SAAS,CAAC;AAIjB,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,UAAU,EACV,UAAU,EACV,SAAS,GACV,CAAC;AAOF,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAA8B;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;gBAE5B,MAAM,EAAE,gBAAgB;IAoCpC;;;;;;;;;OASG;IACG,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAiDjE;;;;OAIG;IACG,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAsB9D,WAAW,CACf,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,UAAU,CAAC;IAKtB,cAAc,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAIlC;;;;;;;;;;;;;;;;;OAiBG;IACG,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;YAgDpC,OAAO;IAiDrB,OAAO,CAAC,KAAK;IAIb,OAAO,CAAC,OAAO;YAOD,IAAI;YAIJ,GAAG;YAIH,OAAO;IAmBrB,OAAO,CAAC,IAAI;IAOZ,OAAO,CAAC,oBAAoB;CAmB7B;AAED,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,UAAU,EACV,UAAU,EACV,SAAS,EACT,aAAa,EACb,YAAY,EACb,MAAM,SAAS,CAAC;AAIjB,YAAY,EACV,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,WAAW,EACX,UAAU,EACV,UAAU,EACV,SAAS,EACT,aAAa,EACb,YAAY,GACb,CAAC;AACF,YAAY,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAO5C,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;IACvC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAA8B;IACvD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;gBAE5B,MAAM,EAAE,gBAAgB;IAwCpC;;;OAGG;IACG,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAsDjE;;;;OAIG;IACG,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,OAAO,CAAC;IAkC9D,WAAW,CACf,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,UAAU,CAAC;IAKtB,cAAc,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI;IAQlC;;;;;;;;;;;;;;;;OAgBG;IACG,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC;IAoDlD;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACG,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;YA0E7C,OAAO;YAiDP,UAAU;IAmDxB,OAAO,CAAC,KAAK;IAIb;;;OAGG;IACH,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,IAAI;YAOE,IAAI;YAIJ,GAAG;YAIH,OAAO;IAmBrB,OAAO,CAAC,oBAAoB;CAmB7B;AAED,eAAe,UAAU,CAAC"}
package/dist/index.js CHANGED
@@ -9,15 +9,21 @@
9
9
  *
10
10
  * const n = new Notiformer({ apiKey: 'ntf_live_...' });
11
11
  *
12
- * // Notify everyone subscribed to the 'payments' channel
12
+ * // Fire-and-forget alert
13
13
  * await n.event({ channel: 'payments', event: 'new_sale', notify: true });
14
14
  *
15
- * // Notify specific people only
16
- * await n.event({
17
- * channel: 'payments',
18
- * event: 'refund_requested',
19
- * notify: true,
20
- * recipients: ['admin@company.com', 'billing@company.com'],
15
+ * // Block and wait for approval
16
+ * const { approved } = await n.ask({ message: 'Deploy to prod?' });
17
+ *
18
+ * // Block and wait for a choice from multiple options
19
+ * const { selected } = await n.select({
20
+ * message: 'How to handle the failed payment?',
21
+ * options: [
22
+ * { value: 'retry', label: '🔄 Retry in 1 hour' },
23
+ * { value: 'notify', label: '📧 Notify customer' },
24
+ * { value: 'cancel', label: '✕ Cancel order', isDestructive: true },
25
+ * ],
26
+ * fallback: 'notify',
21
27
  * });
22
28
  */
23
29
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -59,14 +65,11 @@ class Notiformer {
59
65
  "└─────────────────────────────────────────────────────┘\n");
60
66
  }
61
67
  }
68
+ // ─────────────────────────────────────────────────────────────
69
+ // event()
70
+ // ─────────────────────────────────────────────────────────────
62
71
  /**
63
72
  * Send an event and optionally notify recipients.
64
- *
65
- * - Without `recipients`: notifies the project owner + all members
66
- * subscribed to the event's channel.
67
- * - With `recipients`: notifies only the specified email addresses
68
- * (must be project members). Overrides the default behaviour.
69
- *
70
73
  * Never throws by default — returns null if the call fails.
71
74
  */
72
75
  async event(payload) {
@@ -104,6 +107,7 @@ class Notiformer {
104
107
  const data = await res
105
108
  .json()
106
109
  .catch(() => ({ error: `HTTP ${res.status}` }));
110
+ this.warnUpgrade(data);
107
111
  return this.fail(new Error(`[notiformer] ${(_b = data.error) !== null && _b !== void 0 ? _b : res.statusText}`));
108
112
  }
109
113
  return (await res.json());
@@ -112,10 +116,13 @@ class Notiformer {
112
116
  return this.fail(this.friendlyNetworkError(err));
113
117
  }
114
118
  }
119
+ // ─────────────────────────────────────────────────────────────
120
+ // gate()
121
+ // ─────────────────────────────────────────────────────────────
115
122
  /**
116
123
  * Check whether a feature gate is enabled.
117
124
  * Results are cached locally for 30 seconds by default.
118
- * Returns false if the call fails.
125
+ * Returns false if the call fails or the plan doesn't support gates.
119
126
  */
120
127
  async gate(key, options = {}) {
121
128
  var _a, _b;
@@ -130,8 +137,15 @@ class Notiformer {
130
137
  return cached;
131
138
  try {
132
139
  const res = await this.get(`/v1/gates/${encodeURIComponent(key)}`);
133
- if (!res.ok)
140
+ if (!res.ok) {
141
+ // 403 = Feature Gates not available on this plan
142
+ if (res.status === 403) {
143
+ const data = await res.json().catch(() => ({}));
144
+ console.warn(`[notiformer] gate("${key}") — Feature Gates require Pro or Business plan.` +
145
+ (data.upgradeUrl ? `\n→ Upgrade: ${data.upgradeUrl}` : ""));
146
+ }
134
147
  return fallback;
148
+ }
135
149
  const data = (await res.json());
136
150
  this.gateCache.set(key, data.enabled, ttl);
137
151
  return data.enabled;
@@ -147,20 +161,22 @@ class Notiformer {
147
161
  clearGateCache(key) {
148
162
  key ? this.gateCache.delete(key) : this.gateCache.clear();
149
163
  }
164
+ // ─────────────────────────────────────────────────────────────
165
+ // ask()
166
+ // ─────────────────────────────────────────────────────────────
150
167
  /**
151
- * Pause execution and wait for a human to approve or deny from the Notiformer app.
168
+ * Pause execution and wait for a human to approve or deny.
152
169
  *
153
- * Your agent stops here. A push notification is sent to your phone with
154
- * Approve and Deny buttons. The method resolves when you respond,
155
- * or when the timeout expires.
170
+ * A push notification is sent to the Notiformer app with Approve
171
+ * and Deny buttons. The Promise resolves when you respond or when
172
+ * the timeout expires.
156
173
  *
157
- * Never throws by default — returns { approved: false, timedOut: false } on error.
174
+ * Never throws by default.
158
175
  *
159
176
  * @example
160
177
  * const { approved } = await n.ask({
161
178
  * message: `Send campaign to ${count} users?`,
162
179
  * context: `Campaign: Black Friday · ${count} recipients`,
163
- * details: `CHANGES\n• Segment: paying users only\n• Template: bf-2026\n\nROLLBACK\nSet campaign.active = false`,
164
180
  * timeout: 300,
165
181
  * });
166
182
  * if (approved) await sendEmails();
@@ -196,49 +212,183 @@ class Notiformer {
196
212
  const data = await createRes
197
213
  .json()
198
214
  .catch(() => ({ error: `HTTP ${createRes.status}` }));
215
+ this.warnUpgrade(data);
199
216
  return this.failAsk(new Error(`[notiformer] ${(_c = data.error) !== null && _c !== void 0 ? _c : createRes.statusText}`));
200
217
  }
201
218
  const created = (await createRes.json());
202
219
  return this.pollAsk(created.id, new Date(created.expiresAt).getTime());
203
220
  }
221
+ // ─────────────────────────────────────────────────────────────
222
+ // select()
223
+ // ─────────────────────────────────────────────────────────────
224
+ /**
225
+ * Pause execution and wait for a human to choose from multiple options.
226
+ *
227
+ * Unlike ask() which returns a boolean, select() returns the `value`
228
+ * string of the chosen option. Use it when you need more than two actions.
229
+ *
230
+ * Uses the same monthly quota as ask().
231
+ * Never throws by default.
232
+ *
233
+ * @example
234
+ * const { selected } = await n.select({
235
+ * message: 'How should I handle the failed payment?',
236
+ * options: [
237
+ * { value: 'retry', label: '🔄 Retry in 1 hour' },
238
+ * { value: 'notify', label: '📧 Notify the customer' },
239
+ * { value: 'cancel', label: '✕ Cancel the order', isDestructive: true },
240
+ * ],
241
+ * fallback: 'notify', // returned automatically on timeout
242
+ * timeout: 300,
243
+ * });
244
+ *
245
+ * if (selected === 'retry') await scheduleRetry();
246
+ * if (selected === 'notify') await sendEmail();
247
+ * if (selected === 'cancel') await cancelOrder();
248
+ */
249
+ async select(payload) {
250
+ var _a, _b;
251
+ if (!payload.message)
252
+ throw new Error("[notiformer] select.message is required.");
253
+ if (!payload.options || payload.options.length < 2) {
254
+ throw new Error("[notiformer] select.options requires at least 2 options.");
255
+ }
256
+ if (payload.options.length > 6) {
257
+ throw new Error("[notiformer] select.options supports a maximum of 6 options.");
258
+ }
259
+ if (this.isPlaceholder) {
260
+ console.warn('[notiformer] select() not sent — using example key "ntf_live_test".\n' +
261
+ "→ Replace it with your real key from https://app.notiformer.com/projects");
262
+ return { selected: null, timedOut: false, respondedAt: null };
263
+ }
264
+ if (this.silent)
265
+ return { selected: null, timedOut: false, respondedAt: null };
266
+ // Local validation: fallback must be a valid option value
267
+ if (payload.fallback !== undefined) {
268
+ const validValues = payload.options.map((o) => o.value);
269
+ if (!validValues.includes(payload.fallback)) {
270
+ throw new Error(`[notiformer] select.fallback "${payload.fallback}" must match one of the option values: ` +
271
+ validValues.join(", "));
272
+ }
273
+ }
274
+ const body = {
275
+ message: payload.message,
276
+ options: payload.options,
277
+ timeout: (_a = payload.timeout) !== null && _a !== void 0 ? _a : 300,
278
+ };
279
+ if (payload.context !== undefined)
280
+ body.context = payload.context;
281
+ if (payload.details !== undefined)
282
+ body.details = payload.details;
283
+ if (payload.fallback !== undefined)
284
+ body.fallback = payload.fallback;
285
+ let createRes;
286
+ try {
287
+ createRes = await this.post("/v1/select", body);
288
+ }
289
+ catch (err) {
290
+ return this.failSelect(this.friendlyNetworkError(err));
291
+ }
292
+ if (!createRes.ok) {
293
+ const data = await createRes
294
+ .json()
295
+ .catch(() => ({ error: `HTTP ${createRes.status}` }));
296
+ this.warnUpgrade(data);
297
+ return this.failSelect(new Error(`[notiformer] ${(_b = data.error) !== null && _b !== void 0 ? _b : createRes.statusText}`));
298
+ }
299
+ const created = (await createRes.json());
300
+ return this.pollSelect(created.id, new Date(created.expiresAt).getTime());
301
+ }
302
+ // ─────────────────────────────────────────────────────────────
303
+ // Private: polling
304
+ // ─────────────────────────────────────────────────────────────
204
305
  async pollAsk(askId, expiresAtMs) {
205
306
  var _a, _b;
206
- const POLL_INTERVAL_MS = 2000;
307
+ const POLL_MS = 2000;
308
+ const GRACE_MS = 5000; // matches server grace period
207
309
  while (true) {
208
- await this.sleep(POLL_INTERVAL_MS);
209
- if (Date.now() >= expiresAtMs) {
210
- return { approved: false, timedOut: true, respondedAt: null };
211
- }
310
+ await this.sleep(POLL_MS);
311
+ // ── Poll FIRST, check expiry after ─────────────────────
312
+ // This ensures a response made at the last instant is not
313
+ // incorrectly returned as timedOut.
212
314
  try {
213
315
  const res = await this.get(`/v1/ask/${encodeURIComponent(askId)}`);
214
- if (!res.ok) {
215
- if (res.status >= 400 && res.status < 500) {
216
- const data = await res
217
- .json()
218
- .catch(() => ({ error: `HTTP ${res.status}` }));
219
- return this.failAsk(new Error(`[notiformer] ${(_a = data.error) !== null && _a !== void 0 ? _a : res.statusText}`));
316
+ if (res.ok) {
317
+ const data = (await res.json());
318
+ if (data.status !== "pending") {
319
+ return {
320
+ approved: data.status === "approved",
321
+ timedOut: data.status === "timed_out",
322
+ respondedAt: (_a = data.respondedAt) !== null && _a !== void 0 ? _a : null,
323
+ };
220
324
  }
221
- // 5xx — server error, retry on next interval
222
- continue;
223
325
  }
224
- const data = (await res.json());
225
- if (data.status === "pending")
226
- continue;
227
- return {
228
- approved: data.status === "approved",
229
- timedOut: data.status === "timed_out",
230
- respondedAt: (_b = data.respondedAt) !== null && _b !== void 0 ? _b : null,
231
- };
326
+ else if (res.status >= 400 && res.status < 500) {
327
+ const data = await res
328
+ .json()
329
+ .catch(() => ({ error: `HTTP ${res.status}` }));
330
+ return this.failAsk(new Error(`[notiformer] ${(_b = data.error) !== null && _b !== void 0 ? _b : res.statusText}`));
331
+ }
332
+ // 5xx fall through to expiry check, retry next loop
232
333
  }
233
334
  catch (_c) {
234
- // Network error during a single poll retry until expiry
235
- continue;
335
+ // network error — fall through
336
+ }
337
+ // ── Only declare client-side timeout after grace period ─
338
+ if (Date.now() >= expiresAtMs + GRACE_MS) {
339
+ return { approved: false, timedOut: true, respondedAt: null };
340
+ }
341
+ }
342
+ }
343
+ async pollSelect(selectId, expiresAtMs) {
344
+ var _a, _b, _c;
345
+ const POLL_MS = 2000;
346
+ const GRACE_MS = 5000;
347
+ while (true) {
348
+ await this.sleep(POLL_MS);
349
+ try {
350
+ const res = await this.get(`/v1/select/${encodeURIComponent(selectId)}`);
351
+ if (res.ok) {
352
+ const data = (await res.json());
353
+ if (data.status !== "pending") {
354
+ return {
355
+ selected: (_a = data.selectedValue) !== null && _a !== void 0 ? _a : null,
356
+ timedOut: data.status === "timed_out",
357
+ respondedAt: (_b = data.respondedAt) !== null && _b !== void 0 ? _b : null,
358
+ };
359
+ }
360
+ }
361
+ else if (res.status >= 400 && res.status < 500) {
362
+ const data = await res
363
+ .json()
364
+ .catch(() => ({ error: `HTTP ${res.status}` }));
365
+ return this.failSelect(new Error(`[notiformer] ${(_c = data.error) !== null && _c !== void 0 ? _c : res.statusText}`));
366
+ }
367
+ }
368
+ catch (_d) {
369
+ // network error — fall through
370
+ }
371
+ if (Date.now() >= expiresAtMs + GRACE_MS) {
372
+ return { selected: null, timedOut: true, respondedAt: null };
236
373
  }
237
374
  }
238
375
  }
376
+ // ─────────────────────────────────────────────────────────────
377
+ // Private: helpers
378
+ // ─────────────────────────────────────────────────────────────
239
379
  sleep(ms) {
240
380
  return new Promise((resolve) => setTimeout(resolve, ms));
241
381
  }
382
+ /**
383
+ * If the API response includes an upgradeUrl (quota exceeded),
384
+ * print a visible warning in the developer's console.
385
+ */
386
+ warnUpgrade(data) {
387
+ if (data === null || data === void 0 ? void 0 : data.upgradeUrl) {
388
+ console.warn(`[notiformer] Plan limit reached. Upgrade your plan to continue:\n` +
389
+ `→ ${data.upgradeUrl}`);
390
+ }
391
+ }
242
392
  failAsk(error) {
243
393
  var _a;
244
394
  this.logger.error(error.message);
@@ -247,6 +397,22 @@ class Notiformer {
247
397
  throw error;
248
398
  return { approved: false, timedOut: false, respondedAt: null };
249
399
  }
400
+ failSelect(error) {
401
+ var _a;
402
+ this.logger.error(error.message);
403
+ (_a = this.onError) === null || _a === void 0 ? void 0 : _a.call(this, error);
404
+ if (this.throwOnError)
405
+ throw error;
406
+ return { selected: null, timedOut: false, respondedAt: null };
407
+ }
408
+ fail(error) {
409
+ var _a;
410
+ this.logger.error(error.message);
411
+ (_a = this.onError) === null || _a === void 0 ? void 0 : _a.call(this, error);
412
+ if (this.throwOnError)
413
+ throw error;
414
+ return null;
415
+ }
250
416
  async post(path, body) {
251
417
  return this.request(path, { method: "POST", body: JSON.stringify(body) });
252
418
  }
@@ -264,7 +430,7 @@ class Notiformer {
264
430
  headers: {
265
431
  "Content-Type": "application/json",
266
432
  Authorization: `Bearer ${this.apiKey}`,
267
- "X-SDK-Version": "1.1.0",
433
+ "X-SDK-Version": "1.1.2",
268
434
  ...((_a = init.headers) !== null && _a !== void 0 ? _a : {}),
269
435
  },
270
436
  });
@@ -273,14 +439,6 @@ class Notiformer {
273
439
  clearTimeout(timer);
274
440
  }
275
441
  }
276
- fail(error) {
277
- var _a;
278
- this.logger.error(error.message);
279
- (_a = this.onError) === null || _a === void 0 ? void 0 : _a.call(this, error);
280
- if (this.throwOnError)
281
- throw error;
282
- return null;
283
- }
284
442
  friendlyNetworkError(err) {
285
443
  const msg = err instanceof Error ? err.message.toLowerCase() : "";
286
444
  if (msg.includes("aborted") || msg.includes("timeout")) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -25,9 +25,6 @@ export interface EventPayload {
25
25
  * If set, **only** these emails receive notifications — the default
26
26
  * (owner + channel subscribers) is ignored.
27
27
  *
28
- * Each email must belong to a member of your project
29
- * (added from the Notiformer dashboard).
30
- *
31
28
  * Plan limits: Free=1, Starter=3, Pro=10, Business=30.
32
29
  *
33
30
  * @example
@@ -57,12 +54,11 @@ export interface AskPayload {
57
54
  context?: string;
58
55
  /**
59
56
  * Optional long-form text shown in the approval detail screen of the
60
- * Notiformer app. Supports newlines — use \n to structure content
61
- * (changelogs, diffs, SQL queries, affected services, etc.).
57
+ * Notiformer app. Supports newlines — use \n to structure content.
62
58
  * Max 10,000 characters.
63
59
  *
64
60
  * @example
65
- * details: `CHANGES\n• Fix auth bug\n• Add details field\n\nROLLBACK\n./rollback.sh v1.0.9`
61
+ * details: `CHANGES\n• Fix auth bug\n\nROLLBACK\n./rollback.sh v1.0.9`
66
62
  */
67
63
  details?: string;
68
64
  /**
@@ -85,5 +81,69 @@ export interface AskResult {
85
81
  /** ISO timestamp of when the user responded. null if timed out. */
86
82
  respondedAt: string | null;
87
83
  }
84
+ export interface SelectOption {
85
+ /**
86
+ * The value returned in `result.selected` when this option is chosen.
87
+ * Max 50 chars. Use a short descriptive string.
88
+ *
89
+ * @example 'deploy' | 'rollback' | 'cancel'
90
+ */
91
+ value: string;
92
+ /**
93
+ * The button label shown in the Notiformer app.
94
+ * Max 80 chars. Supports emoji.
95
+ *
96
+ * @example '🚀 Deploy to production'
97
+ */
98
+ label: string;
99
+ /**
100
+ * If true, the button is styled destructively (red) in the mobile app.
101
+ * Use for dangerous or irreversible actions.
102
+ */
103
+ isDestructive?: boolean;
104
+ }
105
+ export interface SelectPayload {
106
+ /**
107
+ * The question shown as the push notification title. Required.
108
+ */
109
+ message: string;
110
+ /**
111
+ * The options shown as buttons in the Notiformer app.
112
+ * Minimum 2, maximum 6 options.
113
+ */
114
+ options: SelectOption[];
115
+ /** Optional detail shown in the notification body (max 500 chars). */
116
+ context?: string;
117
+ /**
118
+ * Optional long-form text shown in the approval detail screen.
119
+ * Supports newlines. Max 10,000 chars.
120
+ */
121
+ details?: string;
122
+ /**
123
+ * Seconds to wait before applying the fallback.
124
+ * Default: 300. Maximum depends on your plan.
125
+ */
126
+ timeout?: number;
127
+ /**
128
+ * The option value to return automatically if nobody responds.
129
+ * Must match one of the option values exactly.
130
+ * If omitted, `selected` is null on timeout.
131
+ *
132
+ * @example 'cancel' // safe default
133
+ */
134
+ fallback?: string;
135
+ }
136
+ export interface SelectResult {
137
+ /**
138
+ * The value string of the option the user selected,
139
+ * or the fallback value on timeout.
140
+ * Null if timed out with no fallback set.
141
+ */
142
+ selected: string | null;
143
+ /** True if nobody responded before the timeout expired. */
144
+ timedOut: boolean;
145
+ /** ISO timestamp of when the user responded. null if timed out. */
146
+ respondedAt: string | null;
147
+ }
88
148
  export type LogLevel = "debug" | "info" | "warn" | "error" | "none" | "silent";
89
149
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;;;;OAaG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;;OAQG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,kFAAkF;IAClF,QAAQ,EAAE,OAAO,CAAC;IAClB,2DAA2D;IAC3D,QAAQ,EAAE,OAAO,CAAC;IAClB,mEAAmE;IACnE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,OAAO,EAAE,MAAM,CAAC;IAChB,wBAAwB;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACjD,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED,MAAM,WAAW,SAAS;IACxB,kFAAkF;IAClF,QAAQ,EAAE,OAAO,CAAC;IAClB,2DAA2D;IAC3D,QAAQ,EAAE,OAAO,CAAC;IAClB,mEAAmE;IACnE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAMD,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,sEAAsE;IACtE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,2DAA2D;IAC3D,QAAQ,EAAE,OAAO,CAAC;IAClB,mEAAmE;IACnE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AAED,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "notiformer",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Human-in-the-loop approval gates and real-time push notifications for AI agents",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",