deepline 0.3.86 → 0.3.87

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.
Files changed (65) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +2 -2
  2. package/dist/bundling-sources/sdk/src/monitor-fleet-contract.ts +1 -1
  3. package/dist/bundling-sources/sdk/src/plays/bundle-play-file.ts +1 -1
  4. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  5. package/dist/bundling-sources/shared_libs/integration-contracts/AGENTS.md +9 -0
  6. package/dist/bundling-sources/shared_libs/integration-contracts/README.md +3 -0
  7. package/dist/bundling-sources/shared_libs/integration-contracts/index.ts +2 -0
  8. package/dist/bundling-sources/shared_libs/monitors/analytics-install-guides.ts +77 -0
  9. package/dist/bundling-sources/shared_libs/monitors/billing/charges.ts +125 -0
  10. package/dist/bundling-sources/shared_libs/monitors/billing/policy-builders.ts +686 -0
  11. package/dist/bundling-sources/shared_libs/monitors/billing/pricing.ts +54 -0
  12. package/dist/bundling-sources/shared_libs/monitors/billing/public-pricing.ts +72 -0
  13. package/dist/bundling-sources/shared_libs/monitors/billing/session.ts +45 -0
  14. package/dist/bundling-sources/shared_libs/monitors/billing/state.ts +454 -0
  15. package/dist/bundling-sources/shared_libs/monitors/billing/tamradar.ts +260 -0
  16. package/dist/bundling-sources/shared_libs/monitors/billing/types.ts +215 -0
  17. package/dist/bundling-sources/shared_libs/monitors/callback-security.ts +333 -0
  18. package/dist/bundling-sources/shared_libs/monitors/cursor-page.ts +42 -0
  19. package/dist/bundling-sources/shared_libs/monitors/framework-managed-payload-fields.ts +71 -0
  20. package/dist/bundling-sources/shared_libs/monitors/generated/provider-monitor-callback-admission-targets.ts +16 -0
  21. package/dist/bundling-sources/shared_libs/monitors/http-error-response.ts +267 -0
  22. package/dist/bundling-sources/shared_libs/monitors/inbound-publish-key.ts +39 -0
  23. package/dist/bundling-sources/shared_libs/monitors/inbound-queue.ts +82 -0
  24. package/dist/bundling-sources/shared_libs/monitors/lifecycle-notifications.ts +473 -0
  25. package/dist/bundling-sources/shared_libs/monitors/org-monitor-limits.ts +1 -1
  26. package/dist/bundling-sources/shared_libs/monitors/package.json +29 -0
  27. package/dist/bundling-sources/shared_libs/monitors/provider-callback-verification.ts +1125 -0
  28. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-capability.ts +964 -0
  29. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-consumer-health.ts +56 -0
  30. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-errors.ts +131 -0
  31. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-identity-claims.ts +527 -0
  32. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-legacy-aliases.ts +16 -0
  33. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-priority-execution.ts +56 -0
  34. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-public-aliases.ts +28 -0
  35. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-runtime-read-eligibility.ts +38 -0
  36. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-status.ts +10 -0
  37. package/dist/bundling-sources/shared_libs/monitors/provider-monitor-upstream-identity.ts +112 -0
  38. package/dist/bundling-sources/shared_libs/monitors/provider-webhook-monitor-runtime.ts +300 -0
  39. package/dist/bundling-sources/shared_libs/monitors/quickstart-prompts.ts +22 -0
  40. package/dist/bundling-sources/shared_libs/monitors/snitcher-install-validation.ts +93 -0
  41. package/dist/bundling-sources/shared_libs/monitors/snitcher-ip2company.ts +332 -0
  42. package/dist/bundling-sources/shared_libs/monitors/snitcher-radar.ts +221 -0
  43. package/dist/bundling-sources/shared_libs/monitors/starter-templates.ts +105 -0
  44. package/dist/bundling-sources/shared_libs/monitors/tracking-provider-guidance.ts +73 -0
  45. package/dist/bundling-sources/shared_libs/monitors/tracking-provider-integrations.ts +81 -0
  46. package/dist/bundling-sources/shared_libs/monitors/tsconfig.json +10 -0
  47. package/dist/bundling-sources/shared_libs/play-runtime/runtime-env.ts +6 -0
  48. package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +1 -1
  49. package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +1 -1
  50. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +302 -0
  51. package/dist/bundling-sources/shared_libs/plays/email-status.ts +1 -1
  52. package/dist/bundling-sources/shared_libs/plays/tool-category-descriptions.ts +2 -2
  53. package/dist/bundling-sources/shared_libs/runtime-env.ts +6 -0
  54. package/dist/bundling-sources/shared_libs/security/header-safe-env.ts +28 -0
  55. package/dist/bundling-sources/shared_libs/security/secret-redaction.ts +267 -0
  56. package/dist/cli/index.js +6 -4
  57. package/dist/cli/index.mjs +6 -4
  58. package/dist/index.d.mts +1 -1
  59. package/dist/index.d.ts +1 -1
  60. package/dist/index.js +3 -3
  61. package/dist/index.mjs +3 -3
  62. package/dist/install-integrity.json +45 -2
  63. package/package.json +3 -1
  64. /package/dist/bundling-sources/shared_libs/{integrations/bettercontact-execution-policy.ts → integration-contracts/bettercontact/execution-policy.ts} +0 -0
  65. /package/dist/bundling-sources/shared_libs/{integrations/theirstack-execution-policy.ts → integration-contracts/theirstack/execution-policy.ts} +0 -0
@@ -0,0 +1,16 @@
1
+ const LEGACY_STORED_PROVIDER_MONITOR_CAPABILITY_ALIASES: Readonly<
2
+ Record<string, string>
3
+ > = {
4
+ 'tamradar.company_radar': 'deepline_native.company_radar',
5
+ 'tamradar.contact_radar': 'deepline_native.contact_radar',
6
+ 'tamradar.industry_radar': 'deepline_native.industry_radar',
7
+ };
8
+
9
+ export function canonicalizeStoredProviderMonitorCapabilityId(
10
+ providerCapability: string,
11
+ ): string {
12
+ return (
13
+ LEGACY_STORED_PROVIDER_MONITOR_CAPABILITY_ALIASES[providerCapability] ??
14
+ providerCapability
15
+ );
16
+ }
@@ -0,0 +1,56 @@
1
+ import type { ProviderMonitorCapabilityDefinition } from '@deepline/monitors/provider-monitor-capability';
2
+ import type { ProviderMonitorStatus } from './provider-monitor-status';
3
+
4
+ export const PRIORITY_MONITOR_EXECUTION_TYPE = 'priority' as const;
5
+ export const MAX_PRIORITY_MONITORS_PER_ORG = 10;
6
+
7
+ export const PRIORITY_MONITOR_SLOT_STATUSES = [
8
+ 'active',
9
+ // A billing suspension writes disabled before upstream deprovisioning. Keep
10
+ // the slot occupied until that delete confirms success and clears the
11
+ // projection; a failed cleanup can leave the priority radar live.
12
+ 'disabled',
13
+ 'deleting',
14
+ 'error',
15
+ 'deleted',
16
+ // A fleet monitor holds its priority reservation from the commit that
17
+ // records the desired state, not from the provider create that follows it.
18
+ 'pending_create',
19
+ ] as const satisfies readonly ProviderMonitorStatus[];
20
+
21
+ const PRIORITY_MONITOR_SLOT_STATUS_SET = new Set<ProviderMonitorStatus>(
22
+ PRIORITY_MONITOR_SLOT_STATUSES,
23
+ );
24
+
25
+ export function requestedPriorityMonitorExecution(definition: {
26
+ controls?: Record<string, unknown>;
27
+ }) {
28
+ return (
29
+ definition.controls?.execution_type === PRIORITY_MONITOR_EXECUTION_TYPE
30
+ );
31
+ }
32
+
33
+ export function priorityMonitorExecutionIsSupported(
34
+ capability: ProviderMonitorCapabilityDefinition,
35
+ ) {
36
+ return capability.provider === 'deepline_native';
37
+ }
38
+
39
+ export function priorityMonitorConsumesSlot(input: {
40
+ priorityExecution: boolean;
41
+ status: ProviderMonitorStatus;
42
+ }) {
43
+ return (
44
+ input.priorityExecution &&
45
+ PRIORITY_MONITOR_SLOT_STATUS_SET.has(input.status)
46
+ );
47
+ }
48
+
49
+ export function priorityMonitorReservedSlotCount(input: {
50
+ priorityExecution: boolean;
51
+ status: ProviderMonitorStatus;
52
+ priorityExecutionSlots?: number;
53
+ }) {
54
+ if (!priorityMonitorConsumesSlot(input)) return 0;
55
+ return input.priorityExecutionSlots === 2 ? 2 : 1;
56
+ }
@@ -0,0 +1,28 @@
1
+ import {
2
+ ANALYTICS_INTEGRATION_SLUG,
3
+ ANALYTICS_PUBLIC_PROVIDER,
4
+ ANALYTICS_PUBLIC_TOOL,
5
+ } from '@/lib/analytics/public';
6
+
7
+ export const ANALYTICS_MONITOR_KEY = ANALYTICS_INTEGRATION_SLUG;
8
+ export const ANALYTICS_INTERNAL_MONITOR_KEY = 'snitcher-website-sessions';
9
+ export const ANALYTICS_INTERNAL_TOOL = 'snitcher.website_sessions';
10
+
11
+ export function canonicalProviderMonitorKey(key: string) {
12
+ return key === ANALYTICS_MONITOR_KEY ? ANALYTICS_INTERNAL_MONITOR_KEY : key;
13
+ }
14
+
15
+ /** Convert a customer-facing monitor tool id to its internal registry id. */
16
+ export function canonicalProviderMonitorTool(tool: string) {
17
+ return tool === ANALYTICS_PUBLIC_TOOL ? ANALYTICS_INTERNAL_TOOL : tool;
18
+ }
19
+
20
+ /** Convert an internal registry id to the stable customer-facing tool id. */
21
+ export function publicProviderMonitorTool(tool: string) {
22
+ return tool === ANALYTICS_INTERNAL_TOOL ? ANALYTICS_PUBLIC_TOOL : tool;
23
+ }
24
+
25
+ /** Convert an internal provider name to the stable customer-facing name. */
26
+ export function publicProviderMonitorProvider(provider: string) {
27
+ return provider === 'snitcher' ? ANALYTICS_PUBLIC_PROVIDER : provider;
28
+ }
@@ -0,0 +1,38 @@
1
+ type ProviderMonitorCallbackRuntimeRow = {
2
+ status: string;
3
+ billing_state: unknown;
4
+ };
5
+
6
+ function billingStateKind(value: unknown) {
7
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
8
+ const state = (value as Record<string, unknown>).state;
9
+ return typeof state === 'string' && state.trim() ? state.trim() : null;
10
+ }
11
+
12
+ /** True when an admitted callback is allowed to write an event for this monitor. */
13
+ export function isProviderMonitorCallbackWriteEnabled(
14
+ row: Pick<ProviderMonitorCallbackRuntimeRow, 'status'>,
15
+ ) {
16
+ return row.status === 'active';
17
+ }
18
+
19
+ /**
20
+ * Matches the Customer DB callback lookup predicate for a row that has already
21
+ * been selected from the Convex callback-token projection.
22
+ */
23
+ export function isProviderMonitorCallbackRuntimeEligible(
24
+ row: ProviderMonitorCallbackRuntimeRow,
25
+ ) {
26
+ if (isProviderMonitorCallbackWriteEnabled(row)) {
27
+ return true;
28
+ }
29
+ return (
30
+ row.status === 'disabled' &&
31
+ (billingStateKind(row.billing_state) === 'upstream_disable_pending' ||
32
+ billingStateKind(row.billing_state) === 'upstream_disable_failed' ||
33
+ // A credit suspension turns the provider resource off but does not turn
34
+ // off receipt capture. The inbound processor stores raw provenance and
35
+ // returns its explicit billing-suspended outcome without typed writes.
36
+ billingStateKind(row.billing_state) === 'billing_suspended')
37
+ );
38
+ }
@@ -0,0 +1,10 @@
1
+ export type ProviderMonitorStatus =
2
+ | 'replacement_pending'
3
+ | 'replacement_active_old_cleanup_pending'
4
+ | 'replacement_cleanup_failed'
5
+ | 'active'
6
+ | 'disabled'
7
+ | 'deleted'
8
+ | 'deleting'
9
+ | 'error'
10
+ | 'pending_create';
@@ -0,0 +1,112 @@
1
+ // Convex-safe provider monitor identity contract.
2
+ //
3
+ // This deliberately contains only declarative identity metadata and pure
4
+ // normalization. Convex claim mutations import it directly, so it must not
5
+ // pull in the Node-only provider registry, generated OpenAPI loaders, or any
6
+ // deployment runtime. A monitor provider adds its account-unique identity here
7
+ // and declares the same fields on its capability; registry validation keeps the
8
+ // two declarations aligned.
9
+
10
+ export type ProviderMonitorUpstreamIdentityValue = string | null;
11
+
12
+ export type ProviderMonitorUpstreamIdentitySignature = {
13
+ tool: string;
14
+ key: Record<string, ProviderMonitorUpstreamIdentityValue>;
15
+ };
16
+
17
+ type ProviderMonitorUpstreamIdentityField = {
18
+ name: string;
19
+ // A provider runtime payload can use its upstream request name while the
20
+ // public monitor definition uses a clearer field name. Identity backfills
21
+ // use this declarative alias rather than provider-specific conditionals.
22
+ runtimePayloadAliases?: readonly string[];
23
+ };
24
+
25
+ const PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS = {
26
+ 'deepline_native.company_radar': [{ name: 'radar_type' }, { name: 'domain' }],
27
+ 'deepline_native.contact_radar': [
28
+ { name: 'radar_type' },
29
+ { name: 'profile_url' },
30
+ ],
31
+ 'deepline_native.industry_radar': [
32
+ { name: 'radar_type' },
33
+ { name: 'industry', runtimePayloadAliases: ['keyword'] },
34
+ ],
35
+ } as const satisfies Record<
36
+ string,
37
+ readonly ProviderMonitorUpstreamIdentityField[]
38
+ >;
39
+
40
+ function canonicalMonitorTool(tool: string) {
41
+ // Public TamRadar ids existed before the deepline_native hard cutover. Keep
42
+ // their legacy control records in the same claim namespace during migration.
43
+ return tool.startsWith('tamradar.')
44
+ ? `deepline_native.${tool.slice('tamradar.'.length)}`
45
+ : tool;
46
+ }
47
+
48
+ function stableJson(value: unknown): string {
49
+ if (Array.isArray(value)) return `[${value.map(stableJson).join(',')}]`;
50
+ if (value && typeof value === 'object') {
51
+ const entries = Object.entries(value as Record<string, unknown>)
52
+ .filter(([, entry]) => entry !== undefined)
53
+ .sort(([left], [right]) => left.localeCompare(right))
54
+ .map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`);
55
+ return `{${entries.join(',')}}`;
56
+ }
57
+ return JSON.stringify(value) ?? 'null';
58
+ }
59
+
60
+ export function normalizeProviderMonitorUpstreamIdentityValue(
61
+ value: unknown,
62
+ ): ProviderMonitorUpstreamIdentityValue {
63
+ if (value === null || value === undefined) return null;
64
+ if (typeof value === 'string') {
65
+ const normalized = value.trim().toLowerCase();
66
+ return normalized || null;
67
+ }
68
+ if (typeof value === 'number' || typeof value === 'boolean') {
69
+ return String(value);
70
+ }
71
+ if (Array.isArray(value)) {
72
+ const normalized = value
73
+ .map(normalizeProviderMonitorUpstreamIdentityValue)
74
+ .filter((entry): entry is string => entry !== null)
75
+ .sort();
76
+ return normalized.length ? normalized.join(',') : null;
77
+ }
78
+ return stableJson(value).toLowerCase();
79
+ }
80
+
81
+ export function providerMonitorUpstreamIdentityFields(tool: string) {
82
+ const canonical = canonicalMonitorTool(tool);
83
+ return PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS[
84
+ canonical as keyof typeof PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS
85
+ ]?.map((field) => field.name);
86
+ }
87
+
88
+ export function providerMonitorUpstreamIdentitySignature(
89
+ tool: string,
90
+ payload: Record<string, unknown> | null | undefined,
91
+ ): ProviderMonitorUpstreamIdentitySignature | null {
92
+ const canonical = canonicalMonitorTool(tool);
93
+ const fields =
94
+ PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS[
95
+ canonical as keyof typeof PROVIDER_MONITOR_UPSTREAM_IDENTITY_FIELDS
96
+ ];
97
+ if (!fields?.length) return null;
98
+ const key: Record<string, ProviderMonitorUpstreamIdentityValue> = {};
99
+ for (const field of fields) {
100
+ const aliases =
101
+ 'runtimePayloadAliases' in field
102
+ ? field.runtimePayloadAliases
103
+ : undefined;
104
+ const value =
105
+ payload?.[field.name] ??
106
+ aliases
107
+ ?.map((alias) => payload?.[alias])
108
+ .find((aliasValue) => aliasValue !== undefined);
109
+ key[field.name] = normalizeProviderMonitorUpstreamIdentityValue(value);
110
+ }
111
+ return { tool: canonical, key };
112
+ }
@@ -0,0 +1,300 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import type { ProviderMonitorCapabilityDefinition } from '@deepline/monitors/provider-monitor-capability';
4
+ import {
5
+ createProviderCallbackSecret,
6
+ MONITOR_CALLBACK_SECRET_HEADER,
7
+ } from '@deepline/monitors/provider-callback-verification';
8
+
9
+ type ProviderMonitorDefinitionLike = {
10
+ key: string;
11
+ name?: string;
12
+ payload?: Record<string, unknown>;
13
+ };
14
+
15
+ const DEFAULT_MONITOR_CALLBACK_BASE_URL = 'http://localhost:3000';
16
+ const DEFAULT_PREVIEW_CALLBACK_URL_FILE =
17
+ '.cache/dev/plays-preview-callback-url';
18
+
19
+ function readRecord(value: unknown): Record<string, unknown> {
20
+ return value && typeof value === 'object' && !Array.isArray(value)
21
+ ? (value as Record<string, unknown>)
22
+ : {};
23
+ }
24
+
25
+ function readString(value: unknown) {
26
+ if (typeof value === 'number' && Number.isFinite(value)) return String(value);
27
+ if (typeof value !== 'string') return null;
28
+ const trimmed = value.trim();
29
+ return trimmed || null;
30
+ }
31
+
32
+ function firstString(...values: unknown[]) {
33
+ for (const value of values) {
34
+ const string = readString(value);
35
+ if (string) return string;
36
+ }
37
+ return null;
38
+ }
39
+
40
+ function isAbsoluteFilePath(value: string) {
41
+ return value.startsWith('/') || /^[a-zA-Z]:[\\/]/.test(value);
42
+ }
43
+
44
+ function cwdRelativePath(value: string) {
45
+ return isAbsoluteFilePath(value) ? value : `${process.cwd()}/${value}`;
46
+ }
47
+
48
+ function readPreviewCallbackUrlFile(
49
+ environment: NodeJS.ProcessEnv = process.env,
50
+ ) {
51
+ const configuredPath = environment.DEEPLINE_PREVIEW_CALLBACK_URL_FILE?.trim();
52
+ const filePath = cwdRelativePath(
53
+ configuredPath || DEFAULT_PREVIEW_CALLBACK_URL_FILE,
54
+ );
55
+ try {
56
+ if (!existsSync(filePath)) return null;
57
+ return readString(readFileSync(filePath, 'utf8'));
58
+ } catch {
59
+ return null;
60
+ }
61
+ }
62
+
63
+ function readStringArray(value: unknown) {
64
+ if (!Array.isArray(value)) return [];
65
+ return value
66
+ .map((item) => readString(item))
67
+ .filter((item): item is string => Boolean(item));
68
+ }
69
+
70
+ function readNumberArray(value: unknown) {
71
+ if (!Array.isArray(value)) return [];
72
+ return value.filter(
73
+ (item): item is number =>
74
+ typeof item === 'number' && Number.isInteger(item),
75
+ );
76
+ }
77
+
78
+ function boundedName(value: string, maxLength: number) {
79
+ const normalized = value.replace(/\s+/g, ' ').trim();
80
+ if (normalized.length <= maxLength) return normalized;
81
+ return normalized.slice(0, maxLength).trim();
82
+ }
83
+
84
+ function managedWebhookName(input: {
85
+ definition: ProviderMonitorDefinitionLike;
86
+ provider: string;
87
+ maxLength?: number;
88
+ }) {
89
+ const source = input.definition.name ?? input.definition.key;
90
+ const name = `Deepline ${input.provider} ${source}`;
91
+ return boundedName(name, input.maxLength ?? 80);
92
+ }
93
+
94
+ /**
95
+ * Resolve the externally reachable origin for provider callbacks and durable
96
+ * workers. Local Convex actions need the preview tunnel, not a loopback URL.
97
+ */
98
+ export function resolveProviderMonitorCallbackBaseUrl(
99
+ explicit?: string | null,
100
+ environment: NodeJS.ProcessEnv = process.env,
101
+ ) {
102
+ const baseUrl =
103
+ explicit ||
104
+ environment.DEEPLINE_WORKER_CALLBACK_URL ||
105
+ readPreviewCallbackUrlFile(environment) ||
106
+ environment.WORKTREE_PUBLIC_APP_URL ||
107
+ environment.DEEPLINE_API_BASE_URL ||
108
+ environment.NEXT_PUBLIC_APP_URL ||
109
+ environment.APP_URL ||
110
+ environment.VERCEL_URL ||
111
+ DEFAULT_MONITOR_CALLBACK_BASE_URL;
112
+ return baseUrl.startsWith('http') ? baseUrl : `https://${baseUrl}`;
113
+ }
114
+
115
+ export function createProviderWebhookCallbackToken() {
116
+ return randomBytes(32).toString('hex');
117
+ }
118
+
119
+ export function providerWebhookCallbackPath(input: {
120
+ provider: string;
121
+ orgId: string;
122
+ token: string;
123
+ }) {
124
+ return `/api/v2/monitors/provider-callbacks/webhook/${encodeURIComponent(
125
+ input.provider,
126
+ )}/${encodeURIComponent(input.orgId)}/${encodeURIComponent(input.token)}`;
127
+ }
128
+
129
+ export function buildProviderWebhookCallbackUrl(input: {
130
+ provider: string;
131
+ orgId: string;
132
+ token: string;
133
+ callbackBaseUrl?: string | null;
134
+ }) {
135
+ const baseUrl = resolveProviderMonitorCallbackBaseUrl(input.callbackBaseUrl);
136
+ if (input.provider === 'snitcher') {
137
+ return `${baseUrl.replace(/\/$/, '')}/api/v2/monitors/provider-callbacks/deepline-analytics`;
138
+ }
139
+ return `${baseUrl.replace(/\/$/, '')}${providerWebhookCallbackPath(input)}`;
140
+ }
141
+
142
+ export function readProviderWebhookCallbackUrl(
143
+ payload: Record<string, unknown>,
144
+ ) {
145
+ const webhook = readRecord(payload.webhook);
146
+ return firstString(
147
+ payload.target_url,
148
+ payload.target_hook_url,
149
+ payload.webhook_url,
150
+ webhook.webhook_url,
151
+ );
152
+ }
153
+
154
+ export function extractProviderWebhookId(raw: unknown) {
155
+ const root = readRecord(raw);
156
+ const data = readRecord(root.data);
157
+ const result = readRecord(root.result);
158
+ const resultData = readRecord(result.data);
159
+ const webhook = readRecord(root.webhook);
160
+ const dataWebhook = readRecord(data.webhook);
161
+ const id = readRecord(root.id);
162
+ const dataId = readRecord(data.id);
163
+ const resultDataId = readRecord(resultData.id);
164
+ return firstString(
165
+ root.webhook_id,
166
+ root.webhookId,
167
+ root.hook_id,
168
+ root.hookId,
169
+ root.id,
170
+ root._id,
171
+ data.webhook_id,
172
+ data.webhookId,
173
+ data.hook_id,
174
+ data.hookId,
175
+ data.id,
176
+ data._id,
177
+ result.webhook_id,
178
+ result.webhookId,
179
+ result.hook_id,
180
+ result.hookId,
181
+ result.id,
182
+ result._id,
183
+ resultData.webhook_id,
184
+ resultData.webhookId,
185
+ resultData.hook_id,
186
+ resultData.hookId,
187
+ resultData.id,
188
+ resultData._id,
189
+ webhook.webhook_id,
190
+ webhook.id,
191
+ dataWebhook.webhook_id,
192
+ dataWebhook.id,
193
+ id.webhook_id,
194
+ dataId.webhook_id,
195
+ resultDataId.webhook_id,
196
+ );
197
+ }
198
+
199
+ export function buildProviderWebhookRuntimePayload(input: {
200
+ definition: ProviderMonitorDefinitionLike;
201
+ capability: ProviderMonitorCapabilityDefinition;
202
+ callbackUrl: string;
203
+ }) {
204
+ const payload = input.definition.payload ?? {};
205
+ const provider = input.capability.provider;
206
+ switch (provider) {
207
+ case 'attio':
208
+ return {
209
+ target_url: input.callbackUrl,
210
+ subscriptions: readStringArray(payload.event_types).map(
211
+ (eventType) => ({
212
+ event_type: eventType,
213
+ }),
214
+ ),
215
+ };
216
+ case 'lemlist':
217
+ return {
218
+ target_url: input.callbackUrl,
219
+ type: readString(payload.event_type),
220
+ secret: createProviderCallbackSecret('lemlist'),
221
+ ...(readString(payload.campaign_id)
222
+ ? { campaign_id: readString(payload.campaign_id) }
223
+ : {}),
224
+ };
225
+ case 'instantly':
226
+ return {
227
+ target_hook_url: input.callbackUrl,
228
+ event_type: readString(payload.event_type),
229
+ headers: {
230
+ [MONITOR_CALLBACK_SECRET_HEADER]:
231
+ createProviderCallbackSecret('instantly'),
232
+ },
233
+ name: managedWebhookName({
234
+ definition: input.definition,
235
+ provider,
236
+ }),
237
+ ...(readString(payload.campaign_id)
238
+ ? { campaign: readString(payload.campaign_id) }
239
+ : {}),
240
+ };
241
+ case 'smartlead':
242
+ return {
243
+ campaign_id: payload.campaign_id,
244
+ webhook: {
245
+ id: null,
246
+ name: managedWebhookName({
247
+ definition: input.definition,
248
+ provider,
249
+ }),
250
+ webhook_url: input.callbackUrl,
251
+ event_types: readStringArray(payload.event_types),
252
+ ...(readStringArray(payload.categories).length
253
+ ? { categories: readStringArray(payload.categories) }
254
+ : {}),
255
+ },
256
+ };
257
+ case 'heyreach':
258
+ return {
259
+ webhook_name: managedWebhookName({
260
+ definition: input.definition,
261
+ provider,
262
+ maxLength: 25,
263
+ }),
264
+ webhook_url: input.callbackUrl,
265
+ event_type: readString(payload.event_type),
266
+ ...(readNumberArray(payload.campaign_ids).length
267
+ ? { campaign_ids: readNumberArray(payload.campaign_ids) }
268
+ : {}),
269
+ };
270
+ case 'vector':
271
+ // Vector's dashboard owns the webhook resource. This payload is durable
272
+ // setup evidence, not an upstream API request.
273
+ return {
274
+ callback_url: input.callbackUrl,
275
+ event_type: 'contact.visited',
276
+ };
277
+ case 'rb2b':
278
+ // RB2B's dashboard owns the single webhook connection. This payload is
279
+ // durable setup evidence, not an upstream API request.
280
+ return {
281
+ callback_url: input.callbackUrl,
282
+ };
283
+ case 'snitcher':
284
+ // Radar is configured at the operator level. The callback URL is durable
285
+ // evidence of the Deepline-owned receiver; the setup API creates the
286
+ // customer tracker and configures session delivery upstream.
287
+ return {
288
+ callback_url: input.callbackUrl,
289
+ domains: readStringArray(payload.domains),
290
+ form_tracking: payload.formTracking === true,
291
+ click_tracking: payload.clickTracking === true,
292
+ custom_events: payload.customEvents !== false,
293
+ wait_for_consent: payload.waitForConsent !== false,
294
+ };
295
+ default:
296
+ throw new Error(
297
+ `Provider webhook monitor payload builder is not implemented for ${provider}.`,
298
+ );
299
+ }
300
+ }
@@ -0,0 +1,22 @@
1
+ export type MonitorQuickstartPrompt = {
2
+ prompt: string;
3
+ icon: 'job-change' | 'new-hires' | 'hiring-signal';
4
+ };
5
+
6
+ export const MONITOR_QUICKSTART_PROMPTS: MonitorQuickstartPrompt[] = [
7
+ {
8
+ prompt:
9
+ '/deepline-gtm Monitor my target accounts for new VP Sales, Marketing, and Revenue hires.',
10
+ icon: 'new-hires',
11
+ },
12
+ {
13
+ prompt:
14
+ '/deepline-gtm Monitor when people at my target accounts change jobs.',
15
+ icon: 'job-change',
16
+ },
17
+ {
18
+ prompt:
19
+ '/deepline-gtm Monitor my target accounts for job openings in the roles I sell to.',
20
+ icon: 'hiring-signal',
21
+ },
22
+ ];
@@ -0,0 +1,93 @@
1
+ import { lookup } from 'node:dns/promises';
2
+ import { isIP } from 'node:net';
3
+ import { DEEPLINE_ANALYTICS_BROWSER_OBJECT } from '@/lib/analytics/public';
4
+
5
+ export type SnitcherInstallationCheck = {
6
+ domain: string;
7
+ status: 'installed' | 'not_found' | 'unreachable';
8
+ message: string;
9
+ };
10
+
11
+ export async function validateSnitcherInstallation(input: {
12
+ domains: string[];
13
+ trackingScriptId: string;
14
+ }) {
15
+ return await Promise.all(
16
+ input.domains.map(async (domain): Promise<SnitcherInstallationCheck> => {
17
+ // Domains were validated and stored during setup. Never accept arbitrary URLs here.
18
+ if (isIP(domain) || domain === 'localhost')
19
+ return {
20
+ domain,
21
+ status: 'unreachable',
22
+ message: 'This domain cannot be checked.',
23
+ };
24
+ try {
25
+ const addresses = await lookup(domain, { all: true });
26
+ if (
27
+ !addresses.length ||
28
+ addresses.some(({ address }) => {
29
+ if (isIP(address) === 6) {
30
+ const lower = address.toLowerCase();
31
+ return (
32
+ lower === '::1' ||
33
+ lower.startsWith('fc') ||
34
+ lower.startsWith('fd') ||
35
+ lower.startsWith('fe80:')
36
+ );
37
+ }
38
+ const [a, b] = address.split('.').map(Number);
39
+ return (
40
+ a === 0 ||
41
+ a === 10 ||
42
+ a === 127 ||
43
+ (a === 169 && b === 254) ||
44
+ (a === 172 && b >= 16 && b <= 31) ||
45
+ (a === 192 && b === 168) ||
46
+ (a === 100 && b >= 64 && b <= 127)
47
+ );
48
+ })
49
+ )
50
+ return {
51
+ domain,
52
+ status: 'unreachable',
53
+ message: 'This domain cannot be checked.',
54
+ };
55
+ const response = await fetch(`https://${domain}`, {
56
+ redirect: 'error',
57
+ signal: AbortSignal.timeout(10_000),
58
+ headers: {
59
+ Accept: 'text/html',
60
+ 'User-Agent': 'DeeplineTrackerValidator/1.0',
61
+ },
62
+ });
63
+ if (!response.ok)
64
+ return {
65
+ domain,
66
+ status: 'unreachable',
67
+ message: `The site returned HTTP ${response.status}.`,
68
+ };
69
+ const body = (await response.text()).slice(0, 500_000);
70
+ const installed =
71
+ body.includes(input.trackingScriptId) &&
72
+ body.includes(DEEPLINE_ANALYTICS_BROWSER_OBJECT);
73
+ return installed
74
+ ? {
75
+ domain,
76
+ status: 'installed',
77
+ message: 'Tracker found in the page source.',
78
+ }
79
+ : {
80
+ domain,
81
+ status: 'not_found',
82
+ message: 'Tracker was not found in the page source yet.',
83
+ };
84
+ } catch {
85
+ return {
86
+ domain,
87
+ status: 'unreachable',
88
+ message: 'Could not reach this site over HTTPS.',
89
+ };
90
+ }
91
+ }),
92
+ );
93
+ }