@synterai/sdk-js 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,1408 @@
1
+ // src/errors.ts
2
+ var SynterError = class _SynterError extends Error {
3
+ constructor(message, status = 0, code = null, details = null) {
4
+ super(message);
5
+ this.name = "SynterError";
6
+ this.status = status;
7
+ this.code = code;
8
+ this.details = details;
9
+ Object.setPrototypeOf(this, _SynterError.prototype);
10
+ }
11
+ };
12
+ var SynterValidationError = class _SynterValidationError extends Error {
13
+ constructor(message, field) {
14
+ super(message);
15
+ this.name = "SynterValidationError";
16
+ this.field = field;
17
+ Object.setPrototypeOf(this, _SynterValidationError.prototype);
18
+ }
19
+ };
20
+
21
+ // src/internal/arg-mapping.ts
22
+ var DATE_RANGE_TO_DAYS = {
23
+ TODAY: 1,
24
+ YESTERDAY: 1,
25
+ LAST_7_DAYS: 7,
26
+ LAST_30_DAYS: 30,
27
+ THIS_MONTH: 31,
28
+ LAST_MONTH: 31
29
+ };
30
+ function buildArgsFromMapping(mapping, input, resolvedPlatform) {
31
+ const args = [];
32
+ const record = input;
33
+ for (const entry of mapping) {
34
+ let value = record[entry.field];
35
+ if ((value === void 0 || value === null) && entry.default !== void 0) {
36
+ value = entry.default;
37
+ }
38
+ if (value === void 0 || value === null) {
39
+ if (entry.required) {
40
+ throw new SynterValidationError(
41
+ `Missing required field \`${entry.field}\` (maps to \`${entry.flag}\`).`,
42
+ entry.field
43
+ );
44
+ }
45
+ continue;
46
+ }
47
+ if (entry.onlyForPlatform && entry.onlyForPlatform !== resolvedPlatform) {
48
+ continue;
49
+ }
50
+ if (entry.transform === "dateRangeToDays") {
51
+ const days = DATE_RANGE_TO_DAYS[String(value)];
52
+ if (days === void 0) {
53
+ continue;
54
+ }
55
+ args.push(entry.flag, String(days));
56
+ continue;
57
+ }
58
+ if (entry.transform === "uppercase") {
59
+ value = String(value).toUpperCase();
60
+ }
61
+ if (entry.type === "array") {
62
+ if (!Array.isArray(value)) {
63
+ throw new SynterValidationError(`Field \`${entry.field}\` must be an array.`, entry.field);
64
+ }
65
+ for (const item of value) {
66
+ args.push(entry.flag, String(item));
67
+ }
68
+ continue;
69
+ }
70
+ args.push(entry.flag, String(value));
71
+ }
72
+ return args;
73
+ }
74
+ function resolvePlatformScriptName(scriptNameByPlatform, defaultPlatform, requestedPlatform) {
75
+ const platform = requestedPlatform ?? defaultPlatform;
76
+ const scriptName = scriptNameByPlatform[platform];
77
+ if (!scriptName) {
78
+ throw new SynterValidationError(
79
+ `Unsupported platform \`${platform}\`. Expected one of: ${Object.keys(scriptNameByPlatform).join(", ")}.`,
80
+ "platform"
81
+ );
82
+ }
83
+ return { platform, scriptName };
84
+ }
85
+ function flattenArgsToFlags(args) {
86
+ const out = [];
87
+ for (const [key, value] of Object.entries(args)) {
88
+ if (value === void 0 || value === null) continue;
89
+ const flag = `--${key.replace(/_/g, "-")}`;
90
+ if (typeof value === "boolean") {
91
+ if (value) out.push(flag);
92
+ continue;
93
+ }
94
+ out.push(flag, String(value));
95
+ }
96
+ return out;
97
+ }
98
+
99
+ // src/resources/campaigns.ts
100
+ var LIST_CAMPAIGNS_SCRIPT_BY_PLATFORM = {
101
+ google: "google_ads_list_campaigns",
102
+ meta: "meta_ads_list_campaigns",
103
+ linkedin: "linkedin_ads_list_campaigns",
104
+ microsoft: "microsoft_ads_list_campaigns",
105
+ reddit: "reddit_ads_list_campaigns",
106
+ tiktok: "tiktok_ads_list_campaigns",
107
+ x: "x_ads_list_campaigns"
108
+ };
109
+ var LIST_CAMPAIGNS_ARG_MAPPING = [
110
+ { field: "status", flag: "--status", type: "string" },
111
+ { field: "limit", flag: "--limit", type: "number" }
112
+ ];
113
+ var CREATE_SEARCH_ARG_MAPPING = [
114
+ { field: "campaign_name", flag: "--campaign-name", type: "string", required: true },
115
+ { field: "daily_budget", flag: "--daily-budget", type: "number", required: true },
116
+ { field: "final_url", flag: "--final-url", type: "string", required: true },
117
+ { field: "keywords", flag: "--keyword", type: "array", required: true },
118
+ { field: "headlines", flag: "--headline", type: "array", required: true },
119
+ { field: "descriptions", flag: "--description", type: "array", required: true },
120
+ { field: "geo_targets", flag: "--geo-targets", type: "array" }
121
+ ];
122
+ var CREATE_DISPLAY_ARG_MAPPING = [
123
+ { field: "campaign_name", flag: "--campaign-name", type: "string", required: true },
124
+ { field: "daily_budget", flag: "--daily-budget", type: "number", required: true },
125
+ { field: "business_name", flag: "--business-name", type: "string", required: true },
126
+ { field: "final_url", flag: "--final-url", type: "string", required: true },
127
+ { field: "landscape_image_url", flag: "--landscape-image", type: "string" },
128
+ { field: "square_image_url", flag: "--square-image", type: "string" },
129
+ { field: "headlines", flag: "--headline", type: "array", required: true },
130
+ { field: "descriptions", flag: "--description", type: "array", required: true },
131
+ { field: "geo_targets", flag: "--geo-targets", type: "array" }
132
+ ];
133
+ var CREATE_PMAX_ARG_MAPPING = [
134
+ { field: "campaign_name", flag: "--campaign-name", type: "string", required: true },
135
+ { field: "daily_budget", flag: "--daily-budget", type: "number", required: true },
136
+ { field: "business_name", flag: "--business-name", type: "string", required: true },
137
+ { field: "final_url", flag: "--final-url", type: "string", required: true },
138
+ { field: "long_headline", flag: "--long-headline", type: "string", required: true },
139
+ { field: "landscape_image_url", flag: "--landscape-image-url", type: "string" },
140
+ { field: "square_image_url", flag: "--square-image-url", type: "string" },
141
+ { field: "logo_url", flag: "--logo-url", type: "string" },
142
+ { field: "target_cpa", flag: "--target-cpa", type: "number" },
143
+ { field: "headlines", flag: "--headline", type: "array", required: true },
144
+ { field: "descriptions", flag: "--description", type: "array", required: true },
145
+ { field: "geo_targets", flag: "--geo-targets", type: "array" }
146
+ ];
147
+ var PAUSE_CAMPAIGN_ARG_MAPPING = [
148
+ { field: "campaign_id", flag: "--campaign-id", type: "string", required: true }
149
+ ];
150
+ var UPDATE_BUDGET_ARG_MAPPING = [
151
+ { field: "campaign_id", flag: "--campaign-id", type: "string", required: true },
152
+ { field: "daily_budget", flag: "--budget", type: "number", required: true }
153
+ ];
154
+ var CampaignsResource = class {
155
+ constructor(client) {
156
+ this.client = client;
157
+ }
158
+ /**
159
+ * List all campaigns across connected ad platforms. Returns campaign
160
+ * name, status, budget, and performance metrics.
161
+ *
162
+ * Custom-dispatch tool (platform-routed script name) — see SPEC.md's
163
+ * "Platform resolution" rule 2 and the note on {@link ListCampaignsInput.platform}.
164
+ */
165
+ async list(input = {}) {
166
+ const { platform, scriptName } = resolvePlatformScriptName(
167
+ LIST_CAMPAIGNS_SCRIPT_BY_PLATFORM,
168
+ "google",
169
+ input.platform
170
+ );
171
+ const args = buildArgsFromMapping(LIST_CAMPAIGNS_ARG_MAPPING, input);
172
+ return this.client.runTool(scriptName, args, platform);
173
+ }
174
+ /**
175
+ * Create a Google Ads Search campaign with an ad group, responsive
176
+ * search ad, and keywords — all in one atomic request. Requires at
177
+ * least 3 headlines (max 30 chars) and 2 descriptions (max 90 chars).
178
+ */
179
+ async createSearch(input) {
180
+ const args = buildArgsFromMapping(CREATE_SEARCH_ARG_MAPPING, input);
181
+ return this.client.runTool("google_ads_create_search_campaign", args, "google");
182
+ }
183
+ /**
184
+ * Create a Google Ads Display campaign with responsive display ads.
185
+ * Supports image uploads from URLs.
186
+ */
187
+ async createDisplay(input) {
188
+ const args = buildArgsFromMapping(CREATE_DISPLAY_ARG_MAPPING, input);
189
+ return this.client.runTool("google_ads_create_display_campaign", args, "google");
190
+ }
191
+ /**
192
+ * Create a Google Ads Performance Max campaign. Requires images,
193
+ * headlines, descriptions, and a business name.
194
+ */
195
+ async createPmax(input) {
196
+ const args = buildArgsFromMapping(CREATE_PMAX_ARG_MAPPING, input);
197
+ return this.client.runTool("google_ads_create_pmax_campaign", args, "google");
198
+ }
199
+ /**
200
+ * Pause a campaign by ID.
201
+ *
202
+ * KNOWN ISSUE: see {@link PauseCampaignInput.platform} — always
203
+ * dispatches with `platform=google` regardless of input.
204
+ */
205
+ async pause(input) {
206
+ if (!input?.platform) {
207
+ throw new SynterValidationError("`platform` is required.", "platform");
208
+ }
209
+ const args = buildArgsFromMapping(PAUSE_CAMPAIGN_ARG_MAPPING, input);
210
+ return this.client.runTool("pause_campaign", args, "google");
211
+ }
212
+ /**
213
+ * Update the daily budget for a campaign.
214
+ *
215
+ * KNOWN ISSUE: see {@link UpdateCampaignBudgetInput.platform} — always
216
+ * dispatches with `platform=google` regardless of input.
217
+ */
218
+ async updateBudget(input) {
219
+ if (!input?.platform) {
220
+ throw new SynterValidationError("`platform` is required.", "platform");
221
+ }
222
+ const args = buildArgsFromMapping(UPDATE_BUDGET_ARG_MAPPING, input);
223
+ return this.client.runTool("update_campaign_budget", args, "google");
224
+ }
225
+ };
226
+
227
+ // src/resources/analytics.ts
228
+ var GET_PERFORMANCE_SCRIPT_BY_PLATFORM = {
229
+ google: "pull_google_ads_data",
230
+ meta: "pull_meta_ads",
231
+ linkedin: "pull_linkedin_ads_data",
232
+ microsoft: "pull_microsoft_ads",
233
+ reddit: "pull_reddit_ads",
234
+ tiktok: "pull_tiktok_ads",
235
+ x: "pull_x_ads"
236
+ };
237
+ var GET_PERFORMANCE_ARG_MAPPING = [
238
+ {
239
+ field: "campaign_id",
240
+ flag: "--campaign-id",
241
+ type: "string",
242
+ onlyForPlatform: "google"
243
+ // "campaign_id filtering is only confirmed supported by the Google
244
+ // script; silently dropped for other platforms rather than sent and
245
+ // ignored/rejected." — catalog.json
246
+ },
247
+ { field: "date_range", flag: "--days", type: "string", transform: "dateRangeToDays" }
248
+ ];
249
+ var AnalyticsResource = class {
250
+ constructor(client) {
251
+ this.client = client;
252
+ }
253
+ /**
254
+ * Get performance metrics (impressions, clicks, spend, conversions,
255
+ * ROAS) for campaigns.
256
+ */
257
+ async getPerformance(input = {}) {
258
+ const { platform, scriptName } = resolvePlatformScriptName(
259
+ GET_PERFORMANCE_SCRIPT_BY_PLATFORM,
260
+ "google",
261
+ input.platform
262
+ );
263
+ const args = buildArgsFromMapping(GET_PERFORMANCE_ARG_MAPPING, input, platform);
264
+ return this.client.runTool(scriptName, args, platform);
265
+ }
266
+ /** Get daily spend breakdown across all connected ad accounts. */
267
+ async getDailySpend(input = {}) {
268
+ const platform = input.platform ?? "google";
269
+ const args = ["--platform", platform.toUpperCase()];
270
+ if (input.days !== void 0) {
271
+ args.push("--days", String(input.days));
272
+ }
273
+ return this.client.runTool("get_account_daily_spend", args, platform);
274
+ }
275
+ };
276
+
277
+ // src/resources/keywords.ts
278
+ var ADD_KEYWORDS_ARG_MAPPING = [
279
+ { field: "ad_group_id", flag: "--ad-group-id", type: "string", required: true },
280
+ { field: "keywords", flag: "--keyword", type: "array", required: true },
281
+ { field: "match_type", flag: "--match-type", type: "string" }
282
+ ];
283
+ var ADD_NEGATIVE_KEYWORDS_ARG_MAPPING = [
284
+ { field: "campaign_id", flag: "--campaign-id", type: "string", required: true },
285
+ { field: "keywords", flag: "--keyword", type: "array", required: true },
286
+ { field: "level", flag: "--level", type: "string" }
287
+ ];
288
+ var KeywordsResource = class {
289
+ constructor(client) {
290
+ this.client = client;
291
+ }
292
+ /** Add keywords to a Google Ads campaign or ad group. */
293
+ async add(input) {
294
+ const args = buildArgsFromMapping(ADD_KEYWORDS_ARG_MAPPING, input);
295
+ return this.client.runTool("google_ads_add_keywords", args, "google");
296
+ }
297
+ /** Add negative keywords to block unwanted search terms. */
298
+ async addNegative(input) {
299
+ const args = buildArgsFromMapping(ADD_NEGATIVE_KEYWORDS_ARG_MAPPING, input);
300
+ return this.client.runTool("google_ads_add_negative_keywords", args, "google");
301
+ }
302
+ };
303
+
304
+ // src/resources/conversions.ts
305
+ var CREATE_CONVERSION_ARG_MAPPING = [
306
+ { field: "name", flag: "--name", type: "string", required: true },
307
+ { field: "value", flag: "--value", type: "number" },
308
+ { field: "category", flag: "--category", type: "string" }
309
+ ];
310
+ var DIAGNOSE_TRACKING_ARG_MAPPING = [
311
+ { field: "url", flag: "--url", type: "string", required: true }
312
+ ];
313
+ var ConversionsResource = class {
314
+ constructor(client) {
315
+ this.client = client;
316
+ }
317
+ /**
318
+ * Create a conversion action in Google Ads. Returns the conversion ID
319
+ * and label for GTM setup.
320
+ */
321
+ async create(input) {
322
+ const args = buildArgsFromMapping(CREATE_CONVERSION_ARG_MAPPING, input);
323
+ return this.client.runTool("google_ads_create_conversion", args, "google");
324
+ }
325
+ /** List all conversion actions configured in Google Ads. */
326
+ async list() {
327
+ return this.client.runTool("google_ads_list_conversions", [], "google");
328
+ }
329
+ /**
330
+ * Check if conversion tracking is properly set up on a website.
331
+ * Verifies gtag.js, GTM, and pixel installation.
332
+ *
333
+ * No platform is sent for this tool — it isn't tied to ad-account
334
+ * credentials (SPEC.md's "Platform resolution" rule 3).
335
+ */
336
+ async diagnoseTracking(input) {
337
+ const args = buildArgsFromMapping(DIAGNOSE_TRACKING_ARG_MAPPING, input);
338
+ return this.client.runTool("diagnose_conversion_tracking", args);
339
+ }
340
+ };
341
+
342
+ // src/resources/creative.ts
343
+ var GENERATE_IMAGE_ARG_MAPPING = [
344
+ { field: "prompt", flag: "--prompt", type: "string", required: true },
345
+ { field: "size", flag: "--size", type: "string" },
346
+ { field: "provider", flag: "--provider", type: "string" },
347
+ { field: "name", flag: "--name", type: "string" }
348
+ ];
349
+ var GENERATE_VIDEO_ARG_MAPPING = [
350
+ { field: "product_name", flag: "--product", type: "string", required: true },
351
+ { field: "key_benefit", flag: "--benefit", type: "string", required: true },
352
+ { field: "concept", flag: "--concept", type: "string" },
353
+ { field: "target_audience", flag: "--audience", type: "string" },
354
+ { field: "duration", flag: "--duration", type: "number" },
355
+ { field: "provider", flag: "--provider", type: "string" }
356
+ ];
357
+ var CreativeResource = class {
358
+ constructor(client) {
359
+ this.client = client;
360
+ }
361
+ /** Generate an AI image for ad creatives using Imagen 4, Flux, or Stable Diffusion. */
362
+ async generateImage(input) {
363
+ const args = buildArgsFromMapping(GENERATE_IMAGE_ARG_MAPPING, input);
364
+ return this.client.runTool("generate_image", args);
365
+ }
366
+ /** Generate an AI video ad using Veo, Runway, or Luma. Great for YouTube and social ads. */
367
+ async generateVideo(input) {
368
+ const args = buildArgsFromMapping(GENERATE_VIDEO_ARG_MAPPING, input);
369
+ return this.client.runTool("generate_video_ad", args);
370
+ }
371
+ };
372
+
373
+ // src/resources/meta.ts
374
+ var CREATE_META_CAMPAIGN_ARG_MAPPING = [
375
+ { field: "name", flag: "--name", type: "string", required: true },
376
+ { field: "objective", flag: "--objective", type: "string", required: true },
377
+ { field: "daily_budget", flag: "--daily-budget", type: "number", required: true }
378
+ ];
379
+ var MetaResource = class {
380
+ constructor(client) {
381
+ this.client = client;
382
+ }
383
+ /** Create a Meta (Facebook/Instagram) advertising campaign. */
384
+ async createCampaign(input) {
385
+ const args = buildArgsFromMapping(CREATE_META_CAMPAIGN_ARG_MAPPING, input);
386
+ return this.client.runTool("meta_ads_create_campaign", args, "meta");
387
+ }
388
+ };
389
+
390
+ // src/resources/linkedin.ts
391
+ var CREATE_LINKEDIN_CAMPAIGN_ARG_MAPPING = [
392
+ { field: "name", flag: "--name", type: "string", required: true },
393
+ { field: "objective", flag: "--objective", type: "string", required: true },
394
+ { field: "daily_budget", flag: "--daily-budget", type: "number", required: true },
395
+ { field: "target_company_sizes", flag: "--company-size", type: "array" },
396
+ { field: "target_industries", flag: "--industry", type: "array" },
397
+ { field: "target_job_functions", flag: "--job-function", type: "array" }
398
+ ];
399
+ var LinkedInResource = class {
400
+ constructor(client) {
401
+ this.client = client;
402
+ }
403
+ /** Create a LinkedIn Ads campaign for B2B advertising. */
404
+ async createCampaign(input) {
405
+ const args = buildArgsFromMapping(CREATE_LINKEDIN_CAMPAIGN_ARG_MAPPING, input);
406
+ return this.client.runTool("linkedin_ads_create_campaign_complete", args, "linkedin");
407
+ }
408
+ };
409
+
410
+ // src/resources/reddit.ts
411
+ var CREATE_REDDIT_CAMPAIGN_ARG_MAPPING = [
412
+ { field: "name", flag: "--name", type: "string", required: true },
413
+ { field: "objective", flag: "--objective", type: "string", required: true },
414
+ { field: "daily_budget", flag: "--daily-budget", type: "number", required: true },
415
+ { field: "subreddits", flag: "--subreddit", type: "array" },
416
+ { field: "interests", flag: "--interest", type: "array" }
417
+ ];
418
+ var RedditResource = class {
419
+ constructor(client) {
420
+ this.client = client;
421
+ }
422
+ /** Create a Reddit Ads campaign for community-based advertising. */
423
+ async createCampaign(input) {
424
+ const args = buildArgsFromMapping(CREATE_REDDIT_CAMPAIGN_ARG_MAPPING, input);
425
+ return this.client.runTool("reddit_ads_create_campaign", args, "reddit");
426
+ }
427
+ };
428
+
429
+ // src/resources/audiences.ts
430
+ var CONSENT_WARNING = "[@synterai/sdk-js] i_have_consent was not provided. Confirm lawful basis under GDPR/CCPA and the destination platform's terms for every identifier before staging or syncing PII, then pass `i_have_consent: true`. (Soft-launch notice \u2014 mirrors the backend's own behavior; this call is not blocked.)";
431
+ var ARTIFACT_ENDPOINT = "https://api.syntermedia.ai/artifacts/audience-sync-input";
432
+ var SYNC_AUDIENCE_SCRIPT_BY_PLATFORM = {
433
+ google: "google_ads_create_customer_match",
434
+ meta: "meta_ads_create_audience",
435
+ linkedin: "linkedin_ads_create_matched_audience",
436
+ microsoft: "microsoft_ads_create_audience",
437
+ reddit: "reddit_ads_create_audience",
438
+ tiktok: "tiktok_ads_create_audience",
439
+ x: "x_ads_create_audience"
440
+ };
441
+ var MANAGE_AUDIENCE_ARG_MAPPING = [
442
+ { field: "action", flag: "--action", type: "string", required: true },
443
+ { field: "name", flag: "--name", type: "string" },
444
+ { field: "emails", flag: "--emails", type: "string" },
445
+ { field: "phones", flag: "--phones", type: "string" },
446
+ { field: "company_domains", flag: "--company-domains", type: "string" },
447
+ { field: "audience_id", flag: "--audience-id", type: "string" },
448
+ { field: "membership_lifespan", flag: "--membership-lifespan", type: "number" },
449
+ { field: "job_resource_name", flag: "--job-resource-name", type: "string" }
450
+ ];
451
+ var AudiencesResource = class {
452
+ constructor(client) {
453
+ this.client = client;
454
+ }
455
+ /**
456
+ * Stage a hashed-PII payload (newline-delimited identifiers — SHA-256
457
+ * emails, phones, or raw MAIDs) in Synter's private artifact store and
458
+ * return an opaque `artifact_id`. Pass that id to `sync()` via
459
+ * `*_artifact_id` parameters. FREE. Use instead of pasting hashed
460
+ * identifiers inline when over ~500 entries.
461
+ *
462
+ * Bypasses the primary `tools/run` dispatcher: different host
463
+ * (`https://api.syntermedia.ai`) and a different auth header
464
+ * (`X-Synter-Key`, not `Authorization: Bearer`) — see SPEC.md/catalog.json
465
+ * `transport.special`.
466
+ */
467
+ async stageArtifact(input) {
468
+ if (!input?.body) {
469
+ throw new SynterValidationError("`body` is required.", "body");
470
+ }
471
+ if (input.i_have_consent === void 0) {
472
+ console.warn(CONSENT_WARNING);
473
+ }
474
+ const payload = { body: input.body };
475
+ if (input.script_name !== void 0) payload.script_name = input.script_name;
476
+ if (input.ttl_seconds !== void 0) payload.ttl_seconds = input.ttl_seconds;
477
+ if (input.i_have_consent !== void 0) payload.i_have_consent = input.i_have_consent;
478
+ return this.client.sendAbsolute(ARTIFACT_ENDPOINT, {
479
+ method: "POST",
480
+ headers: {
481
+ "X-Synter-Key": this.client.getApiKey(),
482
+ "Content-Type": "application/json"
483
+ },
484
+ body: JSON.stringify(payload)
485
+ });
486
+ }
487
+ /**
488
+ * Upload an audience to an ad platform. Accepts inline hashed
489
+ * identifiers, raw mobile_ids, company_domains, a public csv_url, or
490
+ * `*_artifact_id` references staged via `stageArtifact()`. Costs 10
491
+ * credits on success.
492
+ *
493
+ * Custom handler per catalog.json's `dispatch.argBuildingRules` — does
494
+ * not go through the generic argMapping path.
495
+ */
496
+ async sync(input) {
497
+ if (!input?.platform) {
498
+ throw new SynterValidationError("`platform` is required.", "platform");
499
+ }
500
+ const scriptName = SYNC_AUDIENCE_SCRIPT_BY_PLATFORM[input.platform];
501
+ if (!scriptName) {
502
+ throw new SynterValidationError(
503
+ `Unsupported platform \`${input.platform}\`. Expected one of: ${Object.keys(SYNC_AUDIENCE_SCRIPT_BY_PLATFORM).join(", ")}.`,
504
+ "platform"
505
+ );
506
+ }
507
+ const args = [];
508
+ if (input.audience_name !== void 0) args.push("--name", input.audience_name);
509
+ if (input.account_id !== void 0) args.push("--account-id", input.account_id);
510
+ if (input.hashed_emails !== void 0) args.push("--hashed-emails", input.hashed_emails);
511
+ if (input.hashed_emails_artifact_id !== void 0) {
512
+ args.push("--hashed-emails-artifact-id", input.hashed_emails_artifact_id);
513
+ }
514
+ if (input.hashed_phones !== void 0) args.push("--hashed-phones", input.hashed_phones);
515
+ if (input.hashed_phones_artifact_id !== void 0) {
516
+ args.push("--hashed-phones-artifact-id", input.hashed_phones_artifact_id);
517
+ if (input.platform === "google" || input.platform === "meta") {
518
+ args.push("--phones-artifact-id", input.hashed_phones_artifact_id);
519
+ }
520
+ }
521
+ if (input.mobile_ids !== void 0) args.push("--mobile-ids", input.mobile_ids);
522
+ if (input.mobile_ids_artifact_id !== void 0 && input.platform === "meta") {
523
+ args.push("--mobile-ids-artifact-id", input.mobile_ids_artifact_id);
524
+ }
525
+ if (input.company_domains !== void 0) args.push("--company-domains", input.company_domains);
526
+ if (input.csv_url !== void 0) args.push("--csv-url", input.csv_url);
527
+ if (input.customer_file_source !== void 0 && input.platform === "meta") {
528
+ args.push("--customer-file-source", input.customer_file_source);
529
+ }
530
+ if (input.platform === "meta") {
531
+ args.push("--subtype", "CUSTOM");
532
+ }
533
+ if (input.i_have_consent === void 0) {
534
+ console.warn(CONSENT_WARNING);
535
+ }
536
+ const body = { script_name: scriptName, args, platform: input.platform };
537
+ if (input.i_have_consent !== void 0) {
538
+ body.i_have_consent = input.i_have_consent;
539
+ }
540
+ return this.client.request("POST", "/api/v1/tools/run", { body });
541
+ }
542
+ /**
543
+ * Create, upload to, or list first-party audiences in Google Ads via
544
+ * the Data Manager API. Supports Customer Match lists from emails,
545
+ * phone numbers, and company domains.
546
+ */
547
+ async manage(input) {
548
+ const args = buildArgsFromMapping(MANAGE_AUDIENCE_ARG_MAPPING, input);
549
+ return this.client.runTool("google_ads_data_manager", args, "google");
550
+ }
551
+ };
552
+
553
+ // src/toolbox/amp-tools.ts
554
+ var AmpToolbox = class {
555
+ constructor(client, config = {}) {
556
+ this.client = client;
557
+ this.config = config;
558
+ this.config = {
559
+ aiModel: "gpt-4",
560
+ autoOptimize: true,
561
+ optimizeInterval: 24,
562
+ enableABTesting: true,
563
+ ...config
564
+ };
565
+ }
566
+ /**
567
+ * Generate a complete campaign from a brief description
568
+ *
569
+ * @example
570
+ * ```ts
571
+ * const campaign = await amp.generateCampaign({
572
+ * product: 'AI-powered email marketing platform',
573
+ * audience: 'SaaS marketers, 25-45, tech-savvy',
574
+ * goals: ['lead-generation', 'brand-awareness'],
575
+ * budget: { total: 10000, currency: 'USD' },
576
+ * platforms: ['google', 'linkedin'],
577
+ * brandVoice: 'professional, innovative, data-driven'
578
+ * });
579
+ * ```
580
+ */
581
+ async generateCampaign(brief) {
582
+ const response = await this.client.request("POST", "/amp/generate-campaign", {
583
+ body: {
584
+ ...brief,
585
+ aiModel: this.config.aiModel
586
+ }
587
+ });
588
+ return response;
589
+ }
590
+ /**
591
+ * Generate creative variations for A/B testing
592
+ *
593
+ * @example
594
+ * ```ts
595
+ * const variants = await amp.generateCreativeVariants({
596
+ * baseCreative: {
597
+ * headline: 'Transform Your Email Marketing',
598
+ * description: 'AI-powered automation for better results'
599
+ * },
600
+ * count: 5,
601
+ * platform: 'google'
602
+ * });
603
+ * ```
604
+ */
605
+ async generateCreativeVariants(options) {
606
+ const response = await this.client.request("POST", "/amp/generate-creatives", {
607
+ body: {
608
+ ...options,
609
+ aiModel: this.config.aiModel
610
+ }
611
+ });
612
+ return response.variants;
613
+ }
614
+ /**
615
+ * Analyze campaign performance and get AI-powered optimization recommendations
616
+ *
617
+ * @example
618
+ * ```ts
619
+ * const recommendations = await amp.getOptimizationRecommendations({
620
+ * campaignIds: ['cmp_123', 'cmp_456'],
621
+ * window: '7d',
622
+ * minConfidence: 0.7
623
+ * });
624
+ * ```
625
+ */
626
+ async getOptimizationRecommendations(options) {
627
+ const response = await this.client.request(
628
+ "POST",
629
+ "/amp/optimize",
630
+ {
631
+ body: {
632
+ ...options,
633
+ aiModel: this.config.aiModel
634
+ }
635
+ }
636
+ );
637
+ return response.recommendations;
638
+ }
639
+ /**
640
+ * Auto-optimize campaigns based on performance data
641
+ *
642
+ * @example
643
+ * ```ts
644
+ * const result = await amp.autoOptimize({
645
+ * campaignIds: ['cmp_123'],
646
+ * dryRun: true, // Preview changes first
647
+ * optimizationTypes: ['budget', 'bidding']
648
+ * });
649
+ * ```
650
+ */
651
+ async autoOptimize(options) {
652
+ const response = await this.client.request("POST", "/amp/auto-optimize", {
653
+ body: {
654
+ ...options,
655
+ aiModel: this.config.aiModel
656
+ }
657
+ });
658
+ return response;
659
+ }
660
+ /**
661
+ * Generate keyword suggestions based on product/service
662
+ *
663
+ * @example
664
+ * ```ts
665
+ * const keywords = await amp.generateKeywords({
666
+ * product: 'AI email marketing platform',
667
+ * audience: 'SaaS marketers',
668
+ * count: 50
669
+ * });
670
+ * ```
671
+ */
672
+ async generateKeywords(options) {
673
+ return this.client.request("POST", "/amp/generate-keywords", {
674
+ body: {
675
+ ...options,
676
+ count: options.count || 30,
677
+ aiModel: this.config.aiModel
678
+ }
679
+ });
680
+ }
681
+ /**
682
+ * Analyze competitor ads and get insights
683
+ *
684
+ * @example
685
+ * ```ts
686
+ * const analysis = await amp.analyzeCompetitors({
687
+ * competitors: ['competitor1.com', 'competitor2.com'],
688
+ * industry: 'SaaS'
689
+ * });
690
+ * ```
691
+ */
692
+ async analyzeCompetitors(options) {
693
+ return this.client.request("POST", "/amp/analyze-competitors", {
694
+ body: options
695
+ });
696
+ }
697
+ /**
698
+ * Create an A/B test for campaign creatives
699
+ *
700
+ * @example
701
+ * ```ts
702
+ * const test = await amp.createABTest({
703
+ * campaignId: 'cmp_123',
704
+ * variants: [
705
+ * { headline: 'Version A', description: 'Test A' },
706
+ * { headline: 'Version B', description: 'Test B' }
707
+ * ],
708
+ * trafficSplit: [50, 50],
709
+ * duration: '7d'
710
+ * });
711
+ * ```
712
+ */
713
+ async createABTest(options) {
714
+ return this.client.request("POST", "/amp/ab-test", {
715
+ body: options
716
+ });
717
+ }
718
+ /**
719
+ * Get A/B test results and winner
720
+ *
721
+ * @example
722
+ * ```ts
723
+ * const results = await amp.getABTestResults('test_123');
724
+ * ```
725
+ */
726
+ async getABTestResults(testId) {
727
+ return this.client.request("GET", `/amp/ab-test/${testId}`);
728
+ }
729
+ /**
730
+ * Generate audience personas based on campaign data
731
+ *
732
+ * @example
733
+ * ```ts
734
+ * const personas = await amp.generatePersonas({
735
+ * campaignIds: ['cmp_123'],
736
+ * count: 3
737
+ * });
738
+ * ```
739
+ */
740
+ async generatePersonas(options) {
741
+ return this.client.request("POST", "/amp/generate-personas", {
742
+ body: {
743
+ ...options,
744
+ count: options.count || 3
745
+ }
746
+ });
747
+ }
748
+ /**
749
+ * Predict campaign performance before launch
750
+ *
751
+ * @example
752
+ * ```ts
753
+ * const prediction = await amp.predictPerformance({
754
+ * platform: 'google',
755
+ * budget: { daily: 100, total: 3000 },
756
+ * targeting: { keywords: ['email marketing', 'automation'] },
757
+ * creative: { headline: 'Transform Your Email Marketing' }
758
+ * });
759
+ * ```
760
+ */
761
+ async predictPerformance(options) {
762
+ return this.client.request("POST", "/amp/predict-performance", {
763
+ body: options
764
+ });
765
+ }
766
+ /**
767
+ * Get intelligent budget allocation across platforms
768
+ *
769
+ * @example
770
+ * ```ts
771
+ * const allocation = await amp.optimizeBudgetAllocation({
772
+ * totalBudget: 10000,
773
+ * platforms: ['google', 'linkedin', 'reddit'],
774
+ * goal: 'conversions'
775
+ * });
776
+ * ```
777
+ */
778
+ async optimizeBudgetAllocation(options) {
779
+ return this.client.request("POST", "/amp/optimize-budget", {
780
+ body: options
781
+ });
782
+ }
783
+ };
784
+
785
+ // src/client.ts
786
+ var DEFAULT_BASE_URL = "https://syntermedia.ai";
787
+ var DEFAULT_TIMEOUT_MS = 3e4;
788
+ var DEFAULT_MAX_RETRIES = 3;
789
+ var RETRY_BASE_MS = 1e3;
790
+ var RETRY_CAP_MS = 1e4;
791
+ function backoffMs(attempt) {
792
+ return Math.min(RETRY_BASE_MS * 2 ** attempt, RETRY_CAP_MS);
793
+ }
794
+ function sleep(ms) {
795
+ return new Promise((resolve) => setTimeout(resolve, ms));
796
+ }
797
+ var Synter = class {
798
+ constructor(config, ampConfig) {
799
+ const fullConfig = typeof config === "string" ? { apiKey: config } : config;
800
+ if (!fullConfig?.apiKey) {
801
+ throw new SynterValidationError("`apiKey` is required to construct a Synter client.", "apiKey");
802
+ }
803
+ this.apiKey = fullConfig.apiKey;
804
+ this.baseUrl = fullConfig.baseUrl ?? DEFAULT_BASE_URL;
805
+ this.timeout = fullConfig.timeout ?? DEFAULT_TIMEOUT_MS;
806
+ this.maxRetries = fullConfig.maxRetries ?? DEFAULT_MAX_RETRIES;
807
+ this.fetchImpl = fullConfig.fetch ?? globalThis.fetch.bind(globalThis);
808
+ this.campaigns = new CampaignsResource(this);
809
+ this.analytics = new AnalyticsResource(this);
810
+ this.keywords = new KeywordsResource(this);
811
+ this.conversions = new ConversionsResource(this);
812
+ this.creative = new CreativeResource(this);
813
+ this.meta = new MetaResource(this);
814
+ this.linkedin = new LinkedInResource(this);
815
+ this.reddit = new RedditResource(this);
816
+ this.audiences = new AudiencesResource(this);
817
+ this.amp = new AmpToolbox(this, ampConfig);
818
+ }
819
+ // ---------------------------------------------------------------------
820
+ // Primary dispatcher: POST /api/v1/tools/run
821
+ // ---------------------------------------------------------------------
822
+ /**
823
+ * Low-level dispatcher used by every typed resource method. Not usually
824
+ * called directly by SDK consumers — use `execute()` for the public
825
+ * escape hatch, or a typed resource method for everything else.
826
+ */
827
+ async runTool(scriptName, args = [], platform) {
828
+ const body = { script_name: scriptName, args };
829
+ if (platform !== void 0) {
830
+ body.platform = platform;
831
+ }
832
+ return this.sendWithRetry(`${this.baseUrl}/api/v1/tools/run`, {
833
+ method: "POST",
834
+ headers: this.authHeaders(),
835
+ body: JSON.stringify(body)
836
+ });
837
+ }
838
+ /**
839
+ * Universal escape hatch mirroring the `run_tool` catalog tool: run any
840
+ * of the 140+ backend scripts by name. `args` is an idiomatic flag-name
841
+ * map (not a raw CLI string array) — converted internally the same way
842
+ * every other typed method builds its wire args.
843
+ */
844
+ async execute(scriptName, args = {}, platform) {
845
+ if (!scriptName) {
846
+ throw new SynterValidationError("`scriptName` is required.", "script_name");
847
+ }
848
+ return this.runTool(scriptName, flattenArgsToFlags(args), platform);
849
+ }
850
+ // ---------------------------------------------------------------------
851
+ // `utility` category — per SPEC.md's naming table these live at the
852
+ // top level of the client, not under a nested namespace.
853
+ // ---------------------------------------------------------------------
854
+ /** List all connected ad accounts across platforms. */
855
+ async listAdAccounts() {
856
+ return this.runTool("google_ads_list_customers", [], "google");
857
+ }
858
+ /**
859
+ * Upload an image as an asset for use in ads.
860
+ *
861
+ * KNOWN ISSUE (SPEC.md "Known issues" #2): live dispatch hardcodes
862
+ * `platform=google` regardless of `input.platform`. Preserved verbatim.
863
+ */
864
+ async uploadImage(input) {
865
+ if (!input?.image_url) {
866
+ throw new SynterValidationError("`image_url` is required.", "image_url");
867
+ }
868
+ if (!input?.asset_name) {
869
+ throw new SynterValidationError("`asset_name` is required.", "asset_name");
870
+ }
871
+ const args = ["--image-url", input.image_url, "--asset-name", input.asset_name];
872
+ return this.runTool("google_ads_upload_image_asset", args, "google");
873
+ }
874
+ /**
875
+ * List all landing pages for the authenticated workspace.
876
+ *
877
+ * Bypasses the primary `tools/run` dispatcher — special endpoint per
878
+ * SPEC.md/catalog.json `transport.special`: `GET /api/v1/landing-pages`.
879
+ */
880
+ async listLandingPages() {
881
+ return this.sendWithRetry(`${this.baseUrl}/api/v1/landing-pages`, {
882
+ method: "GET",
883
+ headers: this.authHeaders()
884
+ });
885
+ }
886
+ // ---------------------------------------------------------------------
887
+ // Internals used by resource classes and AmpToolbox
888
+ // ---------------------------------------------------------------------
889
+ authHeaders() {
890
+ return {
891
+ Authorization: `Bearer ${this.apiKey}`,
892
+ "Content-Type": "application/json"
893
+ };
894
+ }
895
+ /** The raw API key, for the small number of tools that need a non-Bearer header (e.g. `stage_audience_artifact`). */
896
+ getApiKey() {
897
+ return this.apiKey;
898
+ }
899
+ /**
900
+ * Low-level generic HTTP helper retained for `AmpToolbox` and any other
901
+ * advanced/internal callers that need to hit an arbitrary path relative
902
+ * to the configured base URL, with the same retry/timeout/error
903
+ * semantics as the primary dispatcher.
904
+ */
905
+ async request(method, path, options = {}) {
906
+ const url = new URL(`${this.baseUrl}${path}`);
907
+ if (options.query) {
908
+ for (const [key, value] of Object.entries(options.query)) {
909
+ if (value !== void 0 && value !== null) {
910
+ url.searchParams.set(key, String(value));
911
+ }
912
+ }
913
+ }
914
+ return this.sendWithRetry(url.toString(), {
915
+ method,
916
+ headers: { ...this.authHeaders(), ...options.headers },
917
+ body: options.body !== void 0 ? JSON.stringify(options.body) : void 0
918
+ });
919
+ }
920
+ /**
921
+ * Sends a request to an arbitrary absolute URL with arbitrary headers —
922
+ * used by the two special-endpoint tools (`list_landing_pages` uses
923
+ * `request`/`runTool` against the default base URL; `stage_audience_artifact`
924
+ * needs a different host and a different auth header entirely).
925
+ */
926
+ async sendAbsolute(url, init) {
927
+ return this.sendWithRetry(url, init);
928
+ }
929
+ /**
930
+ * Core transport: timeout + retry policy from SPEC.md — up to 3 retries,
931
+ * exponential backoff (1s base, 10s cap), retrying only 429 (honoring
932
+ * `Retry-After`) and network/timeout errors. Never retries other 4xx
933
+ * (or 5xx — not listed in SPEC.md's `retryOn`).
934
+ */
935
+ async sendWithRetry(url, init) {
936
+ let attempt = 0;
937
+ for (; ; ) {
938
+ const controller = new AbortController();
939
+ const timer = setTimeout(() => controller.abort(), this.timeout);
940
+ let response;
941
+ try {
942
+ response = await this.fetchImpl(url, { ...init, signal: controller.signal });
943
+ } catch (err) {
944
+ clearTimeout(timer);
945
+ if (attempt >= this.maxRetries) {
946
+ const message = err instanceof Error ? err.message : "Network request failed.";
947
+ throw new SynterError(message, 0, null, null);
948
+ }
949
+ await sleep(backoffMs(attempt));
950
+ attempt += 1;
951
+ continue;
952
+ }
953
+ clearTimeout(timer);
954
+ if (response.status === 429) {
955
+ if (attempt >= this.maxRetries) {
956
+ throw await this.toSynterError(response);
957
+ }
958
+ const retryAfterHeader = response.headers.get("Retry-After");
959
+ const retryAfterSeconds = retryAfterHeader !== null ? Number(retryAfterHeader) : NaN;
960
+ const delayMs = Number.isFinite(retryAfterSeconds) ? Math.max(0, retryAfterSeconds) * 1e3 : backoffMs(attempt);
961
+ await sleep(delayMs);
962
+ attempt += 1;
963
+ continue;
964
+ }
965
+ if (!response.ok) {
966
+ throw await this.toSynterError(response);
967
+ }
968
+ if (response.status === 204) {
969
+ return void 0;
970
+ }
971
+ return await response.json();
972
+ }
973
+ }
974
+ async toSynterError(response) {
975
+ let body = null;
976
+ try {
977
+ body = await response.json();
978
+ } catch {
979
+ }
980
+ const record = (body && typeof body === "object" ? body : {}) ?? {};
981
+ const message = typeof record.error === "string" && record.error || typeof record.message === "string" && record.message || response.statusText || "Request failed.";
982
+ const code = typeof record.code === "string" ? record.code : null;
983
+ const details = record.details && typeof record.details === "object" ? record.details : null;
984
+ return new SynterError(message, response.status, code, details);
985
+ }
986
+ };
987
+
988
+ // src/utils/utm.ts
989
+ var PLATFORM_MACROS = {
990
+ google: {
991
+ "{{campaign_id}}": "{campaignid}",
992
+ "{{adgroup_id}}": "{adgroupid}",
993
+ "{{ad_id}}": "{creative}",
994
+ "{{keyword}}": "{keyword}",
995
+ "{{match_type}}": "{matchtype}",
996
+ "{{network}}": "{network}",
997
+ "{{device}}": "{device}",
998
+ "{{placement}}": "{placement}"
999
+ },
1000
+ reddit: {
1001
+ "{{campaign_id}}": "{{campaign_id}}",
1002
+ "{{adgroup_id}}": "{{adgroup_id}}",
1003
+ "{{ad_id}}": "{{ad_id}}"
1004
+ },
1005
+ x: {
1006
+ "{{campaign_id}}": "{campaign_id}",
1007
+ "{{line_item_id}}": "{line_item_id}",
1008
+ "{{creative_id}}": "{creative_id}"
1009
+ }
1010
+ };
1011
+ function buildUTMParameters(template, platform) {
1012
+ const macros = PLATFORM_MACROS[platform];
1013
+ const replaceTemplateVars = (value) => {
1014
+ if (!value) return value;
1015
+ let result = value;
1016
+ for (const [templateVar, platformMacro] of Object.entries(macros)) {
1017
+ result = result.replace(new RegExp(templateVar, "g"), platformMacro);
1018
+ }
1019
+ return result;
1020
+ };
1021
+ return {
1022
+ source: replaceTemplateVars(template.source) || platform,
1023
+ medium: replaceTemplateVars(template.medium) || "cpc",
1024
+ campaign: replaceTemplateVars(template.campaign) || "",
1025
+ content: replaceTemplateVars(template.content),
1026
+ term: replaceTemplateVars(template.term)
1027
+ };
1028
+ }
1029
+ function buildTrackingURL(baseUrl, utmParams) {
1030
+ const url = new URL(baseUrl);
1031
+ url.searchParams.set("utm_source", utmParams.source);
1032
+ url.searchParams.set("utm_medium", utmParams.medium);
1033
+ url.searchParams.set("utm_campaign", utmParams.campaign);
1034
+ if (utmParams.content) {
1035
+ url.searchParams.set("utm_content", utmParams.content);
1036
+ }
1037
+ if (utmParams.term) {
1038
+ url.searchParams.set("utm_term", utmParams.term);
1039
+ }
1040
+ return url.toString();
1041
+ }
1042
+ function extractUTMParameters(url) {
1043
+ const urlObj = new URL(url);
1044
+ return {
1045
+ source: urlObj.searchParams.get("utm_source") || void 0,
1046
+ medium: urlObj.searchParams.get("utm_medium") || void 0,
1047
+ campaign: urlObj.searchParams.get("utm_campaign") || void 0,
1048
+ content: urlObj.searchParams.get("utm_content") || void 0,
1049
+ term: urlObj.searchParams.get("utm_term") || void 0
1050
+ };
1051
+ }
1052
+ function validateUTMTemplate(template) {
1053
+ const errors = [];
1054
+ if (!template.source) {
1055
+ errors.push("utm_source is required");
1056
+ }
1057
+ if (!template.medium) {
1058
+ errors.push("utm_medium is required");
1059
+ }
1060
+ if (!template.campaign) {
1061
+ errors.push("utm_campaign is required");
1062
+ }
1063
+ const invalidChars = /[^a-zA-Z0-9\-_.{}]/;
1064
+ Object.entries(template).forEach(([key, value]) => {
1065
+ if (value && invalidChars.test(value)) {
1066
+ errors.push(`${key} contains invalid characters (use only alphanumeric, -, _, ., {})`);
1067
+ }
1068
+ });
1069
+ return {
1070
+ valid: errors.length === 0,
1071
+ errors
1072
+ };
1073
+ }
1074
+ function generateDefaultUTMTemplate(platform, campaignName) {
1075
+ const sanitizedName = campaignName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
1076
+ return {
1077
+ source: platform,
1078
+ medium: "cpc",
1079
+ campaign: sanitizedName,
1080
+ content: "{{ad_id}}",
1081
+ term: platform === "google" ? "{{keyword}}" : void 0
1082
+ };
1083
+ }
1084
+
1085
+ // src/utils/conversion-tracking.ts
1086
+ var CLICK_ID_PARAMS = {
1087
+ google: ["gclid", "wbraid", "gbraid"],
1088
+ reddit: ["rdt_cid"],
1089
+ x: ["twclid"]
1090
+ };
1091
+ function detectClickId(urlOrParams) {
1092
+ let params;
1093
+ if (typeof urlOrParams === "string") {
1094
+ try {
1095
+ const url = new URL(urlOrParams);
1096
+ params = Object.fromEntries(url.searchParams.entries());
1097
+ } catch {
1098
+ return { platform: null, clickId: null, paramName: null };
1099
+ }
1100
+ } else {
1101
+ params = urlOrParams;
1102
+ }
1103
+ for (const [platform, paramNames] of Object.entries(CLICK_ID_PARAMS)) {
1104
+ for (const paramName of paramNames) {
1105
+ const clickId = params[paramName];
1106
+ if (clickId) {
1107
+ return {
1108
+ platform,
1109
+ clickId,
1110
+ paramName
1111
+ };
1112
+ }
1113
+ }
1114
+ }
1115
+ return { platform: null, clickId: null, paramName: null };
1116
+ }
1117
+ function extractTrackingParams(url, referrer) {
1118
+ const urlObj = new URL(url);
1119
+ const params = Object.fromEntries(urlObj.searchParams.entries());
1120
+ const clickIds = {};
1121
+ for (const [platform, paramNames] of Object.entries(CLICK_ID_PARAMS)) {
1122
+ for (const paramName of paramNames) {
1123
+ if (params[paramName]) {
1124
+ clickIds[platform] = params[paramName];
1125
+ break;
1126
+ }
1127
+ }
1128
+ }
1129
+ return {
1130
+ clickIds,
1131
+ utm: {
1132
+ source: params.utm_source,
1133
+ medium: params.utm_medium,
1134
+ campaign: params.utm_campaign,
1135
+ content: params.utm_content,
1136
+ term: params.utm_term
1137
+ },
1138
+ referrer
1139
+ };
1140
+ }
1141
+ function validateClickId(platform, clickId) {
1142
+ switch (platform) {
1143
+ case "google":
1144
+ return /^[a-zA-Z0-9\-_]+$/.test(clickId);
1145
+ case "reddit":
1146
+ return /^t3_[a-z0-9]+$/i.test(clickId) || /^[a-zA-Z0-9\-_]+$/.test(clickId);
1147
+ case "x":
1148
+ return /^[a-zA-Z0-9\-_]+$/.test(clickId);
1149
+ default:
1150
+ return false;
1151
+ }
1152
+ }
1153
+ function buildConversionPayload(params) {
1154
+ return {
1155
+ platform: params.platform,
1156
+ clickId: params.clickId,
1157
+ conversionName: params.event,
1158
+ conversionTime: params.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
1159
+ value: params.value,
1160
+ currency: params.currency || "USD",
1161
+ orderId: params.orderId
1162
+ };
1163
+ }
1164
+ var EVENT_NAME_MAP = {
1165
+ purchase: {
1166
+ google: "purchase",
1167
+ reddit: "Purchase",
1168
+ x: "tw-purchase"
1169
+ },
1170
+ signup: {
1171
+ google: "sign_up",
1172
+ reddit: "SignUp",
1173
+ x: "tw-signup"
1174
+ },
1175
+ "add-to-cart": {
1176
+ google: "add_to_cart",
1177
+ reddit: "AddToCart",
1178
+ x: "tw-add-to-cart"
1179
+ },
1180
+ "page-view": {
1181
+ google: "page_view",
1182
+ reddit: "PageVisit",
1183
+ x: "tw-page-view"
1184
+ }
1185
+ };
1186
+ function normalizeEventName(eventName, platform) {
1187
+ const normalized = EVENT_NAME_MAP[eventName.toLowerCase()];
1188
+ return normalized?.[platform] || eventName;
1189
+ }
1190
+
1191
+ // src/utils/analytics-integrations.ts
1192
+ var PostHogAdapter = class {
1193
+ constructor(apiKey, host = "https://app.posthog.com") {
1194
+ this.apiKey = apiKey;
1195
+ this.host = host;
1196
+ }
1197
+ async track(event) {
1198
+ const payload = {
1199
+ api_key: this.apiKey,
1200
+ event: event.event,
1201
+ properties: event.properties,
1202
+ timestamp: event.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
1203
+ distinct_id: event.userId || event.anonymousId
1204
+ };
1205
+ await fetch(`${this.host}/capture/`, {
1206
+ method: "POST",
1207
+ headers: { "Content-Type": "application/json" },
1208
+ body: JSON.stringify(payload)
1209
+ });
1210
+ }
1211
+ async identify(userId, traits) {
1212
+ const payload = {
1213
+ api_key: this.apiKey,
1214
+ event: "$identify",
1215
+ distinct_id: userId,
1216
+ properties: {
1217
+ $set: traits
1218
+ }
1219
+ };
1220
+ await fetch(`${this.host}/capture/`, {
1221
+ method: "POST",
1222
+ headers: { "Content-Type": "application/json" },
1223
+ body: JSON.stringify(payload)
1224
+ });
1225
+ }
1226
+ };
1227
+ var HeapAdapter = class {
1228
+ constructor(appId) {
1229
+ this.appId = appId;
1230
+ }
1231
+ async track(event) {
1232
+ const payload = {
1233
+ app_id: this.appId,
1234
+ identity: event.userId || event.anonymousId,
1235
+ event: event.event,
1236
+ properties: event.properties,
1237
+ timestamp: event.timestamp
1238
+ };
1239
+ await fetch("https://heapanalytics.com/api/track", {
1240
+ method: "POST",
1241
+ headers: { "Content-Type": "application/json" },
1242
+ body: JSON.stringify(payload)
1243
+ });
1244
+ }
1245
+ async identify(userId, traits) {
1246
+ const payload = {
1247
+ app_id: this.appId,
1248
+ identity: userId,
1249
+ properties: traits
1250
+ };
1251
+ await fetch("https://heapanalytics.com/api/add_user_properties", {
1252
+ method: "POST",
1253
+ headers: { "Content-Type": "application/json" },
1254
+ body: JSON.stringify(payload)
1255
+ });
1256
+ }
1257
+ };
1258
+ var MixpanelAdapter = class {
1259
+ constructor(token) {
1260
+ this.token = token;
1261
+ }
1262
+ async track(event) {
1263
+ const payload = {
1264
+ event: event.event,
1265
+ properties: {
1266
+ ...event.properties,
1267
+ token: this.token,
1268
+ distinct_id: event.userId || event.anonymousId,
1269
+ time: event.timestamp ? new Date(event.timestamp).getTime() : Date.now()
1270
+ }
1271
+ };
1272
+ const data = Buffer.from(JSON.stringify(payload)).toString("base64");
1273
+ await fetch(`https://api.mixpanel.com/track?data=${data}`, {
1274
+ method: "GET"
1275
+ });
1276
+ }
1277
+ async identify(userId, traits) {
1278
+ const payload = {
1279
+ $token: this.token,
1280
+ $distinct_id: userId,
1281
+ $set: traits
1282
+ };
1283
+ const data = Buffer.from(JSON.stringify(payload)).toString("base64");
1284
+ await fetch(`https://api.mixpanel.com/engage?data=${data}`, {
1285
+ method: "GET"
1286
+ });
1287
+ }
1288
+ };
1289
+ var AnalyticsIntegrationManager = class {
1290
+ constructor(integrations) {
1291
+ this.adapters = /* @__PURE__ */ new Map();
1292
+ integrations.forEach((integration) => {
1293
+ if (!integration.enabled) return;
1294
+ let adapter;
1295
+ switch (integration.platform) {
1296
+ case "posthog":
1297
+ adapter = new PostHogAdapter(integration.apiKey);
1298
+ break;
1299
+ case "heap":
1300
+ adapter = new HeapAdapter(integration.apiKey);
1301
+ break;
1302
+ case "mixpanel":
1303
+ adapter = new MixpanelAdapter(integration.apiKey);
1304
+ break;
1305
+ default:
1306
+ throw new Error(`Unsupported analytics platform: ${integration.platform}`);
1307
+ }
1308
+ this.adapters.set(integration.platform, adapter);
1309
+ });
1310
+ }
1311
+ /**
1312
+ * Track event across all enabled integrations
1313
+ */
1314
+ async track(event) {
1315
+ const promises = Array.from(this.adapters.values()).map(
1316
+ (adapter) => adapter.track(event).catch((error) => {
1317
+ console.error("Analytics tracking error:", error);
1318
+ })
1319
+ );
1320
+ await Promise.allSettled(promises);
1321
+ }
1322
+ /**
1323
+ * Track event to specific platform
1324
+ */
1325
+ async trackTo(platform, event) {
1326
+ const adapter = this.adapters.get(platform);
1327
+ if (!adapter) {
1328
+ throw new Error(`Analytics platform not configured: ${platform}`);
1329
+ }
1330
+ await adapter.track(event);
1331
+ }
1332
+ /**
1333
+ * Identify user across all enabled integrations
1334
+ */
1335
+ async identify(userId, traits) {
1336
+ const promises = Array.from(this.adapters.values()).map(
1337
+ (adapter) => adapter.identify(userId, traits).catch((error) => {
1338
+ console.error("Analytics identify error:", error);
1339
+ })
1340
+ );
1341
+ await Promise.allSettled(promises);
1342
+ }
1343
+ };
1344
+ function normalizeAdClickEvent(trackingParams) {
1345
+ return {
1346
+ event: "ad_click",
1347
+ properties: {
1348
+ // Platform click IDs
1349
+ ...Object.entries(trackingParams.clickIds || {}).reduce((acc, [platform, clickId]) => {
1350
+ acc[`${platform}_click_id`] = clickId;
1351
+ return acc;
1352
+ }, {}),
1353
+ // UTM parameters
1354
+ utm_source: trackingParams.utm?.source,
1355
+ utm_medium: trackingParams.utm?.medium,
1356
+ utm_campaign: trackingParams.utm?.campaign,
1357
+ utm_content: trackingParams.utm?.content,
1358
+ utm_term: trackingParams.utm?.term,
1359
+ // Referrer
1360
+ referrer: trackingParams.referrer,
1361
+ // Metadata
1362
+ tracking_type: "paid_ad"
1363
+ }
1364
+ };
1365
+ }
1366
+ function normalizeConversionEvent(params) {
1367
+ return {
1368
+ event: params.event,
1369
+ properties: {
1370
+ value: params.value,
1371
+ currency: params.currency || "USD",
1372
+ platform: params.platform,
1373
+ campaign_id: params.campaignId,
1374
+ conversion_type: "paid_ad",
1375
+ ...params.metadata
1376
+ }
1377
+ };
1378
+ }
1379
+ export {
1380
+ AmpToolbox,
1381
+ AnalyticsIntegrationManager,
1382
+ AnalyticsResource,
1383
+ AudiencesResource,
1384
+ CampaignsResource,
1385
+ ConversionsResource,
1386
+ CreativeResource,
1387
+ DEFAULT_BASE_URL,
1388
+ KeywordsResource,
1389
+ LinkedInResource,
1390
+ MetaResource,
1391
+ RedditResource,
1392
+ Synter,
1393
+ SynterError,
1394
+ SynterValidationError,
1395
+ buildConversionPayload,
1396
+ buildTrackingURL,
1397
+ buildUTMParameters,
1398
+ Synter as default,
1399
+ detectClickId,
1400
+ extractTrackingParams,
1401
+ extractUTMParameters,
1402
+ generateDefaultUTMTemplate,
1403
+ normalizeAdClickEvent,
1404
+ normalizeConversionEvent,
1405
+ normalizeEventName,
1406
+ validateClickId,
1407
+ validateUTMTemplate
1408
+ };