@hasna/domains 0.0.20 → 0.0.22

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 (49) hide show
  1. package/README.md +140 -63
  2. package/dist/cli/commands/config.d.ts.map +1 -1
  3. package/dist/cli/commands/domain.d.ts.map +1 -1
  4. package/dist/cli/commands/provider.d.ts.map +1 -1
  5. package/dist/cli/commands/providers.d.ts.map +1 -1
  6. package/dist/cli/commands/storage.d.ts +3 -0
  7. package/dist/cli/commands/storage.d.ts.map +1 -0
  8. package/dist/cli/index.js +2099 -550
  9. package/dist/db/domain-records.d.ts +2 -2
  10. package/dist/db/domain-records.d.ts.map +1 -1
  11. package/dist/db/pg-migrations.d.ts +1 -1
  12. package/dist/db/storage-sync.d.ts +55 -0
  13. package/dist/db/storage-sync.d.ts.map +1 -0
  14. package/dist/index.d.ts +3 -3
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +863 -157
  17. package/dist/lib/brandsight.d.ts +46 -2
  18. package/dist/lib/brandsight.d.ts.map +1 -1
  19. package/dist/lib/capability.d.ts +5 -3
  20. package/dist/lib/capability.d.ts.map +1 -1
  21. package/dist/lib/cloudflare-auth.d.ts.map +1 -1
  22. package/dist/lib/cloudflare.d.ts +2 -2
  23. package/dist/lib/cloudflare.d.ts.map +1 -1
  24. package/dist/lib/creds-check.d.ts.map +1 -1
  25. package/dist/lib/env-aliases.d.ts +43 -0
  26. package/dist/lib/env-aliases.d.ts.map +1 -0
  27. package/dist/lib/godaddy.d.ts +4 -4
  28. package/dist/lib/namecheap.d.ts +13 -0
  29. package/dist/lib/namecheap.d.ts.map +1 -1
  30. package/dist/lib/registrar.d.ts +49 -5
  31. package/dist/lib/registrar.d.ts.map +1 -1
  32. package/dist/lib/route53.d.ts +2 -0
  33. package/dist/lib/route53.d.ts.map +1 -1
  34. package/dist/lib/sedo.d.ts +7 -0
  35. package/dist/lib/sedo.d.ts.map +1 -1
  36. package/dist/mcp/index.d.ts.map +1 -1
  37. package/dist/mcp/index.js +14266 -15760
  38. package/dist/mcp/storage-tools.d.ts +3 -0
  39. package/dist/mcp/storage-tools.d.ts.map +1 -0
  40. package/dist/storage.d.ts +5 -0
  41. package/dist/storage.d.ts.map +1 -0
  42. package/dist/storage.js +5703 -0
  43. package/package.json +9 -3
  44. package/dist/cli/commands/cloud.d.ts +0 -3
  45. package/dist/cli/commands/cloud.d.ts.map +0 -1
  46. package/dist/db/cloud-sync.d.ts +0 -33
  47. package/dist/db/cloud-sync.d.ts.map +0 -1
  48. package/dist/mcp/cloud-tools.d.ts +0 -3
  49. package/dist/mcp/cloud-tools.d.ts.map +0 -1
package/dist/cli/index.js CHANGED
@@ -993,7 +993,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
993
993
  this._exitCallback = (err) => {
994
994
  if (err.code !== "commander.executeSubCommandAsync") {
995
995
  throw err;
996
- } else {}
996
+ }
997
997
  };
998
998
  }
999
999
  return this;
@@ -2344,31 +2344,31 @@ var init_migrations = __esm(() => {
2344
2344
 
2345
2345
  // src/db/database.ts
2346
2346
  import { Database } from "bun:sqlite";
2347
- import { copyFileSync, existsSync, mkdirSync, readdirSync, statSync } from "fs";
2348
- import { dirname, join, resolve } from "path";
2349
- import { homedir } from "os";
2347
+ import { copyFileSync, existsSync as existsSync2, mkdirSync, readdirSync, statSync } from "fs";
2348
+ import { dirname, join as join2, resolve } from "path";
2349
+ import { homedir as homedir2 } from "os";
2350
2350
  function getDbPath() {
2351
2351
  if (process.env["HASNA_DOMAINS_DB_PATH"])
2352
2352
  return process.env["HASNA_DOMAINS_DB_PATH"];
2353
2353
  const explicit = process.env["HASNA_DOMAINS_DIR"] ?? process.env["DOMAINS_DIR"];
2354
2354
  if (explicit) {
2355
- return join(explicit, "domains.db");
2355
+ return join2(explicit, "domains.db");
2356
2356
  }
2357
- const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
2357
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir2();
2358
2358
  migrateDotfile("domains");
2359
- return join(home, ".hasna", "domains", "domains.db");
2359
+ return join2(home, ".hasna", "domains", "domains.db");
2360
2360
  }
2361
2361
  function migrateDotfile(name) {
2362
- const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir();
2363
- const oldDir = join(home, `.${name}`);
2364
- const newDir = join(home, ".hasna", name);
2365
- if (!existsSync(oldDir) || existsSync(newDir))
2362
+ const home = process.env["HOME"] || process.env["USERPROFILE"] || homedir2();
2363
+ const oldDir = join2(home, `.${name}`);
2364
+ const newDir = join2(home, ".hasna", name);
2365
+ if (!existsSync2(oldDir) || existsSync2(newDir))
2366
2366
  return;
2367
2367
  mkdirSync(newDir, { recursive: true });
2368
2368
  for (const file of readdirSync(oldDir)) {
2369
- const oldPath = join(oldDir, file);
2369
+ const oldPath = join2(oldDir, file);
2370
2370
  if (statSync(oldPath).isFile())
2371
- copyFileSync(oldPath, join(newDir, file));
2371
+ copyFileSync(oldPath, join2(newDir, file));
2372
2372
  }
2373
2373
  }
2374
2374
  function getDatabase() {
@@ -3541,6 +3541,146 @@ var init_domains = __esm(() => {
3541
3541
  init_monitoring();
3542
3542
  });
3543
3543
 
3544
+ // src/lib/godaddy.ts
3545
+ function getCredentials() {
3546
+ const apiKey = process.env["GODADDY_API_KEY"];
3547
+ const apiSecret = process.env["GODADDY_API_SECRET"];
3548
+ if (!apiKey || !apiSecret) {
3549
+ throw new Error("GoDaddy API credentials not configured. Set GODADDY_API_KEY and GODADDY_API_SECRET environment variables.");
3550
+ }
3551
+ return { apiKey, apiSecret };
3552
+ }
3553
+ function getHeaders() {
3554
+ const { apiKey, apiSecret } = getCredentials();
3555
+ return {
3556
+ Authorization: `sso-key ${apiKey}:${apiSecret}`,
3557
+ "Content-Type": "application/json",
3558
+ Accept: "application/json"
3559
+ };
3560
+ }
3561
+ async function apiRequest2(method, path, body) {
3562
+ const fetchFn = _overriddenFetch || globalThis.fetch;
3563
+ const url = `${GODADDY_API_BASE}${path}`;
3564
+ const headers = getHeaders();
3565
+ const options = { method, headers };
3566
+ if (body !== undefined) {
3567
+ options.body = JSON.stringify(body);
3568
+ }
3569
+ const response = await fetchFn(url, options);
3570
+ if (!response.ok) {
3571
+ const text = await response.text();
3572
+ throw new GoDaddyApiError(`GoDaddy API ${method} ${path} failed with status ${response.status}: ${text}`, response.status, { responseBody: text });
3573
+ }
3574
+ if (response.status === 204) {
3575
+ return;
3576
+ }
3577
+ return await response.json();
3578
+ }
3579
+ async function listGoDaddyDomains() {
3580
+ return apiRequest2("GET", "/v1/domains");
3581
+ }
3582
+ async function getDomainInfo2(domain) {
3583
+ return apiRequest2("GET", `/v1/domains/${encodeURIComponent(domain)}`);
3584
+ }
3585
+ async function renewDomain2(domain) {
3586
+ return apiRequest2("POST", `/v1/domains/${encodeURIComponent(domain)}/renew`, { period: 1 });
3587
+ }
3588
+ async function getDnsRecords2(domain, type) {
3589
+ const path = type ? `/v1/domains/${encodeURIComponent(domain)}/records/${encodeURIComponent(type)}` : `/v1/domains/${encodeURIComponent(domain)}/records`;
3590
+ return apiRequest2("GET", path);
3591
+ }
3592
+ async function setDnsRecords2(domain, records) {
3593
+ await apiRequest2("PUT", `/v1/domains/${encodeURIComponent(domain)}/records`, records);
3594
+ }
3595
+ async function checkAvailability2(domain) {
3596
+ return apiRequest2("GET", `/v1/domains/available?domain=${encodeURIComponent(domain)}`);
3597
+ }
3598
+ function mapGoDaddyStatus(gdStatus) {
3599
+ const s = gdStatus.toUpperCase();
3600
+ if (s === "ACTIVE")
3601
+ return "active";
3602
+ if (s === "EXPIRED")
3603
+ return "expired";
3604
+ if (s === "TRANSFERRED_OUT" || s === "TRANSFERRING" || s === "PENDING_TRANSFER")
3605
+ return "transferring";
3606
+ if (s === "REDEMPTION" || s === "PENDING_REDEMPTION")
3607
+ return "redemption";
3608
+ return "active";
3609
+ }
3610
+ async function syncToLocalDb2(dbFns) {
3611
+ const result = {
3612
+ synced: 0,
3613
+ created: 0,
3614
+ updated: 0,
3615
+ errors: []
3616
+ };
3617
+ let gdDomains;
3618
+ try {
3619
+ gdDomains = await listGoDaddyDomains();
3620
+ } catch (err) {
3621
+ result.errors.push(`Failed to list domains: ${err instanceof Error ? err.message : String(err)}`);
3622
+ return result;
3623
+ }
3624
+ for (const gd of gdDomains) {
3625
+ try {
3626
+ let detail;
3627
+ try {
3628
+ detail = await getDomainInfo2(gd.domain);
3629
+ } catch {
3630
+ detail = gd;
3631
+ }
3632
+ const existing = dbFns.getDomainByName(gd.domain);
3633
+ const domainData = {
3634
+ name: gd.domain,
3635
+ registrar: "GoDaddy",
3636
+ status: mapGoDaddyStatus(gd.status),
3637
+ expires_at: gd.expires ? new Date(gd.expires).toISOString() : undefined,
3638
+ auto_renew: gd.renewAuto,
3639
+ nameservers: gd.nameServers || [],
3640
+ registered_at: detail.createdAt ? new Date(detail.createdAt).toISOString() : undefined,
3641
+ metadata: {
3642
+ godaddy_domain_id: detail.domainId,
3643
+ provider: "godaddy",
3644
+ locked: detail.locked,
3645
+ privacy: detail.privacy
3646
+ }
3647
+ };
3648
+ if (existing) {
3649
+ dbFns.updateDomain(existing.id, domainData);
3650
+ result.updated++;
3651
+ } else {
3652
+ dbFns.createDomain(domainData);
3653
+ result.created++;
3654
+ }
3655
+ result.synced++;
3656
+ } catch (err) {
3657
+ result.errors.push(`Failed to sync ${gd.domain}: ${err instanceof Error ? err.message : String(err)}`);
3658
+ }
3659
+ }
3660
+ return result;
3661
+ }
3662
+ function godaddyCapability(env = process.env) {
3663
+ const configured = !!(env["GODADDY_API_KEY"] && env["GODADDY_API_SECRET"]);
3664
+ return {
3665
+ configured,
3666
+ gated: true,
3667
+ notes: configured ? "Credentials present; DNS/domain management may work with qualifying account access, but availability remains threshold-gated and direct automated purchase is not exposed here. Prefer Route53 for purchases." : "Not configured; GoDaddy production availability remains threshold-gated and DNS/domain management requires qualifying account access."
3668
+ };
3669
+ }
3670
+ var GoDaddyApiError, _overriddenFetch = null, GODADDY_API_BASE = "https://api.godaddy.com";
3671
+ var init_godaddy = __esm(() => {
3672
+ GoDaddyApiError = class GoDaddyApiError extends Error {
3673
+ statusCode;
3674
+ details;
3675
+ constructor(message, statusCode, details) {
3676
+ super(message);
3677
+ this.statusCode = statusCode;
3678
+ this.details = details;
3679
+ this.name = "GoDaddyApiError";
3680
+ }
3681
+ };
3682
+ });
3683
+
3544
3684
  // node_modules/@smithy/types/dist-cjs/index.js
3545
3685
  var require_dist_cjs = __commonJS((exports) => {
3546
3686
  exports.HttpAuthLocation = undefined;
@@ -7316,11 +7456,11 @@ var require_randomUUID = __commonJS((exports) => {
7316
7456
 
7317
7457
  // node_modules/@smithy/uuid/dist-cjs/index.js
7318
7458
  var require_dist_cjs19 = __commonJS((exports) => {
7319
- var randomUUID = require_randomUUID();
7459
+ var randomUUID3 = require_randomUUID();
7320
7460
  var decimalToHex = Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0"));
7321
7461
  var v4 = () => {
7322
- if (randomUUID.randomUUID) {
7323
- return randomUUID.randomUUID();
7462
+ if (randomUUID3.randomUUID) {
7463
+ return randomUUID3.randomUUID();
7324
7464
  }
7325
7465
  const rnds = new Uint8Array(16);
7326
7466
  crypto.getRandomValues(rnds);
@@ -7483,19 +7623,19 @@ var require_serde = __commonJS((exports) => {
7483
7623
  };
7484
7624
  var strictParseDouble = (value) => {
7485
7625
  if (typeof value == "string") {
7486
- return expectNumber(parseNumber(value));
7626
+ return expectNumber(parseNumber2(value));
7487
7627
  }
7488
7628
  return expectNumber(value);
7489
7629
  };
7490
7630
  var strictParseFloat = strictParseDouble;
7491
7631
  var strictParseFloat32 = (value) => {
7492
7632
  if (typeof value == "string") {
7493
- return expectFloat32(parseNumber(value));
7633
+ return expectFloat32(parseNumber2(value));
7494
7634
  }
7495
7635
  return expectFloat32(value);
7496
7636
  };
7497
7637
  var NUMBER_REGEX = /(-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)|(-?Infinity)|(NaN)/g;
7498
- var parseNumber = (value) => {
7638
+ var parseNumber2 = (value) => {
7499
7639
  const matches = value.match(NUMBER_REGEX);
7500
7640
  if (matches === null || matches[0].length !== value.length) {
7501
7641
  throw new TypeError(`Expected real number, got implicit NaN`);
@@ -7530,26 +7670,26 @@ var require_serde = __commonJS((exports) => {
7530
7670
  };
7531
7671
  var strictParseLong = (value) => {
7532
7672
  if (typeof value === "string") {
7533
- return expectLong(parseNumber(value));
7673
+ return expectLong(parseNumber2(value));
7534
7674
  }
7535
7675
  return expectLong(value);
7536
7676
  };
7537
7677
  var strictParseInt = strictParseLong;
7538
7678
  var strictParseInt32 = (value) => {
7539
7679
  if (typeof value === "string") {
7540
- return expectInt32(parseNumber(value));
7680
+ return expectInt32(parseNumber2(value));
7541
7681
  }
7542
7682
  return expectInt32(value);
7543
7683
  };
7544
7684
  var strictParseShort = (value) => {
7545
7685
  if (typeof value === "string") {
7546
- return expectShort(parseNumber(value));
7686
+ return expectShort(parseNumber2(value));
7547
7687
  }
7548
7688
  return expectShort(value);
7549
7689
  };
7550
7690
  var strictParseByte = (value) => {
7551
7691
  if (typeof value === "string") {
7552
- return expectByte(parseNumber(value));
7692
+ return expectByte(parseNumber2(value));
7553
7693
  }
7554
7694
  return expectByte(value);
7555
7695
  };
@@ -10746,8 +10886,8 @@ ${utilHexEncoding.toHex(hashedRequest)}`;
10746
10886
  throw new Error("Resolved credential object is not valid");
10747
10887
  }
10748
10888
  }
10749
- formatDate(now) {
10750
- const longDate = iso8601(now).replace(/[\-:]/g, "");
10889
+ formatDate(now2) {
10890
+ const longDate = iso8601(now2).replace(/[\-:]/g, "");
10751
10891
  return {
10752
10892
  longDate,
10753
10893
  shortDate: longDate.slice(0, 8)
@@ -17519,7 +17659,7 @@ var require_readFile = __commonJS((exports) => {
17519
17659
  var promises_1 = __require("fs/promises");
17520
17660
  exports.filePromises = {};
17521
17661
  exports.fileIntercept = {};
17522
- var readFile = (path, options) => {
17662
+ var readFile2 = (path, options) => {
17523
17663
  if (exports.fileIntercept[path] !== undefined) {
17524
17664
  return exports.fileIntercept[path];
17525
17665
  }
@@ -17528,7 +17668,7 @@ var require_readFile = __commonJS((exports) => {
17528
17668
  }
17529
17669
  return exports.filePromises[path];
17530
17670
  };
17531
- exports.readFile = readFile;
17671
+ exports.readFile = readFile2;
17532
17672
  });
17533
17673
 
17534
17674
  // node_modules/@smithy/shared-ini-file-loader/dist-cjs/index.js
@@ -17538,7 +17678,7 @@ var require_dist_cjs36 = __commonJS((exports) => {
17538
17678
  var getSSOTokenFromFile = require_getSSOTokenFromFile();
17539
17679
  var path = __require("path");
17540
17680
  var types = require_dist_cjs();
17541
- var readFile = require_readFile();
17681
+ var readFile2 = require_readFile();
17542
17682
  var ENV_PROFILE = "AWS_PROFILE";
17543
17683
  var DEFAULT_PROFILE = "default";
17544
17684
  var getProfileName = (init) => init.profile || process.env[ENV_PROFILE] || DEFAULT_PROFILE;
@@ -17622,10 +17762,10 @@ var require_dist_cjs36 = __commonJS((exports) => {
17622
17762
  resolvedConfigFilepath = path.join(homeDir, configFilepath.slice(2));
17623
17763
  }
17624
17764
  const parsedFiles = await Promise.all([
17625
- readFile.readFile(resolvedConfigFilepath, {
17765
+ readFile2.readFile(resolvedConfigFilepath, {
17626
17766
  ignoreCache: init.ignoreCache
17627
17767
  }).then(parseIni).then(getConfigData).catch(swallowError$1),
17628
- readFile.readFile(resolvedFilepath, {
17768
+ readFile2.readFile(resolvedFilepath, {
17629
17769
  ignoreCache: init.ignoreCache
17630
17770
  }).then(parseIni).catch(swallowError$1)
17631
17771
  ]);
@@ -17636,7 +17776,7 @@ var require_dist_cjs36 = __commonJS((exports) => {
17636
17776
  };
17637
17777
  var getSsoSessionData = (data) => Object.entries(data).filter(([key]) => key.startsWith(types.IniSectionType.SSO_SESSION + CONFIG_PREFIX_SEPARATOR)).reduce((acc, [key, value]) => ({ ...acc, [key.substring(key.indexOf(CONFIG_PREFIX_SEPARATOR) + 1)]: value }), {});
17638
17778
  var swallowError = () => ({});
17639
- var loadSsoSessionData = async (init = {}) => readFile.readFile(init.configFilepath ?? getConfigFilepath()).then(parseIni).then(getSsoSessionData).catch(swallowError);
17779
+ var loadSsoSessionData = async (init = {}) => readFile2.readFile(init.configFilepath ?? getConfigFilepath()).then(parseIni).then(getSsoSessionData).catch(swallowError);
17640
17780
  var mergeConfigFiles = (...files) => {
17641
17781
  const merged = {};
17642
17782
  for (const file of files) {
@@ -17656,10 +17796,10 @@ var require_dist_cjs36 = __commonJS((exports) => {
17656
17796
  };
17657
17797
  var externalDataInterceptor = {
17658
17798
  getFileRecord() {
17659
- return readFile.fileIntercept;
17799
+ return readFile2.fileIntercept;
17660
17800
  },
17661
17801
  interceptFile(path2, contents) {
17662
- readFile.fileIntercept[path2] = Promise.resolve(contents);
17802
+ readFile2.fileIntercept[path2] = Promise.resolve(contents);
17663
17803
  },
17664
17804
  getTokenRecord() {
17665
17805
  return getSSOTokenFromFile.tokenIntercept;
@@ -17669,7 +17809,7 @@ var require_dist_cjs36 = __commonJS((exports) => {
17669
17809
  }
17670
17810
  };
17671
17811
  exports.getSSOTokenFromFile = getSSOTokenFromFile.getSSOTokenFromFile;
17672
- exports.readFile = readFile.readFile;
17812
+ exports.readFile = readFile2.readFile;
17673
17813
  exports.CONFIG_PREFIX_SEPARATOR = CONFIG_PREFIX_SEPARATOR;
17674
17814
  exports.DEFAULT_PROFILE = DEFAULT_PROFILE;
17675
17815
  exports.ENV_PROFILE = ENV_PROFILE;
@@ -22581,14 +22721,14 @@ var init_validateTokenKey = __esm(() => {
22581
22721
 
22582
22722
  // node_modules/@aws-sdk/token-providers/dist-es/writeSSOTokenToFile.js
22583
22723
  import { promises as fsPromises } from "fs";
22584
- var import_shared_ini_file_loader, writeFile, writeSSOTokenToFile = (id, ssoToken) => {
22724
+ var import_shared_ini_file_loader, writeFile2, writeSSOTokenToFile = (id, ssoToken) => {
22585
22725
  const tokenFilepath = import_shared_ini_file_loader.getSSOTokenFilepath(id);
22586
22726
  const tokenString = JSON.stringify(ssoToken, null, 2);
22587
- return writeFile(tokenFilepath, tokenString);
22727
+ return writeFile2(tokenFilepath, tokenString);
22588
22728
  };
22589
22729
  var init_writeSSOTokenToFile = __esm(() => {
22590
22730
  import_shared_ini_file_loader = __toESM(require_dist_cjs36(), 1);
22591
- ({ writeFile } = fsPromises);
22731
+ ({ writeFile: writeFile2 } = fsPromises);
22592
22732
  });
22593
22733
 
22594
22734
  // node_modules/@aws-sdk/token-providers/dist-es/fromSso.js
@@ -25378,8 +25518,8 @@ var require_signin = __commonJS((exports) => {
25378
25518
  // node_modules/@aws-sdk/credential-provider-login/dist-es/LoginCredentialsFetcher.js
25379
25519
  import { createHash, createPrivateKey, createPublicKey, sign } from "crypto";
25380
25520
  import { promises as fs2 } from "fs";
25381
- import { homedir as homedir2 } from "os";
25382
- import { dirname as dirname3, join as join2 } from "path";
25521
+ import { homedir as homedir3 } from "os";
25522
+ import { dirname as dirname3, join as join3 } from "path";
25383
25523
  var import_property_provider18, import_protocol_http2, import_shared_ini_file_loader6, LoginCredentialsFetcher;
25384
25524
  var init_LoginCredentialsFetcher = __esm(() => {
25385
25525
  import_property_provider18 = __toESM(require_dist_cjs23(), 1);
@@ -25401,9 +25541,9 @@ var init_LoginCredentialsFetcher = __esm(() => {
25401
25541
  throw new import_property_provider18.CredentialsProviderError(`Failed to load a token for session ${this.loginSession}, please re-authenticate using aws login`, { tryNextLink: false, logger: this.logger });
25402
25542
  }
25403
25543
  const accessToken = token.accessToken;
25404
- const now = Date.now();
25544
+ const now2 = Date.now();
25405
25545
  const expiryTime = new Date(accessToken.expiresAt).getTime();
25406
- const timeUntilExpiry = expiryTime - now;
25546
+ const timeUntilExpiry = expiryTime - now2;
25407
25547
  if (timeUntilExpiry <= LoginCredentialsFetcher.REFRESH_THRESHOLD) {
25408
25548
  return this.refresh(token);
25409
25549
  }
@@ -25539,10 +25679,10 @@ var init_LoginCredentialsFetcher = __esm(() => {
25539
25679
  await fs2.writeFile(tokenFilePath, JSON.stringify(token, null, 2), "utf8");
25540
25680
  }
25541
25681
  getTokenFilePath() {
25542
- const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ?? join2(homedir2(), ".aws", "login", "cache");
25682
+ const directory = process.env.AWS_LOGIN_CACHE_DIRECTORY ?? join3(homedir3(), ".aws", "login", "cache");
25543
25683
  const loginSessionBytes = Buffer.from(this.loginSession, "utf8");
25544
25684
  const loginSessionSha256 = createHash("sha256").update(loginSessionBytes).digest("hex");
25545
- return join2(directory, `${loginSessionSha256}.json`);
25685
+ return join3(directory, `${loginSessionSha256}.json`);
25546
25686
  }
25547
25687
  derToRawSignature(derSignature) {
25548
25688
  let offset = 2;
@@ -34285,11 +34425,32 @@ var init_dist_es14 = __esm(() => {
34285
34425
  });
34286
34426
 
34287
34427
  // src/lib/route53.ts
34428
+ var exports_route53 = {};
34429
+ __export(exports_route53, {
34430
+ upsertRecords: () => upsertRecords,
34431
+ upsertRecord: () => upsertRecord,
34432
+ updateNameservers: () => updateNameservers2,
34433
+ registerDomain: () => registerDomain2,
34434
+ listRegisteredDomains: () => listRegisteredDomains,
34435
+ listRecords: () => listRecords,
34436
+ listHostedZones: () => listHostedZones,
34437
+ getRegistrationStatus: () => getRegistrationStatus,
34438
+ getHostedZone: () => getHostedZone,
34439
+ getDomainDetail: () => getDomainDetail,
34440
+ getConfig: () => getConfig2,
34441
+ findHostedZoneByDomain: () => findHostedZoneByDomain,
34442
+ deleteRecord: () => deleteRecord,
34443
+ deleteHostedZone: () => deleteHostedZone,
34444
+ createRoute53Provider: () => createRoute53Provider,
34445
+ createHostedZone: () => createHostedZone,
34446
+ checkAvailability: () => checkAvailability3
34447
+ });
34288
34448
  function getConfig2() {
34289
34449
  return {
34290
34450
  region: process.env["AWS_REGION"] || "us-east-1",
34291
34451
  accessKeyId: process.env["AWS_ACCESS_KEY_ID"],
34292
- secretAccessKey: process.env["AWS_SECRET_ACCESS_KEY"]
34452
+ secretAccessKey: process.env["AWS_SECRET_ACCESS_KEY"],
34453
+ sessionToken: process.env["AWS_SESSION_TOKEN"]
34293
34454
  };
34294
34455
  }
34295
34456
  function checkCredentials(cfg) {
@@ -34305,7 +34466,7 @@ function makeClients(config) {
34305
34466
  const cfg = config ?? getConfig2();
34306
34467
  checkCredentials(cfg);
34307
34468
  const region = cfg.region || "us-east-1";
34308
- const credentials = cfg.accessKeyId && cfg.secretAccessKey ? { accessKeyId: cfg.accessKeyId, secretAccessKey: cfg.secretAccessKey } : undefined;
34469
+ const credentials = cfg.accessKeyId && cfg.secretAccessKey ? { accessKeyId: cfg.accessKeyId, secretAccessKey: cfg.secretAccessKey, sessionToken: cfg.sessionToken } : undefined;
34309
34470
  return {
34310
34471
  route53: new Route53Client({ region, credentials }),
34311
34472
  domains: new Route53DomainsClient({ region: "us-east-1", credentials })
@@ -34334,7 +34495,7 @@ async function checkAvailability3(domain, config) {
34334
34495
  } catch {}
34335
34496
  return availability;
34336
34497
  }
34337
- async function registerDomain(domain, contact, durationYears = 1, autoRenew = true, config) {
34498
+ async function registerDomain2(domain, contact, durationYears = 1, autoRenew = true, config) {
34338
34499
  const { domains } = makeClients(config);
34339
34500
  const contactDetail = {
34340
34501
  FirstName: contact.first_name,
@@ -34383,7 +34544,7 @@ async function getDomainDetail(domain, config) {
34383
34544
  nameservers: (result.Nameservers ?? []).map((ns) => ns.Name ?? "").filter(Boolean)
34384
34545
  };
34385
34546
  }
34386
- async function updateNameservers(domain, nameservers, config, client) {
34547
+ async function updateNameservers2(domain, nameservers, config, client) {
34387
34548
  if (!nameservers.length) {
34388
34549
  throw new Error("updateNameservers requires at least one nameserver");
34389
34550
  }
@@ -34445,7 +34606,8 @@ async function listHostedZones(config) {
34445
34606
  id: cleanZoneId(z2.Id ?? ""),
34446
34607
  name: z2.Name ?? "",
34447
34608
  record_count: z2.ResourceRecordSetCount ?? 0,
34448
- comment: z2.Config?.Comment
34609
+ comment: z2.Config?.Comment,
34610
+ private_zone: z2.Config?.PrivateZone
34449
34611
  });
34450
34612
  }
34451
34613
  marker = result.IsTruncated ? result.NextMarker : undefined;
@@ -34460,7 +34622,8 @@ async function getHostedZone(hostedZoneId, config) {
34460
34622
  name: result.HostedZone?.Name ?? "",
34461
34623
  record_count: result.HostedZone?.ResourceRecordSetCount ?? 0,
34462
34624
  comment: result.HostedZone?.Config?.Comment,
34463
- name_servers: result.DelegationSet?.NameServers ?? []
34625
+ name_servers: result.DelegationSet?.NameServers ?? [],
34626
+ private_zone: result.HostedZone?.Config?.PrivateZone
34464
34627
  };
34465
34628
  }
34466
34629
  async function deleteHostedZone(hostedZoneId, config) {
@@ -34470,7 +34633,15 @@ async function deleteHostedZone(hostedZoneId, config) {
34470
34633
  async function findHostedZoneByDomain(domain, config) {
34471
34634
  const zones = await listHostedZones(config);
34472
34635
  const normalized = domain.endsWith(".") ? domain : `${domain}.`;
34473
- return zones.find((z2) => z2.name === normalized) ?? null;
34636
+ const matches = zones.filter((z2) => z2.name === normalized);
34637
+ if (matches.length === 0)
34638
+ return null;
34639
+ const publicMatches = matches.filter((z2) => !z2.private_zone);
34640
+ const candidates = publicMatches.length > 0 ? publicMatches : matches;
34641
+ if (candidates.length > 1) {
34642
+ throw new Error(`Multiple Route 53 hosted zones found for ${domain}; specify hosted zone id`);
34643
+ }
34644
+ return candidates[0] ?? null;
34474
34645
  }
34475
34646
  function rrsToRecord(rrs) {
34476
34647
  if (rrs.AliasTarget) {
@@ -34568,19 +34739,55 @@ async function upsertRecords(hostedZoneId, records, config) {
34568
34739
  }
34569
34740
  function createRoute53Provider(config) {
34570
34741
  const cfg = config ?? getConfig2();
34742
+ const registerWithRoute53 = registerDomain2;
34743
+ const updateRoute53Nameservers = updateNameservers2;
34744
+ async function listDomainInventory() {
34745
+ const byDomain = new Map;
34746
+ try {
34747
+ const registered = await listRegisteredDomains(cfg);
34748
+ for (const d3 of registered) {
34749
+ byDomain.set(d3.domain, {
34750
+ domain: d3.domain,
34751
+ registrar: "AWS Route 53",
34752
+ created: "",
34753
+ expires: d3.expiry,
34754
+ nameservers: [],
34755
+ status: "active",
34756
+ auto_renew: d3.auto_renew
34757
+ });
34758
+ }
34759
+ } catch (error) {
34760
+ const message = error instanceof Error ? error.message : String(error);
34761
+ if (!message.includes("route53domains:ListDomains") && !message.includes("AccessDenied")) {
34762
+ throw error;
34763
+ }
34764
+ }
34765
+ const zones = await listHostedZones(cfg);
34766
+ for (const z2 of zones) {
34767
+ const zone = z2.name_servers?.length ? z2 : await getHostedZone(z2.id, cfg).catch(() => z2);
34768
+ const domain = zone.name.replace(/\.$/, "");
34769
+ const nameservers = zone.name_servers ?? [];
34770
+ const existing = byDomain.get(domain);
34771
+ if (existing) {
34772
+ existing.nameservers = nameservers.length > 0 ? nameservers : existing.nameservers;
34773
+ continue;
34774
+ }
34775
+ byDomain.set(domain, {
34776
+ domain,
34777
+ registrar: "AWS Route 53 DNS",
34778
+ created: "",
34779
+ expires: "",
34780
+ nameservers,
34781
+ status: "active",
34782
+ auto_renew: false
34783
+ });
34784
+ }
34785
+ return Array.from(byDomain.values());
34786
+ }
34571
34787
  return {
34572
34788
  name: "route53",
34573
34789
  async listDomains() {
34574
- const domains = await listRegisteredDomains(cfg);
34575
- return domains.map((d3) => ({
34576
- domain: d3.domain,
34577
- registrar: "AWS Route 53",
34578
- created: "",
34579
- expires: d3.expiry,
34580
- nameservers: [],
34581
- status: "active",
34582
- auto_renew: d3.auto_renew
34583
- }));
34790
+ return listDomainInventory();
34584
34791
  },
34585
34792
  async getDomainInfo(domain) {
34586
34793
  const detail = await getDomainDetail(domain, cfg);
@@ -34594,6 +34801,14 @@ function createRoute53Provider(config) {
34594
34801
  auto_renew: detail.auto_renew
34595
34802
  };
34596
34803
  },
34804
+ async registerDomain(domain, contact, options = {}) {
34805
+ const result = await registerWithRoute53(domain, contact, options.years ?? 1, options.autoRenew ?? true, cfg);
34806
+ return { domain, success: !!result.operationId, operationId: result.operationId };
34807
+ },
34808
+ async updateNameservers(domain, nameservers) {
34809
+ const result = await updateRoute53Nameservers(domain, nameservers, cfg);
34810
+ return { domain, success: !!result.operationId, operationId: result.operationId };
34811
+ },
34597
34812
  async renewDomain(_domain) {
34598
34813
  return { domain: _domain, success: false, orderId: undefined, chargedAmount: undefined };
34599
34814
  },
@@ -34641,7 +34856,7 @@ function createRoute53Provider(config) {
34641
34856
  };
34642
34857
  },
34643
34858
  async syncToLocalDb(dbFns) {
34644
- const domains = await listRegisteredDomains(cfg);
34859
+ const domains = await listDomainInventory();
34645
34860
  let synced = 0;
34646
34861
  let created = 0;
34647
34862
  let updated = 0;
@@ -34650,20 +34865,39 @@ function createRoute53Provider(config) {
34650
34865
  try {
34651
34866
  const existing = dbFns.getDomainByName(d3.domain);
34652
34867
  if (existing) {
34868
+ const existingRoute53 = existing.metadata["route53"];
34869
+ const staleDnsOnlyRegistrar = d3.registrar !== "AWS Route 53" && (existing.registrar === "AWS Route 53 DNS" || existing.registrar === "AWS Route 53" && existingRoute53?.source === "route53:hosted_zones");
34653
34870
  dbFns.updateDomain(existing.id, {
34654
- registrar: "AWS Route 53",
34655
- expires_at: d3.expiry || undefined,
34871
+ ...d3.registrar === "AWS Route 53" ? { registrar: "AWS Route 53" } : {},
34872
+ ...staleDnsOnlyRegistrar ? { registrar: null } : {},
34873
+ expires_at: d3.expires || undefined,
34656
34874
  auto_renew: d3.auto_renew,
34875
+ nameservers: d3.nameservers.length > 0 ? d3.nameservers : existing.nameservers,
34876
+ metadata: {
34877
+ ...existing.metadata,
34878
+ route53: {
34879
+ source: d3.registrar === "AWS Route 53" ? "route53domains+hosted_zones" : "route53:hosted_zones",
34880
+ synced_at: new Date().toISOString()
34881
+ }
34882
+ },
34657
34883
  status: "active"
34658
34884
  });
34659
34885
  updated++;
34660
34886
  } else {
34661
34887
  dbFns.createDomain({
34662
34888
  name: d3.domain,
34663
- registrar: "AWS Route 53",
34664
- expires_at: d3.expiry || undefined,
34889
+ ...d3.registrar === "AWS Route 53" ? { registrar: "AWS Route 53" } : {},
34890
+ expires_at: d3.expires || undefined,
34665
34891
  auto_renew: d3.auto_renew,
34666
- status: "active"
34892
+ nameservers: d3.nameservers,
34893
+ status: "active",
34894
+ notes: d3.registrar === "AWS Route 53 DNS" ? "Discovered from Route 53 hosted zones; registrar ownership was not inferred." : undefined,
34895
+ metadata: {
34896
+ route53: {
34897
+ source: d3.registrar === "AWS Route 53" ? "route53domains+hosted_zones" : "route53:hosted_zones",
34898
+ synced_at: new Date().toISOString()
34899
+ }
34900
+ }
34667
34901
  });
34668
34902
  created++;
34669
34903
  }
@@ -34681,15 +34915,132 @@ var init_route53 = __esm(() => {
34681
34915
  init_dist_es14();
34682
34916
  });
34683
34917
 
34684
- // src/lib/cloudflare-auth.ts
34685
- function resolveCloudflareConfig(env = process.env) {
34686
- const accountId = env["CLOUDFLARE_ACCOUNT_ID"] || undefined;
34687
- if (env["CLOUDFLARE_API_TOKEN"]) {
34688
- return { apiToken: env["CLOUDFLARE_API_TOKEN"], accountId };
34918
+ // src/lib/env-aliases.ts
34919
+ function firstEnv(env, names) {
34920
+ for (const key of names) {
34921
+ const value = env[key];
34922
+ if (value)
34923
+ return { key, value };
34689
34924
  }
34690
- if (env["CLOUDFLARE_API_KEY"] && env["CLOUDFLARE_EMAIL"]) {
34691
- return { apiKey: env["CLOUDFLARE_API_KEY"], email: env["CLOUDFLARE_EMAIL"], accountId };
34925
+ return;
34926
+ }
34927
+ function hasEveryEnv(env, groups) {
34928
+ return groups.every((names) => !!firstEnv(env, names));
34929
+ }
34930
+ function flattenEnvNames(groups) {
34931
+ return Array.from(new Set(groups.flatMap((names) => [...names])));
34932
+ }
34933
+ function providerEnvNames(provider) {
34934
+ switch (provider.toLowerCase()) {
34935
+ case "namecheap":
34936
+ return flattenEnvNames([NAMECHEAP_ENV.apiKey, NAMECHEAP_ENV.username, NAMECHEAP_ENV.clientIp]);
34937
+ case "godaddy":
34938
+ return flattenEnvNames([GODADDY_ENV.apiKey, GODADDY_ENV.apiSecret]);
34939
+ case "brandsight":
34940
+ return flattenEnvNames([
34941
+ BRANDSIGHT_ENV.apiKey,
34942
+ BRANDSIGHT_ENV.apiSecret,
34943
+ BRANDSIGHT_ENV.customerId,
34944
+ BRANDSIGHT_ENV.shopperId,
34945
+ BRANDSIGHT_ENV.accountId
34946
+ ]);
34947
+ case "sedo":
34948
+ return flattenEnvNames([SEDO_ENV.partnerId, SEDO_ENV.signKey, SEDO_ENV.username, SEDO_ENV.password]);
34949
+ case "cloudflare":
34950
+ return flattenEnvNames([CLOUDFLARE_ENV.apiToken, CLOUDFLARE_ENV.apiKey, CLOUDFLARE_ENV.email, CLOUDFLARE_ENV.accountId]);
34951
+ case "route53":
34952
+ return ["AWS_PROFILE", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"];
34953
+ case "aws:beepmedia":
34954
+ return flattenEnvNames([BEEPMEDIA_AWS_ENV.accessKeyId, BEEPMEDIA_AWS_ENV.secretAccessKey]);
34955
+ default:
34956
+ return [];
34957
+ }
34958
+ }
34959
+ function hasProviderCredentials(provider, env = process.env) {
34960
+ switch (provider.toLowerCase()) {
34961
+ case "namecheap":
34962
+ return hasEveryEnv(env, [NAMECHEAP_ENV.apiKey, NAMECHEAP_ENV.username, NAMECHEAP_ENV.clientIp]);
34963
+ case "godaddy":
34964
+ return hasEveryEnv(env, [GODADDY_ENV.apiKey, GODADDY_ENV.apiSecret]);
34965
+ case "brandsight":
34966
+ return hasEveryEnv(env, [BRANDSIGHT_ENV.apiKey, BRANDSIGHT_ENV.apiSecret, BRANDSIGHT_ENV.customerId]);
34967
+ case "sedo":
34968
+ return hasEveryEnv(env, [SEDO_ENV.partnerId, SEDO_ENV.signKey, SEDO_ENV.username, SEDO_ENV.password]);
34969
+ case "cloudflare": {
34970
+ const tokenMode = !!firstEnv(env, CLOUDFLARE_ENV.apiToken);
34971
+ const keyMode = hasEveryEnv(env, [CLOUDFLARE_ENV.apiKey, CLOUDFLARE_ENV.email]);
34972
+ return tokenMode || keyMode;
34973
+ }
34974
+ case "route53":
34975
+ return !!env["AWS_PROFILE"] || hasEveryEnv(env, [["AWS_ACCESS_KEY_ID"], ["AWS_SECRET_ACCESS_KEY"]]);
34976
+ case "aws:beepmedia":
34977
+ return hasEveryEnv(env, [BEEPMEDIA_AWS_ENV.accessKeyId, BEEPMEDIA_AWS_ENV.secretAccessKey]);
34978
+ default:
34979
+ return false;
34692
34980
  }
34981
+ }
34982
+ var NAMECHEAP_ENV, GODADDY_ENV, BRANDSIGHT_ENV, SEDO_ENV, CLOUDFLARE_ENV, BEEPMEDIA_AWS_ENV;
34983
+ var init_env_aliases = __esm(() => {
34984
+ NAMECHEAP_ENV = {
34985
+ apiKey: ["NAMECHEAP_API_KEY"],
34986
+ username: ["NAMECHEAP_USERNAME"],
34987
+ clientIp: ["NAMECHEAP_CLIENT_IP"]
34988
+ };
34989
+ GODADDY_ENV = {
34990
+ apiKey: ["GODADDY_API_KEY"],
34991
+ apiSecret: ["GODADDY_API_SECRET"]
34992
+ };
34993
+ BRANDSIGHT_ENV = {
34994
+ apiKey: [
34995
+ "BRANDSIGHT_API_KEY",
34996
+ "HASNAXYZ_BRANDSIGHT_LIVE_API_KEY",
34997
+ "HASNAXYZ_BRANDSIGHT_SANDBOX_API_KEY"
34998
+ ],
34999
+ apiSecret: [
35000
+ "BRANDSIGHT_API_SECRET",
35001
+ "HASNAXYZ_BRANDSIGHT_LIVE_API_SECRET",
35002
+ "HASNAXYZ_BRANDSIGHT_SANDBOX_API_SECRET"
35003
+ ],
35004
+ customerId: [
35005
+ "BRANDSIGHT_CUSTOMER_ID",
35006
+ "HASNAXYZ_BRANDSIGHT_LIVE_CUSTOMER_ID",
35007
+ "HASNAXYZ_BRANDSIGHT_SANDBOX_CUSTOMER_ID"
35008
+ ],
35009
+ shopperId: [
35010
+ "BRANDSIGHT_SHOPPER_ID",
35011
+ "HASNAXYZ_BRANDSIGHT_LIVE_SHOPPER_ID",
35012
+ "HASNAXYZ_BRANDSIGHT_SANDBOX_SHOPPER_ID"
35013
+ ],
35014
+ accountId: ["BRANDSIGHT_ACCOUNT_ID", "HASNAXYZ_BRANDSIGHT_LIVE_ACCOUNT_ID"]
35015
+ };
35016
+ SEDO_ENV = {
35017
+ partnerId: ["SEDO_PARTNER_ID", "HASNAXYZ_SEDO_LIVE_PARTNER_ID"],
35018
+ signKey: ["SEDO_API_KEY", "SEDO_SIGN_KEY", "HASNAXYZ_SEDO_LIVE_API_KEY"],
35019
+ username: ["SEDO_USERNAME", "SEDO_EMAIL", "HASNAXYZ_SEDO_LIVE_USERNAME", "HASNAXYZ_SEDO_LIVE_EMAIL"],
35020
+ password: ["SEDO_PASSWORD", "HASNAXYZ_SEDO_LIVE_PASSWORD"]
35021
+ };
35022
+ CLOUDFLARE_ENV = {
35023
+ apiToken: ["CLOUDFLARE_API_TOKEN", "HASNAXYZ_CLOUDFLARE_LIVE_API_TOKEN"],
35024
+ apiKey: ["CLOUDFLARE_API_KEY", "HASNAXYZ_CLOUDFLARE_LIVE_API_KEY"],
35025
+ email: ["CLOUDFLARE_EMAIL", "HASNAXYZ_CLOUDFLARE_LIVE_EMAIL"],
35026
+ accountId: ["CLOUDFLARE_ACCOUNT_ID", "HASNAXYZ_CLOUDFLARE_LIVE_ACCOUNT_ID"]
35027
+ };
35028
+ BEEPMEDIA_AWS_ENV = {
35029
+ accessKeyId: ["HASNASTUDIO_BEEPMEDIA_AWS_LIVE_ACCESS_KEY_ID", "BEEPMEDIA_AWS_ACCESS_KEY_ID"],
35030
+ secretAccessKey: ["HASNASTUDIO_BEEPMEDIA_AWS_LIVE_SECRET_ACCESS_KEY", "BEEPMEDIA_AWS_SECRET_ACCESS_KEY"]
35031
+ };
35032
+ });
35033
+
35034
+ // src/lib/cloudflare-auth.ts
35035
+ function resolveCloudflareConfig(env = process.env) {
35036
+ const accountId = firstEnv(env, CLOUDFLARE_ENV.accountId)?.value;
35037
+ const apiToken = firstEnv(env, CLOUDFLARE_ENV.apiToken)?.value;
35038
+ if (apiToken)
35039
+ return { apiToken, accountId };
35040
+ const apiKey = firstEnv(env, CLOUDFLARE_ENV.apiKey)?.value;
35041
+ const email = firstEnv(env, CLOUDFLARE_ENV.email)?.value;
35042
+ if (apiKey && email)
35043
+ return { apiKey, email, accountId };
34693
35044
  return accountId ? { accountId } : {};
34694
35045
  }
34695
35046
  function cloudflareAuthHeaders(cfg) {
@@ -34699,6 +35050,9 @@ function cloudflareAuthHeaders(cfg) {
34699
35050
  return { "X-Auth-Key": cfg.apiKey, "X-Auth-Email": cfg.email };
34700
35051
  throw new Error("Cloudflare credentials not configured. Set CLOUDFLARE_API_TOKEN, or CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL.");
34701
35052
  }
35053
+ var init_cloudflare_auth = __esm(() => {
35054
+ init_env_aliases();
35055
+ });
34702
35056
 
34703
35057
  // src/lib/cloudflare.ts
34704
35058
  function getConfig3() {
@@ -34785,26 +35139,111 @@ async function listRecords2(zoneId, config) {
34785
35139
  }
34786
35140
  return records;
34787
35141
  }
34788
- async function upsertRecord2(zoneId, record, config) {
34789
- const existing = await cfFetch(`/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(record.name)}`, { config });
34790
- const body = {
34791
- type: record.type,
34792
- name: record.name,
34793
- content: record.content,
34794
- ttl: record.ttl ?? 1,
34795
- priority: record.priority,
34796
- proxied: record.proxied ?? false
34797
- };
34798
- if (existing && existing.length > 0) {
34799
- await cfFetch(`/zones/${zoneId}/dns_records/${existing[0].id}`, { method: "PUT", body, config });
34800
- } else {
34801
- await cfFetch(`/zones/${zoneId}/dns_records`, { method: "POST", body, config });
35142
+ async function listRecordsByNameType(zoneId, type, name, config) {
35143
+ const result = await cfFetch(`/zones/${zoneId}/dns_records?type=${encodeURIComponent(type)}&name=${encodeURIComponent(name)}`, { config });
35144
+ return (result ?? []).map((r3) => ({
35145
+ id: r3.id,
35146
+ type: r3.type,
35147
+ name: r3.name,
35148
+ content: r3.content,
35149
+ ttl: r3.ttl,
35150
+ priority: r3.priority,
35151
+ proxied: r3.proxied
35152
+ }));
35153
+ }
35154
+ async function replaceRecordsByNameType(zoneId, records, config) {
35155
+ if (records.length === 0)
35156
+ return;
35157
+ const { type, name } = records[0];
35158
+ const existing = await listRecordsByNameType(zoneId, type, name, config);
35159
+ for (const record of existing) {
35160
+ if (record.id)
35161
+ await deleteRecord2(zoneId, record.id, config);
35162
+ }
35163
+ for (const record of records) {
35164
+ await cfFetch(`/zones/${zoneId}/dns_records`, {
35165
+ method: "POST",
35166
+ body: {
35167
+ type: record.type,
35168
+ name: record.name,
35169
+ content: record.content,
35170
+ ttl: record.ttl ?? 1,
35171
+ priority: record.priority,
35172
+ proxied: record.proxied ?? false
35173
+ },
35174
+ config
35175
+ });
34802
35176
  }
34803
35177
  }
35178
+ async function deleteRecord2(zoneId, recordId, config) {
35179
+ await cfFetch(`/zones/${zoneId}/dns_records/${recordId}`, { method: "DELETE", config });
35180
+ }
35181
+ function zoneToDomainInfo(zone) {
35182
+ return {
35183
+ domain: zone.name,
35184
+ registrar: "Cloudflare DNS",
35185
+ created: "",
35186
+ expires: "",
35187
+ nameservers: zone.nameservers,
35188
+ status: zone.status === "active" ? "active" : "discovered",
35189
+ auto_renew: false
35190
+ };
35191
+ }
35192
+ function withCloudflareMetadata(existing, zone) {
35193
+ return {
35194
+ ...existing,
35195
+ cloudflare: {
35196
+ zone_id: zone.id,
35197
+ zone_status: zone.status,
35198
+ source: "cloudflare:zones",
35199
+ synced_at: new Date().toISOString()
35200
+ }
35201
+ };
35202
+ }
34804
35203
  function createCloudflareProvider(config) {
34805
35204
  const cfg = config ?? getConfig3();
34806
35205
  return {
34807
35206
  name: "cloudflare",
35207
+ async listDomains() {
35208
+ const zones = await listZones(cfg);
35209
+ return zones.map(zoneToDomainInfo);
35210
+ },
35211
+ async syncToLocalDb(dbFns) {
35212
+ const zones = await listZones(cfg);
35213
+ let synced = 0;
35214
+ let created = 0;
35215
+ let updated = 0;
35216
+ const errors3 = [];
35217
+ for (const zone of zones) {
35218
+ try {
35219
+ const info = zoneToDomainInfo(zone);
35220
+ const existing = dbFns.getDomainByName(zone.name);
35221
+ if (existing) {
35222
+ dbFns.updateDomain(existing.id, {
35223
+ ...existing.registrar === "Cloudflare DNS" ? { registrar: null } : {},
35224
+ status: existing.status === "discovered" && info.status === "active" ? "active" : existing.status,
35225
+ nameservers: zone.nameservers,
35226
+ metadata: withCloudflareMetadata(existing.metadata, zone)
35227
+ });
35228
+ updated++;
35229
+ } else {
35230
+ dbFns.createDomain({
35231
+ name: zone.name,
35232
+ status: info.status === "active" ? "active" : "discovered",
35233
+ auto_renew: false,
35234
+ nameservers: zone.nameservers,
35235
+ notes: "Discovered from Cloudflare zones; registrar ownership was not inferred.",
35236
+ metadata: withCloudflareMetadata({}, zone)
35237
+ });
35238
+ created++;
35239
+ }
35240
+ synced++;
35241
+ } catch (err) {
35242
+ errors3.push(`${zone.name}: ${err instanceof Error ? err.message : String(err)}`);
35243
+ }
35244
+ }
35245
+ return { synced, created, updated, errors: errors3 };
35246
+ },
34808
35247
  async getDnsRecords(domain) {
34809
35248
  const zone = await getZone(domain, cfg);
34810
35249
  if (!zone)
@@ -34822,65 +35261,59 @@ function createCloudflareProvider(config) {
34822
35261
  const zone = await getZone(domain, cfg);
34823
35262
  if (!zone)
34824
35263
  throw new Error(`No Cloudflare zone found for ${domain}`);
35264
+ const grouped = new Map;
34825
35265
  for (const r3 of records) {
34826
- await upsertRecord2(zone.id, { type: r3.type, name: r3.name, content: r3.value, ttl: r3.ttl || 1, priority: r3.priority }, cfg);
35266
+ const key = `${r3.type}|${r3.name}`;
35267
+ const existing = grouped.get(key) ?? [];
35268
+ existing.push({ type: r3.type, name: r3.name, content: r3.value, ttl: r3.ttl || 1, priority: r3.priority });
35269
+ grouped.set(key, existing);
35270
+ }
35271
+ for (const group of grouped.values()) {
35272
+ await replaceRecordsByNameType(zone.id, group, cfg);
34827
35273
  }
34828
35274
  return true;
34829
35275
  }
34830
35276
  };
34831
35277
  }
34832
35278
  var CF_BASE = "https://api.cloudflare.com/client/v4";
34833
- var init_cloudflare = () => {};
35279
+ var init_cloudflare = __esm(() => {
35280
+ init_cloudflare_auth();
35281
+ });
34834
35282
 
34835
35283
  // src/lib/brandsight.ts
34836
35284
  function resolveBrandsightConfig(env = process.env) {
35285
+ const apiKey = firstEnv(env, BRANDSIGHT_ENV.apiKey)?.value ?? "";
35286
+ const apiSecret = firstEnv(env, BRANDSIGHT_ENV.apiSecret)?.value;
35287
+ const customerId = firstEnv(env, BRANDSIGHT_ENV.customerId)?.value;
35288
+ const shopperId = firstEnv(env, BRANDSIGHT_ENV.shopperId)?.value;
35289
+ const accountId = firstEnv(env, BRANDSIGHT_ENV.accountId)?.value;
34837
35290
  return {
34838
- apiKey: env["BRANDSIGHT_API_KEY"] ?? "",
34839
- apiSecret: env["BRANDSIGHT_API_SECRET"],
34840
- customerId: env["BRANDSIGHT_CUSTOMER_ID"],
34841
- shopperId: env["BRANDSIGHT_SHOPPER_ID"],
34842
- accountId: env["BRANDSIGHT_ACCOUNT_ID"]
35291
+ apiKey,
35292
+ apiSecret,
35293
+ customerId,
35294
+ shopperId,
35295
+ accountId,
35296
+ baseUrl: env["BRANDSIGHT_BASE_URL"]
35297
+ };
35298
+ }
35299
+ function brandsightCapability(env = process.env) {
35300
+ const cfg = resolveBrandsightConfig(env);
35301
+ const configured = !!(cfg.apiKey && cfg.apiSecret && cfg.customerId);
35302
+ return {
35303
+ configured,
35304
+ gated: true,
35305
+ notes: configured ? "Credentials present, but GoDaddy Corporate Domains (Brandsight) is enterprise/contract-only; use Route53 for automated purchase." : "Not configured and enterprise/contract-only; not usable for automated purchase."
34843
35306
  };
34844
35307
  }
34845
35308
  function getApiKey() {
34846
- const key = process.env["BRANDSIGHT_API_KEY"];
35309
+ const key = resolveBrandsightConfig().apiKey;
34847
35310
  if (!key) {
34848
- throw new BrandsightApiError("BRANDSIGHT_API_KEY environment variable is not set");
35311
+ throw new BrandsightApiError("Brandsight API key is not set. Set BRANDSIGHT_API_KEY or HASNAXYZ_BRANDSIGHT_LIVE_API_KEY.");
34849
35312
  }
34850
35313
  return key;
34851
35314
  }
34852
35315
  function getConfig4() {
34853
- return {
34854
- apiKey: process.env["BRANDSIGHT_API_KEY"] ?? "",
34855
- accountId: process.env["BRANDSIGHT_ACCOUNT_ID"]
34856
- };
34857
- }
34858
- async function apiRequest3(method, path, apiKey, body, baseUrl = BRANDSIGHT_BASE) {
34859
- const fetchFn = _fetchFn || globalThis.fetch;
34860
- const url = `${baseUrl}${path}`;
34861
- const headers = {
34862
- Authorization: `Bearer ${apiKey}`,
34863
- "Content-Type": "application/json",
34864
- Accept: "application/json",
34865
- "User-Agent": USER_AGENT
34866
- };
34867
- try {
34868
- const response = await fetchFn(url, {
34869
- method,
34870
- headers,
34871
- body: body ? JSON.stringify(body) : undefined,
34872
- signal: AbortSignal.timeout(15000)
34873
- });
34874
- if (!response.ok) {
34875
- throw new BrandsightApiError(`Brandsight API ${method} ${path} failed with status ${response.status}`, response.status, await response.text());
34876
- }
34877
- const data = await response.json();
34878
- return { data, stub: false };
34879
- } catch (error) {
34880
- if (error instanceof BrandsightApiError)
34881
- throw error;
34882
- return { data: null, stub: true };
34883
- }
35316
+ return resolveBrandsightConfig();
34884
35317
  }
34885
35318
  async function apiGet(path, apiKey, baseUrl = BRANDSIGHT_BASE) {
34886
35319
  const fetchFn = _fetchFn || globalThis.fetch;
@@ -34908,12 +35341,109 @@ async function apiGet(path, apiKey, baseUrl = BRANDSIGHT_BASE) {
34908
35341
  return { data: null, stub: true };
34909
35342
  }
34910
35343
  }
35344
+ function requireDomainConfig(config) {
35345
+ const cfg = config ?? getConfig4();
35346
+ if (!cfg.apiKey || !cfg.apiSecret || !cfg.customerId) {
35347
+ throw new BrandsightApiError("Brandsight Domain API credentials are not configured. Set BRANDSIGHT_API_KEY, BRANDSIGHT_API_SECRET, and BRANDSIGHT_CUSTOMER_ID (or HASNAXYZ_BRANDSIGHT_LIVE_* aliases).");
35348
+ }
35349
+ return cfg;
35350
+ }
35351
+ function domainBaseUrl(cfg) {
35352
+ return cfg.baseUrl ?? BRANDSIGHT_DOMAIN_BASE;
35353
+ }
35354
+ function domainHeaders(cfg) {
35355
+ return {
35356
+ Authorization: `sso-key ${cfg.apiKey}:${cfg.apiSecret}`,
35357
+ "Content-Type": "application/json",
35358
+ Accept: "application/json",
35359
+ "User-Agent": USER_AGENT
35360
+ };
35361
+ }
35362
+ async function domainApiRequest(method, path, config, body) {
35363
+ const cfg = requireDomainConfig(config);
35364
+ const fetchFn = _fetchFn || globalThis.fetch;
35365
+ const response = await fetchFn(`${domainBaseUrl(cfg)}${path}`, {
35366
+ method,
35367
+ headers: domainHeaders(cfg),
35368
+ body: body ? JSON.stringify(body) : undefined,
35369
+ signal: AbortSignal.timeout(30000)
35370
+ });
35371
+ const text = await response.text();
35372
+ if (!response.ok) {
35373
+ throw new BrandsightApiError(`Brandsight Domain API ${method} ${path} failed with status ${response.status}`, response.status, text);
35374
+ }
35375
+ if (!text.trim())
35376
+ return {};
35377
+ return JSON.parse(text);
35378
+ }
35379
+ function normalizeBrandsightDomain(raw) {
35380
+ const nameServers = raw.nameServers ?? raw.nameservers ?? [];
35381
+ const expiresAt = raw.expiresAt ?? raw.expires ?? "";
35382
+ const createdAt = raw.createdAt ?? raw.created ?? "";
35383
+ const renewAuto = raw.renewAuto ?? raw.auto_renew ?? false;
35384
+ return {
35385
+ ...raw,
35386
+ domain: String(raw.domain ?? ""),
35387
+ status: String(raw.status ?? "UNKNOWN"),
35388
+ created: createdAt,
35389
+ expires: expiresAt,
35390
+ auto_renew: renewAuto,
35391
+ locked: Boolean(raw.locked),
35392
+ nameservers: nameServers,
35393
+ createdAt,
35394
+ expiresAt,
35395
+ nameServers,
35396
+ renewAuto
35397
+ };
35398
+ }
35399
+ function customerPath(cfg, path) {
35400
+ return `/customers/${encodeURIComponent(cfg.customerId)}${path}`;
35401
+ }
35402
+ function domainTld(domain) {
35403
+ const parts = domain.split(".").filter(Boolean);
35404
+ if (parts.length < 2)
35405
+ throw new BrandsightApiError(`Invalid domain name: ${domain}`);
35406
+ return parts.slice(1).join(".");
35407
+ }
35408
+ function brandsightContact(contact) {
35409
+ return {
35410
+ addressMailing: {
35411
+ address1: contact.address_line_1,
35412
+ city: contact.city,
35413
+ country: contact.country_code,
35414
+ postalCode: contact.zip_code,
35415
+ state: contact.state
35416
+ },
35417
+ email: contact.email,
35418
+ encoding: "ASCII",
35419
+ nameFirst: contact.first_name,
35420
+ nameLast: contact.last_name,
35421
+ organization: contact.organization_name,
35422
+ phone: contact.phone
35423
+ };
35424
+ }
35425
+ async function domainAvailability(domain, cfg, type, period = 1) {
35426
+ const params = new URLSearchParams({
35427
+ domain,
35428
+ period: String(period),
35429
+ type,
35430
+ optimizeFor: "ACCURACY"
35431
+ });
35432
+ const result = await domainApiRequest("GET", `/domains/available?${params.toString()}`, cfg);
35433
+ return {
35434
+ domain: result.domain ?? domain,
35435
+ available: Boolean(result.available),
35436
+ price: result.price,
35437
+ currency: result.currency,
35438
+ registryPremiumPricing: result.registryPremiumPricing
35439
+ };
35440
+ }
34911
35441
  function generateStubAlerts(brandName) {
34912
- const now = new Date().toISOString();
35442
+ const now2 = new Date().toISOString();
34913
35443
  return [
34914
- { domain: `${brandName}-deals.com`, type: "keyword", registered_at: now },
34915
- { domain: `${brandName.replace(/a/gi, "4").replace(/e/gi, "3")}.com`, type: "homoglyph", registered_at: now },
34916
- { domain: `${brandName}s.com`, type: "typosquat", registered_at: now }
35444
+ { domain: `${brandName}-deals.com`, type: "keyword", registered_at: now2 },
35445
+ { domain: `${brandName.replace(/a/gi, "4").replace(/e/gi, "3")}.com`, type: "homoglyph", registered_at: now2 },
35446
+ { domain: `${brandName}s.com`, type: "typosquat", registered_at: now2 }
34917
35447
  ];
34918
35448
  }
34919
35449
  function generateStubSimilarDomains(domain) {
@@ -34960,32 +35490,160 @@ async function getThreatAssessment(domain) {
34960
35490
  return { ...result.data, stub: false };
34961
35491
  }
34962
35492
  async function listDomains2(config) {
34963
- const cfg = config ?? getConfig4();
34964
- const result = await apiGet("/portfolio/domains", cfg.apiKey);
34965
- if (result.stub)
34966
- return [];
34967
- return result.data.domains ?? [];
35493
+ const cfg = requireDomainConfig(config);
35494
+ const domains = [];
35495
+ const seenMarkers = new Set;
35496
+ let marker;
35497
+ while (true) {
35498
+ const params = new URLSearchParams({ limit: "500" });
35499
+ if (marker)
35500
+ params.set("marker", marker);
35501
+ const batch = await domainApiRequest("GET", customerPath(cfg, `/domains?${params.toString()}`), cfg);
35502
+ if (!Array.isArray(batch) || batch.length === 0)
35503
+ break;
35504
+ domains.push(...batch.map(normalizeBrandsightDomain).filter((d3) => d3.domain));
35505
+ if (batch.length < 500)
35506
+ break;
35507
+ const nextMarker = String(batch[batch.length - 1]?.domain ?? "");
35508
+ if (!nextMarker || seenMarkers.has(nextMarker))
35509
+ break;
35510
+ seenMarkers.add(nextMarker);
35511
+ marker = nextMarker;
35512
+ }
35513
+ return domains;
34968
35514
  }
34969
35515
  async function getDomainInfo3(domain, config) {
34970
- const cfg = config ?? getConfig4();
34971
- const result = await apiGet(`/portfolio/domains/${encodeURIComponent(domain)}`, cfg.apiKey);
34972
- if (result.stub)
34973
- return null;
34974
- return result.data;
35516
+ const cfg = requireDomainConfig(config);
35517
+ const result = await domainApiRequest("GET", customerPath(cfg, `/domains/${encodeURIComponent(domain)}`), cfg);
35518
+ return normalizeBrandsightDomain(result);
34975
35519
  }
34976
35520
  async function checkAvailability4(domain, config) {
34977
- const cfg = config ?? getConfig4();
34978
- const result = await apiGet(`/domains/check?domain=${encodeURIComponent(domain)}`, cfg.apiKey);
34979
- if (result.stub)
34980
- return { domain, available: false };
34981
- return result.data;
35521
+ const cfg = requireDomainConfig(config);
35522
+ return domainAvailability(domain, cfg, "REGISTRATION", 1);
34982
35523
  }
34983
35524
  async function renewDomain3(domain, years = 1, config) {
34984
- const cfg = config ?? getConfig4();
34985
- const result = await apiRequest3("POST", `/portfolio/domains/${encodeURIComponent(domain)}/renew`, cfg.apiKey, { years });
34986
- if (result.stub)
34987
- return { success: false };
34988
- return { success: true, orderId: result.data.orderId };
35525
+ const cfg = requireDomainConfig(config);
35526
+ const current = await getDomainInfo3(domain, cfg);
35527
+ if (!current?.expires)
35528
+ throw new BrandsightApiError(`Cannot renew ${domain}: current expiry is unavailable`);
35529
+ const quote = await domainAvailability(domain, cfg, "RENEWAL", years);
35530
+ const price = quote.price ?? current.renewal?.price;
35531
+ const currency = quote.currency ?? current.renewal?.currency;
35532
+ if (price == null || !currency) {
35533
+ throw new BrandsightApiError(`Cannot renew ${domain}: renewal quote did not include an exact price and currency`);
35534
+ }
35535
+ const result = await domainApiRequest("POST", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/renew`), cfg, {
35536
+ consent: {
35537
+ agreedAt: new Date().toISOString(),
35538
+ agreedBy: cfg.shopperId ?? "domains-cli",
35539
+ currency,
35540
+ price,
35541
+ registryPremiumPricing: quote.registryPremiumPricing ?? false
35542
+ },
35543
+ expires: current.expires,
35544
+ period: years
35545
+ });
35546
+ return { success: true, orderId: result.orderId ?? result.id };
35547
+ }
35548
+ async function getLegalAgreements(tld, privacy = false, config) {
35549
+ const cfg = requireDomainConfig(config);
35550
+ const params = new URLSearchParams({
35551
+ privacy: String(privacy),
35552
+ tlds: tld
35553
+ });
35554
+ return domainApiRequest("GET", customerPath(cfg, `/domains/agreements?${params.toString()}`), cfg);
35555
+ }
35556
+ async function getRegistrationSchema(tld, config) {
35557
+ const cfg = requireDomainConfig(config);
35558
+ return domainApiRequest("GET", customerPath(cfg, `/domains/register/schema/${encodeURIComponent(tld)}`), cfg);
35559
+ }
35560
+ async function validateRegistrationRequest(payload, config) {
35561
+ const cfg = requireDomainConfig(config);
35562
+ await domainApiRequest("POST", customerPath(cfg, "/domains/register/validate"), cfg, payload);
35563
+ return true;
35564
+ }
35565
+ async function registerBrandsightDomain(domain, contact, options = {}, config) {
35566
+ const cfg = requireDomainConfig(config);
35567
+ const period = options.years ?? 1;
35568
+ const availability = await domainAvailability(domain, cfg, "REGISTRATION", period);
35569
+ if (!availability.available)
35570
+ throw new BrandsightApiError(`${domain} is not available for registration`);
35571
+ const price = options.premiumPrice ?? availability.price;
35572
+ if (price == null || !availability.currency) {
35573
+ throw new BrandsightApiError(`Cannot register ${domain}: availability quote did not include an exact price and currency`);
35574
+ }
35575
+ const tld = domainTld(domain);
35576
+ const schema = await getRegistrationSchema(tld, cfg);
35577
+ const required = Array.isArray(schema["required"]) ? schema["required"] : [];
35578
+ if (required.includes("metadata") && !options.metadata) {
35579
+ throw new BrandsightApiError(`Cannot register ${domain}: ${tld} requires TLD-specific metadata; pass registration metadata before validation`);
35580
+ }
35581
+ const privacy = options.privacy ?? false;
35582
+ const agreements = await getLegalAgreements(tld, privacy, cfg);
35583
+ const agreementKeys = agreements.map((a3) => a3.agreementKey).filter(Boolean);
35584
+ const c3 = brandsightContact(contact);
35585
+ const payload = {
35586
+ consent: {
35587
+ agreedAt: new Date().toISOString(),
35588
+ agreedBy: contact.email || cfg.shopperId || "domains-cli",
35589
+ agreementKeys,
35590
+ currency: availability.currency,
35591
+ price,
35592
+ registryPremiumPricing: availability.registryPremiumPricing ?? !!options.premiumPrice
35593
+ },
35594
+ contacts: {
35595
+ admin: c3,
35596
+ billing: c3,
35597
+ registrant: c3,
35598
+ tech: c3
35599
+ },
35600
+ domain,
35601
+ metadata: options.metadata ?? {},
35602
+ nameServers: options.nameservers ?? [],
35603
+ period,
35604
+ privacy,
35605
+ renewAuto: options.autoRenew ?? true
35606
+ };
35607
+ await validateRegistrationRequest(payload, cfg);
35608
+ const result = await domainApiRequest("POST", customerPath(cfg, "/domains/register"), cfg, payload);
35609
+ return {
35610
+ success: true,
35611
+ orderId: result.orderId ?? result.id,
35612
+ operationId: result.operationId ?? result.id,
35613
+ chargedAmount: String(price)
35614
+ };
35615
+ }
35616
+ async function updateNameservers3(domain, nameservers, config) {
35617
+ const cfg = requireDomainConfig(config);
35618
+ const result = await domainApiRequest("PUT", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/nameServers`), cfg, { nameServers: nameservers });
35619
+ return { success: true, operationId: result.operationId ?? result.id };
35620
+ }
35621
+ async function getDnsRecords3(domain, config) {
35622
+ const cfg = requireDomainConfig(config);
35623
+ const records = [];
35624
+ let offset = 0;
35625
+ const limit = 1000;
35626
+ while (true) {
35627
+ const batch = await domainApiRequest("GET", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/records?offset=${offset}&limit=${limit}`), cfg);
35628
+ if (!Array.isArray(batch) || batch.length === 0)
35629
+ break;
35630
+ records.push(...batch);
35631
+ if (batch.length < limit)
35632
+ break;
35633
+ offset++;
35634
+ }
35635
+ return records;
35636
+ }
35637
+ function normalizeBrandsightDnsRecord(record) {
35638
+ return {
35639
+ ...record,
35640
+ ttl: Math.max(record.ttl || 600, 600)
35641
+ };
35642
+ }
35643
+ async function setDnsRecords3(domain, records, config) {
35644
+ const cfg = requireDomainConfig(config);
35645
+ await domainApiRequest("PUT", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/records`), cfg, records.map(normalizeBrandsightDnsRecord));
35646
+ return true;
34989
35647
  }
34990
35648
  async function syncToLocalDb3(dbFns, config) {
34991
35649
  const domains = await listDomains2(config);
@@ -35030,7 +35688,7 @@ function createBrandsightProvider(config) {
35030
35688
  return domains.map((d3) => ({
35031
35689
  domain: d3.domain,
35032
35690
  registrar: "Brandsight",
35033
- created: "",
35691
+ created: d3.created ?? "",
35034
35692
  expires: d3.expires,
35035
35693
  nameservers: d3.nameservers,
35036
35694
  status: d3.status === "ACTIVE" ? "active" : d3.status.toLowerCase(),
@@ -35044,23 +35702,58 @@ function createBrandsightProvider(config) {
35044
35702
  return {
35045
35703
  domain: d3.domain,
35046
35704
  registrar: "Brandsight",
35047
- created: "",
35705
+ created: d3.created ?? "",
35048
35706
  expires: d3.expires,
35049
35707
  nameservers: d3.nameservers,
35050
35708
  status: d3.status === "ACTIVE" ? "active" : d3.status.toLowerCase(),
35051
35709
  auto_renew: d3.auto_renew
35052
35710
  };
35053
35711
  },
35054
- async renewDomain(domain) {
35055
- const result = await renewDomain3(domain, 1, cfg);
35712
+ async renewDomain(domain, years = 1) {
35713
+ const result = await renewDomain3(domain, years, cfg);
35056
35714
  return { domain, success: result.success, orderId: result.orderId };
35057
35715
  },
35716
+ async registerDomain(domain, contact, options) {
35717
+ const result = await registerBrandsightDomain(domain, contact, options, cfg);
35718
+ return {
35719
+ domain,
35720
+ success: result.success,
35721
+ orderId: result.orderId,
35722
+ operationId: result.operationId,
35723
+ chargedAmount: result.chargedAmount
35724
+ };
35725
+ },
35726
+ async updateNameservers(domain, nameservers) {
35727
+ const result = await updateNameservers3(domain, nameservers, cfg);
35728
+ return { domain, success: result.success, operationId: result.operationId };
35729
+ },
35730
+ async getDnsRecords(domain) {
35731
+ const records = await getDnsRecords3(domain, cfg);
35732
+ return records.map((r3) => ({
35733
+ type: r3.type,
35734
+ name: r3.name,
35735
+ value: r3.data,
35736
+ ttl: r3.ttl,
35737
+ priority: r3.priority
35738
+ }));
35739
+ },
35740
+ async setDnsRecords(domain, records) {
35741
+ return setDnsRecords3(domain, records.map((r3) => ({
35742
+ type: r3.type,
35743
+ name: r3.name,
35744
+ data: r3.value,
35745
+ ttl: r3.ttl,
35746
+ priority: r3.priority
35747
+ })), cfg);
35748
+ },
35058
35749
  async checkAvailability(domain) {
35059
35750
  const result = await checkAvailability4(domain, cfg);
35060
35751
  return {
35061
35752
  domain: result.domain,
35062
35753
  available: result.available,
35063
- standard_price: result.price,
35754
+ is_premium: result.registryPremiumPricing,
35755
+ premium_price: result.registryPremiumPricing ? result.price : undefined,
35756
+ standard_price: result.registryPremiumPricing ? undefined : result.price,
35064
35757
  currency: result.currency
35065
35758
  };
35066
35759
  },
@@ -35069,8 +35762,9 @@ function createBrandsightProvider(config) {
35069
35762
  }
35070
35763
  };
35071
35764
  }
35072
- var BrandsightApiError, _fetchFn = null, BRANDSIGHT_BASE = "https://api.brandsight.com/v1";
35765
+ var BrandsightApiError, _fetchFn = null, BRANDSIGHT_BASE = "https://api.brandsight.com/v1", BRANDSIGHT_DOMAIN_BASE = "https://api.godaddy.com/v2";
35073
35766
  var init_brandsight = __esm(() => {
35767
+ init_env_aliases();
35074
35768
  init_version();
35075
35769
  BrandsightApiError = class BrandsightApiError extends Error {
35076
35770
  statusCode;
@@ -35095,9 +35789,9 @@ __export(exports_config, {
35095
35789
  getConfigKey: () => getConfigKey,
35096
35790
  applyPurchaseProfile: () => applyPurchaseProfile
35097
35791
  });
35098
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
35099
- import { homedir as homedir3 } from "os";
35100
- import { join as join3 } from "path";
35792
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
35793
+ import { homedir as homedir4 } from "os";
35794
+ import { join as join4 } from "path";
35101
35795
  function getPurchaseProfile() {
35102
35796
  return loadConfig3().purchase_aws_profile ?? process.env["DOMAINS_PURCHASE_AWS_PROFILE"] ?? undefined;
35103
35797
  }
@@ -35110,11 +35804,11 @@ function applyPurchaseProfile() {
35110
35804
  return profile;
35111
35805
  }
35112
35806
  function configPath() {
35113
- return join3(homedir3(), ".hasna", "domains", "config.json");
35807
+ return join4(homedir4(), ".hasna", "domains", "config.json");
35114
35808
  }
35115
35809
  function loadConfig3() {
35116
35810
  const path = configPath();
35117
- if (!existsSync2(path))
35811
+ if (!existsSync3(path))
35118
35812
  return {};
35119
35813
  try {
35120
35814
  return JSON.parse(readFileSync3(path, "utf-8"));
@@ -35124,8 +35818,8 @@ function loadConfig3() {
35124
35818
  }
35125
35819
  function saveConfig(config) {
35126
35820
  const path = configPath();
35127
- const dir = join3(homedir3(), ".hasna", "domains");
35128
- if (!existsSync2(dir))
35821
+ const dir = join4(homedir4(), ".hasna", "domains");
35822
+ if (!existsSync3(dir))
35129
35823
  mkdirSync2(dir, { recursive: true });
35130
35824
  writeFileSync(path, JSON.stringify(config, null, 2), "utf-8");
35131
35825
  }
@@ -35176,44 +35870,6 @@ function getConfigKey(keyPath) {
35176
35870
  }
35177
35871
  var init_config = () => {};
35178
35872
 
35179
- // src/lib/creds-check.ts
35180
- var exports_creds_check = {};
35181
- __export(exports_creds_check, {
35182
- checkProvisioningCredentials: () => checkProvisioningCredentials
35183
- });
35184
- function checkProvisioningCredentials(env = process.env) {
35185
- const out = [];
35186
- const hasAws = !!(env["AWS_ACCESS_KEY_ID"] && env["AWS_SECRET_ACCESS_KEY"]) || !!env["AWS_PROFILE"];
35187
- out.push({
35188
- provider: "route53",
35189
- configured: hasAws,
35190
- mode: env["AWS_PROFILE"] ? `profile:${env["AWS_PROFILE"]}` : hasAws ? "access-keys" : "none",
35191
- detail: hasAws ? "AWS credentials present (region us-east-1 for Route53 Domains)" : "Set AWS_PROFILE or AWS_ACCESS_KEY_ID/SECRET"
35192
- });
35193
- const cf = resolveCloudflareConfig(env);
35194
- const cfMode = cf.apiToken ? "token" : cf.apiKey && cf.email ? "global-key" : "none";
35195
- out.push({
35196
- provider: "cloudflare",
35197
- configured: cfMode !== "none",
35198
- mode: cfMode + (cf.accountId ? "+account" : ""),
35199
- detail: cfMode === "none" ? "Set CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY+CLOUDFLARE_EMAIL" : cf.accountId ? "ok" : "missing CLOUDFLARE_ACCOUNT_ID (needed to create zones)"
35200
- });
35201
- const bs = resolveBrandsightConfig(env);
35202
- const bsConfigured = !!(bs.apiKey && bs.apiSecret && bs.customerId);
35203
- out.push({
35204
- provider: "brandsight",
35205
- configured: bsConfigured,
35206
- mode: bsConfigured ? "full-creds" : "none",
35207
- detail: "enterprise/contract-only (gated) \u2014 not used for automated purchase"
35208
- });
35209
- const gd = !!(env["GODADDY_API_KEY"] && env["GODADDY_API_SECRET"]);
35210
- out.push({ provider: "godaddy", configured: gd, mode: gd ? "key+secret" : "none", detail: "retail API gated for purchase/DNS since 2024" });
35211
- return out;
35212
- }
35213
- var init_creds_check = __esm(() => {
35214
- init_brandsight();
35215
- });
35216
-
35217
35873
  // src/db/domain-history.ts
35218
35874
  function rowToDomainHistory(row) {
35219
35875
  return {
@@ -35296,8 +35952,10 @@ var init_domain_history = __esm(() => {
35296
35952
  // src/lib/sedo.ts
35297
35953
  var exports_sedo = {};
35298
35954
  __export(exports_sedo, {
35955
+ sedoCapability: () => sedoCapability,
35299
35956
  searchSedoDomains: () => searchSedoDomains,
35300
35957
  searchHealthDomains: () => searchHealthDomains,
35958
+ resolveSedoConfig: () => resolveSedoConfig,
35301
35959
  removeDomainFromSedo: () => removeDomainFromSedo,
35302
35960
  recordSedoPurchase: () => recordSedoPurchase,
35303
35961
  listSedoPortfolio: () => listSedoPortfolio,
@@ -35307,16 +35965,31 @@ __export(exports_sedo, {
35307
35965
  checkSedoBlacklist: () => checkSedoBlacklist,
35308
35966
  addDomainToSedo: () => addDomainToSedo
35309
35967
  });
35968
+ function resolveSedoConfig(env = process.env) {
35969
+ return {
35970
+ partnerId: firstEnv(env, SEDO_ENV.partnerId)?.value,
35971
+ signKey: firstEnv(env, SEDO_ENV.signKey)?.value,
35972
+ username: firstEnv(env, SEDO_ENV.username)?.value,
35973
+ password: firstEnv(env, SEDO_ENV.password)?.value
35974
+ };
35975
+ }
35310
35976
  function getSedoConfig() {
35311
- const partnerId = process.env.SEDO_PARTNER_ID;
35312
- const signKey = process.env.SEDO_API_KEY;
35313
- const username = process.env.SEDO_USERNAME;
35314
- const password = process.env.SEDO_PASSWORD;
35977
+ const config = resolveSedoConfig();
35978
+ const { partnerId, signKey, username, password } = config;
35315
35979
  if (!partnerId || !signKey || !username || !password) {
35316
- throw new Error("Sedo credentials not configured. Required: SEDO_PARTNER_ID, SEDO_API_KEY, SEDO_USERNAME, SEDO_PASSWORD");
35980
+ throw new Error("Sedo credentials not configured. Required: SEDO_PARTNER_ID, SEDO_API_KEY, SEDO_USERNAME, SEDO_PASSWORD (or HASNAXYZ_SEDO_LIVE_* aliases)");
35317
35981
  }
35318
35982
  return { partnerId, signKey, username, password };
35319
35983
  }
35984
+ function sedoCapability(env = process.env) {
35985
+ const cfg = resolveSedoConfig(env);
35986
+ const configured = !!(cfg.partnerId && cfg.signKey && cfg.username && cfg.password);
35987
+ return {
35988
+ configured,
35989
+ gated: true,
35990
+ notes: configured ? "Credentials present; Sedo supports marketplace search, portfolio listing, listing edits, and recorded purchases, but not registrar DNS or direct self-serve registration through this CLI." : "Not configured; Sedo is marketplace-only here, not registrar DNS."
35991
+ };
35992
+ }
35320
35993
  function buildXmlParams(params) {
35321
35994
  return {
35322
35995
  partnerid: params.partnerId,
@@ -35515,10 +36188,85 @@ function recordSedoPurchase(domain, price, orderId) {
35515
36188
  }
35516
36189
  var BASE_URL = "https://api.sedo.com/api/v1/";
35517
36190
  var init_sedo = __esm(() => {
36191
+ init_env_aliases();
35518
36192
  init_domains();
35519
36193
  init_domain_history();
35520
36194
  });
35521
36195
 
36196
+ // src/lib/creds-check.ts
36197
+ var exports_creds_check = {};
36198
+ __export(exports_creds_check, {
36199
+ checkProvisioningCredentials: () => checkProvisioningCredentials
36200
+ });
36201
+ function configuredStatus(provider, mode, detail) {
36202
+ return { provider, configured: true, mode, detail };
36203
+ }
36204
+ function missingStatus(provider, detail) {
36205
+ return { provider, configured: false, mode: "none", detail };
36206
+ }
36207
+ function checkProvisioningCredentials(env = process.env) {
36208
+ const out = [];
36209
+ const hasAwsKeys = !!(env["AWS_ACCESS_KEY_ID"] && env["AWS_SECRET_ACCESS_KEY"]);
36210
+ const hasAwsProfile = !!env["AWS_PROFILE"];
36211
+ if (hasAwsProfile || hasAwsKeys) {
36212
+ out.push(configuredStatus("route53", hasAwsProfile ? `profile:${env["AWS_PROFILE"]}` : "access-keys", "AWS credentials present (Route 53 Domains API uses us-east-1)"));
36213
+ } else {
36214
+ out.push(missingStatus("route53", "Set AWS_PROFILE or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY"));
36215
+ }
36216
+ const beepmediaAws = hasProviderCredentials("aws:beepmedia", env);
36217
+ out.push({
36218
+ provider: "aws:beepmedia",
36219
+ configured: beepmediaAws,
36220
+ mode: beepmediaAws ? "scoped-env" : "none",
36221
+ detail: beepmediaAws ? "Beepmedia AWS scoped env keys are present; export them as AWS_* or use AWS_PROFILE=beepmedia for Route53 sync." : `Set ${BEEPMEDIA_AWS_ENV.accessKeyId.join(" or ")} and ${BEEPMEDIA_AWS_ENV.secretAccessKey.join(" or ")}`
36222
+ });
36223
+ const cf = resolveCloudflareConfig(env);
36224
+ const cfMode = cf.apiToken ? "token" : cf.apiKey && cf.email ? "global-key" : "none";
36225
+ out.push({
36226
+ provider: "cloudflare",
36227
+ configured: cfMode !== "none",
36228
+ mode: cfMode + (cf.accountId ? "+account" : ""),
36229
+ detail: cfMode === "none" ? "Set CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY+CLOUDFLARE_EMAIL" : cf.accountId ? "ok" : "missing CLOUDFLARE_ACCOUNT_ID (needed to create zones)"
36230
+ });
36231
+ out.push({
36232
+ provider: "namecheap",
36233
+ configured: hasProviderCredentials("namecheap", env),
36234
+ mode: hasProviderCredentials("namecheap", env) ? "api-key+username+client-ip" : "none",
36235
+ detail: "Requires NAMECHEAP_API_KEY, NAMECHEAP_USERNAME, and whitelisted NAMECHEAP_CLIENT_IP"
36236
+ });
36237
+ const gd = godaddyCapability(env);
36238
+ out.push({
36239
+ provider: "godaddy",
36240
+ configured: gd.configured,
36241
+ mode: gd.configured ? "key+secret" : "none",
36242
+ detail: gd.notes
36243
+ });
36244
+ const bs = brandsightCapability(env);
36245
+ const bsMode = firstEnv(env, ["BRANDSIGHT_API_KEY"]) ? "standard-env" : bs.configured ? "scoped-env" : "none";
36246
+ out.push({
36247
+ provider: "brandsight",
36248
+ configured: bs.configured,
36249
+ mode: bsMode,
36250
+ detail: bs.notes
36251
+ });
36252
+ const sedo = sedoCapability(env);
36253
+ const sedoMode = firstEnv(env, ["SEDO_API_KEY"]) ? "standard-env" : sedo.configured ? "scoped-env" : "none";
36254
+ out.push({
36255
+ provider: "sedo",
36256
+ configured: sedo.configured,
36257
+ mode: sedoMode,
36258
+ detail: sedo.notes
36259
+ });
36260
+ return out;
36261
+ }
36262
+ var init_creds_check = __esm(() => {
36263
+ init_cloudflare_auth();
36264
+ init_brandsight();
36265
+ init_godaddy();
36266
+ init_sedo();
36267
+ init_env_aliases();
36268
+ });
36269
+
35522
36270
  // src/lib/provision-state.ts
35523
36271
  function isDomainTerminal(s3) {
35524
36272
  return TERMINAL.has(s3);
@@ -35593,7 +36341,7 @@ function makeDomainDaemonDeps() {
35593
36341
  return;
35594
36342
  case "delegate_ns": {
35595
36343
  const zone = await ensureZone(domain);
35596
- await updateNameservers(domain, zone.nameservers);
36344
+ await updateNameservers2(domain, zone.nameservers);
35597
36345
  return;
35598
36346
  }
35599
36347
  case "check_ns_propagation":
@@ -40553,6 +41301,684 @@ var {
40553
41301
  Help
40554
41302
  } = import__.default;
40555
41303
 
41304
+ // node_modules/@hasna/events/dist/commander.js
41305
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
41306
+ import { existsSync } from "fs";
41307
+ import { homedir } from "os";
41308
+ import { join } from "path";
41309
+ import { createHmac, timingSafeEqual } from "crypto";
41310
+ import { randomUUID } from "crypto";
41311
+ import { spawn } from "child_process";
41312
+ import { randomUUID as randomUUID2 } from "crypto";
41313
+ function getPathValue(input, path) {
41314
+ return path.split(".").reduce((value, part) => {
41315
+ if (value && typeof value === "object" && part in value) {
41316
+ return value[part];
41317
+ }
41318
+ return;
41319
+ }, input);
41320
+ }
41321
+ function wildcardToRegExp(pattern) {
41322
+ const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
41323
+ return new RegExp(`^${escaped}$`);
41324
+ }
41325
+ function matchString(value, matcher) {
41326
+ if (matcher === undefined)
41327
+ return true;
41328
+ if (value === undefined)
41329
+ return false;
41330
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
41331
+ return matchers.some((item) => wildcardToRegExp(item).test(value));
41332
+ }
41333
+ function matchRecord(input, matcher) {
41334
+ if (!matcher)
41335
+ return true;
41336
+ return Object.entries(matcher).every(([path, expected]) => {
41337
+ const actual = getPathValue(input, path);
41338
+ if (typeof expected === "string" || Array.isArray(expected)) {
41339
+ return matchString(actual === undefined ? undefined : String(actual), expected);
41340
+ }
41341
+ return actual === expected;
41342
+ });
41343
+ }
41344
+ function eventMatchesFilter(event, filter) {
41345
+ return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
41346
+ }
41347
+ function channelMatchesEvent(channel, event) {
41348
+ if (!channel.enabled)
41349
+ return false;
41350
+ if (!channel.filters || channel.filters.length === 0)
41351
+ return true;
41352
+ return channel.filters.some((filter) => eventMatchesFilter(event, filter));
41353
+ }
41354
+ var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
41355
+ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
41356
+ function getEventsDataDir(override) {
41357
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
41358
+ }
41359
+
41360
+ class JsonEventsStore {
41361
+ dataDir;
41362
+ channelsPath;
41363
+ eventsPath;
41364
+ deliveriesPath;
41365
+ constructor(dataDir = getEventsDataDir()) {
41366
+ this.dataDir = dataDir;
41367
+ this.channelsPath = join(dataDir, "channels.json");
41368
+ this.eventsPath = join(dataDir, "events.json");
41369
+ this.deliveriesPath = join(dataDir, "deliveries.json");
41370
+ }
41371
+ async init() {
41372
+ await mkdir(this.dataDir, { recursive: true, mode: 448 });
41373
+ await chmod(this.dataDir, 448).catch(() => {
41374
+ return;
41375
+ });
41376
+ await this.ensureArrayFile(this.channelsPath);
41377
+ await this.ensureArrayFile(this.eventsPath);
41378
+ await this.ensureArrayFile(this.deliveriesPath);
41379
+ }
41380
+ async addChannel(channel) {
41381
+ await this.init();
41382
+ const channels = await this.readJson(this.channelsPath, []);
41383
+ const index = channels.findIndex((item) => item.id === channel.id);
41384
+ if (index >= 0) {
41385
+ channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
41386
+ } else {
41387
+ channels.push(channel);
41388
+ }
41389
+ await this.writeJson(this.channelsPath, channels);
41390
+ return index >= 0 ? channels[index] : channel;
41391
+ }
41392
+ async listChannels() {
41393
+ await this.init();
41394
+ return this.readJson(this.channelsPath, []);
41395
+ }
41396
+ async getChannel(id) {
41397
+ const channels = await this.listChannels();
41398
+ return channels.find((channel) => channel.id === id);
41399
+ }
41400
+ async removeChannel(id) {
41401
+ await this.init();
41402
+ const channels = await this.readJson(this.channelsPath, []);
41403
+ const next = channels.filter((channel) => channel.id !== id);
41404
+ await this.writeJson(this.channelsPath, next);
41405
+ return next.length !== channels.length;
41406
+ }
41407
+ async appendEvent(event) {
41408
+ await this.init();
41409
+ const events = await this.readJson(this.eventsPath, []);
41410
+ events.push(event);
41411
+ await this.writeJson(this.eventsPath, events);
41412
+ return event;
41413
+ }
41414
+ async listEvents() {
41415
+ await this.init();
41416
+ return this.readJson(this.eventsPath, []);
41417
+ }
41418
+ async findEventByIdentity(identity) {
41419
+ const events = await this.listEvents();
41420
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
41421
+ }
41422
+ async appendDelivery(result) {
41423
+ await this.init();
41424
+ const deliveries = await this.readJson(this.deliveriesPath, []);
41425
+ deliveries.push(result);
41426
+ await this.writeJson(this.deliveriesPath, deliveries);
41427
+ return result;
41428
+ }
41429
+ async listDeliveries() {
41430
+ await this.init();
41431
+ return this.readJson(this.deliveriesPath, []);
41432
+ }
41433
+ async exportData() {
41434
+ return {
41435
+ channels: await this.listChannels(),
41436
+ events: await this.listEvents(),
41437
+ deliveries: await this.listDeliveries()
41438
+ };
41439
+ }
41440
+ async ensureArrayFile(path) {
41441
+ if (!existsSync(path)) {
41442
+ await writeFile(path, `[]
41443
+ `, { encoding: "utf-8", mode: 384 });
41444
+ }
41445
+ await chmod(path, 384).catch(() => {
41446
+ return;
41447
+ });
41448
+ }
41449
+ async readJson(path, fallback) {
41450
+ try {
41451
+ const raw = await readFile(path, "utf-8");
41452
+ if (!raw.trim())
41453
+ return fallback;
41454
+ return JSON.parse(raw);
41455
+ } catch (error) {
41456
+ if (error.code === "ENOENT")
41457
+ return fallback;
41458
+ throw error;
41459
+ }
41460
+ }
41461
+ async writeJson(path, value) {
41462
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
41463
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
41464
+ `, { encoding: "utf-8", mode: 384 });
41465
+ await rename(tempPath, path);
41466
+ await chmod(path, 384).catch(() => {
41467
+ return;
41468
+ });
41469
+ }
41470
+ }
41471
+ var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
41472
+ function buildSignatureBase(timestamp, body) {
41473
+ return `${timestamp}.${body}`;
41474
+ }
41475
+ function signPayload(secret, timestamp, body) {
41476
+ const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
41477
+ return `sha256=${digest}`;
41478
+ }
41479
+ function now() {
41480
+ return new Date().toISOString();
41481
+ }
41482
+ function truncate(value, max = 4096) {
41483
+ return value.length > max ? `${value.slice(0, max)}...` : value;
41484
+ }
41485
+ function buildWebhookRequest(event, channel) {
41486
+ if (!channel.webhook)
41487
+ throw new Error(`Channel ${channel.id} has no webhook config`);
41488
+ const body = JSON.stringify(event);
41489
+ const timestamp = event.time;
41490
+ const headers = {
41491
+ "Content-Type": "application/json",
41492
+ "User-Agent": "@hasna/events",
41493
+ "X-Hasna-Event-Id": event.id,
41494
+ "X-Hasna-Event-Type": event.type,
41495
+ "X-Hasna-Timestamp": timestamp,
41496
+ ...channel.webhook.headers
41497
+ };
41498
+ if (channel.webhook.secret) {
41499
+ headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
41500
+ }
41501
+ return { body, headers };
41502
+ }
41503
+ async function dispatchWebhook(event, channel, options = {}) {
41504
+ if (!channel.webhook)
41505
+ throw new Error(`Channel ${channel.id} has no webhook config`);
41506
+ const startedAt = now();
41507
+ const { body, headers } = buildWebhookRequest(event, channel);
41508
+ const controller = new AbortController;
41509
+ const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
41510
+ try {
41511
+ const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
41512
+ method: "POST",
41513
+ headers,
41514
+ body,
41515
+ signal: controller.signal
41516
+ });
41517
+ const responseBody = truncate(await response.text());
41518
+ return {
41519
+ attempt: 1,
41520
+ status: response.ok ? "success" : "failed",
41521
+ startedAt,
41522
+ completedAt: now(),
41523
+ responseStatus: response.status,
41524
+ responseBody,
41525
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
41526
+ };
41527
+ } catch (error) {
41528
+ return {
41529
+ attempt: 1,
41530
+ status: "failed",
41531
+ startedAt,
41532
+ completedAt: now(),
41533
+ error: error instanceof Error ? error.message : String(error)
41534
+ };
41535
+ } finally {
41536
+ clearTimeout(timeout);
41537
+ }
41538
+ }
41539
+ async function dispatchCommand(event, channel) {
41540
+ if (!channel.command)
41541
+ throw new Error(`Channel ${channel.id} has no command config`);
41542
+ const startedAt = now();
41543
+ const eventJson = JSON.stringify(event);
41544
+ const env = {
41545
+ ...process.env,
41546
+ ...channel.command.env,
41547
+ HASNA_CHANNEL_ID: channel.id,
41548
+ HASNA_EVENT_ID: event.id,
41549
+ HASNA_EVENT_TYPE: event.type,
41550
+ HASNA_EVENT_SOURCE: event.source,
41551
+ HASNA_EVENT_SUBJECT: event.subject ?? "",
41552
+ HASNA_EVENT_SEVERITY: event.severity,
41553
+ HASNA_EVENT_TIME: event.time,
41554
+ HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
41555
+ HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
41556
+ HASNA_EVENT_JSON: eventJson
41557
+ };
41558
+ return new Promise((resolve) => {
41559
+ const child = spawn(channel.command.command, channel.command.args ?? [], {
41560
+ cwd: channel.command.cwd,
41561
+ env,
41562
+ stdio: ["pipe", "pipe", "pipe"]
41563
+ });
41564
+ let stdout = "";
41565
+ let stderr = "";
41566
+ const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
41567
+ child.stdin.end(eventJson);
41568
+ child.stdout.on("data", (chunk) => {
41569
+ stdout += chunk.toString();
41570
+ });
41571
+ child.stderr.on("data", (chunk) => {
41572
+ stderr += chunk.toString();
41573
+ });
41574
+ child.on("error", (error) => {
41575
+ clearTimeout(timeout);
41576
+ resolve({
41577
+ attempt: 1,
41578
+ status: "failed",
41579
+ startedAt,
41580
+ completedAt: now(),
41581
+ stdout: truncate(stdout),
41582
+ stderr: truncate(stderr),
41583
+ error: error.message
41584
+ });
41585
+ });
41586
+ child.on("close", (code, signal) => {
41587
+ clearTimeout(timeout);
41588
+ const success = code === 0;
41589
+ resolve({
41590
+ attempt: 1,
41591
+ status: success ? "success" : "failed",
41592
+ startedAt,
41593
+ completedAt: now(),
41594
+ stdout: truncate(stdout),
41595
+ stderr: truncate(stderr),
41596
+ error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
41597
+ });
41598
+ });
41599
+ });
41600
+ }
41601
+ async function dispatchChannel(event, channel, options = {}) {
41602
+ if (channel.transport === "webhook")
41603
+ return dispatchWebhook(event, channel, options);
41604
+ if (channel.transport === "command")
41605
+ return dispatchCommand(event, channel);
41606
+ return {
41607
+ attempt: 1,
41608
+ status: "skipped",
41609
+ startedAt: now(),
41610
+ completedAt: now(),
41611
+ error: `Unsupported transport: ${channel.transport}`
41612
+ };
41613
+ }
41614
+ function createDeliveryResult(event, channel, attempts) {
41615
+ const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
41616
+ return {
41617
+ id: randomUUID(),
41618
+ eventId: event.id,
41619
+ channelId: channel.id,
41620
+ transport: channel.transport,
41621
+ status,
41622
+ attempts,
41623
+ createdAt: attempts[0]?.startedAt ?? now(),
41624
+ completedAt: attempts.at(-1)?.completedAt ?? now()
41625
+ };
41626
+ }
41627
+ function createEvent(input) {
41628
+ return {
41629
+ id: input.id ?? randomUUID2(),
41630
+ source: input.source,
41631
+ type: input.type,
41632
+ time: normalizeTime(input.time),
41633
+ subject: input.subject,
41634
+ severity: input.severity ?? "info",
41635
+ data: input.data ?? {},
41636
+ message: input.message,
41637
+ dedupeKey: input.dedupeKey,
41638
+ schemaVersion: input.schemaVersion ?? "1.0",
41639
+ metadata: input.metadata ?? {}
41640
+ };
41641
+ }
41642
+
41643
+ class EventsClient {
41644
+ store;
41645
+ redactors;
41646
+ transportOptions;
41647
+ constructor(options = {}) {
41648
+ this.store = options.store ?? new JsonEventsStore(options.dataDir);
41649
+ this.redactors = options.redactors ?? [];
41650
+ this.transportOptions = { fetchImpl: options.fetchImpl };
41651
+ }
41652
+ async addChannel(input) {
41653
+ const timestamp = new Date().toISOString();
41654
+ return this.store.addChannel({
41655
+ ...input,
41656
+ createdAt: input.createdAt ?? timestamp,
41657
+ updatedAt: input.updatedAt ?? timestamp
41658
+ });
41659
+ }
41660
+ async listChannels() {
41661
+ return this.store.listChannels();
41662
+ }
41663
+ async removeChannel(id) {
41664
+ return this.store.removeChannel(id);
41665
+ }
41666
+ async emit(input, options = {}) {
41667
+ const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
41668
+ if (options.dedupe !== false) {
41669
+ const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
41670
+ if (existing) {
41671
+ return { event: existing, deliveries: [], deduped: true };
41672
+ }
41673
+ }
41674
+ await this.store.appendEvent(event);
41675
+ const deliveries = options.deliver === false ? [] : await this.deliver(event);
41676
+ return { event, deliveries, deduped: false };
41677
+ }
41678
+ async listEvents() {
41679
+ return this.store.listEvents();
41680
+ }
41681
+ async listDeliveries() {
41682
+ return this.store.listDeliveries();
41683
+ }
41684
+ async deliver(event) {
41685
+ const channels = await this.store.listChannels();
41686
+ const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
41687
+ const deliveries = [];
41688
+ for (const channel of selected) {
41689
+ const eventForChannel = await this.applyRedaction(event, channel);
41690
+ const result = await this.deliverWithRetry(eventForChannel, channel);
41691
+ await this.store.appendDelivery(result);
41692
+ deliveries.push(result);
41693
+ }
41694
+ return deliveries;
41695
+ }
41696
+ async testChannel(id, input = {}) {
41697
+ const channel = await this.store.getChannel(id);
41698
+ if (!channel)
41699
+ throw new Error(`Channel not found: ${id}`);
41700
+ const event = createEvent({
41701
+ source: input.source ?? "hasna.events",
41702
+ type: input.type ?? "events.test",
41703
+ subject: input.subject ?? id,
41704
+ severity: input.severity ?? "info",
41705
+ data: input.data ?? { test: true },
41706
+ message: input.message ?? "Hasna events test delivery",
41707
+ dedupeKey: input.dedupeKey,
41708
+ schemaVersion: input.schemaVersion,
41709
+ metadata: input.metadata,
41710
+ time: input.time,
41711
+ id: input.id
41712
+ });
41713
+ const eventForChannel = await this.applyRedaction(event, channel);
41714
+ const result = await this.deliverWithRetry(eventForChannel, channel);
41715
+ await this.store.appendDelivery(result);
41716
+ return result;
41717
+ }
41718
+ async replay(options = {}) {
41719
+ const events = (await this.store.listEvents()).filter((event) => {
41720
+ if (options.eventId && event.id !== options.eventId)
41721
+ return false;
41722
+ if (options.source && event.source !== options.source)
41723
+ return false;
41724
+ if (options.type && event.type !== options.type)
41725
+ return false;
41726
+ return true;
41727
+ });
41728
+ if (options.dryRun)
41729
+ return { events, deliveries: [] };
41730
+ const deliveries = [];
41731
+ for (const event of events) {
41732
+ deliveries.push(...await this.deliver(event));
41733
+ }
41734
+ return { events, deliveries };
41735
+ }
41736
+ async applyRedaction(event, channel) {
41737
+ let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
41738
+ for (const redactor of this.redactors) {
41739
+ next = await redactor(next, channel);
41740
+ }
41741
+ return next;
41742
+ }
41743
+ async deliverWithRetry(event, channel) {
41744
+ const policy = normalizeRetryPolicy(channel.retry);
41745
+ const attempts = [];
41746
+ for (let index = 0;index < policy.maxAttempts; index += 1) {
41747
+ const attempt = await dispatchChannel(event, channel, this.transportOptions);
41748
+ attempt.attempt = index + 1;
41749
+ if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
41750
+ attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
41751
+ }
41752
+ attempts.push(attempt);
41753
+ if (attempt.status !== "failed")
41754
+ break;
41755
+ if (attempt.nextBackoffMs)
41756
+ await Bun.sleep(attempt.nextBackoffMs);
41757
+ }
41758
+ return createDeliveryResult(event, channel, attempts);
41759
+ }
41760
+ }
41761
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
41762
+ if (paths.length === 0)
41763
+ return event;
41764
+ const copy = structuredClone(event);
41765
+ for (const path of paths) {
41766
+ setPath(copy, path, replacement);
41767
+ }
41768
+ return copy;
41769
+ }
41770
+ function sanitizeChannelForOutput(channel) {
41771
+ const copy = structuredClone(channel);
41772
+ if (copy.webhook?.secret)
41773
+ copy.webhook.secret = "[REDACTED]";
41774
+ if (copy.command?.env) {
41775
+ copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
41776
+ }
41777
+ return copy;
41778
+ }
41779
+ function sanitizeChannelsForOutput(channels) {
41780
+ return channels.map(sanitizeChannelForOutput);
41781
+ }
41782
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
41783
+ return redactValue(event, replacement);
41784
+ }
41785
+ function shouldRedactKey(key) {
41786
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
41787
+ }
41788
+ function redactValue(value, replacement) {
41789
+ if (Array.isArray(value))
41790
+ return value.map((item) => redactValue(item, replacement));
41791
+ if (!value || typeof value !== "object")
41792
+ return value;
41793
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
41794
+ key,
41795
+ shouldRedactKey(key) ? replacement : redactValue(item, replacement)
41796
+ ]));
41797
+ }
41798
+ function setPath(input, path, replacement) {
41799
+ const parts = path.split(".");
41800
+ let cursor = input;
41801
+ for (const part of parts.slice(0, -1)) {
41802
+ const next = cursor[part];
41803
+ if (!next || typeof next !== "object")
41804
+ return;
41805
+ cursor = next;
41806
+ }
41807
+ const last = parts.at(-1);
41808
+ if (last && last in cursor)
41809
+ cursor[last] = replacement;
41810
+ }
41811
+ function normalizeTime(value) {
41812
+ if (!value)
41813
+ return new Date().toISOString();
41814
+ return value instanceof Date ? value.toISOString() : value;
41815
+ }
41816
+ function normalizeRetryPolicy(policy) {
41817
+ return {
41818
+ maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
41819
+ backoffMs: Math.max(0, policy?.backoffMs ?? 250),
41820
+ multiplier: Math.max(1, policy?.multiplier ?? 2)
41821
+ };
41822
+ }
41823
+ function parseJsonObject(value, fallback) {
41824
+ if (!value)
41825
+ return fallback;
41826
+ const parsed = JSON.parse(value);
41827
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
41828
+ throw new Error("Expected a JSON object");
41829
+ }
41830
+ return parsed;
41831
+ }
41832
+ function parseHeaders(values) {
41833
+ if (!values?.length)
41834
+ return;
41835
+ const headers = {};
41836
+ for (const value of values) {
41837
+ const separator = value.indexOf("=");
41838
+ if (separator === -1)
41839
+ throw new Error(`Invalid header, expected name=value: ${value}`);
41840
+ headers[value.slice(0, separator)] = value.slice(separator + 1);
41841
+ }
41842
+ return headers;
41843
+ }
41844
+ function parseFilter(options) {
41845
+ const filter2 = {};
41846
+ if (options.source)
41847
+ filter2.source = options.source;
41848
+ if (options.type)
41849
+ filter2.type = options.type;
41850
+ if (options.subject)
41851
+ filter2.subject = options.subject;
41852
+ if (options.severity)
41853
+ filter2.severity = options.severity;
41854
+ return Object.keys(filter2).length > 0 ? [filter2] : undefined;
41855
+ }
41856
+ function createClient(options) {
41857
+ if (options.createClient)
41858
+ return options.createClient();
41859
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
41860
+ }
41861
+ function print(value, json, text) {
41862
+ if (json)
41863
+ console.log(JSON.stringify(value, null, 2));
41864
+ else
41865
+ console.log(text);
41866
+ }
41867
+ function registerWebhookCommands(program2, options) {
41868
+ const webhooks = program2.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
41869
+ webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions) => {
41870
+ const timestamp = new Date().toISOString();
41871
+ const channel = {
41872
+ id: actionOptions.id,
41873
+ name: actionOptions.name,
41874
+ enabled: !actionOptions.disabled,
41875
+ transport: actionOptions.transport,
41876
+ filters: parseFilter(actionOptions),
41877
+ retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
41878
+ redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
41879
+ createdAt: timestamp,
41880
+ updatedAt: timestamp
41881
+ };
41882
+ if (actionOptions.transport === "webhook") {
41883
+ channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
41884
+ } else if (actionOptions.transport === "command") {
41885
+ channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
41886
+ } else {
41887
+ throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
41888
+ }
41889
+ const saved = await createClient(options).addChannel(channel);
41890
+ print(sanitizeChannelForOutput(saved), Boolean(actionOptions.json), `Added ${saved.transport} channel ${saved.id}`);
41891
+ });
41892
+ webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
41893
+ const channels = await createClient(options).listChannels();
41894
+ if (actionOptions.json) {
41895
+ console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
41896
+ return;
41897
+ }
41898
+ if (!channels.length) {
41899
+ console.log("No channels configured.");
41900
+ return;
41901
+ }
41902
+ for (const channel of channels) {
41903
+ console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
41904
+ }
41905
+ });
41906
+ webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
41907
+ const removed = await createClient(options).removeChannel(id);
41908
+ print({ removed }, Boolean(actionOptions.json), removed ? `Removed ${id}` : `Channel not found: ${id}`);
41909
+ });
41910
+ webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
41911
+ const result = await createClient(options).testChannel(id, {
41912
+ source: options.source,
41913
+ type: actionOptions.type,
41914
+ subject: actionOptions.subject ?? id,
41915
+ message: actionOptions.message,
41916
+ data: parseJsonObject(actionOptions.data, { test: true })
41917
+ });
41918
+ print(result, Boolean(actionOptions.json), `${result.status}: ${result.channelId}`);
41919
+ });
41920
+ return webhooks;
41921
+ }
41922
+ function registerEventCommands(program2, options) {
41923
+ const events = program2.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
41924
+ events.command("emit").description("Emit an event from this app").argument("<type>", "Event type").option("--source <source>", "Event source override").option("--subject <subject>", "Event subject").option("--severity <severity>", "Event severity", "info").option("--message <message>", "Event message").option("--dedupe-key <key>", "Dedupe key").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--no-deliver", "Record without delivering").option("--no-dedupe", "Allow duplicate id/dedupeKey events").option("-j, --json", "Print JSON output", false).action(async (type, actionOptions) => {
41925
+ const result = await createClient(options).emit({
41926
+ source: actionOptions.source ?? options.source,
41927
+ type,
41928
+ subject: actionOptions.subject,
41929
+ severity: actionOptions.severity,
41930
+ message: actionOptions.message,
41931
+ dedupeKey: actionOptions.dedupeKey,
41932
+ data: parseJsonObject(actionOptions.data, {}),
41933
+ metadata: parseJsonObject(actionOptions.metadata, {})
41934
+ }, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
41935
+ print(result, Boolean(actionOptions.json), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
41936
+ });
41937
+ events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Limit results", parseNumber).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
41938
+ let rows = await createClient(options).listEvents();
41939
+ if (actionOptions.source)
41940
+ rows = rows.filter((event) => event.source === actionOptions.source);
41941
+ if (actionOptions.type)
41942
+ rows = rows.filter((event) => event.type === actionOptions.type);
41943
+ if (actionOptions.limit)
41944
+ rows = rows.slice(-actionOptions.limit);
41945
+ if (actionOptions.json) {
41946
+ console.log(JSON.stringify(rows, null, 2));
41947
+ return;
41948
+ }
41949
+ if (!rows.length) {
41950
+ console.log("No events recorded.");
41951
+ return;
41952
+ }
41953
+ for (const event of rows)
41954
+ console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
41955
+ });
41956
+ events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
41957
+ const result = await createClient(options).replay({
41958
+ eventId: actionOptions.id,
41959
+ source: actionOptions.source,
41960
+ type: actionOptions.type,
41961
+ dryRun: actionOptions.dryRun
41962
+ });
41963
+ print(result, Boolean(actionOptions.json), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
41964
+ });
41965
+ return events;
41966
+ }
41967
+ function registerEventsCommands(program2, options) {
41968
+ registerWebhookCommands(program2, options);
41969
+ registerEventCommands(program2, options);
41970
+ }
41971
+ function parseNumber(value) {
41972
+ const parsed = Number(value);
41973
+ if (!Number.isFinite(parsed))
41974
+ throw new Error(`Expected a number, got ${value}`);
41975
+ return parsed;
41976
+ }
41977
+ function collectValues(value, previous) {
41978
+ previous.push(value);
41979
+ return previous;
41980
+ }
41981
+
40556
41982
  // src/cli/commands/domain.ts
40557
41983
  init_domains();
40558
41984
 
@@ -40655,6 +42081,61 @@ async function renewDomain(domain, years = 1, config) {
40655
42081
  chargedAmount: chargedAmount || undefined
40656
42082
  };
40657
42083
  }
42084
+ function namecheapContactParams(contact) {
42085
+ const organization = contact.organization_name || "NA";
42086
+ const base = {
42087
+ FirstName: contact.first_name,
42088
+ LastName: contact.last_name,
42089
+ OrganizationName: organization,
42090
+ Address1: contact.address_line_1,
42091
+ City: contact.city,
42092
+ StateProvince: contact.state,
42093
+ PostalCode: contact.zip_code,
42094
+ Country: contact.country_code,
42095
+ Phone: contact.phone,
42096
+ EmailAddress: contact.email
42097
+ };
42098
+ const params = {};
42099
+ for (const prefix of ["Registrant", "Tech", "Admin", "AuxBilling"]) {
42100
+ for (const [key, value] of Object.entries(base)) {
42101
+ params[prefix + key] = value;
42102
+ }
42103
+ }
42104
+ return params;
42105
+ }
42106
+ async function registerDomain(domain, contact, options = {}, config) {
42107
+ const cfg = config || getConfig();
42108
+ const params = {
42109
+ DomainName: domain,
42110
+ Years: String(options.years ?? 1),
42111
+ AddFreeWhoisguard: options.whoisGuard === false ? "no" : "yes",
42112
+ WGEnabled: options.whoisGuard === false ? "no" : "yes",
42113
+ ...namecheapContactParams(contact)
42114
+ };
42115
+ if (options.premiumPrice !== undefined) {
42116
+ params.IsPremiumDomain = "true";
42117
+ params.PremiumPrice = String(options.premiumPrice);
42118
+ }
42119
+ const xml = await apiRequest(cfg, "namecheap.domains.create", params);
42120
+ return {
42121
+ domain,
42122
+ success: xml.includes('Status="OK"') || xml.includes('Registered="true"'),
42123
+ orderId: parseXmlValue(xml, "OrderId") || undefined,
42124
+ chargedAmount: parseXmlValue(xml, "ChargedAmount") || undefined
42125
+ };
42126
+ }
42127
+ async function updateNameservers(domain, nameservers, config) {
42128
+ if (nameservers.length === 0)
42129
+ throw new Error("updateNameservers requires at least one nameserver");
42130
+ const cfg = config || getConfig();
42131
+ const { sld, tld } = splitDomain(domain);
42132
+ const xml = await apiRequest(cfg, "namecheap.domains.dns.setCustom", {
42133
+ SLD: sld,
42134
+ TLD: tld,
42135
+ Nameservers: nameservers.join(",")
42136
+ });
42137
+ return xml.includes('Status="OK"') || xml.includes('Updated="true"');
42138
+ }
40658
42139
  async function getDnsRecords(_domain, sld, tld, config) {
40659
42140
  const cfg = config || getConfig();
40660
42141
  const xml = await apiRequest(cfg, "namecheap.domains.dns.getHosts", {
@@ -40783,141 +42264,12 @@ function normalizeDate(dateStr) {
40783
42264
  }
40784
42265
  }
40785
42266
 
40786
- // src/lib/godaddy.ts
40787
- class GoDaddyApiError extends Error {
40788
- statusCode;
40789
- details;
40790
- constructor(message, statusCode, details) {
40791
- super(message);
40792
- this.statusCode = statusCode;
40793
- this.details = details;
40794
- this.name = "GoDaddyApiError";
40795
- }
40796
- }
40797
- var _overriddenFetch = null;
40798
- function getCredentials() {
40799
- const apiKey = process.env["GODADDY_API_KEY"];
40800
- const apiSecret = process.env["GODADDY_API_SECRET"];
40801
- if (!apiKey || !apiSecret) {
40802
- throw new Error("GoDaddy API credentials not configured. Set GODADDY_API_KEY and GODADDY_API_SECRET environment variables.");
40803
- }
40804
- return { apiKey, apiSecret };
40805
- }
40806
- function getHeaders() {
40807
- const { apiKey, apiSecret } = getCredentials();
40808
- return {
40809
- Authorization: `sso-key ${apiKey}:${apiSecret}`,
40810
- "Content-Type": "application/json",
40811
- Accept: "application/json"
40812
- };
40813
- }
40814
- var GODADDY_API_BASE = "https://api.godaddy.com";
40815
- async function apiRequest2(method, path, body) {
40816
- const fetchFn = _overriddenFetch || globalThis.fetch;
40817
- const url = `${GODADDY_API_BASE}${path}`;
40818
- const headers = getHeaders();
40819
- const options = { method, headers };
40820
- if (body !== undefined) {
40821
- options.body = JSON.stringify(body);
40822
- }
40823
- const response = await fetchFn(url, options);
40824
- if (!response.ok) {
40825
- const text = await response.text();
40826
- throw new GoDaddyApiError(`GoDaddy API ${method} ${path} failed with status ${response.status}: ${text}`, response.status, { responseBody: text });
40827
- }
40828
- if (response.status === 204) {
40829
- return;
40830
- }
40831
- return await response.json();
40832
- }
40833
- async function listGoDaddyDomains() {
40834
- return apiRequest2("GET", "/v1/domains");
40835
- }
40836
- async function getDomainInfo2(domain) {
40837
- return apiRequest2("GET", `/v1/domains/${encodeURIComponent(domain)}`);
40838
- }
40839
- async function renewDomain2(domain) {
40840
- return apiRequest2("POST", `/v1/domains/${encodeURIComponent(domain)}/renew`, { period: 1 });
40841
- }
40842
- async function getDnsRecords2(domain, type) {
40843
- const path = type ? `/v1/domains/${encodeURIComponent(domain)}/records/${encodeURIComponent(type)}` : `/v1/domains/${encodeURIComponent(domain)}/records`;
40844
- return apiRequest2("GET", path);
40845
- }
40846
- async function setDnsRecords2(domain, records) {
40847
- await apiRequest2("PUT", `/v1/domains/${encodeURIComponent(domain)}/records`, records);
40848
- }
40849
- async function checkAvailability2(domain) {
40850
- return apiRequest2("GET", `/v1/domains/available?domain=${encodeURIComponent(domain)}`);
40851
- }
40852
- function mapGoDaddyStatus(gdStatus) {
40853
- const s = gdStatus.toUpperCase();
40854
- if (s === "ACTIVE")
40855
- return "active";
40856
- if (s === "EXPIRED")
40857
- return "expired";
40858
- if (s === "TRANSFERRED_OUT" || s === "TRANSFERRING" || s === "PENDING_TRANSFER")
40859
- return "transferring";
40860
- if (s === "REDEMPTION" || s === "PENDING_REDEMPTION")
40861
- return "redemption";
40862
- return "active";
40863
- }
40864
- async function syncToLocalDb2(dbFns) {
40865
- const result = {
40866
- synced: 0,
40867
- created: 0,
40868
- updated: 0,
40869
- errors: []
40870
- };
40871
- let gdDomains;
40872
- try {
40873
- gdDomains = await listGoDaddyDomains();
40874
- } catch (err) {
40875
- result.errors.push(`Failed to list domains: ${err instanceof Error ? err.message : String(err)}`);
40876
- return result;
40877
- }
40878
- for (const gd of gdDomains) {
40879
- try {
40880
- let detail;
40881
- try {
40882
- detail = await getDomainInfo2(gd.domain);
40883
- } catch {
40884
- detail = gd;
40885
- }
40886
- const existing = dbFns.getDomainByName(gd.domain);
40887
- const domainData = {
40888
- name: gd.domain,
40889
- registrar: "GoDaddy",
40890
- status: mapGoDaddyStatus(gd.status),
40891
- expires_at: gd.expires ? new Date(gd.expires).toISOString() : undefined,
40892
- auto_renew: gd.renewAuto,
40893
- nameservers: gd.nameServers || [],
40894
- registered_at: detail.createdAt ? new Date(detail.createdAt).toISOString() : undefined,
40895
- metadata: {
40896
- godaddy_domain_id: detail.domainId,
40897
- provider: "godaddy",
40898
- locked: detail.locked,
40899
- privacy: detail.privacy
40900
- }
40901
- };
40902
- if (existing) {
40903
- dbFns.updateDomain(existing.id, domainData);
40904
- result.updated++;
40905
- } else {
40906
- dbFns.createDomain(domainData);
40907
- result.created++;
40908
- }
40909
- result.synced++;
40910
- } catch (err) {
40911
- result.errors.push(`Failed to sync ${gd.domain}: ${err instanceof Error ? err.message : String(err)}`);
40912
- }
40913
- }
40914
- return result;
40915
- }
40916
-
40917
42267
  // src/lib/registrar.ts
42268
+ init_godaddy();
40918
42269
  init_route53();
40919
42270
  init_cloudflare();
40920
42271
  init_brandsight();
42272
+ init_env_aliases();
40921
42273
  function createNamecheapProvider() {
40922
42274
  return {
40923
42275
  name: "namecheap",
@@ -40947,9 +42299,27 @@ function createNamecheapProvider() {
40947
42299
  auto_renew: true
40948
42300
  };
40949
42301
  },
40950
- async renewDomain(domain) {
42302
+ async registerDomain(domain, contact, options = {}) {
40951
42303
  const config = getConfig();
40952
- const result = await renewDomain(domain, 1, config);
42304
+ const result = await registerDomain(domain, contact, {
42305
+ years: options.years,
42306
+ premiumPrice: options.premiumPrice
42307
+ }, config);
42308
+ return {
42309
+ domain: result.domain,
42310
+ success: result.success,
42311
+ orderId: result.orderId,
42312
+ chargedAmount: result.chargedAmount
42313
+ };
42314
+ },
42315
+ async updateNameservers(domain, nameservers) {
42316
+ const config = getConfig();
42317
+ const success = await updateNameservers(domain, nameservers, config);
42318
+ return { domain, success };
42319
+ },
42320
+ async renewDomain(domain, years = 1) {
42321
+ const config = getConfig();
42322
+ const result = await renewDomain(domain, years, config);
40953
42323
  return {
40954
42324
  domain: result.domain,
40955
42325
  success: result.success,
@@ -41085,8 +42455,9 @@ var providerRegistry = new Map([
41085
42455
  name: "namecheap",
41086
42456
  type: "full",
41087
42457
  configured: false,
41088
- envVars: ["NAMECHEAP_API_KEY", "NAMECHEAP_USERNAME", "NAMECHEAP_CLIENT_IP"]
42458
+ envVars: providerEnvNames("namecheap")
41089
42459
  },
42460
+ createInventory: createNamecheapProvider,
41090
42461
  createRegistrar: createNamecheapProvider,
41091
42462
  createDns: createNamecheapProvider
41092
42463
  }],
@@ -41095,8 +42466,9 @@ var providerRegistry = new Map([
41095
42466
  name: "godaddy",
41096
42467
  type: "full",
41097
42468
  configured: false,
41098
- envVars: ["GODADDY_API_KEY", "GODADDY_API_SECRET"]
42469
+ envVars: providerEnvNames("godaddy")
41099
42470
  },
42471
+ createInventory: createGoDaddyProvider,
41100
42472
  createRegistrar: createGoDaddyProvider,
41101
42473
  createDns: createGoDaddyProvider
41102
42474
  }],
@@ -41105,8 +42477,9 @@ var providerRegistry = new Map([
41105
42477
  name: "route53",
41106
42478
  type: "full",
41107
42479
  configured: false,
41108
- envVars: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"]
42480
+ envVars: providerEnvNames("route53")
41109
42481
  },
42482
+ createInventory: () => createRoute53Provider(),
41110
42483
  createRegistrar: () => createRoute53Provider(),
41111
42484
  createDns: () => createRoute53Provider()
41112
42485
  }],
@@ -41115,47 +42488,77 @@ var providerRegistry = new Map([
41115
42488
  name: "cloudflare",
41116
42489
  type: "dns",
41117
42490
  configured: false,
41118
- envVars: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"]
42491
+ envVars: providerEnvNames("cloudflare")
41119
42492
  },
42493
+ createInventory: () => createCloudflareProvider(),
41120
42494
  createDns: createCloudflareProvider
41121
42495
  }],
41122
42496
  ["brandsight", {
41123
42497
  info: {
41124
42498
  name: "brandsight",
41125
- type: "registrar",
42499
+ type: "full",
41126
42500
  configured: false,
41127
- envVars: ["BRANDSIGHT_API_KEY", "BRANDSIGHT_ACCOUNT_ID"]
42501
+ envVars: providerEnvNames("brandsight")
41128
42502
  },
41129
- createRegistrar: createBrandsightProvider
42503
+ createInventory: createBrandsightProvider,
42504
+ createRegistrar: createBrandsightProvider,
42505
+ createDns: createBrandsightProvider
42506
+ }],
42507
+ ["sedo", {
42508
+ info: {
42509
+ name: "sedo",
42510
+ type: "marketplace",
42511
+ configured: false,
42512
+ envVars: providerEnvNames("sedo")
42513
+ }
41130
42514
  }]
41131
42515
  ]);
41132
- function isConfigured(envVars) {
41133
- return envVars.slice(0, 2).every((v3) => !!process.env[v3]);
42516
+ function isConfigured(providerName) {
42517
+ return hasProviderCredentials(providerName);
41134
42518
  }
41135
42519
  function getAvailableProviders() {
41136
42520
  return Array.from(providerRegistry.values()).map((e3) => ({
41137
42521
  ...e3.info,
41138
- configured: isConfigured(e3.info.envVars)
42522
+ configured: isConfigured(e3.info.name),
42523
+ inventory: !!e3.createInventory
41139
42524
  }));
41140
42525
  }
42526
+ function getProviderInfo(name) {
42527
+ const entry = providerRegistry.get(name.toLowerCase());
42528
+ if (!entry)
42529
+ return null;
42530
+ return { ...entry.info, configured: isConfigured(entry.info.name), inventory: !!entry.createInventory };
42531
+ }
42532
+ function providerHasRegistrar(name) {
42533
+ return !!providerRegistry.get(name.toLowerCase())?.createRegistrar;
42534
+ }
42535
+ function providerHasInventory(name) {
42536
+ return !!providerRegistry.get(name.toLowerCase())?.createInventory;
42537
+ }
42538
+ function getDomainInventoryProvider(name) {
42539
+ const entry = providerRegistry.get(name.toLowerCase());
42540
+ if (!entry?.createInventory)
42541
+ throw new Error(`No domain inventory provider: ${name}`);
42542
+ return entry.createInventory();
42543
+ }
41141
42544
  function getRegistrarProvider(name) {
41142
- const entry = providerRegistry.get(name);
42545
+ const entry = providerRegistry.get(name.toLowerCase());
41143
42546
  if (!entry?.createRegistrar)
41144
42547
  throw new Error(`No registrar provider: ${name}`);
41145
42548
  return entry.createRegistrar();
41146
42549
  }
41147
42550
  function getDnsProvider(name) {
41148
- const entry = providerRegistry.get(name);
42551
+ const entry = providerRegistry.get(name.toLowerCase());
41149
42552
  if (!entry?.createDns)
41150
42553
  throw new Error(`No DNS provider: ${name}`);
41151
42554
  return entry.createDns();
41152
42555
  }
41153
42556
  async function syncAll(dbFns) {
41154
- const available = getAvailableProviders().filter((p3) => p3.configured && (p3.type === "registrar" || p3.type === "full") && p3.name !== "brandsight");
42557
+ const available = getAvailableProviders().filter((p3) => p3.configured && providerHasInventory(p3.name));
41155
42558
  const result = { providers: [], totalSynced: 0, totalErrors: [] };
41156
42559
  for (const info of available) {
41157
42560
  try {
41158
- const provider = getRegistrarProvider(info.name);
42561
+ const provider = getDomainInventoryProvider(info.name);
41159
42562
  const syncResult = await provider.syncToLocalDb(dbFns);
41160
42563
  result.providers.push({ name: info.name, result: syncResult });
41161
42564
  result.totalSynced += syncResult.synced;
@@ -41173,14 +42576,16 @@ function autoDetectRegistrar(domain, getDomainByName2) {
41173
42576
  if (!dbDomain?.registrar)
41174
42577
  return null;
41175
42578
  const r3 = dbDomain.registrar.toLowerCase();
42579
+ if (r3.includes("cloudflare dns") || r3.includes("route 53 dns") || r3.includes("route53 dns"))
42580
+ return null;
41176
42581
  if (r3.includes("namecheap"))
41177
42582
  return "namecheap";
41178
42583
  if (r3.includes("godaddy"))
41179
42584
  return "godaddy";
41180
- if (r3.includes("route 53") || r3.includes("route53") || r3.includes("aws"))
42585
+ if (r3.includes("route 53") || r3.includes("route53"))
41181
42586
  return "route53";
41182
42587
  if (r3.includes("cloudflare"))
41183
- return "cloudflare";
42588
+ return null;
41184
42589
  if (r3.includes("brandsight"))
41185
42590
  return "brandsight";
41186
42591
  return null;
@@ -41520,10 +42925,10 @@ WHOIS for ${result.domain} [${result.source}]:`);
41520
42925
  process.exit(1);
41521
42926
  });
41522
42927
  domain.command("sync").description("Sync domains from a provider to the local DB").option("--provider <name>", "Provider name (default: all configured)").action(async (opts) => {
41523
- const providers = opts.provider ? [opts.provider] : getAvailableProviders().filter((p3) => p3.configured && p3.type !== "dns" && p3.name !== "brandsight").map((p3) => p3.name);
42928
+ const providers = opts.provider ? [opts.provider] : getAvailableProviders().filter((p3) => p3.configured && providerHasInventory(p3.name)).map((p3) => p3.name);
41524
42929
  for (const name of providers) {
41525
42930
  try {
41526
- const provider = getRegistrarProvider(name);
42931
+ const provider = getDomainInventoryProvider(name);
41527
42932
  const result = await provider.syncToLocalDb({ getDomainByName, createDomain, updateDomain });
41528
42933
  console.log(`\u2713 [${name}] Synced ${result.synced} (${result.created} new, ${result.updated} updated)`);
41529
42934
  if (result.errors.length > 0)
@@ -41607,14 +43012,14 @@ WHOIS for ${result.domain} [${result.source}]:`);
41607
43012
  }
41608
43013
  console.log(`Linked ${emailId} to ${details.domain.name}`);
41609
43014
  });
41610
- domain.command("renew <name>").description("Renew a domain via its registrar provider").option("--provider <name>", "Override provider").action(async (name, opts) => {
43015
+ domain.command("renew <name>").description("Renew a domain via its registrar provider").option("--provider <name>", "Override provider").option("--years <n>", "Number of years to renew", "1").action(async (name, opts) => {
41611
43016
  const providerName = opts.provider ?? autoDetectRegistrar(name, getDomainByName) ?? loadConfig3().default_registrar;
41612
43017
  if (!providerName) {
41613
43018
  console.error("Could not detect provider. Use --provider.");
41614
43019
  process.exit(1);
41615
43020
  }
41616
43021
  const provider = getRegistrarProvider(providerName);
41617
- const result = await provider.renewDomain(name);
43022
+ const result = await provider.renewDomain(name, parseInt(opts.years, 10));
41618
43023
  if (result.success) {
41619
43024
  console.log(`\u2713 Renewed: ${name}`);
41620
43025
  if (result.orderId)
@@ -41624,7 +43029,7 @@ WHOIS for ${result.domain} [${result.source}]:`);
41624
43029
  process.exit(1);
41625
43030
  }
41626
43031
  });
41627
- 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("--registrar <name>", "Registrar/seller for recorded purchases (alias of --provider)").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("--price <amount>", "Record a completed purchase instead of registering via Route 53").option("--expires <date>", "Expiry date for recorded purchases").option("--auto-renew <bool>", "Auto-renew for recorded purchases (true/false)").option("--years <n>", "Years", "1").option("--wait", "Poll until registration completes").option("--dns <provider>", "DNS provider to delegate to after purchase (always cloudflare)", "cloudflare").option("--no-delegate", "Skip delegating DNS to Cloudflare after purchase").action(async (name, opts) => {
43032
+ domain.command("buy <name>").description("Purchase or record a domain via registrar provider (contact defaults from: domains config set contact.*)").option("--provider <name>", "Registrar provider (default: config default-registrar or route53)").option("--registrar <name>", "Registrar/seller for recorded purchases (alias of --provider)").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("--price <amount>", "Record a completed purchase instead of registering via Route 53").option("--expires <date>", "Expiry date for recorded purchases").option("--auto-renew <bool>", "Auto-renew for recorded purchases (true/false)").option("--years <n>", "Years", "1").option("--wait", "Poll until registration completes").option("--dns <provider>", "DNS provider to delegate to after purchase (always cloudflare)", "cloudflare").option("--no-delegate", "Skip delegating DNS to Cloudflare after purchase").action(async (name, opts) => {
41628
43033
  const recordedPrice = parseOptionalNumber(opts.price, "--price");
41629
43034
  if (recordedPrice !== undefined) {
41630
43035
  const registrarName = opts.registrar ?? opts.provider ?? "manual";
@@ -41648,12 +43053,75 @@ WHOIS for ${result.domain} [${result.source}]:`);
41648
43053
  }
41649
43054
  const cfg = loadConfig3();
41650
43055
  const providerName = opts.registrar ?? opts.provider ?? cfg.default_registrar ?? "route53";
41651
- const purchaseProfile = applyPurchaseProfile();
43056
+ const purchaseProfile = providerName === "route53" ? applyPurchaseProfile() : undefined;
41652
43057
  if (purchaseProfile)
41653
43058
  console.log(`Using purchase AWS profile: ${purchaseProfile}`);
41654
43059
  if (providerName !== "route53") {
41655
- console.error(`Direct domain purchase only supported via route53. Got: ${providerName}`);
41656
- process.exit(1);
43060
+ try {
43061
+ const provider = getRegistrarProvider(providerName);
43062
+ if (!provider.registerDomain) {
43063
+ console.error(`Direct domain purchase is not supported for ${providerName}. Use route53 or record a marketplace/manual purchase with --price.`);
43064
+ process.exit(1);
43065
+ }
43066
+ const avail = await provider.checkAvailability(name);
43067
+ if (!avail.available) {
43068
+ console.error(`\u2717 ${name} is not available`);
43069
+ process.exit(1);
43070
+ }
43071
+ console.log(`\u2713 Available via ${providerName}`);
43072
+ let contact;
43073
+ try {
43074
+ contact = resolveContact(opts);
43075
+ } catch (e3) {
43076
+ console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
43077
+ process.exit(1);
43078
+ }
43079
+ console.log(`Registering ${name} via ${providerName}...`);
43080
+ const reg = await provider.registerDomain(name, contact, {
43081
+ years: parseInt(opts.years, 10),
43082
+ premiumPrice: avail.premium_price,
43083
+ autoRenew: opts.autoRenew ? opts.autoRenew === "true" : true
43084
+ });
43085
+ if (!reg.success) {
43086
+ console.error(`\u2717 Registration failed via ${providerName}`);
43087
+ process.exit(1);
43088
+ }
43089
+ const existing = getDomainByName(name);
43090
+ const dbInput = {
43091
+ registrar: providerName,
43092
+ status: "active",
43093
+ auto_renew: opts.autoRenew ? opts.autoRenew === "true" : true,
43094
+ purchase_price: reg.chargedAmount ? Number(reg.chargedAmount) : avail.standard_price,
43095
+ purchase_date: new Date().toISOString(),
43096
+ standard_price: avail.standard_price,
43097
+ is_premium: avail.is_premium,
43098
+ premium_price: avail.premium_price
43099
+ };
43100
+ if (existing)
43101
+ updateDomain(existing.id, dbInput);
43102
+ else
43103
+ createDomain({ name, ...dbInput });
43104
+ console.log(`\u2713 Registered and added to portfolio`);
43105
+ if (reg.orderId)
43106
+ console.log(` Order: ${reg.orderId}`);
43107
+ const dnsProvider = opts.dns ?? "cloudflare";
43108
+ if (opts.delegate !== false && dnsProvider === "cloudflare") {
43109
+ if (!provider.updateNameservers) {
43110
+ console.log(` DNS: ${providerName} registration succeeded, but nameserver updates are not implemented for this provider.`);
43111
+ } else {
43112
+ const zone = await ensureZone(name);
43113
+ const nsUpdate = await provider.updateNameservers(name, zone.nameservers);
43114
+ const existing2 = getDomainByName(name);
43115
+ if (existing2)
43116
+ updateDomain(existing2.id, { nameservers: zone.nameservers });
43117
+ console.log(`\u2713 Cloudflare zone ${zone.id}; nameservers updated${nsUpdate.operationId ? ` (op ${nsUpdate.operationId})` : ""}`);
43118
+ }
43119
+ }
43120
+ return;
43121
+ } catch (e3) {
43122
+ console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
43123
+ process.exit(1);
43124
+ }
41657
43125
  }
41658
43126
  try {
41659
43127
  const avail = await checkAvailability3(name);
@@ -41671,7 +43139,7 @@ WHOIS for ${result.domain} [${result.source}]:`);
41671
43139
  process.exit(1);
41672
43140
  }
41673
43141
  console.log(`Registering ${name}...`);
41674
- const reg = await registerDomain(name, contact, parseInt(opts.years));
43142
+ const reg = await registerDomain2(name, contact, parseInt(opts.years));
41675
43143
  console.log(`\u2713 Submitted (operation: ${reg.operationId})`);
41676
43144
  if (opts.wait) {
41677
43145
  let status = "IN_PROGRESS";
@@ -41719,10 +43187,10 @@ WHOIS for ${result.domain} [${result.source}]:`);
41719
43187
  console.log(`Delegating DNS to Cloudflare...`);
41720
43188
  const del = await delegateDomainToCloudflare(name, {
41721
43189
  createCloudflareZone: async (d3) => {
41722
- const z2 = await createZone(d3);
43190
+ const z2 = await ensureZone(d3);
41723
43191
  return { id: z2.id, nameservers: z2.nameservers };
41724
43192
  },
41725
- updateNameservers: (d3, ns) => updateNameservers(d3, ns)
43193
+ updateNameservers: (d3, ns) => updateNameservers2(d3, ns)
41726
43194
  });
41727
43195
  const existing2 = getDomainByName(name);
41728
43196
  if (existing2)
@@ -41741,16 +43209,16 @@ WHOIS for ${result.domain} [${result.source}]:`);
41741
43209
  process.exit(1);
41742
43210
  }
41743
43211
  });
41744
- 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) => {
43212
+ 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 cloudflare)").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) => {
41745
43213
  const cfg = loadConfig3();
41746
43214
  const registrarName = opts.registrar ?? cfg.default_registrar ?? "route53";
41747
- const dnsName = opts.dns ?? cfg.default_dns ?? registrarName;
43215
+ const dnsName = opts.dns ?? cfg.default_dns ?? "cloudflare";
41748
43216
  console.log(`
41749
43217
  Setting up ${name}`);
41750
43218
  console.log(` Registrar: ${registrarName} | DNS: ${dnsName}
41751
43219
  `);
41752
43220
  try {
41753
- process.stdout.write("[1/4] Checking availability... ");
43221
+ process.stdout.write(opts.wait ? "[1/5] Checking availability... " : "[1/4] Checking availability... ");
41754
43222
  const avail = await checkAvailability3(name);
41755
43223
  if (!avail.available) {
41756
43224
  console.log("not available");
@@ -41770,8 +43238,8 @@ Setting up ${name}`);
41770
43238
  console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
41771
43239
  process.exit(1);
41772
43240
  }
41773
- process.stdout.write("[2/4] Registering domain... ");
41774
- const reg = await registerDomain(name, contact, parseInt(opts.years));
43241
+ process.stdout.write(opts.wait ? "[2/5] Registering domain... " : "[2/4] Registering domain... ");
43242
+ const reg = await registerDomain2(name, contact, parseInt(opts.years));
41775
43243
  console.log(`submitted (${reg.operationId})`);
41776
43244
  if (opts.wait) {
41777
43245
  let status = "IN_PROGRESS";
@@ -41788,19 +43256,38 @@ Setting up ${name}`);
41788
43256
  }
41789
43257
  console.log(" Registration confirmed");
41790
43258
  }
41791
- process.stdout.write("[3/4] Creating DNS zone... ");
43259
+ process.stdout.write(opts.wait ? "[3/5] Creating DNS zone... " : "[3/4] Creating DNS zone... ");
41792
43260
  let nameservers = [];
41793
43261
  if (dnsName === "cloudflare") {
41794
- const zone = await createZone(name);
43262
+ const zone = await ensureZone(name);
41795
43263
  nameservers = zone.nameservers ?? [];
41796
- console.log(`created (${zone.id})`);
43264
+ console.log("ready (" + zone.id + ")");
41797
43265
  } else {
41798
43266
  const zone = await createHostedZone(name, `Managed by @hasna/domains`);
41799
43267
  nameservers = zone.name_servers ?? [];
41800
43268
  console.log(`created (${zone.id})`);
41801
43269
  }
41802
- process.stdout.write("[4/4] Adding to portfolio... ");
41803
- createDomain({ name, registrar: `AWS Route 53`, status: "active", auto_renew: true, nameservers });
43270
+ if (nameservers.length > 0) {
43271
+ if (opts.wait) {
43272
+ process.stdout.write("[4/5] Updating registrar nameservers... ");
43273
+ if (registrarName !== "route53") {
43274
+ console.log("skipped");
43275
+ console.error("Only Route53 nameserver delegation is currently implemented for setup.");
43276
+ process.exit(1);
43277
+ }
43278
+ const nsUpdate = await updateNameservers2(name, nameservers);
43279
+ console.log("submitted (" + nsUpdate.operationId + ")");
43280
+ } else {
43281
+ console.log(" Nameserver delegation skipped until registration completes; re-run with --wait or use domains r53 domain-info/status first.");
43282
+ }
43283
+ }
43284
+ process.stdout.write(opts.wait ? "[5/5] Adding to portfolio... " : "[4/4] Adding to portfolio... ");
43285
+ const existing = getDomainByName(name);
43286
+ const dbInput = { registrar: `AWS Route 53`, status: "active", auto_renew: true, nameservers };
43287
+ if (existing)
43288
+ updateDomain(existing.id, dbInput);
43289
+ else
43290
+ createDomain({ name, ...dbInput });
41804
43291
  console.log("done");
41805
43292
  console.log(`
41806
43293
  \u2713 Setup complete for ${name}`);
@@ -42294,6 +43781,10 @@ function registerMonitorCommand(program2) {
42294
43781
  }
42295
43782
 
42296
43783
  // src/cli/commands/provider.ts
43784
+ function isAwsAccessDenied(error) {
43785
+ const msg = error instanceof Error ? error.message : String(error);
43786
+ return msg.includes("AccessDenied") || msg.includes("route53domains:ListDomains");
43787
+ }
42297
43788
  function registerProviderCommand(program2) {
42298
43789
  const provider = program2.command("provider").description("Configure and test registrar/DNS providers");
42299
43790
  provider.command("list").description("Show all providers and their configuration status").option("-j, --json", "Output JSON").action((opts) => {
@@ -42307,7 +43798,8 @@ Registrar / DNS Providers:`);
42307
43798
  for (const p3 of providers) {
42308
43799
  const status = p3.configured ? "\u2713 configured" : "\u2717 not configured";
42309
43800
  const type = p3.type === "full" ? "registrar + dns" : p3.type;
42310
- console.log(` ${p3.name.padEnd(12)} [${type}] ${status}`);
43801
+ const capabilities = [type, p3.inventory ? "inventory" : null].filter(Boolean).join(", ");
43802
+ console.log(` ${p3.name.padEnd(12)} [${capabilities}] ${status}`);
42311
43803
  if (!p3.configured) {
42312
43804
  console.log(` Missing: ${p3.envVars.join(", ")}`);
42313
43805
  }
@@ -42315,22 +43807,23 @@ Registrar / DNS Providers:`);
42315
43807
  console.log();
42316
43808
  });
42317
43809
  provider.command("test <name>").description("Live-test a provider's credentials").option("-j, --json", "Output JSON").action(async (name, opts) => {
43810
+ const providerName = name.toLowerCase();
42318
43811
  const providers = getAvailableProviders();
42319
- const info = providers.find((p3) => p3.name === name);
43812
+ const info = providers.find((p3) => p3.name === providerName);
42320
43813
  if (!info) {
42321
43814
  const error = `Unknown provider: ${name}`;
42322
43815
  if (opts.json) {
42323
- console.log(JSON.stringify({ provider: name, ok: false, error }, null, 2));
43816
+ console.log(JSON.stringify({ provider: providerName, ok: false, error }, null, 2));
42324
43817
  } else {
42325
43818
  console.error(error);
42326
43819
  }
42327
43820
  process.exit(1);
42328
43821
  }
42329
43822
  if (!info.configured) {
42330
- const error = `${name} is not configured. Set: ${info.envVars.join(", ")}`;
43823
+ const error = `${providerName} is not configured. Set: ${info.envVars.join(", ")}`;
42331
43824
  if (opts.json) {
42332
43825
  console.log(JSON.stringify({
42333
- provider: name,
43826
+ provider: providerName,
42334
43827
  ok: false,
42335
43828
  configured: false,
42336
43829
  required_env: info.envVars,
@@ -42342,33 +43835,70 @@ Registrar / DNS Providers:`);
42342
43835
  process.exit(1);
42343
43836
  }
42344
43837
  if (!opts.json) {
42345
- console.log(`Testing ${name}...`);
43838
+ console.log(`Testing ${providerName}...`);
42346
43839
  }
42347
43840
  let registrarOk = null;
42348
43841
  let dnsOk = null;
43842
+ let marketplaceOk = null;
43843
+ const notes = [];
42349
43844
  try {
42350
43845
  if (info.type === "registrar" || info.type === "full") {
42351
- const reg = getRegistrarProvider(name);
42352
- await reg.listDomains();
42353
- registrarOk = true;
42354
- if (!opts.json)
42355
- console.log("\u2713 Registrar connection OK");
43846
+ if (providerName === "route53") {
43847
+ try {
43848
+ const { listRegisteredDomains: listRegisteredDomains2 } = await Promise.resolve().then(() => (init_route53(), exports_route53));
43849
+ await listRegisteredDomains2();
43850
+ registrarOk = true;
43851
+ if (!opts.json)
43852
+ console.log("\u2713 Registrar connection OK");
43853
+ } catch (error) {
43854
+ if (!isAwsAccessDenied(error))
43855
+ throw error;
43856
+ registrarOk = false;
43857
+ notes.push("route53domains-listdomains-access-denied");
43858
+ if (!opts.json)
43859
+ console.log("\u2022 Route53 Domains registrar API is not available for these AWS credentials");
43860
+ }
43861
+ } else {
43862
+ const reg = getRegistrarProvider(providerName);
43863
+ await reg.listDomains();
43864
+ registrarOk = true;
43865
+ if (!opts.json)
43866
+ console.log("\u2713 Registrar connection OK");
43867
+ }
42356
43868
  }
42357
43869
  if (info.type === "dns" || info.type === "full") {
42358
- const dns = getDnsProvider(name);
42359
- await dns.getDnsRecords("__test_nonexistent_domain__.invalid");
42360
- dnsOk = true;
43870
+ if (providerName === "brandsight" && registrarOk) {
43871
+ dnsOk = null;
43872
+ notes.push("dns-not-probed-with-synthetic-invalid-domain");
43873
+ if (!opts.json)
43874
+ console.log("\u2022 DNS not probed with synthetic invalid domain");
43875
+ } else {
43876
+ const dns = getDnsProvider(providerName);
43877
+ await dns.getDnsRecords("__test_nonexistent_domain__.invalid");
43878
+ dnsOk = true;
43879
+ if (!opts.json)
43880
+ console.log("\u2713 DNS connection OK");
43881
+ }
43882
+ }
43883
+ if (info.type === "marketplace") {
43884
+ if (providerName !== "sedo")
43885
+ throw new Error("No marketplace tester for provider: " + providerName);
43886
+ const { listSedoPortfolio: listSedoPortfolio2 } = await Promise.resolve().then(() => (init_sedo(), exports_sedo));
43887
+ await listSedoPortfolio2({ limit: 1 });
43888
+ marketplaceOk = true;
42361
43889
  if (!opts.json)
42362
- console.log("\u2713 DNS connection OK");
43890
+ console.log("\u2713 Marketplace connection OK");
42363
43891
  }
42364
43892
  if (opts.json) {
42365
43893
  console.log(JSON.stringify({
42366
- provider: name,
43894
+ provider: providerName,
42367
43895
  ok: true,
42368
43896
  configured: true,
42369
43897
  type: info.type,
42370
43898
  registrar_ok: registrarOk,
42371
- dns_ok: dnsOk
43899
+ dns_ok: dnsOk,
43900
+ marketplace_ok: marketplaceOk,
43901
+ ...notes.length > 0 ? { notes } : {}
42372
43902
  }, null, 2));
42373
43903
  }
42374
43904
  } catch (e3) {
@@ -42376,13 +43906,15 @@ Registrar / DNS Providers:`);
42376
43906
  if (msg.includes("not found") || msg.includes("No hosted zone") || msg.includes("NXDOMAIN")) {
42377
43907
  if (opts.json) {
42378
43908
  console.log(JSON.stringify({
42379
- provider: name,
43909
+ provider: providerName,
42380
43910
  ok: true,
42381
43911
  configured: true,
42382
43912
  type: info.type,
42383
43913
  registrar_ok: registrarOk,
42384
43914
  dns_ok: true,
42385
- note: "connection-ok-no-domains-yet"
43915
+ marketplace_ok: marketplaceOk,
43916
+ note: "connection-ok-no-domains-yet",
43917
+ ...notes.length > 0 ? { notes } : {}
42386
43918
  }, null, 2));
42387
43919
  } else {
42388
43920
  console.log("\u2713 Connection OK (no domains yet)");
@@ -42391,16 +43923,17 @@ Registrar / DNS Providers:`);
42391
43923
  }
42392
43924
  if (opts.json) {
42393
43925
  console.log(JSON.stringify({
42394
- provider: name,
43926
+ provider: providerName,
42395
43927
  ok: false,
42396
43928
  configured: true,
42397
43929
  type: info.type,
42398
43930
  registrar_ok: registrarOk,
42399
43931
  dns_ok: dnsOk,
43932
+ marketplace_ok: marketplaceOk,
42400
43933
  error: msg
42401
43934
  }, null, 2));
42402
43935
  } else {
42403
- console.error(`\u2717 ${name} test failed: ${msg}`);
43936
+ console.error(`\u2717 ${providerName} test failed: ${msg}`);
42404
43937
  }
42405
43938
  process.exit(1);
42406
43939
  }
@@ -42410,23 +43943,48 @@ Registrar / DNS Providers:`);
42410
43943
  // src/cli/commands/providers.ts
42411
43944
  init_config();
42412
43945
  init_domains();
43946
+ function registrarProviderNames() {
43947
+ return getAvailableProviders().filter((p3) => providerHasRegistrar(p3.name)).map((p3) => p3.name).join(", ");
43948
+ }
43949
+ function inventoryProviderNames() {
43950
+ return getAvailableProviders().filter((p3) => providerHasInventory(p3.name)).map((p3) => p3.name).join(", ");
43951
+ }
43952
+ function requireInventoryProvider(name) {
43953
+ const info = getProviderInfo(name);
43954
+ if (!info) {
43955
+ throw new Error(`Unknown provider: ${name}. Supported domain inventory providers: ${inventoryProviderNames()}`);
43956
+ }
43957
+ if (!providerHasInventory(name)) {
43958
+ throw new Error(`${name} is a ${info.type} provider without domain inventory sync. Supported domain inventory providers: ${inventoryProviderNames()}`);
43959
+ }
43960
+ }
43961
+ function requireRegistrarProvider(name) {
43962
+ const info = getProviderInfo(name);
43963
+ if (!info) {
43964
+ throw new Error(`Unknown provider: ${name}. Supported registrars: ${registrarProviderNames()}`);
43965
+ }
43966
+ if (!providerHasRegistrar(name)) {
43967
+ throw new Error(`${name} is a ${info.type} provider, not a registrar. Supported registrars: ${registrarProviderNames()}`);
43968
+ }
43969
+ }
42413
43970
  function registerProviderCommands(program2) {
42414
- program2.command("providers").description("Show which registrar providers are configured").option("--json", "Output as JSON", false).action((opts) => {
43971
+ program2.command("providers").description("Show which providers are configured").option("--json", "Output as JSON", false).action((opts) => {
42415
43972
  const providers = getAvailableProviders();
42416
43973
  if (opts.json) {
42417
43974
  console.log(JSON.stringify(providers, null, 2));
42418
43975
  } else {
42419
- console.log("Registrar Providers:");
43976
+ console.log("Providers:");
42420
43977
  for (const p3 of providers) {
42421
43978
  const status = p3.configured ? "CONFIGURED" : "not configured";
42422
- console.log(` ${p3.name}: ${status}`);
43979
+ const capabilities = [p3.type, p3.inventory ? "inventory" : null].filter(Boolean).join(", ");
43980
+ console.log(` ${p3.name} [${capabilities}]: ${status}`);
42423
43981
  if (!p3.configured) {
42424
- console.log(` Missing: ${p3.envVars.join(", ")}`);
43982
+ console.log(` Accepted env: ${p3.envVars.join(", ")}`);
42425
43983
  }
42426
43984
  }
42427
43985
  }
42428
43986
  });
42429
- 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) => {
43987
+ program2.command("sync").description("Sync domains from a domain inventory provider to local DB").option("--provider <provider>", "Provider name").option("--all", "Sync from all configured domain inventory providers").option("--json", "Output as JSON", false).action(async (opts) => {
42430
43988
  if (opts.all) {
42431
43989
  try {
42432
43990
  const result = await syncAll({
@@ -42439,7 +43997,7 @@ function registerProviderCommands(program2) {
42439
43997
  } else {
42440
43998
  console.log(`Synced ${result.totalSynced} domain(s) from ${result.providers.length} provider(s)`);
42441
43999
  for (const p3 of result.providers) {
42442
- console.log(` ${p3.name}: ${p3.result.synced} synced`);
44000
+ console.log(` ${p3.name}: ${p3.result.synced} synced (${p3.result.created} new, ${p3.result.updated} updated)`);
42443
44001
  }
42444
44002
  if (result.totalErrors.length > 0) {
42445
44003
  console.log("Errors:");
@@ -42459,59 +44017,29 @@ function registerProviderCommands(program2) {
42459
44017
  console.error("Specify --provider <name> or --all");
42460
44018
  process.exit(1);
42461
44019
  }
42462
- if (provider === "namecheap") {
42463
- try {
42464
- const result = await syncToLocalDb({
42465
- getDomainByName,
42466
- createDomain,
42467
- updateDomain
42468
- });
42469
- if (opts.json) {
42470
- console.log(JSON.stringify(result, null, 2));
42471
- } else {
42472
- console.log(`Synced ${result.synced} domain(s) from Namecheap`);
42473
- for (const d3 of result.domains) {
42474
- console.log(` ${d3}`);
42475
- }
42476
- if (result.errors.length > 0) {
42477
- console.log("Errors:");
42478
- for (const e3 of result.errors) {
42479
- console.log(` - ${e3}`);
42480
- }
42481
- }
42482
- }
42483
- } catch (error) {
42484
- console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
42485
- process.exit(1);
42486
- }
42487
- } else if (provider === "godaddy") {
42488
- try {
42489
- const result = await syncToLocalDb2({
42490
- getDomainByName,
42491
- createDomain,
42492
- updateDomain
42493
- });
42494
- if (opts.json) {
42495
- console.log(JSON.stringify(result, null, 2));
42496
- } else {
42497
- console.log(`Synced ${result.synced} domain(s) from GoDaddy (created: ${result.created}, updated: ${result.updated})`);
42498
- if (result.errors.length > 0) {
42499
- console.log("Errors:");
42500
- for (const e3 of result.errors) {
42501
- console.log(` - ${e3}`);
42502
- }
42503
- }
44020
+ try {
44021
+ requireInventoryProvider(provider);
44022
+ const result = await getDomainInventoryProvider(provider).syncToLocalDb({
44023
+ getDomainByName,
44024
+ createDomain,
44025
+ updateDomain
44026
+ });
44027
+ if (opts.json) {
44028
+ console.log(JSON.stringify({ provider, ...result }, null, 2));
44029
+ } else {
44030
+ console.log(`Synced ${result.synced} domain(s) from ${provider} (${result.created} new, ${result.updated} updated)`);
44031
+ if (result.errors.length > 0) {
44032
+ console.log("Errors:");
44033
+ for (const e3 of result.errors)
44034
+ console.log(` - ${e3}`);
42504
44035
  }
42505
- } catch (error) {
42506
- console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
42507
- process.exit(1);
42508
44036
  }
42509
- } else {
42510
- console.error(`Unsupported provider: ${provider}. Supported: namecheap, godaddy`);
44037
+ } catch (error) {
44038
+ console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
42511
44039
  process.exit(1);
42512
44040
  }
42513
44041
  });
42514
- 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) => {
44042
+ 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").option("--years <n>", "Number of years to renew", "1").option("--json", "Output as JSON", false).action(async (name, opts) => {
42515
44043
  let provider = (opts.provider || "").toLowerCase();
42516
44044
  if (!provider) {
42517
44045
  const detected = autoDetectRegistrar(name, getDomainByName);
@@ -42520,53 +44048,44 @@ function registerProviderCommands(program2) {
42520
44048
  process.exit(1);
42521
44049
  }
42522
44050
  provider = detected;
42523
- console.log(`Auto-detected registrar: ${provider}`);
44051
+ if (!opts.json)
44052
+ console.log(`Auto-detected registrar: ${provider}`);
42524
44053
  }
42525
- if (provider === "namecheap") {
42526
- try {
42527
- const result = await renewDomain(name, parseInt(opts.years));
42528
- if (opts.json) {
42529
- console.log(JSON.stringify(result, null, 2));
42530
- } else {
42531
- console.log(`Renewed ${result.domain} successfully via Namecheap`);
42532
- if (result.chargedAmount)
42533
- console.log(` Charged: $${result.chargedAmount}`);
42534
- if (result.orderId)
42535
- console.log(` Order ID: ${result.orderId}`);
42536
- }
42537
- } catch (error) {
42538
- console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
42539
- process.exit(1);
44054
+ try {
44055
+ requireRegistrarProvider(provider);
44056
+ const result = await getRegistrarProvider(provider).renewDomain(name, parseInt(opts.years, 10));
44057
+ if (!result.success) {
44058
+ throw new Error(`Renewal failed or is not supported for ${provider}`);
42540
44059
  }
42541
- } else if (provider === "godaddy") {
42542
- try {
42543
- const result = await renewDomain2(name);
42544
- if (opts.json) {
42545
- console.log(JSON.stringify(result, null, 2));
42546
- } else {
42547
- console.log(`Renewed ${name} successfully via GoDaddy`);
42548
- if (result.orderId)
42549
- console.log(` Order ID: ${result.orderId}`);
42550
- if (result.total)
42551
- console.log(` Total: $${(result.total / 100).toFixed(2)}`);
42552
- }
42553
- } catch (error) {
42554
- console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
42555
- process.exit(1);
44060
+ if (opts.json) {
44061
+ console.log(JSON.stringify({ provider, ...result }, null, 2));
44062
+ } else {
44063
+ console.log(`Renewed ${result.domain} successfully via ${provider}`);
44064
+ if (result.chargedAmount)
44065
+ console.log(` Charged: $${result.chargedAmount}`);
44066
+ if (result.orderId)
44067
+ console.log(` Order ID: ${result.orderId}`);
42556
44068
  }
42557
- } else {
42558
- console.error(`Unsupported provider: ${provider}. Supported: namecheap, godaddy`);
44069
+ } catch (error) {
44070
+ console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
42559
44071
  process.exit(1);
42560
44072
  }
42561
44073
  });
42562
- program2.command("check").description("Check domain availability via a registrar provider (route53, namecheap, godaddy)").argument("<name>", "Domain name (e.g. example.com)").option("--provider <name>", "Registrar provider \u2014 defaults to config default-registrar, else namecheap").option("--json", "Output as JSON", false).action(async (name, opts) => {
44074
+ program2.command("check").description("Check domain availability via a registrar provider").argument("<name>", "Domain name (e.g. example.com)").option("--provider <name>", "Registrar provider \u2014 defaults to config default-registrar, else route53").option("--json", "Output as JSON", false).action(async (name, opts) => {
42563
44075
  try {
42564
- const providerName = opts.provider ?? loadConfig3().default_registrar ?? "namecheap";
42565
- const result = providerName === "namecheap" ? await checkAvailability(name) : await getRegistrarProvider(providerName).checkAvailability(name);
44076
+ const providerName = (opts.provider ?? loadConfig3().default_registrar ?? "route53").toLowerCase();
44077
+ requireRegistrarProvider(providerName);
44078
+ const result = await getRegistrarProvider(providerName).checkAvailability(name);
42566
44079
  if (opts.json) {
42567
44080
  console.log(JSON.stringify({ provider: providerName, ...result }, null, 2));
42568
44081
  } else {
42569
44082
  console.log(`${result.domain} is ${result.available ? "AVAILABLE" : "NOT available"} (via ${providerName})`);
44083
+ if (result.is_premium)
44084
+ console.log(` Premium ask: ${result.premium_price ?? "unknown"}`);
44085
+ if (result.standard_price !== undefined) {
44086
+ const currency = result.currency ? ` ${result.currency}` : "";
44087
+ console.log(` Standard price: ${result.standard_price}${currency}`);
44088
+ }
42570
44089
  }
42571
44090
  } catch (error) {
42572
44091
  console.error(`Availability check failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -42580,6 +44099,7 @@ init_config();
42580
44099
  var VALID_KEYS = [
42581
44100
  "default-registrar",
42582
44101
  "default-dns",
44102
+ "purchase-aws-profile",
42583
44103
  "contact.first-name",
42584
44104
  "contact.last-name",
42585
44105
  "contact.email",
@@ -42601,8 +44121,9 @@ function registerConfigCommands(program2) {
42601
44121
  }
42602
44122
  console.log(`
42603
44123
  Configuration (~/.hasna/domains/config.json):`);
42604
- console.log(` default-registrar: ${cfg.default_registrar ?? "(not set)"}`);
42605
- console.log(` default-dns: ${cfg.default_dns ?? "(not set)"}`);
44124
+ console.log(` default-registrar: ${cfg.default_registrar ?? "(not set)"}`);
44125
+ console.log(` default-dns: ${cfg.default_dns ?? "(not set)"}`);
44126
+ console.log(` purchase-aws-profile: ${cfg.purchase_aws_profile ?? "(not set)"}`);
42606
44127
  if (cfg.contact && Object.keys(cfg.contact).length > 0) {
42607
44128
  console.log(`
42608
44129
  contact:`);
@@ -42703,7 +44224,7 @@ function registerDoctorCommand(program2) {
42703
44224
  fail(`${c3.provider}: not configured (${c3.detail})`);
42704
44225
  }
42705
44226
  section("Providers");
42706
- const providers = getAvailableProviders().filter((p3) => p3.name !== "brandsight");
44227
+ const providers = getAvailableProviders().filter((p3) => p3.type !== "marketplace");
42707
44228
  for (const p3 of providers) {
42708
44229
  if (!p3.configured) {
42709
44230
  fail(`${p3.name}: not configured`, `Set: ${p3.envVars.join(", ")}`);
@@ -42753,15 +44274,15 @@ ${"\u2500".repeat(45)}`);
42753
44274
  }
42754
44275
 
42755
44276
  // src/cli/commands/mcp-install.ts
42756
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
42757
- import { homedir as homedir4 } from "os";
42758
- import { dirname as dirname4, join as join4 } from "path";
44277
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
44278
+ import { homedir as homedir5 } from "os";
44279
+ import { dirname as dirname4, join as join5 } from "path";
42759
44280
  import { execSync as execSync3 } from "child_process";
42760
44281
  var MCP_SERVER_NAME = "domains";
42761
44282
  function getClaudeConfigPaths() {
42762
44283
  return {
42763
- global: join4(homedir4(), ".claude", "claude_desktop_config.json"),
42764
- project: join4(process.cwd(), ".claude", "settings.json")
44284
+ global: join5(homedir5(), ".claude", "claude_desktop_config.json"),
44285
+ project: join5(process.cwd(), ".claude", "settings.json")
42765
44286
  };
42766
44287
  }
42767
44288
  function getMcpBinaryPath() {
@@ -42773,12 +44294,12 @@ function getMcpBinaryPath() {
42773
44294
  }
42774
44295
  function ensureConfigDir(configPath2) {
42775
44296
  const dir = dirname4(configPath2);
42776
- if (!existsSync3(dir)) {
44297
+ if (!existsSync4(dir)) {
42777
44298
  mkdirSync3(dir, { recursive: true });
42778
44299
  }
42779
44300
  }
42780
44301
  function readConfig(configPath2) {
42781
- if (!existsSync3(configPath2))
44302
+ if (!existsSync4(configPath2))
42782
44303
  return {};
42783
44304
  try {
42784
44305
  return JSON.parse(readFileSync5(configPath2, "utf-8"));
@@ -42807,7 +44328,7 @@ function registerMcpCommand(program2) {
42807
44328
  mcp.command("uninstall").description("Remove domains MCP server from Claude Code config").option("--project", "Remove from project config instead of global").action((opts) => {
42808
44329
  const paths = getClaudeConfigPaths();
42809
44330
  const configPath2 = opts.project ? paths.project : paths.global;
42810
- if (!existsSync3(configPath2)) {
44331
+ if (!existsSync4(configPath2)) {
42811
44332
  console.log("Config file not found \u2014 nothing to remove.");
42812
44333
  return;
42813
44334
  }
@@ -42825,7 +44346,7 @@ function registerMcpCommand(program2) {
42825
44346
  const paths = getClaudeConfigPaths();
42826
44347
  const status = [];
42827
44348
  for (const [scope, configPath2] of [["global", paths.global], ["project", paths.project]]) {
42828
- if (!existsSync3(configPath2)) {
44349
+ if (!existsSync4(configPath2)) {
42829
44350
  status.push({ scope, config_path: configPath2, exists: false, registered: false });
42830
44351
  continue;
42831
44352
  }
@@ -43049,7 +44570,7 @@ function registerRoute53Commands(program2) {
43049
44570
  process.exit(1);
43050
44571
  }
43051
44572
  console.log(`Registering ${domain}...`);
43052
- const result = await registerDomain(domain, contact, parseInt(opts.years), opts.autoRenew);
44573
+ const result = await registerDomain2(domain, contact, parseInt(opts.years), opts.autoRenew);
43053
44574
  console.log(`\u2713 Registration submitted: ${domain}`);
43054
44575
  console.log(` Operation ID: ${result.operationId}`);
43055
44576
  console.log(` Check status: domains r53 status ${result.operationId}`);
@@ -43346,7 +44867,7 @@ DNS Records for ${domain}:`);
43346
44867
  console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
43347
44868
  process.exit(1);
43348
44869
  }
43349
- const reg = await registerDomain(domain, contact, parseInt(opts.years));
44870
+ const reg = await registerDomain2(domain, contact, parseInt(opts.years));
43350
44871
  console.log(` \u2713 Submitted (operation: ${reg.operationId})`);
43351
44872
  console.log(` Waiting for registration to complete...`);
43352
44873
  let status = "IN_PROGRESS";
@@ -43377,7 +44898,7 @@ DNS Records for ${domain}:`);
43377
44898
  },
43378
44899
  getRegistrarNs: async (d3) => (await getDomainDetail(d3)).nameservers,
43379
44900
  setRegistrarNs: async (d3, ns) => {
43380
- await updateNameservers(d3, ns);
44901
+ await updateNameservers2(d3, ns);
43381
44902
  }
43382
44903
  });
43383
44904
  console.log(` \u2713 Zone ${setup.created ? "created" : "reused"} (${setup.zoneId})` + (setup.nsUpdated ? ` \u2014 registry NS repointed to this zone` : ``));
@@ -45554,10 +47075,10 @@ function registerInteractiveCommand(program2) {
45554
47075
  });
45555
47076
  }
45556
47077
 
45557
- // src/cli/commands/cloud.ts
47078
+ // src/cli/commands/storage.ts
45558
47079
  import chalk2 from "chalk";
45559
47080
 
45560
- // src/db/cloud-sync.ts
47081
+ // src/db/storage-sync.ts
45561
47082
  init_database();
45562
47083
 
45563
47084
  // src/db/pg-migrations.ts
@@ -45734,8 +47255,8 @@ class PgAdapterAsync {
45734
47255
  }
45735
47256
  }
45736
47257
 
45737
- // src/db/cloud-sync.ts
45738
- var CLOUD_TABLES = [
47258
+ // src/db/storage-sync.ts
47259
+ var STORAGE_TABLES = [
45739
47260
  "domains",
45740
47261
  "dns_records",
45741
47262
  "alerts",
@@ -45755,25 +47276,50 @@ var PRIMARY_KEYS = {
45755
47276
  domain_history: ["id"],
45756
47277
  domain_reputation: ["id"]
45757
47278
  };
45758
- function getCloudDatabaseUrl() {
45759
- return process.env["HASNA_DOMAINS_CLOUD_DATABASE_URL"] ?? process.env["OPEN_DOMAINS_CLOUD_DATABASE_URL"] ?? process.env["DOMAINS_CLOUD_DATABASE_URL"] ?? null;
47279
+ var DOMAINS_STORAGE_ENV = "HASNA_DOMAINS_DATABASE_URL";
47280
+ var DOMAINS_STORAGE_FALLBACK_ENV = "DOMAINS_DATABASE_URL";
47281
+ var DOMAINS_STORAGE_MODE_ENV = "HASNA_DOMAINS_STORAGE_MODE";
47282
+ var DOMAINS_STORAGE_MODE_FALLBACK_ENV = "DOMAINS_STORAGE_MODE";
47283
+ var STORAGE_DATABASE_ENV = [DOMAINS_STORAGE_ENV, DOMAINS_STORAGE_FALLBACK_ENV];
47284
+ var STORAGE_MODE_ENV = [DOMAINS_STORAGE_MODE_ENV, DOMAINS_STORAGE_MODE_FALLBACK_ENV];
47285
+ function firstEnv2(names) {
47286
+ for (const name of names) {
47287
+ const value = process.env[name];
47288
+ if (value)
47289
+ return value;
47290
+ }
47291
+ return null;
47292
+ }
47293
+ function normalizeStorageMode(value) {
47294
+ if (!value)
47295
+ return null;
47296
+ const normalized = value.trim().toLowerCase();
47297
+ if (normalized === "local" || normalized === "remote" || normalized === "hybrid")
47298
+ return normalized;
47299
+ return null;
45760
47300
  }
45761
- async function getCloudPg() {
45762
- const url = getCloudDatabaseUrl();
47301
+ function getStorageDatabaseUrl() {
47302
+ return firstEnv2(STORAGE_DATABASE_ENV);
47303
+ }
47304
+ function getStorageMode() {
47305
+ return normalizeStorageMode(firstEnv2(STORAGE_MODE_ENV)) ?? (getStorageDatabaseUrl() ? "remote" : "local");
47306
+ }
47307
+ async function getStoragePg() {
47308
+ const url = getStorageDatabaseUrl();
45763
47309
  if (!url) {
45764
- throw new Error("Missing HASNA_DOMAINS_CLOUD_DATABASE_URL, OPEN_DOMAINS_CLOUD_DATABASE_URL, or DOMAINS_CLOUD_DATABASE_URL");
47310
+ throw new Error("Missing HASNA_DOMAINS_DATABASE_URL or DOMAINS_DATABASE_URL");
45765
47311
  }
45766
47312
  return new PgAdapterAsync(url);
45767
47313
  }
45768
- async function runCloudMigrations(remote) {
47314
+ async function runStorageMigrations(remote) {
45769
47315
  for (const sql of PG_MIGRATIONS)
45770
47316
  await remote.run(sql);
45771
47317
  }
45772
- async function cloudPush(options) {
45773
- const remote = await getCloudPg();
47318
+ async function storagePush(options) {
47319
+ const remote = await getStoragePg();
45774
47320
  const db = getDatabase();
45775
47321
  try {
45776
- await runCloudMigrations(remote);
47322
+ await runStorageMigrations(remote);
45777
47323
  const results = [];
45778
47324
  for (const table of resolveTables(options?.tables))
45779
47325
  results.push(await pushTable(db, remote, table));
@@ -45783,11 +47329,11 @@ async function cloudPush(options) {
45783
47329
  await remote.close();
45784
47330
  }
45785
47331
  }
45786
- async function cloudPull(options) {
45787
- const remote = await getCloudPg();
47332
+ async function storagePull(options) {
47333
+ const remote = await getStoragePg();
45788
47334
  const db = getDatabase();
45789
47335
  try {
45790
- await runCloudMigrations(remote);
47336
+ await runStorageMigrations(remote);
45791
47337
  const results = [];
45792
47338
  for (const table of resolveTables(options?.tables))
45793
47339
  results.push(await pullTable(remote, db, table));
@@ -45797,20 +47343,30 @@ async function cloudPull(options) {
45797
47343
  await remote.close();
45798
47344
  }
45799
47345
  }
45800
- async function cloudSync(options) {
45801
- const pull = await cloudPull(options);
45802
- const push = await cloudPush(options);
47346
+ async function storageSync(options) {
47347
+ const pull = await storagePull(options);
47348
+ const push = await storagePush(options);
45803
47349
  return { pull, push };
45804
47350
  }
45805
- function getSyncMetaAll() {
47351
+ function getStorageSyncMetaAll() {
45806
47352
  const db = getDatabase();
45807
47353
  ensureSyncMetaTable(db);
45808
47354
  return db.query("SELECT table_name, last_synced_at, direction FROM _domains_sync_meta ORDER BY table_name, direction").all();
45809
47355
  }
47356
+ function getStorageStatus() {
47357
+ return {
47358
+ configured: Boolean(getStorageDatabaseUrl()),
47359
+ env: STORAGE_DATABASE_ENV,
47360
+ mode: getStorageMode(),
47361
+ service: "domains",
47362
+ tables: STORAGE_TABLES,
47363
+ sync: getStorageSyncMetaAll()
47364
+ };
47365
+ }
45810
47366
  function resolveTables(tables) {
45811
47367
  if (!tables || tables.length === 0)
45812
- return [...CLOUD_TABLES];
45813
- const allowed = new Set(CLOUD_TABLES);
47368
+ return [...STORAGE_TABLES];
47369
+ const allowed = new Set(STORAGE_TABLES);
45814
47370
  const requested = tables.map((table) => table.trim()).filter(Boolean);
45815
47371
  const invalid = requested.filter((table) => !allowed.has(table));
45816
47372
  if (invalid.length > 0)
@@ -45946,12 +47502,7 @@ function coerceForSqlite(value) {
45946
47502
  return String(value);
45947
47503
  }
45948
47504
 
45949
- // src/cli/commands/cloud.ts
45950
- var CLOUD_ENV = [
45951
- "HASNA_DOMAINS_CLOUD_DATABASE_URL",
45952
- "OPEN_DOMAINS_CLOUD_DATABASE_URL",
45953
- "DOMAINS_CLOUD_DATABASE_URL"
45954
- ];
47505
+ // src/cli/commands/storage.ts
45955
47506
  function parseTables(value) {
45956
47507
  if (!value)
45957
47508
  return;
@@ -45968,21 +47519,15 @@ function printResults(results, label) {
45968
47519
  }
45969
47520
  console.log(`Done. ${total} rows ${label}.`);
45970
47521
  }
45971
- function registerCloudCommand(program2) {
45972
- const cloud = program2.command("cloud").description("Cloud sync commands");
45973
- cloud.command("status").description("Show cloud config and local sync state").option("--json", "Output as JSON").action((opts) => {
45974
- const info = {
45975
- configured: Boolean(getCloudDatabaseUrl()),
45976
- env: CLOUD_ENV,
45977
- service: "domains",
45978
- tables: CLOUD_TABLES,
45979
- sync: getSyncMetaAll()
45980
- };
47522
+ function installStorageSubcommands(storage) {
47523
+ storage.command("status").description("Show remote storage config and local sync state").option("--json", "Output as JSON").action((opts) => {
47524
+ const info = getStorageStatus();
45981
47525
  if (opts.json) {
45982
47526
  printJson(info);
45983
47527
  return;
45984
47528
  }
45985
- console.log(`Cloud configured: ${info.configured ? "yes" : "no"}`);
47529
+ console.log(`Storage mode: ${info.mode}`);
47530
+ console.log(`Remote storage configured: ${info.configured ? "yes" : "no"}`);
45986
47531
  console.log(`Tables: ${info.tables.join(", ")}`);
45987
47532
  if (info.sync.length === 0)
45988
47533
  console.log("Sync: no local sync history");
@@ -45990,9 +47535,9 @@ function registerCloudCommand(program2) {
45990
47535
  console.log(` ${entry.table_name} ${entry.direction}: ${entry.last_synced_at ?? "never"}`);
45991
47536
  }
45992
47537
  });
45993
- cloud.command("push").description("Push local domains data to cloud PostgreSQL").option("--tables <tables>", "Comma-separated table names").option("--json", "Output as JSON").action(async (opts) => {
47538
+ storage.command("push").description("Push local domains data to remote PostgreSQL storage").option("--tables <tables>", "Comma-separated table names").option("--json", "Output as JSON").action(async (opts) => {
45994
47539
  try {
45995
- const results = await cloudPush({ tables: parseTables(opts.tables) });
47540
+ const results = await storagePush({ tables: parseTables(opts.tables) });
45996
47541
  if (opts.json) {
45997
47542
  printJson(results);
45998
47543
  return;
@@ -46003,9 +47548,9 @@ function registerCloudCommand(program2) {
46003
47548
  process.exit(1);
46004
47549
  }
46005
47550
  });
46006
- cloud.command("pull").description("Pull domains data from cloud PostgreSQL to local SQLite").option("--tables <tables>", "Comma-separated table names").option("--json", "Output as JSON").action(async (opts) => {
47551
+ storage.command("pull").description("Pull domains data from remote PostgreSQL storage to local SQLite").option("--tables <tables>", "Comma-separated table names").option("--json", "Output as JSON").action(async (opts) => {
46007
47552
  try {
46008
- const results = await cloudPull({ tables: parseTables(opts.tables) });
47553
+ const results = await storagePull({ tables: parseTables(opts.tables) });
46009
47554
  if (opts.json) {
46010
47555
  printJson(results);
46011
47556
  return;
@@ -46016,9 +47561,9 @@ function registerCloudCommand(program2) {
46016
47561
  process.exit(1);
46017
47562
  }
46018
47563
  });
46019
- cloud.command("sync").description("Bidirectional sync: pull then push").option("--tables <tables>", "Comma-separated table names").option("--json", "Output as JSON").action(async (opts) => {
47564
+ storage.command("sync").description("Bidirectional sync: pull then push").option("--tables <tables>", "Comma-separated table names").option("--json", "Output as JSON").action(async (opts) => {
46020
47565
  try {
46021
- const result = await cloudSync({ tables: parseTables(opts.tables) });
47566
+ const result = await storageSync({ tables: parseTables(opts.tables) });
46022
47567
  if (opts.json) {
46023
47568
  printJson(result);
46024
47569
  return;
@@ -46031,6 +47576,9 @@ function registerCloudCommand(program2) {
46031
47576
  }
46032
47577
  });
46033
47578
  }
47579
+ function registerStorageCommand(program2) {
47580
+ installStorageSubcommands(program2.command("storage").description("Manage domains local/remote storage sync"));
47581
+ }
46034
47582
 
46035
47583
  // src/cli/index.ts
46036
47584
  init_version();
@@ -46056,6 +47604,7 @@ registerOutreachCommand(program2);
46056
47604
  registerSedoCommand(program2);
46057
47605
  registerWalletCommand(program2);
46058
47606
  registerProvisionCommand(program2);
46059
- registerCloudCommand(program2);
47607
+ registerStorageCommand(program2);
47608
+ registerEventsCommands(program2, { source: "domains" });
46060
47609
  registerInteractiveCommand(program2);
46061
47610
  program2.parse(process.argv);