@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/README.md +133 -0
- package/dist/index.d.mts +1260 -0
- package/dist/index.d.ts +1260 -0
- package/dist/index.js +1461 -0
- package/dist/index.mjs +1408 -0
- package/package.json +67 -0
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
|
+
});
|