@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.
Files changed (37) hide show
  1. package/dist/cli/commands/config.d.ts +3 -0
  2. package/dist/cli/commands/config.d.ts.map +1 -0
  3. package/dist/cli/commands/dns.d.ts.map +1 -1
  4. package/dist/cli/commands/doctor.d.ts +3 -0
  5. package/dist/cli/commands/doctor.d.ts.map +1 -0
  6. package/dist/cli/commands/domain.d.ts +3 -0
  7. package/dist/cli/commands/domain.d.ts.map +1 -0
  8. package/dist/cli/commands/mcp-install.d.ts +3 -0
  9. package/dist/cli/commands/mcp-install.d.ts.map +1 -0
  10. package/dist/cli/commands/monitor.d.ts +3 -0
  11. package/dist/cli/commands/monitor.d.ts.map +1 -0
  12. package/dist/cli/commands/provider.d.ts +3 -0
  13. package/dist/cli/commands/provider.d.ts.map +1 -0
  14. package/dist/cli/commands/route53.d.ts.map +1 -1
  15. package/dist/cli/commands/serve.d.ts +3 -0
  16. package/dist/cli/commands/serve.d.ts.map +1 -0
  17. package/dist/cli/commands/ssl.d.ts +3 -0
  18. package/dist/cli/commands/ssl.d.ts.map +1 -0
  19. package/dist/cli/commands/zone.d.ts +3 -0
  20. package/dist/cli/commands/zone.d.ts.map +1 -0
  21. package/dist/cli/index.js +1674 -895
  22. package/dist/db/dns-tools.d.ts.map +1 -1
  23. package/dist/index.js +420 -92
  24. package/dist/lib/brandsight.d.ts +42 -2
  25. package/dist/lib/brandsight.d.ts.map +1 -1
  26. package/dist/lib/cloudflare.d.ts +39 -0
  27. package/dist/lib/cloudflare.d.ts.map +1 -0
  28. package/dist/lib/config.d.ts +49 -0
  29. package/dist/lib/config.d.ts.map +1 -0
  30. package/dist/lib/registrar.d.ts +22 -4
  31. package/dist/lib/registrar.d.ts.map +1 -1
  32. package/dist/lib/route53.d.ts +2 -2
  33. package/dist/lib/route53.d.ts.map +1 -1
  34. package/dist/lib/version.d.ts +3 -0
  35. package/dist/lib/version.d.ts.map +1 -0
  36. package/dist/mcp/index.js +23080 -20411
  37. 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((resolve2, reject) => {
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
- resolve2(bytes2);
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((resolve2) => {
3407
- timeoutId = Number(timing.setTimeout(() => resolve2(true), Math.max(MIN_WAIT_TIME, maxContinueTimeoutMs)));
3406
+ new Promise((resolve3) => {
3407
+ timeoutId = Number(timing.setTimeout(() => resolve3(true), Math.max(MIN_WAIT_TIME, maxContinueTimeoutMs)));
3408
3408
  }),
3409
- new Promise((resolve2) => {
3409
+ new Promise((resolve3) => {
3410
3410
  httpRequest.on("continue", () => {
3411
3411
  timing.clearTimeout(timeoutId);
3412
- resolve2(true);
3412
+ resolve3(true);
3413
3413
  });
3414
3414
  httpRequest.on("response", () => {
3415
3415
  timing.clearTimeout(timeoutId);
3416
- resolve2(false);
3416
+ resolve3(false);
3417
3417
  });
3418
3418
  httpRequest.on("error", () => {
3419
3419
  timing.clearTimeout(timeoutId);
3420
- resolve2(false);
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((resolve2, reject) => {
3495
+ this.configProvider = new Promise((resolve3, reject) => {
3496
3496
  if (typeof options === "function") {
3497
3497
  options().then((_options) => {
3498
- resolve2(this.resolveDefaultConfig(_options));
3498
+ resolve3(this.resolveDefaultConfig(_options));
3499
3499
  }).catch(reject);
3500
3500
  } else {
3501
- resolve2(this.resolveDefaultConfig(options));
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 resolve2 = async (arg) => {
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
- resolve2({ response: httpResponse });
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((resolve2, reject) => {
3797
+ this.configProvider = new Promise((resolve3, reject) => {
3798
3798
  if (typeof options === "function") {
3799
3799
  options().then((opts) => {
3800
- resolve2(opts || {});
3800
+ resolve3(opts || {});
3801
3801
  }).catch(reject);
3802
3802
  } else {
3803
- resolve2(options || {});
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 resolve2 = async (arg) => {
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
- resolve2({ response: httpResponse });
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((resolve2, reject) => {
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
- resolve2(bytes);
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((resolve2, reject) => {
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((resolve2, reject) => {
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((resolve2, reject) => {
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
- resolve2(result.substring(dataOffset));
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(resolve2) {
5419
- resolve2(value);
5418
+ return value instanceof P ? value : new P(function(resolve3) {
5419
+ resolve3(value);
5420
5420
  });
5421
5421
  }
5422
- return new (P || (P = Promise))(function(resolve2, reject) {
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 ? resolve2(result.value) : adopt(result.value).then(fulfilled, rejected);
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(resolve2, reject) {
5668
- v = o[n](v), settle(resolve2, reject, v.done, v.value);
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(resolve2, reject, d, v) {
5672
+ function settle(resolve3, reject, d, v) {
5673
5673
  Promise.resolve(v).then(function(v2) {
5674
- resolve2({ value: v2, done: d });
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((resolve2) => DefaultRateLimiter.setTimeoutFn(resolve2, delay));
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 USER_AGENT = "user-agent";
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[USER_AGENT]} ${normalUAValue}` : normalUAValue;
15702
+ headers[X_AMZ_USER_AGENT] = headers[X_AMZ_USER_AGENT] ? `${headers[USER_AGENT2]} ${normalUAValue}` : normalUAValue;
15703
15703
  }
15704
- headers[USER_AGENT] = sdkUserAgentValue;
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((resolve2) => setTimeout(resolve2, delay));
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((resolve2) => setTimeout(resolve2, delay));
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((resolve2, reject) => {
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
- resolve2(Buffer2.concat(chunks));
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((resolve2) => setTimeout(resolve2, delayMs));
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 = (dirname3) => {
17877
+ var getNodeModulesParentDirs = (dirname4) => {
17878
17878
  const cwd = process.cwd();
17879
- if (!dirname3) {
17879
+ if (!dirname4) {
17880
17880
  return [cwd];
17881
17881
  }
17882
- const normalizedPath = node_path.normalize(dirname3);
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 dirname3 = typeof __dirname !== "undefined" ? __dirname : undefined;
17930
- const nodeModulesParentDirs = getNodeModulesParentDirs(dirname3);
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 dirname3, join as join7 } from "path";
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 = dirname3(tokenFilePath);
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": "open-domains/0.0.1" }
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
- function getProvider(name) {
42312
- switch (name) {
42313
- case "namecheap":
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
- configured: !!(process.env["NAMECHEAP_API_KEY"] && process.env["NAMECHEAP_USERNAME"] && process.env["NAMECHEAP_CLIENT_IP"]),
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
- configured: !!(process.env["GODADDY_API_KEY"] && process.env["GODADDY_API_SECRET"]),
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
- configured: !!(process.env["AWS_ACCESS_KEY_ID"] && process.env["AWS_SECRET_ACCESS_KEY"]),
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
- configured: !!process.env["BRANDSIGHT_API_KEY"],
42343
- envVars: ["BRANDSIGHT_API_KEY"]
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
- async function syncAll(dbFns) {
42348
- const available = getAvailableProviders().filter((p3) => p3.configured && (p3.name === "namecheap" || p3.name === "godaddy" || p3.name === "route53"));
42349
- const result = {
42350
- providers: [],
42351
- totalSynced: 0,
42352
- totalErrors: []
42353
- };
42354
- for (const info of available) {
42355
- try {
42356
- const provider = getProvider(info.name);
42357
- const syncResult = await provider.syncToLocalDb(dbFns);
42358
- result.providers.push({ name: info.name, result: syncResult });
42359
- result.totalSynced += syncResult.synced;
42360
- result.totalErrors.push(...syncResult.errors.map((e3) => `[${info.name}] ${e3}`));
42361
- } catch (error) {
42362
- const msg = `[${info.name}] Sync failed: ${error instanceof Error ? error.message : String(error)}`;
42363
- result.totalErrors.push(msg);
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 || !dbDomain.registrar)
42277
+ if (!dbDomain?.registrar)
42375
42278
  return null;
42376
- const registrar = dbDomain.registrar.toLowerCase();
42377
- if (registrar.includes("namecheap"))
42279
+ const r3 = dbDomain.registrar.toLowerCase();
42280
+ if (r3.includes("namecheap"))
42378
42281
  return "namecheap";
42379
- if (registrar.includes("godaddy"))
42282
+ if (r3.includes("godaddy"))
42380
42283
  return "godaddy";
42381
- if (registrar.includes("route 53") || registrar.includes("route53") || registrar.includes("aws"))
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/cli/commands/providers.ts
42387
- function registerProviderCommands(program2) {
42388
- program2.command("providers").description("Show which registrar providers are configured").option("--json", "Output as JSON", false).action((opts) => {
42389
- const providers = getAvailableProviders();
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(providers, null, 2));
42392
- } else {
42393
- console.log("Registrar Providers:");
42394
- for (const p3 of providers) {
42395
- const status = p3.configured ? "CONFIGURED" : "not configured";
42396
- console.log(` ${p3.name}: ${status}`);
42397
- if (!p3.configured) {
42398
- console.log(` Missing: ${p3.envVars.join(", ")}`);
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
- program2.command("sync").description("Sync domains from a provider to local DB").option("--provider <provider>", "Provider name (namecheap, godaddy)").option("--all", "Sync from all configured providers").option("--json", "Output as JSON", false).action(async (opts) => {
42404
- if (opts.all) {
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 result = await syncAll({
42407
- getDomainByName,
42408
- createDomain,
42409
- updateDomain
42410
- });
42411
- if (opts.json) {
42412
- console.log(JSON.stringify(result, null, 2));
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
- const provider = (opts.provider || "").toLowerCase();
42432
- if (!provider) {
42433
- console.error("Specify --provider <name> or --all");
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
- if (provider === "namecheap") {
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
- const result = await syncToLocalDb({
42439
- getDomainByName,
42440
- createDomain,
42441
- updateDomain
42442
- });
42443
- if (opts.json) {
42444
- console.log(JSON.stringify(result, null, 2));
42445
- } else {
42446
- console.log(`Synced ${result.synced} domain(s) from Namecheap`);
42447
- for (const d3 of result.domains) {
42448
- console.log(` ${d3}`);
42449
- }
42450
- if (result.errors.length > 0) {
42451
- console.log("Errors:");
42452
- for (const e3 of result.errors) {
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
- } catch (error) {
42458
- console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
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
- } else if (provider === "godaddy") {
42615
+ let contact;
42462
42616
  try {
42463
- const result = await syncToLocalDb2({
42464
- getDomainByName,
42465
- createDomain,
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.error(`Unsupported provider: ${provider}. Supported: namecheap, godaddy`);
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
- program2.command("renew").description("Renew a domain via provider (auto-detects registrar from DB if --provider not given)").argument("<name>", "Domain name (e.g. example.com)").option("--provider <provider>", "Provider name (namecheap, godaddy)").option("--years <n>", "Number of years to renew", "1").option("--json", "Output as JSON", false).action(async (name, opts) => {
42489
- let provider = (opts.provider || "").toLowerCase();
42490
- if (!provider) {
42491
- const detected = autoDetectRegistrar(name, getDomainByName);
42492
- if (!detected) {
42493
- console.error(`Could not auto-detect registrar for '${name}'. Use --provider.`);
42494
- process.exit(1);
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
- if (provider === "namecheap") {
42500
- try {
42501
- const result = await renewDomain(name, parseInt(opts.years));
42502
- if (opts.json) {
42503
- console.log(JSON.stringify(result, null, 2));
42504
- } else {
42505
- console.log(`Renewed ${result.domain} successfully via Namecheap`);
42506
- if (result.chargedAmount)
42507
- console.log(` Charged: $${result.chargedAmount}`);
42508
- if (result.orderId)
42509
- console.log(` Order ID: ${result.orderId}`);
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
- } catch (error) {
42512
- console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
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
- } else if (provider === "godaddy") {
42516
- try {
42517
- const result = await renewDomain2(name);
42518
- if (opts.json) {
42519
- console.log(JSON.stringify(result, null, 2));
42520
- } else {
42521
- console.log(`Renewed ${name} successfully via GoDaddy`);
42522
- if (result.orderId)
42523
- console.log(` Order ID: ${result.orderId}`);
42524
- if (result.total)
42525
- console.log(` Total: $${(result.total / 100).toFixed(2)}`);
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
- } catch (error) {
42528
- console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
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
- } else {
42532
- console.error(`Unsupported provider: ${provider}. Supported: namecheap, godaddy`);
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
- program2.command("check").description("Check domain availability").argument("<name>", "Domain name (e.g. example.com)").option("--json", "Output as JSON", false).action(async (name, opts) => {
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
- const result = await checkAvailability(name);
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(result, null, 2));
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
- if (result.available) {
42543
- console.log(`${result.domain} is AVAILABLE`);
42544
- } else {
42545
- console.log(`${result.domain} is NOT available`);
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 (error) {
42549
- console.error(`Availability check failed: ${error instanceof Error ? error.message : String(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/lib/brandsight.ts
42556
- class BrandsightApiError extends Error {
42557
- statusCode;
42558
- responseBody;
42559
- constructor(message, statusCode, responseBody) {
42560
- super(message);
42561
- this.statusCode = statusCode;
42562
- this.responseBody = responseBody;
42563
- this.name = "BrandsightApiError";
42564
- }
42565
- }
42566
- var _fetchFn = null;
42567
- function getApiKey() {
42568
- const key = process.env["BRANDSIGHT_API_KEY"];
42569
- if (!key) {
42570
- throw new BrandsightApiError("BRANDSIGHT_API_KEY environment variable is not set");
42571
- }
42572
- return key;
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
- async function apiGet(path, apiKey, baseUrl = "https://api.brandsight.com/v1") {
42575
- const fetchFn = _fetchFn || globalThis.fetch;
42576
- const url = `${baseUrl}${path}`;
42577
- const headers = {
42578
- Authorization: `Bearer ${apiKey}`,
42579
- "Content-Type": "application/json",
42580
- Accept: "application/json",
42581
- "User-Agent": "open-domains/0.0.1"
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 (!response.ok) {
42590
- throw new BrandsightApiError(`Brandsight API GET ${path} failed with status ${response.status}`, response.status, await response.text());
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
- const data = await response.json();
42593
- return { data, stub: false };
42594
- } catch (error) {
42595
- if (error instanceof BrandsightApiError)
42596
- throw error;
42597
- return { data: null, stub: true };
42598
- }
42599
- }
42600
- function generateStubAlerts(brandName) {
42601
- const now = new Date().toISOString();
42602
- return [
42603
- { domain: `${brandName}-deals.com`, type: "keyword", registered_at: now },
42604
- { domain: `${brandName.replace(/a/gi, "4").replace(/e/gi, "3")}.com`, type: "homoglyph", registered_at: now },
42605
- { domain: `${brandName}s.com`, type: "typosquat", registered_at: now }
42606
- ];
42607
- }
42608
- function generateStubSimilarDomains(domain) {
42609
- const base = domain.replace(/\.[^.]+$/, "");
42610
- const tld = domain.slice(base.length);
42611
- return [
42612
- `${base}-online${tld}`,
42613
- `${base}s${tld}`,
42614
- `${base.replace(/a/gi, "4")}${tld}`,
42615
- `${base}-app${tld}`,
42616
- `get${base}${tld}`
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/brandsight.ts
42653
- function registerBrandsightCommands(program2) {
42654
- program2.command("monitor").description("Monitor a brand for similar domain registrations (Brandsight)").argument("<brand>", "Brand name to monitor").option("--json", "Output as JSON", false).action(async (brand, opts) => {
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
- } else {
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
- } catch (error) {
42674
- console.error(`Monitor failed: ${error instanceof Error ? error.message : String(error)}`);
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
- program2.command("similar").description("Find typosquat/competing domains similar to a domain (Brandsight)").argument("<domain>", "Domain to check").option("--json", "Output as JSON", false).action(async (domain, opts) => {
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
- } else {
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
- } catch (error) {
42694
- console.error(`Similar domains check failed: ${error instanceof Error ? error.message : String(error)}`);
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
- program2.command("threats").description("Get threat assessment for a domain (Brandsight)").argument("<domain>", "Domain to assess").option("--json", "Output as JSON", false).action(async (domain, opts) => {
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
- if (result.stub)
42705
- console.log("(stub data \u2014 Brandsight API unreachable)");
42706
- console.log(`Threat Assessment for ${result.domain}:`);
42707
- console.log(` Risk Level: ${result.risk_level}`);
42708
- if (result.threats.length > 0) {
42709
- console.log(" Threats:");
42710
- for (const t3 of result.threats) {
42711
- console.log(` - ${t3}`);
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
- console.log(" Threats: none detected");
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
- } catch (error) {
42719
- console.error(`Threat assessment failed: ${error instanceof Error ? error.message : String(error)}`);
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 readFileSync4 } from "fs";
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").requiredOption("--email <email>", "Registrant email").requiredOption("--first-name <name>", "First name").requiredOption("--last-name <name>", "Last name").requiredOption("--phone <phone>", "Phone (e.g. +1.5551234567)").requiredOption("--address <addr>", "Street address").requiredOption("--city <city>", "City").requiredOption("--state <state>", "State/province").requiredOption("--country <code>", "Country code (e.g. US, RO)").requiredOption("--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) => {
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
- const contact = {
42768
- first_name: opts.firstName,
42769
- last_name: opts.lastName,
42770
- email: opts.email,
42771
- phone: opts.phone,
42772
- address_line_1: opts.address,
42773
- city: opts.city,
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 = readFileSync4(opts.file, "utf-8");
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").requiredOption("--email <email>", "Registrant email").requiredOption("--first-name <name>", "First name").requiredOption("--last-name <name>", "Last name").requiredOption("--phone <phone>", "Phone").requiredOption("--address <addr>", "Street address").requiredOption("--city <city>", "City").requiredOption("--state <state>", "State/province").requiredOption("--country <code>", "Country code").requiredOption("--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) => {
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
- const contact = {
43071
- first_name: opts.firstName,
43072
- last_name: opts.lastName,
43073
- email: opts.email,
43074
- phone: opts.phone,
43075
- address_line_1: opts.address,
43076
- city: opts.city,
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("0.0.3");
43138
- registerDomainCommands(program2);
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
- registerProviderCommands(program2);
43142
- registerBrandsightCommands(program2);
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);