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