@hasna/microservices 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/microservices/microservice-ads/src/cli/index.ts +198 -0
  2. package/microservices/microservice-ads/src/db/campaigns.ts +304 -0
  3. package/microservices/microservice-ads/src/mcp/index.ts +160 -0
  4. package/microservices/microservice-contracts/src/cli/index.ts +410 -23
  5. package/microservices/microservice-contracts/src/db/contracts.ts +430 -1
  6. package/microservices/microservice-contracts/src/db/migrations.ts +83 -0
  7. package/microservices/microservice-contracts/src/mcp/index.ts +312 -3
  8. package/microservices/microservice-domains/src/cli/index.ts +253 -0
  9. package/microservices/microservice-domains/src/db/domains.ts +613 -0
  10. package/microservices/microservice-domains/src/index.ts +21 -0
  11. package/microservices/microservice-domains/src/mcp/index.ts +168 -0
  12. package/microservices/microservice-hiring/src/cli/index.ts +318 -8
  13. package/microservices/microservice-hiring/src/db/hiring.ts +503 -0
  14. package/microservices/microservice-hiring/src/db/migrations.ts +21 -0
  15. package/microservices/microservice-hiring/src/index.ts +29 -0
  16. package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
  17. package/microservices/microservice-hiring/src/mcp/index.ts +245 -0
  18. package/microservices/microservice-payments/src/cli/index.ts +255 -3
  19. package/microservices/microservice-payments/src/db/migrations.ts +18 -0
  20. package/microservices/microservice-payments/src/db/payments.ts +552 -0
  21. package/microservices/microservice-payments/src/mcp/index.ts +223 -0
  22. package/microservices/microservice-payroll/src/cli/index.ts +269 -0
  23. package/microservices/microservice-payroll/src/db/migrations.ts +26 -0
  24. package/microservices/microservice-payroll/src/db/payroll.ts +636 -0
  25. package/microservices/microservice-payroll/src/mcp/index.ts +246 -0
  26. package/microservices/microservice-shipping/src/cli/index.ts +211 -3
  27. package/microservices/microservice-shipping/src/db/migrations.ts +8 -0
  28. package/microservices/microservice-shipping/src/db/shipping.ts +453 -3
  29. package/microservices/microservice-shipping/src/mcp/index.ts +149 -1
  30. package/microservices/microservice-social/src/cli/index.ts +244 -2
  31. package/microservices/microservice-social/src/db/migrations.ts +33 -0
  32. package/microservices/microservice-social/src/db/social.ts +378 -4
  33. package/microservices/microservice-social/src/mcp/index.ts +221 -1
  34. package/microservices/microservice-subscriptions/src/cli/index.ts +315 -0
  35. package/microservices/microservice-subscriptions/src/db/migrations.ts +68 -0
  36. package/microservices/microservice-subscriptions/src/db/subscriptions.ts +567 -3
  37. package/microservices/microservice-subscriptions/src/mcp/index.ts +267 -1
  38. package/package.json +1 -1
@@ -22,6 +22,15 @@ import {
22
22
  createAlert,
23
23
  listAlerts,
24
24
  deleteAlert,
25
+ whoisLookup,
26
+ checkDnsPropagation,
27
+ checkSsl,
28
+ exportZoneFile,
29
+ importZoneFile,
30
+ discoverSubdomains,
31
+ validateDns,
32
+ exportPortfolio,
33
+ checkAllDomains,
25
34
  } from "../db/domains.js";
26
35
 
27
36
  const server = new McpServer({
@@ -355,6 +364,165 @@ server.registerTool(
355
364
  }
356
365
  );
357
366
 
367
+ // --- WHOIS Lookup ---
368
+
369
+ server.registerTool(
370
+ "whois_lookup",
371
+ {
372
+ title: "WHOIS Lookup",
373
+ description: "Run a WHOIS lookup for a domain. Parses registrar, expiry, nameservers from output. Updates DB record if found.",
374
+ inputSchema: { domain: z.string().describe("Domain name (e.g. example.com)") },
375
+ },
376
+ async ({ domain }) => {
377
+ try {
378
+ const result = whoisLookup(domain);
379
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
380
+ } catch (error: unknown) {
381
+ return {
382
+ content: [{ type: "text", text: `WHOIS lookup failed: ${error instanceof Error ? error.message : String(error)}` }],
383
+ isError: true,
384
+ };
385
+ }
386
+ }
387
+ );
388
+
389
+ // --- DNS Propagation Check ---
390
+
391
+ server.registerTool(
392
+ "check_dns_propagation",
393
+ {
394
+ title: "Check DNS Propagation",
395
+ description: "Check DNS propagation by querying multiple DNS servers (Google, Cloudflare, Quad9, OpenDNS).",
396
+ inputSchema: {
397
+ domain: z.string().describe("Domain name to check"),
398
+ record_type: z.string().default("A").describe("DNS record type (A, AAAA, CNAME, MX, TXT, NS)"),
399
+ },
400
+ },
401
+ async ({ domain, record_type }) => {
402
+ const result = checkDnsPropagation(domain, record_type);
403
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
404
+ }
405
+ );
406
+
407
+ // --- SSL Check ---
408
+
409
+ server.registerTool(
410
+ "check_ssl",
411
+ {
412
+ title: "Check SSL Certificate",
413
+ description: "Check SSL certificate for a domain. Extracts issuer and expiry. Updates DB record if found.",
414
+ inputSchema: { domain: z.string().describe("Domain name (e.g. example.com)") },
415
+ },
416
+ async ({ domain }) => {
417
+ const result = checkSsl(domain);
418
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
419
+ }
420
+ );
421
+
422
+ // --- Zone File Export ---
423
+
424
+ server.registerTool(
425
+ "export_zone_file",
426
+ {
427
+ title: "Export Zone File",
428
+ description: "Export DNS records for a domain as a BIND-format zone file.",
429
+ inputSchema: { domain_id: z.string().describe("Domain ID") },
430
+ },
431
+ async ({ domain_id }) => {
432
+ const zone = exportZoneFile(domain_id);
433
+ if (!zone) {
434
+ return { content: [{ type: "text", text: `Domain '${domain_id}' not found.` }], isError: true };
435
+ }
436
+ return { content: [{ type: "text", text: zone }] };
437
+ }
438
+ );
439
+
440
+ // --- Zone File Import ---
441
+
442
+ server.registerTool(
443
+ "import_zone_file",
444
+ {
445
+ title: "Import Zone File",
446
+ description: "Import DNS records from BIND zone file content into a domain.",
447
+ inputSchema: {
448
+ domain_id: z.string().describe("Domain ID"),
449
+ content: z.string().describe("Zone file content"),
450
+ },
451
+ },
452
+ async ({ domain_id, content }) => {
453
+ const result = importZoneFile(domain_id, content);
454
+ if (!result) {
455
+ return { content: [{ type: "text", text: `Domain '${domain_id}' not found.` }], isError: true };
456
+ }
457
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
458
+ }
459
+ );
460
+
461
+ // --- Subdomain Discovery ---
462
+
463
+ server.registerTool(
464
+ "discover_subdomains",
465
+ {
466
+ title: "Discover Subdomains",
467
+ description: "Discover subdomains via certificate transparency logs (crt.sh).",
468
+ inputSchema: { domain: z.string().describe("Domain name") },
469
+ },
470
+ async ({ domain }) => {
471
+ const result = await discoverSubdomains(domain);
472
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
473
+ }
474
+ );
475
+
476
+ // --- DNS Validation ---
477
+
478
+ server.registerTool(
479
+ "validate_dns",
480
+ {
481
+ title: "Validate DNS",
482
+ description: "Validate DNS records for common issues (CNAME conflicts, missing MX, orphan records).",
483
+ inputSchema: { domain_id: z.string().describe("Domain ID") },
484
+ },
485
+ async ({ domain_id }) => {
486
+ const result = validateDns(domain_id);
487
+ if (!result) {
488
+ return { content: [{ type: "text", text: `Domain '${domain_id}' not found.` }], isError: true };
489
+ }
490
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
491
+ }
492
+ );
493
+
494
+ // --- Portfolio Export ---
495
+
496
+ server.registerTool(
497
+ "export_portfolio",
498
+ {
499
+ title: "Export Portfolio",
500
+ description: "Export all domains as CSV or JSON with expiry, SSL, registrar, and auto-renew info.",
501
+ inputSchema: {
502
+ format: z.enum(["csv", "json"]).default("json").describe("Export format"),
503
+ },
504
+ },
505
+ async ({ format }) => {
506
+ const output = exportPortfolio(format);
507
+ return { content: [{ type: "text", text: output }] };
508
+ }
509
+ );
510
+
511
+ // --- Bulk Domain Check ---
512
+
513
+ server.registerTool(
514
+ "check_all_domains",
515
+ {
516
+ title: "Check All Domains",
517
+ description: "Run WHOIS + SSL + DNS validation on all domains. Returns a summary of issues found.",
518
+ inputSchema: {},
519
+ },
520
+ async () => {
521
+ const results = checkAllDomains();
522
+ return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] };
523
+ }
524
+ );
525
+
358
526
  // --- Start ---
359
527
  async function main() {
360
528
  const transport = new StdioServerTransport();
@@ -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
  createJob,
6
7
  getJob,
@@ -17,7 +18,17 @@ import {
17
18
  createInterview,
18
19
  listInterviews,
19
20
  addInterviewFeedback,
21
+ bulkImportApplicants,
22
+ generateOffer,
23
+ getHiringForecast,
24
+ submitStructuredFeedback,
25
+ bulkReject,
26
+ getReferralStats,
27
+ saveJobAsTemplate,
28
+ createJobFromTemplate,
29
+ listJobTemplates,
20
30
  } from "../db/hiring.js";
31
+ import { scoreApplicant, rankApplicants } from "../lib/scoring.js";
21
32
 
22
33
  const program = new Command();
23
34
 
@@ -291,6 +302,142 @@ applicantCmd
291
302
  }
292
303
  });
293
304
 
305
+ // --- Bulk Import ---
306
+
307
+ applicantCmd
308
+ .command("bulk-import")
309
+ .description("Bulk import applicants from a CSV file")
310
+ .requiredOption("--file <path>", "Path to CSV file (name,email,phone,job_id,source,resume_url)")
311
+ .option("--json", "Output as JSON", false)
312
+ .action((opts) => {
313
+ const csvData = readFileSync(opts.file, "utf-8");
314
+ const result = bulkImportApplicants(csvData);
315
+
316
+ if (opts.json) {
317
+ console.log(JSON.stringify(result, null, 2));
318
+ } else {
319
+ console.log(`Imported: ${result.imported}`);
320
+ console.log(`Skipped: ${result.skipped}`);
321
+ if (result.errors.length > 0) {
322
+ console.log("Errors:");
323
+ for (const e of result.errors) {
324
+ console.log(` - ${e}`);
325
+ }
326
+ }
327
+ }
328
+ });
329
+
330
+ // --- AI Scoring ---
331
+
332
+ applicantCmd
333
+ .command("score")
334
+ .description("AI-score an applicant against job requirements")
335
+ .argument("<id>", "Applicant ID")
336
+ .option("--json", "Output as JSON", false)
337
+ .action(async (id, opts) => {
338
+ try {
339
+ const score = await scoreApplicant(id);
340
+
341
+ if (opts.json) {
342
+ console.log(JSON.stringify(score, null, 2));
343
+ } else {
344
+ console.log(`Match: ${score.match_pct}%`);
345
+ console.log(`Recommendation: ${score.recommendation}`);
346
+ if (score.strengths.length) console.log(`Strengths: ${score.strengths.join(", ")}`);
347
+ if (score.gaps.length) console.log(`Gaps: ${score.gaps.join(", ")}`);
348
+ }
349
+ } catch (err) {
350
+ console.error(err instanceof Error ? err.message : String(err));
351
+ process.exit(1);
352
+ }
353
+ });
354
+
355
+ // --- AI Bulk Ranking ---
356
+
357
+ applicantCmd
358
+ .command("rank")
359
+ .description("AI-rank all applicants for a job by fit score")
360
+ .requiredOption("--job <id>", "Job ID")
361
+ .option("--json", "Output as JSON", false)
362
+ .action(async (opts) => {
363
+ try {
364
+ const ranked = await rankApplicants(opts.job);
365
+
366
+ if (opts.json) {
367
+ console.log(JSON.stringify(ranked, null, 2));
368
+ } else {
369
+ if (ranked.length === 0) {
370
+ console.log("No applicants to rank.");
371
+ return;
372
+ }
373
+ console.log("Ranking:");
374
+ for (let i = 0; i < ranked.length; i++) {
375
+ const { applicant, score } = ranked[i];
376
+ console.log(` ${i + 1}. ${applicant.name} — ${score.match_pct}% (${score.recommendation})`);
377
+ }
378
+ }
379
+ } catch (err) {
380
+ console.error(err instanceof Error ? err.message : String(err));
381
+ process.exit(1);
382
+ }
383
+ });
384
+
385
+ // --- Offer Letter ---
386
+
387
+ applicantCmd
388
+ .command("offer")
389
+ .description("Generate a Markdown offer letter")
390
+ .argument("<id>", "Applicant ID")
391
+ .requiredOption("--salary <amount>", "Annual salary")
392
+ .requiredOption("--start-date <date>", "Start date (YYYY-MM-DD)")
393
+ .option("--title <title>", "Position title override")
394
+ .option("--department <dept>", "Department override")
395
+ .option("--benefits <text>", "Benefits description")
396
+ .option("--equity <text>", "Equity details")
397
+ .option("--signing-bonus <amount>", "Signing bonus")
398
+ .option("--json", "Output as JSON", false)
399
+ .action((id, opts) => {
400
+ try {
401
+ const letter = generateOffer(id, {
402
+ salary: parseInt(opts.salary),
403
+ start_date: opts.startDate,
404
+ position_title: opts.title,
405
+ department: opts.department,
406
+ benefits: opts.benefits,
407
+ equity: opts.equity,
408
+ signing_bonus: opts.signingBonus ? parseInt(opts.signingBonus) : undefined,
409
+ });
410
+
411
+ if (opts.json) {
412
+ console.log(JSON.stringify({ offer_letter: letter }, null, 2));
413
+ } else {
414
+ console.log(letter);
415
+ }
416
+ } catch (err) {
417
+ console.error(err instanceof Error ? err.message : String(err));
418
+ process.exit(1);
419
+ }
420
+ });
421
+
422
+ // --- Bulk Rejection ---
423
+
424
+ applicantCmd
425
+ .command("reject-batch")
426
+ .description("Bulk reject applicants for a job by status")
427
+ .requiredOption("--job <id>", "Job ID")
428
+ .requiredOption("--status <status>", "Status to reject (applied/screening/etc.)")
429
+ .option("--reason <reason>", "Rejection reason")
430
+ .option("--json", "Output as JSON", false)
431
+ .action((opts) => {
432
+ const result = bulkReject(opts.job, opts.status, opts.reason);
433
+
434
+ if (opts.json) {
435
+ console.log(JSON.stringify(result, null, 2));
436
+ } else {
437
+ console.log(`Rejected ${result.rejected} applicant(s)`);
438
+ }
439
+ });
440
+
294
441
  // --- Interviews ---
295
442
 
296
443
  const interviewCmd = program
@@ -356,17 +503,46 @@ interviewCmd
356
503
 
357
504
  interviewCmd
358
505
  .command("feedback")
359
- .description("Add feedback to an interview")
506
+ .description("Add feedback to an interview (supports structured scoring dimensions)")
360
507
  .argument("<id>", "Interview ID")
361
- .requiredOption("--feedback <text>", "Feedback text")
362
- .option("--rating <n>", "Rating (1-5)")
508
+ .option("--feedback <text>", "Feedback text")
509
+ .option("--rating <n>", "Overall rating (1-5)")
510
+ .option("--technical <n>", "Technical score (1-5)")
511
+ .option("--communication <n>", "Communication score (1-5)")
512
+ .option("--culture-fit <n>", "Culture fit score (1-5)")
513
+ .option("--problem-solving <n>", "Problem solving score (1-5)")
514
+ .option("--leadership <n>", "Leadership score (1-5)")
363
515
  .option("--json", "Output as JSON", false)
364
516
  .action((id, opts) => {
365
- const interview = addInterviewFeedback(
366
- id,
367
- opts.feedback,
368
- opts.rating ? parseInt(opts.rating) : undefined
369
- );
517
+ const hasStructured = opts.technical || opts.communication || opts.cultureFit ||
518
+ opts.problemSolving || opts.leadership;
519
+
520
+ let interview;
521
+ if (hasStructured) {
522
+ interview = submitStructuredFeedback(
523
+ id,
524
+ {
525
+ technical: opts.technical ? parseInt(opts.technical) : undefined,
526
+ communication: opts.communication ? parseInt(opts.communication) : undefined,
527
+ culture_fit: opts.cultureFit ? parseInt(opts.cultureFit) : undefined,
528
+ problem_solving: opts.problemSolving ? parseInt(opts.problemSolving) : undefined,
529
+ leadership: opts.leadership ? parseInt(opts.leadership) : undefined,
530
+ overall: opts.rating ? parseInt(opts.rating) : undefined,
531
+ },
532
+ opts.feedback
533
+ );
534
+ } else {
535
+ if (!opts.feedback) {
536
+ console.error("Either --feedback or structured scores (--technical, --communication, etc.) are required.");
537
+ process.exit(1);
538
+ }
539
+ interview = addInterviewFeedback(
540
+ id,
541
+ opts.feedback,
542
+ opts.rating ? parseInt(opts.rating) : undefined
543
+ );
544
+ }
545
+
370
546
  if (!interview) {
371
547
  console.error(`Interview '${id}' not found.`);
372
548
  process.exit(1);
@@ -428,4 +604,138 @@ program
428
604
  }
429
605
  });
430
606
 
607
+ // --- Referral Stats ---
608
+
609
+ const statsCmd = program
610
+ .command("stats-referrals")
611
+ .description("Show referral/source conversion rates")
612
+ .option("--json", "Output as JSON", false)
613
+ .action((opts) => {
614
+ const stats = getReferralStats();
615
+
616
+ if (opts.json) {
617
+ console.log(JSON.stringify(stats, null, 2));
618
+ } else {
619
+ if (stats.length === 0) {
620
+ console.log("No applicant source data.");
621
+ return;
622
+ }
623
+ console.log("Referral Stats:");
624
+ for (const s of stats) {
625
+ console.log(` ${s.source}: ${s.total} total, ${s.hired} hired, ${s.conversion_rate}% conversion`);
626
+ }
627
+ }
628
+ });
629
+
630
+ // --- Forecast ---
631
+
632
+ program
633
+ .command("forecast")
634
+ .description("Estimate days-to-fill based on pipeline velocity")
635
+ .argument("<job-id>", "Job ID")
636
+ .option("--json", "Output as JSON", false)
637
+ .action((jobId, opts) => {
638
+ try {
639
+ const forecast = getHiringForecast(jobId);
640
+
641
+ if (opts.json) {
642
+ console.log(JSON.stringify(forecast, null, 2));
643
+ } else {
644
+ console.log(`Forecast for: ${forecast.job_title}`);
645
+ console.log(` Total applicants: ${forecast.total_applicants}`);
646
+ console.log(` Estimated days to fill: ${forecast.estimated_days_to_fill ?? "N/A"}`);
647
+
648
+ if (Object.keys(forecast.avg_days_per_stage).length) {
649
+ console.log(" Avg days per transition:");
650
+ for (const [stage, days] of Object.entries(forecast.avg_days_per_stage)) {
651
+ console.log(` ${stage}: ${days} days`);
652
+ }
653
+ }
654
+
655
+ if (Object.keys(forecast.conversion_rates).length) {
656
+ console.log(" Conversion rates:");
657
+ for (const [stage, rate] of Object.entries(forecast.conversion_rates)) {
658
+ console.log(` ${stage}: ${rate}%`);
659
+ }
660
+ }
661
+ }
662
+ } catch (err) {
663
+ console.error(err instanceof Error ? err.message : String(err));
664
+ process.exit(1);
665
+ }
666
+ });
667
+
668
+ // --- Job Templates ---
669
+
670
+ jobCmd
671
+ .command("save-template")
672
+ .description("Save a job as a reusable template")
673
+ .argument("<id>", "Job ID")
674
+ .requiredOption("--name <name>", "Template name")
675
+ .option("--json", "Output as JSON", false)
676
+ .action((id, opts) => {
677
+ try {
678
+ const template = saveJobAsTemplate(id, opts.name);
679
+
680
+ if (opts.json) {
681
+ console.log(JSON.stringify(template, null, 2));
682
+ } else {
683
+ console.log(`Saved template: ${template.name} (${template.id})`);
684
+ }
685
+ } catch (err) {
686
+ console.error(err instanceof Error ? err.message : String(err));
687
+ process.exit(1);
688
+ }
689
+ });
690
+
691
+ jobCmd
692
+ .command("from-template")
693
+ .description("Create a job from a template")
694
+ .requiredOption("--template <name>", "Template name")
695
+ .option("--title <title>", "Override title")
696
+ .option("--department <dept>", "Override department")
697
+ .option("--location <loc>", "Override location")
698
+ .option("--salary-range <range>", "Override salary range")
699
+ .option("--json", "Output as JSON", false)
700
+ .action((opts) => {
701
+ try {
702
+ const job = createJobFromTemplate(opts.template, {
703
+ title: opts.title,
704
+ department: opts.department,
705
+ location: opts.location,
706
+ salary_range: opts.salaryRange,
707
+ });
708
+
709
+ if (opts.json) {
710
+ console.log(JSON.stringify(job, null, 2));
711
+ } else {
712
+ console.log(`Created job from template: ${job.title} (${job.id})`);
713
+ }
714
+ } catch (err) {
715
+ console.error(err instanceof Error ? err.message : String(err));
716
+ process.exit(1);
717
+ }
718
+ });
719
+
720
+ jobCmd
721
+ .command("templates")
722
+ .description("List all job templates")
723
+ .option("--json", "Output as JSON", false)
724
+ .action((opts) => {
725
+ const templates = listJobTemplates();
726
+
727
+ if (opts.json) {
728
+ console.log(JSON.stringify(templates, null, 2));
729
+ } else {
730
+ if (templates.length === 0) {
731
+ console.log("No templates found.");
732
+ return;
733
+ }
734
+ for (const t of templates) {
735
+ console.log(` ${t.name} — ${t.title} (${t.id})`);
736
+ }
737
+ console.log(`\n${templates.length} template(s)`);
738
+ }
739
+ });
740
+
431
741
  program.parse(process.argv);