@hasna/microservices 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/bin/index.js +63 -0
  2. package/bin/mcp.js +63 -0
  3. package/dist/index.js +63 -0
  4. package/microservices/microservice-ads/package.json +27 -0
  5. package/microservices/microservice-ads/src/cli/index.ts +605 -0
  6. package/microservices/microservice-ads/src/db/campaigns.ts +797 -0
  7. package/microservices/microservice-ads/src/db/database.ts +93 -0
  8. package/microservices/microservice-ads/src/db/migrations.ts +60 -0
  9. package/microservices/microservice-ads/src/index.ts +39 -0
  10. package/microservices/microservice-ads/src/mcp/index.ts +480 -0
  11. package/microservices/microservice-contracts/package.json +27 -0
  12. package/microservices/microservice-contracts/src/cli/index.ts +770 -0
  13. package/microservices/microservice-contracts/src/db/contracts.ts +925 -0
  14. package/microservices/microservice-contracts/src/db/database.ts +93 -0
  15. package/microservices/microservice-contracts/src/db/migrations.ts +141 -0
  16. package/microservices/microservice-contracts/src/index.ts +43 -0
  17. package/microservices/microservice-contracts/src/mcp/index.ts +617 -0
  18. package/microservices/microservice-domains/package.json +27 -0
  19. package/microservices/microservice-domains/src/cli/index.ts +691 -0
  20. package/microservices/microservice-domains/src/db/database.ts +93 -0
  21. package/microservices/microservice-domains/src/db/domains.ts +1164 -0
  22. package/microservices/microservice-domains/src/db/migrations.ts +60 -0
  23. package/microservices/microservice-domains/src/index.ts +65 -0
  24. package/microservices/microservice-domains/src/mcp/index.ts +536 -0
  25. package/microservices/microservice-hiring/package.json +27 -0
  26. package/microservices/microservice-hiring/src/cli/index.ts +741 -0
  27. package/microservices/microservice-hiring/src/db/database.ts +93 -0
  28. package/microservices/microservice-hiring/src/db/hiring.ts +1085 -0
  29. package/microservices/microservice-hiring/src/db/migrations.ts +89 -0
  30. package/microservices/microservice-hiring/src/index.ts +80 -0
  31. package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
  32. package/microservices/microservice-hiring/src/mcp/index.ts +709 -0
  33. package/microservices/microservice-payments/package.json +27 -0
  34. package/microservices/microservice-payments/src/cli/index.ts +609 -0
  35. package/microservices/microservice-payments/src/db/database.ts +93 -0
  36. package/microservices/microservice-payments/src/db/migrations.ts +81 -0
  37. package/microservices/microservice-payments/src/db/payments.ts +1204 -0
  38. package/microservices/microservice-payments/src/index.ts +51 -0
  39. package/microservices/microservice-payments/src/mcp/index.ts +683 -0
  40. package/microservices/microservice-payroll/package.json +27 -0
  41. package/microservices/microservice-payroll/src/cli/index.ts +643 -0
  42. package/microservices/microservice-payroll/src/db/database.ts +93 -0
  43. package/microservices/microservice-payroll/src/db/migrations.ts +95 -0
  44. package/microservices/microservice-payroll/src/db/payroll.ts +1377 -0
  45. package/microservices/microservice-payroll/src/index.ts +48 -0
  46. package/microservices/microservice-payroll/src/mcp/index.ts +666 -0
  47. package/microservices/microservice-shipping/package.json +27 -0
  48. package/microservices/microservice-shipping/src/cli/index.ts +606 -0
  49. package/microservices/microservice-shipping/src/db/database.ts +93 -0
  50. package/microservices/microservice-shipping/src/db/migrations.ts +69 -0
  51. package/microservices/microservice-shipping/src/db/shipping.ts +1093 -0
  52. package/microservices/microservice-shipping/src/index.ts +53 -0
  53. package/microservices/microservice-shipping/src/mcp/index.ts +533 -0
  54. package/microservices/microservice-social/package.json +27 -0
  55. package/microservices/microservice-social/src/cli/index.ts +689 -0
  56. package/microservices/microservice-social/src/db/database.ts +93 -0
  57. package/microservices/microservice-social/src/db/migrations.ts +88 -0
  58. package/microservices/microservice-social/src/db/social.ts +1046 -0
  59. package/microservices/microservice-social/src/index.ts +46 -0
  60. package/microservices/microservice-social/src/mcp/index.ts +655 -0
  61. package/microservices/microservice-subscriptions/package.json +27 -0
  62. package/microservices/microservice-subscriptions/src/cli/index.ts +715 -0
  63. package/microservices/microservice-subscriptions/src/db/database.ts +93 -0
  64. package/microservices/microservice-subscriptions/src/db/migrations.ts +125 -0
  65. package/microservices/microservice-subscriptions/src/db/subscriptions.ts +1256 -0
  66. package/microservices/microservice-subscriptions/src/index.ts +41 -0
  67. package/microservices/microservice-subscriptions/src/mcp/index.ts +631 -0
  68. package/package.json +1 -1
@@ -0,0 +1,691 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from "commander";
4
+ import {
5
+ createDomain,
6
+ getDomain,
7
+ listDomains,
8
+ updateDomain,
9
+ deleteDomain,
10
+ searchDomains,
11
+ listExpiring,
12
+ listSslExpiring,
13
+ getDomainStats,
14
+ getByRegistrar,
15
+ createDnsRecord,
16
+ listDnsRecords,
17
+ updateDnsRecord,
18
+ deleteDnsRecord,
19
+ createAlert,
20
+ listAlerts,
21
+ deleteAlert,
22
+ whoisLookup,
23
+ checkDnsPropagation,
24
+ checkSsl,
25
+ exportZoneFile,
26
+ importZoneFile,
27
+ discoverSubdomains,
28
+ validateDns,
29
+ exportPortfolio,
30
+ checkAllDomains,
31
+ getDomainByName,
32
+ } from "../db/domains.js";
33
+ import { readFileSync, writeFileSync } from "node:fs";
34
+
35
+ const program = new Command();
36
+
37
+ program
38
+ .name("microservice-domains")
39
+ .description("Domain portfolio and DNS management microservice")
40
+ .version("0.0.1");
41
+
42
+ // --- Domains ---
43
+
44
+ program
45
+ .command("add")
46
+ .description("Add a new domain")
47
+ .requiredOption("--name <name>", "Domain name (e.g. example.com)")
48
+ .option("--registrar <registrar>", "Domain registrar")
49
+ .option("--status <status>", "Status (active/expired/transferring/redemption)", "active")
50
+ .option("--registered-at <date>", "Registration date (ISO)")
51
+ .option("--expires-at <date>", "Expiration date (ISO)")
52
+ .option("--no-auto-renew", "Disable auto-renew")
53
+ .option("--nameservers <ns>", "Comma-separated nameservers")
54
+ .option("--ssl-expires-at <date>", "SSL expiration date (ISO)")
55
+ .option("--ssl-issuer <issuer>", "SSL certificate issuer")
56
+ .option("--notes <notes>", "Notes")
57
+ .option("--json", "Output as JSON", false)
58
+ .action((opts) => {
59
+ const domain = createDomain({
60
+ name: opts.name,
61
+ registrar: opts.registrar,
62
+ status: opts.status,
63
+ registered_at: opts.registeredAt,
64
+ expires_at: opts.expiresAt,
65
+ auto_renew: opts.autoRenew,
66
+ nameservers: opts.nameservers
67
+ ? opts.nameservers.split(",").map((s: string) => s.trim())
68
+ : undefined,
69
+ ssl_expires_at: opts.sslExpiresAt,
70
+ ssl_issuer: opts.sslIssuer,
71
+ notes: opts.notes,
72
+ });
73
+
74
+ if (opts.json) {
75
+ console.log(JSON.stringify(domain, null, 2));
76
+ } else {
77
+ console.log(`Created domain: ${domain.name} (${domain.id})`);
78
+ }
79
+ });
80
+
81
+ program
82
+ .command("get")
83
+ .description("Get a domain by ID")
84
+ .argument("<id>", "Domain ID")
85
+ .option("--json", "Output as JSON", false)
86
+ .action((id, opts) => {
87
+ const domain = getDomain(id);
88
+ if (!domain) {
89
+ console.error(`Domain '${id}' not found.`);
90
+ process.exit(1);
91
+ }
92
+
93
+ if (opts.json) {
94
+ console.log(JSON.stringify(domain, null, 2));
95
+ } else {
96
+ console.log(`${domain.name} [${domain.status}]`);
97
+ if (domain.registrar) console.log(` Registrar: ${domain.registrar}`);
98
+ if (domain.expires_at) console.log(` Expires: ${domain.expires_at}`);
99
+ if (domain.ssl_expires_at) console.log(` SSL Expires: ${domain.ssl_expires_at}`);
100
+ if (domain.ssl_issuer) console.log(` SSL Issuer: ${domain.ssl_issuer}`);
101
+ console.log(` Auto-renew: ${domain.auto_renew ? "yes" : "no"}`);
102
+ if (domain.nameservers.length) console.log(` Nameservers: ${domain.nameservers.join(", ")}`);
103
+ if (domain.notes) console.log(` Notes: ${domain.notes}`);
104
+ }
105
+ });
106
+
107
+ program
108
+ .command("list")
109
+ .description("List domains")
110
+ .option("--search <query>", "Search by name, registrar, or notes")
111
+ .option("--status <status>", "Filter by status")
112
+ .option("--registrar <registrar>", "Filter by registrar")
113
+ .option("--limit <n>", "Limit results")
114
+ .option("--json", "Output as JSON", false)
115
+ .action((opts) => {
116
+ const domains = listDomains({
117
+ search: opts.search,
118
+ status: opts.status,
119
+ registrar: opts.registrar,
120
+ limit: opts.limit ? parseInt(opts.limit) : undefined,
121
+ });
122
+
123
+ if (opts.json) {
124
+ console.log(JSON.stringify(domains, null, 2));
125
+ } else {
126
+ if (domains.length === 0) {
127
+ console.log("No domains found.");
128
+ return;
129
+ }
130
+ for (const d of domains) {
131
+ const expires = d.expires_at ? ` (expires ${d.expires_at})` : "";
132
+ console.log(` ${d.name} [${d.status}]${expires}`);
133
+ }
134
+ console.log(`\n${domains.length} domain(s)`);
135
+ }
136
+ });
137
+
138
+ program
139
+ .command("update")
140
+ .description("Update a domain")
141
+ .argument("<id>", "Domain ID")
142
+ .option("--name <name>", "Domain name")
143
+ .option("--registrar <registrar>", "Registrar")
144
+ .option("--status <status>", "Status")
145
+ .option("--registered-at <date>", "Registration date")
146
+ .option("--expires-at <date>", "Expiration date")
147
+ .option("--auto-renew <bool>", "Auto-renew (true/false)")
148
+ .option("--nameservers <ns>", "Comma-separated nameservers")
149
+ .option("--ssl-expires-at <date>", "SSL expiration date")
150
+ .option("--ssl-issuer <issuer>", "SSL issuer")
151
+ .option("--notes <notes>", "Notes")
152
+ .option("--json", "Output as JSON", false)
153
+ .action((id, opts) => {
154
+ const input: Record<string, unknown> = {};
155
+ if (opts.name !== undefined) input.name = opts.name;
156
+ if (opts.registrar !== undefined) input.registrar = opts.registrar;
157
+ if (opts.status !== undefined) input.status = opts.status;
158
+ if (opts.registeredAt !== undefined) input.registered_at = opts.registeredAt;
159
+ if (opts.expiresAt !== undefined) input.expires_at = opts.expiresAt;
160
+ if (opts.autoRenew !== undefined) input.auto_renew = opts.autoRenew === "true";
161
+ if (opts.nameservers !== undefined)
162
+ input.nameservers = opts.nameservers.split(",").map((s: string) => s.trim());
163
+ if (opts.sslExpiresAt !== undefined) input.ssl_expires_at = opts.sslExpiresAt;
164
+ if (opts.sslIssuer !== undefined) input.ssl_issuer = opts.sslIssuer;
165
+ if (opts.notes !== undefined) input.notes = opts.notes;
166
+
167
+ const domain = updateDomain(id, input);
168
+ if (!domain) {
169
+ console.error(`Domain '${id}' not found.`);
170
+ process.exit(1);
171
+ }
172
+
173
+ if (opts.json) {
174
+ console.log(JSON.stringify(domain, null, 2));
175
+ } else {
176
+ console.log(`Updated: ${domain.name}`);
177
+ }
178
+ });
179
+
180
+ program
181
+ .command("delete")
182
+ .description("Delete a domain")
183
+ .argument("<id>", "Domain ID")
184
+ .action((id) => {
185
+ const deleted = deleteDomain(id);
186
+ if (deleted) {
187
+ console.log(`Deleted domain ${id}`);
188
+ } else {
189
+ console.error(`Domain '${id}' not found.`);
190
+ process.exit(1);
191
+ }
192
+ });
193
+
194
+ program
195
+ .command("search")
196
+ .description("Search domains")
197
+ .argument("<query>", "Search term")
198
+ .option("--json", "Output as JSON", false)
199
+ .action((query, opts) => {
200
+ const results = searchDomains(query);
201
+
202
+ if (opts.json) {
203
+ console.log(JSON.stringify(results, null, 2));
204
+ } else {
205
+ if (results.length === 0) {
206
+ console.log(`No domains matching "${query}".`);
207
+ return;
208
+ }
209
+ for (const d of results) {
210
+ console.log(` ${d.name} [${d.status}]`);
211
+ }
212
+ }
213
+ });
214
+
215
+ program
216
+ .command("expiring")
217
+ .description("List domains expiring within N days")
218
+ .option("--days <n>", "Number of days ahead", "30")
219
+ .option("--json", "Output as JSON", false)
220
+ .action((opts) => {
221
+ const days = parseInt(opts.days);
222
+ const domains = listExpiring(days);
223
+
224
+ if (opts.json) {
225
+ console.log(JSON.stringify(domains, null, 2));
226
+ } else {
227
+ if (domains.length === 0) {
228
+ console.log(`No domains expiring within ${days} days.`);
229
+ return;
230
+ }
231
+ console.log(`Domains expiring within ${days} days:`);
232
+ for (const d of domains) {
233
+ console.log(` ${d.name} — expires ${d.expires_at}`);
234
+ }
235
+ }
236
+ });
237
+
238
+ program
239
+ .command("ssl")
240
+ .description("List domains with SSL expiring within N days")
241
+ .option("--days <n>", "Number of days ahead", "30")
242
+ .option("--json", "Output as JSON", false)
243
+ .action((opts) => {
244
+ const days = parseInt(opts.days);
245
+ const domains = listSslExpiring(days);
246
+
247
+ if (opts.json) {
248
+ console.log(JSON.stringify(domains, null, 2));
249
+ } else {
250
+ if (domains.length === 0) {
251
+ console.log(`No SSL certificates expiring within ${days} days.`);
252
+ return;
253
+ }
254
+ console.log(`SSL certificates expiring within ${days} days:`);
255
+ for (const d of domains) {
256
+ console.log(` ${d.name} — SSL expires ${d.ssl_expires_at} (${d.ssl_issuer || "unknown issuer"})`);
257
+ }
258
+ }
259
+ });
260
+
261
+ program
262
+ .command("stats")
263
+ .description("Show domain portfolio statistics")
264
+ .option("--json", "Output as JSON", false)
265
+ .action((opts) => {
266
+ const stats = getDomainStats();
267
+
268
+ if (opts.json) {
269
+ console.log(JSON.stringify(stats, null, 2));
270
+ } else {
271
+ console.log("Domain Portfolio Stats:");
272
+ console.log(` Total: ${stats.total}`);
273
+ console.log(` Active: ${stats.active}`);
274
+ console.log(` Expired: ${stats.expired}`);
275
+ console.log(` Transferring: ${stats.transferring}`);
276
+ console.log(` Redemption: ${stats.redemption}`);
277
+ console.log(` Auto-renew enabled: ${stats.auto_renew_enabled}`);
278
+ console.log(` Expiring (30 days): ${stats.expiring_30_days}`);
279
+ console.log(` SSL expiring (30 days): ${stats.ssl_expiring_30_days}`);
280
+ }
281
+ });
282
+
283
+ // --- WHOIS Lookup ---
284
+
285
+ program
286
+ .command("whois")
287
+ .description("Run WHOIS lookup for a domain and update DB record")
288
+ .argument("<name>", "Domain name (e.g. example.com)")
289
+ .option("--json", "Output as JSON", false)
290
+ .action((name, opts) => {
291
+ try {
292
+ const result = whoisLookup(name);
293
+ if (opts.json) {
294
+ console.log(JSON.stringify(result, null, 2));
295
+ } else {
296
+ console.log(`WHOIS for ${result.domain}:`);
297
+ console.log(` Registrar: ${result.registrar || "unknown"}`);
298
+ console.log(` Expires: ${result.expires_at || "unknown"}`);
299
+ if (result.nameservers.length > 0) {
300
+ console.log(` Nameservers: ${result.nameservers.join(", ")}`);
301
+ }
302
+ }
303
+ } catch (error: unknown) {
304
+ console.error(`WHOIS lookup failed: ${error instanceof Error ? error.message : String(error)}`);
305
+ process.exit(1);
306
+ }
307
+ });
308
+
309
+ // --- SSL Check ---
310
+
311
+ program
312
+ .command("ssl-check")
313
+ .description("Check SSL certificate for a domain and update DB record")
314
+ .argument("<name>", "Domain name (e.g. example.com)")
315
+ .option("--json", "Output as JSON", false)
316
+ .action((name, opts) => {
317
+ const result = checkSsl(name);
318
+ if (opts.json) {
319
+ console.log(JSON.stringify(result, null, 2));
320
+ } else {
321
+ if (result.error) {
322
+ console.error(`SSL check failed: ${result.error}`);
323
+ process.exit(1);
324
+ }
325
+ console.log(`SSL Certificate for ${result.domain}:`);
326
+ console.log(` Issuer: ${result.issuer || "unknown"}`);
327
+ console.log(` Expires: ${result.expires_at || "unknown"}`);
328
+ if (result.subject) console.log(` Subject: ${result.subject}`);
329
+ }
330
+ });
331
+
332
+ // --- Portfolio Export ---
333
+
334
+ program
335
+ .command("export")
336
+ .description("Export all domains as CSV or JSON")
337
+ .option("--format <format>", "Export format (csv or json)", "json")
338
+ .option("--output <file>", "Write to file instead of stdout")
339
+ .action((opts) => {
340
+ const format = opts.format === "csv" ? "csv" : "json";
341
+ const output = exportPortfolio(format as "csv" | "json");
342
+ if (opts.output) {
343
+ writeFileSync(opts.output, output, "utf-8");
344
+ console.log(`Exported to ${opts.output}`);
345
+ } else {
346
+ console.log(output);
347
+ }
348
+ });
349
+
350
+ // --- Bulk Domain Check ---
351
+
352
+ program
353
+ .command("check-all")
354
+ .description("Run WHOIS + SSL + DNS validation on all domains")
355
+ .option("--json", "Output as JSON", false)
356
+ .action((opts) => {
357
+ const results = checkAllDomains();
358
+ if (opts.json) {
359
+ console.log(JSON.stringify(results, null, 2));
360
+ } else {
361
+ if (results.length === 0) {
362
+ console.log("No domains to check.");
363
+ return;
364
+ }
365
+ for (const r of results) {
366
+ console.log(`\n${r.domain}:`);
367
+ if (r.whois) {
368
+ console.log(` WHOIS: registrar=${r.whois.registrar || "?"}, expires=${r.whois.expires_at || "?"}`);
369
+ if (r.whois.error) console.log(` Error: ${r.whois.error}`);
370
+ }
371
+ if (r.ssl) {
372
+ console.log(` SSL: issuer=${r.ssl.issuer || "?"}, expires=${r.ssl.expires_at || "?"}`);
373
+ if (r.ssl.error) console.log(` Error: ${r.ssl.error}`);
374
+ }
375
+ if (r.dns_validation) {
376
+ console.log(` DNS: valid=${r.dns_validation.valid}, issues=${r.dns_validation.issue_count}`);
377
+ for (const e of r.dns_validation.errors) {
378
+ console.log(` ${e}`);
379
+ }
380
+ }
381
+ }
382
+ console.log(`\nChecked ${results.length} domain(s)`);
383
+ }
384
+ });
385
+
386
+ // --- DNS Records ---
387
+
388
+ const dnsCmd = program
389
+ .command("dns")
390
+ .description("DNS record management");
391
+
392
+ dnsCmd
393
+ .command("list")
394
+ .description("List DNS records for a domain")
395
+ .argument("<domain-id>", "Domain ID")
396
+ .option("--type <type>", "Filter by record type (A/AAAA/CNAME/MX/TXT/NS/SRV)")
397
+ .option("--json", "Output as JSON", false)
398
+ .action((domainId, opts) => {
399
+ const records = listDnsRecords(domainId, opts.type);
400
+
401
+ if (opts.json) {
402
+ console.log(JSON.stringify(records, null, 2));
403
+ } else {
404
+ if (records.length === 0) {
405
+ console.log("No DNS records found.");
406
+ return;
407
+ }
408
+ for (const r of records) {
409
+ const priority = r.priority !== null ? ` (priority: ${r.priority})` : "";
410
+ console.log(` ${r.type}\t${r.name}\t${r.value}\tTTL:${r.ttl}${priority}`);
411
+ }
412
+ }
413
+ });
414
+
415
+ dnsCmd
416
+ .command("add")
417
+ .description("Add a DNS record")
418
+ .requiredOption("--domain <id>", "Domain ID")
419
+ .requiredOption("--type <type>", "Record type (A/AAAA/CNAME/MX/TXT/NS/SRV)")
420
+ .requiredOption("--name <name>", "Record name")
421
+ .requiredOption("--value <value>", "Record value")
422
+ .option("--ttl <ttl>", "TTL in seconds", "3600")
423
+ .option("--priority <n>", "Priority (for MX/SRV)")
424
+ .option("--json", "Output as JSON", false)
425
+ .action((opts) => {
426
+ const record = createDnsRecord({
427
+ domain_id: opts.domain,
428
+ type: opts.type,
429
+ name: opts.name,
430
+ value: opts.value,
431
+ ttl: parseInt(opts.ttl),
432
+ priority: opts.priority ? parseInt(opts.priority) : undefined,
433
+ });
434
+
435
+ if (opts.json) {
436
+ console.log(JSON.stringify(record, null, 2));
437
+ } else {
438
+ console.log(`Created DNS record: ${record.type} ${record.name} -> ${record.value} (${record.id})`);
439
+ }
440
+ });
441
+
442
+ dnsCmd
443
+ .command("update")
444
+ .description("Update a DNS record")
445
+ .argument("<id>", "Record ID")
446
+ .option("--type <type>", "Record type")
447
+ .option("--name <name>", "Record name")
448
+ .option("--value <value>", "Record value")
449
+ .option("--ttl <ttl>", "TTL in seconds")
450
+ .option("--priority <n>", "Priority")
451
+ .option("--json", "Output as JSON", false)
452
+ .action((id, opts) => {
453
+ const input: Record<string, unknown> = {};
454
+ if (opts.type !== undefined) input.type = opts.type;
455
+ if (opts.name !== undefined) input.name = opts.name;
456
+ if (opts.value !== undefined) input.value = opts.value;
457
+ if (opts.ttl !== undefined) input.ttl = parseInt(opts.ttl);
458
+ if (opts.priority !== undefined) input.priority = parseInt(opts.priority);
459
+
460
+ const record = updateDnsRecord(id, input);
461
+ if (!record) {
462
+ console.error(`DNS record '${id}' not found.`);
463
+ process.exit(1);
464
+ }
465
+
466
+ if (opts.json) {
467
+ console.log(JSON.stringify(record, null, 2));
468
+ } else {
469
+ console.log(`Updated DNS record: ${record.type} ${record.name} -> ${record.value}`);
470
+ }
471
+ });
472
+
473
+ dnsCmd
474
+ .command("remove")
475
+ .description("Remove a DNS record")
476
+ .argument("<id>", "Record ID")
477
+ .action((id) => {
478
+ const deleted = deleteDnsRecord(id);
479
+ if (deleted) {
480
+ console.log(`Deleted DNS record ${id}`);
481
+ } else {
482
+ console.error(`DNS record '${id}' not found.`);
483
+ process.exit(1);
484
+ }
485
+ });
486
+
487
+ // --- DNS Propagation Check ---
488
+
489
+ dnsCmd
490
+ .command("check-propagation")
491
+ .description("Check DNS propagation across multiple servers")
492
+ .argument("<domain>", "Domain name to check")
493
+ .option("--record <type>", "Record type (A/AAAA/CNAME/MX/TXT/NS)", "A")
494
+ .option("--json", "Output as JSON", false)
495
+ .action((domain, opts) => {
496
+ const result = checkDnsPropagation(domain, opts.record);
497
+ if (opts.json) {
498
+ console.log(JSON.stringify(result, null, 2));
499
+ } else {
500
+ console.log(`DNS Propagation for ${result.domain} (${result.record_type}):`);
501
+ console.log(` Consistent: ${result.consistent ? "yes" : "NO"}`);
502
+ for (const s of result.servers) {
503
+ const values = s.values.length > 0 ? s.values.join(", ") : "(empty)";
504
+ const status = s.status === "error" ? ` [ERROR: ${s.error}]` : "";
505
+ console.log(` ${s.name} (${s.server}): ${values}${status}`);
506
+ }
507
+ }
508
+ });
509
+
510
+ // --- Zone File Export ---
511
+
512
+ dnsCmd
513
+ .command("export")
514
+ .description("Export DNS records as BIND zone file")
515
+ .argument("<domain-id>", "Domain ID")
516
+ .option("--format <format>", "Export format (zone)", "zone")
517
+ .option("--output <file>", "Write to file instead of stdout")
518
+ .action((domainId, opts) => {
519
+ const zone = exportZoneFile(domainId);
520
+ if (!zone) {
521
+ console.error(`Domain '${domainId}' not found.`);
522
+ process.exit(1);
523
+ }
524
+ if (opts.output) {
525
+ writeFileSync(opts.output, zone, "utf-8");
526
+ console.log(`Exported zone file to ${opts.output}`);
527
+ } else {
528
+ console.log(zone);
529
+ }
530
+ });
531
+
532
+ // --- Zone File Import ---
533
+
534
+ dnsCmd
535
+ .command("import")
536
+ .description("Import DNS records from a BIND zone file")
537
+ .argument("<domain-id>", "Domain ID")
538
+ .requiredOption("--file <path>", "Path to zone file")
539
+ .option("--json", "Output as JSON", false)
540
+ .action((domainId, opts) => {
541
+ let content: string;
542
+ try {
543
+ content = readFileSync(opts.file, "utf-8");
544
+ } catch {
545
+ console.error(`Could not read file: ${opts.file}`);
546
+ process.exit(1);
547
+ }
548
+
549
+ const result = importZoneFile(domainId, content);
550
+ if (!result) {
551
+ console.error(`Domain '${domainId}' not found.`);
552
+ process.exit(1);
553
+ }
554
+
555
+ if (opts.json) {
556
+ console.log(JSON.stringify(result, null, 2));
557
+ } else {
558
+ console.log(`Imported ${result.imported} record(s), skipped ${result.skipped}`);
559
+ if (result.errors.length > 0) {
560
+ console.log("Errors:");
561
+ for (const e of result.errors) {
562
+ console.log(` - ${e}`);
563
+ }
564
+ }
565
+ }
566
+ });
567
+
568
+ // --- Subdomain Discovery ---
569
+
570
+ dnsCmd
571
+ .command("discover-subdomains")
572
+ .description("Discover subdomains via certificate transparency logs (crt.sh)")
573
+ .argument("<domain>", "Domain name")
574
+ .option("--json", "Output as JSON", false)
575
+ .action(async (domain, opts) => {
576
+ const result = await discoverSubdomains(domain);
577
+ if (opts.json) {
578
+ console.log(JSON.stringify(result, null, 2));
579
+ } else {
580
+ if (result.error) {
581
+ console.error(`Discovery failed: ${result.error}`);
582
+ process.exit(1);
583
+ }
584
+ if (result.subdomains.length === 0) {
585
+ console.log(`No subdomains found for ${domain}.`);
586
+ return;
587
+ }
588
+ console.log(`Subdomains for ${domain} (source: ${result.source}):`);
589
+ for (const s of result.subdomains) {
590
+ console.log(` ${s}`);
591
+ }
592
+ console.log(`\n${result.subdomains.length} subdomain(s) found`);
593
+ }
594
+ });
595
+
596
+ // --- DNS Validation ---
597
+
598
+ dnsCmd
599
+ .command("validate")
600
+ .description("Validate DNS records for common issues")
601
+ .argument("<domain-id>", "Domain ID")
602
+ .option("--json", "Output as JSON", false)
603
+ .action((domainId, opts) => {
604
+ const result = validateDns(domainId);
605
+ if (!result) {
606
+ console.error(`Domain '${domainId}' not found.`);
607
+ process.exit(1);
608
+ }
609
+
610
+ if (opts.json) {
611
+ console.log(JSON.stringify(result, null, 2));
612
+ } else {
613
+ console.log(`DNS Validation for ${result.domain_name}:`);
614
+ console.log(` Valid: ${result.valid ? "yes" : "NO"}`);
615
+ if (result.issues.length === 0) {
616
+ console.log(" No issues found.");
617
+ } else {
618
+ for (const issue of result.issues) {
619
+ const prefix = issue.type === "error" ? "ERROR" : "WARN";
620
+ console.log(` [${prefix}] ${issue.message}`);
621
+ }
622
+ }
623
+ }
624
+ });
625
+
626
+ // --- Alerts ---
627
+
628
+ const alertCmd = program
629
+ .command("alert")
630
+ .description("Alert management");
631
+
632
+ alertCmd
633
+ .command("set")
634
+ .description("Set an alert for a domain")
635
+ .requiredOption("--domain <id>", "Domain ID")
636
+ .requiredOption("--type <type>", "Alert type (expiry/ssl_expiry/dns_change)")
637
+ .option("--days-before <n>", "Trigger N days before")
638
+ .option("--json", "Output as JSON", false)
639
+ .action((opts) => {
640
+ const alert = createAlert({
641
+ domain_id: opts.domain,
642
+ type: opts.type,
643
+ trigger_days_before: opts.daysBefore ? parseInt(opts.daysBefore) : undefined,
644
+ });
645
+
646
+ if (opts.json) {
647
+ console.log(JSON.stringify(alert, null, 2));
648
+ } else {
649
+ const daysBefore = alert.trigger_days_before ? ` (${alert.trigger_days_before} days before)` : "";
650
+ console.log(`Created alert: ${alert.type}${daysBefore} for domain ${alert.domain_id} (${alert.id})`);
651
+ }
652
+ });
653
+
654
+ alertCmd
655
+ .command("list")
656
+ .description("List alerts for a domain")
657
+ .argument("<domain-id>", "Domain ID")
658
+ .option("--json", "Output as JSON", false)
659
+ .action((domainId, opts) => {
660
+ const alerts = listAlerts(domainId);
661
+
662
+ if (opts.json) {
663
+ console.log(JSON.stringify(alerts, null, 2));
664
+ } else {
665
+ if (alerts.length === 0) {
666
+ console.log("No alerts set.");
667
+ return;
668
+ }
669
+ for (const a of alerts) {
670
+ const daysBefore = a.trigger_days_before ? ` (${a.trigger_days_before} days before)` : "";
671
+ const sent = a.sent_at ? ` — sent ${a.sent_at}` : "";
672
+ console.log(` ${a.type}${daysBefore}${sent}`);
673
+ }
674
+ }
675
+ });
676
+
677
+ alertCmd
678
+ .command("remove")
679
+ .description("Remove an alert")
680
+ .argument("<id>", "Alert ID")
681
+ .action((id) => {
682
+ const deleted = deleteAlert(id);
683
+ if (deleted) {
684
+ console.log(`Deleted alert ${id}`);
685
+ } else {
686
+ console.error(`Alert '${id}' not found.`);
687
+ process.exit(1);
688
+ }
689
+ });
690
+
691
+ program.parse(process.argv);