@hasna/domains 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/dist/cli/commands/config.d.ts +3 -0
- package/dist/cli/commands/config.d.ts.map +1 -0
- package/dist/cli/commands/dns.d.ts.map +1 -1
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/domain.d.ts +3 -0
- package/dist/cli/commands/domain.d.ts.map +1 -0
- package/dist/cli/commands/mcp-install.d.ts +3 -0
- package/dist/cli/commands/mcp-install.d.ts.map +1 -0
- package/dist/cli/commands/monitor.d.ts +3 -0
- package/dist/cli/commands/monitor.d.ts.map +1 -0
- package/dist/cli/commands/provider.d.ts +3 -0
- package/dist/cli/commands/provider.d.ts.map +1 -0
- package/dist/cli/commands/route53.d.ts.map +1 -1
- package/dist/cli/commands/serve.d.ts +3 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/ssl.d.ts +3 -0
- package/dist/cli/commands/ssl.d.ts.map +1 -0
- package/dist/cli/commands/zone.d.ts +3 -0
- package/dist/cli/commands/zone.d.ts.map +1 -0
- package/dist/cli/index.js +1674 -895
- package/dist/db/dns-tools.d.ts.map +1 -1
- package/dist/index.js +420 -92
- package/dist/lib/brandsight.d.ts +42 -2
- package/dist/lib/brandsight.d.ts.map +1 -1
- package/dist/lib/cloudflare.d.ts +39 -0
- package/dist/lib/cloudflare.d.ts.map +1 -0
- package/dist/lib/config.d.ts +49 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/registrar.d.ts +22 -4
- package/dist/lib/registrar.d.ts.map +1 -1
- package/dist/lib/route53.d.ts +2 -2
- package/dist/lib/route53.d.ts.map +1 -1
- package/dist/lib/version.d.ts +3 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/mcp/index.js +23080 -20411
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3196,7 +3196,7 @@ var require_headStream = __commonJS((exports) => {
|
|
|
3196
3196
|
if ((0, stream_type_check_1.isReadableStream)(stream)) {
|
|
3197
3197
|
return (0, headStream_browser_1.headStream)(stream, bytes);
|
|
3198
3198
|
}
|
|
3199
|
-
return new Promise((
|
|
3199
|
+
return new Promise((resolve3, reject) => {
|
|
3200
3200
|
const collector = new Collector;
|
|
3201
3201
|
collector.limit = bytes;
|
|
3202
3202
|
stream.pipe(collector);
|
|
@@ -3207,7 +3207,7 @@ var require_headStream = __commonJS((exports) => {
|
|
|
3207
3207
|
collector.on("error", reject);
|
|
3208
3208
|
collector.on("finish", function() {
|
|
3209
3209
|
const bytes2 = new Uint8Array(Buffer.concat(this.buffers));
|
|
3210
|
-
|
|
3210
|
+
resolve3(bytes2);
|
|
3211
3211
|
});
|
|
3212
3212
|
});
|
|
3213
3213
|
};
|
|
@@ -3403,21 +3403,21 @@ var require_dist_cjs13 = __commonJS((exports) => {
|
|
|
3403
3403
|
let sendBody = true;
|
|
3404
3404
|
if (!externalAgent && expect === "100-continue") {
|
|
3405
3405
|
sendBody = await Promise.race([
|
|
3406
|
-
new Promise((
|
|
3407
|
-
timeoutId = Number(timing.setTimeout(() =>
|
|
3406
|
+
new Promise((resolve3) => {
|
|
3407
|
+
timeoutId = Number(timing.setTimeout(() => resolve3(true), Math.max(MIN_WAIT_TIME, maxContinueTimeoutMs)));
|
|
3408
3408
|
}),
|
|
3409
|
-
new Promise((
|
|
3409
|
+
new Promise((resolve3) => {
|
|
3410
3410
|
httpRequest.on("continue", () => {
|
|
3411
3411
|
timing.clearTimeout(timeoutId);
|
|
3412
|
-
|
|
3412
|
+
resolve3(true);
|
|
3413
3413
|
});
|
|
3414
3414
|
httpRequest.on("response", () => {
|
|
3415
3415
|
timing.clearTimeout(timeoutId);
|
|
3416
|
-
|
|
3416
|
+
resolve3(false);
|
|
3417
3417
|
});
|
|
3418
3418
|
httpRequest.on("error", () => {
|
|
3419
3419
|
timing.clearTimeout(timeoutId);
|
|
3420
|
-
|
|
3420
|
+
resolve3(false);
|
|
3421
3421
|
});
|
|
3422
3422
|
})
|
|
3423
3423
|
]);
|
|
@@ -3492,13 +3492,13 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
|
|
|
3492
3492
|
return socketWarningTimestamp;
|
|
3493
3493
|
}
|
|
3494
3494
|
constructor(options) {
|
|
3495
|
-
this.configProvider = new Promise((
|
|
3495
|
+
this.configProvider = new Promise((resolve3, reject) => {
|
|
3496
3496
|
if (typeof options === "function") {
|
|
3497
3497
|
options().then((_options) => {
|
|
3498
|
-
|
|
3498
|
+
resolve3(this.resolveDefaultConfig(_options));
|
|
3499
3499
|
}).catch(reject);
|
|
3500
3500
|
} else {
|
|
3501
|
-
|
|
3501
|
+
resolve3(this.resolveDefaultConfig(options));
|
|
3502
3502
|
}
|
|
3503
3503
|
});
|
|
3504
3504
|
}
|
|
@@ -3518,7 +3518,7 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
|
|
|
3518
3518
|
return new Promise((_resolve, _reject) => {
|
|
3519
3519
|
let writeRequestBodyPromise = undefined;
|
|
3520
3520
|
const timeouts = [];
|
|
3521
|
-
const
|
|
3521
|
+
const resolve3 = async (arg) => {
|
|
3522
3522
|
await writeRequestBodyPromise;
|
|
3523
3523
|
timeouts.forEach(timing.clearTimeout);
|
|
3524
3524
|
_resolve(arg);
|
|
@@ -3582,7 +3582,7 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
|
|
|
3582
3582
|
headers: getTransformedHeaders(res.headers),
|
|
3583
3583
|
body: res
|
|
3584
3584
|
});
|
|
3585
|
-
|
|
3585
|
+
resolve3({ response: httpResponse });
|
|
3586
3586
|
});
|
|
3587
3587
|
req.on("error", (err) => {
|
|
3588
3588
|
if (NODEJS_TIMEOUT_ERROR_CODES.includes(err.code)) {
|
|
@@ -3794,13 +3794,13 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
|
|
|
3794
3794
|
return new NodeHttp2Handler(instanceOrOptions);
|
|
3795
3795
|
}
|
|
3796
3796
|
constructor(options) {
|
|
3797
|
-
this.configProvider = new Promise((
|
|
3797
|
+
this.configProvider = new Promise((resolve3, reject) => {
|
|
3798
3798
|
if (typeof options === "function") {
|
|
3799
3799
|
options().then((opts) => {
|
|
3800
|
-
|
|
3800
|
+
resolve3(opts || {});
|
|
3801
3801
|
}).catch(reject);
|
|
3802
3802
|
} else {
|
|
3803
|
-
|
|
3803
|
+
resolve3(options || {});
|
|
3804
3804
|
}
|
|
3805
3805
|
});
|
|
3806
3806
|
}
|
|
@@ -3820,7 +3820,7 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
|
|
|
3820
3820
|
return new Promise((_resolve, _reject) => {
|
|
3821
3821
|
let fulfilled = false;
|
|
3822
3822
|
let writeRequestBodyPromise = undefined;
|
|
3823
|
-
const
|
|
3823
|
+
const resolve3 = async (arg) => {
|
|
3824
3824
|
await writeRequestBodyPromise;
|
|
3825
3825
|
_resolve(arg);
|
|
3826
3826
|
};
|
|
@@ -3875,7 +3875,7 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
|
|
|
3875
3875
|
body: req
|
|
3876
3876
|
});
|
|
3877
3877
|
fulfilled = true;
|
|
3878
|
-
|
|
3878
|
+
resolve3({ response: httpResponse });
|
|
3879
3879
|
if (disableConcurrentStreams) {
|
|
3880
3880
|
session.close();
|
|
3881
3881
|
this.connectionManager.deleteSession(authority, session);
|
|
@@ -3952,7 +3952,7 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
|
|
|
3952
3952
|
if (isReadableStreamInstance(stream)) {
|
|
3953
3953
|
return collectReadableStream(stream);
|
|
3954
3954
|
}
|
|
3955
|
-
return new Promise((
|
|
3955
|
+
return new Promise((resolve3, reject) => {
|
|
3956
3956
|
const collector = new Collector;
|
|
3957
3957
|
stream.pipe(collector);
|
|
3958
3958
|
stream.on("error", (err) => {
|
|
@@ -3962,7 +3962,7 @@ or increase socketAcquisitionWarningTimeout=(millis) in the NodeHttpHandler conf
|
|
|
3962
3962
|
collector.on("error", reject);
|
|
3963
3963
|
collector.on("finish", function() {
|
|
3964
3964
|
const bytes = new Uint8Array(Buffer.concat(this.bufferedBytes));
|
|
3965
|
-
|
|
3965
|
+
resolve3(bytes);
|
|
3966
3966
|
});
|
|
3967
3967
|
});
|
|
3968
3968
|
};
|
|
@@ -4003,7 +4003,7 @@ var require_dist_cjs14 = __commonJS((exports) => {
|
|
|
4003
4003
|
return new Request(url, requestOptions);
|
|
4004
4004
|
}
|
|
4005
4005
|
function requestTimeout(timeoutInMs = 0) {
|
|
4006
|
-
return new Promise((
|
|
4006
|
+
return new Promise((resolve3, reject) => {
|
|
4007
4007
|
if (timeoutInMs) {
|
|
4008
4008
|
setTimeout(() => {
|
|
4009
4009
|
const timeoutError = new Error(`Request did not complete within ${timeoutInMs} ms`);
|
|
@@ -4119,7 +4119,7 @@ var require_dist_cjs14 = __commonJS((exports) => {
|
|
|
4119
4119
|
requestTimeout(requestTimeoutInMs)
|
|
4120
4120
|
];
|
|
4121
4121
|
if (abortSignal) {
|
|
4122
|
-
raceOfPromises.push(new Promise((
|
|
4122
|
+
raceOfPromises.push(new Promise((resolve3, reject) => {
|
|
4123
4123
|
const onAbort = () => {
|
|
4124
4124
|
const abortError = buildAbortError(abortSignal);
|
|
4125
4125
|
reject(abortError);
|
|
@@ -4199,7 +4199,7 @@ var require_dist_cjs14 = __commonJS((exports) => {
|
|
|
4199
4199
|
return collected;
|
|
4200
4200
|
}
|
|
4201
4201
|
function readToBase64(blob) {
|
|
4202
|
-
return new Promise((
|
|
4202
|
+
return new Promise((resolve3, reject) => {
|
|
4203
4203
|
const reader = new FileReader;
|
|
4204
4204
|
reader.onloadend = () => {
|
|
4205
4205
|
if (reader.readyState !== 2) {
|
|
@@ -4208,7 +4208,7 @@ var require_dist_cjs14 = __commonJS((exports) => {
|
|
|
4208
4208
|
const result = reader.result ?? "";
|
|
4209
4209
|
const commaIndex = result.indexOf(",");
|
|
4210
4210
|
const dataOffset = commaIndex > -1 ? commaIndex + 1 : result.length;
|
|
4211
|
-
|
|
4211
|
+
resolve3(result.substring(dataOffset));
|
|
4212
4212
|
};
|
|
4213
4213
|
reader.onabort = () => reject(new Error("Read aborted"));
|
|
4214
4214
|
reader.onerror = () => reject(reader.error);
|
|
@@ -5415,11 +5415,11 @@ var require_tslib = __commonJS((exports, module) => {
|
|
|
5415
5415
|
};
|
|
5416
5416
|
__awaiter = function(thisArg, _arguments, P, generator) {
|
|
5417
5417
|
function adopt(value) {
|
|
5418
|
-
return value instanceof P ? value : new P(function(
|
|
5419
|
-
|
|
5418
|
+
return value instanceof P ? value : new P(function(resolve3) {
|
|
5419
|
+
resolve3(value);
|
|
5420
5420
|
});
|
|
5421
5421
|
}
|
|
5422
|
-
return new (P || (P = Promise))(function(
|
|
5422
|
+
return new (P || (P = Promise))(function(resolve3, reject) {
|
|
5423
5423
|
function fulfilled(value) {
|
|
5424
5424
|
try {
|
|
5425
5425
|
step(generator.next(value));
|
|
@@ -5435,7 +5435,7 @@ var require_tslib = __commonJS((exports, module) => {
|
|
|
5435
5435
|
}
|
|
5436
5436
|
}
|
|
5437
5437
|
function step(result) {
|
|
5438
|
-
result.done ?
|
|
5438
|
+
result.done ? resolve3(result.value) : adopt(result.value).then(fulfilled, rejected);
|
|
5439
5439
|
}
|
|
5440
5440
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
5441
5441
|
});
|
|
@@ -5664,14 +5664,14 @@ var require_tslib = __commonJS((exports, module) => {
|
|
|
5664
5664
|
}, i);
|
|
5665
5665
|
function verb(n) {
|
|
5666
5666
|
i[n] = o[n] && function(v) {
|
|
5667
|
-
return new Promise(function(
|
|
5668
|
-
v = o[n](v), settle(
|
|
5667
|
+
return new Promise(function(resolve3, reject) {
|
|
5668
|
+
v = o[n](v), settle(resolve3, reject, v.done, v.value);
|
|
5669
5669
|
});
|
|
5670
5670
|
};
|
|
5671
5671
|
}
|
|
5672
|
-
function settle(
|
|
5672
|
+
function settle(resolve3, reject, d, v) {
|
|
5673
5673
|
Promise.resolve(v).then(function(v2) {
|
|
5674
|
-
|
|
5674
|
+
resolve3({ value: v2, done: d });
|
|
5675
5675
|
}, reject);
|
|
5676
5676
|
}
|
|
5677
5677
|
};
|
|
@@ -15357,7 +15357,7 @@ var require_dist_cjs31 = __commonJS((exports) => {
|
|
|
15357
15357
|
this.refillTokenBucket();
|
|
15358
15358
|
if (amount > this.currentCapacity) {
|
|
15359
15359
|
const delay = (amount - this.currentCapacity) / this.fillRate * 1000;
|
|
15360
|
-
await new Promise((
|
|
15360
|
+
await new Promise((resolve3) => DefaultRateLimiter.setTimeoutFn(resolve3, delay));
|
|
15361
15361
|
}
|
|
15362
15362
|
this.currentCapacity = this.currentCapacity - amount;
|
|
15363
15363
|
}
|
|
@@ -15651,7 +15651,7 @@ var require_dist_cjs32 = __commonJS((exports) => {
|
|
|
15651
15651
|
}
|
|
15652
15652
|
}
|
|
15653
15653
|
}
|
|
15654
|
-
var
|
|
15654
|
+
var USER_AGENT2 = "user-agent";
|
|
15655
15655
|
var X_AMZ_USER_AGENT = "x-amz-user-agent";
|
|
15656
15656
|
var SPACE = " ";
|
|
15657
15657
|
var UA_NAME_SEPARATOR = "/";
|
|
@@ -15699,9 +15699,9 @@ var require_dist_cjs32 = __commonJS((exports) => {
|
|
|
15699
15699
|
].join(SPACE);
|
|
15700
15700
|
if (options.runtime !== "browser") {
|
|
15701
15701
|
if (normalUAValue) {
|
|
15702
|
-
headers[X_AMZ_USER_AGENT] = headers[X_AMZ_USER_AGENT] ? `${headers[
|
|
15702
|
+
headers[X_AMZ_USER_AGENT] = headers[X_AMZ_USER_AGENT] ? `${headers[USER_AGENT2]} ${normalUAValue}` : normalUAValue;
|
|
15703
15703
|
}
|
|
15704
|
-
headers[
|
|
15704
|
+
headers[USER_AGENT2] = sdkUserAgentValue;
|
|
15705
15705
|
} else {
|
|
15706
15706
|
headers[X_AMZ_USER_AGENT] = sdkUserAgentValue;
|
|
15707
15707
|
}
|
|
@@ -16803,7 +16803,7 @@ var require_dist_cjs40 = __commonJS((exports) => {
|
|
|
16803
16803
|
const delayFromResponse = getDelayFromRetryAfterHeader(err.$response);
|
|
16804
16804
|
const delay = Math.max(delayFromResponse || 0, delayFromDecider);
|
|
16805
16805
|
totalDelay += delay;
|
|
16806
|
-
await new Promise((
|
|
16806
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
16807
16807
|
continue;
|
|
16808
16808
|
}
|
|
16809
16809
|
if (!err.$metadata) {
|
|
@@ -16955,7 +16955,7 @@ var require_dist_cjs40 = __commonJS((exports) => {
|
|
|
16955
16955
|
attempts = retryToken.getRetryCount();
|
|
16956
16956
|
const delay = retryToken.getRetryDelay();
|
|
16957
16957
|
totalRetryDelay += delay;
|
|
16958
|
-
await new Promise((
|
|
16958
|
+
await new Promise((resolve3) => setTimeout(resolve3, delay));
|
|
16959
16959
|
}
|
|
16960
16960
|
}
|
|
16961
16961
|
} else {
|
|
@@ -17078,7 +17078,7 @@ var init_dist_es = __esm(() => {
|
|
|
17078
17078
|
import { Buffer as Buffer2 } from "buffer";
|
|
17079
17079
|
import { request } from "http";
|
|
17080
17080
|
function httpRequest(options) {
|
|
17081
|
-
return new Promise((
|
|
17081
|
+
return new Promise((resolve3, reject) => {
|
|
17082
17082
|
const req = request({
|
|
17083
17083
|
method: "GET",
|
|
17084
17084
|
...options,
|
|
@@ -17103,7 +17103,7 @@ function httpRequest(options) {
|
|
|
17103
17103
|
chunks.push(chunk);
|
|
17104
17104
|
});
|
|
17105
17105
|
res.on("end", () => {
|
|
17106
|
-
|
|
17106
|
+
resolve3(Buffer2.concat(chunks));
|
|
17107
17107
|
req.destroy();
|
|
17108
17108
|
});
|
|
17109
17109
|
});
|
|
@@ -17564,7 +17564,7 @@ var retryWrapper = (toRetry, maxRetries, delayMs) => {
|
|
|
17564
17564
|
try {
|
|
17565
17565
|
return await toRetry();
|
|
17566
17566
|
} catch (e) {
|
|
17567
|
-
await new Promise((
|
|
17567
|
+
await new Promise((resolve3) => setTimeout(resolve3, delayMs));
|
|
17568
17568
|
}
|
|
17569
17569
|
}
|
|
17570
17570
|
return await toRetry();
|
|
@@ -17874,12 +17874,12 @@ var require_dist_cjs41 = __commonJS((exports) => {
|
|
|
17874
17874
|
}
|
|
17875
17875
|
return ["md/nodejs", node_process.versions.node];
|
|
17876
17876
|
};
|
|
17877
|
-
var getNodeModulesParentDirs = (
|
|
17877
|
+
var getNodeModulesParentDirs = (dirname4) => {
|
|
17878
17878
|
const cwd = process.cwd();
|
|
17879
|
-
if (!
|
|
17879
|
+
if (!dirname4) {
|
|
17880
17880
|
return [cwd];
|
|
17881
17881
|
}
|
|
17882
|
-
const normalizedPath = node_path.normalize(
|
|
17882
|
+
const normalizedPath = node_path.normalize(dirname4);
|
|
17883
17883
|
const parts = normalizedPath.split(node_path.sep);
|
|
17884
17884
|
const nodeModulesIndex = parts.indexOf("node_modules");
|
|
17885
17885
|
const parentDir = nodeModulesIndex !== -1 ? parts.slice(0, nodeModulesIndex).join(node_path.sep) : normalizedPath;
|
|
@@ -17926,8 +17926,8 @@ var require_dist_cjs41 = __commonJS((exports) => {
|
|
|
17926
17926
|
tscVersion = null;
|
|
17927
17927
|
return;
|
|
17928
17928
|
}
|
|
17929
|
-
const
|
|
17930
|
-
const nodeModulesParentDirs = getNodeModulesParentDirs(
|
|
17929
|
+
const dirname4 = typeof __dirname !== "undefined" ? __dirname : undefined;
|
|
17930
|
+
const nodeModulesParentDirs = getNodeModulesParentDirs(dirname4);
|
|
17931
17931
|
let versionFromApp;
|
|
17932
17932
|
for (const nodeModulesParentDir of nodeModulesParentDirs) {
|
|
17933
17933
|
try {
|
|
@@ -23670,7 +23670,7 @@ var require_signin = __commonJS((exports) => {
|
|
|
23670
23670
|
import { createHash, createPrivateKey, createPublicKey, sign } from "crypto";
|
|
23671
23671
|
import { promises as fs2 } from "fs";
|
|
23672
23672
|
import { homedir as homedir6 } from "os";
|
|
23673
|
-
import { dirname as
|
|
23673
|
+
import { dirname as dirname4, join as join7 } from "path";
|
|
23674
23674
|
var import_property_provider18, import_protocol_http2, import_shared_ini_file_loader6, LoginCredentialsFetcher;
|
|
23675
23675
|
var init_LoginCredentialsFetcher = __esm(() => {
|
|
23676
23676
|
import_property_provider18 = __toESM(require_dist_cjs23(), 1);
|
|
@@ -23823,7 +23823,7 @@ var init_LoginCredentialsFetcher = __esm(() => {
|
|
|
23823
23823
|
}
|
|
23824
23824
|
async saveToken(token) {
|
|
23825
23825
|
const tokenFilePath = this.getTokenFilePath();
|
|
23826
|
-
const directory =
|
|
23826
|
+
const directory = dirname4(tokenFilePath);
|
|
23827
23827
|
try {
|
|
23828
23828
|
await fs2.mkdir(directory, { recursive: true });
|
|
23829
23829
|
} catch (error) {}
|
|
@@ -34022,6 +34022,11 @@ function deleteDomain(id) {
|
|
|
34022
34022
|
const result = db.prepare("DELETE FROM domains WHERE id = ?").run(id);
|
|
34023
34023
|
return result.changes > 0;
|
|
34024
34024
|
}
|
|
34025
|
+
function countDomains() {
|
|
34026
|
+
const db = getDatabase();
|
|
34027
|
+
const row = db.prepare("SELECT COUNT(*) as count FROM domains").get();
|
|
34028
|
+
return row.count;
|
|
34029
|
+
}
|
|
34025
34030
|
function searchDomains(query) {
|
|
34026
34031
|
return listDomains({ search: query });
|
|
34027
34032
|
}
|
|
@@ -34170,6 +34175,28 @@ function deleteAlert(id) {
|
|
|
34170
34175
|
}
|
|
34171
34176
|
// src/db/dns-tools.ts
|
|
34172
34177
|
import { execSync } from "child_process";
|
|
34178
|
+
|
|
34179
|
+
// src/lib/version.ts
|
|
34180
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
34181
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
34182
|
+
import { fileURLToPath } from "url";
|
|
34183
|
+
var cachedVersion = null;
|
|
34184
|
+
function getPackageVersion() {
|
|
34185
|
+
if (cachedVersion)
|
|
34186
|
+
return cachedVersion;
|
|
34187
|
+
try {
|
|
34188
|
+
const moduleDir = dirname3(fileURLToPath(import.meta.url));
|
|
34189
|
+
const packageJsonPath = resolve2(moduleDir, "../../package.json");
|
|
34190
|
+
const pkg = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
|
|
34191
|
+
cachedVersion = pkg.version ?? "0.0.0";
|
|
34192
|
+
} catch {
|
|
34193
|
+
cachedVersion = "0.0.0";
|
|
34194
|
+
}
|
|
34195
|
+
return cachedVersion;
|
|
34196
|
+
}
|
|
34197
|
+
var USER_AGENT = `open-domains/${getPackageVersion()}`;
|
|
34198
|
+
|
|
34199
|
+
// src/db/dns-tools.ts
|
|
34173
34200
|
function whoisLookup(domainName) {
|
|
34174
34201
|
let raw;
|
|
34175
34202
|
try {
|
|
@@ -34383,7 +34410,7 @@ async function discoverSubdomains(domain) {
|
|
|
34383
34410
|
const url = `https://crt.sh/?q=%25.${encodeURIComponent(domain)}&output=json`;
|
|
34384
34411
|
const response = await fetch(url, {
|
|
34385
34412
|
signal: AbortSignal.timeout(15000),
|
|
34386
|
-
headers: { "User-Agent":
|
|
34413
|
+
headers: { "User-Agent": USER_AGENT }
|
|
34387
34414
|
});
|
|
34388
34415
|
if (!response.ok) {
|
|
34389
34416
|
return {
|
|
@@ -34554,502 +34581,6 @@ function csvEscape(value) {
|
|
|
34554
34581
|
}
|
|
34555
34582
|
return value;
|
|
34556
34583
|
}
|
|
34557
|
-
function checkAllDomains() {
|
|
34558
|
-
const domains = listDomains();
|
|
34559
|
-
const results = [];
|
|
34560
|
-
for (const domain of domains) {
|
|
34561
|
-
const result = {
|
|
34562
|
-
domain: domain.name,
|
|
34563
|
-
domain_id: domain.id
|
|
34564
|
-
};
|
|
34565
|
-
try {
|
|
34566
|
-
const whois = whoisLookup(domain.name);
|
|
34567
|
-
result.whois = {
|
|
34568
|
-
registrar: whois.registrar,
|
|
34569
|
-
expires_at: whois.expires_at
|
|
34570
|
-
};
|
|
34571
|
-
} catch (error) {
|
|
34572
|
-
result.whois = {
|
|
34573
|
-
registrar: null,
|
|
34574
|
-
expires_at: null,
|
|
34575
|
-
error: error instanceof Error ? error.message : String(error)
|
|
34576
|
-
};
|
|
34577
|
-
}
|
|
34578
|
-
const ssl = checkSsl(domain.name);
|
|
34579
|
-
result.ssl = {
|
|
34580
|
-
issuer: ssl.issuer,
|
|
34581
|
-
expires_at: ssl.expires_at,
|
|
34582
|
-
error: ssl.error
|
|
34583
|
-
};
|
|
34584
|
-
const validation = validateDns(domain.id);
|
|
34585
|
-
if (validation) {
|
|
34586
|
-
result.dns_validation = {
|
|
34587
|
-
valid: validation.valid,
|
|
34588
|
-
issue_count: validation.issues.length,
|
|
34589
|
-
errors: validation.issues.map((i) => `[${i.type}] ${i.message}`)
|
|
34590
|
-
};
|
|
34591
|
-
}
|
|
34592
|
-
results.push(result);
|
|
34593
|
-
}
|
|
34594
|
-
return results;
|
|
34595
|
-
}
|
|
34596
|
-
// src/cli/commands/domains.ts
|
|
34597
|
-
import { writeFileSync as writeFileSync2 } from "fs";
|
|
34598
|
-
function registerDomainCommands(program2) {
|
|
34599
|
-
program2.command("add").description("Add a new domain").requiredOption("--name <name>", "Domain name (e.g. example.com)").option("--registrar <registrar>", "Domain registrar").option("--status <status>", "Status (active/expired/transferring/redemption)", "active").option("--registered-at <date>", "Registration date (ISO)").option("--expires-at <date>", "Expiration date (ISO)").option("--no-auto-renew", "Disable auto-renew").option("--nameservers <ns>", "Comma-separated nameservers").option("--ssl-expires-at <date>", "SSL expiration date (ISO)").option("--ssl-issuer <issuer>", "SSL certificate issuer").option("--notes <notes>", "Notes").option("--json", "Output as JSON", false).action((opts) => {
|
|
34600
|
-
const domain = createDomain({
|
|
34601
|
-
name: opts.name,
|
|
34602
|
-
registrar: opts.registrar,
|
|
34603
|
-
status: opts.status,
|
|
34604
|
-
registered_at: opts.registeredAt,
|
|
34605
|
-
expires_at: opts.expiresAt,
|
|
34606
|
-
auto_renew: opts.autoRenew,
|
|
34607
|
-
nameservers: opts.nameservers ? opts.nameservers.split(",").map((s) => s.trim()) : undefined,
|
|
34608
|
-
ssl_expires_at: opts.sslExpiresAt,
|
|
34609
|
-
ssl_issuer: opts.sslIssuer,
|
|
34610
|
-
notes: opts.notes
|
|
34611
|
-
});
|
|
34612
|
-
if (opts.json) {
|
|
34613
|
-
console.log(JSON.stringify(domain, null, 2));
|
|
34614
|
-
} else {
|
|
34615
|
-
console.log(`Created domain: ${domain.name} (${domain.id})`);
|
|
34616
|
-
}
|
|
34617
|
-
});
|
|
34618
|
-
program2.command("get").description("Get a domain by ID").argument("<id>", "Domain ID").option("--json", "Output as JSON", false).action((id, opts) => {
|
|
34619
|
-
const domain = getDomain(id);
|
|
34620
|
-
if (!domain) {
|
|
34621
|
-
console.error(`Domain '${id}' not found.`);
|
|
34622
|
-
process.exit(1);
|
|
34623
|
-
}
|
|
34624
|
-
if (opts.json) {
|
|
34625
|
-
console.log(JSON.stringify(domain, null, 2));
|
|
34626
|
-
} else {
|
|
34627
|
-
console.log(`${domain.name} [${domain.status}]`);
|
|
34628
|
-
if (domain.registrar)
|
|
34629
|
-
console.log(` Registrar: ${domain.registrar}`);
|
|
34630
|
-
if (domain.expires_at)
|
|
34631
|
-
console.log(` Expires: ${domain.expires_at}`);
|
|
34632
|
-
if (domain.ssl_expires_at)
|
|
34633
|
-
console.log(` SSL Expires: ${domain.ssl_expires_at}`);
|
|
34634
|
-
if (domain.ssl_issuer)
|
|
34635
|
-
console.log(` SSL Issuer: ${domain.ssl_issuer}`);
|
|
34636
|
-
console.log(` Auto-renew: ${domain.auto_renew ? "yes" : "no"}`);
|
|
34637
|
-
if (domain.nameservers.length)
|
|
34638
|
-
console.log(` Nameservers: ${domain.nameservers.join(", ")}`);
|
|
34639
|
-
if (domain.notes)
|
|
34640
|
-
console.log(` Notes: ${domain.notes}`);
|
|
34641
|
-
}
|
|
34642
|
-
});
|
|
34643
|
-
program2.command("list").description("List domains").option("--search <query>", "Search by name, registrar, or notes").option("--status <status>", "Filter by status").option("--registrar <registrar>", "Filter by registrar").option("--limit <n>", "Limit results").option("--json", "Output as JSON", false).action((opts) => {
|
|
34644
|
-
const domains = listDomains({
|
|
34645
|
-
search: opts.search,
|
|
34646
|
-
status: opts.status,
|
|
34647
|
-
registrar: opts.registrar,
|
|
34648
|
-
limit: opts.limit ? parseInt(opts.limit) : undefined
|
|
34649
|
-
});
|
|
34650
|
-
if (opts.json) {
|
|
34651
|
-
console.log(JSON.stringify(domains, null, 2));
|
|
34652
|
-
} else {
|
|
34653
|
-
if (domains.length === 0) {
|
|
34654
|
-
console.log("No domains found.");
|
|
34655
|
-
return;
|
|
34656
|
-
}
|
|
34657
|
-
for (const d of domains) {
|
|
34658
|
-
const expires = d.expires_at ? ` (expires ${d.expires_at})` : "";
|
|
34659
|
-
console.log(` ${d.name} [${d.status}]${expires}`);
|
|
34660
|
-
}
|
|
34661
|
-
console.log(`
|
|
34662
|
-
${domains.length} domain(s)`);
|
|
34663
|
-
}
|
|
34664
|
-
});
|
|
34665
|
-
program2.command("update").description("Update a domain").argument("<id>", "Domain ID").option("--name <name>", "Domain name").option("--registrar <registrar>", "Registrar").option("--status <status>", "Status").option("--registered-at <date>", "Registration date").option("--expires-at <date>", "Expiration date").option("--auto-renew <bool>", "Auto-renew (true/false)").option("--nameservers <ns>", "Comma-separated nameservers").option("--ssl-expires-at <date>", "SSL expiration date").option("--ssl-issuer <issuer>", "SSL issuer").option("--notes <notes>", "Notes").option("--json", "Output as JSON", false).action((id, opts) => {
|
|
34666
|
-
const input = {};
|
|
34667
|
-
if (opts.name !== undefined)
|
|
34668
|
-
input.name = opts.name;
|
|
34669
|
-
if (opts.registrar !== undefined)
|
|
34670
|
-
input.registrar = opts.registrar;
|
|
34671
|
-
if (opts.status !== undefined)
|
|
34672
|
-
input.status = opts.status;
|
|
34673
|
-
if (opts.registeredAt !== undefined)
|
|
34674
|
-
input.registered_at = opts.registeredAt;
|
|
34675
|
-
if (opts.expiresAt !== undefined)
|
|
34676
|
-
input.expires_at = opts.expiresAt;
|
|
34677
|
-
if (opts.autoRenew !== undefined)
|
|
34678
|
-
input.auto_renew = opts.autoRenew === "true";
|
|
34679
|
-
if (opts.nameservers !== undefined)
|
|
34680
|
-
input.nameservers = opts.nameservers.split(",").map((s) => s.trim());
|
|
34681
|
-
if (opts.sslExpiresAt !== undefined)
|
|
34682
|
-
input.ssl_expires_at = opts.sslExpiresAt;
|
|
34683
|
-
if (opts.sslIssuer !== undefined)
|
|
34684
|
-
input.ssl_issuer = opts.sslIssuer;
|
|
34685
|
-
if (opts.notes !== undefined)
|
|
34686
|
-
input.notes = opts.notes;
|
|
34687
|
-
const domain = updateDomain(id, input);
|
|
34688
|
-
if (!domain) {
|
|
34689
|
-
console.error(`Domain '${id}' not found.`);
|
|
34690
|
-
process.exit(1);
|
|
34691
|
-
}
|
|
34692
|
-
if (opts.json) {
|
|
34693
|
-
console.log(JSON.stringify(domain, null, 2));
|
|
34694
|
-
} else {
|
|
34695
|
-
console.log(`Updated: ${domain.name}`);
|
|
34696
|
-
}
|
|
34697
|
-
});
|
|
34698
|
-
program2.command("delete").description("Delete a domain").argument("<id>", "Domain ID").action((id) => {
|
|
34699
|
-
const deleted = deleteDomain(id);
|
|
34700
|
-
if (deleted) {
|
|
34701
|
-
console.log(`Deleted domain ${id}`);
|
|
34702
|
-
} else {
|
|
34703
|
-
console.error(`Domain '${id}' not found.`);
|
|
34704
|
-
process.exit(1);
|
|
34705
|
-
}
|
|
34706
|
-
});
|
|
34707
|
-
program2.command("search").description("Search domains").argument("<query>", "Search term").option("--json", "Output as JSON", false).action((query, opts) => {
|
|
34708
|
-
const results = searchDomains(query);
|
|
34709
|
-
if (opts.json) {
|
|
34710
|
-
console.log(JSON.stringify(results, null, 2));
|
|
34711
|
-
} else {
|
|
34712
|
-
if (results.length === 0) {
|
|
34713
|
-
console.log(`No domains matching "${query}".`);
|
|
34714
|
-
return;
|
|
34715
|
-
}
|
|
34716
|
-
for (const d of results) {
|
|
34717
|
-
console.log(` ${d.name} [${d.status}]`);
|
|
34718
|
-
}
|
|
34719
|
-
}
|
|
34720
|
-
});
|
|
34721
|
-
program2.command("expiring").description("List domains expiring within N days").option("--days <n>", "Number of days ahead", "30").option("--json", "Output as JSON", false).action((opts) => {
|
|
34722
|
-
const days = parseInt(opts.days);
|
|
34723
|
-
const domains = listExpiring(days);
|
|
34724
|
-
if (opts.json) {
|
|
34725
|
-
console.log(JSON.stringify(domains, null, 2));
|
|
34726
|
-
} else {
|
|
34727
|
-
if (domains.length === 0) {
|
|
34728
|
-
console.log(`No domains expiring within ${days} days.`);
|
|
34729
|
-
return;
|
|
34730
|
-
}
|
|
34731
|
-
console.log(`Domains expiring within ${days} days:`);
|
|
34732
|
-
for (const d of domains) {
|
|
34733
|
-
console.log(` ${d.name} \u2014 expires ${d.expires_at}`);
|
|
34734
|
-
}
|
|
34735
|
-
}
|
|
34736
|
-
});
|
|
34737
|
-
program2.command("ssl").description("List domains with SSL expiring within N days").option("--days <n>", "Number of days ahead", "30").option("--json", "Output as JSON", false).action((opts) => {
|
|
34738
|
-
const days = parseInt(opts.days);
|
|
34739
|
-
const domains = listSslExpiring(days);
|
|
34740
|
-
if (opts.json) {
|
|
34741
|
-
console.log(JSON.stringify(domains, null, 2));
|
|
34742
|
-
} else {
|
|
34743
|
-
if (domains.length === 0) {
|
|
34744
|
-
console.log(`No SSL certificates expiring within ${days} days.`);
|
|
34745
|
-
return;
|
|
34746
|
-
}
|
|
34747
|
-
console.log(`SSL certificates expiring within ${days} days:`);
|
|
34748
|
-
for (const d of domains) {
|
|
34749
|
-
console.log(` ${d.name} \u2014 SSL expires ${d.ssl_expires_at} (${d.ssl_issuer || "unknown issuer"})`);
|
|
34750
|
-
}
|
|
34751
|
-
}
|
|
34752
|
-
});
|
|
34753
|
-
program2.command("stats").description("Show domain portfolio statistics").option("--json", "Output as JSON", false).action((opts) => {
|
|
34754
|
-
const stats = getDomainStats();
|
|
34755
|
-
if (opts.json) {
|
|
34756
|
-
console.log(JSON.stringify(stats, null, 2));
|
|
34757
|
-
} else {
|
|
34758
|
-
console.log("Domain Portfolio Stats:");
|
|
34759
|
-
console.log(` Total: ${stats.total}`);
|
|
34760
|
-
console.log(` Active: ${stats.active}`);
|
|
34761
|
-
console.log(` Expired: ${stats.expired}`);
|
|
34762
|
-
console.log(` Transferring: ${stats.transferring}`);
|
|
34763
|
-
console.log(` Redemption: ${stats.redemption}`);
|
|
34764
|
-
console.log(` Auto-renew enabled: ${stats.auto_renew_enabled}`);
|
|
34765
|
-
console.log(` Expiring (30 days): ${stats.expiring_30_days}`);
|
|
34766
|
-
console.log(` SSL expiring (30 days): ${stats.ssl_expiring_30_days}`);
|
|
34767
|
-
}
|
|
34768
|
-
});
|
|
34769
|
-
program2.command("whois").description("Run WHOIS lookup for a domain and update DB record").argument("<name>", "Domain name (e.g. example.com)").option("--json", "Output as JSON", false).action((name, opts) => {
|
|
34770
|
-
try {
|
|
34771
|
-
const result = whoisLookup(name);
|
|
34772
|
-
if (opts.json) {
|
|
34773
|
-
console.log(JSON.stringify(result, null, 2));
|
|
34774
|
-
} else {
|
|
34775
|
-
console.log(`WHOIS for ${result.domain}:`);
|
|
34776
|
-
console.log(` Registrar: ${result.registrar || "unknown"}`);
|
|
34777
|
-
console.log(` Expires: ${result.expires_at || "unknown"}`);
|
|
34778
|
-
if (result.nameservers.length > 0) {
|
|
34779
|
-
console.log(` Nameservers: ${result.nameservers.join(", ")}`);
|
|
34780
|
-
}
|
|
34781
|
-
}
|
|
34782
|
-
} catch (error) {
|
|
34783
|
-
console.error(`WHOIS lookup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
34784
|
-
process.exit(1);
|
|
34785
|
-
}
|
|
34786
|
-
});
|
|
34787
|
-
program2.command("ssl-check").description("Check SSL certificate for a domain and update DB record").argument("<name>", "Domain name (e.g. example.com)").option("--json", "Output as JSON", false).action((name, opts) => {
|
|
34788
|
-
const result = checkSsl(name);
|
|
34789
|
-
if (opts.json) {
|
|
34790
|
-
console.log(JSON.stringify(result, null, 2));
|
|
34791
|
-
} else {
|
|
34792
|
-
if (result.error) {
|
|
34793
|
-
console.error(`SSL check failed: ${result.error}`);
|
|
34794
|
-
process.exit(1);
|
|
34795
|
-
}
|
|
34796
|
-
console.log(`SSL Certificate for ${result.domain}:`);
|
|
34797
|
-
console.log(` Issuer: ${result.issuer || "unknown"}`);
|
|
34798
|
-
console.log(` Expires: ${result.expires_at || "unknown"}`);
|
|
34799
|
-
if (result.subject)
|
|
34800
|
-
console.log(` Subject: ${result.subject}`);
|
|
34801
|
-
}
|
|
34802
|
-
});
|
|
34803
|
-
program2.command("export").description("Export all domains as CSV or JSON").option("--format <format>", "Export format (csv or json)", "json").option("--output <file>", "Write to file instead of stdout").action((opts) => {
|
|
34804
|
-
const format = opts.format === "csv" ? "csv" : "json";
|
|
34805
|
-
const output = exportPortfolio(format);
|
|
34806
|
-
if (opts.output) {
|
|
34807
|
-
writeFileSync2(opts.output, output, "utf-8");
|
|
34808
|
-
console.log(`Exported to ${opts.output}`);
|
|
34809
|
-
} else {
|
|
34810
|
-
console.log(output);
|
|
34811
|
-
}
|
|
34812
|
-
});
|
|
34813
|
-
program2.command("check-all").description("Run WHOIS + SSL + DNS validation on all domains").option("--json", "Output as JSON", false).action((opts) => {
|
|
34814
|
-
const results = checkAllDomains();
|
|
34815
|
-
if (opts.json) {
|
|
34816
|
-
console.log(JSON.stringify(results, null, 2));
|
|
34817
|
-
} else {
|
|
34818
|
-
if (results.length === 0) {
|
|
34819
|
-
console.log("No domains to check.");
|
|
34820
|
-
return;
|
|
34821
|
-
}
|
|
34822
|
-
for (const r of results) {
|
|
34823
|
-
console.log(`
|
|
34824
|
-
${r.domain}:`);
|
|
34825
|
-
if (r.whois) {
|
|
34826
|
-
console.log(` WHOIS: registrar=${r.whois.registrar || "?"}, expires=${r.whois.expires_at || "?"}`);
|
|
34827
|
-
if (r.whois.error)
|
|
34828
|
-
console.log(` Error: ${r.whois.error}`);
|
|
34829
|
-
}
|
|
34830
|
-
if (r.ssl) {
|
|
34831
|
-
console.log(` SSL: issuer=${r.ssl.issuer || "?"}, expires=${r.ssl.expires_at || "?"}`);
|
|
34832
|
-
if (r.ssl.error)
|
|
34833
|
-
console.log(` Error: ${r.ssl.error}`);
|
|
34834
|
-
}
|
|
34835
|
-
if (r.dns_validation) {
|
|
34836
|
-
console.log(` DNS: valid=${r.dns_validation.valid}, issues=${r.dns_validation.issue_count}`);
|
|
34837
|
-
for (const e of r.dns_validation.errors) {
|
|
34838
|
-
console.log(` ${e}`);
|
|
34839
|
-
}
|
|
34840
|
-
}
|
|
34841
|
-
}
|
|
34842
|
-
console.log(`
|
|
34843
|
-
Checked ${results.length} domain(s)`);
|
|
34844
|
-
}
|
|
34845
|
-
});
|
|
34846
|
-
}
|
|
34847
|
-
|
|
34848
|
-
// src/cli/commands/dns.ts
|
|
34849
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
34850
|
-
function registerDnsCommands(program2) {
|
|
34851
|
-
const dnsCmd = program2.command("dns").description("DNS record management");
|
|
34852
|
-
dnsCmd.command("list").description("List DNS records for a domain").argument("<domain-id>", "Domain ID").option("--type <type>", "Filter by record type (A/AAAA/CNAME/MX/TXT/NS/SRV)").option("--json", "Output as JSON", false).action((domainId, opts) => {
|
|
34853
|
-
const records = listDnsRecords(domainId, opts.type);
|
|
34854
|
-
if (opts.json) {
|
|
34855
|
-
console.log(JSON.stringify(records, null, 2));
|
|
34856
|
-
} else {
|
|
34857
|
-
if (records.length === 0) {
|
|
34858
|
-
console.log("No DNS records found.");
|
|
34859
|
-
return;
|
|
34860
|
-
}
|
|
34861
|
-
for (const r of records) {
|
|
34862
|
-
const priority = r.priority !== null ? ` (priority: ${r.priority})` : "";
|
|
34863
|
-
console.log(` ${r.type} ${r.name} ${r.value} TTL:${r.ttl}${priority}`);
|
|
34864
|
-
}
|
|
34865
|
-
}
|
|
34866
|
-
});
|
|
34867
|
-
dnsCmd.command("add").description("Add a DNS record").requiredOption("--domain <id>", "Domain ID").requiredOption("--type <type>", "Record type (A/AAAA/CNAME/MX/TXT/NS/SRV)").requiredOption("--name <name>", "Record name").requiredOption("--value <value>", "Record value").option("--ttl <ttl>", "TTL in seconds", "3600").option("--priority <n>", "Priority (for MX/SRV)").option("--json", "Output as JSON", false).action((opts) => {
|
|
34868
|
-
const record = createDnsRecord({
|
|
34869
|
-
domain_id: opts.domain,
|
|
34870
|
-
type: opts.type,
|
|
34871
|
-
name: opts.name,
|
|
34872
|
-
value: opts.value,
|
|
34873
|
-
ttl: parseInt(opts.ttl),
|
|
34874
|
-
priority: opts.priority ? parseInt(opts.priority) : undefined
|
|
34875
|
-
});
|
|
34876
|
-
if (opts.json) {
|
|
34877
|
-
console.log(JSON.stringify(record, null, 2));
|
|
34878
|
-
} else {
|
|
34879
|
-
console.log(`Created DNS record: ${record.type} ${record.name} -> ${record.value} (${record.id})`);
|
|
34880
|
-
}
|
|
34881
|
-
});
|
|
34882
|
-
dnsCmd.command("update").description("Update a DNS record").argument("<id>", "Record ID").option("--type <type>", "Record type").option("--name <name>", "Record name").option("--value <value>", "Record value").option("--ttl <ttl>", "TTL in seconds").option("--priority <n>", "Priority").option("--json", "Output as JSON", false).action((id, opts) => {
|
|
34883
|
-
const input = {};
|
|
34884
|
-
if (opts.type !== undefined)
|
|
34885
|
-
input.type = opts.type;
|
|
34886
|
-
if (opts.name !== undefined)
|
|
34887
|
-
input.name = opts.name;
|
|
34888
|
-
if (opts.value !== undefined)
|
|
34889
|
-
input.value = opts.value;
|
|
34890
|
-
if (opts.ttl !== undefined)
|
|
34891
|
-
input.ttl = parseInt(opts.ttl);
|
|
34892
|
-
if (opts.priority !== undefined)
|
|
34893
|
-
input.priority = parseInt(opts.priority);
|
|
34894
|
-
const record = updateDnsRecord(id, input);
|
|
34895
|
-
if (!record) {
|
|
34896
|
-
console.error(`DNS record '${id}' not found.`);
|
|
34897
|
-
process.exit(1);
|
|
34898
|
-
}
|
|
34899
|
-
if (opts.json) {
|
|
34900
|
-
console.log(JSON.stringify(record, null, 2));
|
|
34901
|
-
} else {
|
|
34902
|
-
console.log(`Updated DNS record: ${record.type} ${record.name} -> ${record.value}`);
|
|
34903
|
-
}
|
|
34904
|
-
});
|
|
34905
|
-
dnsCmd.command("remove").description("Remove a DNS record").argument("<id>", "Record ID").action((id) => {
|
|
34906
|
-
const deleted = deleteDnsRecord(id);
|
|
34907
|
-
if (deleted) {
|
|
34908
|
-
console.log(`Deleted DNS record ${id}`);
|
|
34909
|
-
} else {
|
|
34910
|
-
console.error(`DNS record '${id}' not found.`);
|
|
34911
|
-
process.exit(1);
|
|
34912
|
-
}
|
|
34913
|
-
});
|
|
34914
|
-
dnsCmd.command("check-propagation").description("Check DNS propagation across multiple servers").argument("<domain>", "Domain name to check").option("--record <type>", "Record type (A/AAAA/CNAME/MX/TXT/NS)", "A").option("--json", "Output as JSON", false).action((domain, opts) => {
|
|
34915
|
-
const result = checkDnsPropagation(domain, opts.record);
|
|
34916
|
-
if (opts.json) {
|
|
34917
|
-
console.log(JSON.stringify(result, null, 2));
|
|
34918
|
-
} else {
|
|
34919
|
-
console.log(`DNS Propagation for ${result.domain} (${result.record_type}):`);
|
|
34920
|
-
console.log(` Consistent: ${result.consistent ? "yes" : "NO"}`);
|
|
34921
|
-
for (const s of result.servers) {
|
|
34922
|
-
const values = s.values.length > 0 ? s.values.join(", ") : "(empty)";
|
|
34923
|
-
const status = s.status === "error" ? ` [ERROR: ${s.error}]` : "";
|
|
34924
|
-
console.log(` ${s.name} (${s.server}): ${values}${status}`);
|
|
34925
|
-
}
|
|
34926
|
-
}
|
|
34927
|
-
});
|
|
34928
|
-
dnsCmd.command("export").description("Export DNS records as BIND zone file").argument("<domain-id>", "Domain ID").option("--output <file>", "Write to file instead of stdout").action((domainId, opts) => {
|
|
34929
|
-
const zone = exportZoneFile(domainId);
|
|
34930
|
-
if (!zone) {
|
|
34931
|
-
console.error(`Domain '${domainId}' not found.`);
|
|
34932
|
-
process.exit(1);
|
|
34933
|
-
}
|
|
34934
|
-
if (opts.output) {
|
|
34935
|
-
writeFileSync3(opts.output, zone, "utf-8");
|
|
34936
|
-
console.log(`Exported zone file to ${opts.output}`);
|
|
34937
|
-
} else {
|
|
34938
|
-
console.log(zone);
|
|
34939
|
-
}
|
|
34940
|
-
});
|
|
34941
|
-
dnsCmd.command("import").description("Import DNS records from a BIND zone file").argument("<domain-id>", "Domain ID").requiredOption("--file <path>", "Path to zone file").option("--json", "Output as JSON", false).action((domainId, opts) => {
|
|
34942
|
-
let content;
|
|
34943
|
-
try {
|
|
34944
|
-
content = readFileSync2(opts.file, "utf-8");
|
|
34945
|
-
} catch {
|
|
34946
|
-
console.error(`Could not read file: ${opts.file}`);
|
|
34947
|
-
process.exit(1);
|
|
34948
|
-
}
|
|
34949
|
-
const result = importZoneFile(domainId, content);
|
|
34950
|
-
if (!result) {
|
|
34951
|
-
console.error(`Domain '${domainId}' not found.`);
|
|
34952
|
-
process.exit(1);
|
|
34953
|
-
}
|
|
34954
|
-
if (opts.json) {
|
|
34955
|
-
console.log(JSON.stringify(result, null, 2));
|
|
34956
|
-
} else {
|
|
34957
|
-
console.log(`Imported ${result.imported} record(s), skipped ${result.skipped}`);
|
|
34958
|
-
if (result.errors.length > 0) {
|
|
34959
|
-
console.log("Errors:");
|
|
34960
|
-
for (const e of result.errors) {
|
|
34961
|
-
console.log(` - ${e}`);
|
|
34962
|
-
}
|
|
34963
|
-
}
|
|
34964
|
-
}
|
|
34965
|
-
});
|
|
34966
|
-
dnsCmd.command("discover-subdomains").description("Discover subdomains via certificate transparency logs (crt.sh)").argument("<domain>", "Domain name").option("--json", "Output as JSON", false).action(async (domain, opts) => {
|
|
34967
|
-
const result = await discoverSubdomains(domain);
|
|
34968
|
-
if (opts.json) {
|
|
34969
|
-
console.log(JSON.stringify(result, null, 2));
|
|
34970
|
-
} else {
|
|
34971
|
-
if (result.error) {
|
|
34972
|
-
console.error(`Discovery failed: ${result.error}`);
|
|
34973
|
-
process.exit(1);
|
|
34974
|
-
}
|
|
34975
|
-
if (result.subdomains.length === 0) {
|
|
34976
|
-
console.log(`No subdomains found for ${domain}.`);
|
|
34977
|
-
return;
|
|
34978
|
-
}
|
|
34979
|
-
console.log(`Subdomains for ${domain} (source: ${result.source}):`);
|
|
34980
|
-
for (const s of result.subdomains) {
|
|
34981
|
-
console.log(` ${s}`);
|
|
34982
|
-
}
|
|
34983
|
-
console.log(`
|
|
34984
|
-
${result.subdomains.length} subdomain(s) found`);
|
|
34985
|
-
}
|
|
34986
|
-
});
|
|
34987
|
-
dnsCmd.command("validate").description("Validate DNS records for common issues").argument("<domain-id>", "Domain ID").option("--json", "Output as JSON", false).action((domainId, opts) => {
|
|
34988
|
-
const result = validateDns(domainId);
|
|
34989
|
-
if (!result) {
|
|
34990
|
-
console.error(`Domain '${domainId}' not found.`);
|
|
34991
|
-
process.exit(1);
|
|
34992
|
-
}
|
|
34993
|
-
if (opts.json) {
|
|
34994
|
-
console.log(JSON.stringify(result, null, 2));
|
|
34995
|
-
} else {
|
|
34996
|
-
console.log(`DNS Validation for ${result.domain_name}:`);
|
|
34997
|
-
console.log(` Valid: ${result.valid ? "yes" : "NO"}`);
|
|
34998
|
-
if (result.issues.length === 0) {
|
|
34999
|
-
console.log(" No issues found.");
|
|
35000
|
-
} else {
|
|
35001
|
-
for (const issue of result.issues) {
|
|
35002
|
-
const prefix = issue.type === "error" ? "ERROR" : "WARN";
|
|
35003
|
-
console.log(` [${prefix}] ${issue.message}`);
|
|
35004
|
-
}
|
|
35005
|
-
}
|
|
35006
|
-
}
|
|
35007
|
-
});
|
|
35008
|
-
}
|
|
35009
|
-
|
|
35010
|
-
// src/cli/commands/alerts.ts
|
|
35011
|
-
function registerAlertCommands(program2) {
|
|
35012
|
-
const alertCmd = program2.command("alert").description("Alert management");
|
|
35013
|
-
alertCmd.command("set").description("Set an alert for a domain").requiredOption("--domain <id>", "Domain ID").requiredOption("--type <type>", "Alert type (expiry/ssl_expiry/dns_change)").option("--days-before <n>", "Trigger N days before").option("--json", "Output as JSON", false).action((opts) => {
|
|
35014
|
-
const alert = createAlert({
|
|
35015
|
-
domain_id: opts.domain,
|
|
35016
|
-
type: opts.type,
|
|
35017
|
-
trigger_days_before: opts.daysBefore ? parseInt(opts.daysBefore) : undefined
|
|
35018
|
-
});
|
|
35019
|
-
if (opts.json) {
|
|
35020
|
-
console.log(JSON.stringify(alert, null, 2));
|
|
35021
|
-
} else {
|
|
35022
|
-
const daysBefore = alert.trigger_days_before ? ` (${alert.trigger_days_before} days before)` : "";
|
|
35023
|
-
console.log(`Created alert: ${alert.type}${daysBefore} for domain ${alert.domain_id} (${alert.id})`);
|
|
35024
|
-
}
|
|
35025
|
-
});
|
|
35026
|
-
alertCmd.command("list").description("List alerts for a domain").argument("<domain-id>", "Domain ID").option("--json", "Output as JSON", false).action((domainId, opts) => {
|
|
35027
|
-
const alerts = listAlerts(domainId);
|
|
35028
|
-
if (opts.json) {
|
|
35029
|
-
console.log(JSON.stringify(alerts, null, 2));
|
|
35030
|
-
} else {
|
|
35031
|
-
if (alerts.length === 0) {
|
|
35032
|
-
console.log("No alerts set.");
|
|
35033
|
-
return;
|
|
35034
|
-
}
|
|
35035
|
-
for (const a of alerts) {
|
|
35036
|
-
const daysBefore = a.trigger_days_before ? ` (${a.trigger_days_before} days before)` : "";
|
|
35037
|
-
const sent = a.sent_at ? ` \u2014 sent ${a.sent_at}` : "";
|
|
35038
|
-
console.log(` ${a.type}${daysBefore}${sent}`);
|
|
35039
|
-
}
|
|
35040
|
-
}
|
|
35041
|
-
});
|
|
35042
|
-
alertCmd.command("remove").description("Remove an alert").argument("<id>", "Alert ID").action((id) => {
|
|
35043
|
-
const deleted = deleteAlert(id);
|
|
35044
|
-
if (deleted) {
|
|
35045
|
-
console.log(`Deleted alert ${id}`);
|
|
35046
|
-
} else {
|
|
35047
|
-
console.error(`Alert '${id}' not found.`);
|
|
35048
|
-
process.exit(1);
|
|
35049
|
-
}
|
|
35050
|
-
});
|
|
35051
|
-
}
|
|
35052
|
-
|
|
35053
34584
|
// src/lib/namecheap.ts
|
|
35054
34585
|
function getConfig() {
|
|
35055
34586
|
const apiKey = process.env["NAMECHEAP_API_KEY"];
|
|
@@ -42156,6 +41687,368 @@ function createRoute53Provider(config) {
|
|
|
42156
41687
|
};
|
|
42157
41688
|
}
|
|
42158
41689
|
|
|
41690
|
+
// src/lib/cloudflare.ts
|
|
41691
|
+
function getConfig3() {
|
|
41692
|
+
return {
|
|
41693
|
+
apiToken: process.env["CLOUDFLARE_API_TOKEN"],
|
|
41694
|
+
accountId: process.env["CLOUDFLARE_ACCOUNT_ID"]
|
|
41695
|
+
};
|
|
41696
|
+
}
|
|
41697
|
+
function checkCredentials2(cfg) {
|
|
41698
|
+
if (!cfg.apiToken) {
|
|
41699
|
+
throw new Error("Cloudflare credentials not configured. Set CLOUDFLARE_API_TOKEN environment variable.");
|
|
41700
|
+
}
|
|
41701
|
+
}
|
|
41702
|
+
var CF_BASE = "https://api.cloudflare.com/client/v4";
|
|
41703
|
+
async function cfFetch(path, opts = {}) {
|
|
41704
|
+
const cfg = opts.config ?? getConfig3();
|
|
41705
|
+
checkCredentials2(cfg);
|
|
41706
|
+
const res = await fetch(`${CF_BASE}${path}`, {
|
|
41707
|
+
method: opts.method ?? "GET",
|
|
41708
|
+
headers: {
|
|
41709
|
+
Authorization: `Bearer ${cfg.apiToken}`,
|
|
41710
|
+
"Content-Type": "application/json"
|
|
41711
|
+
},
|
|
41712
|
+
body: opts.body ? JSON.stringify(opts.body) : undefined
|
|
41713
|
+
});
|
|
41714
|
+
const json = await res.json();
|
|
41715
|
+
if (!json.success) {
|
|
41716
|
+
const msg = json.errors?.[0]?.message ?? `Cloudflare API error (${res.status})`;
|
|
41717
|
+
throw new Error(msg);
|
|
41718
|
+
}
|
|
41719
|
+
return json.result;
|
|
41720
|
+
}
|
|
41721
|
+
async function listZones(config) {
|
|
41722
|
+
const zones = [];
|
|
41723
|
+
let page = 1;
|
|
41724
|
+
while (true) {
|
|
41725
|
+
const result = await cfFetch(`/zones?per_page=50&page=${page}`, { config });
|
|
41726
|
+
if (!result || result.length === 0)
|
|
41727
|
+
break;
|
|
41728
|
+
for (const z2 of result) {
|
|
41729
|
+
zones.push({ id: z2.id, name: z2.name, status: z2.status, nameservers: z2.name_servers, original_nameservers: z2.original_name_servers });
|
|
41730
|
+
}
|
|
41731
|
+
if (result.length < 50)
|
|
41732
|
+
break;
|
|
41733
|
+
page++;
|
|
41734
|
+
}
|
|
41735
|
+
return zones;
|
|
41736
|
+
}
|
|
41737
|
+
async function getZone(domain, config) {
|
|
41738
|
+
const result = await cfFetch(`/zones?name=${encodeURIComponent(domain)}`, { config });
|
|
41739
|
+
if (!result || result.length === 0)
|
|
41740
|
+
return null;
|
|
41741
|
+
const z2 = result[0];
|
|
41742
|
+
return { id: z2.id, name: z2.name, status: z2.status, nameservers: z2.name_servers, original_nameservers: z2.original_name_servers };
|
|
41743
|
+
}
|
|
41744
|
+
async function createZone(domain, config) {
|
|
41745
|
+
const cfg = config ?? getConfig3();
|
|
41746
|
+
if (!cfg.accountId) {
|
|
41747
|
+
throw new Error("CLOUDFLARE_ACCOUNT_ID is required to create a zone.");
|
|
41748
|
+
}
|
|
41749
|
+
const result = await cfFetch("/zones", { method: "POST", body: { name: domain, account: { id: cfg.accountId }, jump_start: false }, config: cfg });
|
|
41750
|
+
return { id: result.id, name: result.name, status: result.status, nameservers: result.name_servers };
|
|
41751
|
+
}
|
|
41752
|
+
async function deleteZone(zoneId, config) {
|
|
41753
|
+
await cfFetch(`/zones/${zoneId}`, { method: "DELETE", config });
|
|
41754
|
+
}
|
|
41755
|
+
async function listRecords2(zoneId, config) {
|
|
41756
|
+
const records = [];
|
|
41757
|
+
let page = 1;
|
|
41758
|
+
while (true) {
|
|
41759
|
+
const result = await cfFetch(`/zones/${zoneId}/dns_records?per_page=100&page=${page}`, { config });
|
|
41760
|
+
if (!result || result.length === 0)
|
|
41761
|
+
break;
|
|
41762
|
+
for (const r3 of result) {
|
|
41763
|
+
records.push({ id: r3.id, type: r3.type, name: r3.name, content: r3.content, ttl: r3.ttl, priority: r3.priority, proxied: r3.proxied });
|
|
41764
|
+
}
|
|
41765
|
+
if (result.length < 100)
|
|
41766
|
+
break;
|
|
41767
|
+
page++;
|
|
41768
|
+
}
|
|
41769
|
+
return records;
|
|
41770
|
+
}
|
|
41771
|
+
async function upsertRecord2(zoneId, record, config) {
|
|
41772
|
+
const existing = await cfFetch(`/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(record.name)}`, { config });
|
|
41773
|
+
const body = {
|
|
41774
|
+
type: record.type,
|
|
41775
|
+
name: record.name,
|
|
41776
|
+
content: record.content,
|
|
41777
|
+
ttl: record.ttl ?? 1,
|
|
41778
|
+
priority: record.priority,
|
|
41779
|
+
proxied: record.proxied ?? false
|
|
41780
|
+
};
|
|
41781
|
+
if (existing && existing.length > 0) {
|
|
41782
|
+
await cfFetch(`/zones/${zoneId}/dns_records/${existing[0].id}`, { method: "PUT", body, config });
|
|
41783
|
+
} else {
|
|
41784
|
+
await cfFetch(`/zones/${zoneId}/dns_records`, { method: "POST", body, config });
|
|
41785
|
+
}
|
|
41786
|
+
}
|
|
41787
|
+
function createCloudflareProvider(config) {
|
|
41788
|
+
const cfg = config ?? getConfig3();
|
|
41789
|
+
return {
|
|
41790
|
+
name: "cloudflare",
|
|
41791
|
+
async getDnsRecords(domain) {
|
|
41792
|
+
const zone = await getZone(domain, cfg);
|
|
41793
|
+
if (!zone)
|
|
41794
|
+
return [];
|
|
41795
|
+
const records = await listRecords2(zone.id, cfg);
|
|
41796
|
+
return records.map((r3) => ({
|
|
41797
|
+
type: r3.type,
|
|
41798
|
+
name: r3.name,
|
|
41799
|
+
value: r3.content,
|
|
41800
|
+
ttl: r3.ttl === 1 ? 0 : r3.ttl,
|
|
41801
|
+
priority: r3.priority
|
|
41802
|
+
}));
|
|
41803
|
+
},
|
|
41804
|
+
async setDnsRecords(domain, records) {
|
|
41805
|
+
const zone = await getZone(domain, cfg);
|
|
41806
|
+
if (!zone)
|
|
41807
|
+
throw new Error(`No Cloudflare zone found for ${domain}`);
|
|
41808
|
+
for (const r3 of records) {
|
|
41809
|
+
await upsertRecord2(zone.id, { type: r3.type, name: r3.name, content: r3.value, ttl: r3.ttl || 1, priority: r3.priority }, cfg);
|
|
41810
|
+
}
|
|
41811
|
+
return true;
|
|
41812
|
+
}
|
|
41813
|
+
};
|
|
41814
|
+
}
|
|
41815
|
+
|
|
41816
|
+
// src/lib/brandsight.ts
|
|
41817
|
+
class BrandsightApiError extends Error {
|
|
41818
|
+
statusCode;
|
|
41819
|
+
responseBody;
|
|
41820
|
+
constructor(message, statusCode, responseBody) {
|
|
41821
|
+
super(message);
|
|
41822
|
+
this.statusCode = statusCode;
|
|
41823
|
+
this.responseBody = responseBody;
|
|
41824
|
+
this.name = "BrandsightApiError";
|
|
41825
|
+
}
|
|
41826
|
+
}
|
|
41827
|
+
var _fetchFn = null;
|
|
41828
|
+
function getApiKey() {
|
|
41829
|
+
const key = process.env["BRANDSIGHT_API_KEY"];
|
|
41830
|
+
if (!key) {
|
|
41831
|
+
throw new BrandsightApiError("BRANDSIGHT_API_KEY environment variable is not set");
|
|
41832
|
+
}
|
|
41833
|
+
return key;
|
|
41834
|
+
}
|
|
41835
|
+
function getConfig4() {
|
|
41836
|
+
return {
|
|
41837
|
+
apiKey: process.env["BRANDSIGHT_API_KEY"] ?? "",
|
|
41838
|
+
accountId: process.env["BRANDSIGHT_ACCOUNT_ID"]
|
|
41839
|
+
};
|
|
41840
|
+
}
|
|
41841
|
+
var BRANDSIGHT_BASE = "https://api.brandsight.com/v1";
|
|
41842
|
+
async function apiRequest3(method, path, apiKey, body, baseUrl = BRANDSIGHT_BASE) {
|
|
41843
|
+
const fetchFn = _fetchFn || globalThis.fetch;
|
|
41844
|
+
const url = `${baseUrl}${path}`;
|
|
41845
|
+
const headers = {
|
|
41846
|
+
Authorization: `Bearer ${apiKey}`,
|
|
41847
|
+
"Content-Type": "application/json",
|
|
41848
|
+
Accept: "application/json",
|
|
41849
|
+
"User-Agent": USER_AGENT
|
|
41850
|
+
};
|
|
41851
|
+
try {
|
|
41852
|
+
const response = await fetchFn(url, {
|
|
41853
|
+
method,
|
|
41854
|
+
headers,
|
|
41855
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
41856
|
+
signal: AbortSignal.timeout(15000)
|
|
41857
|
+
});
|
|
41858
|
+
if (!response.ok) {
|
|
41859
|
+
throw new BrandsightApiError(`Brandsight API ${method} ${path} failed with status ${response.status}`, response.status, await response.text());
|
|
41860
|
+
}
|
|
41861
|
+
const data = await response.json();
|
|
41862
|
+
return { data, stub: false };
|
|
41863
|
+
} catch (error) {
|
|
41864
|
+
if (error instanceof BrandsightApiError)
|
|
41865
|
+
throw error;
|
|
41866
|
+
return { data: null, stub: true };
|
|
41867
|
+
}
|
|
41868
|
+
}
|
|
41869
|
+
async function apiGet(path, apiKey, baseUrl = BRANDSIGHT_BASE) {
|
|
41870
|
+
const fetchFn = _fetchFn || globalThis.fetch;
|
|
41871
|
+
const url = `${baseUrl}${path}`;
|
|
41872
|
+
const headers = {
|
|
41873
|
+
Authorization: `Bearer ${apiKey}`,
|
|
41874
|
+
"Content-Type": "application/json",
|
|
41875
|
+
Accept: "application/json",
|
|
41876
|
+
"User-Agent": USER_AGENT
|
|
41877
|
+
};
|
|
41878
|
+
try {
|
|
41879
|
+
const response = await fetchFn(url, {
|
|
41880
|
+
method: "GET",
|
|
41881
|
+
headers,
|
|
41882
|
+
signal: AbortSignal.timeout(15000)
|
|
41883
|
+
});
|
|
41884
|
+
if (!response.ok) {
|
|
41885
|
+
throw new BrandsightApiError(`Brandsight API GET ${path} failed with status ${response.status}`, response.status, await response.text());
|
|
41886
|
+
}
|
|
41887
|
+
const data = await response.json();
|
|
41888
|
+
return { data, stub: false };
|
|
41889
|
+
} catch (error) {
|
|
41890
|
+
if (error instanceof BrandsightApiError)
|
|
41891
|
+
throw error;
|
|
41892
|
+
return { data: null, stub: true };
|
|
41893
|
+
}
|
|
41894
|
+
}
|
|
41895
|
+
function generateStubAlerts(brandName) {
|
|
41896
|
+
const now = new Date().toISOString();
|
|
41897
|
+
return [
|
|
41898
|
+
{ domain: `${brandName}-deals.com`, type: "keyword", registered_at: now },
|
|
41899
|
+
{ domain: `${brandName.replace(/a/gi, "4").replace(/e/gi, "3")}.com`, type: "homoglyph", registered_at: now },
|
|
41900
|
+
{ domain: `${brandName}s.com`, type: "typosquat", registered_at: now }
|
|
41901
|
+
];
|
|
41902
|
+
}
|
|
41903
|
+
function generateStubSimilarDomains(domain) {
|
|
41904
|
+
const base = domain.replace(/\.[^.]+$/, "");
|
|
41905
|
+
const tld = domain.slice(base.length);
|
|
41906
|
+
return [
|
|
41907
|
+
`${base}-online${tld}`,
|
|
41908
|
+
`${base}s${tld}`,
|
|
41909
|
+
`${base.replace(/a/gi, "4")}${tld}`,
|
|
41910
|
+
`${base}-app${tld}`,
|
|
41911
|
+
`get${base}${tld}`
|
|
41912
|
+
];
|
|
41913
|
+
}
|
|
41914
|
+
function generateStubThreatAssessment(domain) {
|
|
41915
|
+
return {
|
|
41916
|
+
domain,
|
|
41917
|
+
risk_level: "low",
|
|
41918
|
+
threats: [],
|
|
41919
|
+
recommendation: "No immediate threats detected. Continue routine monitoring."
|
|
41920
|
+
};
|
|
41921
|
+
}
|
|
41922
|
+
async function monitorBrand(brandName) {
|
|
41923
|
+
const apiKey = getApiKey();
|
|
41924
|
+
const result = await apiGet(`/brands/${encodeURIComponent(brandName)}/monitor`, apiKey);
|
|
41925
|
+
if (result.stub) {
|
|
41926
|
+
return { brand: brandName, alerts: generateStubAlerts(brandName), stub: true };
|
|
41927
|
+
}
|
|
41928
|
+
return { brand: brandName, alerts: result.data.alerts, stub: false };
|
|
41929
|
+
}
|
|
41930
|
+
async function getSimilarDomains(domain) {
|
|
41931
|
+
const apiKey = getApiKey();
|
|
41932
|
+
const result = await apiGet(`/domains/${encodeURIComponent(domain)}/similar`, apiKey);
|
|
41933
|
+
if (result.stub) {
|
|
41934
|
+
return { domain, similar: generateStubSimilarDomains(domain), stub: true };
|
|
41935
|
+
}
|
|
41936
|
+
return { domain, similar: result.data.similar, stub: false };
|
|
41937
|
+
}
|
|
41938
|
+
async function getThreatAssessment(domain) {
|
|
41939
|
+
const apiKey = getApiKey();
|
|
41940
|
+
const result = await apiGet(`/domains/${encodeURIComponent(domain)}/threats`, apiKey);
|
|
41941
|
+
if (result.stub) {
|
|
41942
|
+
return { ...generateStubThreatAssessment(domain), stub: true };
|
|
41943
|
+
}
|
|
41944
|
+
return { ...result.data, stub: false };
|
|
41945
|
+
}
|
|
41946
|
+
async function listDomains2(config) {
|
|
41947
|
+
const cfg = config ?? getConfig4();
|
|
41948
|
+
const result = await apiGet("/portfolio/domains", cfg.apiKey);
|
|
41949
|
+
if (result.stub)
|
|
41950
|
+
return [];
|
|
41951
|
+
return result.data.domains ?? [];
|
|
41952
|
+
}
|
|
41953
|
+
async function getDomainInfo3(domain, config) {
|
|
41954
|
+
const cfg = config ?? getConfig4();
|
|
41955
|
+
const result = await apiGet(`/portfolio/domains/${encodeURIComponent(domain)}`, cfg.apiKey);
|
|
41956
|
+
if (result.stub)
|
|
41957
|
+
return null;
|
|
41958
|
+
return result.data;
|
|
41959
|
+
}
|
|
41960
|
+
async function checkAvailability4(domain, config) {
|
|
41961
|
+
const cfg = config ?? getConfig4();
|
|
41962
|
+
const result = await apiGet(`/domains/check?domain=${encodeURIComponent(domain)}`, cfg.apiKey);
|
|
41963
|
+
if (result.stub)
|
|
41964
|
+
return { domain, available: false };
|
|
41965
|
+
return result.data;
|
|
41966
|
+
}
|
|
41967
|
+
async function renewDomain3(domain, years = 1, config) {
|
|
41968
|
+
const cfg = config ?? getConfig4();
|
|
41969
|
+
const result = await apiRequest3("POST", `/portfolio/domains/${encodeURIComponent(domain)}/renew`, cfg.apiKey, { years });
|
|
41970
|
+
if (result.stub)
|
|
41971
|
+
return { success: false };
|
|
41972
|
+
return { success: true, orderId: result.data.orderId };
|
|
41973
|
+
}
|
|
41974
|
+
async function syncToLocalDb3(dbFns, config) {
|
|
41975
|
+
const domains = await listDomains2(config);
|
|
41976
|
+
let synced = 0, created = 0, updated = 0;
|
|
41977
|
+
const errors = [];
|
|
41978
|
+
for (const d3 of domains) {
|
|
41979
|
+
try {
|
|
41980
|
+
const existing = dbFns.getDomainByName(d3.domain);
|
|
41981
|
+
if (existing) {
|
|
41982
|
+
dbFns.updateDomain(existing.id, {
|
|
41983
|
+
registrar: "Brandsight",
|
|
41984
|
+
expires_at: d3.expires || undefined,
|
|
41985
|
+
auto_renew: d3.auto_renew,
|
|
41986
|
+
status: "active",
|
|
41987
|
+
nameservers: d3.nameservers
|
|
41988
|
+
});
|
|
41989
|
+
updated++;
|
|
41990
|
+
} else {
|
|
41991
|
+
dbFns.createDomain({
|
|
41992
|
+
name: d3.domain,
|
|
41993
|
+
registrar: "Brandsight",
|
|
41994
|
+
expires_at: d3.expires || undefined,
|
|
41995
|
+
auto_renew: d3.auto_renew,
|
|
41996
|
+
status: "active",
|
|
41997
|
+
nameservers: d3.nameservers
|
|
41998
|
+
});
|
|
41999
|
+
created++;
|
|
42000
|
+
}
|
|
42001
|
+
synced++;
|
|
42002
|
+
} catch (err) {
|
|
42003
|
+
errors.push(`${d3.domain}: ${err instanceof Error ? err.message : String(err)}`);
|
|
42004
|
+
}
|
|
42005
|
+
}
|
|
42006
|
+
return { synced, created, updated, errors };
|
|
42007
|
+
}
|
|
42008
|
+
function createBrandsightProvider(config) {
|
|
42009
|
+
const cfg = config ?? getConfig4();
|
|
42010
|
+
return {
|
|
42011
|
+
name: "brandsight",
|
|
42012
|
+
async listDomains() {
|
|
42013
|
+
const domains = await listDomains2(cfg);
|
|
42014
|
+
return domains.map((d3) => ({
|
|
42015
|
+
domain: d3.domain,
|
|
42016
|
+
registrar: "Brandsight",
|
|
42017
|
+
created: "",
|
|
42018
|
+
expires: d3.expires,
|
|
42019
|
+
nameservers: d3.nameservers,
|
|
42020
|
+
status: d3.status === "ACTIVE" ? "active" : d3.status.toLowerCase(),
|
|
42021
|
+
auto_renew: d3.auto_renew
|
|
42022
|
+
}));
|
|
42023
|
+
},
|
|
42024
|
+
async getDomainInfo(domain) {
|
|
42025
|
+
const d3 = await getDomainInfo3(domain, cfg);
|
|
42026
|
+
if (!d3)
|
|
42027
|
+
throw new Error(`Domain not found in Brandsight: ${domain}`);
|
|
42028
|
+
return {
|
|
42029
|
+
domain: d3.domain,
|
|
42030
|
+
registrar: "Brandsight",
|
|
42031
|
+
created: "",
|
|
42032
|
+
expires: d3.expires,
|
|
42033
|
+
nameservers: d3.nameservers,
|
|
42034
|
+
status: d3.status === "ACTIVE" ? "active" : d3.status.toLowerCase(),
|
|
42035
|
+
auto_renew: d3.auto_renew
|
|
42036
|
+
};
|
|
42037
|
+
},
|
|
42038
|
+
async renewDomain(domain) {
|
|
42039
|
+
const result = await renewDomain3(domain, 1, cfg);
|
|
42040
|
+
return { domain, success: result.success, orderId: result.orderId };
|
|
42041
|
+
},
|
|
42042
|
+
async checkAvailability(domain) {
|
|
42043
|
+
const result = await checkAvailability4(domain, cfg);
|
|
42044
|
+
return { domain: result.domain, available: result.available };
|
|
42045
|
+
},
|
|
42046
|
+
async syncToLocalDb(dbFns) {
|
|
42047
|
+
return syncToLocalDb3(dbFns, cfg);
|
|
42048
|
+
}
|
|
42049
|
+
};
|
|
42050
|
+
}
|
|
42051
|
+
|
|
42159
42052
|
// src/lib/registrar.ts
|
|
42160
42053
|
function createNamecheapProvider() {
|
|
42161
42054
|
return {
|
|
@@ -42308,422 +42201,1312 @@ function createGoDaddyProvider() {
|
|
|
42308
42201
|
}
|
|
42309
42202
|
};
|
|
42310
42203
|
}
|
|
42311
|
-
|
|
42312
|
-
|
|
42313
|
-
|
|
42314
|
-
return createNamecheapProvider();
|
|
42315
|
-
case "godaddy":
|
|
42316
|
-
return createGoDaddyProvider();
|
|
42317
|
-
case "route53":
|
|
42318
|
-
return createRoute53Provider();
|
|
42319
|
-
default:
|
|
42320
|
-
throw new Error(`Unknown registrar provider: ${name}`);
|
|
42321
|
-
}
|
|
42322
|
-
}
|
|
42323
|
-
function getAvailableProviders() {
|
|
42324
|
-
return [
|
|
42325
|
-
{
|
|
42204
|
+
var providerRegistry = new Map([
|
|
42205
|
+
["namecheap", {
|
|
42206
|
+
info: {
|
|
42326
42207
|
name: "namecheap",
|
|
42327
|
-
|
|
42208
|
+
type: "full",
|
|
42209
|
+
configured: false,
|
|
42328
42210
|
envVars: ["NAMECHEAP_API_KEY", "NAMECHEAP_USERNAME", "NAMECHEAP_CLIENT_IP"]
|
|
42329
42211
|
},
|
|
42330
|
-
|
|
42212
|
+
createRegistrar: createNamecheapProvider,
|
|
42213
|
+
createDns: createNamecheapProvider
|
|
42214
|
+
}],
|
|
42215
|
+
["godaddy", {
|
|
42216
|
+
info: {
|
|
42331
42217
|
name: "godaddy",
|
|
42332
|
-
|
|
42218
|
+
type: "full",
|
|
42219
|
+
configured: false,
|
|
42333
42220
|
envVars: ["GODADDY_API_KEY", "GODADDY_API_SECRET"]
|
|
42334
42221
|
},
|
|
42335
|
-
|
|
42222
|
+
createRegistrar: createGoDaddyProvider,
|
|
42223
|
+
createDns: createGoDaddyProvider
|
|
42224
|
+
}],
|
|
42225
|
+
["route53", {
|
|
42226
|
+
info: {
|
|
42336
42227
|
name: "route53",
|
|
42337
|
-
|
|
42228
|
+
type: "full",
|
|
42229
|
+
configured: false,
|
|
42338
42230
|
envVars: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"]
|
|
42339
42231
|
},
|
|
42340
|
-
|
|
42232
|
+
createRegistrar: () => createRoute53Provider(),
|
|
42233
|
+
createDns: () => createRoute53Provider()
|
|
42234
|
+
}],
|
|
42235
|
+
["cloudflare", {
|
|
42236
|
+
info: {
|
|
42237
|
+
name: "cloudflare",
|
|
42238
|
+
type: "dns",
|
|
42239
|
+
configured: false,
|
|
42240
|
+
envVars: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"]
|
|
42241
|
+
},
|
|
42242
|
+
createDns: createCloudflareProvider
|
|
42243
|
+
}],
|
|
42244
|
+
["brandsight", {
|
|
42245
|
+
info: {
|
|
42341
42246
|
name: "brandsight",
|
|
42342
|
-
|
|
42343
|
-
|
|
42344
|
-
|
|
42345
|
-
|
|
42247
|
+
type: "registrar",
|
|
42248
|
+
configured: false,
|
|
42249
|
+
envVars: ["BRANDSIGHT_API_KEY", "BRANDSIGHT_ACCOUNT_ID"]
|
|
42250
|
+
},
|
|
42251
|
+
createRegistrar: createBrandsightProvider
|
|
42252
|
+
}]
|
|
42253
|
+
]);
|
|
42254
|
+
function isConfigured(envVars) {
|
|
42255
|
+
return envVars.slice(0, 2).every((v3) => !!process.env[v3]);
|
|
42346
42256
|
}
|
|
42347
|
-
|
|
42348
|
-
|
|
42349
|
-
|
|
42350
|
-
|
|
42351
|
-
|
|
42352
|
-
|
|
42353
|
-
|
|
42354
|
-
|
|
42355
|
-
|
|
42356
|
-
|
|
42357
|
-
|
|
42358
|
-
|
|
42359
|
-
|
|
42360
|
-
|
|
42361
|
-
|
|
42362
|
-
|
|
42363
|
-
|
|
42364
|
-
result.providers.push({
|
|
42365
|
-
name: info.name,
|
|
42366
|
-
result: { synced: 0, created: 0, updated: 0, errors: [msg] }
|
|
42367
|
-
});
|
|
42368
|
-
}
|
|
42369
|
-
}
|
|
42370
|
-
return result;
|
|
42257
|
+
function getAvailableProviders() {
|
|
42258
|
+
return Array.from(providerRegistry.values()).map((e3) => ({
|
|
42259
|
+
...e3.info,
|
|
42260
|
+
configured: isConfigured(e3.info.envVars)
|
|
42261
|
+
}));
|
|
42262
|
+
}
|
|
42263
|
+
function getRegistrarProvider(name) {
|
|
42264
|
+
const entry = providerRegistry.get(name);
|
|
42265
|
+
if (!entry?.createRegistrar)
|
|
42266
|
+
throw new Error(`No registrar provider: ${name}`);
|
|
42267
|
+
return entry.createRegistrar();
|
|
42268
|
+
}
|
|
42269
|
+
function getDnsProvider(name) {
|
|
42270
|
+
const entry = providerRegistry.get(name);
|
|
42271
|
+
if (!entry?.createDns)
|
|
42272
|
+
throw new Error(`No DNS provider: ${name}`);
|
|
42273
|
+
return entry.createDns();
|
|
42371
42274
|
}
|
|
42372
42275
|
function autoDetectRegistrar(domain, getDomainByName2) {
|
|
42373
42276
|
const dbDomain = getDomainByName2(domain);
|
|
42374
|
-
if (!dbDomain
|
|
42277
|
+
if (!dbDomain?.registrar)
|
|
42375
42278
|
return null;
|
|
42376
|
-
const
|
|
42377
|
-
if (
|
|
42279
|
+
const r3 = dbDomain.registrar.toLowerCase();
|
|
42280
|
+
if (r3.includes("namecheap"))
|
|
42378
42281
|
return "namecheap";
|
|
42379
|
-
if (
|
|
42282
|
+
if (r3.includes("godaddy"))
|
|
42380
42283
|
return "godaddy";
|
|
42381
|
-
if (
|
|
42284
|
+
if (r3.includes("route 53") || r3.includes("route53") || r3.includes("aws"))
|
|
42382
42285
|
return "route53";
|
|
42286
|
+
if (r3.includes("cloudflare"))
|
|
42287
|
+
return "cloudflare";
|
|
42288
|
+
if (r3.includes("brandsight"))
|
|
42289
|
+
return "brandsight";
|
|
42383
42290
|
return null;
|
|
42384
42291
|
}
|
|
42385
42292
|
|
|
42386
|
-
// src/
|
|
42387
|
-
|
|
42388
|
-
|
|
42389
|
-
|
|
42293
|
+
// src/lib/config.ts
|
|
42294
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
42295
|
+
import { homedir as homedir8 } from "os";
|
|
42296
|
+
import { join as join8 } from "path";
|
|
42297
|
+
function configPath() {
|
|
42298
|
+
return join8(homedir8(), ".hasna", "domains", "config.json");
|
|
42299
|
+
}
|
|
42300
|
+
function loadConfig3() {
|
|
42301
|
+
const path = configPath();
|
|
42302
|
+
if (!existsSync6(path))
|
|
42303
|
+
return {};
|
|
42304
|
+
try {
|
|
42305
|
+
return JSON.parse(readFileSync4(path, "utf-8"));
|
|
42306
|
+
} catch {
|
|
42307
|
+
return {};
|
|
42308
|
+
}
|
|
42309
|
+
}
|
|
42310
|
+
function saveConfig(config) {
|
|
42311
|
+
const path = configPath();
|
|
42312
|
+
const dir = join8(homedir8(), ".hasna", "domains");
|
|
42313
|
+
if (!existsSync6(dir))
|
|
42314
|
+
mkdirSync5(dir, { recursive: true });
|
|
42315
|
+
writeFileSync2(path, JSON.stringify(config, null, 2), "utf-8");
|
|
42316
|
+
}
|
|
42317
|
+
function setConfigKey(keyPath, value) {
|
|
42318
|
+
const config = loadConfig3();
|
|
42319
|
+
const parts = keyPath.split(".");
|
|
42320
|
+
if (parts.length === 1) {
|
|
42321
|
+
config[parts[0]] = value;
|
|
42322
|
+
} else if (parts.length === 2 && parts[0] === "contact") {
|
|
42323
|
+
if (!config.contact)
|
|
42324
|
+
config.contact = {};
|
|
42325
|
+
config.contact[parts[1]] = value;
|
|
42326
|
+
} else {
|
|
42327
|
+
throw new Error(`Unknown config key: ${keyPath}`);
|
|
42328
|
+
}
|
|
42329
|
+
saveConfig(config);
|
|
42330
|
+
return config;
|
|
42331
|
+
}
|
|
42332
|
+
function resolveContact(opts) {
|
|
42333
|
+
const cfg = loadConfig3().contact ?? {};
|
|
42334
|
+
const get = (opt, cfgKey) => opt || cfg[cfgKey] || "";
|
|
42335
|
+
const first_name = get(opts.firstName, "first_name");
|
|
42336
|
+
const last_name = get(opts.lastName, "last_name");
|
|
42337
|
+
const email = get(opts.email, "email");
|
|
42338
|
+
const phone = get(opts.phone, "phone");
|
|
42339
|
+
const address_line_1 = get(opts.address, "address_line_1");
|
|
42340
|
+
const city = get(opts.city, "city");
|
|
42341
|
+
const state = get(opts.state, "state");
|
|
42342
|
+
const country_code = get(opts.country, "country_code");
|
|
42343
|
+
const zip_code = get(opts.zip, "zip_code");
|
|
42344
|
+
const organization_name = opts.org || cfg.organization_name;
|
|
42345
|
+
const missing = ["first_name", "last_name", "email", "phone", "address_line_1", "city", "state", "country_code", "zip_code"].filter((k3) => !{ first_name, last_name, email, phone, address_line_1, city, state, country_code, zip_code }[k3]);
|
|
42346
|
+
if (missing.length > 0) {
|
|
42347
|
+
throw new Error(`Missing registrant contact fields: ${missing.join(", ")}.
|
|
42348
|
+
` + `Set defaults with: domains config set contact.<field> <value>`);
|
|
42349
|
+
}
|
|
42350
|
+
return { first_name, last_name, email, phone, address_line_1, city, state, country_code, zip_code, organization_name };
|
|
42351
|
+
}
|
|
42352
|
+
|
|
42353
|
+
// src/cli/commands/domain.ts
|
|
42354
|
+
function registerDomainCommand(program2) {
|
|
42355
|
+
const domain = program2.command("domain").description("Domain portfolio management");
|
|
42356
|
+
domain.command("list").description("List all domains in the portfolio").option("--status <status>", "Filter by status (active/expired/transferring/redemption)").option("--registrar <name>", "Filter by registrar").option("--json", "Output JSON").action((opts) => {
|
|
42357
|
+
const domains = listDomains({ status: opts.status, registrar: opts.registrar });
|
|
42390
42358
|
if (opts.json) {
|
|
42391
|
-
console.log(JSON.stringify(
|
|
42392
|
-
|
|
42393
|
-
|
|
42394
|
-
|
|
42395
|
-
|
|
42396
|
-
|
|
42397
|
-
|
|
42398
|
-
|
|
42399
|
-
|
|
42359
|
+
console.log(JSON.stringify({ domains, count: domains.length }, null, 2));
|
|
42360
|
+
return;
|
|
42361
|
+
}
|
|
42362
|
+
if (domains.length === 0) {
|
|
42363
|
+
console.log("No domains found.");
|
|
42364
|
+
return;
|
|
42365
|
+
}
|
|
42366
|
+
for (const d3 of domains) {
|
|
42367
|
+
const exp = d3.expires_at ? ` (expires ${d3.expires_at.split("T")[0]})` : "";
|
|
42368
|
+
console.log(` ${d3.name} [${d3.status}]${exp}`);
|
|
42369
|
+
}
|
|
42370
|
+
console.log(`
|
|
42371
|
+
${domains.length} domain(s)`);
|
|
42372
|
+
});
|
|
42373
|
+
domain.command("get <id>").description("Get a domain by ID").option("--json", "Output JSON").action((id, opts) => {
|
|
42374
|
+
const d3 = getDomain(id);
|
|
42375
|
+
if (!d3) {
|
|
42376
|
+
console.error(`Domain '${id}' not found.`);
|
|
42377
|
+
process.exit(1);
|
|
42378
|
+
}
|
|
42379
|
+
if (opts.json) {
|
|
42380
|
+
console.log(JSON.stringify(d3, null, 2));
|
|
42381
|
+
return;
|
|
42382
|
+
}
|
|
42383
|
+
console.log(`
|
|
42384
|
+
${d3.name} [${d3.status}]`);
|
|
42385
|
+
if (d3.registrar)
|
|
42386
|
+
console.log(` Registrar: ${d3.registrar}`);
|
|
42387
|
+
if (d3.expires_at)
|
|
42388
|
+
console.log(` Expires: ${d3.expires_at.split("T")[0]}`);
|
|
42389
|
+
console.log(` Auto-renew: ${d3.auto_renew ? "yes" : "no"}`);
|
|
42390
|
+
if (d3.notes)
|
|
42391
|
+
console.log(` Notes: ${d3.notes}`);
|
|
42392
|
+
console.log();
|
|
42393
|
+
});
|
|
42394
|
+
domain.command("add").description("Add a domain to the portfolio").requiredOption("--name <name>", "Domain name").option("--registrar <name>", "Registrar name").option("--status <s>", "Status (active/expired/transferring/redemption)", "active").option("--expires <date>", "Expiry date (YYYY-MM-DD)").option("--notes <text>", "Notes").option("--json", "Output JSON").action((opts) => {
|
|
42395
|
+
const d3 = createDomain({
|
|
42396
|
+
name: opts.name,
|
|
42397
|
+
registrar: opts.registrar,
|
|
42398
|
+
status: opts.status,
|
|
42399
|
+
expires_at: opts.expires,
|
|
42400
|
+
notes: opts.notes
|
|
42401
|
+
});
|
|
42402
|
+
if (opts.json) {
|
|
42403
|
+
console.log(JSON.stringify(d3, null, 2));
|
|
42404
|
+
return;
|
|
42405
|
+
}
|
|
42406
|
+
console.log(`Created domain: ${d3.name} (${d3.id})`);
|
|
42407
|
+
});
|
|
42408
|
+
domain.command("update <id>").description("Update a domain").option("--registrar <name>", "Registrar").option("--status <s>", "Status").option("--expires <date>", "Expiry date").option("--notes <text>", "Notes").option("--json", "Output JSON").action((id, opts) => {
|
|
42409
|
+
const d3 = updateDomain(id, {
|
|
42410
|
+
registrar: opts.registrar,
|
|
42411
|
+
status: opts.status,
|
|
42412
|
+
expires_at: opts.expires,
|
|
42413
|
+
notes: opts.notes
|
|
42414
|
+
});
|
|
42415
|
+
if (!d3) {
|
|
42416
|
+
console.error(`Domain '${id}' not found.`);
|
|
42417
|
+
process.exit(1);
|
|
42418
|
+
}
|
|
42419
|
+
if (opts.json) {
|
|
42420
|
+
console.log(JSON.stringify(d3, null, 2));
|
|
42421
|
+
return;
|
|
42422
|
+
}
|
|
42423
|
+
console.log(`Updated: ${d3.name}`);
|
|
42424
|
+
});
|
|
42425
|
+
domain.command("delete <id>").description("Delete a domain from the portfolio").action((id) => {
|
|
42426
|
+
const deleted = deleteDomain(id);
|
|
42427
|
+
if (deleted)
|
|
42428
|
+
console.log(`Deleted domain ${id}`);
|
|
42429
|
+
else {
|
|
42430
|
+
console.error(`Domain '${id}' not found.`);
|
|
42431
|
+
process.exit(1);
|
|
42432
|
+
}
|
|
42433
|
+
});
|
|
42434
|
+
domain.command("search <query>").description("Search domains by name, registrar, or notes").option("--json", "Output JSON").action((query, opts) => {
|
|
42435
|
+
const results = searchDomains(query);
|
|
42436
|
+
if (opts.json) {
|
|
42437
|
+
console.log(JSON.stringify({ results, count: results.length }, null, 2));
|
|
42438
|
+
return;
|
|
42439
|
+
}
|
|
42440
|
+
for (const d3 of results)
|
|
42441
|
+
console.log(` ${d3.name} [${d3.status}]`);
|
|
42442
|
+
if (results.length === 0)
|
|
42443
|
+
console.log("No results.");
|
|
42444
|
+
});
|
|
42445
|
+
domain.command("expiring").description("List domains expiring soon").option("--days <n>", "Days threshold", "30").option("--json", "Output JSON").action((opts) => {
|
|
42446
|
+
const domains = listExpiring(parseInt(opts.days));
|
|
42447
|
+
if (opts.json) {
|
|
42448
|
+
console.log(JSON.stringify(domains, null, 2));
|
|
42449
|
+
return;
|
|
42450
|
+
}
|
|
42451
|
+
if (domains.length === 0) {
|
|
42452
|
+
console.log(`No domains expiring within ${opts.days} days.`);
|
|
42453
|
+
return;
|
|
42454
|
+
}
|
|
42455
|
+
console.log(`
|
|
42456
|
+
Expiring within ${opts.days} days:`);
|
|
42457
|
+
for (const d3 of domains)
|
|
42458
|
+
console.log(` ${d3.name.padEnd(40)} expires ${(d3.expires_at ?? "").split("T")[0]}`);
|
|
42459
|
+
console.log();
|
|
42460
|
+
});
|
|
42461
|
+
domain.command("stats").description("Show portfolio statistics").option("--json", "Output JSON").action((opts) => {
|
|
42462
|
+
const stats = getDomainStats();
|
|
42463
|
+
if (opts.json) {
|
|
42464
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
42465
|
+
return;
|
|
42466
|
+
}
|
|
42467
|
+
console.log("Domain Portfolio Stats:");
|
|
42468
|
+
for (const [k3, v3] of Object.entries(stats)) {
|
|
42469
|
+
console.log(` ${k3.replace(/_/g, " ")}: ${v3}`);
|
|
42470
|
+
}
|
|
42471
|
+
});
|
|
42472
|
+
domain.command("whois <name>").description("Run WHOIS lookup and update local DB record").option("--json", "Output JSON").action((name, opts) => {
|
|
42473
|
+
const result = whoisLookup(name);
|
|
42474
|
+
if (opts.json) {
|
|
42475
|
+
console.log(JSON.stringify(result, null, 2));
|
|
42476
|
+
return;
|
|
42477
|
+
}
|
|
42478
|
+
console.log(`
|
|
42479
|
+
WHOIS for ${result.domain}:`);
|
|
42480
|
+
console.log(` Registrar: ${result.registrar ?? "unknown"}`);
|
|
42481
|
+
console.log(` Expires: ${result.expires_at ?? "unknown"}`);
|
|
42482
|
+
if (result.nameservers.length) {
|
|
42483
|
+
console.log(` NS: ${result.nameservers.join(", ")}`);
|
|
42484
|
+
}
|
|
42485
|
+
console.log();
|
|
42486
|
+
});
|
|
42487
|
+
domain.command("export").description("Export all domains as CSV or JSON").option("--format <fmt>", "Format: csv or json", "json").action((opts) => {
|
|
42488
|
+
const output = exportPortfolio(opts.format);
|
|
42489
|
+
console.log(output);
|
|
42490
|
+
});
|
|
42491
|
+
domain.command("check <domains...>").description("Check domain availability via configured registrar").option("--provider <name>", "Registrar provider to use").action(async (domains, opts) => {
|
|
42492
|
+
const cfg = loadConfig3();
|
|
42493
|
+
const providerName = opts.provider ?? cfg.default_registrar ?? "route53";
|
|
42494
|
+
const results = await Promise.allSettled(domains.map(async (d3) => {
|
|
42495
|
+
const provider = getRegistrarProvider(providerName);
|
|
42496
|
+
return provider.checkAvailability(d3);
|
|
42497
|
+
}));
|
|
42498
|
+
let anyError = false;
|
|
42499
|
+
for (let i3 = 0;i3 < domains.length; i3++) {
|
|
42500
|
+
const r3 = results[i3];
|
|
42501
|
+
if (r3.status === "rejected") {
|
|
42502
|
+
const reason = r3.reason;
|
|
42503
|
+
console.error(`\u2717 ${domains[i3]}: ${reason instanceof Error ? reason.message : String(reason)}`);
|
|
42504
|
+
anyError = true;
|
|
42505
|
+
} else {
|
|
42506
|
+
const result = r3.value;
|
|
42507
|
+
console.log(`${result.available ? "\u2713" : "\u2717"} ${result.domain} is ${result.available ? "available" : "not available"}`);
|
|
42400
42508
|
}
|
|
42401
42509
|
}
|
|
42510
|
+
if (anyError)
|
|
42511
|
+
process.exit(1);
|
|
42402
42512
|
});
|
|
42403
|
-
|
|
42404
|
-
|
|
42513
|
+
domain.command("sync").description("Sync domains from a provider to the local DB").option("--provider <name>", "Provider name (default: all configured)").action(async (opts) => {
|
|
42514
|
+
const providers = opts.provider ? [opts.provider] : getAvailableProviders().filter((p3) => p3.configured && p3.type !== "dns" && p3.name !== "brandsight").map((p3) => p3.name);
|
|
42515
|
+
for (const name of providers) {
|
|
42405
42516
|
try {
|
|
42406
|
-
const
|
|
42407
|
-
|
|
42408
|
-
|
|
42409
|
-
|
|
42410
|
-
|
|
42411
|
-
|
|
42412
|
-
|
|
42413
|
-
} else {
|
|
42414
|
-
console.log(`Synced ${result.totalSynced} domain(s) from ${result.providers.length} provider(s)`);
|
|
42415
|
-
for (const p3 of result.providers) {
|
|
42416
|
-
console.log(` ${p3.name}: ${p3.result.synced} synced`);
|
|
42417
|
-
}
|
|
42418
|
-
if (result.totalErrors.length > 0) {
|
|
42419
|
-
console.log("Errors:");
|
|
42420
|
-
for (const e3 of result.totalErrors) {
|
|
42421
|
-
console.log(` - ${e3}`);
|
|
42422
|
-
}
|
|
42423
|
-
}
|
|
42424
|
-
}
|
|
42425
|
-
} catch (error) {
|
|
42426
|
-
console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42427
|
-
process.exit(1);
|
|
42517
|
+
const provider = getRegistrarProvider(name);
|
|
42518
|
+
const result = await provider.syncToLocalDb({ getDomainByName, createDomain, updateDomain });
|
|
42519
|
+
console.log(`\u2713 [${name}] Synced ${result.synced} (${result.created} new, ${result.updated} updated)`);
|
|
42520
|
+
if (result.errors.length > 0)
|
|
42521
|
+
console.log(` Errors: ${result.errors.join(", ")}`);
|
|
42522
|
+
} catch (e3) {
|
|
42523
|
+
console.error(`\u2717 [${name}] ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42428
42524
|
}
|
|
42429
|
-
return;
|
|
42430
42525
|
}
|
|
42431
|
-
|
|
42432
|
-
|
|
42433
|
-
|
|
42526
|
+
});
|
|
42527
|
+
domain.command("renew <name>").description("Renew a domain via its registrar provider").option("--provider <name>", "Override provider").action(async (name, opts) => {
|
|
42528
|
+
const providerName = opts.provider ?? autoDetectRegistrar(name, getDomainByName) ?? loadConfig3().default_registrar;
|
|
42529
|
+
if (!providerName) {
|
|
42530
|
+
console.error("Could not detect provider. Use --provider.");
|
|
42531
|
+
process.exit(1);
|
|
42532
|
+
}
|
|
42533
|
+
const provider = getRegistrarProvider(providerName);
|
|
42534
|
+
const result = await provider.renewDomain(name);
|
|
42535
|
+
if (result.success) {
|
|
42536
|
+
console.log(`\u2713 Renewed: ${name}`);
|
|
42537
|
+
if (result.orderId)
|
|
42538
|
+
console.log(` Order: ${result.orderId}`);
|
|
42539
|
+
} else {
|
|
42540
|
+
console.error(`\u2717 Renewal failed or not supported for ${providerName}`);
|
|
42541
|
+
process.exit(1);
|
|
42542
|
+
}
|
|
42543
|
+
});
|
|
42544
|
+
domain.command("buy <name>").description("Purchase a domain via Route 53 (contact defaults from: domains config set contact.*)").option("--provider <name>", "Registrar provider (default: config default-registrar or route53)").option("--email <email>", "Registrant email").option("--first-name <n>", "First name").option("--last-name <n>", "Last name").option("--phone <p>", "Phone").option("--address <a>", "Street address").option("--city <c>", "City").option("--state <s>", "State/province").option("--country <c>", "Country code").option("--zip <z>", "ZIP code").option("--org <o>", "Organization").option("--years <n>", "Years", "1").option("--wait", "Poll until registration completes").action(async (name, opts) => {
|
|
42545
|
+
const cfg = loadConfig3();
|
|
42546
|
+
const providerName = opts.provider ?? cfg.default_registrar ?? "route53";
|
|
42547
|
+
if (providerName !== "route53") {
|
|
42548
|
+
console.error(`Direct domain purchase only supported via route53. Got: ${providerName}`);
|
|
42434
42549
|
process.exit(1);
|
|
42435
42550
|
}
|
|
42436
|
-
|
|
42551
|
+
try {
|
|
42552
|
+
const avail = await checkAvailability3(name);
|
|
42553
|
+
if (!avail.available) {
|
|
42554
|
+
console.error(`\u2717 ${name} is not available`);
|
|
42555
|
+
process.exit(1);
|
|
42556
|
+
}
|
|
42557
|
+
const price = avail.price ? ` (USD ${avail.price}/yr)` : "";
|
|
42558
|
+
console.log(`\u2713 Available${price}`);
|
|
42559
|
+
let contact;
|
|
42437
42560
|
try {
|
|
42438
|
-
|
|
42439
|
-
|
|
42440
|
-
|
|
42441
|
-
|
|
42442
|
-
|
|
42443
|
-
|
|
42444
|
-
|
|
42445
|
-
|
|
42446
|
-
|
|
42447
|
-
|
|
42448
|
-
|
|
42449
|
-
|
|
42450
|
-
|
|
42451
|
-
|
|
42452
|
-
|
|
42453
|
-
console.log(` - ${e3}`);
|
|
42454
|
-
}
|
|
42455
|
-
}
|
|
42561
|
+
contact = resolveContact(opts);
|
|
42562
|
+
} catch (e3) {
|
|
42563
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42564
|
+
process.exit(1);
|
|
42565
|
+
}
|
|
42566
|
+
console.log(`Registering ${name}...`);
|
|
42567
|
+
const reg = await registerDomain(name, contact, parseInt(opts.years));
|
|
42568
|
+
console.log(`\u2713 Submitted (operation: ${reg.operationId})`);
|
|
42569
|
+
if (opts.wait) {
|
|
42570
|
+
let status = "IN_PROGRESS";
|
|
42571
|
+
while (status === "IN_PROGRESS" || status === "SUBMITTED") {
|
|
42572
|
+
await new Promise((r3) => setTimeout(r3, 1e4));
|
|
42573
|
+
const s3 = await getRegistrationStatus(reg.operationId);
|
|
42574
|
+
status = s3.status;
|
|
42575
|
+
process.stdout.write(` Status: ${status}\r`);
|
|
42456
42576
|
}
|
|
42457
|
-
|
|
42458
|
-
|
|
42577
|
+
console.log();
|
|
42578
|
+
if (status !== "SUCCESSFUL") {
|
|
42579
|
+
console.error(`\u2717 Registration ${status}`);
|
|
42580
|
+
process.exit(1);
|
|
42581
|
+
}
|
|
42582
|
+
console.log(`\u2713 Registration complete`);
|
|
42583
|
+
}
|
|
42584
|
+
createDomain({ name, registrar: "AWS Route 53", status: "active", auto_renew: true });
|
|
42585
|
+
console.log(`\u2713 Added to portfolio`);
|
|
42586
|
+
if (!opts.wait)
|
|
42587
|
+
console.log(` Check: domains r53 status ${reg.operationId}`);
|
|
42588
|
+
} catch (e3) {
|
|
42589
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42590
|
+
process.exit(1);
|
|
42591
|
+
}
|
|
42592
|
+
});
|
|
42593
|
+
domain.command("setup <name>").description("Full setup: buy domain + create DNS zone + point nameservers (contact defaults from config)").option("--registrar <n>", "Registrar provider (default: config default-registrar or route53)").option("--dns <n>", "DNS provider (default: config default-dns or route53)").option("--email <e>", "Registrant email").option("--first-name <n>", "First name").option("--last-name <n>", "Last name").option("--phone <p>", "Phone").option("--address <a>", "Street address").option("--city <c>", "City").option("--state <s>", "State/province").option("--country <c>", "Country code").option("--zip <z>", "ZIP code").option("--org <o>", "Organization").option("--years <n>", "Years", "1").option("--wait", "Poll until registration completes before creating DNS zone").action(async (name, opts) => {
|
|
42594
|
+
const cfg = loadConfig3();
|
|
42595
|
+
const registrarName = opts.registrar ?? cfg.default_registrar ?? "route53";
|
|
42596
|
+
const dnsName = opts.dns ?? cfg.default_dns ?? registrarName;
|
|
42597
|
+
console.log(`
|
|
42598
|
+
Setting up ${name}`);
|
|
42599
|
+
console.log(` Registrar: ${registrarName} | DNS: ${dnsName}
|
|
42600
|
+
`);
|
|
42601
|
+
try {
|
|
42602
|
+
process.stdout.write("[1/4] Checking availability... ");
|
|
42603
|
+
const avail = await checkAvailability3(name);
|
|
42604
|
+
if (!avail.available) {
|
|
42605
|
+
console.log("not available");
|
|
42606
|
+
console.error(`\u2717 ${name} is not available`);
|
|
42607
|
+
process.exit(1);
|
|
42608
|
+
}
|
|
42609
|
+
const price = avail.price ? `(USD ${avail.price}/yr)` : "";
|
|
42610
|
+
console.log(`available ${price}`);
|
|
42611
|
+
if (registrarName !== "route53") {
|
|
42612
|
+
console.error("Direct domain purchase currently only supported via route53.");
|
|
42459
42613
|
process.exit(1);
|
|
42460
42614
|
}
|
|
42461
|
-
|
|
42615
|
+
let contact;
|
|
42462
42616
|
try {
|
|
42463
|
-
|
|
42464
|
-
|
|
42465
|
-
|
|
42466
|
-
updateDomain
|
|
42467
|
-
});
|
|
42468
|
-
if (opts.json) {
|
|
42469
|
-
console.log(JSON.stringify(result, null, 2));
|
|
42470
|
-
} else {
|
|
42471
|
-
console.log(`Synced ${result.synced} domain(s) from GoDaddy (created: ${result.created}, updated: ${result.updated})`);
|
|
42472
|
-
if (result.errors.length > 0) {
|
|
42473
|
-
console.log("Errors:");
|
|
42474
|
-
for (const e3 of result.errors) {
|
|
42475
|
-
console.log(` - ${e3}`);
|
|
42476
|
-
}
|
|
42477
|
-
}
|
|
42478
|
-
}
|
|
42479
|
-
} catch (error) {
|
|
42480
|
-
console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
42617
|
+
contact = resolveContact(opts);
|
|
42618
|
+
} catch (e3) {
|
|
42619
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42481
42620
|
process.exit(1);
|
|
42482
42621
|
}
|
|
42622
|
+
process.stdout.write("[2/4] Registering domain... ");
|
|
42623
|
+
const reg = await registerDomain(name, contact, parseInt(opts.years));
|
|
42624
|
+
console.log(`submitted (${reg.operationId})`);
|
|
42625
|
+
if (opts.wait) {
|
|
42626
|
+
let status = "IN_PROGRESS";
|
|
42627
|
+
while (status === "IN_PROGRESS" || status === "SUBMITTED") {
|
|
42628
|
+
await new Promise((r3) => setTimeout(r3, 1e4));
|
|
42629
|
+
const s3 = await getRegistrationStatus(reg.operationId);
|
|
42630
|
+
status = s3.status;
|
|
42631
|
+
process.stdout.write(` Waiting: ${status}...\r`);
|
|
42632
|
+
}
|
|
42633
|
+
console.log();
|
|
42634
|
+
if (status !== "SUCCESSFUL") {
|
|
42635
|
+
console.error(`\u2717 Registration ${status}`);
|
|
42636
|
+
process.exit(1);
|
|
42637
|
+
}
|
|
42638
|
+
console.log(" Registration confirmed");
|
|
42639
|
+
}
|
|
42640
|
+
process.stdout.write("[3/4] Creating DNS zone... ");
|
|
42641
|
+
let nameservers = [];
|
|
42642
|
+
if (dnsName === "cloudflare") {
|
|
42643
|
+
const zone = await createZone(name);
|
|
42644
|
+
nameservers = zone.nameservers ?? [];
|
|
42645
|
+
console.log(`created (${zone.id})`);
|
|
42646
|
+
} else {
|
|
42647
|
+
const zone = await createHostedZone(name, `Managed by @hasna/domains`);
|
|
42648
|
+
nameservers = zone.name_servers ?? [];
|
|
42649
|
+
console.log(`created (${zone.id})`);
|
|
42650
|
+
}
|
|
42651
|
+
process.stdout.write("[4/4] Adding to portfolio... ");
|
|
42652
|
+
createDomain({ name, registrar: `AWS Route 53`, status: "active", auto_renew: true, nameservers });
|
|
42653
|
+
console.log("done");
|
|
42654
|
+
console.log(`
|
|
42655
|
+
\u2713 Setup complete for ${name}`);
|
|
42656
|
+
if (!opts.wait) {
|
|
42657
|
+
console.log(` \u26A0 Registration pending \u2014 check: domains r53 status ${reg.operationId}`);
|
|
42658
|
+
console.log(` \u26A0 If registration fails, clean up: domains zone delete <zoneId> --force`);
|
|
42659
|
+
}
|
|
42660
|
+
if (nameservers.length > 0) {
|
|
42661
|
+
console.log(`
|
|
42662
|
+
Point your registrar to these name servers:`);
|
|
42663
|
+
for (const ns of nameservers)
|
|
42664
|
+
console.log(` ${ns}`);
|
|
42665
|
+
}
|
|
42666
|
+
console.log();
|
|
42667
|
+
} catch (e3) {
|
|
42668
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42669
|
+
process.exit(1);
|
|
42670
|
+
}
|
|
42671
|
+
});
|
|
42672
|
+
}
|
|
42673
|
+
|
|
42674
|
+
// src/cli/commands/dns.ts
|
|
42675
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
42676
|
+
function registerDnsCommands(program2) {
|
|
42677
|
+
const dnsCmd = program2.command("dns").description("DNS record management");
|
|
42678
|
+
dnsCmd.command("list").description("List DNS records for a domain").argument("<domain-id>", "Domain ID").option("--type <type>", "Filter by record type (A/AAAA/CNAME/MX/TXT/NS/SRV)").option("--json", "Output as JSON", false).action((domainId, opts) => {
|
|
42679
|
+
const records = listDnsRecords(domainId, opts.type);
|
|
42680
|
+
if (opts.json) {
|
|
42681
|
+
console.log(JSON.stringify(records, null, 2));
|
|
42682
|
+
} else {
|
|
42683
|
+
if (records.length === 0) {
|
|
42684
|
+
console.log("No DNS records found.");
|
|
42685
|
+
return;
|
|
42686
|
+
}
|
|
42687
|
+
for (const r3 of records) {
|
|
42688
|
+
const priority = r3.priority !== null ? ` (priority: ${r3.priority})` : "";
|
|
42689
|
+
console.log(` ${r3.type} ${r3.name} ${r3.value} TTL:${r3.ttl}${priority}`);
|
|
42690
|
+
}
|
|
42691
|
+
}
|
|
42692
|
+
});
|
|
42693
|
+
dnsCmd.command("add").description("Add a DNS record").requiredOption("--domain <id>", "Domain ID").requiredOption("--type <type>", "Record type (A/AAAA/CNAME/MX/TXT/NS/SRV)").requiredOption("--name <name>", "Record name").requiredOption("--value <value>", "Record value").option("--ttl <ttl>", "TTL in seconds", "3600").option("--priority <n>", "Priority (for MX/SRV)").option("--json", "Output as JSON", false).action((opts) => {
|
|
42694
|
+
const record = createDnsRecord({
|
|
42695
|
+
domain_id: opts.domain,
|
|
42696
|
+
type: opts.type,
|
|
42697
|
+
name: opts.name,
|
|
42698
|
+
value: opts.value,
|
|
42699
|
+
ttl: parseInt(opts.ttl),
|
|
42700
|
+
priority: opts.priority ? parseInt(opts.priority) : undefined
|
|
42701
|
+
});
|
|
42702
|
+
if (opts.json) {
|
|
42703
|
+
console.log(JSON.stringify(record, null, 2));
|
|
42704
|
+
} else {
|
|
42705
|
+
console.log(`Created DNS record: ${record.type} ${record.name} -> ${record.value} (${record.id})`);
|
|
42706
|
+
}
|
|
42707
|
+
});
|
|
42708
|
+
dnsCmd.command("update").description("Update a DNS record").argument("<id>", "Record ID").option("--type <type>", "Record type").option("--name <name>", "Record name").option("--value <value>", "Record value").option("--ttl <ttl>", "TTL in seconds").option("--priority <n>", "Priority").option("--json", "Output as JSON", false).action((id, opts) => {
|
|
42709
|
+
const input = {};
|
|
42710
|
+
if (opts.type !== undefined)
|
|
42711
|
+
input.type = opts.type;
|
|
42712
|
+
if (opts.name !== undefined)
|
|
42713
|
+
input.name = opts.name;
|
|
42714
|
+
if (opts.value !== undefined)
|
|
42715
|
+
input.value = opts.value;
|
|
42716
|
+
if (opts.ttl !== undefined)
|
|
42717
|
+
input.ttl = parseInt(opts.ttl);
|
|
42718
|
+
if (opts.priority !== undefined)
|
|
42719
|
+
input.priority = parseInt(opts.priority);
|
|
42720
|
+
const record = updateDnsRecord(id, input);
|
|
42721
|
+
if (!record) {
|
|
42722
|
+
console.error(`DNS record '${id}' not found.`);
|
|
42723
|
+
process.exit(1);
|
|
42724
|
+
}
|
|
42725
|
+
if (opts.json) {
|
|
42726
|
+
console.log(JSON.stringify(record, null, 2));
|
|
42483
42727
|
} else {
|
|
42484
|
-
console.
|
|
42728
|
+
console.log(`Updated DNS record: ${record.type} ${record.name} -> ${record.value}`);
|
|
42729
|
+
}
|
|
42730
|
+
});
|
|
42731
|
+
dnsCmd.command("remove").description("Remove a DNS record").argument("<id>", "Record ID").action((id) => {
|
|
42732
|
+
const deleted = deleteDnsRecord(id);
|
|
42733
|
+
if (deleted) {
|
|
42734
|
+
console.log(`Deleted DNS record ${id}`);
|
|
42735
|
+
} else {
|
|
42736
|
+
console.error(`DNS record '${id}' not found.`);
|
|
42485
42737
|
process.exit(1);
|
|
42486
42738
|
}
|
|
42487
42739
|
});
|
|
42488
|
-
|
|
42489
|
-
|
|
42490
|
-
if (
|
|
42491
|
-
|
|
42492
|
-
|
|
42493
|
-
|
|
42494
|
-
|
|
42740
|
+
dnsCmd.command("check-propagation").description("Check DNS propagation across multiple servers").argument("<domain>", "Domain name to check").option("--record <type>", "Record type (A/AAAA/CNAME/MX/TXT/NS)", "A").option("--json", "Output as JSON", false).action((domain, opts) => {
|
|
42741
|
+
const result = checkDnsPropagation(domain, opts.record);
|
|
42742
|
+
if (opts.json) {
|
|
42743
|
+
console.log(JSON.stringify(result, null, 2));
|
|
42744
|
+
} else {
|
|
42745
|
+
console.log(`DNS Propagation for ${result.domain} (${result.record_type}):`);
|
|
42746
|
+
console.log(` Consistent: ${result.consistent ? "yes" : "NO"}`);
|
|
42747
|
+
for (const s3 of result.servers) {
|
|
42748
|
+
const values = s3.values.length > 0 ? s3.values.join(", ") : "(empty)";
|
|
42749
|
+
const status = s3.status === "error" ? ` [ERROR: ${s3.error}]` : "";
|
|
42750
|
+
console.log(` ${s3.name} (${s3.server}): ${values}${status}`);
|
|
42495
42751
|
}
|
|
42496
|
-
provider = detected;
|
|
42497
|
-
console.log(`Auto-detected registrar: ${provider}`);
|
|
42498
42752
|
}
|
|
42499
|
-
|
|
42500
|
-
|
|
42501
|
-
|
|
42502
|
-
|
|
42503
|
-
|
|
42504
|
-
|
|
42505
|
-
|
|
42506
|
-
|
|
42507
|
-
|
|
42508
|
-
|
|
42509
|
-
|
|
42753
|
+
});
|
|
42754
|
+
dnsCmd.command("export").description("Export DNS records as BIND zone file").argument("<domain-id>", "Domain ID").option("--output <file>", "Write to file instead of stdout").action((domainId, opts) => {
|
|
42755
|
+
const zone = exportZoneFile(domainId);
|
|
42756
|
+
if (!zone) {
|
|
42757
|
+
console.error(`Domain '${domainId}' not found.`);
|
|
42758
|
+
process.exit(1);
|
|
42759
|
+
}
|
|
42760
|
+
if (opts.output) {
|
|
42761
|
+
writeFileSync3(opts.output, zone, "utf-8");
|
|
42762
|
+
console.log(`Exported zone file to ${opts.output}`);
|
|
42763
|
+
} else {
|
|
42764
|
+
console.log(zone);
|
|
42765
|
+
}
|
|
42766
|
+
});
|
|
42767
|
+
dnsCmd.command("import").description("Import DNS records from a BIND zone file").argument("<domain-id>", "Domain ID").requiredOption("--file <path>", "Path to zone file").option("--json", "Output as JSON", false).action((domainId, opts) => {
|
|
42768
|
+
let content;
|
|
42769
|
+
try {
|
|
42770
|
+
content = readFileSync5(opts.file, "utf-8");
|
|
42771
|
+
} catch {
|
|
42772
|
+
console.error(`Could not read file: ${opts.file}`);
|
|
42773
|
+
process.exit(1);
|
|
42774
|
+
}
|
|
42775
|
+
const result = importZoneFile(domainId, content);
|
|
42776
|
+
if (!result) {
|
|
42777
|
+
console.error(`Domain '${domainId}' not found.`);
|
|
42778
|
+
process.exit(1);
|
|
42779
|
+
}
|
|
42780
|
+
if (opts.json) {
|
|
42781
|
+
console.log(JSON.stringify(result, null, 2));
|
|
42782
|
+
} else {
|
|
42783
|
+
console.log(`Imported ${result.imported} record(s), skipped ${result.skipped}`);
|
|
42784
|
+
if (result.errors.length > 0) {
|
|
42785
|
+
console.log("Errors:");
|
|
42786
|
+
for (const e3 of result.errors) {
|
|
42787
|
+
console.log(` - ${e3}`);
|
|
42510
42788
|
}
|
|
42511
|
-
}
|
|
42512
|
-
|
|
42789
|
+
}
|
|
42790
|
+
}
|
|
42791
|
+
});
|
|
42792
|
+
dnsCmd.command("discover-subdomains").description("Discover subdomains via certificate transparency logs (crt.sh)").argument("<domain>", "Domain name").option("--json", "Output as JSON", false).action(async (domain, opts) => {
|
|
42793
|
+
const result = await discoverSubdomains(domain);
|
|
42794
|
+
if (opts.json) {
|
|
42795
|
+
console.log(JSON.stringify(result, null, 2));
|
|
42796
|
+
} else {
|
|
42797
|
+
if (result.error) {
|
|
42798
|
+
console.error(`Discovery failed: ${result.error}`);
|
|
42513
42799
|
process.exit(1);
|
|
42514
42800
|
}
|
|
42515
|
-
|
|
42516
|
-
|
|
42517
|
-
|
|
42518
|
-
|
|
42519
|
-
|
|
42520
|
-
|
|
42521
|
-
|
|
42522
|
-
|
|
42523
|
-
|
|
42524
|
-
|
|
42525
|
-
|
|
42801
|
+
if (result.subdomains.length === 0) {
|
|
42802
|
+
console.log(`No subdomains found for ${domain}.`);
|
|
42803
|
+
return;
|
|
42804
|
+
}
|
|
42805
|
+
console.log(`Subdomains for ${domain} (source: ${result.source}):`);
|
|
42806
|
+
for (const s3 of result.subdomains) {
|
|
42807
|
+
console.log(` ${s3}`);
|
|
42808
|
+
}
|
|
42809
|
+
console.log(`
|
|
42810
|
+
${result.subdomains.length} subdomain(s) found`);
|
|
42811
|
+
}
|
|
42812
|
+
});
|
|
42813
|
+
dnsCmd.command("validate").description("Validate DNS records for common issues").argument("<domain-id>", "Domain ID").option("--json", "Output as JSON", false).action((domainId, opts) => {
|
|
42814
|
+
const result = validateDns(domainId);
|
|
42815
|
+
if (!result) {
|
|
42816
|
+
console.error(`Domain '${domainId}' not found.`);
|
|
42817
|
+
process.exit(1);
|
|
42818
|
+
}
|
|
42819
|
+
if (opts.json) {
|
|
42820
|
+
console.log(JSON.stringify(result, null, 2));
|
|
42821
|
+
} else {
|
|
42822
|
+
console.log(`DNS Validation for ${result.domain_name}:`);
|
|
42823
|
+
console.log(` Valid: ${result.valid ? "yes" : "NO"}`);
|
|
42824
|
+
if (result.issues.length === 0) {
|
|
42825
|
+
console.log(" No issues found.");
|
|
42826
|
+
} else {
|
|
42827
|
+
for (const issue of result.issues) {
|
|
42828
|
+
const prefix = issue.type === "error" ? "ERROR" : "WARN";
|
|
42829
|
+
console.log(` [${prefix}] ${issue.message}`);
|
|
42526
42830
|
}
|
|
42527
|
-
}
|
|
42528
|
-
|
|
42831
|
+
}
|
|
42832
|
+
}
|
|
42833
|
+
});
|
|
42834
|
+
dnsCmd.command("pull <domain>").description("Pull live DNS records from provider into local DB").option("--provider <name>", "DNS provider (route53, cloudflare) \u2014 defaults to config default-dns").action(async (domain, opts) => {
|
|
42835
|
+
const providerName = opts.provider ?? loadConfig3().default_dns ?? "route53";
|
|
42836
|
+
try {
|
|
42837
|
+
const provider = getDnsProvider(providerName);
|
|
42838
|
+
const records = await provider.getDnsRecords(domain);
|
|
42839
|
+
const dbDomain = getDomainByName(domain);
|
|
42840
|
+
if (!dbDomain) {
|
|
42841
|
+
console.error(`Domain '${domain}' not found in local DB. Add it first: domains domain add --name ${domain}`);
|
|
42529
42842
|
process.exit(1);
|
|
42530
42843
|
}
|
|
42531
|
-
|
|
42532
|
-
|
|
42844
|
+
let count = 0;
|
|
42845
|
+
for (const r3 of records) {
|
|
42846
|
+
createDnsRecord({ domain_id: dbDomain.id, type: r3.type, name: r3.name, value: r3.value, ttl: r3.ttl, priority: r3.priority });
|
|
42847
|
+
count++;
|
|
42848
|
+
}
|
|
42849
|
+
console.log(`\u2713 Pulled ${count} record(s) from ${providerName} into local DB for ${domain}`);
|
|
42850
|
+
} catch (e3) {
|
|
42851
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42852
|
+
process.exit(1);
|
|
42853
|
+
}
|
|
42854
|
+
});
|
|
42855
|
+
dnsCmd.command("push <domain-id>").description("Push local DB records to live DNS provider").option("--provider <name>", "DNS provider \u2014 defaults to config default-dns").action(async (domainId, opts) => {
|
|
42856
|
+
const providerName = opts.provider ?? loadConfig3().default_dns ?? "route53";
|
|
42857
|
+
try {
|
|
42858
|
+
const records = listDnsRecords(domainId);
|
|
42859
|
+
if (records.length === 0) {
|
|
42860
|
+
console.log("No local DNS records to push.");
|
|
42861
|
+
return;
|
|
42862
|
+
}
|
|
42863
|
+
const dbDomain = getDomain(domainId) ?? (() => {
|
|
42864
|
+
throw new Error(`Domain '${domainId}' not found`);
|
|
42865
|
+
})();
|
|
42866
|
+
const provider = getDnsProvider(providerName);
|
|
42867
|
+
await provider.setDnsRecords(dbDomain.name, records.map((r3) => ({
|
|
42868
|
+
type: r3.type,
|
|
42869
|
+
name: r3.name,
|
|
42870
|
+
value: r3.value,
|
|
42871
|
+
ttl: r3.ttl,
|
|
42872
|
+
priority: r3.priority ?? undefined
|
|
42873
|
+
})));
|
|
42874
|
+
console.log(`\u2713 Pushed ${records.length} record(s) to ${providerName} for ${dbDomain.name}`);
|
|
42875
|
+
} catch (e3) {
|
|
42876
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42533
42877
|
process.exit(1);
|
|
42534
42878
|
}
|
|
42535
42879
|
});
|
|
42536
|
-
|
|
42880
|
+
}
|
|
42881
|
+
|
|
42882
|
+
// src/cli/commands/zone.ts
|
|
42883
|
+
function resolveProvider(flag) {
|
|
42884
|
+
return flag ?? loadConfig3().default_dns ?? "route53";
|
|
42885
|
+
}
|
|
42886
|
+
function registerZoneCommand(program2) {
|
|
42887
|
+
const zone = program2.command("zone").description("Hosted zone management (provider-agnostic)");
|
|
42888
|
+
zone.command("list").description("List all hosted zones").option("--provider <name>", "DNS provider (route53, cloudflare) \u2014 defaults to config default-dns").option("--json", "Output JSON").action(async (opts) => {
|
|
42889
|
+
const provider = resolveProvider(opts.provider);
|
|
42537
42890
|
try {
|
|
42538
|
-
|
|
42891
|
+
let zones;
|
|
42892
|
+
if (provider === "cloudflare") {
|
|
42893
|
+
zones = (await listZones()).map((z2) => ({ id: z2.id, name: z2.name, status: z2.status, nameservers: z2.nameservers }));
|
|
42894
|
+
} else {
|
|
42895
|
+
zones = await listHostedZones();
|
|
42896
|
+
}
|
|
42539
42897
|
if (opts.json) {
|
|
42540
|
-
console.log(JSON.stringify(
|
|
42898
|
+
console.log(JSON.stringify(zones, null, 2));
|
|
42899
|
+
return;
|
|
42900
|
+
}
|
|
42901
|
+
if (zones.length === 0) {
|
|
42902
|
+
console.log("No hosted zones.");
|
|
42903
|
+
return;
|
|
42904
|
+
}
|
|
42905
|
+
console.log(`
|
|
42906
|
+
Hosted Zones (${provider}):`);
|
|
42907
|
+
for (const z2 of zones) {
|
|
42908
|
+
const extra = z2.record_count !== undefined ? ` ${z2.record_count} records` : "";
|
|
42909
|
+
console.log(` ${z2.id.padEnd(32)} ${z2.name}${extra}`);
|
|
42910
|
+
}
|
|
42911
|
+
console.log();
|
|
42912
|
+
} catch (e3) {
|
|
42913
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42914
|
+
process.exit(1);
|
|
42915
|
+
}
|
|
42916
|
+
});
|
|
42917
|
+
zone.command("create <domain>").description("Create a hosted zone").option("--provider <name>", "DNS provider").option("--comment <text>", "Zone comment (Route 53 only)").action(async (domain, opts) => {
|
|
42918
|
+
const provider = resolveProvider(opts.provider);
|
|
42919
|
+
try {
|
|
42920
|
+
if (provider === "cloudflare") {
|
|
42921
|
+
const z2 = await createZone(domain);
|
|
42922
|
+
console.log(`\u2713 Zone created: ${domain} (${z2.id})`);
|
|
42923
|
+
if (z2.nameservers?.length) {
|
|
42924
|
+
console.log(` Name servers:`);
|
|
42925
|
+
for (const ns of z2.nameservers)
|
|
42926
|
+
console.log(` ${ns}`);
|
|
42927
|
+
}
|
|
42541
42928
|
} else {
|
|
42542
|
-
|
|
42543
|
-
|
|
42544
|
-
|
|
42545
|
-
console.log(
|
|
42929
|
+
const z2 = await createHostedZone(domain, opts.comment);
|
|
42930
|
+
console.log(`\u2713 Zone created: ${domain} (${z2.id})`);
|
|
42931
|
+
if (z2.name_servers?.length) {
|
|
42932
|
+
console.log(` Name servers:`);
|
|
42933
|
+
for (const ns of z2.name_servers)
|
|
42934
|
+
console.log(` ${ns}`);
|
|
42546
42935
|
}
|
|
42547
42936
|
}
|
|
42548
|
-
} catch (
|
|
42549
|
-
console.error(`
|
|
42937
|
+
} catch (e3) {
|
|
42938
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42939
|
+
process.exit(1);
|
|
42940
|
+
}
|
|
42941
|
+
});
|
|
42942
|
+
zone.command("info <zoneId>").description("Get details of a hosted zone").option("--provider <name>", "DNS provider").option("--json", "Output JSON").action(async (zoneId, opts) => {
|
|
42943
|
+
const provider = resolveProvider(opts.provider);
|
|
42944
|
+
try {
|
|
42945
|
+
if (provider === "cloudflare") {
|
|
42946
|
+
const zones = await listZones();
|
|
42947
|
+
const z2 = zones.find((z3) => z3.id === zoneId || z3.name === zoneId);
|
|
42948
|
+
if (!z2) {
|
|
42949
|
+
console.error(`Zone not found: ${zoneId}`);
|
|
42950
|
+
process.exit(1);
|
|
42951
|
+
}
|
|
42952
|
+
if (opts.json) {
|
|
42953
|
+
console.log(JSON.stringify(z2, null, 2));
|
|
42954
|
+
return;
|
|
42955
|
+
}
|
|
42956
|
+
console.log(`
|
|
42957
|
+
Zone: ${z2.name}`);
|
|
42958
|
+
console.log(` ID: ${z2.id}`);
|
|
42959
|
+
console.log(` Status: ${z2.status}`);
|
|
42960
|
+
if (z2.nameservers?.length) {
|
|
42961
|
+
console.log(` Name servers:`);
|
|
42962
|
+
for (const ns of z2.nameservers)
|
|
42963
|
+
console.log(` ${ns}`);
|
|
42964
|
+
}
|
|
42965
|
+
console.log();
|
|
42966
|
+
} else {
|
|
42967
|
+
const z2 = await getHostedZone(zoneId);
|
|
42968
|
+
if (opts.json) {
|
|
42969
|
+
console.log(JSON.stringify(z2, null, 2));
|
|
42970
|
+
return;
|
|
42971
|
+
}
|
|
42972
|
+
console.log(`
|
|
42973
|
+
Zone: ${z2.name}`);
|
|
42974
|
+
console.log(` ID: ${z2.id}`);
|
|
42975
|
+
console.log(` Records: ${z2.record_count}`);
|
|
42976
|
+
if (z2.comment)
|
|
42977
|
+
console.log(` Comment: ${z2.comment}`);
|
|
42978
|
+
if (z2.name_servers?.length) {
|
|
42979
|
+
console.log(` Name servers:`);
|
|
42980
|
+
for (const ns of z2.name_servers)
|
|
42981
|
+
console.log(` ${ns}`);
|
|
42982
|
+
}
|
|
42983
|
+
console.log();
|
|
42984
|
+
}
|
|
42985
|
+
} catch (e3) {
|
|
42986
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42987
|
+
process.exit(1);
|
|
42988
|
+
}
|
|
42989
|
+
});
|
|
42990
|
+
zone.command("delete <zoneId>").description("Delete a hosted zone (irreversible \u2014 requires --force)").option("--provider <name>", "DNS provider").option("--force", "Confirm deletion").action(async (zoneId, opts) => {
|
|
42991
|
+
const provider = resolveProvider(opts.provider);
|
|
42992
|
+
try {
|
|
42993
|
+
if (!opts.force) {
|
|
42994
|
+
console.log(`Would delete zone: ${zoneId} (${provider})`);
|
|
42995
|
+
console.log("Re-run with --force to confirm.");
|
|
42996
|
+
return;
|
|
42997
|
+
}
|
|
42998
|
+
if (provider === "cloudflare") {
|
|
42999
|
+
await deleteZone(zoneId);
|
|
43000
|
+
} else {
|
|
43001
|
+
await deleteHostedZone(zoneId);
|
|
43002
|
+
}
|
|
43003
|
+
console.log(`\u2713 Zone deleted: ${zoneId}`);
|
|
43004
|
+
} catch (e3) {
|
|
43005
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42550
43006
|
process.exit(1);
|
|
42551
43007
|
}
|
|
42552
43008
|
});
|
|
42553
43009
|
}
|
|
42554
43010
|
|
|
42555
|
-
// src/
|
|
42556
|
-
|
|
42557
|
-
|
|
42558
|
-
|
|
42559
|
-
|
|
42560
|
-
|
|
42561
|
-
|
|
42562
|
-
|
|
42563
|
-
|
|
42564
|
-
|
|
42565
|
-
}
|
|
42566
|
-
|
|
42567
|
-
|
|
42568
|
-
|
|
42569
|
-
|
|
42570
|
-
|
|
42571
|
-
|
|
42572
|
-
|
|
43011
|
+
// src/cli/commands/ssl.ts
|
|
43012
|
+
function registerSslCommand(program2) {
|
|
43013
|
+
const ssl = program2.command("ssl").description("SSL certificate management");
|
|
43014
|
+
ssl.command("check <domain>").description("Check SSL certificate for a domain and update the local DB record").option("--json", "Output JSON").action((domain, opts) => {
|
|
43015
|
+
const result = checkSsl(domain);
|
|
43016
|
+
if (opts.json) {
|
|
43017
|
+
console.log(JSON.stringify(result, null, 2));
|
|
43018
|
+
return;
|
|
43019
|
+
}
|
|
43020
|
+
console.log(`
|
|
43021
|
+
SSL Certificate for ${result.domain}:`);
|
|
43022
|
+
if (result.error) {
|
|
43023
|
+
console.log(` Error: ${result.error}`);
|
|
43024
|
+
} else {
|
|
43025
|
+
console.log(` Issuer: ${result.issuer ?? "unknown"}`);
|
|
43026
|
+
console.log(` Expires: ${result.expires_at ? result.expires_at.split("T")[0] : "unknown"}`);
|
|
43027
|
+
}
|
|
43028
|
+
console.log();
|
|
43029
|
+
});
|
|
43030
|
+
ssl.command("expiring").description("List domains with SSL certificates expiring soon").option("--days <n>", "Days threshold", "30").option("--json", "Output JSON").action((opts) => {
|
|
43031
|
+
const domains = listSslExpiring(parseInt(opts.days));
|
|
43032
|
+
if (opts.json) {
|
|
43033
|
+
console.log(JSON.stringify(domains, null, 2));
|
|
43034
|
+
return;
|
|
43035
|
+
}
|
|
43036
|
+
if (domains.length === 0) {
|
|
43037
|
+
console.log(`No SSL certificates expiring within ${opts.days} days.`);
|
|
43038
|
+
return;
|
|
43039
|
+
}
|
|
43040
|
+
console.log(`
|
|
43041
|
+
SSL expiring within ${opts.days} days:`);
|
|
43042
|
+
for (const d3 of domains) {
|
|
43043
|
+
const exp = d3.ssl_expires_at ? d3.ssl_expires_at.split("T")[0] : "unknown";
|
|
43044
|
+
console.log(` ${d3.name.padEnd(40)} expires ${exp}`);
|
|
43045
|
+
}
|
|
43046
|
+
console.log();
|
|
43047
|
+
});
|
|
42573
43048
|
}
|
|
42574
|
-
|
|
42575
|
-
|
|
42576
|
-
|
|
42577
|
-
const
|
|
42578
|
-
|
|
42579
|
-
|
|
42580
|
-
|
|
42581
|
-
|
|
42582
|
-
|
|
42583
|
-
try {
|
|
42584
|
-
const response = await fetchFn(url, {
|
|
42585
|
-
method: "GET",
|
|
42586
|
-
headers,
|
|
42587
|
-
signal: AbortSignal.timeout(15000)
|
|
43049
|
+
|
|
43050
|
+
// src/cli/commands/alerts.ts
|
|
43051
|
+
function registerAlertCommands(program2) {
|
|
43052
|
+
const alertCmd = program2.command("alert").description("Alert management");
|
|
43053
|
+
alertCmd.command("set").description("Set an alert for a domain").requiredOption("--domain <id>", "Domain ID").requiredOption("--type <type>", "Alert type (expiry/ssl_expiry/dns_change)").option("--days-before <n>", "Trigger N days before").option("--json", "Output as JSON", false).action((opts) => {
|
|
43054
|
+
const alert = createAlert({
|
|
43055
|
+
domain_id: opts.domain,
|
|
43056
|
+
type: opts.type,
|
|
43057
|
+
trigger_days_before: opts.daysBefore ? parseInt(opts.daysBefore) : undefined
|
|
42588
43058
|
});
|
|
42589
|
-
if (
|
|
42590
|
-
|
|
43059
|
+
if (opts.json) {
|
|
43060
|
+
console.log(JSON.stringify(alert, null, 2));
|
|
43061
|
+
} else {
|
|
43062
|
+
const daysBefore = alert.trigger_days_before ? ` (${alert.trigger_days_before} days before)` : "";
|
|
43063
|
+
console.log(`Created alert: ${alert.type}${daysBefore} for domain ${alert.domain_id} (${alert.id})`);
|
|
42591
43064
|
}
|
|
42592
|
-
|
|
42593
|
-
|
|
42594
|
-
|
|
42595
|
-
if (
|
|
42596
|
-
|
|
42597
|
-
|
|
42598
|
-
|
|
42599
|
-
|
|
42600
|
-
|
|
42601
|
-
|
|
42602
|
-
|
|
42603
|
-
|
|
42604
|
-
|
|
42605
|
-
|
|
42606
|
-
|
|
42607
|
-
}
|
|
42608
|
-
|
|
42609
|
-
|
|
42610
|
-
|
|
42611
|
-
|
|
42612
|
-
|
|
42613
|
-
|
|
42614
|
-
|
|
42615
|
-
|
|
42616
|
-
|
|
42617
|
-
|
|
42618
|
-
}
|
|
42619
|
-
function generateStubThreatAssessment(domain) {
|
|
42620
|
-
return {
|
|
42621
|
-
domain,
|
|
42622
|
-
risk_level: "low",
|
|
42623
|
-
threats: [],
|
|
42624
|
-
recommendation: "No immediate threats detected. Continue routine monitoring."
|
|
42625
|
-
};
|
|
42626
|
-
}
|
|
42627
|
-
async function monitorBrand(brandName) {
|
|
42628
|
-
const apiKey = getApiKey();
|
|
42629
|
-
const result = await apiGet(`/brands/${encodeURIComponent(brandName)}/monitor`, apiKey);
|
|
42630
|
-
if (result.stub) {
|
|
42631
|
-
return { brand: brandName, alerts: generateStubAlerts(brandName), stub: true };
|
|
42632
|
-
}
|
|
42633
|
-
return { brand: brandName, alerts: result.data.alerts, stub: false };
|
|
42634
|
-
}
|
|
42635
|
-
async function getSimilarDomains(domain) {
|
|
42636
|
-
const apiKey = getApiKey();
|
|
42637
|
-
const result = await apiGet(`/domains/${encodeURIComponent(domain)}/similar`, apiKey);
|
|
42638
|
-
if (result.stub) {
|
|
42639
|
-
return { domain, similar: generateStubSimilarDomains(domain), stub: true };
|
|
42640
|
-
}
|
|
42641
|
-
return { domain, similar: result.data.similar, stub: false };
|
|
42642
|
-
}
|
|
42643
|
-
async function getThreatAssessment(domain) {
|
|
42644
|
-
const apiKey = getApiKey();
|
|
42645
|
-
const result = await apiGet(`/domains/${encodeURIComponent(domain)}/threats`, apiKey);
|
|
42646
|
-
if (result.stub) {
|
|
42647
|
-
return { ...generateStubThreatAssessment(domain), stub: true };
|
|
42648
|
-
}
|
|
42649
|
-
return { ...result.data, stub: false };
|
|
43065
|
+
});
|
|
43066
|
+
alertCmd.command("list").description("List alerts for a domain").argument("<domain-id>", "Domain ID").option("--json", "Output as JSON", false).action((domainId, opts) => {
|
|
43067
|
+
const alerts = listAlerts(domainId);
|
|
43068
|
+
if (opts.json) {
|
|
43069
|
+
console.log(JSON.stringify(alerts, null, 2));
|
|
43070
|
+
} else {
|
|
43071
|
+
if (alerts.length === 0) {
|
|
43072
|
+
console.log("No alerts set.");
|
|
43073
|
+
return;
|
|
43074
|
+
}
|
|
43075
|
+
for (const a3 of alerts) {
|
|
43076
|
+
const daysBefore = a3.trigger_days_before ? ` (${a3.trigger_days_before} days before)` : "";
|
|
43077
|
+
const sent = a3.sent_at ? ` \u2014 sent ${a3.sent_at}` : "";
|
|
43078
|
+
console.log(` ${a3.type}${daysBefore}${sent}`);
|
|
43079
|
+
}
|
|
43080
|
+
}
|
|
43081
|
+
});
|
|
43082
|
+
alertCmd.command("remove").description("Remove an alert").argument("<id>", "Alert ID").action((id) => {
|
|
43083
|
+
const deleted = deleteAlert(id);
|
|
43084
|
+
if (deleted) {
|
|
43085
|
+
console.log(`Deleted alert ${id}`);
|
|
43086
|
+
} else {
|
|
43087
|
+
console.error(`Alert '${id}' not found.`);
|
|
43088
|
+
process.exit(1);
|
|
43089
|
+
}
|
|
43090
|
+
});
|
|
42650
43091
|
}
|
|
42651
43092
|
|
|
42652
|
-
// src/cli/commands/
|
|
42653
|
-
function
|
|
42654
|
-
program2.command("monitor").description("
|
|
43093
|
+
// src/cli/commands/monitor.ts
|
|
43094
|
+
function registerMonitorCommand(program2) {
|
|
43095
|
+
const monitor = program2.command("monitor").description("Brand monitoring and threat detection (Brandsight)");
|
|
43096
|
+
monitor.command("watch <brand>").description("Monitor a brand for new lookalike domain registrations").option("--json", "Output JSON").action(async (brand, opts) => {
|
|
42655
43097
|
try {
|
|
42656
43098
|
const result = await monitorBrand(brand);
|
|
42657
43099
|
if (opts.json) {
|
|
42658
43100
|
console.log(JSON.stringify(result, null, 2));
|
|
42659
|
-
|
|
42660
|
-
if (result.stub)
|
|
42661
|
-
console.log("(stub data \u2014 Brandsight API unreachable)");
|
|
42662
|
-
console.log(`Brand monitoring for "${result.brand}":`);
|
|
42663
|
-
if (result.alerts.length === 0) {
|
|
42664
|
-
console.log(" No alerts.");
|
|
42665
|
-
} else {
|
|
42666
|
-
for (const a3 of result.alerts) {
|
|
42667
|
-
console.log(` [${a3.type}] ${a3.domain} \u2014 registered ${a3.registered_at}`);
|
|
42668
|
-
}
|
|
42669
|
-
}
|
|
42670
|
-
console.log(`
|
|
42671
|
-
${result.alerts.length} alert(s)`);
|
|
43101
|
+
return;
|
|
42672
43102
|
}
|
|
42673
|
-
|
|
42674
|
-
|
|
43103
|
+
console.log(JSON.stringify(result, null, 2));
|
|
43104
|
+
} catch (e3) {
|
|
43105
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42675
43106
|
process.exit(1);
|
|
42676
43107
|
}
|
|
42677
43108
|
});
|
|
42678
|
-
|
|
43109
|
+
monitor.command("similar <domain>").description("Find typosquat and competing domains similar to a domain").option("--json", "Output JSON").action(async (domain, opts) => {
|
|
42679
43110
|
try {
|
|
42680
43111
|
const result = await getSimilarDomains(domain);
|
|
42681
43112
|
if (opts.json) {
|
|
42682
43113
|
console.log(JSON.stringify(result, null, 2));
|
|
42683
|
-
|
|
42684
|
-
if (result.stub)
|
|
42685
|
-
console.log("(stub data \u2014 Brandsight API unreachable)");
|
|
42686
|
-
console.log(`Similar domains for ${result.domain}:`);
|
|
42687
|
-
for (const d3 of result.similar) {
|
|
42688
|
-
console.log(` ${d3}`);
|
|
42689
|
-
}
|
|
42690
|
-
console.log(`
|
|
42691
|
-
${result.similar.length} similar domain(s)`);
|
|
43114
|
+
return;
|
|
42692
43115
|
}
|
|
42693
|
-
|
|
42694
|
-
|
|
43116
|
+
console.log(JSON.stringify(result, null, 2));
|
|
43117
|
+
} catch (e3) {
|
|
43118
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
42695
43119
|
process.exit(1);
|
|
42696
43120
|
}
|
|
42697
43121
|
});
|
|
42698
|
-
|
|
43122
|
+
monitor.command("threats <domain>").description("Get a threat assessment for a domain (risk level, threats, recommendation)").option("--json", "Output JSON").action(async (domain, opts) => {
|
|
42699
43123
|
try {
|
|
42700
43124
|
const result = await getThreatAssessment(domain);
|
|
42701
43125
|
if (opts.json) {
|
|
42702
43126
|
console.log(JSON.stringify(result, null, 2));
|
|
43127
|
+
return;
|
|
43128
|
+
}
|
|
43129
|
+
console.log(JSON.stringify(result, null, 2));
|
|
43130
|
+
} catch (e3) {
|
|
43131
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
43132
|
+
process.exit(1);
|
|
43133
|
+
}
|
|
43134
|
+
});
|
|
43135
|
+
}
|
|
43136
|
+
|
|
43137
|
+
// src/cli/commands/provider.ts
|
|
43138
|
+
function registerProviderCommand(program2) {
|
|
43139
|
+
const provider = program2.command("provider").description("Configure and test registrar/DNS providers");
|
|
43140
|
+
provider.command("list").description("Show all providers and their configuration status").option("--json", "Output JSON").action((opts) => {
|
|
43141
|
+
const providers = getAvailableProviders();
|
|
43142
|
+
if (opts.json) {
|
|
43143
|
+
console.log(JSON.stringify(providers, null, 2));
|
|
43144
|
+
return;
|
|
43145
|
+
}
|
|
43146
|
+
console.log(`
|
|
43147
|
+
Registrar / DNS Providers:`);
|
|
43148
|
+
for (const p3 of providers) {
|
|
43149
|
+
const status = p3.configured ? "\u2713 configured" : "\u2717 not configured";
|
|
43150
|
+
const type = p3.type === "full" ? "registrar + dns" : p3.type;
|
|
43151
|
+
console.log(` ${p3.name.padEnd(12)} [${type}] ${status}`);
|
|
43152
|
+
if (!p3.configured) {
|
|
43153
|
+
console.log(` Missing: ${p3.envVars.join(", ")}`);
|
|
43154
|
+
}
|
|
43155
|
+
}
|
|
43156
|
+
console.log();
|
|
43157
|
+
});
|
|
43158
|
+
provider.command("test <name>").description("Live-test a provider's credentials").action(async (name) => {
|
|
43159
|
+
const providers = getAvailableProviders();
|
|
43160
|
+
const info = providers.find((p3) => p3.name === name);
|
|
43161
|
+
if (!info) {
|
|
43162
|
+
console.error(`Unknown provider: ${name}`);
|
|
43163
|
+
process.exit(1);
|
|
43164
|
+
}
|
|
43165
|
+
if (!info.configured) {
|
|
43166
|
+
console.error(`\u2717 ${name} is not configured. Set: ${info.envVars.join(", ")}`);
|
|
43167
|
+
process.exit(1);
|
|
43168
|
+
}
|
|
43169
|
+
console.log(`Testing ${name}...`);
|
|
43170
|
+
try {
|
|
43171
|
+
if (info.type === "registrar" || info.type === "full") {
|
|
43172
|
+
const reg = getRegistrarProvider(name);
|
|
43173
|
+
await reg.listDomains();
|
|
43174
|
+
console.log(`\u2713 Registrar connection OK`);
|
|
43175
|
+
}
|
|
43176
|
+
if (info.type === "dns" || info.type === "full") {
|
|
43177
|
+
const dns = getDnsProvider(name);
|
|
43178
|
+
await dns.getDnsRecords("__test_nonexistent_domain__.invalid");
|
|
43179
|
+
console.log(`\u2713 DNS connection OK`);
|
|
43180
|
+
}
|
|
43181
|
+
} catch (e3) {
|
|
43182
|
+
const msg = e3 instanceof Error ? e3.message : String(e3);
|
|
43183
|
+
if (msg.includes("not found") || msg.includes("No hosted zone") || msg.includes("NXDOMAIN")) {
|
|
43184
|
+
console.log(`\u2713 Connection OK (no domains yet)`);
|
|
42703
43185
|
} else {
|
|
42704
|
-
|
|
42705
|
-
|
|
42706
|
-
|
|
42707
|
-
|
|
42708
|
-
|
|
42709
|
-
|
|
42710
|
-
|
|
42711
|
-
|
|
42712
|
-
|
|
43186
|
+
console.error(`\u2717 ${name} test failed: ${msg}`);
|
|
43187
|
+
process.exit(1);
|
|
43188
|
+
}
|
|
43189
|
+
}
|
|
43190
|
+
});
|
|
43191
|
+
}
|
|
43192
|
+
|
|
43193
|
+
// src/cli/commands/config.ts
|
|
43194
|
+
var VALID_KEYS = [
|
|
43195
|
+
"default-registrar",
|
|
43196
|
+
"default-dns",
|
|
43197
|
+
"contact.first-name",
|
|
43198
|
+
"contact.last-name",
|
|
43199
|
+
"contact.email",
|
|
43200
|
+
"contact.phone",
|
|
43201
|
+
"contact.address",
|
|
43202
|
+
"contact.city",
|
|
43203
|
+
"contact.state",
|
|
43204
|
+
"contact.country",
|
|
43205
|
+
"contact.zip",
|
|
43206
|
+
"contact.org"
|
|
43207
|
+
];
|
|
43208
|
+
function registerConfigCommands(program2) {
|
|
43209
|
+
const config = program2.command("config").description("View and set configuration");
|
|
43210
|
+
config.command("show").description("Show current configuration").option("--json", "Output JSON").action((opts) => {
|
|
43211
|
+
const cfg = loadConfig3();
|
|
43212
|
+
if (opts.json) {
|
|
43213
|
+
console.log(JSON.stringify(cfg, null, 2));
|
|
43214
|
+
return;
|
|
43215
|
+
}
|
|
43216
|
+
console.log(`
|
|
43217
|
+
Configuration (~/.hasna/domains/config.json):`);
|
|
43218
|
+
console.log(` default-registrar: ${cfg.default_registrar ?? "(not set)"}`);
|
|
43219
|
+
console.log(` default-dns: ${cfg.default_dns ?? "(not set)"}`);
|
|
43220
|
+
if (cfg.contact && Object.keys(cfg.contact).length > 0) {
|
|
43221
|
+
console.log(`
|
|
43222
|
+
contact:`);
|
|
43223
|
+
for (const [k3, v3] of Object.entries(cfg.contact)) {
|
|
43224
|
+
if (v3)
|
|
43225
|
+
console.log(` ${k3}: ${v3}`);
|
|
43226
|
+
}
|
|
43227
|
+
} else {
|
|
43228
|
+
console.log(" contact: (not set)");
|
|
43229
|
+
}
|
|
43230
|
+
console.log();
|
|
43231
|
+
});
|
|
43232
|
+
config.command("set <key> <value>").description(`Set a config value. Keys: ${VALID_KEYS.join(", ")}`).action((key, value) => {
|
|
43233
|
+
const normalized = key.replace(/-/g, "_");
|
|
43234
|
+
try {
|
|
43235
|
+
setConfigKey(normalized, value);
|
|
43236
|
+
console.log(`\u2713 Set ${key} = ${value}`);
|
|
43237
|
+
} catch (e3) {
|
|
43238
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
43239
|
+
process.exit(1);
|
|
43240
|
+
}
|
|
43241
|
+
});
|
|
43242
|
+
config.command("unset <key>").description("Remove a config value").action((key) => {
|
|
43243
|
+
const normalized = key.replace(/-/g, "_");
|
|
43244
|
+
const cfg = loadConfig3();
|
|
43245
|
+
const parts = normalized.split(".");
|
|
43246
|
+
if (parts.length === 1) {
|
|
43247
|
+
delete cfg[parts[0]];
|
|
43248
|
+
} else if (parts.length === 2 && parts[0] === "contact" && cfg.contact) {
|
|
43249
|
+
delete cfg.contact[parts[1]];
|
|
43250
|
+
}
|
|
43251
|
+
saveConfig(cfg);
|
|
43252
|
+
console.log(`\u2713 Unset ${key}`);
|
|
43253
|
+
});
|
|
43254
|
+
}
|
|
43255
|
+
|
|
43256
|
+
// src/cli/commands/doctor.ts
|
|
43257
|
+
import { execSync as execSync2 } from "child_process";
|
|
43258
|
+
function registerDoctorCommand(program2) {
|
|
43259
|
+
program2.command("doctor").description("Run diagnostics \u2014 check credentials, DB, and provider connectivity").action(async () => {
|
|
43260
|
+
let passed = 0;
|
|
43261
|
+
let failed = 0;
|
|
43262
|
+
function ok(msg) {
|
|
43263
|
+
console.log(` \u2713 ${msg}`);
|
|
43264
|
+
passed++;
|
|
43265
|
+
}
|
|
43266
|
+
function fail(msg, fix) {
|
|
43267
|
+
console.log(` \u2717 ${msg}`);
|
|
43268
|
+
if (fix)
|
|
43269
|
+
console.log(` \u2192 ${fix}`);
|
|
43270
|
+
failed++;
|
|
43271
|
+
}
|
|
43272
|
+
console.log(`
|
|
43273
|
+
\u2500\u2500 Database \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
43274
|
+
try {
|
|
43275
|
+
const count = countDomains();
|
|
43276
|
+
ok(`Local DB accessible (${count} domain${count !== 1 ? "s" : ""})`);
|
|
43277
|
+
} catch (e3) {
|
|
43278
|
+
fail("Local DB not accessible", `Check ~/.hasna/domains/domains.db`);
|
|
43279
|
+
}
|
|
43280
|
+
console.log(`
|
|
43281
|
+
\u2500\u2500 Config \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
43282
|
+
const cfg = loadConfig3();
|
|
43283
|
+
if (cfg.default_registrar)
|
|
43284
|
+
ok(`default-registrar: ${cfg.default_registrar}`);
|
|
43285
|
+
else
|
|
43286
|
+
fail("No default registrar set", "domains config set default-registrar route53");
|
|
43287
|
+
if (cfg.default_dns)
|
|
43288
|
+
ok(`default-dns: ${cfg.default_dns}`);
|
|
43289
|
+
else
|
|
43290
|
+
fail("No default DNS provider set", "domains config set default-dns cloudflare");
|
|
43291
|
+
const contactFields = ["first_name", "last_name", "email", "phone", "address_line_1", "city", "state", "country_code", "zip_code"];
|
|
43292
|
+
const missingContact = contactFields.filter((f3) => !cfg.contact?.[f3]);
|
|
43293
|
+
if (missingContact.length === 0)
|
|
43294
|
+
ok("Registrant contact info complete");
|
|
43295
|
+
else
|
|
43296
|
+
fail(`Missing contact fields: ${missingContact.join(", ")}`, "domains config set contact.<field> <value>");
|
|
43297
|
+
console.log(`
|
|
43298
|
+
\u2500\u2500 Providers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
43299
|
+
const providers = getAvailableProviders().filter((p3) => p3.name !== "brandsight");
|
|
43300
|
+
for (const p3 of providers) {
|
|
43301
|
+
if (!p3.configured) {
|
|
43302
|
+
fail(`${p3.name}: not configured`, `Set: ${p3.envVars.join(", ")}`);
|
|
43303
|
+
continue;
|
|
43304
|
+
}
|
|
43305
|
+
try {
|
|
43306
|
+
if (p3.type === "registrar" || p3.type === "full") {
|
|
43307
|
+
const reg = getRegistrarProvider(p3.name);
|
|
43308
|
+
await reg.listDomains();
|
|
42713
43309
|
} else {
|
|
42714
|
-
|
|
43310
|
+
const dns = getDnsProvider(p3.name);
|
|
43311
|
+
await dns.getDnsRecords("__test__.invalid");
|
|
43312
|
+
}
|
|
43313
|
+
ok(`${p3.name}: connected`);
|
|
43314
|
+
} catch (e3) {
|
|
43315
|
+
const msg = e3 instanceof Error ? e3.message : String(e3);
|
|
43316
|
+
if (msg.includes("not found") || msg.includes("No hosted zone") || msg.includes("No Cloudflare zone")) {
|
|
43317
|
+
ok(`${p3.name}: connected (no domains yet)`);
|
|
43318
|
+
} else {
|
|
43319
|
+
fail(`${p3.name}: ${msg}`);
|
|
42715
43320
|
}
|
|
42716
|
-
console.log(` Recommendation: ${result.recommendation}`);
|
|
42717
43321
|
}
|
|
42718
|
-
}
|
|
42719
|
-
|
|
43322
|
+
}
|
|
43323
|
+
console.log(`
|
|
43324
|
+
\u2500\u2500 Tools \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
43325
|
+
try {
|
|
43326
|
+
execSync2("whois --version 2>/dev/null || whois example.com 2>/dev/null", { timeout: 3000 });
|
|
43327
|
+
ok("whois binary found");
|
|
43328
|
+
} catch {
|
|
43329
|
+
fail("whois not installed", "apt install whois / brew install whois");
|
|
43330
|
+
}
|
|
43331
|
+
console.log(`
|
|
43332
|
+
${"\u2500".repeat(45)}`);
|
|
43333
|
+
console.log(` ${passed} passed / ${failed} failed
|
|
43334
|
+
`);
|
|
43335
|
+
if (failed > 0)
|
|
42720
43336
|
process.exit(1);
|
|
43337
|
+
});
|
|
43338
|
+
}
|
|
43339
|
+
|
|
43340
|
+
// src/cli/commands/mcp-install.ts
|
|
43341
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
43342
|
+
import { homedir as homedir9 } from "os";
|
|
43343
|
+
import { join as join9 } from "path";
|
|
43344
|
+
import { execSync as execSync3 } from "child_process";
|
|
43345
|
+
var MCP_SERVER_NAME = "domains";
|
|
43346
|
+
function getClaudeConfigPaths() {
|
|
43347
|
+
return {
|
|
43348
|
+
global: join9(homedir9(), ".claude", "claude_desktop_config.json"),
|
|
43349
|
+
project: join9(process.cwd(), ".claude", "settings.json")
|
|
43350
|
+
};
|
|
43351
|
+
}
|
|
43352
|
+
function getMcpBinaryPath() {
|
|
43353
|
+
try {
|
|
43354
|
+
return execSync3("which domains-mcp", { encoding: "utf-8" }).trim();
|
|
43355
|
+
} catch {
|
|
43356
|
+
return "domains-mcp";
|
|
43357
|
+
}
|
|
43358
|
+
}
|
|
43359
|
+
function registerMcpCommand(program2) {
|
|
43360
|
+
const mcp = program2.command("mcp").description("MCP server management for AI agents");
|
|
43361
|
+
mcp.command("install").description("Register domains MCP server with Claude Code").option("--project", "Install in project .claude/settings.json instead of global config").action((opts) => {
|
|
43362
|
+
const paths = getClaudeConfigPaths();
|
|
43363
|
+
const configPath2 = opts.project ? paths.project : paths.global;
|
|
43364
|
+
const binary = getMcpBinaryPath();
|
|
43365
|
+
let config = {};
|
|
43366
|
+
if (existsSync7(configPath2)) {
|
|
43367
|
+
try {
|
|
43368
|
+
config = JSON.parse(readFileSync6(configPath2, "utf-8"));
|
|
43369
|
+
} catch {
|
|
43370
|
+
config = {};
|
|
43371
|
+
}
|
|
43372
|
+
}
|
|
43373
|
+
const mcpServers = config.mcpServers ?? {};
|
|
43374
|
+
mcpServers[MCP_SERVER_NAME] = { command: binary, args: [] };
|
|
43375
|
+
config.mcpServers = mcpServers;
|
|
43376
|
+
writeFileSync4(configPath2, JSON.stringify(config, null, 2), "utf-8");
|
|
43377
|
+
console.log(`\u2713 MCP server registered: ${MCP_SERVER_NAME}`);
|
|
43378
|
+
console.log(` Config: ${configPath2}`);
|
|
43379
|
+
console.log(` Binary: ${binary}`);
|
|
43380
|
+
console.log(`
|
|
43381
|
+
Restart Claude Code to activate.`);
|
|
43382
|
+
});
|
|
43383
|
+
mcp.command("uninstall").description("Remove domains MCP server from Claude Code config").option("--project", "Remove from project config instead of global").action((opts) => {
|
|
43384
|
+
const paths = getClaudeConfigPaths();
|
|
43385
|
+
const configPath2 = opts.project ? paths.project : paths.global;
|
|
43386
|
+
if (!existsSync7(configPath2)) {
|
|
43387
|
+
console.log("Config file not found \u2014 nothing to remove.");
|
|
43388
|
+
return;
|
|
43389
|
+
}
|
|
43390
|
+
const config = JSON.parse(readFileSync6(configPath2, "utf-8"));
|
|
43391
|
+
const mcpServers = config.mcpServers;
|
|
43392
|
+
if (!mcpServers?.[MCP_SERVER_NAME]) {
|
|
43393
|
+
console.log(`MCP server '${MCP_SERVER_NAME}' is not registered.`);
|
|
43394
|
+
return;
|
|
43395
|
+
}
|
|
43396
|
+
delete mcpServers[MCP_SERVER_NAME];
|
|
43397
|
+
writeFileSync4(configPath2, JSON.stringify(config, null, 2), "utf-8");
|
|
43398
|
+
console.log(`\u2713 MCP server removed: ${MCP_SERVER_NAME}`);
|
|
43399
|
+
});
|
|
43400
|
+
mcp.command("status").description("Check if domains MCP server is registered").action(() => {
|
|
43401
|
+
const paths = getClaudeConfigPaths();
|
|
43402
|
+
for (const [label, configPath2] of [["global", paths.global], ["project", paths.project]]) {
|
|
43403
|
+
if (!existsSync7(configPath2)) {
|
|
43404
|
+
console.log(` ${label}: not found`);
|
|
43405
|
+
continue;
|
|
43406
|
+
}
|
|
43407
|
+
try {
|
|
43408
|
+
const config = JSON.parse(readFileSync6(configPath2, "utf-8"));
|
|
43409
|
+
const mcpServers = config.mcpServers;
|
|
43410
|
+
if (mcpServers?.[MCP_SERVER_NAME]) {
|
|
43411
|
+
console.log(` ${label}: \u2713 registered`);
|
|
43412
|
+
} else {
|
|
43413
|
+
console.log(` ${label}: \u2717 not registered`);
|
|
43414
|
+
}
|
|
43415
|
+
} catch {
|
|
43416
|
+
console.log(` ${label}: error reading config`);
|
|
43417
|
+
}
|
|
42721
43418
|
}
|
|
42722
43419
|
});
|
|
42723
43420
|
}
|
|
42724
43421
|
|
|
43422
|
+
// src/cli/commands/serve.ts
|
|
43423
|
+
function registerServeCommand(program2) {
|
|
43424
|
+
const packageVersion = getPackageVersion();
|
|
43425
|
+
program2.command("serve").description("Start HTTP API server").option("--port <n>", "Port to listen on", "3000").option("--host <h>", "Host to bind to", "127.0.0.1").action(async (opts) => {
|
|
43426
|
+
const port = parseInt(opts.port);
|
|
43427
|
+
const server = Bun.serve({
|
|
43428
|
+
port,
|
|
43429
|
+
hostname: opts.host,
|
|
43430
|
+
fetch(req) {
|
|
43431
|
+
const url = new URL(req.url);
|
|
43432
|
+
const path = url.pathname;
|
|
43433
|
+
const method = req.method;
|
|
43434
|
+
const json = (data, status = 200) => new Response(JSON.stringify(data), {
|
|
43435
|
+
status,
|
|
43436
|
+
headers: { "Content-Type": "application/json" }
|
|
43437
|
+
});
|
|
43438
|
+
const notFound = () => json({ error: "Not found" }, 404);
|
|
43439
|
+
try {
|
|
43440
|
+
if (method === "GET" && path === "/health") {
|
|
43441
|
+
return json({ status: "ok", version: packageVersion });
|
|
43442
|
+
}
|
|
43443
|
+
if (method === "GET" && path === "/domains") {
|
|
43444
|
+
const search = url.searchParams.get("search") ?? undefined;
|
|
43445
|
+
const status = url.searchParams.get("status");
|
|
43446
|
+
const domains = listDomains({ search, status });
|
|
43447
|
+
return json({ domains, count: domains.length });
|
|
43448
|
+
}
|
|
43449
|
+
if (method === "POST" && path === "/domains") {
|
|
43450
|
+
return req.json().then((body) => {
|
|
43451
|
+
const domain = createDomain(body);
|
|
43452
|
+
return json(domain, 201);
|
|
43453
|
+
});
|
|
43454
|
+
}
|
|
43455
|
+
const domainMatch = path.match(/^\/domains\/([^/]+)$/);
|
|
43456
|
+
if (domainMatch) {
|
|
43457
|
+
const id = domainMatch[1];
|
|
43458
|
+
if (method === "GET") {
|
|
43459
|
+
const domain = getDomain(id);
|
|
43460
|
+
return domain ? json(domain) : notFound();
|
|
43461
|
+
}
|
|
43462
|
+
if (method === "PUT" || method === "PATCH") {
|
|
43463
|
+
return req.json().then((body) => {
|
|
43464
|
+
const domain = updateDomain(id, body);
|
|
43465
|
+
return domain ? json(domain) : notFound();
|
|
43466
|
+
});
|
|
43467
|
+
}
|
|
43468
|
+
if (method === "DELETE") {
|
|
43469
|
+
const deleted = deleteDomain(id);
|
|
43470
|
+
return json({ id, deleted });
|
|
43471
|
+
}
|
|
43472
|
+
}
|
|
43473
|
+
const dnsMatch = path.match(/^\/domains\/([^/]+)\/dns$/);
|
|
43474
|
+
if (dnsMatch) {
|
|
43475
|
+
const id = dnsMatch[1];
|
|
43476
|
+
if (method === "GET") {
|
|
43477
|
+
const records = listDnsRecords(id);
|
|
43478
|
+
return json({ records, count: records.length });
|
|
43479
|
+
}
|
|
43480
|
+
if (method === "POST") {
|
|
43481
|
+
return req.json().then((body) => {
|
|
43482
|
+
const record = createDnsRecord({ ...body, domain_id: id });
|
|
43483
|
+
return json(record, 201);
|
|
43484
|
+
});
|
|
43485
|
+
}
|
|
43486
|
+
}
|
|
43487
|
+
return notFound();
|
|
43488
|
+
} catch (e3) {
|
|
43489
|
+
return json({ error: e3 instanceof Error ? e3.message : String(e3) }, 500);
|
|
43490
|
+
}
|
|
43491
|
+
}
|
|
43492
|
+
});
|
|
43493
|
+
console.log(`\u2713 domains API server running at http://${opts.host}:${port}`);
|
|
43494
|
+
console.log(` GET /health`);
|
|
43495
|
+
console.log(` GET /domains`);
|
|
43496
|
+
console.log(` POST /domains`);
|
|
43497
|
+
console.log(` GET /domains/:id`);
|
|
43498
|
+
console.log(` PUT /domains/:id`);
|
|
43499
|
+
console.log(` DELETE /domains/:id`);
|
|
43500
|
+
console.log(` GET /domains/:id/dns`);
|
|
43501
|
+
console.log(` POST /domains/:id/dns`);
|
|
43502
|
+
console.log(`
|
|
43503
|
+
Press Ctrl+C to stop.`);
|
|
43504
|
+
await new Promise(() => {});
|
|
43505
|
+
});
|
|
43506
|
+
}
|
|
43507
|
+
|
|
42725
43508
|
// src/cli/commands/route53.ts
|
|
42726
|
-
import { readFileSync as
|
|
43509
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
42727
43510
|
function registerRoute53Commands(program2) {
|
|
42728
43511
|
const r53 = program2.command("r53").description("AWS Route 53 \u2014 domain purchase, hosted zones & DNS");
|
|
42729
43512
|
r53.command("check <domains...>").description("Check if one or more domains are available for purchase").action(async (domains) => {
|
|
@@ -42757,25 +43540,20 @@ function registerRoute53Commands(program2) {
|
|
|
42757
43540
|
process.exit(1);
|
|
42758
43541
|
}
|
|
42759
43542
|
});
|
|
42760
|
-
r53.command("buy <domain>").description("Register (purchase) a domain via Route 53").
|
|
43543
|
+
r53.command("buy <domain>").description("Register (purchase) a domain via Route 53 (contact defaults from: domains config set contact.*)").option("--email <email>", "Registrant email").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--phone <phone>", "Phone (e.g. +40.754013776)").option("--address <addr>", "Street address").option("--city <city>", "City").option("--state <state>", "State/province").option("--country <code>", "Country code (e.g. US, RO)").option("--zip <zip>", "ZIP/postal code").option("--org <name>", "Organization name").option("--years <n>", "Registration years", "1").option("--no-auto-renew", "Disable auto-renewal").action(async (domain, opts) => {
|
|
42761
43544
|
try {
|
|
42762
43545
|
const avail = await checkAvailability3(domain);
|
|
42763
43546
|
if (!avail.available) {
|
|
42764
43547
|
console.error(`\u2717 ${domain} is not available for registration`);
|
|
42765
43548
|
process.exit(1);
|
|
42766
43549
|
}
|
|
42767
|
-
|
|
42768
|
-
|
|
42769
|
-
|
|
42770
|
-
|
|
42771
|
-
|
|
42772
|
-
|
|
42773
|
-
|
|
42774
|
-
state: opts.state,
|
|
42775
|
-
country_code: opts.country,
|
|
42776
|
-
zip_code: opts.zip,
|
|
42777
|
-
organization_name: opts.org
|
|
42778
|
-
};
|
|
43550
|
+
let contact;
|
|
43551
|
+
try {
|
|
43552
|
+
contact = resolveContact(opts);
|
|
43553
|
+
} catch (e3) {
|
|
43554
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
43555
|
+
process.exit(1);
|
|
43556
|
+
}
|
|
42779
43557
|
console.log(`Registering ${domain}...`);
|
|
42780
43558
|
const result = await registerDomain(domain, contact, parseInt(opts.years), opts.autoRenew);
|
|
42781
43559
|
console.log(`\u2713 Registration submitted: ${domain}`);
|
|
@@ -43017,7 +43795,7 @@ DNS Records for ${domain}:`);
|
|
|
43017
43795
|
try {
|
|
43018
43796
|
let raw;
|
|
43019
43797
|
try {
|
|
43020
|
-
raw =
|
|
43798
|
+
raw = readFileSync7(opts.file, "utf-8");
|
|
43021
43799
|
} catch {
|
|
43022
43800
|
console.error(`Could not read file: ${opts.file}`);
|
|
43023
43801
|
process.exit(1);
|
|
@@ -43056,7 +43834,7 @@ DNS Records for ${domain}:`);
|
|
|
43056
43834
|
process.exit(1);
|
|
43057
43835
|
}
|
|
43058
43836
|
});
|
|
43059
|
-
r53.command("full-setup <domain>").description("Buy domain + create zone + sync to DB \u2014 all in one").
|
|
43837
|
+
r53.command("full-setup <domain>").description("Buy domain + create zone + sync to DB \u2014 all in one (contact defaults from: domains config set contact.*)").option("--email <email>", "Registrant email").option("--first-name <name>", "First name").option("--last-name <name>", "Last name").option("--phone <phone>", "Phone").option("--address <addr>", "Street address").option("--city <city>", "City").option("--state <state>", "State/province").option("--country <code>", "Country code").option("--zip <zip>", "ZIP code").option("--org <name>", "Organization name").option("--years <n>", "Registration years", "1").option("--wait", "Poll until registration completes (or fails)").action(async (domain, opts) => {
|
|
43060
43838
|
try {
|
|
43061
43839
|
console.log(`[1/4] Checking availability...`);
|
|
43062
43840
|
const avail = await checkAvailability3(domain);
|
|
@@ -43067,18 +43845,13 @@ DNS Records for ${domain}:`);
|
|
|
43067
43845
|
const price = avail.price ? ` (${avail.currency ?? "USD"} ${avail.price}/yr)` : "";
|
|
43068
43846
|
console.log(` \u2713 Available${price}`);
|
|
43069
43847
|
console.log(`[2/4] Registering domain...`);
|
|
43070
|
-
|
|
43071
|
-
|
|
43072
|
-
|
|
43073
|
-
|
|
43074
|
-
|
|
43075
|
-
|
|
43076
|
-
|
|
43077
|
-
state: opts.state,
|
|
43078
|
-
country_code: opts.country,
|
|
43079
|
-
zip_code: opts.zip,
|
|
43080
|
-
organization_name: opts.org
|
|
43081
|
-
};
|
|
43848
|
+
let contact;
|
|
43849
|
+
try {
|
|
43850
|
+
contact = resolveContact(opts);
|
|
43851
|
+
} catch (e3) {
|
|
43852
|
+
console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
|
|
43853
|
+
process.exit(1);
|
|
43854
|
+
}
|
|
43082
43855
|
const reg = await registerDomain(domain, contact, parseInt(opts.years));
|
|
43083
43856
|
console.log(` \u2713 Submitted (operation: ${reg.operationId})`);
|
|
43084
43857
|
if (opts.wait) {
|
|
@@ -43134,11 +43907,17 @@ DNS Records for ${domain}:`);
|
|
|
43134
43907
|
|
|
43135
43908
|
// src/cli/index.ts
|
|
43136
43909
|
var program2 = new Command;
|
|
43137
|
-
program2.name("domains").description("Domain portfolio and DNS management for AI agents").version(
|
|
43138
|
-
|
|
43910
|
+
program2.name("domains").description("Domain portfolio and DNS management for AI agents").version(getPackageVersion());
|
|
43911
|
+
registerDomainCommand(program2);
|
|
43139
43912
|
registerDnsCommands(program2);
|
|
43913
|
+
registerZoneCommand(program2);
|
|
43914
|
+
registerSslCommand(program2);
|
|
43140
43915
|
registerAlertCommands(program2);
|
|
43141
|
-
|
|
43142
|
-
|
|
43916
|
+
registerMonitorCommand(program2);
|
|
43917
|
+
registerProviderCommand(program2);
|
|
43918
|
+
registerConfigCommands(program2);
|
|
43919
|
+
registerDoctorCommand(program2);
|
|
43920
|
+
registerMcpCommand(program2);
|
|
43921
|
+
registerServeCommand(program2);
|
|
43143
43922
|
registerRoute53Commands(program2);
|
|
43144
43923
|
program2.parse(process.argv);
|