@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.
- package/bin/index.js +63 -0
- package/bin/mcp.js +63 -0
- package/dist/index.js +63 -0
- package/microservices/microservice-ads/package.json +27 -0
- package/microservices/microservice-ads/src/cli/index.ts +605 -0
- package/microservices/microservice-ads/src/db/campaigns.ts +797 -0
- package/microservices/microservice-ads/src/db/database.ts +93 -0
- package/microservices/microservice-ads/src/db/migrations.ts +60 -0
- package/microservices/microservice-ads/src/index.ts +39 -0
- package/microservices/microservice-ads/src/mcp/index.ts +480 -0
- package/microservices/microservice-contracts/package.json +27 -0
- package/microservices/microservice-contracts/src/cli/index.ts +770 -0
- package/microservices/microservice-contracts/src/db/contracts.ts +925 -0
- package/microservices/microservice-contracts/src/db/database.ts +93 -0
- package/microservices/microservice-contracts/src/db/migrations.ts +141 -0
- package/microservices/microservice-contracts/src/index.ts +43 -0
- package/microservices/microservice-contracts/src/mcp/index.ts +617 -0
- package/microservices/microservice-domains/package.json +27 -0
- package/microservices/microservice-domains/src/cli/index.ts +691 -0
- package/microservices/microservice-domains/src/db/database.ts +93 -0
- package/microservices/microservice-domains/src/db/domains.ts +1164 -0
- package/microservices/microservice-domains/src/db/migrations.ts +60 -0
- package/microservices/microservice-domains/src/index.ts +65 -0
- package/microservices/microservice-domains/src/mcp/index.ts +536 -0
- package/microservices/microservice-hiring/package.json +27 -0
- package/microservices/microservice-hiring/src/cli/index.ts +741 -0
- package/microservices/microservice-hiring/src/db/database.ts +93 -0
- package/microservices/microservice-hiring/src/db/hiring.ts +1085 -0
- package/microservices/microservice-hiring/src/db/migrations.ts +89 -0
- package/microservices/microservice-hiring/src/index.ts +80 -0
- package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
- package/microservices/microservice-hiring/src/mcp/index.ts +709 -0
- package/microservices/microservice-payments/package.json +27 -0
- package/microservices/microservice-payments/src/cli/index.ts +609 -0
- package/microservices/microservice-payments/src/db/database.ts +93 -0
- package/microservices/microservice-payments/src/db/migrations.ts +81 -0
- package/microservices/microservice-payments/src/db/payments.ts +1204 -0
- package/microservices/microservice-payments/src/index.ts +51 -0
- package/microservices/microservice-payments/src/mcp/index.ts +683 -0
- package/microservices/microservice-payroll/package.json +27 -0
- package/microservices/microservice-payroll/src/cli/index.ts +643 -0
- package/microservices/microservice-payroll/src/db/database.ts +93 -0
- package/microservices/microservice-payroll/src/db/migrations.ts +95 -0
- package/microservices/microservice-payroll/src/db/payroll.ts +1377 -0
- package/microservices/microservice-payroll/src/index.ts +48 -0
- package/microservices/microservice-payroll/src/mcp/index.ts +666 -0
- package/microservices/microservice-shipping/package.json +27 -0
- package/microservices/microservice-shipping/src/cli/index.ts +606 -0
- package/microservices/microservice-shipping/src/db/database.ts +93 -0
- package/microservices/microservice-shipping/src/db/migrations.ts +69 -0
- package/microservices/microservice-shipping/src/db/shipping.ts +1093 -0
- package/microservices/microservice-shipping/src/index.ts +53 -0
- package/microservices/microservice-shipping/src/mcp/index.ts +533 -0
- package/microservices/microservice-social/package.json +27 -0
- package/microservices/microservice-social/src/cli/index.ts +689 -0
- package/microservices/microservice-social/src/db/database.ts +93 -0
- package/microservices/microservice-social/src/db/migrations.ts +88 -0
- package/microservices/microservice-social/src/db/social.ts +1046 -0
- package/microservices/microservice-social/src/index.ts +46 -0
- package/microservices/microservice-social/src/mcp/index.ts +655 -0
- package/microservices/microservice-subscriptions/package.json +27 -0
- package/microservices/microservice-subscriptions/src/cli/index.ts +715 -0
- package/microservices/microservice-subscriptions/src/db/database.ts +93 -0
- package/microservices/microservice-subscriptions/src/db/migrations.ts +125 -0
- package/microservices/microservice-subscriptions/src/db/subscriptions.ts +1256 -0
- package/microservices/microservice-subscriptions/src/index.ts +41 -0
- package/microservices/microservice-subscriptions/src/mcp/index.ts +631 -0
- 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);
|