@hasna/microservices 0.0.4 → 0.0.6

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 (57) hide show
  1. package/bin/index.js +9 -1
  2. package/bin/mcp.js +9 -1
  3. package/dist/index.js +9 -1
  4. package/microservices/microservice-ads/src/cli/index.ts +198 -0
  5. package/microservices/microservice-ads/src/db/campaigns.ts +304 -0
  6. package/microservices/microservice-ads/src/mcp/index.ts +160 -0
  7. package/microservices/microservice-company/package.json +27 -0
  8. package/microservices/microservice-company/src/cli/index.ts +1126 -0
  9. package/microservices/microservice-company/src/db/company.ts +854 -0
  10. package/microservices/microservice-company/src/db/database.ts +93 -0
  11. package/microservices/microservice-company/src/db/migrations.ts +214 -0
  12. package/microservices/microservice-company/src/db/workflow-migrations.ts +44 -0
  13. package/microservices/microservice-company/src/index.ts +60 -0
  14. package/microservices/microservice-company/src/lib/audit.ts +168 -0
  15. package/microservices/microservice-company/src/lib/finance.ts +299 -0
  16. package/microservices/microservice-company/src/lib/settings.ts +85 -0
  17. package/microservices/microservice-company/src/lib/workflows.ts +698 -0
  18. package/microservices/microservice-company/src/mcp/index.ts +991 -0
  19. package/microservices/microservice-contracts/src/cli/index.ts +410 -23
  20. package/microservices/microservice-contracts/src/db/contracts.ts +430 -1
  21. package/microservices/microservice-contracts/src/db/migrations.ts +83 -0
  22. package/microservices/microservice-contracts/src/mcp/index.ts +312 -3
  23. package/microservices/microservice-domains/src/cli/index.ts +673 -0
  24. package/microservices/microservice-domains/src/db/domains.ts +613 -0
  25. package/microservices/microservice-domains/src/index.ts +21 -0
  26. package/microservices/microservice-domains/src/lib/brandsight.ts +285 -0
  27. package/microservices/microservice-domains/src/lib/godaddy.ts +328 -0
  28. package/microservices/microservice-domains/src/lib/namecheap.ts +474 -0
  29. package/microservices/microservice-domains/src/lib/registrar.ts +355 -0
  30. package/microservices/microservice-domains/src/mcp/index.ts +413 -0
  31. package/microservices/microservice-hiring/src/cli/index.ts +318 -8
  32. package/microservices/microservice-hiring/src/db/hiring.ts +503 -0
  33. package/microservices/microservice-hiring/src/db/migrations.ts +21 -0
  34. package/microservices/microservice-hiring/src/index.ts +29 -0
  35. package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
  36. package/microservices/microservice-hiring/src/mcp/index.ts +245 -0
  37. package/microservices/microservice-payments/src/cli/index.ts +255 -3
  38. package/microservices/microservice-payments/src/db/migrations.ts +18 -0
  39. package/microservices/microservice-payments/src/db/payments.ts +552 -0
  40. package/microservices/microservice-payments/src/mcp/index.ts +223 -0
  41. package/microservices/microservice-payroll/src/cli/index.ts +269 -0
  42. package/microservices/microservice-payroll/src/db/migrations.ts +26 -0
  43. package/microservices/microservice-payroll/src/db/payroll.ts +636 -0
  44. package/microservices/microservice-payroll/src/mcp/index.ts +246 -0
  45. package/microservices/microservice-shipping/src/cli/index.ts +211 -3
  46. package/microservices/microservice-shipping/src/db/migrations.ts +8 -0
  47. package/microservices/microservice-shipping/src/db/shipping.ts +453 -3
  48. package/microservices/microservice-shipping/src/mcp/index.ts +149 -1
  49. package/microservices/microservice-social/src/cli/index.ts +244 -2
  50. package/microservices/microservice-social/src/db/migrations.ts +33 -0
  51. package/microservices/microservice-social/src/db/social.ts +378 -4
  52. package/microservices/microservice-social/src/mcp/index.ts +221 -1
  53. package/microservices/microservice-subscriptions/src/cli/index.ts +315 -0
  54. package/microservices/microservice-subscriptions/src/db/migrations.ts +68 -0
  55. package/microservices/microservice-subscriptions/src/db/subscriptions.ts +567 -3
  56. package/microservices/microservice-subscriptions/src/mcp/index.ts +267 -1
  57. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env bun
2
2
 
3
3
  import { Command } from "commander";
4
+ import { readFileSync } from "node:fs";
4
5
  import {
5
6
  createAccount,
6
7
  getAccount,
@@ -23,8 +24,19 @@ import {
23
24
  getStatsByPlatform,
24
25
  getCalendar,
25
26
  getOverallStats,
27
+ batchSchedule,
28
+ crossPost,
29
+ getBestTimeToPost,
30
+ reschedulePost,
31
+ submitPostForReview,
32
+ approvePost,
33
+ createRecurringPost,
34
+ getHashtagStats,
35
+ checkPlatformLimit,
36
+ PLATFORM_LIMITS,
26
37
  type Platform,
27
38
  type PostStatus,
39
+ type Recurrence,
28
40
  } from "../db/social.js";
29
41
 
30
42
  const program = new Command();
@@ -49,8 +61,15 @@ postCmd
49
61
  .option("--status <status>", "Post status (draft/scheduled/published/failed)", "draft")
50
62
  .option("--scheduled-at <datetime>", "Schedule date/time")
51
63
  .option("--tags <tags>", "Comma-separated tags")
64
+ .option("--recurring <recurrence>", "Recurrence (daily/weekly/biweekly/monthly)")
52
65
  .option("--json", "Output as JSON", false)
53
66
  .action((opts) => {
67
+ // Check platform character limit
68
+ const warning = checkPlatformLimit(opts.content, opts.account);
69
+ if (warning) {
70
+ console.warn(`Warning: Content (${warning.content_length} chars) exceeds ${warning.platform} limit (${warning.limit} chars) by ${warning.over_by} chars`);
71
+ }
72
+
54
73
  const post = createPost({
55
74
  account_id: opts.account,
56
75
  content: opts.content,
@@ -58,6 +77,7 @@ postCmd
58
77
  status: opts.status as PostStatus,
59
78
  scheduled_at: opts.scheduledAt,
60
79
  tags: opts.tags ? opts.tags.split(",").map((t: string) => t.trim()) : undefined,
80
+ recurrence: opts.recurring as Recurrence | undefined,
61
81
  });
62
82
 
63
83
  if (opts.json) {
@@ -134,8 +154,18 @@ postCmd
134
154
  .description("Schedule a post")
135
155
  .argument("<id>", "Post ID")
136
156
  .requiredOption("--at <datetime>", "Schedule date/time")
157
+ .option("--recurring <recurrence>", "Recurrence (daily/weekly/biweekly/monthly)")
137
158
  .option("--json", "Output as JSON", false)
138
159
  .action((id, opts) => {
160
+ if (opts.recurring) {
161
+ const post = getPost(id);
162
+ if (!post) {
163
+ console.error(`Post '${id}' not found.`);
164
+ process.exit(1);
165
+ }
166
+ updatePost(id, { recurrence: opts.recurring as Recurrence });
167
+ }
168
+
139
169
  const post = schedulePost(id, opts.at);
140
170
  if (!post) {
141
171
  console.error(`Post '${id}' not found.`);
@@ -145,7 +175,7 @@ postCmd
145
175
  if (opts.json) {
146
176
  console.log(JSON.stringify(post, null, 2));
147
177
  } else {
148
- console.log(`Scheduled post ${post.id} for ${post.scheduled_at}`);
178
+ console.log(`Scheduled post ${post.id} for ${post.scheduled_at}${post.recurrence ? ` (recurring: ${post.recurrence})` : ""}`);
149
179
  }
150
180
  });
151
181
 
@@ -169,6 +199,158 @@ postCmd
169
199
  }
170
200
  });
171
201
 
202
+ postCmd
203
+ .command("schedule-batch")
204
+ .description("Schedule multiple posts from a JSON file")
205
+ .requiredOption("--file <path>", "Path to JSON file with post array")
206
+ .option("--json", "Output as JSON", false)
207
+ .action((opts) => {
208
+ let postsData;
209
+ try {
210
+ postsData = JSON.parse(readFileSync(opts.file, "utf-8"));
211
+ } catch (err) {
212
+ console.error(`Failed to read file: ${err instanceof Error ? err.message : String(err)}`);
213
+ process.exit(1);
214
+ }
215
+
216
+ if (!Array.isArray(postsData)) {
217
+ console.error("File must contain a JSON array of posts.");
218
+ process.exit(1);
219
+ }
220
+
221
+ const result = batchSchedule(postsData);
222
+
223
+ if (opts.json) {
224
+ console.log(JSON.stringify(result, null, 2));
225
+ } else {
226
+ console.log(`Scheduled ${result.scheduled.length} post(s)`);
227
+ if (result.errors.length > 0) {
228
+ console.log(`Errors: ${result.errors.length}`);
229
+ for (const err of result.errors) {
230
+ console.log(` [${err.index}] ${err.error}`);
231
+ }
232
+ }
233
+ if (result.warnings.length > 0) {
234
+ console.log("Warnings:");
235
+ for (const w of result.warnings) {
236
+ console.log(` ${w.platform}: content (${w.content_length}) exceeds limit (${w.limit}) by ${w.over_by} chars`);
237
+ }
238
+ }
239
+ }
240
+ });
241
+
242
+ postCmd
243
+ .command("crosspost")
244
+ .description("Create identical post on multiple platforms")
245
+ .requiredOption("--content <text>", "Post content")
246
+ .requiredOption("--platforms <list>", "Comma-separated platforms (x,linkedin,bluesky,...)")
247
+ .option("--tags <tags>", "Comma-separated tags")
248
+ .option("--scheduled-at <datetime>", "Schedule date/time")
249
+ .option("--json", "Output as JSON", false)
250
+ .action((opts) => {
251
+ const platforms = opts.platforms.split(",").map((p: string) => p.trim()) as Platform[];
252
+
253
+ try {
254
+ const result = crossPost(opts.content, platforms, {
255
+ tags: opts.tags ? opts.tags.split(",").map((t: string) => t.trim()) : undefined,
256
+ scheduled_at: opts.scheduledAt,
257
+ });
258
+
259
+ if (opts.json) {
260
+ console.log(JSON.stringify(result, null, 2));
261
+ } else {
262
+ console.log(`Cross-posted to ${result.posts.length} platform(s):`);
263
+ for (const post of result.posts) {
264
+ const account = getAccount(post.account_id);
265
+ console.log(` ${account?.platform || "?"} → ${post.id} [${post.status}]`);
266
+ }
267
+ if (result.warnings.length > 0) {
268
+ console.log("Warnings:");
269
+ for (const w of result.warnings) {
270
+ console.log(` ${w.platform}: content (${w.content_length}) exceeds limit (${w.limit}) by ${w.over_by} chars`);
271
+ }
272
+ }
273
+ }
274
+ } catch (err) {
275
+ console.error(err instanceof Error ? err.message : String(err));
276
+ process.exit(1);
277
+ }
278
+ });
279
+
280
+ postCmd
281
+ .command("reschedule")
282
+ .description("Reschedule a post to a new date/time")
283
+ .argument("<id>", "Post ID")
284
+ .requiredOption("--to <datetime>", "New schedule date/time")
285
+ .option("--json", "Output as JSON", false)
286
+ .action((id, opts) => {
287
+ try {
288
+ const post = reschedulePost(id, opts.to);
289
+ if (!post) {
290
+ console.error(`Post '${id}' not found.`);
291
+ process.exit(1);
292
+ }
293
+
294
+ if (opts.json) {
295
+ console.log(JSON.stringify(post, null, 2));
296
+ } else {
297
+ console.log(`Rescheduled post ${post.id} to ${post.scheduled_at}`);
298
+ }
299
+ } catch (err) {
300
+ console.error(err instanceof Error ? err.message : String(err));
301
+ process.exit(1);
302
+ }
303
+ });
304
+
305
+ postCmd
306
+ .command("submit")
307
+ .description("Submit a draft post for review")
308
+ .argument("<id>", "Post ID")
309
+ .option("--json", "Output as JSON", false)
310
+ .action((id, opts) => {
311
+ try {
312
+ const post = submitPostForReview(id);
313
+ if (!post) {
314
+ console.error(`Post '${id}' not found.`);
315
+ process.exit(1);
316
+ }
317
+
318
+ if (opts.json) {
319
+ console.log(JSON.stringify(post, null, 2));
320
+ } else {
321
+ console.log(`Post ${post.id} submitted for review [${post.status}]`);
322
+ }
323
+ } catch (err) {
324
+ console.error(err instanceof Error ? err.message : String(err));
325
+ process.exit(1);
326
+ }
327
+ });
328
+
329
+ postCmd
330
+ .command("approve")
331
+ .description("Approve a post pending review")
332
+ .argument("<id>", "Post ID")
333
+ .option("--at <datetime>", "Schedule date/time for approved post")
334
+ .option("--json", "Output as JSON", false)
335
+ .action((id, opts) => {
336
+ try {
337
+ const post = approvePost(id, opts.at);
338
+ if (!post) {
339
+ console.error(`Post '${id}' not found.`);
340
+ process.exit(1);
341
+ }
342
+
343
+ if (opts.json) {
344
+ console.log(JSON.stringify(post, null, 2));
345
+ } else {
346
+ console.log(`Post ${post.id} approved and scheduled for ${post.scheduled_at}`);
347
+ }
348
+ } catch (err) {
349
+ console.error(err instanceof Error ? err.message : String(err));
350
+ process.exit(1);
351
+ }
352
+ });
353
+
172
354
  postCmd
173
355
  .command("delete")
174
356
  .description("Delete a post")
@@ -287,8 +469,12 @@ program
287
469
 
288
470
  // --- Analytics ---
289
471
 
290
- program
472
+ const analyticsCmd = program
291
473
  .command("analytics")
474
+ .description("Engagement analytics");
475
+
476
+ analyticsCmd
477
+ .command("engagement")
292
478
  .description("View engagement analytics")
293
479
  .option("--account <id>", "Filter by account ID")
294
480
  .option("--by-platform", "Group by platform")
@@ -330,6 +516,62 @@ program
330
516
  }
331
517
  });
332
518
 
519
+ analyticsCmd
520
+ .command("best-time")
521
+ .description("Find best time to post based on historical engagement")
522
+ .requiredOption("--account <id>", "Account ID")
523
+ .option("--json", "Output as JSON", false)
524
+ .action((opts) => {
525
+ const result = getBestTimeToPost(opts.account);
526
+
527
+ if (opts.json) {
528
+ console.log(JSON.stringify(result, null, 2));
529
+ } else {
530
+ if (result.total_analyzed === 0) {
531
+ console.log("No published posts to analyze.");
532
+ return;
533
+ }
534
+ console.log(`Analyzed ${result.total_analyzed} published post(s)\n`);
535
+
536
+ if (result.best_hours.length > 0) {
537
+ console.log("Best hours to post:");
538
+ for (const slot of result.best_hours.slice(0, 5)) {
539
+ console.log(` ${slot.day_name} ${slot.hour}:00 — avg engagement: ${slot.avg_engagement} (${slot.post_count} posts)`);
540
+ }
541
+ }
542
+
543
+ if (result.best_days.length > 0) {
544
+ console.log("\nBest days to post:");
545
+ for (const day of result.best_days) {
546
+ console.log(` ${day.day_name} — avg engagement: ${day.avg_engagement} (${day.post_count} posts)`);
547
+ }
548
+ }
549
+ }
550
+ });
551
+
552
+ analyticsCmd
553
+ .command("hashtags")
554
+ .description("Hashtag performance analytics")
555
+ .requiredOption("--account <id>", "Account ID")
556
+ .option("--json", "Output as JSON", false)
557
+ .action((opts) => {
558
+ const stats = getHashtagStats(opts.account);
559
+
560
+ if (opts.json) {
561
+ console.log(JSON.stringify(stats, null, 2));
562
+ } else {
563
+ if (stats.length === 0) {
564
+ console.log("No hashtags found in published posts.");
565
+ return;
566
+ }
567
+ console.log("Hashtag Analytics:");
568
+ for (const h of stats) {
569
+ console.log(` #${h.hashtag} — ${h.post_count} post(s), avg engagement: ${h.avg_engagement}`);
570
+ console.log(` likes: ${h.total_likes}, shares: ${h.total_shares}, comments: ${h.total_comments}`);
571
+ }
572
+ }
573
+ });
574
+
333
575
  // --- Templates ---
334
576
 
335
577
  const templateCmd = program
@@ -52,4 +52,37 @@ export const MIGRATIONS: MigrationEntry[] = [
52
52
  CREATE INDEX IF NOT EXISTS idx_templates_name ON templates(name);
53
53
  `,
54
54
  },
55
+ {
56
+ id: 2,
57
+ name: "add_pending_review_and_recurrence",
58
+ sql: `
59
+ -- Recreate posts table with expanded status enum and recurrence column.
60
+ -- SQLite doesn't support ALTER CHECK constraints, so we recreate the table.
61
+ CREATE TABLE IF NOT EXISTS posts_new (
62
+ id TEXT PRIMARY KEY,
63
+ account_id TEXT NOT NULL REFERENCES accounts(id) ON DELETE CASCADE,
64
+ content TEXT NOT NULL,
65
+ media_urls TEXT NOT NULL DEFAULT '[]',
66
+ status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'scheduled', 'published', 'failed', 'pending_review')),
67
+ scheduled_at TEXT,
68
+ published_at TEXT,
69
+ platform_post_id TEXT,
70
+ engagement TEXT NOT NULL DEFAULT '{}',
71
+ tags TEXT NOT NULL DEFAULT '[]',
72
+ recurrence TEXT,
73
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
74
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
75
+ );
76
+
77
+ INSERT INTO posts_new SELECT id, account_id, content, media_urls, status, scheduled_at, published_at, platform_post_id, engagement, tags, NULL, created_at, updated_at FROM posts;
78
+ DROP TABLE posts;
79
+ ALTER TABLE posts_new RENAME TO posts;
80
+
81
+ CREATE INDEX IF NOT EXISTS idx_posts_account ON posts(account_id);
82
+ CREATE INDEX IF NOT EXISTS idx_posts_status ON posts(status);
83
+ CREATE INDEX IF NOT EXISTS idx_posts_scheduled ON posts(scheduled_at);
84
+ CREATE INDEX IF NOT EXISTS idx_posts_published ON posts(published_at);
85
+ CREATE INDEX IF NOT EXISTS idx_posts_recurrence ON posts(recurrence);
86
+ `,
87
+ },
55
88
  ];