@hasna/microservices 0.0.3 → 0.0.5
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/bin/index.js +63 -0
- package/bin/mcp.js +63 -0
- package/dist/index.js +63 -0
- package/microservices/microservice-ads/package.json +27 -0
- package/microservices/microservice-ads/src/cli/index.ts +605 -0
- package/microservices/microservice-ads/src/db/campaigns.ts +797 -0
- package/microservices/microservice-ads/src/db/database.ts +93 -0
- package/microservices/microservice-ads/src/db/migrations.ts +60 -0
- package/microservices/microservice-ads/src/index.ts +39 -0
- package/microservices/microservice-ads/src/mcp/index.ts +480 -0
- package/microservices/microservice-contracts/package.json +27 -0
- package/microservices/microservice-contracts/src/cli/index.ts +770 -0
- package/microservices/microservice-contracts/src/db/contracts.ts +925 -0
- package/microservices/microservice-contracts/src/db/database.ts +93 -0
- package/microservices/microservice-contracts/src/db/migrations.ts +141 -0
- package/microservices/microservice-contracts/src/index.ts +43 -0
- package/microservices/microservice-contracts/src/mcp/index.ts +617 -0
- package/microservices/microservice-domains/package.json +27 -0
- package/microservices/microservice-domains/src/cli/index.ts +691 -0
- package/microservices/microservice-domains/src/db/database.ts +93 -0
- package/microservices/microservice-domains/src/db/domains.ts +1164 -0
- package/microservices/microservice-domains/src/db/migrations.ts +60 -0
- package/microservices/microservice-domains/src/index.ts +65 -0
- package/microservices/microservice-domains/src/mcp/index.ts +536 -0
- package/microservices/microservice-hiring/package.json +27 -0
- package/microservices/microservice-hiring/src/cli/index.ts +741 -0
- package/microservices/microservice-hiring/src/db/database.ts +93 -0
- package/microservices/microservice-hiring/src/db/hiring.ts +1085 -0
- package/microservices/microservice-hiring/src/db/migrations.ts +89 -0
- package/microservices/microservice-hiring/src/index.ts +80 -0
- package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
- package/microservices/microservice-hiring/src/mcp/index.ts +709 -0
- package/microservices/microservice-payments/package.json +27 -0
- package/microservices/microservice-payments/src/cli/index.ts +609 -0
- package/microservices/microservice-payments/src/db/database.ts +93 -0
- package/microservices/microservice-payments/src/db/migrations.ts +81 -0
- package/microservices/microservice-payments/src/db/payments.ts +1204 -0
- package/microservices/microservice-payments/src/index.ts +51 -0
- package/microservices/microservice-payments/src/mcp/index.ts +683 -0
- package/microservices/microservice-payroll/package.json +27 -0
- package/microservices/microservice-payroll/src/cli/index.ts +643 -0
- package/microservices/microservice-payroll/src/db/database.ts +93 -0
- package/microservices/microservice-payroll/src/db/migrations.ts +95 -0
- package/microservices/microservice-payroll/src/db/payroll.ts +1377 -0
- package/microservices/microservice-payroll/src/index.ts +48 -0
- package/microservices/microservice-payroll/src/mcp/index.ts +666 -0
- package/microservices/microservice-shipping/package.json +27 -0
- package/microservices/microservice-shipping/src/cli/index.ts +606 -0
- package/microservices/microservice-shipping/src/db/database.ts +93 -0
- package/microservices/microservice-shipping/src/db/migrations.ts +69 -0
- package/microservices/microservice-shipping/src/db/shipping.ts +1093 -0
- package/microservices/microservice-shipping/src/index.ts +53 -0
- package/microservices/microservice-shipping/src/mcp/index.ts +533 -0
- package/microservices/microservice-social/package.json +27 -0
- package/microservices/microservice-social/src/cli/index.ts +689 -0
- package/microservices/microservice-social/src/db/database.ts +93 -0
- package/microservices/microservice-social/src/db/migrations.ts +88 -0
- package/microservices/microservice-social/src/db/social.ts +1046 -0
- package/microservices/microservice-social/src/index.ts +46 -0
- package/microservices/microservice-social/src/mcp/index.ts +655 -0
- package/microservices/microservice-subscriptions/package.json +27 -0
- package/microservices/microservice-subscriptions/src/cli/index.ts +715 -0
- package/microservices/microservice-subscriptions/src/db/database.ts +93 -0
- package/microservices/microservice-subscriptions/src/db/migrations.ts +125 -0
- package/microservices/microservice-subscriptions/src/db/subscriptions.ts +1256 -0
- package/microservices/microservice-subscriptions/src/index.ts +41 -0
- package/microservices/microservice-subscriptions/src/mcp/index.ts +631 -0
- package/package.json +1 -1
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import {
|
|
5
|
+
createCampaign,
|
|
6
|
+
getCampaign,
|
|
7
|
+
listCampaigns,
|
|
8
|
+
updateCampaign,
|
|
9
|
+
deleteCampaign,
|
|
10
|
+
pauseCampaign,
|
|
11
|
+
resumeCampaign,
|
|
12
|
+
getCampaignStats,
|
|
13
|
+
getSpendByPlatform,
|
|
14
|
+
getPlatforms,
|
|
15
|
+
bulkPause,
|
|
16
|
+
bulkResume,
|
|
17
|
+
getRankedCampaigns,
|
|
18
|
+
checkBudgetStatus,
|
|
19
|
+
comparePlatforms,
|
|
20
|
+
exportCampaigns,
|
|
21
|
+
cloneCampaign,
|
|
22
|
+
getBudgetRemaining,
|
|
23
|
+
getAdGroupStats,
|
|
24
|
+
} from "../db/campaigns.js";
|
|
25
|
+
import {
|
|
26
|
+
createAdGroup,
|
|
27
|
+
listAdGroups,
|
|
28
|
+
} from "../db/campaigns.js";
|
|
29
|
+
import {
|
|
30
|
+
createAd,
|
|
31
|
+
listAds,
|
|
32
|
+
} from "../db/campaigns.js";
|
|
33
|
+
|
|
34
|
+
const program = new Command();
|
|
35
|
+
|
|
36
|
+
program
|
|
37
|
+
.name("microservice-ads")
|
|
38
|
+
.description("Ad campaign management microservice")
|
|
39
|
+
.version("0.0.1");
|
|
40
|
+
|
|
41
|
+
// --- Campaigns ---
|
|
42
|
+
|
|
43
|
+
const campaignCmd = program
|
|
44
|
+
.command("campaign")
|
|
45
|
+
.description("Campaign management");
|
|
46
|
+
|
|
47
|
+
campaignCmd
|
|
48
|
+
.command("create")
|
|
49
|
+
.description("Create a new campaign")
|
|
50
|
+
.requiredOption("--platform <platform>", "Platform (google/meta/linkedin/tiktok)")
|
|
51
|
+
.requiredOption("--name <name>", "Campaign name")
|
|
52
|
+
.option("--status <status>", "Status (draft/active/paused/completed)", "draft")
|
|
53
|
+
.option("--budget-daily <amount>", "Daily budget")
|
|
54
|
+
.option("--budget-total <amount>", "Total budget")
|
|
55
|
+
.option("--start-date <date>", "Start date (YYYY-MM-DD)")
|
|
56
|
+
.option("--end-date <date>", "End date (YYYY-MM-DD)")
|
|
57
|
+
.option("--json", "Output as JSON", false)
|
|
58
|
+
.action((opts) => {
|
|
59
|
+
const campaign = createCampaign({
|
|
60
|
+
platform: opts.platform,
|
|
61
|
+
name: opts.name,
|
|
62
|
+
status: opts.status,
|
|
63
|
+
budget_daily: opts.budgetDaily ? parseFloat(opts.budgetDaily) : undefined,
|
|
64
|
+
budget_total: opts.budgetTotal ? parseFloat(opts.budgetTotal) : undefined,
|
|
65
|
+
start_date: opts.startDate,
|
|
66
|
+
end_date: opts.endDate,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
if (opts.json) {
|
|
70
|
+
console.log(JSON.stringify(campaign, null, 2));
|
|
71
|
+
} else {
|
|
72
|
+
console.log(`Created campaign: ${campaign.name} [${campaign.platform}] (${campaign.id})`);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
campaignCmd
|
|
77
|
+
.command("list")
|
|
78
|
+
.description("List campaigns")
|
|
79
|
+
.option("--platform <platform>", "Filter by platform")
|
|
80
|
+
.option("--status <status>", "Filter by status")
|
|
81
|
+
.option("--search <query>", "Search by name")
|
|
82
|
+
.option("--limit <n>", "Limit results")
|
|
83
|
+
.option("--json", "Output as JSON", false)
|
|
84
|
+
.action((opts) => {
|
|
85
|
+
const campaigns = listCampaigns({
|
|
86
|
+
platform: opts.platform,
|
|
87
|
+
status: opts.status,
|
|
88
|
+
search: opts.search,
|
|
89
|
+
limit: opts.limit ? parseInt(opts.limit) : undefined,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
if (opts.json) {
|
|
93
|
+
console.log(JSON.stringify(campaigns, null, 2));
|
|
94
|
+
} else {
|
|
95
|
+
if (campaigns.length === 0) {
|
|
96
|
+
console.log("No campaigns found.");
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
for (const c of campaigns) {
|
|
100
|
+
const budget = c.budget_total > 0 ? ` $${c.budget_total}` : "";
|
|
101
|
+
console.log(` [${c.platform}] ${c.name} (${c.status})${budget}`);
|
|
102
|
+
}
|
|
103
|
+
console.log(`\n${campaigns.length} campaign(s)`);
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
campaignCmd
|
|
108
|
+
.command("get")
|
|
109
|
+
.description("Get a campaign by ID")
|
|
110
|
+
.argument("<id>", "Campaign ID")
|
|
111
|
+
.option("--json", "Output as JSON", false)
|
|
112
|
+
.action((id, opts) => {
|
|
113
|
+
const campaign = getCampaign(id);
|
|
114
|
+
if (!campaign) {
|
|
115
|
+
console.error(`Campaign '${id}' not found.`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (opts.json) {
|
|
120
|
+
console.log(JSON.stringify(campaign, null, 2));
|
|
121
|
+
} else {
|
|
122
|
+
console.log(`${campaign.name} [${campaign.platform}]`);
|
|
123
|
+
console.log(` Status: ${campaign.status}`);
|
|
124
|
+
console.log(` Budget: $${campaign.budget_daily}/day, $${campaign.budget_total} total`);
|
|
125
|
+
console.log(` Spend: $${campaign.spend}`);
|
|
126
|
+
console.log(` Impressions: ${campaign.impressions}, Clicks: ${campaign.clicks}, Conversions: ${campaign.conversions}`);
|
|
127
|
+
console.log(` ROAS: ${campaign.roas}`);
|
|
128
|
+
if (campaign.start_date) console.log(` Start: ${campaign.start_date}`);
|
|
129
|
+
if (campaign.end_date) console.log(` End: ${campaign.end_date}`);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
campaignCmd
|
|
134
|
+
.command("update")
|
|
135
|
+
.description("Update a campaign")
|
|
136
|
+
.argument("<id>", "Campaign ID")
|
|
137
|
+
.option("--platform <platform>", "Platform")
|
|
138
|
+
.option("--name <name>", "Campaign name")
|
|
139
|
+
.option("--status <status>", "Status")
|
|
140
|
+
.option("--budget-daily <amount>", "Daily budget")
|
|
141
|
+
.option("--budget-total <amount>", "Total budget")
|
|
142
|
+
.option("--spend <amount>", "Total spend")
|
|
143
|
+
.option("--impressions <n>", "Impressions")
|
|
144
|
+
.option("--clicks <n>", "Clicks")
|
|
145
|
+
.option("--conversions <n>", "Conversions")
|
|
146
|
+
.option("--roas <n>", "ROAS")
|
|
147
|
+
.option("--start-date <date>", "Start date")
|
|
148
|
+
.option("--end-date <date>", "End date")
|
|
149
|
+
.option("--json", "Output as JSON", false)
|
|
150
|
+
.action((id, opts) => {
|
|
151
|
+
const input: Record<string, unknown> = {};
|
|
152
|
+
if (opts.platform !== undefined) input.platform = opts.platform;
|
|
153
|
+
if (opts.name !== undefined) input.name = opts.name;
|
|
154
|
+
if (opts.status !== undefined) input.status = opts.status;
|
|
155
|
+
if (opts.budgetDaily !== undefined) input.budget_daily = parseFloat(opts.budgetDaily);
|
|
156
|
+
if (opts.budgetTotal !== undefined) input.budget_total = parseFloat(opts.budgetTotal);
|
|
157
|
+
if (opts.spend !== undefined) input.spend = parseFloat(opts.spend);
|
|
158
|
+
if (opts.impressions !== undefined) input.impressions = parseInt(opts.impressions);
|
|
159
|
+
if (opts.clicks !== undefined) input.clicks = parseInt(opts.clicks);
|
|
160
|
+
if (opts.conversions !== undefined) input.conversions = parseInt(opts.conversions);
|
|
161
|
+
if (opts.roas !== undefined) input.roas = parseFloat(opts.roas);
|
|
162
|
+
if (opts.startDate !== undefined) input.start_date = opts.startDate;
|
|
163
|
+
if (opts.endDate !== undefined) input.end_date = opts.endDate;
|
|
164
|
+
|
|
165
|
+
const campaign = updateCampaign(id, input);
|
|
166
|
+
if (!campaign) {
|
|
167
|
+
console.error(`Campaign '${id}' not found.`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (opts.json) {
|
|
172
|
+
console.log(JSON.stringify(campaign, null, 2));
|
|
173
|
+
} else {
|
|
174
|
+
console.log(`Updated: ${campaign.name} [${campaign.platform}]`);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
campaignCmd
|
|
179
|
+
.command("delete")
|
|
180
|
+
.description("Delete a campaign")
|
|
181
|
+
.argument("<id>", "Campaign ID")
|
|
182
|
+
.action((id) => {
|
|
183
|
+
const deleted = deleteCampaign(id);
|
|
184
|
+
if (deleted) {
|
|
185
|
+
console.log(`Deleted campaign ${id}`);
|
|
186
|
+
} else {
|
|
187
|
+
console.error(`Campaign '${id}' not found.`);
|
|
188
|
+
process.exit(1);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
campaignCmd
|
|
193
|
+
.command("pause")
|
|
194
|
+
.description("Pause a campaign")
|
|
195
|
+
.argument("<id>", "Campaign ID")
|
|
196
|
+
.option("--json", "Output as JSON", false)
|
|
197
|
+
.action((id, opts) => {
|
|
198
|
+
const campaign = pauseCampaign(id);
|
|
199
|
+
if (!campaign) {
|
|
200
|
+
console.error(`Campaign '${id}' not found.`);
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (opts.json) {
|
|
205
|
+
console.log(JSON.stringify(campaign, null, 2));
|
|
206
|
+
} else {
|
|
207
|
+
console.log(`Paused: ${campaign.name}`);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
campaignCmd
|
|
212
|
+
.command("resume")
|
|
213
|
+
.description("Resume a paused campaign")
|
|
214
|
+
.argument("<id>", "Campaign ID")
|
|
215
|
+
.option("--json", "Output as JSON", false)
|
|
216
|
+
.action((id, opts) => {
|
|
217
|
+
const campaign = resumeCampaign(id);
|
|
218
|
+
if (!campaign) {
|
|
219
|
+
console.error(`Campaign '${id}' not found.`);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (opts.json) {
|
|
224
|
+
console.log(JSON.stringify(campaign, null, 2));
|
|
225
|
+
} else {
|
|
226
|
+
console.log(`Resumed: ${campaign.name}`);
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// 1. Bulk pause/resume
|
|
231
|
+
campaignCmd
|
|
232
|
+
.command("bulk-pause")
|
|
233
|
+
.description("Pause all active campaigns on a platform")
|
|
234
|
+
.requiredOption("--platform <platform>", "Platform (google/meta/linkedin/tiktok)")
|
|
235
|
+
.option("--json", "Output as JSON", false)
|
|
236
|
+
.action((opts) => {
|
|
237
|
+
const result = bulkPause(opts.platform);
|
|
238
|
+
if (opts.json) {
|
|
239
|
+
console.log(JSON.stringify(result, null, 2));
|
|
240
|
+
} else {
|
|
241
|
+
console.log(`Paused ${result.updated_count} campaign(s) on ${result.platform}`);
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
campaignCmd
|
|
246
|
+
.command("bulk-resume")
|
|
247
|
+
.description("Resume all paused campaigns on a platform")
|
|
248
|
+
.requiredOption("--platform <platform>", "Platform (google/meta/linkedin/tiktok)")
|
|
249
|
+
.option("--json", "Output as JSON", false)
|
|
250
|
+
.action((opts) => {
|
|
251
|
+
const result = bulkResume(opts.platform);
|
|
252
|
+
if (opts.json) {
|
|
253
|
+
console.log(JSON.stringify(result, null, 2));
|
|
254
|
+
} else {
|
|
255
|
+
console.log(`Resumed ${result.updated_count} campaign(s) on ${result.platform}`);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// 3. Budget alerts
|
|
260
|
+
campaignCmd
|
|
261
|
+
.command("check-budget")
|
|
262
|
+
.description("Check budget status for a campaign")
|
|
263
|
+
.argument("<id>", "Campaign ID")
|
|
264
|
+
.option("--json", "Output as JSON", false)
|
|
265
|
+
.action((id, opts) => {
|
|
266
|
+
const status = checkBudgetStatus(id);
|
|
267
|
+
if (!status) {
|
|
268
|
+
console.error(`Campaign '${id}' not found.`);
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (opts.json) {
|
|
273
|
+
console.log(JSON.stringify(status, null, 2));
|
|
274
|
+
} else {
|
|
275
|
+
console.log(`Budget Status: ${status.campaign_name}`);
|
|
276
|
+
console.log(` Over budget: ${status.over_budget ? "YES" : "No"}`);
|
|
277
|
+
console.log(` Daily remaining: $${status.daily_remaining}`);
|
|
278
|
+
console.log(` Total remaining: $${status.total_remaining}`);
|
|
279
|
+
console.log(` % used: ${status.pct_used}%`);
|
|
280
|
+
console.log(` Days active: ${status.days_active}`);
|
|
281
|
+
console.log(` Expected spend: $${status.expected_spend}`);
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// 5. CSV export
|
|
286
|
+
campaignCmd
|
|
287
|
+
.command("export")
|
|
288
|
+
.description("Export campaigns to CSV or JSON")
|
|
289
|
+
.option("--format <format>", "Format (csv/json)", "csv")
|
|
290
|
+
.action((opts) => {
|
|
291
|
+
const output = exportCampaigns(opts.format);
|
|
292
|
+
console.log(output);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
// 6. Campaign cloning
|
|
296
|
+
campaignCmd
|
|
297
|
+
.command("clone")
|
|
298
|
+
.description("Clone a campaign with all ad groups and ads")
|
|
299
|
+
.argument("<id>", "Campaign ID to clone")
|
|
300
|
+
.requiredOption("--name <name>", "New campaign name")
|
|
301
|
+
.option("--json", "Output as JSON", false)
|
|
302
|
+
.action((id, opts) => {
|
|
303
|
+
const cloned = cloneCampaign(id, opts.name);
|
|
304
|
+
if (!cloned) {
|
|
305
|
+
console.error(`Campaign '${id}' not found.`);
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (opts.json) {
|
|
310
|
+
console.log(JSON.stringify(cloned, null, 2));
|
|
311
|
+
} else {
|
|
312
|
+
console.log(`Cloned campaign: ${cloned.name} (${cloned.id})`);
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// 7. Budget remaining
|
|
317
|
+
campaignCmd
|
|
318
|
+
.command("budget-remaining")
|
|
319
|
+
.description("Show budget remaining for a campaign")
|
|
320
|
+
.argument("<id>", "Campaign ID")
|
|
321
|
+
.option("--json", "Output as JSON", false)
|
|
322
|
+
.action((id, opts) => {
|
|
323
|
+
const remaining = getBudgetRemaining(id);
|
|
324
|
+
if (!remaining) {
|
|
325
|
+
console.error(`Campaign '${id}' not found.`);
|
|
326
|
+
process.exit(1);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (opts.json) {
|
|
330
|
+
console.log(JSON.stringify(remaining, null, 2));
|
|
331
|
+
} else {
|
|
332
|
+
console.log(`Budget Remaining: ${remaining.campaign_name}`);
|
|
333
|
+
console.log(` Daily budget: $${remaining.budget_daily}`);
|
|
334
|
+
console.log(` Total budget: $${remaining.budget_total}`);
|
|
335
|
+
console.log(` Spend: $${remaining.spend}`);
|
|
336
|
+
console.log(` Daily remaining: $${remaining.daily_remaining}`);
|
|
337
|
+
console.log(` Total remaining: $${remaining.total_remaining}`);
|
|
338
|
+
console.log(` Days remaining at current rate: ${remaining.days_remaining_at_daily_rate}`);
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// --- Ad Groups ---
|
|
343
|
+
|
|
344
|
+
const adGroupCmd = program
|
|
345
|
+
.command("ad-group")
|
|
346
|
+
.description("Ad group management");
|
|
347
|
+
|
|
348
|
+
adGroupCmd
|
|
349
|
+
.command("create")
|
|
350
|
+
.description("Create a new ad group")
|
|
351
|
+
.requiredOption("--campaign <id>", "Campaign ID")
|
|
352
|
+
.requiredOption("--name <name>", "Ad group name")
|
|
353
|
+
.option("--targeting <json>", "Targeting JSON")
|
|
354
|
+
.option("--status <status>", "Status", "draft")
|
|
355
|
+
.option("--json", "Output as JSON", false)
|
|
356
|
+
.action((opts) => {
|
|
357
|
+
const adGroup = createAdGroup({
|
|
358
|
+
campaign_id: opts.campaign,
|
|
359
|
+
name: opts.name,
|
|
360
|
+
targeting: opts.targeting ? JSON.parse(opts.targeting) : undefined,
|
|
361
|
+
status: opts.status,
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
if (opts.json) {
|
|
365
|
+
console.log(JSON.stringify(adGroup, null, 2));
|
|
366
|
+
} else {
|
|
367
|
+
console.log(`Created ad group: ${adGroup.name} (${adGroup.id})`);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
adGroupCmd
|
|
372
|
+
.command("list")
|
|
373
|
+
.description("List ad groups")
|
|
374
|
+
.option("--campaign <id>", "Filter by campaign ID")
|
|
375
|
+
.option("--json", "Output as JSON", false)
|
|
376
|
+
.action((opts) => {
|
|
377
|
+
const adGroups = listAdGroups(opts.campaign);
|
|
378
|
+
|
|
379
|
+
if (opts.json) {
|
|
380
|
+
console.log(JSON.stringify(adGroups, null, 2));
|
|
381
|
+
} else {
|
|
382
|
+
if (adGroups.length === 0) {
|
|
383
|
+
console.log("No ad groups found.");
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
for (const ag of adGroups) {
|
|
387
|
+
console.log(` ${ag.name} (${ag.status}) — campaign: ${ag.campaign_id}`);
|
|
388
|
+
}
|
|
389
|
+
console.log(`\n${adGroups.length} ad group(s)`);
|
|
390
|
+
}
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// 8. Ad group stats
|
|
394
|
+
adGroupCmd
|
|
395
|
+
.command("stats")
|
|
396
|
+
.description("Show aggregated stats for an ad group")
|
|
397
|
+
.argument("<id>", "Ad group ID")
|
|
398
|
+
.option("--json", "Output as JSON", false)
|
|
399
|
+
.action((id, opts) => {
|
|
400
|
+
const stats = getAdGroupStats(id);
|
|
401
|
+
if (!stats) {
|
|
402
|
+
console.error(`Ad group '${id}' not found.`);
|
|
403
|
+
process.exit(1);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (opts.json) {
|
|
407
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
408
|
+
} else {
|
|
409
|
+
console.log(`Ad Group Stats: ${stats.ad_group_name}`);
|
|
410
|
+
console.log(` Campaign: ${stats.campaign_id}`);
|
|
411
|
+
console.log(` Total ads: ${stats.total_ads}`);
|
|
412
|
+
console.log(` Active ads: ${stats.active_ads}`);
|
|
413
|
+
if (Object.keys(stats.metrics).length > 0) {
|
|
414
|
+
console.log(" Aggregated metrics:");
|
|
415
|
+
for (const [key, value] of Object.entries(stats.metrics)) {
|
|
416
|
+
console.log(` ${key}: ${value}`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// --- Ads ---
|
|
423
|
+
|
|
424
|
+
const adCmd = program
|
|
425
|
+
.command("ad")
|
|
426
|
+
.description("Ad management");
|
|
427
|
+
|
|
428
|
+
adCmd
|
|
429
|
+
.command("create")
|
|
430
|
+
.description("Create a new ad")
|
|
431
|
+
.requiredOption("--ad-group <id>", "Ad group ID")
|
|
432
|
+
.requiredOption("--headline <text>", "Ad headline")
|
|
433
|
+
.option("--description <text>", "Ad description")
|
|
434
|
+
.option("--creative-url <url>", "Creative URL")
|
|
435
|
+
.option("--status <status>", "Status", "draft")
|
|
436
|
+
.option("--json", "Output as JSON", false)
|
|
437
|
+
.action((opts) => {
|
|
438
|
+
const ad = createAd({
|
|
439
|
+
ad_group_id: opts.adGroup,
|
|
440
|
+
headline: opts.headline,
|
|
441
|
+
description: opts.description,
|
|
442
|
+
creative_url: opts.creativeUrl,
|
|
443
|
+
status: opts.status,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
if (opts.json) {
|
|
447
|
+
console.log(JSON.stringify(ad, null, 2));
|
|
448
|
+
} else {
|
|
449
|
+
console.log(`Created ad: ${ad.headline} (${ad.id})`);
|
|
450
|
+
}
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
adCmd
|
|
454
|
+
.command("list")
|
|
455
|
+
.description("List ads")
|
|
456
|
+
.option("--ad-group <id>", "Filter by ad group ID")
|
|
457
|
+
.option("--json", "Output as JSON", false)
|
|
458
|
+
.action((opts) => {
|
|
459
|
+
const ads = listAds(opts.adGroup);
|
|
460
|
+
|
|
461
|
+
if (opts.json) {
|
|
462
|
+
console.log(JSON.stringify(ads, null, 2));
|
|
463
|
+
} else {
|
|
464
|
+
if (ads.length === 0) {
|
|
465
|
+
console.log("No ads found.");
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
for (const a of ads) {
|
|
469
|
+
console.log(` ${a.headline} (${a.status})`);
|
|
470
|
+
}
|
|
471
|
+
console.log(`\n${ads.length} ad(s)`);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// --- Stats & Reports ---
|
|
476
|
+
|
|
477
|
+
program
|
|
478
|
+
.command("stats")
|
|
479
|
+
.description("Show campaign statistics")
|
|
480
|
+
.option("--sort-by <metric>", "Sort campaigns by metric (roas/ctr/spend)")
|
|
481
|
+
.option("--limit <n>", "Limit ranked results", "10")
|
|
482
|
+
.option("--json", "Output as JSON", false)
|
|
483
|
+
.action((opts) => {
|
|
484
|
+
// 2. Performance ranking via --sort-by
|
|
485
|
+
if (opts.sortBy) {
|
|
486
|
+
const ranked = getRankedCampaigns(opts.sortBy, parseInt(opts.limit));
|
|
487
|
+
if (opts.json) {
|
|
488
|
+
console.log(JSON.stringify(ranked, null, 2));
|
|
489
|
+
} else {
|
|
490
|
+
console.log(`Campaigns ranked by ${opts.sortBy}:`);
|
|
491
|
+
for (let i = 0; i < ranked.length; i++) {
|
|
492
|
+
const c = ranked[i];
|
|
493
|
+
const ctr = c.impressions > 0 ? ((c.clicks / c.impressions) * 100).toFixed(2) : "0.00";
|
|
494
|
+
console.log(` ${i + 1}. ${c.name} [${c.platform}] — ROAS: ${c.roas}, CTR: ${ctr}%, Spend: $${c.spend}`);
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
const stats = getCampaignStats();
|
|
501
|
+
|
|
502
|
+
if (opts.json) {
|
|
503
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
504
|
+
} else {
|
|
505
|
+
console.log("Campaign Statistics:");
|
|
506
|
+
console.log(` Total campaigns: ${stats.total_campaigns}`);
|
|
507
|
+
console.log(` Active campaigns: ${stats.active_campaigns}`);
|
|
508
|
+
console.log(` Total spend: $${stats.total_spend.toFixed(2)}`);
|
|
509
|
+
console.log(` Total impressions: ${stats.total_impressions}`);
|
|
510
|
+
console.log(` Total clicks: ${stats.total_clicks}`);
|
|
511
|
+
console.log(` Total conversions: ${stats.total_conversions}`);
|
|
512
|
+
console.log(` Avg ROAS: ${stats.avg_roas.toFixed(2)}`);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// 4. Cross-platform comparison
|
|
517
|
+
program
|
|
518
|
+
.command("compare-platforms")
|
|
519
|
+
.description("Compare ROAS/CPA/spend across platforms side-by-side")
|
|
520
|
+
.option("--json", "Output as JSON", false)
|
|
521
|
+
.action((opts) => {
|
|
522
|
+
const comparison = comparePlatforms();
|
|
523
|
+
|
|
524
|
+
if (opts.json) {
|
|
525
|
+
console.log(JSON.stringify(comparison, null, 2));
|
|
526
|
+
} else {
|
|
527
|
+
if (comparison.length === 0) {
|
|
528
|
+
console.log("No platform data.");
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
console.log("Platform Comparison:");
|
|
532
|
+
console.log(" Platform | Campaigns | Spend | ROAS | CTR | CPA");
|
|
533
|
+
console.log(" -------------|-----------|--------------|-------|--------|-------");
|
|
534
|
+
for (const p of comparison) {
|
|
535
|
+
const platform = p.platform.padEnd(12);
|
|
536
|
+
const campaigns = String(p.campaign_count).padEnd(9);
|
|
537
|
+
const spend = `$${p.total_spend.toFixed(2)}`.padEnd(12);
|
|
538
|
+
const roas = p.avg_roas.toFixed(2).padEnd(5);
|
|
539
|
+
const ctr = `${p.avg_ctr.toFixed(2)}%`.padEnd(6);
|
|
540
|
+
const cpa = p.avg_cpa > 0 ? `$${p.avg_cpa.toFixed(2)}` : "N/A";
|
|
541
|
+
console.log(` ${platform} | ${campaigns} | ${spend} | ${roas} | ${ctr} | ${cpa}`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
program
|
|
547
|
+
.command("spend")
|
|
548
|
+
.description("Show spend by platform")
|
|
549
|
+
.option("--json", "Output as JSON", false)
|
|
550
|
+
.action((opts) => {
|
|
551
|
+
const spend = getSpendByPlatform();
|
|
552
|
+
|
|
553
|
+
if (opts.json) {
|
|
554
|
+
console.log(JSON.stringify(spend, null, 2));
|
|
555
|
+
} else {
|
|
556
|
+
if (spend.length === 0) {
|
|
557
|
+
console.log("No spend data.");
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
console.log("Spend by Platform:");
|
|
561
|
+
for (const s of spend) {
|
|
562
|
+
console.log(` ${s.platform}: $${s.total_spend.toFixed(2)} (${s.campaign_count} campaigns)`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
program
|
|
568
|
+
.command("platforms")
|
|
569
|
+
.description("List platforms with campaigns")
|
|
570
|
+
.option("--json", "Output as JSON", false)
|
|
571
|
+
.action((opts) => {
|
|
572
|
+
const platforms = getPlatforms();
|
|
573
|
+
|
|
574
|
+
if (opts.json) {
|
|
575
|
+
console.log(JSON.stringify(platforms, null, 2));
|
|
576
|
+
} else {
|
|
577
|
+
if (platforms.length === 0) {
|
|
578
|
+
console.log("No platforms found.");
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
console.log("Platforms:");
|
|
582
|
+
for (const p of platforms) {
|
|
583
|
+
console.log(` ${p}`);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
program
|
|
589
|
+
.command("providers")
|
|
590
|
+
.description("List supported ad providers")
|
|
591
|
+
.option("--json", "Output as JSON", false)
|
|
592
|
+
.action((opts) => {
|
|
593
|
+
const providers = ["google", "meta", "linkedin", "tiktok"];
|
|
594
|
+
|
|
595
|
+
if (opts.json) {
|
|
596
|
+
console.log(JSON.stringify(providers, null, 2));
|
|
597
|
+
} else {
|
|
598
|
+
console.log("Supported Ad Providers:");
|
|
599
|
+
for (const p of providers) {
|
|
600
|
+
console.log(` ${p}`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
program.parse(process.argv);
|