@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.
- package/bin/index.js +9 -1
- package/bin/mcp.js +9 -1
- package/dist/index.js +9 -1
- package/microservices/microservice-ads/src/cli/index.ts +198 -0
- package/microservices/microservice-ads/src/db/campaigns.ts +304 -0
- package/microservices/microservice-ads/src/mcp/index.ts +160 -0
- package/microservices/microservice-company/package.json +27 -0
- package/microservices/microservice-company/src/cli/index.ts +1126 -0
- package/microservices/microservice-company/src/db/company.ts +854 -0
- package/microservices/microservice-company/src/db/database.ts +93 -0
- package/microservices/microservice-company/src/db/migrations.ts +214 -0
- package/microservices/microservice-company/src/db/workflow-migrations.ts +44 -0
- package/microservices/microservice-company/src/index.ts +60 -0
- package/microservices/microservice-company/src/lib/audit.ts +168 -0
- package/microservices/microservice-company/src/lib/finance.ts +299 -0
- package/microservices/microservice-company/src/lib/settings.ts +85 -0
- package/microservices/microservice-company/src/lib/workflows.ts +698 -0
- package/microservices/microservice-company/src/mcp/index.ts +991 -0
- package/microservices/microservice-contracts/src/cli/index.ts +410 -23
- package/microservices/microservice-contracts/src/db/contracts.ts +430 -1
- package/microservices/microservice-contracts/src/db/migrations.ts +83 -0
- package/microservices/microservice-contracts/src/mcp/index.ts +312 -3
- package/microservices/microservice-domains/src/cli/index.ts +673 -0
- package/microservices/microservice-domains/src/db/domains.ts +613 -0
- package/microservices/microservice-domains/src/index.ts +21 -0
- package/microservices/microservice-domains/src/lib/brandsight.ts +285 -0
- package/microservices/microservice-domains/src/lib/godaddy.ts +328 -0
- package/microservices/microservice-domains/src/lib/namecheap.ts +474 -0
- package/microservices/microservice-domains/src/lib/registrar.ts +355 -0
- package/microservices/microservice-domains/src/mcp/index.ts +413 -0
- package/microservices/microservice-hiring/src/cli/index.ts +318 -8
- package/microservices/microservice-hiring/src/db/hiring.ts +503 -0
- package/microservices/microservice-hiring/src/db/migrations.ts +21 -0
- package/microservices/microservice-hiring/src/index.ts +29 -0
- package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
- package/microservices/microservice-hiring/src/mcp/index.ts +245 -0
- package/microservices/microservice-payments/src/cli/index.ts +255 -3
- package/microservices/microservice-payments/src/db/migrations.ts +18 -0
- package/microservices/microservice-payments/src/db/payments.ts +552 -0
- package/microservices/microservice-payments/src/mcp/index.ts +223 -0
- package/microservices/microservice-payroll/src/cli/index.ts +269 -0
- package/microservices/microservice-payroll/src/db/migrations.ts +26 -0
- package/microservices/microservice-payroll/src/db/payroll.ts +636 -0
- package/microservices/microservice-payroll/src/mcp/index.ts +246 -0
- package/microservices/microservice-shipping/src/cli/index.ts +211 -3
- package/microservices/microservice-shipping/src/db/migrations.ts +8 -0
- package/microservices/microservice-shipping/src/db/shipping.ts +453 -3
- package/microservices/microservice-shipping/src/mcp/index.ts +149 -1
- package/microservices/microservice-social/src/cli/index.ts +244 -2
- package/microservices/microservice-social/src/db/migrations.ts +33 -0
- package/microservices/microservice-social/src/db/social.ts +378 -4
- package/microservices/microservice-social/src/mcp/index.ts +221 -1
- package/microservices/microservice-subscriptions/src/cli/index.ts +315 -0
- package/microservices/microservice-subscriptions/src/db/migrations.ts +68 -0
- package/microservices/microservice-subscriptions/src/db/subscriptions.ts +567 -3
- package/microservices/microservice-subscriptions/src/mcp/index.ts +267 -1
- 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
|
];
|