@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.
Files changed (68) hide show
  1. package/bin/index.js +63 -0
  2. package/bin/mcp.js +63 -0
  3. package/dist/index.js +63 -0
  4. package/microservices/microservice-ads/package.json +27 -0
  5. package/microservices/microservice-ads/src/cli/index.ts +605 -0
  6. package/microservices/microservice-ads/src/db/campaigns.ts +797 -0
  7. package/microservices/microservice-ads/src/db/database.ts +93 -0
  8. package/microservices/microservice-ads/src/db/migrations.ts +60 -0
  9. package/microservices/microservice-ads/src/index.ts +39 -0
  10. package/microservices/microservice-ads/src/mcp/index.ts +480 -0
  11. package/microservices/microservice-contracts/package.json +27 -0
  12. package/microservices/microservice-contracts/src/cli/index.ts +770 -0
  13. package/microservices/microservice-contracts/src/db/contracts.ts +925 -0
  14. package/microservices/microservice-contracts/src/db/database.ts +93 -0
  15. package/microservices/microservice-contracts/src/db/migrations.ts +141 -0
  16. package/microservices/microservice-contracts/src/index.ts +43 -0
  17. package/microservices/microservice-contracts/src/mcp/index.ts +617 -0
  18. package/microservices/microservice-domains/package.json +27 -0
  19. package/microservices/microservice-domains/src/cli/index.ts +691 -0
  20. package/microservices/microservice-domains/src/db/database.ts +93 -0
  21. package/microservices/microservice-domains/src/db/domains.ts +1164 -0
  22. package/microservices/microservice-domains/src/db/migrations.ts +60 -0
  23. package/microservices/microservice-domains/src/index.ts +65 -0
  24. package/microservices/microservice-domains/src/mcp/index.ts +536 -0
  25. package/microservices/microservice-hiring/package.json +27 -0
  26. package/microservices/microservice-hiring/src/cli/index.ts +741 -0
  27. package/microservices/microservice-hiring/src/db/database.ts +93 -0
  28. package/microservices/microservice-hiring/src/db/hiring.ts +1085 -0
  29. package/microservices/microservice-hiring/src/db/migrations.ts +89 -0
  30. package/microservices/microservice-hiring/src/index.ts +80 -0
  31. package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
  32. package/microservices/microservice-hiring/src/mcp/index.ts +709 -0
  33. package/microservices/microservice-payments/package.json +27 -0
  34. package/microservices/microservice-payments/src/cli/index.ts +609 -0
  35. package/microservices/microservice-payments/src/db/database.ts +93 -0
  36. package/microservices/microservice-payments/src/db/migrations.ts +81 -0
  37. package/microservices/microservice-payments/src/db/payments.ts +1204 -0
  38. package/microservices/microservice-payments/src/index.ts +51 -0
  39. package/microservices/microservice-payments/src/mcp/index.ts +683 -0
  40. package/microservices/microservice-payroll/package.json +27 -0
  41. package/microservices/microservice-payroll/src/cli/index.ts +643 -0
  42. package/microservices/microservice-payroll/src/db/database.ts +93 -0
  43. package/microservices/microservice-payroll/src/db/migrations.ts +95 -0
  44. package/microservices/microservice-payroll/src/db/payroll.ts +1377 -0
  45. package/microservices/microservice-payroll/src/index.ts +48 -0
  46. package/microservices/microservice-payroll/src/mcp/index.ts +666 -0
  47. package/microservices/microservice-shipping/package.json +27 -0
  48. package/microservices/microservice-shipping/src/cli/index.ts +606 -0
  49. package/microservices/microservice-shipping/src/db/database.ts +93 -0
  50. package/microservices/microservice-shipping/src/db/migrations.ts +69 -0
  51. package/microservices/microservice-shipping/src/db/shipping.ts +1093 -0
  52. package/microservices/microservice-shipping/src/index.ts +53 -0
  53. package/microservices/microservice-shipping/src/mcp/index.ts +533 -0
  54. package/microservices/microservice-social/package.json +27 -0
  55. package/microservices/microservice-social/src/cli/index.ts +689 -0
  56. package/microservices/microservice-social/src/db/database.ts +93 -0
  57. package/microservices/microservice-social/src/db/migrations.ts +88 -0
  58. package/microservices/microservice-social/src/db/social.ts +1046 -0
  59. package/microservices/microservice-social/src/index.ts +46 -0
  60. package/microservices/microservice-social/src/mcp/index.ts +655 -0
  61. package/microservices/microservice-subscriptions/package.json +27 -0
  62. package/microservices/microservice-subscriptions/src/cli/index.ts +715 -0
  63. package/microservices/microservice-subscriptions/src/db/database.ts +93 -0
  64. package/microservices/microservice-subscriptions/src/db/migrations.ts +125 -0
  65. package/microservices/microservice-subscriptions/src/db/subscriptions.ts +1256 -0
  66. package/microservices/microservice-subscriptions/src/index.ts +41 -0
  67. package/microservices/microservice-subscriptions/src/mcp/index.ts +631 -0
  68. 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);