deepline 0.3.109 → 0.3.110

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.
@@ -200,7 +200,7 @@ export const SDK_RELEASE = {
200
200
  // getters keep their established compatibility behavior.
201
201
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
202
202
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
203
- version: '0.3.109',
203
+ version: '0.3.110',
204
204
  updateSummary:
205
205
  'Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.',
206
206
  packageCapabilities: {
@@ -903,6 +903,24 @@
903
903
  "failureAlert": "convex_action_wrapper",
904
904
  "domainReportRequired": true
905
905
  },
906
+ {
907
+ "id": "convex.process-notification-usage-refreshes",
908
+ "name": "Process product notification usage refreshes",
909
+ "scheduler": "convex_cron",
910
+ "cadence": "every 60s",
911
+ "timezone": "UTC",
912
+ "source": "packages/convex/crons.ts",
913
+ "locator": "process notification usage refreshes",
914
+ "owner": "platform",
915
+ "criticality": "critical",
916
+ "purpose": "Recover missed usage-notification wakes and coalesce latest-state observations per organization.",
917
+ "successDefinition": "Queued usage wakes are observed and retired after their notification transaction commits; an empty queue is a valid no-op.",
918
+ "failureDefinition": "The worker cannot read or commit a batch, leaving usage wakes for the next retry and reporting the failure.",
919
+ "startGraceMs": 30000,
920
+ "runTimeoutMs": 1800000,
921
+ "failureAlert": "convex_action_wrapper",
922
+ "domainReportRequired": true
923
+ },
906
924
  {
907
925
  "id": "convex.fail-stale-play-deployments",
908
926
  "name": "Fail stale play deployments",
@@ -18,6 +18,7 @@ export const MANUALLY_RUNNABLE_CONVEX_CRON_JOB_IDS = [
18
18
  'convex.monitor-deployment-job-tick',
19
19
  'convex.provider-monitor-global-audit-daily',
20
20
  'convex.recover-notification-delivery-work',
21
+ 'convex.process-notification-usage-refreshes',
21
22
  'convex.observe-operational-queue-health',
22
23
  'convex.cleanup-expired-notification-audit-records',
23
24
  'convex.fail-stale-play-deployments',
@@ -68,6 +68,8 @@ export type ProductNotificationPresentation = {
68
68
  subjectKind: string;
69
69
  subjectId: string;
70
70
  severity?: ProductNotificationSeverity;
71
+ /** Selects an alternate email layout; absent means the standard one. */
72
+ emailFormat?: 'thank_you';
71
73
  };
72
74
 
73
75
  type ProductNotificationRuleInput = {
@@ -233,6 +235,31 @@ function formatCredits(value: number): string {
233
235
  return value.toLocaleString('en-US', { maximumFractionDigits: 2 });
234
236
  }
235
237
 
238
+ export const DEEPLINE_DOCS_URL = 'https://deepline.com/docs';
239
+
240
+ /** Points a newly paying workspace at the docs to get value from Deepline. */
241
+ const LEARN_DEEPLINE_DETAIL: NotificationText = [
242
+ 'To learn more about how to use Deepline, ',
243
+ { link: 'check out the docs', href: DEEPLINE_DOCS_URL },
244
+ '.',
245
+ ];
246
+
247
+ export const DEEPLINE_DEMO_URL =
248
+ 'https://calendly.com/d/csdb-85z-t6z/deepline-demo';
249
+
250
+ /**
251
+ * Growth welcomes lead with a demo call. One detail per line so the links read
252
+ * as a short list with the app's Lucide icons.
253
+ */
254
+ const GROWTH_LEARN_MORE_DETAILS: NotificationText[] = [
255
+ 'To learn more about Deepline:',
256
+ [{ icon: 'calendar' }, { link: 'Schedule a demo', href: DEEPLINE_DEMO_URL }],
257
+ [
258
+ { icon: 'book-open' },
259
+ { link: 'Check out the docs', href: DEEPLINE_DOCS_URL },
260
+ ],
261
+ ];
262
+
236
263
  /** One canonical registry owns every notification type's runtime and UI metadata. */
237
264
  const PRODUCT_NOTIFICATION_TYPE_REGISTRY = [
238
265
  {
@@ -546,6 +573,108 @@ const PRODUCT_NOTIFICATION_TYPE_REGISTRY = [
546
573
  },
547
574
  },
548
575
  },
576
+ {
577
+ id: 'credits.purchased',
578
+ eventName: ProductNotificationEvent.CreditsPurchaseCompleted,
579
+ requiredFilter: {},
580
+ allowedTriggers: ['threshold_crossing'],
581
+ catalog: {
582
+ name: 'Credit purchase thank-you',
583
+ category: 'credits',
584
+ description:
585
+ 'Thank the workspace when a credit purchase of at least the chosen size completes.',
586
+ allowMultiple: false,
587
+ editor: {
588
+ kind: 'threshold',
589
+ label: 'Minimum credits',
590
+ defaultThreshold: 500,
591
+ minimum: 0,
592
+ suffix: 'credits',
593
+ },
594
+ },
595
+ rule: {
596
+ default: { ruleKey: 'default.credits_purchased', groupKey: 'default' },
597
+ create: (input, base, editor) => ({
598
+ ...base,
599
+ repeatMode: 'every_match',
600
+ trigger: {
601
+ kind: 'threshold_crossing',
602
+ direction: 'at_or_above',
603
+ threshold: input.threshold ?? defaultThreshold(editor),
604
+ unit: 'credits',
605
+ },
606
+ }),
607
+ validate: (trigger) => {
608
+ if (
609
+ trigger.kind !== 'threshold_crossing' ||
610
+ trigger.direction !== 'at_or_above' ||
611
+ trigger.unit !== 'credits'
612
+ ) {
613
+ throw new Error(
614
+ 'Credit purchase rules must reach a purchased credit amount.',
615
+ );
616
+ }
617
+ if (!Number.isFinite(trigger.threshold) || trigger.threshold < 0) {
618
+ throw new Error(
619
+ 'Credit purchase thresholds must be finite and nonnegative.',
620
+ );
621
+ }
622
+ },
623
+ present: ({ payload }) => ({
624
+ title: 'Thank you for your credit purchase',
625
+ summary: [
626
+ 'Your ',
627
+ { workspace: true },
628
+ ' added ',
629
+ { strong: `${formatCredits(Number(payload.credits))} credits` },
630
+ '. Thank you for supporting Deepline.',
631
+ ],
632
+ details: [LEARN_DEEPLINE_DETAIL],
633
+ emailFormat: 'thank_you',
634
+ targetUrl: String(payload.targetUrl),
635
+ subjectKind: 'workspace',
636
+ subjectId: String(payload.accountId),
637
+ }),
638
+ },
639
+ },
640
+ {
641
+ id: 'plan.purchased',
642
+ eventName: ProductNotificationEvent.BillingPlanPurchased,
643
+ requiredFilter: {},
644
+ allowedTriggers: ['each_match'],
645
+ catalog: {
646
+ name: 'Plan welcome',
647
+ category: 'credits',
648
+ description: 'Thank the workspace when it purchases a plan.',
649
+ allowMultiple: false,
650
+ editor: { kind: 'none' },
651
+ },
652
+ rule: {
653
+ default: { ruleKey: 'default.plan_purchased', groupKey: 'default' },
654
+ create: (_input, base) => ({
655
+ ...base,
656
+ repeatMode: 'every_match',
657
+ trigger: { kind: 'each_match' },
658
+ }),
659
+ present: ({ payload }) => ({
660
+ title: `Welcome to ${payload.planName}`,
661
+ summary: [
662
+ 'Your ',
663
+ { workspace: true },
664
+ ` is now on the ${payload.planName} plan. Thank you for supporting Deepline.`,
665
+ ],
666
+ // No credit-lifetime claims: they differ by plan.
667
+ details:
668
+ payload.offerKey === 'growth-v1'
669
+ ? GROWTH_LEARN_MORE_DETAILS
670
+ : [LEARN_DEEPLINE_DETAIL],
671
+ emailFormat: 'thank_you',
672
+ targetUrl: String(payload.targetUrl),
673
+ subjectKind: 'workspace',
674
+ subjectId: String(payload.accountId),
675
+ }),
676
+ },
677
+ },
549
678
  {
550
679
  id: 'play.failed',
551
680
  eventName: ProductNotificationEvent.PlayRunCompleted,
@@ -1,7 +1,17 @@
1
1
  /** Type-owned content, independent of email markup and persisted audit strings. */
2
2
  export type NotificationText =
3
3
  | string
4
- | readonly (string | { strong: string } | { workspace: true })[];
4
+ | readonly (
5
+ | string
6
+ | { strong: string }
7
+ | { workspace: true }
8
+ /** An absolute https link authored in code, never customer input. */
9
+ | { link: string; href: string }
10
+ /** A decorative brand icon; plain text omits it. */
11
+ | { icon: NotificationIcon }
12
+ )[];
13
+
14
+ export type NotificationIcon = 'calendar' | 'book-open';
5
15
 
6
16
  export function notificationText(
7
17
  content: NotificationText,
@@ -12,6 +22,8 @@ export function notificationText(
12
22
  .map((part) => {
13
23
  if (typeof part === 'string') return part;
14
24
  if ('strong' in part) return part.strong;
25
+ if ('link' in part) return `${part.link} (${part.href})`;
26
+ if ('icon' in part) return '';
15
27
  return workspaceName ? `workspace ${workspaceName}` : 'workspace';
16
28
  })
17
29
  .join('');
@@ -14,7 +14,7 @@ import {
14
14
  * they create sparse copy-on-write overrides.
15
15
  */
16
16
  export const CURRENT_PRODUCT_NOTIFICATION_DEFAULT_POLICY = {
17
- version: 5,
17
+ version: 6,
18
18
  groups: [
19
19
  {
20
20
  groupKey: 'default',
@@ -32,10 +32,24 @@ type ProductNotificationEmailContent = {
32
32
  details: NotificationText[];
33
33
  targetUrl?: string;
34
34
  severity?: 'success' | 'warning' | 'error';
35
+ /**
36
+ * `thank_you` is the standard layout without the notification group header,
37
+ * with details shown as a compact list instead of divided rows. Kept as its
38
+ * own format so these emails can diverge later.
39
+ */
40
+ emailFormat?: 'thank_you';
35
41
  };
36
42
 
37
43
  const MAX_RECIPIENTS = 50;
38
44
 
45
+ /**
46
+ * Lucide icons rasterized to 48px PNGs in the light-theme muted foreground
47
+ * color, served from `apps/deepline-api/public/brand/email`. Email clients do
48
+ * not render inline SVG.
49
+ */
50
+ export const NOTIFICATION_ICON_BASE_URL =
51
+ 'https://code.deepline.com/brand/email';
52
+
39
53
  const SEVERITY_STYLE: Record<
40
54
  NonNullable<ProductNotificationEmailContent['severity']>,
41
55
  { bg: string; text: string; label: string }
@@ -74,6 +88,14 @@ function unsubscribeUrl(value: string): string {
74
88
  return url.toString();
75
89
  }
76
90
 
91
+ function externalLinkUrl(value: string): string {
92
+ const url = new URL(value);
93
+ if (url.protocol !== 'https:') {
94
+ throw new Error('Notification content links must use HTTPS.');
95
+ }
96
+ return url.toString();
97
+ }
98
+
77
99
  function contentHtml(
78
100
  content: NotificationText,
79
101
  workspaceName?: string,
@@ -84,6 +106,10 @@ function contentHtml(
84
106
  if (typeof part === 'string') return escapeHtml(part);
85
107
  if ('strong' in part)
86
108
  return `<strong>${escapeHtml(part.strong)}</strong>`;
109
+ if ('link' in part)
110
+ return `<a href="${escapeHtml(externalLinkUrl(part.href))}" style="color:#0a0a0a;text-decoration:underline">${escapeHtml(part.link)}</a>`;
111
+ if ('icon' in part)
112
+ return `<img src="${NOTIFICATION_ICON_BASE_URL}/icon-${part.icon}.png" width="16" height="16" alt="" style="display:inline-block;vertical-align:-3px;margin-right:8px;border:0">`;
87
113
  return workspaceName
88
114
  ? `workspace <strong>${escapeHtml(workspaceName)}</strong>`
89
115
  : 'workspace';
@@ -95,8 +121,12 @@ function detailHtml(
95
121
  detail: NotificationText,
96
122
  workspaceName: string | undefined,
97
123
  borderBottom: boolean,
124
+ /** List-style rows: no dividers, tighter spacing. */
125
+ compact = false,
98
126
  ): string {
99
- const border = borderBottom ? ';border-bottom:1px solid #f0f0f0' : '';
127
+ const border =
128
+ borderBottom && !compact ? ';border-bottom:1px solid #f0f0f0' : '';
129
+ const padding = compact ? '5px 0' : '12px 0';
100
130
  if (
101
131
  Array.isArray(detail) &&
102
132
  detail.length === 2 &&
@@ -109,7 +139,7 @@ function detailHtml(
109
139
  const value = typeof detail[1] === 'string' ? detail[1] : detail[1].strong;
110
140
  return `<table role="presentation" width="100%" cellspacing="0" cellpadding="0"><tr><td style="padding:12px 0${border};font-size:14px;color:#0a0a0a">${escapeHtml(detail[0].trimEnd())}</td><td align="right" style="padding:12px 0${border};font-size:14px;color:#0a0a0a${typeof detail[1] === 'string' ? '' : ';font-weight:600'}">${escapeHtml(value)}</td></tr></table>`;
111
141
  }
112
- return `<p style="margin:0;padding:12px 0${border};font-size:14px;color:#0a0a0a">${contentHtml(detail, workspaceName)}</p>`;
142
+ return `<p style="margin:0;padding:${padding}${border};font-size:14px;color:#0a0a0a">${contentHtml(detail, workspaceName)}</p>`;
113
143
  }
114
144
 
115
145
  export function renderProductNotificationEmail(
@@ -155,9 +185,12 @@ export function renderProductNotificationEmail(
155
185
  const oneClickOptOutUrl = input.oneClickUnsubscribeUrl
156
186
  ? unsubscribeUrl(input.oneClickUnsubscribeUrl)
157
187
  : optOutUrl;
188
+ const showGroupHeader = input.content.emailFormat !== 'thank_you';
158
189
  const text = [
159
190
  input.content.title,
160
- `Notification group: ${input.content.groupName}`,
191
+ ...(showGroupHeader
192
+ ? [`Notification group: ${input.content.groupName}`]
193
+ : []),
161
194
  ...(targetUrl ? [`Open Deepline: ${targetUrl}`] : []),
162
195
  '',
163
196
  notificationText(input.content.summary, input.workspaceName),
@@ -184,16 +217,25 @@ export function renderProductNotificationEmail(
184
217
  `<td align="left" valign="middle"><a href="${escapeHtml(logoUrl)}" style="display:inline-block;text-decoration:none"><img src="https://code.deepline.com/brand/logo_full_dark.png" width="120" height="auto" alt="Deepline" style="display:block;border:0"></a></td>`,
185
218
  `<td align="right" valign="middle">${severityBadge}</td>`,
186
219
  '</tr></table>',
187
- '<p style="margin:0 0 16px">',
188
- `<span style="font-size:12px;color:#a3a3a3;letter-spacing:.06em;text-transform:uppercase">${escapeHtml(input.content.groupName)}</span>`,
189
- '</p>',
220
+ ...(showGroupHeader
221
+ ? [
222
+ '<p style="margin:0 0 16px">',
223
+ `<span style="font-size:12px;color:#a3a3a3;letter-spacing:.06em;text-transform:uppercase">${escapeHtml(input.content.groupName)}</span>`,
224
+ '</p>',
225
+ ]
226
+ : []),
190
227
  `<h1 style="margin:0 0 8px;font-size:22px;font-weight:600;color:#0a0a0a;line-height:1.3;letter-spacing:-0.02em">${escapeHtml(input.content.title)}</h1>`,
191
228
  `<p style="margin:0 0 24px;font-size:15px;line-height:1.6;color:#525252">${contentHtml(input.content.summary, input.workspaceName)}</p>`,
192
229
  ...(detailCount > 0
193
230
  ? [
194
- '<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border:1px solid #e5e5e5;border-radius:10px;margin:0 0 28px"><tr><td style="padding:4px 18px">',
231
+ `<table role="presentation" width="100%" cellspacing="0" cellpadding="0" style="border:1px solid #e5e5e5;border-radius:10px;margin:0 0 28px"><tr><td style="padding:${showGroupHeader ? '4px' : '11px'} 18px">`,
195
232
  ...input.content.details.map((detail, index) =>
196
- detailHtml(detail, input.workspaceName, index < detailCount - 1),
233
+ detailHtml(
234
+ detail,
235
+ input.workspaceName,
236
+ index < detailCount - 1,
237
+ !showGroupHeader,
238
+ ),
197
239
  ),
198
240
  '</td></tr></table>',
199
241
  ]
@@ -15,7 +15,9 @@ export const PRODUCT_NOTIFICATION_MAX_SOURCE_CLOCK_SKEW_MS = 5 * 60 * 1_000;
15
15
 
16
16
  export const ProductNotificationEvent = {
17
17
  CreditsBalanceChanged: 'credits.balance.changed',
18
+ CreditsPurchaseCompleted: 'credits.purchase.completed',
18
19
  CreditsUsageRecorded: 'credits.usage.recorded',
20
+ BillingPlanPurchased: 'billing.plan.purchased',
19
21
  MonitorLifecycleChanged: 'monitor.lifecycle.changed',
20
22
  PlayLifecycleChanged: 'play.lifecycle.changed',
21
23
  PlayRunCompleted: 'play.run.completed',
@@ -31,6 +33,7 @@ export type ProductNotificationEvent =
31
33
 
32
34
  export const ProductNotificationProducerKey = {
33
35
  BillingCredits: 'billing.credits',
36
+ BillingPlans: 'billing.plans',
34
37
  BillingUsage: 'billing.usage',
35
38
  MonitorsLifecycle: 'monitors.lifecycle',
36
39
  PlaysLifecycle: 'plays.lifecycle',
@@ -53,6 +56,8 @@ export const PRODUCT_NOTIFICATION_EVENT_NAMES = [
53
56
  ProductNotificationEvent.PlayRunCompleted,
54
57
  ProductNotificationEvent.WebhookDeliveryCompleted,
55
58
  ProductNotificationEvent.CreditsUsageRecorded,
59
+ ProductNotificationEvent.CreditsPurchaseCompleted,
60
+ ProductNotificationEvent.BillingPlanPurchased,
56
61
  ] as const;
57
62
 
58
63
  export const PRODUCT_NOTIFICATION_COMPATIBILITY_EVENT_NAMES = [
@@ -175,6 +180,33 @@ export const PRODUCT_NOTIFICATION_EVENT_DEFINITIONS: readonly ProductNotificatio
175
180
  allowedTriggers: ['threshold_crossing'],
176
181
  allowedThresholdUnits: ['percent', 'usage'],
177
182
  },
183
+ {
184
+ // One paid credit purchase (manual or automatic recharge) settled.
185
+ eventName: ProductNotificationEvent.CreditsPurchaseCompleted,
186
+ producerKey: ProductNotificationProducerKey.BillingCredits,
187
+ requiredFacts: {
188
+ accountId: 'string',
189
+ credits: 'number',
190
+ targetUrl: 'string',
191
+ },
192
+ orderingSubjectFact: 'accountId',
193
+ allowedTriggers: ['threshold_crossing'],
194
+ allowedThresholdUnits: ['credits'],
195
+ },
196
+ {
197
+ // The workspace moved onto a paid acquisition offer.
198
+ eventName: ProductNotificationEvent.BillingPlanPurchased,
199
+ producerKey: ProductNotificationProducerKey.BillingPlans,
200
+ requiredFacts: {
201
+ accountId: 'string',
202
+ offerKey: 'string',
203
+ planName: 'string',
204
+ targetUrl: 'string',
205
+ },
206
+ optionalFacts: { previousOfferKey: 'string' },
207
+ orderingSubjectFact: 'accountId',
208
+ allowedTriggers: ['each_match'],
209
+ },
178
210
  ...PRODUCT_NOTIFICATION_COMPATIBILITY_EVENT_NAMES.map((eventName) => ({
179
211
  eventName,
180
212
  producerKey: ProductNotificationProducerKey.LegacyProductNotifications,
@@ -396,6 +428,13 @@ export function adaptProductNotificationObservation(input: {
396
428
  unit: 'credits',
397
429
  };
398
430
  }
431
+ if (event.eventName === ProductNotificationEvent.CreditsPurchaseCompleted) {
432
+ return {
433
+ kind: 'threshold_state',
434
+ currentValue: Number(event.payload.credits),
435
+ unit: 'credits',
436
+ };
437
+ }
399
438
  const limit = Number(event.payload.limit);
400
439
  if (event.eventName === ProductNotificationEvent.CreditsUsageRecorded) {
401
440
  if (!(limit > 0)) throw new Error('Monthly usage limit must be positive.');
package/dist/cli/index.js CHANGED
@@ -1298,7 +1298,7 @@ var SDK_RELEASE = {
1298
1298
  // getters keep their established compatibility behavior.
1299
1299
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
1300
1300
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
1301
- version: "0.3.109",
1301
+ version: "0.3.110",
1302
1302
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
1303
1303
  packageCapabilities: {
1304
1304
  updatePreferences: 1
@@ -1271,7 +1271,7 @@ var SDK_RELEASE = {
1271
1271
  // getters keep their established compatibility behavior.
1272
1272
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
1273
1273
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
1274
- version: "0.3.109",
1274
+ version: "0.3.110",
1275
1275
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
1276
1276
  packageCapabilities: {
1277
1277
  updatePreferences: 1
package/dist/index.d.mts CHANGED
@@ -318,7 +318,17 @@ type NotificationText = string | readonly (string | {
318
318
  strong: string;
319
319
  } | {
320
320
  workspace: true;
321
+ }
322
+ /** An absolute https link authored in code, never customer input. */
323
+ | {
324
+ link: string;
325
+ href: string;
326
+ }
327
+ /** A decorative brand icon; plain text omits it. */
328
+ | {
329
+ icon: NotificationIcon;
321
330
  })[];
331
+ type NotificationIcon = 'calendar' | 'book-open';
322
332
 
323
333
  /**
324
334
  * The sections the configuration surface skims by. Declared here so UI, API,
@@ -368,6 +378,8 @@ type ProductNotificationPresentation = {
368
378
  subjectKind: string;
369
379
  subjectId: string;
370
380
  severity?: ProductNotificationSeverity;
381
+ /** Selects an alternate email layout; absent means the standard one. */
382
+ emailFormat?: 'thank_you';
371
383
  };
372
384
  type ProductNotificationRuleInput = {
373
385
  ruleKey: string;
@@ -698,6 +710,143 @@ declare const PRODUCT_NOTIFICATION_TYPE_REGISTRY: readonly [{
698
710
  severity: "warning";
699
711
  };
700
712
  };
713
+ }, {
714
+ readonly id: "credits.purchased";
715
+ readonly eventName: "credits.purchase.completed";
716
+ readonly requiredFilter: {};
717
+ readonly allowedTriggers: ["threshold_crossing"];
718
+ readonly catalog: {
719
+ readonly name: "Credit purchase thank-you";
720
+ readonly category: "credits";
721
+ readonly description: "Thank the workspace when a credit purchase of at least the chosen size completes.";
722
+ readonly allowMultiple: false;
723
+ readonly editor: {
724
+ readonly kind: "threshold";
725
+ readonly label: "Minimum credits";
726
+ readonly defaultThreshold: 500;
727
+ readonly minimum: 0;
728
+ readonly suffix: "credits";
729
+ };
730
+ };
731
+ readonly rule: {
732
+ readonly default: {
733
+ readonly ruleKey: "default.credits_purchased";
734
+ readonly groupKey: "default";
735
+ };
736
+ readonly create: (input: ProductNotificationRuleInput, base: ProductNotificationRuleBase, editor: {
737
+ kind: "threshold";
738
+ label: string;
739
+ defaultThreshold: number;
740
+ minimum: number;
741
+ maximum?: number;
742
+ suffix?: string;
743
+ /**
744
+ * When set, editors show and accept `remainingOf - threshold` (for
745
+ * example percent remaining) while the stored trigger stays in the
746
+ * runtime's "used" terms. Bounds and defaults are stored values.
747
+ */
748
+ remainingOf?: number;
749
+ } | {
750
+ kind: "play_failure";
751
+ defaultFailureCount: number;
752
+ } | {
753
+ kind: "play_scope";
754
+ } | {
755
+ kind: "none";
756
+ }) => {
757
+ repeatMode: "every_match";
758
+ trigger: {
759
+ kind: "threshold_crossing";
760
+ direction: "at_or_above";
761
+ threshold: number;
762
+ unit: string;
763
+ };
764
+ groupKey: string;
765
+ enabled: boolean;
766
+ conditionGeneration?: number | undefined;
767
+ ruleKey: string;
768
+ notificationType: string;
769
+ eventName: string;
770
+ eventFilter: ProductNotificationFacts;
771
+ };
772
+ readonly validate: (trigger: ProductNotificationTrigger) => void;
773
+ readonly present: ({ payload }: {
774
+ payload: ProductNotificationFacts;
775
+ trigger: ProductNotificationTrigger;
776
+ occurredAt?: number;
777
+ }) => {
778
+ title: string;
779
+ summary: (string | {
780
+ workspace: true;
781
+ strong?: undefined;
782
+ } | {
783
+ strong: string;
784
+ workspace?: undefined;
785
+ })[];
786
+ details: (readonly (string | {
787
+ strong: string;
788
+ } | {
789
+ workspace: true;
790
+ } | {
791
+ link: string;
792
+ href: string;
793
+ } | {
794
+ icon: NotificationIcon;
795
+ })[])[];
796
+ emailFormat: "thank_you";
797
+ targetUrl: string;
798
+ subjectKind: string;
799
+ subjectId: string;
800
+ };
801
+ };
802
+ }, {
803
+ readonly id: "plan.purchased";
804
+ readonly eventName: "billing.plan.purchased";
805
+ readonly requiredFilter: {};
806
+ readonly allowedTriggers: ["each_match"];
807
+ readonly catalog: {
808
+ readonly name: "Plan welcome";
809
+ readonly category: "credits";
810
+ readonly description: "Thank the workspace when it purchases a plan.";
811
+ readonly allowMultiple: false;
812
+ readonly editor: {
813
+ readonly kind: "none";
814
+ };
815
+ };
816
+ readonly rule: {
817
+ readonly default: {
818
+ readonly ruleKey: "default.plan_purchased";
819
+ readonly groupKey: "default";
820
+ };
821
+ readonly create: (_input: ProductNotificationRuleInput, base: ProductNotificationRuleBase) => {
822
+ repeatMode: "every_match";
823
+ trigger: {
824
+ kind: "each_match";
825
+ };
826
+ groupKey: string;
827
+ enabled: boolean;
828
+ conditionGeneration?: number | undefined;
829
+ ruleKey: string;
830
+ notificationType: string;
831
+ eventName: string;
832
+ eventFilter: ProductNotificationFacts;
833
+ };
834
+ readonly present: ({ payload }: {
835
+ payload: ProductNotificationFacts;
836
+ trigger: ProductNotificationTrigger;
837
+ occurredAt?: number;
838
+ }) => {
839
+ title: string;
840
+ summary: (string | {
841
+ workspace: true;
842
+ })[];
843
+ details: NotificationText[];
844
+ emailFormat: "thank_you";
845
+ targetUrl: string;
846
+ subjectKind: string;
847
+ subjectId: string;
848
+ };
849
+ };
701
850
  }, {
702
851
  readonly id: "play.failed";
703
852
  readonly eventName: "play.run.completed";
package/dist/index.d.ts CHANGED
@@ -318,7 +318,17 @@ type NotificationText = string | readonly (string | {
318
318
  strong: string;
319
319
  } | {
320
320
  workspace: true;
321
+ }
322
+ /** An absolute https link authored in code, never customer input. */
323
+ | {
324
+ link: string;
325
+ href: string;
326
+ }
327
+ /** A decorative brand icon; plain text omits it. */
328
+ | {
329
+ icon: NotificationIcon;
321
330
  })[];
331
+ type NotificationIcon = 'calendar' | 'book-open';
322
332
 
323
333
  /**
324
334
  * The sections the configuration surface skims by. Declared here so UI, API,
@@ -368,6 +378,8 @@ type ProductNotificationPresentation = {
368
378
  subjectKind: string;
369
379
  subjectId: string;
370
380
  severity?: ProductNotificationSeverity;
381
+ /** Selects an alternate email layout; absent means the standard one. */
382
+ emailFormat?: 'thank_you';
371
383
  };
372
384
  type ProductNotificationRuleInput = {
373
385
  ruleKey: string;
@@ -698,6 +710,143 @@ declare const PRODUCT_NOTIFICATION_TYPE_REGISTRY: readonly [{
698
710
  severity: "warning";
699
711
  };
700
712
  };
713
+ }, {
714
+ readonly id: "credits.purchased";
715
+ readonly eventName: "credits.purchase.completed";
716
+ readonly requiredFilter: {};
717
+ readonly allowedTriggers: ["threshold_crossing"];
718
+ readonly catalog: {
719
+ readonly name: "Credit purchase thank-you";
720
+ readonly category: "credits";
721
+ readonly description: "Thank the workspace when a credit purchase of at least the chosen size completes.";
722
+ readonly allowMultiple: false;
723
+ readonly editor: {
724
+ readonly kind: "threshold";
725
+ readonly label: "Minimum credits";
726
+ readonly defaultThreshold: 500;
727
+ readonly minimum: 0;
728
+ readonly suffix: "credits";
729
+ };
730
+ };
731
+ readonly rule: {
732
+ readonly default: {
733
+ readonly ruleKey: "default.credits_purchased";
734
+ readonly groupKey: "default";
735
+ };
736
+ readonly create: (input: ProductNotificationRuleInput, base: ProductNotificationRuleBase, editor: {
737
+ kind: "threshold";
738
+ label: string;
739
+ defaultThreshold: number;
740
+ minimum: number;
741
+ maximum?: number;
742
+ suffix?: string;
743
+ /**
744
+ * When set, editors show and accept `remainingOf - threshold` (for
745
+ * example percent remaining) while the stored trigger stays in the
746
+ * runtime's "used" terms. Bounds and defaults are stored values.
747
+ */
748
+ remainingOf?: number;
749
+ } | {
750
+ kind: "play_failure";
751
+ defaultFailureCount: number;
752
+ } | {
753
+ kind: "play_scope";
754
+ } | {
755
+ kind: "none";
756
+ }) => {
757
+ repeatMode: "every_match";
758
+ trigger: {
759
+ kind: "threshold_crossing";
760
+ direction: "at_or_above";
761
+ threshold: number;
762
+ unit: string;
763
+ };
764
+ groupKey: string;
765
+ enabled: boolean;
766
+ conditionGeneration?: number | undefined;
767
+ ruleKey: string;
768
+ notificationType: string;
769
+ eventName: string;
770
+ eventFilter: ProductNotificationFacts;
771
+ };
772
+ readonly validate: (trigger: ProductNotificationTrigger) => void;
773
+ readonly present: ({ payload }: {
774
+ payload: ProductNotificationFacts;
775
+ trigger: ProductNotificationTrigger;
776
+ occurredAt?: number;
777
+ }) => {
778
+ title: string;
779
+ summary: (string | {
780
+ workspace: true;
781
+ strong?: undefined;
782
+ } | {
783
+ strong: string;
784
+ workspace?: undefined;
785
+ })[];
786
+ details: (readonly (string | {
787
+ strong: string;
788
+ } | {
789
+ workspace: true;
790
+ } | {
791
+ link: string;
792
+ href: string;
793
+ } | {
794
+ icon: NotificationIcon;
795
+ })[])[];
796
+ emailFormat: "thank_you";
797
+ targetUrl: string;
798
+ subjectKind: string;
799
+ subjectId: string;
800
+ };
801
+ };
802
+ }, {
803
+ readonly id: "plan.purchased";
804
+ readonly eventName: "billing.plan.purchased";
805
+ readonly requiredFilter: {};
806
+ readonly allowedTriggers: ["each_match"];
807
+ readonly catalog: {
808
+ readonly name: "Plan welcome";
809
+ readonly category: "credits";
810
+ readonly description: "Thank the workspace when it purchases a plan.";
811
+ readonly allowMultiple: false;
812
+ readonly editor: {
813
+ readonly kind: "none";
814
+ };
815
+ };
816
+ readonly rule: {
817
+ readonly default: {
818
+ readonly ruleKey: "default.plan_purchased";
819
+ readonly groupKey: "default";
820
+ };
821
+ readonly create: (_input: ProductNotificationRuleInput, base: ProductNotificationRuleBase) => {
822
+ repeatMode: "every_match";
823
+ trigger: {
824
+ kind: "each_match";
825
+ };
826
+ groupKey: string;
827
+ enabled: boolean;
828
+ conditionGeneration?: number | undefined;
829
+ ruleKey: string;
830
+ notificationType: string;
831
+ eventName: string;
832
+ eventFilter: ProductNotificationFacts;
833
+ };
834
+ readonly present: ({ payload }: {
835
+ payload: ProductNotificationFacts;
836
+ trigger: ProductNotificationTrigger;
837
+ occurredAt?: number;
838
+ }) => {
839
+ title: string;
840
+ summary: (string | {
841
+ workspace: true;
842
+ })[];
843
+ details: NotificationText[];
844
+ emailFormat: "thank_you";
845
+ targetUrl: string;
846
+ subjectKind: string;
847
+ subjectId: string;
848
+ };
849
+ };
701
850
  }, {
702
851
  readonly id: "play.failed";
703
852
  readonly eventName: "play.run.completed";
package/dist/index.js CHANGED
@@ -860,7 +860,7 @@ var SDK_RELEASE = {
860
860
  // getters keep their established compatibility behavior.
861
861
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
862
862
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
863
- version: "0.3.109",
863
+ version: "0.3.110",
864
864
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
865
865
  packageCapabilities: {
866
866
  updatePreferences: 1
package/dist/index.mjs CHANGED
@@ -768,7 +768,7 @@ var SDK_RELEASE = {
768
768
  // getters keep their established compatibility behavior.
769
769
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
770
770
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
771
- version: "0.3.109",
771
+ version: "0.3.110",
772
772
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
773
773
  packageCapabilities: {
774
774
  updatePreferences: 1
@@ -149,7 +149,7 @@ type SdkRelease = {
149
149
  supportPolicy: SdkSupportPolicy;
150
150
  };
151
151
  declare const SDK_RELEASE: {
152
- readonly version: "0.3.109";
152
+ readonly version: "0.3.110";
153
153
  readonly updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.";
154
154
  readonly packageCapabilities: {
155
155
  readonly updatePreferences: 1;
package/dist/release.d.ts CHANGED
@@ -149,7 +149,7 @@ type SdkRelease = {
149
149
  supportPolicy: SdkSupportPolicy;
150
150
  };
151
151
  declare const SDK_RELEASE: {
152
- readonly version: "0.3.109";
152
+ readonly version: "0.3.110";
153
153
  readonly updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.";
154
154
  readonly packageCapabilities: {
155
155
  readonly updatePreferences: 1;
package/dist/release.js CHANGED
@@ -74,7 +74,7 @@ var SDK_RELEASE = {
74
74
  // getters keep their established compatibility behavior.
75
75
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
76
76
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
77
- version: "0.3.109",
77
+ version: "0.3.110",
78
78
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
79
79
  packageCapabilities: {
80
80
  updatePreferences: 1
package/dist/release.mjs CHANGED
@@ -48,7 +48,7 @@ var SDK_RELEASE = {
48
48
  // getters keep their established compatibility behavior.
49
49
  // 0.3.1 deprecated the legacy `deepline enrich` command in favor of Plays.
50
50
  // 0.3.90 is the first deliberately versioned SDK release for API v3.
51
- version: "0.3.109",
51
+ version: "0.3.110",
52
52
  updateSummary: "Play grep now returns concise results with the matched fields and terms. Automatic CLI updates are enabled by default; use `deepline settings autoupdate off` to opt out, `deepline settings autoupdate on` to re-enable updates, or `deepline settings autoupdate pin <version>` to hold an exact release.",
53
53
  packageCapabilities: {
54
54
  updatePreferences: 1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.3.109",
3
+ "version": "0.3.110",
4
4
  "description": "GTM data CLI and TypeScript SDK for coding agents",
5
5
  "license": "MIT",
6
6
  "homepage": "https://code.deepline.com",