@hasna/microservices 0.0.16 → 0.0.18
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 +143 -23
- package/bin/index.js +784 -13987
- package/bin/mcp.js +298 -10973
- package/dist/index.js +251 -10056
- package/package.json +36 -26
- package/microservices/microservice-ads/package.json +0 -28
- package/microservices/microservice-ads/src/cli/index.ts +0 -605
- package/microservices/microservice-ads/src/db/campaigns.ts +0 -797
- package/microservices/microservice-ads/src/db/database.ts +0 -91
- package/microservices/microservice-ads/src/db/migrations.ts +0 -60
- package/microservices/microservice-ads/src/index.ts +0 -39
- package/microservices/microservice-ads/src/mcp/index.ts +0 -480
- package/microservices/microservice-analytics/package.json +0 -28
- package/microservices/microservice-analytics/src/cli/index.ts +0 -373
- package/microservices/microservice-analytics/src/db/analytics.ts +0 -564
- package/microservices/microservice-analytics/src/db/database.ts +0 -91
- package/microservices/microservice-analytics/src/db/migrations.ts +0 -50
- package/microservices/microservice-analytics/src/index.ts +0 -37
- package/microservices/microservice-analytics/src/mcp/index.ts +0 -334
- package/microservices/microservice-assets/package.json +0 -28
- package/microservices/microservice-assets/src/cli/index.ts +0 -375
- package/microservices/microservice-assets/src/db/assets.ts +0 -370
- package/microservices/microservice-assets/src/db/database.ts +0 -91
- package/microservices/microservice-assets/src/db/migrations.ts +0 -51
- package/microservices/microservice-assets/src/index.ts +0 -32
- package/microservices/microservice-assets/src/mcp/index.ts +0 -346
- package/microservices/microservice-bookkeeping/package.json +0 -28
- package/microservices/microservice-bookkeeping/src/cli/index.ts +0 -386
- package/microservices/microservice-bookkeeping/src/db/bookkeeping.ts +0 -591
- package/microservices/microservice-bookkeeping/src/db/database.ts +0 -91
- package/microservices/microservice-bookkeeping/src/db/migrations.ts +0 -52
- package/microservices/microservice-bookkeeping/src/index.ts +0 -32
- package/microservices/microservice-bookkeeping/src/mcp/index.ts +0 -284
- package/microservices/microservice-calendar/package.json +0 -28
- package/microservices/microservice-calendar/src/cli/index.ts +0 -287
- package/microservices/microservice-calendar/src/db/calendar.ts +0 -328
- package/microservices/microservice-calendar/src/db/database.ts +0 -91
- package/microservices/microservice-calendar/src/db/migrations.ts +0 -47
- package/microservices/microservice-calendar/src/index.ts +0 -24
- package/microservices/microservice-calendar/src/mcp/index.ts +0 -226
- package/microservices/microservice-company/package.json +0 -28
- package/microservices/microservice-company/src/cli/index.ts +0 -1126
- package/microservices/microservice-company/src/db/company.ts +0 -854
- package/microservices/microservice-company/src/db/database.ts +0 -91
- package/microservices/microservice-company/src/db/migrations.ts +0 -214
- package/microservices/microservice-company/src/db/workflow-migrations.ts +0 -44
- package/microservices/microservice-company/src/index.ts +0 -60
- package/microservices/microservice-company/src/lib/audit.ts +0 -168
- package/microservices/microservice-company/src/lib/finance.ts +0 -299
- package/microservices/microservice-company/src/lib/settings.ts +0 -85
- package/microservices/microservice-company/src/lib/workflows.ts +0 -698
- package/microservices/microservice-company/src/mcp/index.ts +0 -991
- package/microservices/microservice-compliance/package.json +0 -28
- package/microservices/microservice-compliance/src/cli/index.ts +0 -467
- package/microservices/microservice-compliance/src/db/compliance.ts +0 -633
- package/microservices/microservice-compliance/src/db/database.ts +0 -91
- package/microservices/microservice-compliance/src/db/migrations.ts +0 -63
- package/microservices/microservice-compliance/src/index.ts +0 -46
- package/microservices/microservice-compliance/src/mcp/index.ts +0 -438
- package/microservices/microservice-contacts/package.json +0 -28
- package/microservices/microservice-contacts/src/cli/index.ts +0 -393
- package/microservices/microservice-contacts/src/db/companies.ts +0 -167
- package/microservices/microservice-contacts/src/db/contacts.ts +0 -249
- package/microservices/microservice-contacts/src/db/database.ts +0 -91
- package/microservices/microservice-contacts/src/db/migrations.ts +0 -71
- package/microservices/microservice-contacts/src/db/relationships.ts +0 -53
- package/microservices/microservice-contacts/src/index.ts +0 -42
- package/microservices/microservice-contacts/src/mcp/index.ts +0 -303
- package/microservices/microservice-contracts/package.json +0 -28
- package/microservices/microservice-contracts/src/cli/index.ts +0 -770
- package/microservices/microservice-contracts/src/db/contracts.ts +0 -925
- package/microservices/microservice-contracts/src/db/database.ts +0 -91
- package/microservices/microservice-contracts/src/db/migrations.ts +0 -141
- package/microservices/microservice-contracts/src/index.ts +0 -43
- package/microservices/microservice-contracts/src/mcp/index.ts +0 -617
- package/microservices/microservice-crm/package.json +0 -28
- package/microservices/microservice-crm/src/cli/index.ts +0 -396
- package/microservices/microservice-crm/src/db/database.ts +0 -91
- package/microservices/microservice-crm/src/db/migrations.ts +0 -66
- package/microservices/microservice-crm/src/db/pipeline.ts +0 -397
- package/microservices/microservice-crm/src/index.ts +0 -34
- package/microservices/microservice-crm/src/mcp/index.ts +0 -294
- package/microservices/microservice-documents/package.json +0 -28
- package/microservices/microservice-documents/src/cli/index.ts +0 -246
- package/microservices/microservice-documents/src/db/database.ts +0 -91
- package/microservices/microservice-documents/src/db/documents.ts +0 -316
- package/microservices/microservice-documents/src/db/migrations.ts +0 -49
- package/microservices/microservice-documents/src/index.ts +0 -24
- package/microservices/microservice-documents/src/mcp/index.ts +0 -202
- package/microservices/microservice-domains/package.json +0 -28
- package/microservices/microservice-domains/src/cli/index.ts +0 -1111
- package/microservices/microservice-domains/src/db/database.ts +0 -91
- package/microservices/microservice-domains/src/db/domains.ts +0 -1164
- package/microservices/microservice-domains/src/db/migrations.ts +0 -60
- package/microservices/microservice-domains/src/index.ts +0 -65
- package/microservices/microservice-domains/src/lib/brandsight.ts +0 -350
- package/microservices/microservice-domains/src/lib/godaddy.ts +0 -338
- package/microservices/microservice-domains/src/lib/namecheap.ts +0 -262
- package/microservices/microservice-domains/src/lib/registrar.ts +0 -355
- package/microservices/microservice-domains/src/mcp/index.ts +0 -781
- package/microservices/microservice-expenses/package.json +0 -28
- package/microservices/microservice-expenses/src/cli/index.ts +0 -267
- package/microservices/microservice-expenses/src/db/database.ts +0 -91
- package/microservices/microservice-expenses/src/db/expenses.ts +0 -345
- package/microservices/microservice-expenses/src/db/migrations.ts +0 -45
- package/microservices/microservice-expenses/src/index.ts +0 -25
- package/microservices/microservice-expenses/src/mcp/index.ts +0 -196
- package/microservices/microservice-habits/package.json +0 -28
- package/microservices/microservice-habits/src/cli/index.ts +0 -315
- package/microservices/microservice-habits/src/db/database.ts +0 -91
- package/microservices/microservice-habits/src/db/habits.ts +0 -451
- package/microservices/microservice-habits/src/db/migrations.ts +0 -46
- package/microservices/microservice-habits/src/index.ts +0 -31
- package/microservices/microservice-habits/src/mcp/index.ts +0 -313
- package/microservices/microservice-health/package.json +0 -28
- package/microservices/microservice-health/src/cli/index.ts +0 -484
- package/microservices/microservice-health/src/db/database.ts +0 -91
- package/microservices/microservice-health/src/db/health.ts +0 -708
- package/microservices/microservice-health/src/db/migrations.ts +0 -70
- package/microservices/microservice-health/src/index.ts +0 -63
- package/microservices/microservice-health/src/mcp/index.ts +0 -437
- package/microservices/microservice-hiring/package.json +0 -28
- package/microservices/microservice-hiring/src/cli/index.ts +0 -741
- package/microservices/microservice-hiring/src/db/database.ts +0 -91
- package/microservices/microservice-hiring/src/db/hiring.ts +0 -1085
- package/microservices/microservice-hiring/src/db/migrations.ts +0 -89
- package/microservices/microservice-hiring/src/index.ts +0 -80
- package/microservices/microservice-hiring/src/lib/scoring.ts +0 -206
- package/microservices/microservice-hiring/src/mcp/index.ts +0 -709
- package/microservices/microservice-inventory/package.json +0 -28
- package/microservices/microservice-inventory/src/cli/index.ts +0 -365
- package/microservices/microservice-inventory/src/db/database.ts +0 -91
- package/microservices/microservice-inventory/src/db/inventory.ts +0 -393
- package/microservices/microservice-inventory/src/db/migrations.ts +0 -54
- package/microservices/microservice-inventory/src/index.ts +0 -28
- package/microservices/microservice-inventory/src/mcp/index.ts +0 -250
- package/microservices/microservice-invoices/dashboard/index.html +0 -12
- package/microservices/microservice-invoices/dashboard/package.json +0 -29
- package/microservices/microservice-invoices/dashboard/tsconfig.json +0 -14
- package/microservices/microservice-invoices/dashboard/vite.config.ts +0 -15
- package/microservices/microservice-invoices/package.json +0 -31
- package/microservices/microservice-invoices/src/cli/index.ts +0 -308
- package/microservices/microservice-invoices/src/db/business.ts +0 -241
- package/microservices/microservice-invoices/src/db/clients.ts +0 -127
- package/microservices/microservice-invoices/src/db/database.ts +0 -91
- package/microservices/microservice-invoices/src/db/invoices.ts +0 -345
- package/microservices/microservice-invoices/src/db/migrations.ts +0 -184
- package/microservices/microservice-invoices/src/index.ts +0 -56
- package/microservices/microservice-invoices/src/mcp/index.ts +0 -242
- package/microservices/microservice-invoices/src/server/index.ts +0 -162
- package/microservices/microservice-leads/package.json +0 -28
- package/microservices/microservice-leads/src/cli/index.ts +0 -596
- package/microservices/microservice-leads/src/db/database.ts +0 -91
- package/microservices/microservice-leads/src/db/leads.ts +0 -520
- package/microservices/microservice-leads/src/db/lists.ts +0 -151
- package/microservices/microservice-leads/src/db/migrations.ts +0 -93
- package/microservices/microservice-leads/src/index.ts +0 -65
- package/microservices/microservice-leads/src/lib/enrichment.ts +0 -202
- package/microservices/microservice-leads/src/lib/scoring.ts +0 -134
- package/microservices/microservice-leads/src/mcp/index.ts +0 -533
- package/microservices/microservice-notes/package.json +0 -28
- package/microservices/microservice-notes/src/cli/index.ts +0 -63
- package/microservices/microservice-notes/src/db/database.ts +0 -91
- package/microservices/microservice-notes/src/db/migrations.ts +0 -40
- package/microservices/microservice-notes/src/db/notes.ts +0 -114
- package/microservices/microservice-notes/src/index.ts +0 -2
- package/microservices/microservice-notes/src/mcp/index.ts +0 -37
- package/microservices/microservice-notifications/package.json +0 -28
- package/microservices/microservice-notifications/src/cli/index.ts +0 -349
- package/microservices/microservice-notifications/src/db/database.ts +0 -91
- package/microservices/microservice-notifications/src/db/migrations.ts +0 -62
- package/microservices/microservice-notifications/src/db/notifications.ts +0 -509
- package/microservices/microservice-notifications/src/index.ts +0 -41
- package/microservices/microservice-notifications/src/mcp/index.ts +0 -422
- package/microservices/microservice-payments/package.json +0 -28
- package/microservices/microservice-payments/src/cli/index.ts +0 -609
- package/microservices/microservice-payments/src/db/database.ts +0 -91
- package/microservices/microservice-payments/src/db/migrations.ts +0 -81
- package/microservices/microservice-payments/src/db/payments.ts +0 -1204
- package/microservices/microservice-payments/src/index.ts +0 -51
- package/microservices/microservice-payments/src/mcp/index.ts +0 -683
- package/microservices/microservice-payroll/package.json +0 -28
- package/microservices/microservice-payroll/src/cli/index.ts +0 -643
- package/microservices/microservice-payroll/src/db/database.ts +0 -91
- package/microservices/microservice-payroll/src/db/migrations.ts +0 -95
- package/microservices/microservice-payroll/src/db/payroll.ts +0 -1377
- package/microservices/microservice-payroll/src/index.ts +0 -48
- package/microservices/microservice-payroll/src/mcp/index.ts +0 -666
- package/microservices/microservice-products/package.json +0 -28
- package/microservices/microservice-products/src/cli/index.ts +0 -416
- package/microservices/microservice-products/src/db/categories.ts +0 -154
- package/microservices/microservice-products/src/db/database.ts +0 -91
- package/microservices/microservice-products/src/db/migrations.ts +0 -58
- package/microservices/microservice-products/src/db/pricing-tiers.ts +0 -66
- package/microservices/microservice-products/src/db/products.ts +0 -452
- package/microservices/microservice-products/src/index.ts +0 -53
- package/microservices/microservice-products/src/mcp/index.ts +0 -453
- package/microservices/microservice-projects/package.json +0 -28
- package/microservices/microservice-projects/src/cli/index.ts +0 -480
- package/microservices/microservice-projects/src/db/database.ts +0 -91
- package/microservices/microservice-projects/src/db/migrations.ts +0 -65
- package/microservices/microservice-projects/src/db/projects.ts +0 -715
- package/microservices/microservice-projects/src/index.ts +0 -57
- package/microservices/microservice-projects/src/mcp/index.ts +0 -501
- package/microservices/microservice-proposals/package.json +0 -28
- package/microservices/microservice-proposals/src/cli/index.ts +0 -400
- package/microservices/microservice-proposals/src/db/database.ts +0 -91
- package/microservices/microservice-proposals/src/db/migrations.ts +0 -52
- package/microservices/microservice-proposals/src/db/proposals.ts +0 -532
- package/microservices/microservice-proposals/src/index.ts +0 -37
- package/microservices/microservice-proposals/src/mcp/index.ts +0 -375
- package/microservices/microservice-reading/package.json +0 -28
- package/microservices/microservice-reading/src/cli/index.ts +0 -464
- package/microservices/microservice-reading/src/db/database.ts +0 -91
- package/microservices/microservice-reading/src/db/migrations.ts +0 -59
- package/microservices/microservice-reading/src/db/reading.ts +0 -524
- package/microservices/microservice-reading/src/index.ts +0 -51
- package/microservices/microservice-reading/src/mcp/index.ts +0 -368
- package/microservices/microservice-shipping/package.json +0 -28
- package/microservices/microservice-shipping/src/cli/index.ts +0 -606
- package/microservices/microservice-shipping/src/db/database.ts +0 -91
- package/microservices/microservice-shipping/src/db/migrations.ts +0 -69
- package/microservices/microservice-shipping/src/db/shipping.ts +0 -1093
- package/microservices/microservice-shipping/src/index.ts +0 -53
- package/microservices/microservice-shipping/src/mcp/index.ts +0 -533
- package/microservices/microservice-social/package.json +0 -29
- package/microservices/microservice-social/src/cli/index.ts +0 -1583
- package/microservices/microservice-social/src/db/database.ts +0 -91
- package/microservices/microservice-social/src/db/migrations.ts +0 -160
- package/microservices/microservice-social/src/db/social.ts +0 -1076
- package/microservices/microservice-social/src/index.ts +0 -46
- package/microservices/microservice-social/src/lib/audience.ts +0 -353
- package/microservices/microservice-social/src/lib/content-ai.ts +0 -278
- package/microservices/microservice-social/src/lib/media.ts +0 -311
- package/microservices/microservice-social/src/lib/mentions.ts +0 -434
- package/microservices/microservice-social/src/lib/metrics-sync.ts +0 -264
- package/microservices/microservice-social/src/lib/publisher.ts +0 -377
- package/microservices/microservice-social/src/lib/scheduler.ts +0 -229
- package/microservices/microservice-social/src/lib/sentiment.ts +0 -256
- package/microservices/microservice-social/src/lib/threads.ts +0 -291
- package/microservices/microservice-social/src/mcp/index.ts +0 -1425
- package/microservices/microservice-social/src/server/index.ts +0 -441
- package/microservices/microservice-subscriptions/package.json +0 -28
- package/microservices/microservice-subscriptions/src/cli/index.ts +0 -715
- package/microservices/microservice-subscriptions/src/db/database.ts +0 -91
- package/microservices/microservice-subscriptions/src/db/migrations.ts +0 -125
- package/microservices/microservice-subscriptions/src/db/subscriptions.ts +0 -1256
- package/microservices/microservice-subscriptions/src/index.ts +0 -41
- package/microservices/microservice-subscriptions/src/mcp/index.ts +0 -631
- package/microservices/microservice-timesheets/package.json +0 -28
- package/microservices/microservice-timesheets/src/cli/index.ts +0 -373
- package/microservices/microservice-timesheets/src/db/database.ts +0 -91
- package/microservices/microservice-timesheets/src/db/locale.ts +0 -217
- package/microservices/microservice-timesheets/src/db/migrations.ts +0 -74
- package/microservices/microservice-timesheets/src/db/timesheets.ts +0 -447
- package/microservices/microservice-timesheets/src/index.ts +0 -44
- package/microservices/microservice-timesheets/src/mcp/index.ts +0 -269
- package/microservices/microservice-transcriber/package.json +0 -29
- package/microservices/microservice-transcriber/src/cli/index.ts +0 -1593
- package/microservices/microservice-transcriber/src/db/annotations.ts +0 -37
- package/microservices/microservice-transcriber/src/db/comments.ts +0 -166
- package/microservices/microservice-transcriber/src/db/database.ts +0 -91
- package/microservices/microservice-transcriber/src/db/migrations.ts +0 -118
- package/microservices/microservice-transcriber/src/db/proofread.ts +0 -119
- package/microservices/microservice-transcriber/src/db/transcripts.ts +0 -395
- package/microservices/microservice-transcriber/src/index.ts +0 -43
- package/microservices/microservice-transcriber/src/lib/config.ts +0 -77
- package/microservices/microservice-transcriber/src/lib/diff.ts +0 -91
- package/microservices/microservice-transcriber/src/lib/downloader.ts +0 -638
- package/microservices/microservice-transcriber/src/lib/feeds.ts +0 -62
- package/microservices/microservice-transcriber/src/lib/live.ts +0 -94
- package/microservices/microservice-transcriber/src/lib/notion.ts +0 -129
- package/microservices/microservice-transcriber/src/lib/proofread.ts +0 -296
- package/microservices/microservice-transcriber/src/lib/providers.ts +0 -713
- package/microservices/microservice-transcriber/src/lib/summarizer.ts +0 -147
- package/microservices/microservice-transcriber/src/lib/translator.ts +0 -75
- package/microservices/microservice-transcriber/src/lib/webhook.ts +0 -37
- package/microservices/microservice-transcriber/src/mcp/index.ts +0 -1330
- package/microservices/microservice-transcriber/src/server/index.ts +0 -199
- package/microservices/microservice-travel/package.json +0 -28
- package/microservices/microservice-travel/src/cli/index.ts +0 -505
- package/microservices/microservice-travel/src/db/database.ts +0 -91
- package/microservices/microservice-travel/src/db/migrations.ts +0 -77
- package/microservices/microservice-travel/src/db/travel.ts +0 -802
- package/microservices/microservice-travel/src/index.ts +0 -60
- package/microservices/microservice-travel/src/mcp/index.ts +0 -495
- package/microservices/microservice-wiki/package.json +0 -28
- package/microservices/microservice-wiki/src/cli/index.ts +0 -345
- package/microservices/microservice-wiki/src/db/database.ts +0 -91
- package/microservices/microservice-wiki/src/db/migrations.ts +0 -55
- package/microservices/microservice-wiki/src/db/wiki.ts +0 -395
- package/microservices/microservice-wiki/src/index.ts +0 -32
- package/microservices/microservice-wiki/src/mcp/index.ts +0 -344
|
@@ -1,1583 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import { Command } from "commander";
|
|
4
|
-
import { readFileSync } from "node:fs";
|
|
5
|
-
import {
|
|
6
|
-
createAccount,
|
|
7
|
-
getAccount,
|
|
8
|
-
listAccounts,
|
|
9
|
-
updateAccount,
|
|
10
|
-
deleteAccount,
|
|
11
|
-
createPost,
|
|
12
|
-
getPost,
|
|
13
|
-
listPosts,
|
|
14
|
-
updatePost,
|
|
15
|
-
deletePost,
|
|
16
|
-
schedulePost,
|
|
17
|
-
publishPost,
|
|
18
|
-
createTemplate,
|
|
19
|
-
listTemplates,
|
|
20
|
-
getTemplate,
|
|
21
|
-
deleteTemplate,
|
|
22
|
-
useTemplate,
|
|
23
|
-
getEngagementStats,
|
|
24
|
-
getStatsByPlatform,
|
|
25
|
-
getCalendar,
|
|
26
|
-
getOverallStats,
|
|
27
|
-
batchSchedule,
|
|
28
|
-
crossPost,
|
|
29
|
-
getBestTimeToPost,
|
|
30
|
-
reschedulePost,
|
|
31
|
-
submitPostForReview,
|
|
32
|
-
approvePost,
|
|
33
|
-
createRecurringPost,
|
|
34
|
-
getHashtagStats,
|
|
35
|
-
checkPlatformLimit,
|
|
36
|
-
PLATFORM_LIMITS,
|
|
37
|
-
type Platform,
|
|
38
|
-
type PostStatus,
|
|
39
|
-
type Recurrence,
|
|
40
|
-
} from "../db/social.js";
|
|
41
|
-
import {
|
|
42
|
-
startScheduler,
|
|
43
|
-
stopScheduler,
|
|
44
|
-
getSchedulerStatus,
|
|
45
|
-
} from "../lib/scheduler.js";
|
|
46
|
-
import {
|
|
47
|
-
syncAllMetrics,
|
|
48
|
-
syncAccountMetrics,
|
|
49
|
-
startMetricsSync,
|
|
50
|
-
stopMetricsSync,
|
|
51
|
-
getMetricsSyncStatus,
|
|
52
|
-
getSyncReport,
|
|
53
|
-
} from "../lib/metrics-sync.js";
|
|
54
|
-
import {
|
|
55
|
-
validateMedia,
|
|
56
|
-
getSupportedFormats,
|
|
57
|
-
uploadMedia,
|
|
58
|
-
} from "../lib/media.js";
|
|
59
|
-
import {
|
|
60
|
-
listMentions,
|
|
61
|
-
getMention,
|
|
62
|
-
markRead,
|
|
63
|
-
markAllRead,
|
|
64
|
-
getMentionStats,
|
|
65
|
-
replyToMention,
|
|
66
|
-
pollMentions,
|
|
67
|
-
stopPolling,
|
|
68
|
-
type MentionType,
|
|
69
|
-
} from "../lib/mentions.js";
|
|
70
|
-
import {
|
|
71
|
-
createThread,
|
|
72
|
-
getThread,
|
|
73
|
-
publishThread,
|
|
74
|
-
deleteThread,
|
|
75
|
-
createCarousel,
|
|
76
|
-
} from "../lib/threads.js";
|
|
77
|
-
import {
|
|
78
|
-
generatePost as aiGeneratePost,
|
|
79
|
-
suggestHashtags as aiSuggestHashtags,
|
|
80
|
-
optimizePost as aiOptimizePost,
|
|
81
|
-
generateThread as aiGenerateThread,
|
|
82
|
-
repurposePost as aiRepurposePost,
|
|
83
|
-
type Tone,
|
|
84
|
-
} from "../lib/content-ai.js";
|
|
85
|
-
import {
|
|
86
|
-
syncFollowers,
|
|
87
|
-
getAudienceInsights,
|
|
88
|
-
getFollowerGrowthChart,
|
|
89
|
-
getTopFollowers,
|
|
90
|
-
} from "../lib/audience.js";
|
|
91
|
-
import {
|
|
92
|
-
analyzeSentiment,
|
|
93
|
-
getSentimentReport,
|
|
94
|
-
autoAnalyzeMention,
|
|
95
|
-
} from "../lib/sentiment.js";
|
|
96
|
-
|
|
97
|
-
const program = new Command();
|
|
98
|
-
|
|
99
|
-
program
|
|
100
|
-
.name("microservice-social")
|
|
101
|
-
.description("Social media management microservice")
|
|
102
|
-
.version("0.0.1");
|
|
103
|
-
|
|
104
|
-
// --- Posts ---
|
|
105
|
-
|
|
106
|
-
const postCmd = program
|
|
107
|
-
.command("post")
|
|
108
|
-
.description("Post management");
|
|
109
|
-
|
|
110
|
-
postCmd
|
|
111
|
-
.command("create")
|
|
112
|
-
.description("Create a new post")
|
|
113
|
-
.requiredOption("--account <id>", "Account ID")
|
|
114
|
-
.requiredOption("--content <text>", "Post content")
|
|
115
|
-
.option("--media <urls>", "Comma-separated media URLs")
|
|
116
|
-
.option("--status <status>", "Post status (draft/scheduled/published/failed)", "draft")
|
|
117
|
-
.option("--scheduled-at <datetime>", "Schedule date/time")
|
|
118
|
-
.option("--tags <tags>", "Comma-separated tags")
|
|
119
|
-
.option("--recurring <recurrence>", "Recurrence (daily/weekly/biweekly/monthly)")
|
|
120
|
-
.option("--json", "Output as JSON", false)
|
|
121
|
-
.action((opts) => {
|
|
122
|
-
// Check platform character limit
|
|
123
|
-
const warning = checkPlatformLimit(opts.content, opts.account);
|
|
124
|
-
if (warning) {
|
|
125
|
-
console.warn(`Warning: Content (${warning.content_length} chars) exceeds ${warning.platform} limit (${warning.limit} chars) by ${warning.over_by} chars`);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const post = createPost({
|
|
129
|
-
account_id: opts.account,
|
|
130
|
-
content: opts.content,
|
|
131
|
-
media_urls: opts.media ? opts.media.split(",").map((u: string) => u.trim()) : undefined,
|
|
132
|
-
status: opts.status as PostStatus,
|
|
133
|
-
scheduled_at: opts.scheduledAt,
|
|
134
|
-
tags: opts.tags ? opts.tags.split(",").map((t: string) => t.trim()) : undefined,
|
|
135
|
-
recurrence: opts.recurring as Recurrence | undefined,
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
if (opts.json) {
|
|
139
|
-
console.log(JSON.stringify(post, null, 2));
|
|
140
|
-
} else {
|
|
141
|
-
console.log(`Created post: ${post.id} [${post.status}]`);
|
|
142
|
-
console.log(` Content: ${post.content.substring(0, 80)}${post.content.length > 80 ? "..." : ""}`);
|
|
143
|
-
}
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
postCmd
|
|
147
|
-
.command("list")
|
|
148
|
-
.description("List posts")
|
|
149
|
-
.option("--account <id>", "Filter by account ID")
|
|
150
|
-
.option("--status <status>", "Filter by status")
|
|
151
|
-
.option("--tag <tag>", "Filter by tag")
|
|
152
|
-
.option("--search <query>", "Search post content")
|
|
153
|
-
.option("--limit <n>", "Limit results")
|
|
154
|
-
.option("--json", "Output as JSON", false)
|
|
155
|
-
.action((opts) => {
|
|
156
|
-
const posts = listPosts({
|
|
157
|
-
account_id: opts.account,
|
|
158
|
-
status: opts.status as PostStatus | undefined,
|
|
159
|
-
tag: opts.tag,
|
|
160
|
-
search: opts.search,
|
|
161
|
-
limit: opts.limit ? parseInt(opts.limit) : undefined,
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
if (opts.json) {
|
|
165
|
-
console.log(JSON.stringify(posts, null, 2));
|
|
166
|
-
} else {
|
|
167
|
-
if (posts.length === 0) {
|
|
168
|
-
console.log("No posts found.");
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
for (const p of posts) {
|
|
172
|
-
const preview = p.content.substring(0, 60) + (p.content.length > 60 ? "..." : "");
|
|
173
|
-
const tags = p.tags.length ? ` [${p.tags.join(", ")}]` : "";
|
|
174
|
-
console.log(` [${p.status}] ${preview}${tags}`);
|
|
175
|
-
}
|
|
176
|
-
console.log(`\n${posts.length} post(s)`);
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
postCmd
|
|
181
|
-
.command("get")
|
|
182
|
-
.description("Get a post by ID")
|
|
183
|
-
.argument("<id>", "Post ID")
|
|
184
|
-
.option("--json", "Output as JSON", false)
|
|
185
|
-
.action((id, opts) => {
|
|
186
|
-
const post = getPost(id);
|
|
187
|
-
if (!post) {
|
|
188
|
-
console.error(`Post '${id}' not found.`);
|
|
189
|
-
process.exit(1);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (opts.json) {
|
|
193
|
-
console.log(JSON.stringify(post, null, 2));
|
|
194
|
-
} else {
|
|
195
|
-
console.log(`Post ${post.id} [${post.status}]`);
|
|
196
|
-
console.log(` Content: ${post.content}`);
|
|
197
|
-
console.log(` Account: ${post.account_id}`);
|
|
198
|
-
if (post.scheduled_at) console.log(` Scheduled: ${post.scheduled_at}`);
|
|
199
|
-
if (post.published_at) console.log(` Published: ${post.published_at}`);
|
|
200
|
-
if (post.tags.length) console.log(` Tags: ${post.tags.join(", ")}`);
|
|
201
|
-
if (Object.keys(post.engagement).length) {
|
|
202
|
-
console.log(` Engagement: ${JSON.stringify(post.engagement)}`);
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
postCmd
|
|
208
|
-
.command("schedule")
|
|
209
|
-
.description("Schedule a post")
|
|
210
|
-
.argument("<id>", "Post ID")
|
|
211
|
-
.requiredOption("--at <datetime>", "Schedule date/time")
|
|
212
|
-
.option("--recurring <recurrence>", "Recurrence (daily/weekly/biweekly/monthly)")
|
|
213
|
-
.option("--json", "Output as JSON", false)
|
|
214
|
-
.action((id, opts) => {
|
|
215
|
-
if (opts.recurring) {
|
|
216
|
-
const post = getPost(id);
|
|
217
|
-
if (!post) {
|
|
218
|
-
console.error(`Post '${id}' not found.`);
|
|
219
|
-
process.exit(1);
|
|
220
|
-
}
|
|
221
|
-
updatePost(id, { recurrence: opts.recurring as Recurrence });
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const post = schedulePost(id, opts.at);
|
|
225
|
-
if (!post) {
|
|
226
|
-
console.error(`Post '${id}' not found.`);
|
|
227
|
-
process.exit(1);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
if (opts.json) {
|
|
231
|
-
console.log(JSON.stringify(post, null, 2));
|
|
232
|
-
} else {
|
|
233
|
-
console.log(`Scheduled post ${post.id} for ${post.scheduled_at}${post.recurrence ? ` (recurring: ${post.recurrence})` : ""}`);
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
|
|
237
|
-
postCmd
|
|
238
|
-
.command("publish")
|
|
239
|
-
.description("Publish a post — sends to the platform API if --live, otherwise marks as published locally")
|
|
240
|
-
.argument("<id>", "Post ID")
|
|
241
|
-
.option("--platform-id <id>", "Platform post ID (for manual/local publish)")
|
|
242
|
-
.option("--live", "Publish via platform API (X, Meta)", false)
|
|
243
|
-
.option("--json", "Output as JSON", false)
|
|
244
|
-
.action(async (id, opts) => {
|
|
245
|
-
try {
|
|
246
|
-
let post;
|
|
247
|
-
if (opts.live) {
|
|
248
|
-
const { publishToApi } = await import("../lib/publisher.js");
|
|
249
|
-
post = await publishToApi(id);
|
|
250
|
-
} else {
|
|
251
|
-
post = publishPost(id, opts.platformId);
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (!post) {
|
|
255
|
-
console.error(`Post '${id}' not found.`);
|
|
256
|
-
process.exit(1);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
if (opts.json) {
|
|
260
|
-
console.log(JSON.stringify(post, null, 2));
|
|
261
|
-
} else {
|
|
262
|
-
console.log(`Published post ${post.id} at ${post.published_at}${post.platform_post_id ? ` (platform ID: ${post.platform_post_id})` : ""}`);
|
|
263
|
-
}
|
|
264
|
-
} catch (err) {
|
|
265
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
266
|
-
process.exit(1);
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
postCmd
|
|
271
|
-
.command("schedule-batch")
|
|
272
|
-
.description("Schedule multiple posts from a JSON file")
|
|
273
|
-
.requiredOption("--file <path>", "Path to JSON file with post array")
|
|
274
|
-
.option("--json", "Output as JSON", false)
|
|
275
|
-
.action((opts) => {
|
|
276
|
-
let postsData;
|
|
277
|
-
try {
|
|
278
|
-
postsData = JSON.parse(readFileSync(opts.file, "utf-8"));
|
|
279
|
-
} catch (err) {
|
|
280
|
-
console.error(`Failed to read file: ${err instanceof Error ? err.message : String(err)}`);
|
|
281
|
-
process.exit(1);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (!Array.isArray(postsData)) {
|
|
285
|
-
console.error("File must contain a JSON array of posts.");
|
|
286
|
-
process.exit(1);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const result = batchSchedule(postsData);
|
|
290
|
-
|
|
291
|
-
if (opts.json) {
|
|
292
|
-
console.log(JSON.stringify(result, null, 2));
|
|
293
|
-
} else {
|
|
294
|
-
console.log(`Scheduled ${result.scheduled.length} post(s)`);
|
|
295
|
-
if (result.errors.length > 0) {
|
|
296
|
-
console.log(`Errors: ${result.errors.length}`);
|
|
297
|
-
for (const err of result.errors) {
|
|
298
|
-
console.log(` [${err.index}] ${err.error}`);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
if (result.warnings.length > 0) {
|
|
302
|
-
console.log("Warnings:");
|
|
303
|
-
for (const w of result.warnings) {
|
|
304
|
-
console.log(` ${w.platform}: content (${w.content_length}) exceeds limit (${w.limit}) by ${w.over_by} chars`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
postCmd
|
|
311
|
-
.command("crosspost")
|
|
312
|
-
.description("Create identical post on multiple platforms")
|
|
313
|
-
.requiredOption("--content <text>", "Post content")
|
|
314
|
-
.requiredOption("--platforms <list>", "Comma-separated platforms (x,linkedin,bluesky,...)")
|
|
315
|
-
.option("--tags <tags>", "Comma-separated tags")
|
|
316
|
-
.option("--scheduled-at <datetime>", "Schedule date/time")
|
|
317
|
-
.option("--json", "Output as JSON", false)
|
|
318
|
-
.action((opts) => {
|
|
319
|
-
const platforms = opts.platforms.split(",").map((p: string) => p.trim()) as Platform[];
|
|
320
|
-
|
|
321
|
-
try {
|
|
322
|
-
const result = crossPost(opts.content, platforms, {
|
|
323
|
-
tags: opts.tags ? opts.tags.split(",").map((t: string) => t.trim()) : undefined,
|
|
324
|
-
scheduled_at: opts.scheduledAt,
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
if (opts.json) {
|
|
328
|
-
console.log(JSON.stringify(result, null, 2));
|
|
329
|
-
} else {
|
|
330
|
-
console.log(`Cross-posted to ${result.posts.length} platform(s):`);
|
|
331
|
-
for (const post of result.posts) {
|
|
332
|
-
const account = getAccount(post.account_id);
|
|
333
|
-
console.log(` ${account?.platform || "?"} → ${post.id} [${post.status}]`);
|
|
334
|
-
}
|
|
335
|
-
if (result.warnings.length > 0) {
|
|
336
|
-
console.log("Warnings:");
|
|
337
|
-
for (const w of result.warnings) {
|
|
338
|
-
console.log(` ${w.platform}: content (${w.content_length}) exceeds limit (${w.limit}) by ${w.over_by} chars`);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
} catch (err) {
|
|
343
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
344
|
-
process.exit(1);
|
|
345
|
-
}
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
postCmd
|
|
349
|
-
.command("reschedule")
|
|
350
|
-
.description("Reschedule a post to a new date/time")
|
|
351
|
-
.argument("<id>", "Post ID")
|
|
352
|
-
.requiredOption("--to <datetime>", "New schedule date/time")
|
|
353
|
-
.option("--json", "Output as JSON", false)
|
|
354
|
-
.action((id, opts) => {
|
|
355
|
-
try {
|
|
356
|
-
const post = reschedulePost(id, opts.to);
|
|
357
|
-
if (!post) {
|
|
358
|
-
console.error(`Post '${id}' not found.`);
|
|
359
|
-
process.exit(1);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
if (opts.json) {
|
|
363
|
-
console.log(JSON.stringify(post, null, 2));
|
|
364
|
-
} else {
|
|
365
|
-
console.log(`Rescheduled post ${post.id} to ${post.scheduled_at}`);
|
|
366
|
-
}
|
|
367
|
-
} catch (err) {
|
|
368
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
369
|
-
process.exit(1);
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
postCmd
|
|
374
|
-
.command("submit")
|
|
375
|
-
.description("Submit a draft post for review")
|
|
376
|
-
.argument("<id>", "Post ID")
|
|
377
|
-
.option("--json", "Output as JSON", false)
|
|
378
|
-
.action((id, opts) => {
|
|
379
|
-
try {
|
|
380
|
-
const post = submitPostForReview(id);
|
|
381
|
-
if (!post) {
|
|
382
|
-
console.error(`Post '${id}' not found.`);
|
|
383
|
-
process.exit(1);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
if (opts.json) {
|
|
387
|
-
console.log(JSON.stringify(post, null, 2));
|
|
388
|
-
} else {
|
|
389
|
-
console.log(`Post ${post.id} submitted for review [${post.status}]`);
|
|
390
|
-
}
|
|
391
|
-
} catch (err) {
|
|
392
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
393
|
-
process.exit(1);
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
postCmd
|
|
398
|
-
.command("approve")
|
|
399
|
-
.description("Approve a post pending review")
|
|
400
|
-
.argument("<id>", "Post ID")
|
|
401
|
-
.option("--at <datetime>", "Schedule date/time for approved post")
|
|
402
|
-
.option("--json", "Output as JSON", false)
|
|
403
|
-
.action((id, opts) => {
|
|
404
|
-
try {
|
|
405
|
-
const post = approvePost(id, opts.at);
|
|
406
|
-
if (!post) {
|
|
407
|
-
console.error(`Post '${id}' not found.`);
|
|
408
|
-
process.exit(1);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (opts.json) {
|
|
412
|
-
console.log(JSON.stringify(post, null, 2));
|
|
413
|
-
} else {
|
|
414
|
-
console.log(`Post ${post.id} approved and scheduled for ${post.scheduled_at}`);
|
|
415
|
-
}
|
|
416
|
-
} catch (err) {
|
|
417
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
418
|
-
process.exit(1);
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
postCmd
|
|
423
|
-
.command("delete")
|
|
424
|
-
.description("Delete a post")
|
|
425
|
-
.argument("<id>", "Post ID")
|
|
426
|
-
.action((id) => {
|
|
427
|
-
const deleted = deletePost(id);
|
|
428
|
-
if (deleted) {
|
|
429
|
-
console.log(`Deleted post ${id}`);
|
|
430
|
-
} else {
|
|
431
|
-
console.error(`Post '${id}' not found.`);
|
|
432
|
-
process.exit(1);
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
postCmd
|
|
437
|
-
.command("create-thread")
|
|
438
|
-
.description("Create a thread of multiple posts")
|
|
439
|
-
.requiredOption("--content <texts...>", "Content for each post in the thread")
|
|
440
|
-
.requiredOption("--account <id>", "Account ID")
|
|
441
|
-
.option("--schedule <datetime>", "Schedule date/time")
|
|
442
|
-
.option("--tags <tags>", "Comma-separated tags")
|
|
443
|
-
.option("--json", "Output as JSON", false)
|
|
444
|
-
.action((opts) => {
|
|
445
|
-
try {
|
|
446
|
-
const result = createThread(opts.content, opts.account, {
|
|
447
|
-
scheduledAt: opts.schedule,
|
|
448
|
-
tags: opts.tags ? opts.tags.split(",").map((t: string) => t.trim()) : undefined,
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
if (opts.json) {
|
|
452
|
-
console.log(JSON.stringify(result, null, 2));
|
|
453
|
-
} else {
|
|
454
|
-
console.log(`Created thread ${result.threadId} with ${result.posts.length} post(s)`);
|
|
455
|
-
for (const post of result.posts) {
|
|
456
|
-
const preview = post.content.substring(0, 60) + (post.content.length > 60 ? "..." : "");
|
|
457
|
-
console.log(` [${post.thread_position}] ${preview}`);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
} catch (err) {
|
|
461
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
462
|
-
process.exit(1);
|
|
463
|
-
}
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
postCmd
|
|
467
|
-
.command("get-thread")
|
|
468
|
-
.description("Get all posts in a thread")
|
|
469
|
-
.argument("<thread-id>", "Thread ID")
|
|
470
|
-
.option("--json", "Output as JSON", false)
|
|
471
|
-
.action((threadId, opts) => {
|
|
472
|
-
try {
|
|
473
|
-
const posts = getThread(threadId);
|
|
474
|
-
|
|
475
|
-
if (opts.json) {
|
|
476
|
-
console.log(JSON.stringify({ threadId, posts, count: posts.length }, null, 2));
|
|
477
|
-
} else {
|
|
478
|
-
console.log(`Thread ${threadId} (${posts.length} post(s)):`);
|
|
479
|
-
for (const post of posts) {
|
|
480
|
-
const preview = post.content.substring(0, 60) + (post.content.length > 60 ? "..." : "");
|
|
481
|
-
console.log(` [${post.thread_position}] [${post.status}] ${preview}`);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
} catch (err) {
|
|
485
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
486
|
-
process.exit(1);
|
|
487
|
-
}
|
|
488
|
-
});
|
|
489
|
-
|
|
490
|
-
postCmd
|
|
491
|
-
.command("publish-thread")
|
|
492
|
-
.description("Publish a thread to the platform API")
|
|
493
|
-
.argument("<thread-id>", "Thread ID")
|
|
494
|
-
.option("--live", "Publish via platform API", false)
|
|
495
|
-
.option("--json", "Output as JSON", false)
|
|
496
|
-
.action(async (threadId, opts) => {
|
|
497
|
-
try {
|
|
498
|
-
if (opts.live) {
|
|
499
|
-
const result = await publishThread(threadId);
|
|
500
|
-
|
|
501
|
-
if (opts.json) {
|
|
502
|
-
console.log(JSON.stringify(result, null, 2));
|
|
503
|
-
} else {
|
|
504
|
-
console.log(`Published thread ${result.threadId} (${result.posts.length} post(s))`);
|
|
505
|
-
for (const post of result.posts) {
|
|
506
|
-
console.log(` [${post.thread_position}] platform ID: ${post.platform_post_id}`);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
} else {
|
|
510
|
-
// Local publish — mark all posts as published
|
|
511
|
-
const posts = getThread(threadId);
|
|
512
|
-
for (const post of posts) {
|
|
513
|
-
publishPost(post.id);
|
|
514
|
-
}
|
|
515
|
-
if (opts.json) {
|
|
516
|
-
const updated = getThread(threadId);
|
|
517
|
-
console.log(JSON.stringify({ threadId, posts: updated }, null, 2));
|
|
518
|
-
} else {
|
|
519
|
-
console.log(`Locally published thread ${threadId} (${posts.length} post(s))`);
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
} catch (err) {
|
|
523
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
524
|
-
process.exit(1);
|
|
525
|
-
}
|
|
526
|
-
});
|
|
527
|
-
|
|
528
|
-
postCmd
|
|
529
|
-
.command("create-carousel")
|
|
530
|
-
.description("Create a carousel post with multiple images")
|
|
531
|
-
.requiredOption("--images <urls>", "Comma-separated image URLs")
|
|
532
|
-
.requiredOption("--account <id>", "Account ID")
|
|
533
|
-
.option("--captions <texts>", "Comma-separated captions")
|
|
534
|
-
.option("--json", "Output as JSON", false)
|
|
535
|
-
.action((opts) => {
|
|
536
|
-
try {
|
|
537
|
-
const images = opts.images.split(",").map((u: string) => u.trim());
|
|
538
|
-
const captions = opts.captions ? opts.captions.split(",").map((c: string) => c.trim()) : [];
|
|
539
|
-
const post = createCarousel(images, captions, opts.account);
|
|
540
|
-
|
|
541
|
-
if (opts.json) {
|
|
542
|
-
console.log(JSON.stringify(post, null, 2));
|
|
543
|
-
} else {
|
|
544
|
-
console.log(`Created carousel post: ${post.id}`);
|
|
545
|
-
console.log(` Images: ${post.media_urls.length}`);
|
|
546
|
-
if (post.content) console.log(` Content: ${post.content.substring(0, 80)}${post.content.length > 80 ? "..." : ""}`);
|
|
547
|
-
}
|
|
548
|
-
} catch (err) {
|
|
549
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
550
|
-
process.exit(1);
|
|
551
|
-
}
|
|
552
|
-
});
|
|
553
|
-
|
|
554
|
-
// --- Accounts ---
|
|
555
|
-
|
|
556
|
-
const accountCmd = program
|
|
557
|
-
.command("account")
|
|
558
|
-
.description("Account management");
|
|
559
|
-
|
|
560
|
-
accountCmd
|
|
561
|
-
.command("add")
|
|
562
|
-
.description("Add a social media account")
|
|
563
|
-
.requiredOption("--platform <platform>", "Platform (x/linkedin/instagram/threads/bluesky)")
|
|
564
|
-
.requiredOption("--handle <handle>", "Account handle")
|
|
565
|
-
.option("--display-name <name>", "Display name")
|
|
566
|
-
.option("--connected", "Mark as connected", false)
|
|
567
|
-
.option("--token-env <var>", "Environment variable for access token")
|
|
568
|
-
.option("--json", "Output as JSON", false)
|
|
569
|
-
.action((opts) => {
|
|
570
|
-
const account = createAccount({
|
|
571
|
-
platform: opts.platform as Platform,
|
|
572
|
-
handle: opts.handle,
|
|
573
|
-
display_name: opts.displayName,
|
|
574
|
-
connected: opts.connected,
|
|
575
|
-
access_token_env: opts.tokenEnv,
|
|
576
|
-
});
|
|
577
|
-
|
|
578
|
-
if (opts.json) {
|
|
579
|
-
console.log(JSON.stringify(account, null, 2));
|
|
580
|
-
} else {
|
|
581
|
-
console.log(`Added account: @${account.handle} on ${account.platform} (${account.id})`);
|
|
582
|
-
}
|
|
583
|
-
});
|
|
584
|
-
|
|
585
|
-
accountCmd
|
|
586
|
-
.command("list")
|
|
587
|
-
.description("List accounts")
|
|
588
|
-
.option("--platform <platform>", "Filter by platform")
|
|
589
|
-
.option("--connected", "Show only connected accounts")
|
|
590
|
-
.option("--json", "Output as JSON", false)
|
|
591
|
-
.action((opts) => {
|
|
592
|
-
const accounts = listAccounts({
|
|
593
|
-
platform: opts.platform as Platform | undefined,
|
|
594
|
-
connected: opts.connected ? true : undefined,
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
if (opts.json) {
|
|
598
|
-
console.log(JSON.stringify(accounts, null, 2));
|
|
599
|
-
} else {
|
|
600
|
-
if (accounts.length === 0) {
|
|
601
|
-
console.log("No accounts found.");
|
|
602
|
-
return;
|
|
603
|
-
}
|
|
604
|
-
for (const a of accounts) {
|
|
605
|
-
const connected = a.connected ? " (connected)" : "";
|
|
606
|
-
const name = a.display_name ? ` - ${a.display_name}` : "";
|
|
607
|
-
console.log(` [${a.platform}] @${a.handle}${name}${connected}`);
|
|
608
|
-
}
|
|
609
|
-
console.log(`\n${accounts.length} account(s)`);
|
|
610
|
-
}
|
|
611
|
-
});
|
|
612
|
-
|
|
613
|
-
accountCmd
|
|
614
|
-
.command("remove")
|
|
615
|
-
.description("Remove an account")
|
|
616
|
-
.argument("<id>", "Account ID")
|
|
617
|
-
.action((id) => {
|
|
618
|
-
const deleted = deleteAccount(id);
|
|
619
|
-
if (deleted) {
|
|
620
|
-
console.log(`Removed account ${id}`);
|
|
621
|
-
} else {
|
|
622
|
-
console.error(`Account '${id}' not found.`);
|
|
623
|
-
process.exit(1);
|
|
624
|
-
}
|
|
625
|
-
});
|
|
626
|
-
|
|
627
|
-
// --- Calendar ---
|
|
628
|
-
|
|
629
|
-
program
|
|
630
|
-
.command("calendar")
|
|
631
|
-
.description("View scheduled posts by date")
|
|
632
|
-
.option("--start <date>", "Start date (YYYY-MM-DD)")
|
|
633
|
-
.option("--end <date>", "End date (YYYY-MM-DD)")
|
|
634
|
-
.option("--json", "Output as JSON", false)
|
|
635
|
-
.action((opts) => {
|
|
636
|
-
const calendar = getCalendar(opts.start, opts.end);
|
|
637
|
-
|
|
638
|
-
if (opts.json) {
|
|
639
|
-
console.log(JSON.stringify(calendar, null, 2));
|
|
640
|
-
} else {
|
|
641
|
-
const dates = Object.keys(calendar).sort();
|
|
642
|
-
if (dates.length === 0) {
|
|
643
|
-
console.log("No scheduled posts.");
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
for (const date of dates) {
|
|
647
|
-
console.log(`\n${date}:`);
|
|
648
|
-
for (const post of calendar[date]) {
|
|
649
|
-
const preview = post.content.substring(0, 50) + (post.content.length > 50 ? "..." : "");
|
|
650
|
-
console.log(` ${post.scheduled_at} — ${preview}`);
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
// --- Analytics ---
|
|
657
|
-
|
|
658
|
-
const analyticsCmd = program
|
|
659
|
-
.command("analytics")
|
|
660
|
-
.description("Engagement analytics");
|
|
661
|
-
|
|
662
|
-
analyticsCmd
|
|
663
|
-
.command("engagement")
|
|
664
|
-
.description("View engagement analytics")
|
|
665
|
-
.option("--account <id>", "Filter by account ID")
|
|
666
|
-
.option("--by-platform", "Group by platform")
|
|
667
|
-
.option("--json", "Output as JSON", false)
|
|
668
|
-
.action((opts) => {
|
|
669
|
-
if (opts.byPlatform) {
|
|
670
|
-
const stats = getStatsByPlatform();
|
|
671
|
-
|
|
672
|
-
if (opts.json) {
|
|
673
|
-
console.log(JSON.stringify(stats, null, 2));
|
|
674
|
-
} else {
|
|
675
|
-
if (stats.length === 0) {
|
|
676
|
-
console.log("No platform data.");
|
|
677
|
-
return;
|
|
678
|
-
}
|
|
679
|
-
for (const s of stats) {
|
|
680
|
-
console.log(`\n${s.platform} (${s.account_count} account(s), ${s.post_count} post(s)):`);
|
|
681
|
-
console.log(` Published: ${s.engagement.total_posts}`);
|
|
682
|
-
console.log(` Likes: ${s.engagement.total_likes} (avg ${s.engagement.avg_likes})`);
|
|
683
|
-
console.log(` Shares: ${s.engagement.total_shares} (avg ${s.engagement.avg_shares})`);
|
|
684
|
-
console.log(` Comments: ${s.engagement.total_comments} (avg ${s.engagement.avg_comments})`);
|
|
685
|
-
console.log(` Impressions: ${s.engagement.total_impressions} (avg ${s.engagement.avg_impressions})`);
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
} else {
|
|
689
|
-
const stats = getEngagementStats(opts.account);
|
|
690
|
-
|
|
691
|
-
if (opts.json) {
|
|
692
|
-
console.log(JSON.stringify(stats, null, 2));
|
|
693
|
-
} else {
|
|
694
|
-
console.log("Engagement Analytics:");
|
|
695
|
-
console.log(` Published posts: ${stats.total_posts}`);
|
|
696
|
-
console.log(` Total likes: ${stats.total_likes} (avg ${stats.avg_likes})`);
|
|
697
|
-
console.log(` Total shares: ${stats.total_shares} (avg ${stats.avg_shares})`);
|
|
698
|
-
console.log(` Total comments: ${stats.total_comments} (avg ${stats.avg_comments})`);
|
|
699
|
-
console.log(` Total impressions: ${stats.total_impressions} (avg ${stats.avg_impressions})`);
|
|
700
|
-
console.log(` Total clicks: ${stats.total_clicks} (avg ${stats.avg_clicks})`);
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
});
|
|
704
|
-
|
|
705
|
-
analyticsCmd
|
|
706
|
-
.command("best-time")
|
|
707
|
-
.description("Find best time to post based on historical engagement")
|
|
708
|
-
.requiredOption("--account <id>", "Account ID")
|
|
709
|
-
.option("--json", "Output as JSON", false)
|
|
710
|
-
.action((opts) => {
|
|
711
|
-
const result = getBestTimeToPost(opts.account);
|
|
712
|
-
|
|
713
|
-
if (opts.json) {
|
|
714
|
-
console.log(JSON.stringify(result, null, 2));
|
|
715
|
-
} else {
|
|
716
|
-
if (result.total_analyzed === 0) {
|
|
717
|
-
console.log("No published posts to analyze.");
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
console.log(`Analyzed ${result.total_analyzed} published post(s)\n`);
|
|
721
|
-
|
|
722
|
-
if (result.best_hours.length > 0) {
|
|
723
|
-
console.log("Best hours to post:");
|
|
724
|
-
for (const slot of result.best_hours.slice(0, 5)) {
|
|
725
|
-
console.log(` ${slot.day_name} ${slot.hour}:00 — avg engagement: ${slot.avg_engagement} (${slot.post_count} posts)`);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
if (result.best_days.length > 0) {
|
|
730
|
-
console.log("\nBest days to post:");
|
|
731
|
-
for (const day of result.best_days) {
|
|
732
|
-
console.log(` ${day.day_name} — avg engagement: ${day.avg_engagement} (${day.post_count} posts)`);
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
});
|
|
737
|
-
|
|
738
|
-
analyticsCmd
|
|
739
|
-
.command("hashtags")
|
|
740
|
-
.description("Hashtag performance analytics")
|
|
741
|
-
.requiredOption("--account <id>", "Account ID")
|
|
742
|
-
.option("--json", "Output as JSON", false)
|
|
743
|
-
.action((opts) => {
|
|
744
|
-
const stats = getHashtagStats(opts.account);
|
|
745
|
-
|
|
746
|
-
if (opts.json) {
|
|
747
|
-
console.log(JSON.stringify(stats, null, 2));
|
|
748
|
-
} else {
|
|
749
|
-
if (stats.length === 0) {
|
|
750
|
-
console.log("No hashtags found in published posts.");
|
|
751
|
-
return;
|
|
752
|
-
}
|
|
753
|
-
console.log("Hashtag Analytics:");
|
|
754
|
-
for (const h of stats) {
|
|
755
|
-
console.log(` #${h.hashtag} — ${h.post_count} post(s), avg engagement: ${h.avg_engagement}`);
|
|
756
|
-
console.log(` likes: ${h.total_likes}, shares: ${h.total_shares}, comments: ${h.total_comments}`);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
});
|
|
760
|
-
|
|
761
|
-
// --- Templates ---
|
|
762
|
-
|
|
763
|
-
const templateCmd = program
|
|
764
|
-
.command("template")
|
|
765
|
-
.description("Post template management");
|
|
766
|
-
|
|
767
|
-
templateCmd
|
|
768
|
-
.command("create")
|
|
769
|
-
.description("Create a post template")
|
|
770
|
-
.requiredOption("--name <name>", "Template name")
|
|
771
|
-
.requiredOption("--content <content>", "Template content (use {{var}} for variables)")
|
|
772
|
-
.option("--variables <vars>", "Comma-separated variable names")
|
|
773
|
-
.option("--json", "Output as JSON", false)
|
|
774
|
-
.action((opts) => {
|
|
775
|
-
const template = createTemplate({
|
|
776
|
-
name: opts.name,
|
|
777
|
-
content: opts.content,
|
|
778
|
-
variables: opts.variables ? opts.variables.split(",").map((v: string) => v.trim()) : undefined,
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
if (opts.json) {
|
|
782
|
-
console.log(JSON.stringify(template, null, 2));
|
|
783
|
-
} else {
|
|
784
|
-
console.log(`Created template: ${template.name} (${template.id})`);
|
|
785
|
-
}
|
|
786
|
-
});
|
|
787
|
-
|
|
788
|
-
templateCmd
|
|
789
|
-
.command("list")
|
|
790
|
-
.description("List templates")
|
|
791
|
-
.option("--json", "Output as JSON", false)
|
|
792
|
-
.action((opts) => {
|
|
793
|
-
const templates = listTemplates();
|
|
794
|
-
|
|
795
|
-
if (opts.json) {
|
|
796
|
-
console.log(JSON.stringify(templates, null, 2));
|
|
797
|
-
} else {
|
|
798
|
-
if (templates.length === 0) {
|
|
799
|
-
console.log("No templates found.");
|
|
800
|
-
return;
|
|
801
|
-
}
|
|
802
|
-
for (const t of templates) {
|
|
803
|
-
const vars = t.variables.length ? ` (vars: ${t.variables.join(", ")})` : "";
|
|
804
|
-
console.log(` ${t.name}${vars}`);
|
|
805
|
-
}
|
|
806
|
-
console.log(`\n${templates.length} template(s)`);
|
|
807
|
-
}
|
|
808
|
-
});
|
|
809
|
-
|
|
810
|
-
templateCmd
|
|
811
|
-
.command("use")
|
|
812
|
-
.description("Create a post from a template")
|
|
813
|
-
.argument("<template-id>", "Template ID")
|
|
814
|
-
.requiredOption("--account <id>", "Account ID")
|
|
815
|
-
.option("--values <json>", "JSON object of variable values")
|
|
816
|
-
.option("--tags <tags>", "Comma-separated tags")
|
|
817
|
-
.option("--json", "Output as JSON", false)
|
|
818
|
-
.action((templateId, opts) => {
|
|
819
|
-
const values = opts.values ? JSON.parse(opts.values) : {};
|
|
820
|
-
const tags = opts.tags ? opts.tags.split(",").map((t: string) => t.trim()) : undefined;
|
|
821
|
-
|
|
822
|
-
const post = useTemplate(templateId, opts.account, values, tags);
|
|
823
|
-
|
|
824
|
-
if (opts.json) {
|
|
825
|
-
console.log(JSON.stringify(post, null, 2));
|
|
826
|
-
} else {
|
|
827
|
-
console.log(`Created post from template: ${post.id}`);
|
|
828
|
-
console.log(` Content: ${post.content.substring(0, 80)}${post.content.length > 80 ? "..." : ""}`);
|
|
829
|
-
}
|
|
830
|
-
});
|
|
831
|
-
|
|
832
|
-
templateCmd
|
|
833
|
-
.command("delete")
|
|
834
|
-
.description("Delete a template")
|
|
835
|
-
.argument("<id>", "Template ID")
|
|
836
|
-
.action((id) => {
|
|
837
|
-
const deleted = deleteTemplate(id);
|
|
838
|
-
if (deleted) {
|
|
839
|
-
console.log(`Deleted template ${id}`);
|
|
840
|
-
} else {
|
|
841
|
-
console.error(`Template '${id}' not found.`);
|
|
842
|
-
process.exit(1);
|
|
843
|
-
}
|
|
844
|
-
});
|
|
845
|
-
|
|
846
|
-
// --- Stats ---
|
|
847
|
-
|
|
848
|
-
program
|
|
849
|
-
.command("stats")
|
|
850
|
-
.description("Overall statistics")
|
|
851
|
-
.option("--json", "Output as JSON", false)
|
|
852
|
-
.action((opts) => {
|
|
853
|
-
const stats = getOverallStats();
|
|
854
|
-
|
|
855
|
-
if (opts.json) {
|
|
856
|
-
console.log(JSON.stringify(stats, null, 2));
|
|
857
|
-
} else {
|
|
858
|
-
console.log("Social Media Stats:");
|
|
859
|
-
console.log(` Accounts: ${stats.total_accounts}`);
|
|
860
|
-
console.log(` Posts: ${stats.total_posts}`);
|
|
861
|
-
if (Object.keys(stats.posts_by_status).length) {
|
|
862
|
-
for (const [status, count] of Object.entries(stats.posts_by_status)) {
|
|
863
|
-
console.log(` ${status}: ${count}`);
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
console.log(` Templates: ${stats.total_templates}`);
|
|
867
|
-
console.log(` Published engagement:`);
|
|
868
|
-
console.log(` Likes: ${stats.engagement.total_likes}`);
|
|
869
|
-
console.log(` Shares: ${stats.engagement.total_shares}`);
|
|
870
|
-
console.log(` Comments: ${stats.engagement.total_comments}`);
|
|
871
|
-
console.log(` Impressions: ${stats.engagement.total_impressions}`);
|
|
872
|
-
}
|
|
873
|
-
});
|
|
874
|
-
|
|
875
|
-
// --- Scheduler ---
|
|
876
|
-
|
|
877
|
-
const schedulerCmd = program
|
|
878
|
-
.command("scheduler")
|
|
879
|
-
.description("Scheduled post publishing worker");
|
|
880
|
-
|
|
881
|
-
schedulerCmd
|
|
882
|
-
.command("start")
|
|
883
|
-
.description("Start the scheduler to auto-publish due posts")
|
|
884
|
-
.option("--interval <ms>", "Check interval in milliseconds", "60000")
|
|
885
|
-
.action((opts) => {
|
|
886
|
-
try {
|
|
887
|
-
const interval = parseInt(opts.interval);
|
|
888
|
-
startScheduler(interval);
|
|
889
|
-
console.log(`Scheduler started (interval: ${interval}ms)`);
|
|
890
|
-
console.log("Press Ctrl+C to stop.");
|
|
891
|
-
// Keep process alive
|
|
892
|
-
process.on("SIGINT", () => {
|
|
893
|
-
stopScheduler();
|
|
894
|
-
console.log("\nScheduler stopped.");
|
|
895
|
-
process.exit(0);
|
|
896
|
-
});
|
|
897
|
-
} catch (err) {
|
|
898
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
899
|
-
process.exit(1);
|
|
900
|
-
}
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
schedulerCmd
|
|
904
|
-
.command("status")
|
|
905
|
-
.description("Show scheduler status")
|
|
906
|
-
.option("--json", "Output as JSON", false)
|
|
907
|
-
.action((opts) => {
|
|
908
|
-
const status = getSchedulerStatus();
|
|
909
|
-
|
|
910
|
-
if (opts.json) {
|
|
911
|
-
console.log(JSON.stringify(status, null, 2));
|
|
912
|
-
} else {
|
|
913
|
-
console.log("Scheduler Status:");
|
|
914
|
-
console.log(` Running: ${status.running}`);
|
|
915
|
-
console.log(` Last check: ${status.lastCheck || "never"}`);
|
|
916
|
-
console.log(` Posts processed: ${status.postsProcessed}`);
|
|
917
|
-
console.log(` Errors: ${status.errors}`);
|
|
918
|
-
}
|
|
919
|
-
});
|
|
920
|
-
|
|
921
|
-
schedulerCmd
|
|
922
|
-
.command("stop")
|
|
923
|
-
.description("Stop the scheduler")
|
|
924
|
-
.action(() => {
|
|
925
|
-
stopScheduler();
|
|
926
|
-
console.log("Scheduler stopped.");
|
|
927
|
-
});
|
|
928
|
-
|
|
929
|
-
// --- Media ---
|
|
930
|
-
|
|
931
|
-
const mediaCmd = program
|
|
932
|
-
.command("media")
|
|
933
|
-
.description("Media upload and validation");
|
|
934
|
-
|
|
935
|
-
mediaCmd
|
|
936
|
-
.command("upload")
|
|
937
|
-
.description("Upload a media file to a platform")
|
|
938
|
-
.argument("<file>", "Path to media file")
|
|
939
|
-
.requiredOption("--platform <platform>", "Target platform (x/linkedin/instagram/threads/bluesky)")
|
|
940
|
-
.option("--page-id <id>", "Page ID (required for Meta/LinkedIn)")
|
|
941
|
-
.option("--json", "Output as JSON", false)
|
|
942
|
-
.action(async (file, opts) => {
|
|
943
|
-
const platform = opts.platform as Platform;
|
|
944
|
-
|
|
945
|
-
// Validate first
|
|
946
|
-
const validation = validateMedia(file, platform);
|
|
947
|
-
if (!validation.valid) {
|
|
948
|
-
console.error("Validation errors:");
|
|
949
|
-
for (const err of validation.errors) {
|
|
950
|
-
console.error(` - ${err}`);
|
|
951
|
-
}
|
|
952
|
-
process.exit(1);
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
try {
|
|
956
|
-
const result = await uploadMedia(file, platform, opts.pageId);
|
|
957
|
-
|
|
958
|
-
if (opts.json) {
|
|
959
|
-
console.log(JSON.stringify(result, null, 2));
|
|
960
|
-
} else {
|
|
961
|
-
console.log(`Uploaded successfully. Media ID: ${result.mediaId}`);
|
|
962
|
-
if (result.url) console.log(` URL: ${result.url}`);
|
|
963
|
-
}
|
|
964
|
-
} catch (err) {
|
|
965
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
966
|
-
process.exit(1);
|
|
967
|
-
}
|
|
968
|
-
});
|
|
969
|
-
|
|
970
|
-
mediaCmd
|
|
971
|
-
.command("formats")
|
|
972
|
-
.description("Show supported media formats for a platform")
|
|
973
|
-
.requiredOption("--platform <platform>", "Platform (x/linkedin/instagram/threads/bluesky)")
|
|
974
|
-
.option("--json", "Output as JSON", false)
|
|
975
|
-
.action((opts) => {
|
|
976
|
-
const platform = opts.platform as Platform;
|
|
977
|
-
const formats = getSupportedFormats(platform);
|
|
978
|
-
|
|
979
|
-
if (opts.json) {
|
|
980
|
-
console.log(JSON.stringify({ platform, formats }, null, 2));
|
|
981
|
-
} else {
|
|
982
|
-
console.log(`Supported formats for ${platform}: ${formats.join(", ")}`);
|
|
983
|
-
}
|
|
984
|
-
});
|
|
985
|
-
|
|
986
|
-
mediaCmd
|
|
987
|
-
.command("validate")
|
|
988
|
-
.description("Validate a media file for a platform")
|
|
989
|
-
.argument("<file>", "Path to media file")
|
|
990
|
-
.requiredOption("--platform <platform>", "Target platform")
|
|
991
|
-
.option("--json", "Output as JSON", false)
|
|
992
|
-
.action((file, opts) => {
|
|
993
|
-
const platform = opts.platform as Platform;
|
|
994
|
-
const result = validateMedia(file, platform);
|
|
995
|
-
|
|
996
|
-
if (opts.json) {
|
|
997
|
-
console.log(JSON.stringify(result, null, 2));
|
|
998
|
-
} else {
|
|
999
|
-
if (result.valid) {
|
|
1000
|
-
console.log(`File '${file}' is valid for ${platform}.`);
|
|
1001
|
-
} else {
|
|
1002
|
-
console.error(`File '${file}' is NOT valid for ${platform}:`);
|
|
1003
|
-
for (const err of result.errors) {
|
|
1004
|
-
console.error(` - ${err}`);
|
|
1005
|
-
}
|
|
1006
|
-
process.exit(1);
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
});
|
|
1010
|
-
|
|
1011
|
-
// --- Metrics Sync ---
|
|
1012
|
-
|
|
1013
|
-
const metricsCmd = program
|
|
1014
|
-
.command("metrics")
|
|
1015
|
-
.description("Metrics sync — pull engagement data from platform APIs");
|
|
1016
|
-
|
|
1017
|
-
metricsCmd
|
|
1018
|
-
.command("sync")
|
|
1019
|
-
.description("Sync metrics for recent published posts")
|
|
1020
|
-
.option("--watch", "Continuously sync on an interval", false)
|
|
1021
|
-
.option("--interval <ms>", "Sync interval in milliseconds (with --watch)", "300000")
|
|
1022
|
-
.option("--json", "Output as JSON", false)
|
|
1023
|
-
.action(async (opts) => {
|
|
1024
|
-
if (opts.watch) {
|
|
1025
|
-
const interval = parseInt(opts.interval);
|
|
1026
|
-
try {
|
|
1027
|
-
startMetricsSync(interval);
|
|
1028
|
-
console.log(`Metrics sync started (interval: ${interval}ms)`);
|
|
1029
|
-
console.log("Press Ctrl+C to stop.");
|
|
1030
|
-
process.on("SIGINT", () => {
|
|
1031
|
-
stopMetricsSync();
|
|
1032
|
-
const report = getSyncReport();
|
|
1033
|
-
console.log(`\nMetrics sync stopped. Posts synced: ${report.posts_synced}, Errors: ${report.errors.length}`);
|
|
1034
|
-
process.exit(0);
|
|
1035
|
-
});
|
|
1036
|
-
} catch (err) {
|
|
1037
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1038
|
-
process.exit(1);
|
|
1039
|
-
}
|
|
1040
|
-
} else {
|
|
1041
|
-
try {
|
|
1042
|
-
const report = await syncAllMetrics();
|
|
1043
|
-
|
|
1044
|
-
if (opts.json) {
|
|
1045
|
-
console.log(JSON.stringify(report, null, 2));
|
|
1046
|
-
} else {
|
|
1047
|
-
console.log(`Metrics sync complete:`);
|
|
1048
|
-
console.log(` Posts synced: ${report.posts_synced}`);
|
|
1049
|
-
console.log(` Accounts synced: ${report.accounts_synced}`);
|
|
1050
|
-
if (report.errors.length > 0) {
|
|
1051
|
-
console.log(` Errors: ${report.errors.length}`);
|
|
1052
|
-
for (const err of report.errors) {
|
|
1053
|
-
console.log(` [${err.type}] ${err.id}: ${err.message}`);
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
}
|
|
1057
|
-
} catch (err) {
|
|
1058
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1059
|
-
process.exit(1);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
});
|
|
1063
|
-
|
|
1064
|
-
metricsCmd
|
|
1065
|
-
.command("status")
|
|
1066
|
-
.description("Show metrics sync status")
|
|
1067
|
-
.option("--json", "Output as JSON", false)
|
|
1068
|
-
.action((opts) => {
|
|
1069
|
-
const status = getMetricsSyncStatus();
|
|
1070
|
-
|
|
1071
|
-
if (opts.json) {
|
|
1072
|
-
console.log(JSON.stringify(status, null, 2));
|
|
1073
|
-
} else {
|
|
1074
|
-
console.log("Metrics Sync Status:");
|
|
1075
|
-
console.log(` Running: ${status.running}`);
|
|
1076
|
-
console.log(` Interval: ${status.interval_ms}ms`);
|
|
1077
|
-
console.log(` Last sync: ${status.last_sync || "never"}`);
|
|
1078
|
-
console.log(` Posts synced: ${status.posts_synced}`);
|
|
1079
|
-
console.log(` Accounts synced: ${status.accounts_synced}`);
|
|
1080
|
-
console.log(` Errors: ${status.errors}`);
|
|
1081
|
-
}
|
|
1082
|
-
});
|
|
1083
|
-
|
|
1084
|
-
// --- Mentions ---
|
|
1085
|
-
|
|
1086
|
-
const mentionsCmd = program
|
|
1087
|
-
.command("mentions")
|
|
1088
|
-
.description("Mention monitoring");
|
|
1089
|
-
|
|
1090
|
-
mentionsCmd
|
|
1091
|
-
.command("list")
|
|
1092
|
-
.description("List mentions")
|
|
1093
|
-
.option("--account <id>", "Filter by account ID")
|
|
1094
|
-
.option("--unread", "Show only unread mentions", false)
|
|
1095
|
-
.option("--type <type>", "Filter by type (mention/reply/quote/dm)")
|
|
1096
|
-
.option("--limit <n>", "Limit results")
|
|
1097
|
-
.option("--json", "Output as JSON", false)
|
|
1098
|
-
.action((opts) => {
|
|
1099
|
-
const mentions = listMentions(opts.account, {
|
|
1100
|
-
unread: opts.unread ? true : undefined,
|
|
1101
|
-
type: opts.type as MentionType | undefined,
|
|
1102
|
-
limit: opts.limit ? parseInt(opts.limit) : undefined,
|
|
1103
|
-
});
|
|
1104
|
-
|
|
1105
|
-
if (opts.json) {
|
|
1106
|
-
console.log(JSON.stringify(mentions, null, 2));
|
|
1107
|
-
} else {
|
|
1108
|
-
if (mentions.length === 0) {
|
|
1109
|
-
console.log("No mentions found.");
|
|
1110
|
-
return;
|
|
1111
|
-
}
|
|
1112
|
-
for (const m of mentions) {
|
|
1113
|
-
const readFlag = m.read ? " " : "*";
|
|
1114
|
-
const preview = m.content ? m.content.substring(0, 60) + (m.content.length > 60 ? "..." : "") : "(no content)";
|
|
1115
|
-
const author = m.author_handle ? `@${m.author_handle}` : m.author || "unknown";
|
|
1116
|
-
console.log(` ${readFlag} [${m.type || "?"}] ${author}: ${preview}`);
|
|
1117
|
-
}
|
|
1118
|
-
console.log(`\n${mentions.length} mention(s)`);
|
|
1119
|
-
}
|
|
1120
|
-
});
|
|
1121
|
-
|
|
1122
|
-
mentionsCmd
|
|
1123
|
-
.command("reply")
|
|
1124
|
-
.description("Reply to a mention")
|
|
1125
|
-
.argument("<id>", "Mention ID")
|
|
1126
|
-
.requiredOption("--content <text>", "Reply content")
|
|
1127
|
-
.option("--json", "Output as JSON", false)
|
|
1128
|
-
.action(async (id, opts) => {
|
|
1129
|
-
try {
|
|
1130
|
-
const result = await replyToMention(id, opts.content);
|
|
1131
|
-
if (opts.json) {
|
|
1132
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1133
|
-
} else {
|
|
1134
|
-
console.log(`Reply sent. Platform reply ID: ${result.platformReplyId}`);
|
|
1135
|
-
}
|
|
1136
|
-
} catch (err) {
|
|
1137
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1138
|
-
process.exit(1);
|
|
1139
|
-
}
|
|
1140
|
-
});
|
|
1141
|
-
|
|
1142
|
-
mentionsCmd
|
|
1143
|
-
.command("read")
|
|
1144
|
-
.description("Mark a mention as read")
|
|
1145
|
-
.argument("<id>", "Mention ID")
|
|
1146
|
-
.option("--json", "Output as JSON", false)
|
|
1147
|
-
.action((id, opts) => {
|
|
1148
|
-
const mention = markRead(id);
|
|
1149
|
-
if (!mention) {
|
|
1150
|
-
console.error(`Mention '${id}' not found.`);
|
|
1151
|
-
process.exit(1);
|
|
1152
|
-
}
|
|
1153
|
-
if (opts.json) {
|
|
1154
|
-
console.log(JSON.stringify(mention, null, 2));
|
|
1155
|
-
} else {
|
|
1156
|
-
console.log(`Marked mention ${id} as read.`);
|
|
1157
|
-
}
|
|
1158
|
-
});
|
|
1159
|
-
|
|
1160
|
-
mentionsCmd
|
|
1161
|
-
.command("read-all")
|
|
1162
|
-
.description("Mark all mentions for an account as read")
|
|
1163
|
-
.argument("<account-id>", "Account ID")
|
|
1164
|
-
.action((accountId) => {
|
|
1165
|
-
const count = markAllRead(accountId);
|
|
1166
|
-
console.log(`Marked ${count} mention(s) as read.`);
|
|
1167
|
-
});
|
|
1168
|
-
|
|
1169
|
-
mentionsCmd
|
|
1170
|
-
.command("watch")
|
|
1171
|
-
.description("Start polling for new mentions")
|
|
1172
|
-
.option("--interval <ms>", "Poll interval in milliseconds", "120000")
|
|
1173
|
-
.action((opts) => {
|
|
1174
|
-
const interval = parseInt(opts.interval);
|
|
1175
|
-
pollMentions(interval);
|
|
1176
|
-
console.log(`Mention poller started (interval: ${interval}ms)`);
|
|
1177
|
-
console.log("Press Ctrl+C to stop.");
|
|
1178
|
-
process.on("SIGINT", () => {
|
|
1179
|
-
stopPolling();
|
|
1180
|
-
console.log("\nMention poller stopped.");
|
|
1181
|
-
process.exit(0);
|
|
1182
|
-
});
|
|
1183
|
-
});
|
|
1184
|
-
|
|
1185
|
-
mentionsCmd
|
|
1186
|
-
.command("stats")
|
|
1187
|
-
.description("Get mention statistics for an account")
|
|
1188
|
-
.argument("<account-id>", "Account ID")
|
|
1189
|
-
.option("--json", "Output as JSON", false)
|
|
1190
|
-
.action((accountId, opts) => {
|
|
1191
|
-
const stats = getMentionStats(accountId);
|
|
1192
|
-
if (opts.json) {
|
|
1193
|
-
console.log(JSON.stringify(stats, null, 2));
|
|
1194
|
-
} else {
|
|
1195
|
-
console.log("Mention Stats:");
|
|
1196
|
-
console.log(` Total: ${stats.total}`);
|
|
1197
|
-
console.log(` Unread: ${stats.unread}`);
|
|
1198
|
-
if (Object.keys(stats.by_type).length) {
|
|
1199
|
-
console.log(" By type:");
|
|
1200
|
-
for (const [type, count] of Object.entries(stats.by_type)) {
|
|
1201
|
-
console.log(` ${type}: ${count}`);
|
|
1202
|
-
}
|
|
1203
|
-
}
|
|
1204
|
-
if (Object.keys(stats.by_sentiment).length) {
|
|
1205
|
-
console.log(" By sentiment:");
|
|
1206
|
-
for (const [sentiment, count] of Object.entries(stats.by_sentiment)) {
|
|
1207
|
-
console.log(` ${sentiment}: ${count}`);
|
|
1208
|
-
}
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
});
|
|
1212
|
-
|
|
1213
|
-
// --- AI Content Generation ---
|
|
1214
|
-
|
|
1215
|
-
const aiCmd = program
|
|
1216
|
-
.command("ai")
|
|
1217
|
-
.description("AI-powered content generation");
|
|
1218
|
-
|
|
1219
|
-
aiCmd
|
|
1220
|
-
.command("generate")
|
|
1221
|
-
.description("Generate a post using AI")
|
|
1222
|
-
.requiredOption("--topic <topic>", "Topic to write about")
|
|
1223
|
-
.requiredOption("--platform <platform>", "Target platform (x/linkedin/instagram/threads/bluesky)")
|
|
1224
|
-
.option("--tone <tone>", "Tone: professional, casual, witty", "professional")
|
|
1225
|
-
.option("--no-hashtags", "Disable hashtags")
|
|
1226
|
-
.option("--emoji", "Include emojis", false)
|
|
1227
|
-
.option("--language <lang>", "Language", "English")
|
|
1228
|
-
.option("--json", "Output as JSON", false)
|
|
1229
|
-
.action(async (opts) => {
|
|
1230
|
-
try {
|
|
1231
|
-
const result = await aiGeneratePost(opts.topic, opts.platform as Platform, {
|
|
1232
|
-
tone: opts.tone as Tone,
|
|
1233
|
-
includeHashtags: opts.hashtags,
|
|
1234
|
-
includeEmoji: opts.emoji,
|
|
1235
|
-
language: opts.language,
|
|
1236
|
-
});
|
|
1237
|
-
|
|
1238
|
-
if (opts.json) {
|
|
1239
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1240
|
-
} else {
|
|
1241
|
-
console.log("Generated Post:");
|
|
1242
|
-
console.log(` ${result.content}`);
|
|
1243
|
-
if (result.hashtags.length) {
|
|
1244
|
-
console.log(` Hashtags: ${result.hashtags.map((h: string) => "#" + h).join(" ")}`);
|
|
1245
|
-
}
|
|
1246
|
-
if (result.suggested_media_prompt) {
|
|
1247
|
-
console.log(` Media prompt: ${result.suggested_media_prompt}`);
|
|
1248
|
-
}
|
|
1249
|
-
}
|
|
1250
|
-
} catch (err) {
|
|
1251
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1252
|
-
process.exit(1);
|
|
1253
|
-
}
|
|
1254
|
-
});
|
|
1255
|
-
|
|
1256
|
-
aiCmd
|
|
1257
|
-
.command("suggest-hashtags")
|
|
1258
|
-
.description("Suggest hashtags for content")
|
|
1259
|
-
.requiredOption("--content <text>", "Post content to analyze")
|
|
1260
|
-
.requiredOption("--platform <platform>", "Target platform")
|
|
1261
|
-
.option("--count <n>", "Number of hashtags", "5")
|
|
1262
|
-
.option("--json", "Output as JSON", false)
|
|
1263
|
-
.action(async (opts) => {
|
|
1264
|
-
try {
|
|
1265
|
-
const hashtags = await aiSuggestHashtags(opts.content, opts.platform as Platform, parseInt(opts.count));
|
|
1266
|
-
|
|
1267
|
-
if (opts.json) {
|
|
1268
|
-
console.log(JSON.stringify({ hashtags }, null, 2));
|
|
1269
|
-
} else {
|
|
1270
|
-
console.log("Suggested Hashtags:");
|
|
1271
|
-
for (const h of hashtags) {
|
|
1272
|
-
console.log(` #${h}`);
|
|
1273
|
-
}
|
|
1274
|
-
}
|
|
1275
|
-
} catch (err) {
|
|
1276
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1277
|
-
process.exit(1);
|
|
1278
|
-
}
|
|
1279
|
-
});
|
|
1280
|
-
|
|
1281
|
-
aiCmd
|
|
1282
|
-
.command("optimize")
|
|
1283
|
-
.description("Optimize a post for better engagement")
|
|
1284
|
-
.argument("<post-id>", "Post ID to optimize")
|
|
1285
|
-
.option("--json", "Output as JSON", false)
|
|
1286
|
-
.action(async (postId, opts) => {
|
|
1287
|
-
try {
|
|
1288
|
-
const post = getPost(postId);
|
|
1289
|
-
if (!post) {
|
|
1290
|
-
console.error(`Post '${postId}' not found.`);
|
|
1291
|
-
process.exit(1);
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
const account = getAccount(post.account_id);
|
|
1295
|
-
if (!account) {
|
|
1296
|
-
console.error(`Account '${post.account_id}' not found.`);
|
|
1297
|
-
process.exit(1);
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
const result = await aiOptimizePost(post.content, account.platform);
|
|
1301
|
-
|
|
1302
|
-
if (opts.json) {
|
|
1303
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1304
|
-
} else {
|
|
1305
|
-
console.log("Optimized Post:");
|
|
1306
|
-
console.log(` ${result.optimized_content}`);
|
|
1307
|
-
if (result.improvements.length) {
|
|
1308
|
-
console.log("\nImprovements:");
|
|
1309
|
-
for (const imp of result.improvements) {
|
|
1310
|
-
console.log(` - ${imp}`);
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
}
|
|
1314
|
-
} catch (err) {
|
|
1315
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1316
|
-
process.exit(1);
|
|
1317
|
-
}
|
|
1318
|
-
});
|
|
1319
|
-
|
|
1320
|
-
aiCmd
|
|
1321
|
-
.command("generate-thread")
|
|
1322
|
-
.description("Generate a multi-tweet thread using AI")
|
|
1323
|
-
.requiredOption("--topic <topic>", "Topic to write about")
|
|
1324
|
-
.option("--tweets <n>", "Number of tweets in thread", "5")
|
|
1325
|
-
.option("--json", "Output as JSON", false)
|
|
1326
|
-
.action(async (opts) => {
|
|
1327
|
-
try {
|
|
1328
|
-
const tweets = await aiGenerateThread(opts.topic, parseInt(opts.tweets));
|
|
1329
|
-
|
|
1330
|
-
if (opts.json) {
|
|
1331
|
-
console.log(JSON.stringify({ tweets }, null, 2));
|
|
1332
|
-
} else {
|
|
1333
|
-
console.log("Generated Thread:");
|
|
1334
|
-
for (let i = 0; i < tweets.length; i++) {
|
|
1335
|
-
console.log(`\n [${i + 1}/${tweets.length}] ${tweets[i]}`);
|
|
1336
|
-
}
|
|
1337
|
-
}
|
|
1338
|
-
} catch (err) {
|
|
1339
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1340
|
-
process.exit(1);
|
|
1341
|
-
}
|
|
1342
|
-
});
|
|
1343
|
-
|
|
1344
|
-
aiCmd
|
|
1345
|
-
.command("repurpose")
|
|
1346
|
-
.description("Repurpose a post for a different platform")
|
|
1347
|
-
.argument("<post-id>", "Post ID to repurpose")
|
|
1348
|
-
.requiredOption("--to <platform>", "Target platform (x/linkedin/instagram/threads/bluesky)")
|
|
1349
|
-
.option("--json", "Output as JSON", false)
|
|
1350
|
-
.action(async (postId, opts) => {
|
|
1351
|
-
try {
|
|
1352
|
-
const post = getPost(postId);
|
|
1353
|
-
if (!post) {
|
|
1354
|
-
console.error(`Post '${postId}' not found.`);
|
|
1355
|
-
process.exit(1);
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
const account = getAccount(post.account_id);
|
|
1359
|
-
if (!account) {
|
|
1360
|
-
console.error(`Account '${post.account_id}' not found.`);
|
|
1361
|
-
process.exit(1);
|
|
1362
|
-
}
|
|
1363
|
-
|
|
1364
|
-
const result = await aiRepurposePost(post.content, account.platform, opts.to as Platform);
|
|
1365
|
-
|
|
1366
|
-
if (opts.json) {
|
|
1367
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1368
|
-
} else {
|
|
1369
|
-
console.log(`Repurposed for ${opts.to}:`);
|
|
1370
|
-
console.log(` ${result.content}`);
|
|
1371
|
-
}
|
|
1372
|
-
} catch (err) {
|
|
1373
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1374
|
-
process.exit(1);
|
|
1375
|
-
}
|
|
1376
|
-
});
|
|
1377
|
-
|
|
1378
|
-
// --- Audience ---
|
|
1379
|
-
|
|
1380
|
-
const audienceCmd = program
|
|
1381
|
-
.command("audience")
|
|
1382
|
-
.description("Follower sync and audience insights");
|
|
1383
|
-
|
|
1384
|
-
audienceCmd
|
|
1385
|
-
.command("sync")
|
|
1386
|
-
.description("Sync followers from the platform API")
|
|
1387
|
-
.argument("<account-id>", "Account ID")
|
|
1388
|
-
.option("--json", "Output as JSON", false)
|
|
1389
|
-
.action((accountId, opts) => {
|
|
1390
|
-
const result = syncFollowers(accountId);
|
|
1391
|
-
|
|
1392
|
-
if (opts.json) {
|
|
1393
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1394
|
-
} else {
|
|
1395
|
-
console.log(`Follower sync complete:`);
|
|
1396
|
-
console.log(` Synced: ${result.synced}`);
|
|
1397
|
-
console.log(` New: ${result.new_followers}`);
|
|
1398
|
-
console.log(` Unfollowed: ${result.unfollowed}`);
|
|
1399
|
-
if (result.message) console.log(` Note: ${result.message}`);
|
|
1400
|
-
}
|
|
1401
|
-
});
|
|
1402
|
-
|
|
1403
|
-
audienceCmd
|
|
1404
|
-
.command("insights")
|
|
1405
|
-
.description("Get audience insights for an account")
|
|
1406
|
-
.argument("<account-id>", "Account ID")
|
|
1407
|
-
.option("--json", "Output as JSON", false)
|
|
1408
|
-
.action((accountId, opts) => {
|
|
1409
|
-
const insights = getAudienceInsights(accountId);
|
|
1410
|
-
|
|
1411
|
-
if (opts.json) {
|
|
1412
|
-
console.log(JSON.stringify(insights, null, 2));
|
|
1413
|
-
} else {
|
|
1414
|
-
console.log("Audience Insights:");
|
|
1415
|
-
console.log(` Total followers: ${insights.total_followers}`);
|
|
1416
|
-
console.log(` Growth (7d): ${insights.growth_rate_7d}%`);
|
|
1417
|
-
console.log(` Growth (30d): ${insights.growth_rate_30d}%`);
|
|
1418
|
-
console.log(` New followers (7d): ${insights.new_followers_7d}`);
|
|
1419
|
-
console.log(` Lost followers (7d): ${insights.lost_followers_7d}`);
|
|
1420
|
-
if (insights.top_followers.length > 0) {
|
|
1421
|
-
console.log(" Top followers:");
|
|
1422
|
-
for (const f of insights.top_followers.slice(0, 5)) {
|
|
1423
|
-
console.log(` @${f.username || "?"} — ${f.follower_count} followers`);
|
|
1424
|
-
}
|
|
1425
|
-
}
|
|
1426
|
-
}
|
|
1427
|
-
});
|
|
1428
|
-
|
|
1429
|
-
audienceCmd
|
|
1430
|
-
.command("growth")
|
|
1431
|
-
.description("Show follower growth chart data")
|
|
1432
|
-
.argument("<account-id>", "Account ID")
|
|
1433
|
-
.option("--days <n>", "Number of days", "30")
|
|
1434
|
-
.option("--json", "Output as JSON", false)
|
|
1435
|
-
.action((accountId, opts) => {
|
|
1436
|
-
const days = parseInt(opts.days);
|
|
1437
|
-
const chart = getFollowerGrowthChart(accountId, days);
|
|
1438
|
-
|
|
1439
|
-
if (opts.json) {
|
|
1440
|
-
console.log(JSON.stringify(chart, null, 2));
|
|
1441
|
-
} else {
|
|
1442
|
-
if (chart.length === 0) {
|
|
1443
|
-
console.log("No snapshot data available.");
|
|
1444
|
-
return;
|
|
1445
|
-
}
|
|
1446
|
-
console.log(`Follower Growth (last ${days} days):`);
|
|
1447
|
-
for (const point of chart) {
|
|
1448
|
-
console.log(` ${point.date}: ${point.count}`);
|
|
1449
|
-
}
|
|
1450
|
-
}
|
|
1451
|
-
});
|
|
1452
|
-
|
|
1453
|
-
audienceCmd
|
|
1454
|
-
.command("top")
|
|
1455
|
-
.description("Show top followers by their follower count")
|
|
1456
|
-
.argument("<account-id>", "Account ID")
|
|
1457
|
-
.option("--limit <n>", "Number of results", "10")
|
|
1458
|
-
.option("--json", "Output as JSON", false)
|
|
1459
|
-
.action((accountId, opts) => {
|
|
1460
|
-
const limit = parseInt(opts.limit);
|
|
1461
|
-
const followers = getTopFollowers(accountId, limit);
|
|
1462
|
-
|
|
1463
|
-
if (opts.json) {
|
|
1464
|
-
console.log(JSON.stringify(followers, null, 2));
|
|
1465
|
-
} else {
|
|
1466
|
-
if (followers.length === 0) {
|
|
1467
|
-
console.log("No followers found.");
|
|
1468
|
-
return;
|
|
1469
|
-
}
|
|
1470
|
-
console.log("Top Followers:");
|
|
1471
|
-
for (const f of followers) {
|
|
1472
|
-
const name = f.display_name ? ` (${f.display_name})` : "";
|
|
1473
|
-
console.log(` @${f.username || "?"}${name} — ${f.follower_count} followers`);
|
|
1474
|
-
}
|
|
1475
|
-
}
|
|
1476
|
-
});
|
|
1477
|
-
|
|
1478
|
-
// --- Sentiment ---
|
|
1479
|
-
|
|
1480
|
-
const sentimentCmd = program
|
|
1481
|
-
.command("sentiment")
|
|
1482
|
-
.description("Sentiment analysis for mentions");
|
|
1483
|
-
|
|
1484
|
-
sentimentCmd
|
|
1485
|
-
.command("analyze")
|
|
1486
|
-
.description("Analyze sentiment of a text")
|
|
1487
|
-
.requiredOption("--text <text>", "Text to analyze")
|
|
1488
|
-
.option("--json", "Output as JSON", false)
|
|
1489
|
-
.action(async (opts) => {
|
|
1490
|
-
try {
|
|
1491
|
-
const result = await analyzeSentiment(opts.text);
|
|
1492
|
-
if (opts.json) {
|
|
1493
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1494
|
-
} else {
|
|
1495
|
-
console.log(`Sentiment: ${result.sentiment}`);
|
|
1496
|
-
console.log(`Score: ${result.score}`);
|
|
1497
|
-
if (result.keywords.length) {
|
|
1498
|
-
console.log(`Keywords: ${result.keywords.join(", ")}`);
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
} catch (err) {
|
|
1502
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1503
|
-
process.exit(1);
|
|
1504
|
-
}
|
|
1505
|
-
});
|
|
1506
|
-
|
|
1507
|
-
sentimentCmd
|
|
1508
|
-
.command("report")
|
|
1509
|
-
.description("Get sentiment report for an account")
|
|
1510
|
-
.argument("<account-id>", "Account ID")
|
|
1511
|
-
.option("--days <n>", "Number of days to analyze", "30")
|
|
1512
|
-
.option("--json", "Output as JSON", false)
|
|
1513
|
-
.action((accountId, opts) => {
|
|
1514
|
-
const days = parseInt(opts.days);
|
|
1515
|
-
const report = getSentimentReport(accountId, days);
|
|
1516
|
-
|
|
1517
|
-
if (opts.json) {
|
|
1518
|
-
console.log(JSON.stringify(report, null, 2));
|
|
1519
|
-
} else {
|
|
1520
|
-
if (report.total_analyzed === 0) {
|
|
1521
|
-
console.log("No analyzed mentions found.");
|
|
1522
|
-
return;
|
|
1523
|
-
}
|
|
1524
|
-
console.log(`Sentiment Report (last ${days} days):`);
|
|
1525
|
-
console.log(` Total analyzed: ${report.total_analyzed}`);
|
|
1526
|
-
console.log(` Positive: ${report.positive_pct}%`);
|
|
1527
|
-
console.log(` Neutral: ${report.neutral_pct}%`);
|
|
1528
|
-
console.log(` Negative: ${report.negative_pct}%`);
|
|
1529
|
-
if (report.trending_keywords.length) {
|
|
1530
|
-
console.log(` Trending keywords: ${report.trending_keywords.join(", ")}`);
|
|
1531
|
-
}
|
|
1532
|
-
if (report.most_positive) {
|
|
1533
|
-
const preview = report.most_positive.content.substring(0, 60);
|
|
1534
|
-
console.log(` Most positive: ${preview}${report.most_positive.content.length > 60 ? "..." : ""}`);
|
|
1535
|
-
}
|
|
1536
|
-
if (report.most_negative) {
|
|
1537
|
-
const preview = report.most_negative.content.substring(0, 60);
|
|
1538
|
-
console.log(` Most negative: ${preview}${report.most_negative.content.length > 60 ? "..." : ""}`);
|
|
1539
|
-
}
|
|
1540
|
-
}
|
|
1541
|
-
});
|
|
1542
|
-
|
|
1543
|
-
sentimentCmd
|
|
1544
|
-
.command("auto")
|
|
1545
|
-
.description("Auto-analyze sentiment for a mention")
|
|
1546
|
-
.argument("<mention-id>", "Mention ID")
|
|
1547
|
-
.option("--json", "Output as JSON", false)
|
|
1548
|
-
.action(async (mentionId, opts) => {
|
|
1549
|
-
try {
|
|
1550
|
-
const result = await autoAnalyzeMention(mentionId);
|
|
1551
|
-
if (!result) {
|
|
1552
|
-
console.error("Analysis returned no result.");
|
|
1553
|
-
process.exit(1);
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
if (opts.json) {
|
|
1557
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1558
|
-
} else {
|
|
1559
|
-
console.log(`Analyzed mention ${mentionId}:`);
|
|
1560
|
-
console.log(` Sentiment: ${result.sentiment}`);
|
|
1561
|
-
console.log(` Score: ${result.score}`);
|
|
1562
|
-
if (result.keywords.length) {
|
|
1563
|
-
console.log(` Keywords: ${result.keywords.join(", ")}`);
|
|
1564
|
-
}
|
|
1565
|
-
}
|
|
1566
|
-
} catch (err) {
|
|
1567
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
1568
|
-
process.exit(1);
|
|
1569
|
-
}
|
|
1570
|
-
});
|
|
1571
|
-
|
|
1572
|
-
// --- Serve ---
|
|
1573
|
-
|
|
1574
|
-
program
|
|
1575
|
-
.command("serve")
|
|
1576
|
-
.description("Start REST API server with web dashboard")
|
|
1577
|
-
.option("--port <port>", "Port to listen on", "19650")
|
|
1578
|
-
.action(async (opts) => {
|
|
1579
|
-
process.env["PORT"] = opts.port;
|
|
1580
|
-
await import("../server/index.js");
|
|
1581
|
-
});
|
|
1582
|
-
|
|
1583
|
-
program.parse(process.argv);
|