@hasna/domains 0.0.20 → 0.0.21

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 (41) hide show
  1. package/README.md +139 -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 +1525 -448
  9. package/dist/db/pg-migrations.d.ts +1 -1
  10. package/dist/db/storage-sync.d.ts +55 -0
  11. package/dist/db/storage-sync.d.ts.map +1 -0
  12. package/dist/index.d.ts +3 -3
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +348 -58
  15. package/dist/lib/brandsight.d.ts.map +1 -1
  16. package/dist/lib/capability.d.ts.map +1 -1
  17. package/dist/lib/cloudflare-auth.d.ts.map +1 -1
  18. package/dist/lib/creds-check.d.ts.map +1 -1
  19. package/dist/lib/env-aliases.d.ts +43 -0
  20. package/dist/lib/env-aliases.d.ts.map +1 -0
  21. package/dist/lib/namecheap.d.ts +13 -0
  22. package/dist/lib/namecheap.d.ts.map +1 -1
  23. package/dist/lib/registrar.d.ts +36 -2
  24. package/dist/lib/registrar.d.ts.map +1 -1
  25. package/dist/lib/route53.d.ts.map +1 -1
  26. package/dist/lib/sedo.d.ts +7 -0
  27. package/dist/lib/sedo.d.ts.map +1 -1
  28. package/dist/mcp/index.d.ts.map +1 -1
  29. package/dist/mcp/index.js +16721 -18634
  30. package/dist/mcp/storage-tools.d.ts +3 -0
  31. package/dist/mcp/storage-tools.d.ts.map +1 -0
  32. package/dist/storage.d.ts +5 -0
  33. package/dist/storage.d.ts.map +1 -0
  34. package/dist/storage.js +5703 -0
  35. package/package.json +8 -2
  36. package/dist/cli/commands/cloud.d.ts +0 -3
  37. package/dist/cli/commands/cloud.d.ts.map +0 -1
  38. package/dist/db/cloud-sync.d.ts +0 -33
  39. package/dist/db/cloud-sync.d.ts.map +0 -1
  40. package/dist/mcp/cloud-tools.d.ts +0 -3
  41. 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; availability checks may work, but purchase + DNS writes require a qualifying account (>=10 domains / DDC). Prefer Route53." : "Not configured; and retail Domains API is gated for purchase/DNS since 2024."
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;
@@ -34334,7 +34474,7 @@ async function checkAvailability3(domain, config) {
34334
34474
  } catch {}
34335
34475
  return availability;
34336
34476
  }
34337
- async function registerDomain(domain, contact, durationYears = 1, autoRenew = true, config) {
34477
+ async function registerDomain2(domain, contact, durationYears = 1, autoRenew = true, config) {
34338
34478
  const { domains } = makeClients(config);
34339
34479
  const contactDetail = {
34340
34480
  FirstName: contact.first_name,
@@ -34383,7 +34523,7 @@ async function getDomainDetail(domain, config) {
34383
34523
  nameservers: (result.Nameservers ?? []).map((ns) => ns.Name ?? "").filter(Boolean)
34384
34524
  };
34385
34525
  }
34386
- async function updateNameservers(domain, nameservers, config, client) {
34526
+ async function updateNameservers2(domain, nameservers, config, client) {
34387
34527
  if (!nameservers.length) {
34388
34528
  throw new Error("updateNameservers requires at least one nameserver");
34389
34529
  }
@@ -34568,6 +34708,8 @@ async function upsertRecords(hostedZoneId, records, config) {
34568
34708
  }
34569
34709
  function createRoute53Provider(config) {
34570
34710
  const cfg = config ?? getConfig2();
34711
+ const registerWithRoute53 = registerDomain2;
34712
+ const updateRoute53Nameservers = updateNameservers2;
34571
34713
  return {
34572
34714
  name: "route53",
34573
34715
  async listDomains() {
@@ -34594,6 +34736,14 @@ function createRoute53Provider(config) {
34594
34736
  auto_renew: detail.auto_renew
34595
34737
  };
34596
34738
  },
34739
+ async registerDomain(domain, contact, options = {}) {
34740
+ const result = await registerWithRoute53(domain, contact, options.years ?? 1, options.autoRenew ?? true, cfg);
34741
+ return { domain, success: !!result.operationId, operationId: result.operationId };
34742
+ },
34743
+ async updateNameservers(domain, nameservers) {
34744
+ const result = await updateRoute53Nameservers(domain, nameservers, cfg);
34745
+ return { domain, success: !!result.operationId, operationId: result.operationId };
34746
+ },
34597
34747
  async renewDomain(_domain) {
34598
34748
  return { domain: _domain, success: false, orderId: undefined, chargedAmount: undefined };
34599
34749
  },
@@ -34681,15 +34831,132 @@ var init_route53 = __esm(() => {
34681
34831
  init_dist_es14();
34682
34832
  });
34683
34833
 
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 };
34834
+ // src/lib/env-aliases.ts
34835
+ function firstEnv(env, names) {
34836
+ for (const key of names) {
34837
+ const value = env[key];
34838
+ if (value)
34839
+ return { key, value };
34689
34840
  }
34690
- if (env["CLOUDFLARE_API_KEY"] && env["CLOUDFLARE_EMAIL"]) {
34691
- return { apiKey: env["CLOUDFLARE_API_KEY"], email: env["CLOUDFLARE_EMAIL"], accountId };
34841
+ return;
34842
+ }
34843
+ function hasEveryEnv(env, groups) {
34844
+ return groups.every((names) => !!firstEnv(env, names));
34845
+ }
34846
+ function flattenEnvNames(groups) {
34847
+ return Array.from(new Set(groups.flatMap((names) => [...names])));
34848
+ }
34849
+ function providerEnvNames(provider) {
34850
+ switch (provider.toLowerCase()) {
34851
+ case "namecheap":
34852
+ return flattenEnvNames([NAMECHEAP_ENV.apiKey, NAMECHEAP_ENV.username, NAMECHEAP_ENV.clientIp]);
34853
+ case "godaddy":
34854
+ return flattenEnvNames([GODADDY_ENV.apiKey, GODADDY_ENV.apiSecret]);
34855
+ case "brandsight":
34856
+ return flattenEnvNames([
34857
+ BRANDSIGHT_ENV.apiKey,
34858
+ BRANDSIGHT_ENV.apiSecret,
34859
+ BRANDSIGHT_ENV.customerId,
34860
+ BRANDSIGHT_ENV.shopperId,
34861
+ BRANDSIGHT_ENV.accountId
34862
+ ]);
34863
+ case "sedo":
34864
+ return flattenEnvNames([SEDO_ENV.partnerId, SEDO_ENV.signKey, SEDO_ENV.username, SEDO_ENV.password]);
34865
+ case "cloudflare":
34866
+ return flattenEnvNames([CLOUDFLARE_ENV.apiToken, CLOUDFLARE_ENV.apiKey, CLOUDFLARE_ENV.email, CLOUDFLARE_ENV.accountId]);
34867
+ case "route53":
34868
+ return ["AWS_PROFILE", "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"];
34869
+ case "aws:beepmedia":
34870
+ return flattenEnvNames([BEEPMEDIA_AWS_ENV.accessKeyId, BEEPMEDIA_AWS_ENV.secretAccessKey]);
34871
+ default:
34872
+ return [];
34873
+ }
34874
+ }
34875
+ function hasProviderCredentials(provider, env = process.env) {
34876
+ switch (provider.toLowerCase()) {
34877
+ case "namecheap":
34878
+ return hasEveryEnv(env, [NAMECHEAP_ENV.apiKey, NAMECHEAP_ENV.username, NAMECHEAP_ENV.clientIp]);
34879
+ case "godaddy":
34880
+ return hasEveryEnv(env, [GODADDY_ENV.apiKey, GODADDY_ENV.apiSecret]);
34881
+ case "brandsight":
34882
+ return hasEveryEnv(env, [BRANDSIGHT_ENV.apiKey, BRANDSIGHT_ENV.apiSecret, BRANDSIGHT_ENV.customerId]);
34883
+ case "sedo":
34884
+ return hasEveryEnv(env, [SEDO_ENV.partnerId, SEDO_ENV.signKey, SEDO_ENV.username, SEDO_ENV.password]);
34885
+ case "cloudflare": {
34886
+ const tokenMode = !!firstEnv(env, CLOUDFLARE_ENV.apiToken);
34887
+ const keyMode = hasEveryEnv(env, [CLOUDFLARE_ENV.apiKey, CLOUDFLARE_ENV.email]);
34888
+ return tokenMode || keyMode;
34889
+ }
34890
+ case "route53":
34891
+ return !!env["AWS_PROFILE"] || hasEveryEnv(env, [["AWS_ACCESS_KEY_ID"], ["AWS_SECRET_ACCESS_KEY"]]);
34892
+ case "aws:beepmedia":
34893
+ return hasEveryEnv(env, [BEEPMEDIA_AWS_ENV.accessKeyId, BEEPMEDIA_AWS_ENV.secretAccessKey]);
34894
+ default:
34895
+ return false;
34692
34896
  }
34897
+ }
34898
+ var NAMECHEAP_ENV, GODADDY_ENV, BRANDSIGHT_ENV, SEDO_ENV, CLOUDFLARE_ENV, BEEPMEDIA_AWS_ENV;
34899
+ var init_env_aliases = __esm(() => {
34900
+ NAMECHEAP_ENV = {
34901
+ apiKey: ["NAMECHEAP_API_KEY"],
34902
+ username: ["NAMECHEAP_USERNAME"],
34903
+ clientIp: ["NAMECHEAP_CLIENT_IP"]
34904
+ };
34905
+ GODADDY_ENV = {
34906
+ apiKey: ["GODADDY_API_KEY"],
34907
+ apiSecret: ["GODADDY_API_SECRET"]
34908
+ };
34909
+ BRANDSIGHT_ENV = {
34910
+ apiKey: [
34911
+ "BRANDSIGHT_API_KEY",
34912
+ "HASNAXYZ_BRANDSIGHT_LIVE_API_KEY",
34913
+ "HASNAXYZ_BRANDSIGHT_SANDBOX_API_KEY"
34914
+ ],
34915
+ apiSecret: [
34916
+ "BRANDSIGHT_API_SECRET",
34917
+ "HASNAXYZ_BRANDSIGHT_LIVE_API_SECRET",
34918
+ "HASNAXYZ_BRANDSIGHT_SANDBOX_API_SECRET"
34919
+ ],
34920
+ customerId: [
34921
+ "BRANDSIGHT_CUSTOMER_ID",
34922
+ "HASNAXYZ_BRANDSIGHT_LIVE_CUSTOMER_ID",
34923
+ "HASNAXYZ_BRANDSIGHT_SANDBOX_CUSTOMER_ID"
34924
+ ],
34925
+ shopperId: [
34926
+ "BRANDSIGHT_SHOPPER_ID",
34927
+ "HASNAXYZ_BRANDSIGHT_LIVE_SHOPPER_ID",
34928
+ "HASNAXYZ_BRANDSIGHT_SANDBOX_SHOPPER_ID"
34929
+ ],
34930
+ accountId: ["BRANDSIGHT_ACCOUNT_ID", "HASNAXYZ_BRANDSIGHT_LIVE_ACCOUNT_ID"]
34931
+ };
34932
+ SEDO_ENV = {
34933
+ partnerId: ["SEDO_PARTNER_ID", "HASNAXYZ_SEDO_LIVE_PARTNER_ID"],
34934
+ signKey: ["SEDO_API_KEY", "SEDO_SIGN_KEY", "HASNAXYZ_SEDO_LIVE_API_KEY"],
34935
+ username: ["SEDO_USERNAME", "SEDO_EMAIL", "HASNAXYZ_SEDO_LIVE_USERNAME", "HASNAXYZ_SEDO_LIVE_EMAIL"],
34936
+ password: ["SEDO_PASSWORD", "HASNAXYZ_SEDO_LIVE_PASSWORD"]
34937
+ };
34938
+ CLOUDFLARE_ENV = {
34939
+ apiToken: ["CLOUDFLARE_API_TOKEN", "HASNAXYZ_CLOUDFLARE_LIVE_API_TOKEN"],
34940
+ apiKey: ["CLOUDFLARE_API_KEY", "HASNAXYZ_CLOUDFLARE_LIVE_API_KEY"],
34941
+ email: ["CLOUDFLARE_EMAIL", "HASNAXYZ_CLOUDFLARE_LIVE_EMAIL"],
34942
+ accountId: ["CLOUDFLARE_ACCOUNT_ID", "HASNAXYZ_CLOUDFLARE_LIVE_ACCOUNT_ID"]
34943
+ };
34944
+ BEEPMEDIA_AWS_ENV = {
34945
+ accessKeyId: ["HASNASTUDIO_BEEPMEDIA_AWS_LIVE_ACCESS_KEY_ID", "BEEPMEDIA_AWS_ACCESS_KEY_ID"],
34946
+ secretAccessKey: ["HASNASTUDIO_BEEPMEDIA_AWS_LIVE_SECRET_ACCESS_KEY", "BEEPMEDIA_AWS_SECRET_ACCESS_KEY"]
34947
+ };
34948
+ });
34949
+
34950
+ // src/lib/cloudflare-auth.ts
34951
+ function resolveCloudflareConfig(env = process.env) {
34952
+ const accountId = firstEnv(env, CLOUDFLARE_ENV.accountId)?.value;
34953
+ const apiToken = firstEnv(env, CLOUDFLARE_ENV.apiToken)?.value;
34954
+ if (apiToken)
34955
+ return { apiToken, accountId };
34956
+ const apiKey = firstEnv(env, CLOUDFLARE_ENV.apiKey)?.value;
34957
+ const email = firstEnv(env, CLOUDFLARE_ENV.email)?.value;
34958
+ if (apiKey && email)
34959
+ return { apiKey, email, accountId };
34693
34960
  return accountId ? { accountId } : {};
34694
34961
  }
34695
34962
  function cloudflareAuthHeaders(cfg) {
@@ -34699,6 +34966,9 @@ function cloudflareAuthHeaders(cfg) {
34699
34966
  return { "X-Auth-Key": cfg.apiKey, "X-Auth-Email": cfg.email };
34700
34967
  throw new Error("Cloudflare credentials not configured. Set CLOUDFLARE_API_TOKEN, or CLOUDFLARE_API_KEY + CLOUDFLARE_EMAIL.");
34701
34968
  }
34969
+ var init_cloudflare_auth = __esm(() => {
34970
+ init_env_aliases();
34971
+ });
34702
34972
 
34703
34973
  // src/lib/cloudflare.ts
34704
34974
  function getConfig3() {
@@ -34830,30 +35100,44 @@ function createCloudflareProvider(config) {
34830
35100
  };
34831
35101
  }
34832
35102
  var CF_BASE = "https://api.cloudflare.com/client/v4";
34833
- var init_cloudflare = () => {};
35103
+ var init_cloudflare = __esm(() => {
35104
+ init_cloudflare_auth();
35105
+ });
34834
35106
 
34835
35107
  // src/lib/brandsight.ts
34836
35108
  function resolveBrandsightConfig(env = process.env) {
35109
+ const apiKey = firstEnv(env, BRANDSIGHT_ENV.apiKey)?.value ?? "";
35110
+ const apiSecret = firstEnv(env, BRANDSIGHT_ENV.apiSecret)?.value;
35111
+ const customerId = firstEnv(env, BRANDSIGHT_ENV.customerId)?.value;
35112
+ const shopperId = firstEnv(env, BRANDSIGHT_ENV.shopperId)?.value;
35113
+ const accountId = firstEnv(env, BRANDSIGHT_ENV.accountId)?.value;
34837
35114
  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"]
35115
+ apiKey,
35116
+ apiSecret,
35117
+ customerId,
35118
+ shopperId,
35119
+ accountId,
35120
+ baseUrl: env["BRANDSIGHT_BASE_URL"]
35121
+ };
35122
+ }
35123
+ function brandsightCapability(env = process.env) {
35124
+ const cfg = resolveBrandsightConfig(env);
35125
+ const configured = !!(cfg.apiKey && cfg.apiSecret && cfg.customerId);
35126
+ return {
35127
+ configured,
35128
+ gated: true,
35129
+ 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
35130
  };
34844
35131
  }
34845
35132
  function getApiKey() {
34846
- const key = process.env["BRANDSIGHT_API_KEY"];
35133
+ const key = resolveBrandsightConfig().apiKey;
34847
35134
  if (!key) {
34848
- throw new BrandsightApiError("BRANDSIGHT_API_KEY environment variable is not set");
35135
+ throw new BrandsightApiError("Brandsight API key is not set. Set BRANDSIGHT_API_KEY or HASNAXYZ_BRANDSIGHT_LIVE_API_KEY.");
34849
35136
  }
34850
35137
  return key;
34851
35138
  }
34852
35139
  function getConfig4() {
34853
- return {
34854
- apiKey: process.env["BRANDSIGHT_API_KEY"] ?? "",
34855
- accountId: process.env["BRANDSIGHT_ACCOUNT_ID"]
34856
- };
35140
+ return resolveBrandsightConfig();
34857
35141
  }
34858
35142
  async function apiRequest3(method, path, apiKey, body, baseUrl = BRANDSIGHT_BASE) {
34859
35143
  const fetchFn = _fetchFn || globalThis.fetch;
@@ -34909,11 +35193,11 @@ async function apiGet(path, apiKey, baseUrl = BRANDSIGHT_BASE) {
34909
35193
  }
34910
35194
  }
34911
35195
  function generateStubAlerts(brandName) {
34912
- const now = new Date().toISOString();
35196
+ const now2 = new Date().toISOString();
34913
35197
  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 }
35198
+ { domain: `${brandName}-deals.com`, type: "keyword", registered_at: now2 },
35199
+ { domain: `${brandName.replace(/a/gi, "4").replace(/e/gi, "3")}.com`, type: "homoglyph", registered_at: now2 },
35200
+ { domain: `${brandName}s.com`, type: "typosquat", registered_at: now2 }
34917
35201
  ];
34918
35202
  }
34919
35203
  function generateStubSimilarDomains(domain) {
@@ -35051,8 +35335,8 @@ function createBrandsightProvider(config) {
35051
35335
  auto_renew: d3.auto_renew
35052
35336
  };
35053
35337
  },
35054
- async renewDomain(domain) {
35055
- const result = await renewDomain3(domain, 1, cfg);
35338
+ async renewDomain(domain, years = 1) {
35339
+ const result = await renewDomain3(domain, years, cfg);
35056
35340
  return { domain, success: result.success, orderId: result.orderId };
35057
35341
  },
35058
35342
  async checkAvailability(domain) {
@@ -35071,6 +35355,7 @@ function createBrandsightProvider(config) {
35071
35355
  }
35072
35356
  var BrandsightApiError, _fetchFn = null, BRANDSIGHT_BASE = "https://api.brandsight.com/v1";
35073
35357
  var init_brandsight = __esm(() => {
35358
+ init_env_aliases();
35074
35359
  init_version();
35075
35360
  BrandsightApiError = class BrandsightApiError extends Error {
35076
35361
  statusCode;
@@ -35095,9 +35380,9 @@ __export(exports_config, {
35095
35380
  getConfigKey: () => getConfigKey,
35096
35381
  applyPurchaseProfile: () => applyPurchaseProfile
35097
35382
  });
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";
35383
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync } from "fs";
35384
+ import { homedir as homedir4 } from "os";
35385
+ import { join as join4 } from "path";
35101
35386
  function getPurchaseProfile() {
35102
35387
  return loadConfig3().purchase_aws_profile ?? process.env["DOMAINS_PURCHASE_AWS_PROFILE"] ?? undefined;
35103
35388
  }
@@ -35110,11 +35395,11 @@ function applyPurchaseProfile() {
35110
35395
  return profile;
35111
35396
  }
35112
35397
  function configPath() {
35113
- return join3(homedir3(), ".hasna", "domains", "config.json");
35398
+ return join4(homedir4(), ".hasna", "domains", "config.json");
35114
35399
  }
35115
35400
  function loadConfig3() {
35116
35401
  const path = configPath();
35117
- if (!existsSync2(path))
35402
+ if (!existsSync3(path))
35118
35403
  return {};
35119
35404
  try {
35120
35405
  return JSON.parse(readFileSync3(path, "utf-8"));
@@ -35124,8 +35409,8 @@ function loadConfig3() {
35124
35409
  }
35125
35410
  function saveConfig(config) {
35126
35411
  const path = configPath();
35127
- const dir = join3(homedir3(), ".hasna", "domains");
35128
- if (!existsSync2(dir))
35412
+ const dir = join4(homedir4(), ".hasna", "domains");
35413
+ if (!existsSync3(dir))
35129
35414
  mkdirSync2(dir, { recursive: true });
35130
35415
  writeFileSync(path, JSON.stringify(config, null, 2), "utf-8");
35131
35416
  }
@@ -35176,44 +35461,6 @@ function getConfigKey(keyPath) {
35176
35461
  }
35177
35462
  var init_config = () => {};
35178
35463
 
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
35464
  // src/db/domain-history.ts
35218
35465
  function rowToDomainHistory(row) {
35219
35466
  return {
@@ -35296,8 +35543,10 @@ var init_domain_history = __esm(() => {
35296
35543
  // src/lib/sedo.ts
35297
35544
  var exports_sedo = {};
35298
35545
  __export(exports_sedo, {
35546
+ sedoCapability: () => sedoCapability,
35299
35547
  searchSedoDomains: () => searchSedoDomains,
35300
35548
  searchHealthDomains: () => searchHealthDomains,
35549
+ resolveSedoConfig: () => resolveSedoConfig,
35301
35550
  removeDomainFromSedo: () => removeDomainFromSedo,
35302
35551
  recordSedoPurchase: () => recordSedoPurchase,
35303
35552
  listSedoPortfolio: () => listSedoPortfolio,
@@ -35307,16 +35556,31 @@ __export(exports_sedo, {
35307
35556
  checkSedoBlacklist: () => checkSedoBlacklist,
35308
35557
  addDomainToSedo: () => addDomainToSedo
35309
35558
  });
35559
+ function resolveSedoConfig(env = process.env) {
35560
+ return {
35561
+ partnerId: firstEnv(env, SEDO_ENV.partnerId)?.value,
35562
+ signKey: firstEnv(env, SEDO_ENV.signKey)?.value,
35563
+ username: firstEnv(env, SEDO_ENV.username)?.value,
35564
+ password: firstEnv(env, SEDO_ENV.password)?.value
35565
+ };
35566
+ }
35310
35567
  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;
35568
+ const config = resolveSedoConfig();
35569
+ const { partnerId, signKey, username, password } = config;
35315
35570
  if (!partnerId || !signKey || !username || !password) {
35316
- throw new Error("Sedo credentials not configured. Required: SEDO_PARTNER_ID, SEDO_API_KEY, SEDO_USERNAME, SEDO_PASSWORD");
35571
+ throw new Error("Sedo credentials not configured. Required: SEDO_PARTNER_ID, SEDO_API_KEY, SEDO_USERNAME, SEDO_PASSWORD (or HASNAXYZ_SEDO_LIVE_* aliases)");
35317
35572
  }
35318
35573
  return { partnerId, signKey, username, password };
35319
35574
  }
35575
+ function sedoCapability(env = process.env) {
35576
+ const cfg = resolveSedoConfig(env);
35577
+ const configured = !!(cfg.partnerId && cfg.signKey && cfg.username && cfg.password);
35578
+ return {
35579
+ configured,
35580
+ gated: true,
35581
+ 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."
35582
+ };
35583
+ }
35320
35584
  function buildXmlParams(params) {
35321
35585
  return {
35322
35586
  partnerid: params.partnerId,
@@ -35515,10 +35779,85 @@ function recordSedoPurchase(domain, price, orderId) {
35515
35779
  }
35516
35780
  var BASE_URL = "https://api.sedo.com/api/v1/";
35517
35781
  var init_sedo = __esm(() => {
35782
+ init_env_aliases();
35518
35783
  init_domains();
35519
35784
  init_domain_history();
35520
35785
  });
35521
35786
 
35787
+ // src/lib/creds-check.ts
35788
+ var exports_creds_check = {};
35789
+ __export(exports_creds_check, {
35790
+ checkProvisioningCredentials: () => checkProvisioningCredentials
35791
+ });
35792
+ function configuredStatus(provider, mode, detail) {
35793
+ return { provider, configured: true, mode, detail };
35794
+ }
35795
+ function missingStatus(provider, detail) {
35796
+ return { provider, configured: false, mode: "none", detail };
35797
+ }
35798
+ function checkProvisioningCredentials(env = process.env) {
35799
+ const out = [];
35800
+ const hasAwsKeys = !!(env["AWS_ACCESS_KEY_ID"] && env["AWS_SECRET_ACCESS_KEY"]);
35801
+ const hasAwsProfile = !!env["AWS_PROFILE"];
35802
+ if (hasAwsProfile || hasAwsKeys) {
35803
+ out.push(configuredStatus("route53", hasAwsProfile ? `profile:${env["AWS_PROFILE"]}` : "access-keys", "AWS credentials present (Route 53 Domains API uses us-east-1)"));
35804
+ } else {
35805
+ out.push(missingStatus("route53", "Set AWS_PROFILE or AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY"));
35806
+ }
35807
+ const beepmediaAws = hasProviderCredentials("aws:beepmedia", env);
35808
+ out.push({
35809
+ provider: "aws:beepmedia",
35810
+ configured: beepmediaAws,
35811
+ mode: beepmediaAws ? "scoped-env" : "none",
35812
+ 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 ")}`
35813
+ });
35814
+ const cf = resolveCloudflareConfig(env);
35815
+ const cfMode = cf.apiToken ? "token" : cf.apiKey && cf.email ? "global-key" : "none";
35816
+ out.push({
35817
+ provider: "cloudflare",
35818
+ configured: cfMode !== "none",
35819
+ mode: cfMode + (cf.accountId ? "+account" : ""),
35820
+ detail: cfMode === "none" ? "Set CLOUDFLARE_API_TOKEN or CLOUDFLARE_API_KEY+CLOUDFLARE_EMAIL" : cf.accountId ? "ok" : "missing CLOUDFLARE_ACCOUNT_ID (needed to create zones)"
35821
+ });
35822
+ out.push({
35823
+ provider: "namecheap",
35824
+ configured: hasProviderCredentials("namecheap", env),
35825
+ mode: hasProviderCredentials("namecheap", env) ? "api-key+username+client-ip" : "none",
35826
+ detail: "Requires NAMECHEAP_API_KEY, NAMECHEAP_USERNAME, and whitelisted NAMECHEAP_CLIENT_IP"
35827
+ });
35828
+ const gd = godaddyCapability(env);
35829
+ out.push({
35830
+ provider: "godaddy",
35831
+ configured: gd.configured,
35832
+ mode: gd.configured ? "key+secret" : "none",
35833
+ detail: gd.notes
35834
+ });
35835
+ const bs = brandsightCapability(env);
35836
+ const bsMode = firstEnv(env, ["BRANDSIGHT_API_KEY"]) ? "standard-env" : bs.configured ? "scoped-env" : "none";
35837
+ out.push({
35838
+ provider: "brandsight",
35839
+ configured: bs.configured,
35840
+ mode: bsMode,
35841
+ detail: bs.notes
35842
+ });
35843
+ const sedo = sedoCapability(env);
35844
+ const sedoMode = firstEnv(env, ["SEDO_API_KEY"]) ? "standard-env" : sedo.configured ? "scoped-env" : "none";
35845
+ out.push({
35846
+ provider: "sedo",
35847
+ configured: sedo.configured,
35848
+ mode: sedoMode,
35849
+ detail: sedo.notes
35850
+ });
35851
+ return out;
35852
+ }
35853
+ var init_creds_check = __esm(() => {
35854
+ init_cloudflare_auth();
35855
+ init_brandsight();
35856
+ init_godaddy();
35857
+ init_sedo();
35858
+ init_env_aliases();
35859
+ });
35860
+
35522
35861
  // src/lib/provision-state.ts
35523
35862
  function isDomainTerminal(s3) {
35524
35863
  return TERMINAL.has(s3);
@@ -35593,7 +35932,7 @@ function makeDomainDaemonDeps() {
35593
35932
  return;
35594
35933
  case "delegate_ns": {
35595
35934
  const zone = await ensureZone(domain);
35596
- await updateNameservers(domain, zone.nameservers);
35935
+ await updateNameservers2(domain, zone.nameservers);
35597
35936
  return;
35598
35937
  }
35599
35938
  case "check_ns_propagation":
@@ -40553,6 +40892,684 @@ var {
40553
40892
  Help
40554
40893
  } = import__.default;
40555
40894
 
40895
+ // node_modules/@hasna/events/dist/commander.js
40896
+ import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
40897
+ import { existsSync } from "fs";
40898
+ import { homedir } from "os";
40899
+ import { join } from "path";
40900
+ import { createHmac, timingSafeEqual } from "crypto";
40901
+ import { randomUUID } from "crypto";
40902
+ import { spawn } from "child_process";
40903
+ import { randomUUID as randomUUID2 } from "crypto";
40904
+ function getPathValue(input, path) {
40905
+ return path.split(".").reduce((value, part) => {
40906
+ if (value && typeof value === "object" && part in value) {
40907
+ return value[part];
40908
+ }
40909
+ return;
40910
+ }, input);
40911
+ }
40912
+ function wildcardToRegExp(pattern) {
40913
+ const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
40914
+ return new RegExp(`^${escaped}$`);
40915
+ }
40916
+ function matchString(value, matcher) {
40917
+ if (matcher === undefined)
40918
+ return true;
40919
+ if (value === undefined)
40920
+ return false;
40921
+ const matchers = Array.isArray(matcher) ? matcher : [matcher];
40922
+ return matchers.some((item) => wildcardToRegExp(item).test(value));
40923
+ }
40924
+ function matchRecord(input, matcher) {
40925
+ if (!matcher)
40926
+ return true;
40927
+ return Object.entries(matcher).every(([path, expected]) => {
40928
+ const actual = getPathValue(input, path);
40929
+ if (typeof expected === "string" || Array.isArray(expected)) {
40930
+ return matchString(actual === undefined ? undefined : String(actual), expected);
40931
+ }
40932
+ return actual === expected;
40933
+ });
40934
+ }
40935
+ function eventMatchesFilter(event, filter) {
40936
+ 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);
40937
+ }
40938
+ function channelMatchesEvent(channel, event) {
40939
+ if (!channel.enabled)
40940
+ return false;
40941
+ if (!channel.filters || channel.filters.length === 0)
40942
+ return true;
40943
+ return channel.filters.some((filter) => eventMatchesFilter(event, filter));
40944
+ }
40945
+ var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
40946
+ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
40947
+ function getEventsDataDir(override) {
40948
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
40949
+ }
40950
+
40951
+ class JsonEventsStore {
40952
+ dataDir;
40953
+ channelsPath;
40954
+ eventsPath;
40955
+ deliveriesPath;
40956
+ constructor(dataDir = getEventsDataDir()) {
40957
+ this.dataDir = dataDir;
40958
+ this.channelsPath = join(dataDir, "channels.json");
40959
+ this.eventsPath = join(dataDir, "events.json");
40960
+ this.deliveriesPath = join(dataDir, "deliveries.json");
40961
+ }
40962
+ async init() {
40963
+ await mkdir(this.dataDir, { recursive: true, mode: 448 });
40964
+ await chmod(this.dataDir, 448).catch(() => {
40965
+ return;
40966
+ });
40967
+ await this.ensureArrayFile(this.channelsPath);
40968
+ await this.ensureArrayFile(this.eventsPath);
40969
+ await this.ensureArrayFile(this.deliveriesPath);
40970
+ }
40971
+ async addChannel(channel) {
40972
+ await this.init();
40973
+ const channels = await this.readJson(this.channelsPath, []);
40974
+ const index = channels.findIndex((item) => item.id === channel.id);
40975
+ if (index >= 0) {
40976
+ channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
40977
+ } else {
40978
+ channels.push(channel);
40979
+ }
40980
+ await this.writeJson(this.channelsPath, channels);
40981
+ return index >= 0 ? channels[index] : channel;
40982
+ }
40983
+ async listChannels() {
40984
+ await this.init();
40985
+ return this.readJson(this.channelsPath, []);
40986
+ }
40987
+ async getChannel(id) {
40988
+ const channels = await this.listChannels();
40989
+ return channels.find((channel) => channel.id === id);
40990
+ }
40991
+ async removeChannel(id) {
40992
+ await this.init();
40993
+ const channels = await this.readJson(this.channelsPath, []);
40994
+ const next = channels.filter((channel) => channel.id !== id);
40995
+ await this.writeJson(this.channelsPath, next);
40996
+ return next.length !== channels.length;
40997
+ }
40998
+ async appendEvent(event) {
40999
+ await this.init();
41000
+ const events = await this.readJson(this.eventsPath, []);
41001
+ events.push(event);
41002
+ await this.writeJson(this.eventsPath, events);
41003
+ return event;
41004
+ }
41005
+ async listEvents() {
41006
+ await this.init();
41007
+ return this.readJson(this.eventsPath, []);
41008
+ }
41009
+ async findEventByIdentity(identity) {
41010
+ const events = await this.listEvents();
41011
+ return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
41012
+ }
41013
+ async appendDelivery(result) {
41014
+ await this.init();
41015
+ const deliveries = await this.readJson(this.deliveriesPath, []);
41016
+ deliveries.push(result);
41017
+ await this.writeJson(this.deliveriesPath, deliveries);
41018
+ return result;
41019
+ }
41020
+ async listDeliveries() {
41021
+ await this.init();
41022
+ return this.readJson(this.deliveriesPath, []);
41023
+ }
41024
+ async exportData() {
41025
+ return {
41026
+ channels: await this.listChannels(),
41027
+ events: await this.listEvents(),
41028
+ deliveries: await this.listDeliveries()
41029
+ };
41030
+ }
41031
+ async ensureArrayFile(path) {
41032
+ if (!existsSync(path)) {
41033
+ await writeFile(path, `[]
41034
+ `, { encoding: "utf-8", mode: 384 });
41035
+ }
41036
+ await chmod(path, 384).catch(() => {
41037
+ return;
41038
+ });
41039
+ }
41040
+ async readJson(path, fallback) {
41041
+ try {
41042
+ const raw = await readFile(path, "utf-8");
41043
+ if (!raw.trim())
41044
+ return fallback;
41045
+ return JSON.parse(raw);
41046
+ } catch (error) {
41047
+ if (error.code === "ENOENT")
41048
+ return fallback;
41049
+ throw error;
41050
+ }
41051
+ }
41052
+ async writeJson(path, value) {
41053
+ const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
41054
+ await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
41055
+ `, { encoding: "utf-8", mode: 384 });
41056
+ await rename(tempPath, path);
41057
+ await chmod(path, 384).catch(() => {
41058
+ return;
41059
+ });
41060
+ }
41061
+ }
41062
+ var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
41063
+ function buildSignatureBase(timestamp, body) {
41064
+ return `${timestamp}.${body}`;
41065
+ }
41066
+ function signPayload(secret, timestamp, body) {
41067
+ const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
41068
+ return `sha256=${digest}`;
41069
+ }
41070
+ function now() {
41071
+ return new Date().toISOString();
41072
+ }
41073
+ function truncate(value, max = 4096) {
41074
+ return value.length > max ? `${value.slice(0, max)}...` : value;
41075
+ }
41076
+ function buildWebhookRequest(event, channel) {
41077
+ if (!channel.webhook)
41078
+ throw new Error(`Channel ${channel.id} has no webhook config`);
41079
+ const body = JSON.stringify(event);
41080
+ const timestamp = event.time;
41081
+ const headers = {
41082
+ "Content-Type": "application/json",
41083
+ "User-Agent": "@hasna/events",
41084
+ "X-Hasna-Event-Id": event.id,
41085
+ "X-Hasna-Event-Type": event.type,
41086
+ "X-Hasna-Timestamp": timestamp,
41087
+ ...channel.webhook.headers
41088
+ };
41089
+ if (channel.webhook.secret) {
41090
+ headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
41091
+ }
41092
+ return { body, headers };
41093
+ }
41094
+ async function dispatchWebhook(event, channel, options = {}) {
41095
+ if (!channel.webhook)
41096
+ throw new Error(`Channel ${channel.id} has no webhook config`);
41097
+ const startedAt = now();
41098
+ const { body, headers } = buildWebhookRequest(event, channel);
41099
+ const controller = new AbortController;
41100
+ const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
41101
+ try {
41102
+ const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
41103
+ method: "POST",
41104
+ headers,
41105
+ body,
41106
+ signal: controller.signal
41107
+ });
41108
+ const responseBody = truncate(await response.text());
41109
+ return {
41110
+ attempt: 1,
41111
+ status: response.ok ? "success" : "failed",
41112
+ startedAt,
41113
+ completedAt: now(),
41114
+ responseStatus: response.status,
41115
+ responseBody,
41116
+ error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
41117
+ };
41118
+ } catch (error) {
41119
+ return {
41120
+ attempt: 1,
41121
+ status: "failed",
41122
+ startedAt,
41123
+ completedAt: now(),
41124
+ error: error instanceof Error ? error.message : String(error)
41125
+ };
41126
+ } finally {
41127
+ clearTimeout(timeout);
41128
+ }
41129
+ }
41130
+ async function dispatchCommand(event, channel) {
41131
+ if (!channel.command)
41132
+ throw new Error(`Channel ${channel.id} has no command config`);
41133
+ const startedAt = now();
41134
+ const eventJson = JSON.stringify(event);
41135
+ const env = {
41136
+ ...process.env,
41137
+ ...channel.command.env,
41138
+ HASNA_CHANNEL_ID: channel.id,
41139
+ HASNA_EVENT_ID: event.id,
41140
+ HASNA_EVENT_TYPE: event.type,
41141
+ HASNA_EVENT_SOURCE: event.source,
41142
+ HASNA_EVENT_SUBJECT: event.subject ?? "",
41143
+ HASNA_EVENT_SEVERITY: event.severity,
41144
+ HASNA_EVENT_TIME: event.time,
41145
+ HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
41146
+ HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
41147
+ HASNA_EVENT_JSON: eventJson
41148
+ };
41149
+ return new Promise((resolve) => {
41150
+ const child = spawn(channel.command.command, channel.command.args ?? [], {
41151
+ cwd: channel.command.cwd,
41152
+ env,
41153
+ stdio: ["pipe", "pipe", "pipe"]
41154
+ });
41155
+ let stdout = "";
41156
+ let stderr = "";
41157
+ const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
41158
+ child.stdin.end(eventJson);
41159
+ child.stdout.on("data", (chunk) => {
41160
+ stdout += chunk.toString();
41161
+ });
41162
+ child.stderr.on("data", (chunk) => {
41163
+ stderr += chunk.toString();
41164
+ });
41165
+ child.on("error", (error) => {
41166
+ clearTimeout(timeout);
41167
+ resolve({
41168
+ attempt: 1,
41169
+ status: "failed",
41170
+ startedAt,
41171
+ completedAt: now(),
41172
+ stdout: truncate(stdout),
41173
+ stderr: truncate(stderr),
41174
+ error: error.message
41175
+ });
41176
+ });
41177
+ child.on("close", (code, signal) => {
41178
+ clearTimeout(timeout);
41179
+ const success = code === 0;
41180
+ resolve({
41181
+ attempt: 1,
41182
+ status: success ? "success" : "failed",
41183
+ startedAt,
41184
+ completedAt: now(),
41185
+ stdout: truncate(stdout),
41186
+ stderr: truncate(stderr),
41187
+ error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
41188
+ });
41189
+ });
41190
+ });
41191
+ }
41192
+ async function dispatchChannel(event, channel, options = {}) {
41193
+ if (channel.transport === "webhook")
41194
+ return dispatchWebhook(event, channel, options);
41195
+ if (channel.transport === "command")
41196
+ return dispatchCommand(event, channel);
41197
+ return {
41198
+ attempt: 1,
41199
+ status: "skipped",
41200
+ startedAt: now(),
41201
+ completedAt: now(),
41202
+ error: `Unsupported transport: ${channel.transport}`
41203
+ };
41204
+ }
41205
+ function createDeliveryResult(event, channel, attempts) {
41206
+ const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
41207
+ return {
41208
+ id: randomUUID(),
41209
+ eventId: event.id,
41210
+ channelId: channel.id,
41211
+ transport: channel.transport,
41212
+ status,
41213
+ attempts,
41214
+ createdAt: attempts[0]?.startedAt ?? now(),
41215
+ completedAt: attempts.at(-1)?.completedAt ?? now()
41216
+ };
41217
+ }
41218
+ function createEvent(input) {
41219
+ return {
41220
+ id: input.id ?? randomUUID2(),
41221
+ source: input.source,
41222
+ type: input.type,
41223
+ time: normalizeTime(input.time),
41224
+ subject: input.subject,
41225
+ severity: input.severity ?? "info",
41226
+ data: input.data ?? {},
41227
+ message: input.message,
41228
+ dedupeKey: input.dedupeKey,
41229
+ schemaVersion: input.schemaVersion ?? "1.0",
41230
+ metadata: input.metadata ?? {}
41231
+ };
41232
+ }
41233
+
41234
+ class EventsClient {
41235
+ store;
41236
+ redactors;
41237
+ transportOptions;
41238
+ constructor(options = {}) {
41239
+ this.store = options.store ?? new JsonEventsStore(options.dataDir);
41240
+ this.redactors = options.redactors ?? [];
41241
+ this.transportOptions = { fetchImpl: options.fetchImpl };
41242
+ }
41243
+ async addChannel(input) {
41244
+ const timestamp = new Date().toISOString();
41245
+ return this.store.addChannel({
41246
+ ...input,
41247
+ createdAt: input.createdAt ?? timestamp,
41248
+ updatedAt: input.updatedAt ?? timestamp
41249
+ });
41250
+ }
41251
+ async listChannels() {
41252
+ return this.store.listChannels();
41253
+ }
41254
+ async removeChannel(id) {
41255
+ return this.store.removeChannel(id);
41256
+ }
41257
+ async emit(input, options = {}) {
41258
+ const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
41259
+ if (options.dedupe !== false) {
41260
+ const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
41261
+ if (existing) {
41262
+ return { event: existing, deliveries: [], deduped: true };
41263
+ }
41264
+ }
41265
+ await this.store.appendEvent(event);
41266
+ const deliveries = options.deliver === false ? [] : await this.deliver(event);
41267
+ return { event, deliveries, deduped: false };
41268
+ }
41269
+ async listEvents() {
41270
+ return this.store.listEvents();
41271
+ }
41272
+ async listDeliveries() {
41273
+ return this.store.listDeliveries();
41274
+ }
41275
+ async deliver(event) {
41276
+ const channels = await this.store.listChannels();
41277
+ const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
41278
+ const deliveries = [];
41279
+ for (const channel of selected) {
41280
+ const eventForChannel = await this.applyRedaction(event, channel);
41281
+ const result = await this.deliverWithRetry(eventForChannel, channel);
41282
+ await this.store.appendDelivery(result);
41283
+ deliveries.push(result);
41284
+ }
41285
+ return deliveries;
41286
+ }
41287
+ async testChannel(id, input = {}) {
41288
+ const channel = await this.store.getChannel(id);
41289
+ if (!channel)
41290
+ throw new Error(`Channel not found: ${id}`);
41291
+ const event = createEvent({
41292
+ source: input.source ?? "hasna.events",
41293
+ type: input.type ?? "events.test",
41294
+ subject: input.subject ?? id,
41295
+ severity: input.severity ?? "info",
41296
+ data: input.data ?? { test: true },
41297
+ message: input.message ?? "Hasna events test delivery",
41298
+ dedupeKey: input.dedupeKey,
41299
+ schemaVersion: input.schemaVersion,
41300
+ metadata: input.metadata,
41301
+ time: input.time,
41302
+ id: input.id
41303
+ });
41304
+ const eventForChannel = await this.applyRedaction(event, channel);
41305
+ const result = await this.deliverWithRetry(eventForChannel, channel);
41306
+ await this.store.appendDelivery(result);
41307
+ return result;
41308
+ }
41309
+ async replay(options = {}) {
41310
+ const events = (await this.store.listEvents()).filter((event) => {
41311
+ if (options.eventId && event.id !== options.eventId)
41312
+ return false;
41313
+ if (options.source && event.source !== options.source)
41314
+ return false;
41315
+ if (options.type && event.type !== options.type)
41316
+ return false;
41317
+ return true;
41318
+ });
41319
+ if (options.dryRun)
41320
+ return { events, deliveries: [] };
41321
+ const deliveries = [];
41322
+ for (const event of events) {
41323
+ deliveries.push(...await this.deliver(event));
41324
+ }
41325
+ return { events, deliveries };
41326
+ }
41327
+ async applyRedaction(event, channel) {
41328
+ let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
41329
+ for (const redactor of this.redactors) {
41330
+ next = await redactor(next, channel);
41331
+ }
41332
+ return next;
41333
+ }
41334
+ async deliverWithRetry(event, channel) {
41335
+ const policy = normalizeRetryPolicy(channel.retry);
41336
+ const attempts = [];
41337
+ for (let index = 0;index < policy.maxAttempts; index += 1) {
41338
+ const attempt = await dispatchChannel(event, channel, this.transportOptions);
41339
+ attempt.attempt = index + 1;
41340
+ if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
41341
+ attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
41342
+ }
41343
+ attempts.push(attempt);
41344
+ if (attempt.status !== "failed")
41345
+ break;
41346
+ if (attempt.nextBackoffMs)
41347
+ await Bun.sleep(attempt.nextBackoffMs);
41348
+ }
41349
+ return createDeliveryResult(event, channel, attempts);
41350
+ }
41351
+ }
41352
+ function redactPaths(event, paths, replacement = "[REDACTED]") {
41353
+ if (paths.length === 0)
41354
+ return event;
41355
+ const copy = structuredClone(event);
41356
+ for (const path of paths) {
41357
+ setPath(copy, path, replacement);
41358
+ }
41359
+ return copy;
41360
+ }
41361
+ function sanitizeChannelForOutput(channel) {
41362
+ const copy = structuredClone(channel);
41363
+ if (copy.webhook?.secret)
41364
+ copy.webhook.secret = "[REDACTED]";
41365
+ if (copy.command?.env) {
41366
+ copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
41367
+ }
41368
+ return copy;
41369
+ }
41370
+ function sanitizeChannelsForOutput(channels) {
41371
+ return channels.map(sanitizeChannelForOutput);
41372
+ }
41373
+ function redactSensitiveKeys(event, replacement = "[REDACTED]") {
41374
+ return redactValue(event, replacement);
41375
+ }
41376
+ function shouldRedactKey(key) {
41377
+ return /secret|token|password|api[_-]?key|authorization/i.test(key);
41378
+ }
41379
+ function redactValue(value, replacement) {
41380
+ if (Array.isArray(value))
41381
+ return value.map((item) => redactValue(item, replacement));
41382
+ if (!value || typeof value !== "object")
41383
+ return value;
41384
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [
41385
+ key,
41386
+ shouldRedactKey(key) ? replacement : redactValue(item, replacement)
41387
+ ]));
41388
+ }
41389
+ function setPath(input, path, replacement) {
41390
+ const parts = path.split(".");
41391
+ let cursor = input;
41392
+ for (const part of parts.slice(0, -1)) {
41393
+ const next = cursor[part];
41394
+ if (!next || typeof next !== "object")
41395
+ return;
41396
+ cursor = next;
41397
+ }
41398
+ const last = parts.at(-1);
41399
+ if (last && last in cursor)
41400
+ cursor[last] = replacement;
41401
+ }
41402
+ function normalizeTime(value) {
41403
+ if (!value)
41404
+ return new Date().toISOString();
41405
+ return value instanceof Date ? value.toISOString() : value;
41406
+ }
41407
+ function normalizeRetryPolicy(policy) {
41408
+ return {
41409
+ maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
41410
+ backoffMs: Math.max(0, policy?.backoffMs ?? 250),
41411
+ multiplier: Math.max(1, policy?.multiplier ?? 2)
41412
+ };
41413
+ }
41414
+ function parseJsonObject(value, fallback) {
41415
+ if (!value)
41416
+ return fallback;
41417
+ const parsed = JSON.parse(value);
41418
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
41419
+ throw new Error("Expected a JSON object");
41420
+ }
41421
+ return parsed;
41422
+ }
41423
+ function parseHeaders(values) {
41424
+ if (!values?.length)
41425
+ return;
41426
+ const headers = {};
41427
+ for (const value of values) {
41428
+ const separator = value.indexOf("=");
41429
+ if (separator === -1)
41430
+ throw new Error(`Invalid header, expected name=value: ${value}`);
41431
+ headers[value.slice(0, separator)] = value.slice(separator + 1);
41432
+ }
41433
+ return headers;
41434
+ }
41435
+ function parseFilter(options) {
41436
+ const filter2 = {};
41437
+ if (options.source)
41438
+ filter2.source = options.source;
41439
+ if (options.type)
41440
+ filter2.type = options.type;
41441
+ if (options.subject)
41442
+ filter2.subject = options.subject;
41443
+ if (options.severity)
41444
+ filter2.severity = options.severity;
41445
+ return Object.keys(filter2).length > 0 ? [filter2] : undefined;
41446
+ }
41447
+ function createClient(options) {
41448
+ if (options.createClient)
41449
+ return options.createClient();
41450
+ return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
41451
+ }
41452
+ function print(value, json, text) {
41453
+ if (json)
41454
+ console.log(JSON.stringify(value, null, 2));
41455
+ else
41456
+ console.log(text);
41457
+ }
41458
+ function registerWebhookCommands(program2, options) {
41459
+ const webhooks = program2.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
41460
+ 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) => {
41461
+ const timestamp = new Date().toISOString();
41462
+ const channel = {
41463
+ id: actionOptions.id,
41464
+ name: actionOptions.name,
41465
+ enabled: !actionOptions.disabled,
41466
+ transport: actionOptions.transport,
41467
+ filters: parseFilter(actionOptions),
41468
+ retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
41469
+ redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
41470
+ createdAt: timestamp,
41471
+ updatedAt: timestamp
41472
+ };
41473
+ if (actionOptions.transport === "webhook") {
41474
+ channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
41475
+ } else if (actionOptions.transport === "command") {
41476
+ channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
41477
+ } else {
41478
+ throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
41479
+ }
41480
+ const saved = await createClient(options).addChannel(channel);
41481
+ print(sanitizeChannelForOutput(saved), Boolean(actionOptions.json), `Added ${saved.transport} channel ${saved.id}`);
41482
+ });
41483
+ webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
41484
+ const channels = await createClient(options).listChannels();
41485
+ if (actionOptions.json) {
41486
+ console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
41487
+ return;
41488
+ }
41489
+ if (!channels.length) {
41490
+ console.log("No channels configured.");
41491
+ return;
41492
+ }
41493
+ for (const channel of channels) {
41494
+ console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
41495
+ }
41496
+ });
41497
+ webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
41498
+ const removed = await createClient(options).removeChannel(id);
41499
+ print({ removed }, Boolean(actionOptions.json), removed ? `Removed ${id}` : `Channel not found: ${id}`);
41500
+ });
41501
+ 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) => {
41502
+ const result = await createClient(options).testChannel(id, {
41503
+ source: options.source,
41504
+ type: actionOptions.type,
41505
+ subject: actionOptions.subject ?? id,
41506
+ message: actionOptions.message,
41507
+ data: parseJsonObject(actionOptions.data, { test: true })
41508
+ });
41509
+ print(result, Boolean(actionOptions.json), `${result.status}: ${result.channelId}`);
41510
+ });
41511
+ return webhooks;
41512
+ }
41513
+ function registerEventCommands(program2, options) {
41514
+ const events = program2.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
41515
+ 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) => {
41516
+ const result = await createClient(options).emit({
41517
+ source: actionOptions.source ?? options.source,
41518
+ type,
41519
+ subject: actionOptions.subject,
41520
+ severity: actionOptions.severity,
41521
+ message: actionOptions.message,
41522
+ dedupeKey: actionOptions.dedupeKey,
41523
+ data: parseJsonObject(actionOptions.data, {}),
41524
+ metadata: parseJsonObject(actionOptions.metadata, {})
41525
+ }, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
41526
+ print(result, Boolean(actionOptions.json), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
41527
+ });
41528
+ 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) => {
41529
+ let rows = await createClient(options).listEvents();
41530
+ if (actionOptions.source)
41531
+ rows = rows.filter((event) => event.source === actionOptions.source);
41532
+ if (actionOptions.type)
41533
+ rows = rows.filter((event) => event.type === actionOptions.type);
41534
+ if (actionOptions.limit)
41535
+ rows = rows.slice(-actionOptions.limit);
41536
+ if (actionOptions.json) {
41537
+ console.log(JSON.stringify(rows, null, 2));
41538
+ return;
41539
+ }
41540
+ if (!rows.length) {
41541
+ console.log("No events recorded.");
41542
+ return;
41543
+ }
41544
+ for (const event of rows)
41545
+ console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
41546
+ });
41547
+ 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) => {
41548
+ const result = await createClient(options).replay({
41549
+ eventId: actionOptions.id,
41550
+ source: actionOptions.source,
41551
+ type: actionOptions.type,
41552
+ dryRun: actionOptions.dryRun
41553
+ });
41554
+ print(result, Boolean(actionOptions.json), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
41555
+ });
41556
+ return events;
41557
+ }
41558
+ function registerEventsCommands(program2, options) {
41559
+ registerWebhookCommands(program2, options);
41560
+ registerEventCommands(program2, options);
41561
+ }
41562
+ function parseNumber(value) {
41563
+ const parsed = Number(value);
41564
+ if (!Number.isFinite(parsed))
41565
+ throw new Error(`Expected a number, got ${value}`);
41566
+ return parsed;
41567
+ }
41568
+ function collectValues(value, previous) {
41569
+ previous.push(value);
41570
+ return previous;
41571
+ }
41572
+
40556
41573
  // src/cli/commands/domain.ts
40557
41574
  init_domains();
40558
41575
 
@@ -40655,6 +41672,61 @@ async function renewDomain(domain, years = 1, config) {
40655
41672
  chargedAmount: chargedAmount || undefined
40656
41673
  };
40657
41674
  }
41675
+ function namecheapContactParams(contact) {
41676
+ const organization = contact.organization_name || "NA";
41677
+ const base = {
41678
+ FirstName: contact.first_name,
41679
+ LastName: contact.last_name,
41680
+ OrganizationName: organization,
41681
+ Address1: contact.address_line_1,
41682
+ City: contact.city,
41683
+ StateProvince: contact.state,
41684
+ PostalCode: contact.zip_code,
41685
+ Country: contact.country_code,
41686
+ Phone: contact.phone,
41687
+ EmailAddress: contact.email
41688
+ };
41689
+ const params = {};
41690
+ for (const prefix of ["Registrant", "Tech", "Admin", "AuxBilling"]) {
41691
+ for (const [key, value] of Object.entries(base)) {
41692
+ params[prefix + key] = value;
41693
+ }
41694
+ }
41695
+ return params;
41696
+ }
41697
+ async function registerDomain(domain, contact, options = {}, config) {
41698
+ const cfg = config || getConfig();
41699
+ const params = {
41700
+ DomainName: domain,
41701
+ Years: String(options.years ?? 1),
41702
+ AddFreeWhoisguard: options.whoisGuard === false ? "no" : "yes",
41703
+ WGEnabled: options.whoisGuard === false ? "no" : "yes",
41704
+ ...namecheapContactParams(contact)
41705
+ };
41706
+ if (options.premiumPrice !== undefined) {
41707
+ params.IsPremiumDomain = "true";
41708
+ params.PremiumPrice = String(options.premiumPrice);
41709
+ }
41710
+ const xml = await apiRequest(cfg, "namecheap.domains.create", params);
41711
+ return {
41712
+ domain,
41713
+ success: xml.includes('Status="OK"') || xml.includes('Registered="true"'),
41714
+ orderId: parseXmlValue(xml, "OrderId") || undefined,
41715
+ chargedAmount: parseXmlValue(xml, "ChargedAmount") || undefined
41716
+ };
41717
+ }
41718
+ async function updateNameservers(domain, nameservers, config) {
41719
+ if (nameservers.length === 0)
41720
+ throw new Error("updateNameservers requires at least one nameserver");
41721
+ const cfg = config || getConfig();
41722
+ const { sld, tld } = splitDomain(domain);
41723
+ const xml = await apiRequest(cfg, "namecheap.domains.dns.setCustom", {
41724
+ SLD: sld,
41725
+ TLD: tld,
41726
+ Nameservers: nameservers.join(",")
41727
+ });
41728
+ return xml.includes('Status="OK"') || xml.includes('Updated="true"');
41729
+ }
40658
41730
  async function getDnsRecords(_domain, sld, tld, config) {
40659
41731
  const cfg = config || getConfig();
40660
41732
  const xml = await apiRequest(cfg, "namecheap.domains.dns.getHosts", {
@@ -40783,141 +41855,12 @@ function normalizeDate(dateStr) {
40783
41855
  }
40784
41856
  }
40785
41857
 
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
41858
  // src/lib/registrar.ts
41859
+ init_godaddy();
40918
41860
  init_route53();
40919
41861
  init_cloudflare();
40920
41862
  init_brandsight();
41863
+ init_env_aliases();
40921
41864
  function createNamecheapProvider() {
40922
41865
  return {
40923
41866
  name: "namecheap",
@@ -40947,9 +41890,27 @@ function createNamecheapProvider() {
40947
41890
  auto_renew: true
40948
41891
  };
40949
41892
  },
40950
- async renewDomain(domain) {
41893
+ async registerDomain(domain, contact, options = {}) {
40951
41894
  const config = getConfig();
40952
- const result = await renewDomain(domain, 1, config);
41895
+ const result = await registerDomain(domain, contact, {
41896
+ years: options.years,
41897
+ premiumPrice: options.premiumPrice
41898
+ }, config);
41899
+ return {
41900
+ domain: result.domain,
41901
+ success: result.success,
41902
+ orderId: result.orderId,
41903
+ chargedAmount: result.chargedAmount
41904
+ };
41905
+ },
41906
+ async updateNameservers(domain, nameservers) {
41907
+ const config = getConfig();
41908
+ const success = await updateNameservers(domain, nameservers, config);
41909
+ return { domain, success };
41910
+ },
41911
+ async renewDomain(domain, years = 1) {
41912
+ const config = getConfig();
41913
+ const result = await renewDomain(domain, years, config);
40953
41914
  return {
40954
41915
  domain: result.domain,
40955
41916
  success: result.success,
@@ -41085,7 +42046,7 @@ var providerRegistry = new Map([
41085
42046
  name: "namecheap",
41086
42047
  type: "full",
41087
42048
  configured: false,
41088
- envVars: ["NAMECHEAP_API_KEY", "NAMECHEAP_USERNAME", "NAMECHEAP_CLIENT_IP"]
42049
+ envVars: providerEnvNames("namecheap")
41089
42050
  },
41090
42051
  createRegistrar: createNamecheapProvider,
41091
42052
  createDns: createNamecheapProvider
@@ -41095,7 +42056,7 @@ var providerRegistry = new Map([
41095
42056
  name: "godaddy",
41096
42057
  type: "full",
41097
42058
  configured: false,
41098
- envVars: ["GODADDY_API_KEY", "GODADDY_API_SECRET"]
42059
+ envVars: providerEnvNames("godaddy")
41099
42060
  },
41100
42061
  createRegistrar: createGoDaddyProvider,
41101
42062
  createDns: createGoDaddyProvider
@@ -41105,7 +42066,7 @@ var providerRegistry = new Map([
41105
42066
  name: "route53",
41106
42067
  type: "full",
41107
42068
  configured: false,
41108
- envVars: ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_REGION"]
42069
+ envVars: providerEnvNames("route53")
41109
42070
  },
41110
42071
  createRegistrar: () => createRoute53Provider(),
41111
42072
  createDns: () => createRoute53Provider()
@@ -41115,7 +42076,7 @@ var providerRegistry = new Map([
41115
42076
  name: "cloudflare",
41116
42077
  type: "dns",
41117
42078
  configured: false,
41118
- envVars: ["CLOUDFLARE_API_TOKEN", "CLOUDFLARE_ACCOUNT_ID"]
42079
+ envVars: providerEnvNames("cloudflare")
41119
42080
  },
41120
42081
  createDns: createCloudflareProvider
41121
42082
  }],
@@ -41124,34 +42085,51 @@ var providerRegistry = new Map([
41124
42085
  name: "brandsight",
41125
42086
  type: "registrar",
41126
42087
  configured: false,
41127
- envVars: ["BRANDSIGHT_API_KEY", "BRANDSIGHT_ACCOUNT_ID"]
42088
+ envVars: providerEnvNames("brandsight")
41128
42089
  },
41129
42090
  createRegistrar: createBrandsightProvider
42091
+ }],
42092
+ ["sedo", {
42093
+ info: {
42094
+ name: "sedo",
42095
+ type: "marketplace",
42096
+ configured: false,
42097
+ envVars: providerEnvNames("sedo")
42098
+ }
41130
42099
  }]
41131
42100
  ]);
41132
- function isConfigured(envVars) {
41133
- return envVars.slice(0, 2).every((v3) => !!process.env[v3]);
42101
+ function isConfigured(providerName) {
42102
+ return hasProviderCredentials(providerName);
41134
42103
  }
41135
42104
  function getAvailableProviders() {
41136
42105
  return Array.from(providerRegistry.values()).map((e3) => ({
41137
42106
  ...e3.info,
41138
- configured: isConfigured(e3.info.envVars)
42107
+ configured: isConfigured(e3.info.name)
41139
42108
  }));
41140
42109
  }
42110
+ function getProviderInfo(name) {
42111
+ const entry = providerRegistry.get(name.toLowerCase());
42112
+ if (!entry)
42113
+ return null;
42114
+ return { ...entry.info, configured: isConfigured(entry.info.name) };
42115
+ }
42116
+ function providerHasRegistrar(name) {
42117
+ return !!providerRegistry.get(name.toLowerCase())?.createRegistrar;
42118
+ }
41141
42119
  function getRegistrarProvider(name) {
41142
- const entry = providerRegistry.get(name);
42120
+ const entry = providerRegistry.get(name.toLowerCase());
41143
42121
  if (!entry?.createRegistrar)
41144
42122
  throw new Error(`No registrar provider: ${name}`);
41145
42123
  return entry.createRegistrar();
41146
42124
  }
41147
42125
  function getDnsProvider(name) {
41148
- const entry = providerRegistry.get(name);
42126
+ const entry = providerRegistry.get(name.toLowerCase());
41149
42127
  if (!entry?.createDns)
41150
42128
  throw new Error(`No DNS provider: ${name}`);
41151
42129
  return entry.createDns();
41152
42130
  }
41153
42131
  async function syncAll(dbFns) {
41154
- const available = getAvailableProviders().filter((p3) => p3.configured && (p3.type === "registrar" || p3.type === "full") && p3.name !== "brandsight");
42132
+ const available = getAvailableProviders().filter((p3) => p3.configured && providerHasRegistrar(p3.name));
41155
42133
  const result = { providers: [], totalSynced: 0, totalErrors: [] };
41156
42134
  for (const info of available) {
41157
42135
  try {
@@ -41520,7 +42498,7 @@ WHOIS for ${result.domain} [${result.source}]:`);
41520
42498
  process.exit(1);
41521
42499
  });
41522
42500
  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);
42501
+ const providers = opts.provider ? [opts.provider] : getAvailableProviders().filter((p3) => p3.configured && (p3.type === "registrar" || p3.type === "full")).map((p3) => p3.name);
41524
42502
  for (const name of providers) {
41525
42503
  try {
41526
42504
  const provider = getRegistrarProvider(name);
@@ -41607,14 +42585,14 @@ WHOIS for ${result.domain} [${result.source}]:`);
41607
42585
  }
41608
42586
  console.log(`Linked ${emailId} to ${details.domain.name}`);
41609
42587
  });
41610
- domain.command("renew <name>").description("Renew a domain via its registrar provider").option("--provider <name>", "Override provider").action(async (name, opts) => {
42588
+ 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
42589
  const providerName = opts.provider ?? autoDetectRegistrar(name, getDomainByName) ?? loadConfig3().default_registrar;
41612
42590
  if (!providerName) {
41613
42591
  console.error("Could not detect provider. Use --provider.");
41614
42592
  process.exit(1);
41615
42593
  }
41616
42594
  const provider = getRegistrarProvider(providerName);
41617
- const result = await provider.renewDomain(name);
42595
+ const result = await provider.renewDomain(name, parseInt(opts.years, 10));
41618
42596
  if (result.success) {
41619
42597
  console.log(`\u2713 Renewed: ${name}`);
41620
42598
  if (result.orderId)
@@ -41624,7 +42602,7 @@ WHOIS for ${result.domain} [${result.source}]:`);
41624
42602
  process.exit(1);
41625
42603
  }
41626
42604
  });
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) => {
42605
+ 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
42606
  const recordedPrice = parseOptionalNumber(opts.price, "--price");
41629
42607
  if (recordedPrice !== undefined) {
41630
42608
  const registrarName = opts.registrar ?? opts.provider ?? "manual";
@@ -41648,12 +42626,75 @@ WHOIS for ${result.domain} [${result.source}]:`);
41648
42626
  }
41649
42627
  const cfg = loadConfig3();
41650
42628
  const providerName = opts.registrar ?? opts.provider ?? cfg.default_registrar ?? "route53";
41651
- const purchaseProfile = applyPurchaseProfile();
42629
+ const purchaseProfile = providerName === "route53" ? applyPurchaseProfile() : undefined;
41652
42630
  if (purchaseProfile)
41653
42631
  console.log(`Using purchase AWS profile: ${purchaseProfile}`);
41654
42632
  if (providerName !== "route53") {
41655
- console.error(`Direct domain purchase only supported via route53. Got: ${providerName}`);
41656
- process.exit(1);
42633
+ try {
42634
+ const provider = getRegistrarProvider(providerName);
42635
+ if (!provider.registerDomain) {
42636
+ console.error(`Direct domain purchase is not supported for ${providerName}. Use route53 or record a marketplace/manual purchase with --price.`);
42637
+ process.exit(1);
42638
+ }
42639
+ const avail = await provider.checkAvailability(name);
42640
+ if (!avail.available) {
42641
+ console.error(`\u2717 ${name} is not available`);
42642
+ process.exit(1);
42643
+ }
42644
+ console.log(`\u2713 Available via ${providerName}`);
42645
+ let contact;
42646
+ try {
42647
+ contact = resolveContact(opts);
42648
+ } catch (e3) {
42649
+ console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
42650
+ process.exit(1);
42651
+ }
42652
+ console.log(`Registering ${name} via ${providerName}...`);
42653
+ const reg = await provider.registerDomain(name, contact, {
42654
+ years: parseInt(opts.years, 10),
42655
+ premiumPrice: avail.premium_price,
42656
+ autoRenew: opts.autoRenew ? opts.autoRenew === "true" : true
42657
+ });
42658
+ if (!reg.success) {
42659
+ console.error(`\u2717 Registration failed via ${providerName}`);
42660
+ process.exit(1);
42661
+ }
42662
+ const existing = getDomainByName(name);
42663
+ const dbInput = {
42664
+ registrar: providerName,
42665
+ status: "active",
42666
+ auto_renew: opts.autoRenew ? opts.autoRenew === "true" : true,
42667
+ purchase_price: reg.chargedAmount ? Number(reg.chargedAmount) : avail.standard_price,
42668
+ purchase_date: new Date().toISOString(),
42669
+ standard_price: avail.standard_price,
42670
+ is_premium: avail.is_premium,
42671
+ premium_price: avail.premium_price
42672
+ };
42673
+ if (existing)
42674
+ updateDomain(existing.id, dbInput);
42675
+ else
42676
+ createDomain({ name, ...dbInput });
42677
+ console.log(`\u2713 Registered and added to portfolio`);
42678
+ if (reg.orderId)
42679
+ console.log(` Order: ${reg.orderId}`);
42680
+ const dnsProvider = opts.dns ?? "cloudflare";
42681
+ if (opts.delegate !== false && dnsProvider === "cloudflare") {
42682
+ if (!provider.updateNameservers) {
42683
+ console.log(` DNS: ${providerName} registration succeeded, but nameserver updates are not implemented for this provider.`);
42684
+ } else {
42685
+ const zone = await ensureZone(name);
42686
+ const nsUpdate = await provider.updateNameservers(name, zone.nameservers);
42687
+ const existing2 = getDomainByName(name);
42688
+ if (existing2)
42689
+ updateDomain(existing2.id, { nameservers: zone.nameservers });
42690
+ console.log(`\u2713 Cloudflare zone ${zone.id}; nameservers updated${nsUpdate.operationId ? ` (op ${nsUpdate.operationId})` : ""}`);
42691
+ }
42692
+ }
42693
+ return;
42694
+ } catch (e3) {
42695
+ console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
42696
+ process.exit(1);
42697
+ }
41657
42698
  }
41658
42699
  try {
41659
42700
  const avail = await checkAvailability3(name);
@@ -41671,7 +42712,7 @@ WHOIS for ${result.domain} [${result.source}]:`);
41671
42712
  process.exit(1);
41672
42713
  }
41673
42714
  console.log(`Registering ${name}...`);
41674
- const reg = await registerDomain(name, contact, parseInt(opts.years));
42715
+ const reg = await registerDomain2(name, contact, parseInt(opts.years));
41675
42716
  console.log(`\u2713 Submitted (operation: ${reg.operationId})`);
41676
42717
  if (opts.wait) {
41677
42718
  let status = "IN_PROGRESS";
@@ -41719,10 +42760,10 @@ WHOIS for ${result.domain} [${result.source}]:`);
41719
42760
  console.log(`Delegating DNS to Cloudflare...`);
41720
42761
  const del = await delegateDomainToCloudflare(name, {
41721
42762
  createCloudflareZone: async (d3) => {
41722
- const z2 = await createZone(d3);
42763
+ const z2 = await ensureZone(d3);
41723
42764
  return { id: z2.id, nameservers: z2.nameservers };
41724
42765
  },
41725
- updateNameservers: (d3, ns) => updateNameservers(d3, ns)
42766
+ updateNameservers: (d3, ns) => updateNameservers2(d3, ns)
41726
42767
  });
41727
42768
  const existing2 = getDomainByName(name);
41728
42769
  if (existing2)
@@ -41741,16 +42782,16 @@ WHOIS for ${result.domain} [${result.source}]:`);
41741
42782
  process.exit(1);
41742
42783
  }
41743
42784
  });
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) => {
42785
+ 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
42786
  const cfg = loadConfig3();
41746
42787
  const registrarName = opts.registrar ?? cfg.default_registrar ?? "route53";
41747
- const dnsName = opts.dns ?? cfg.default_dns ?? registrarName;
42788
+ const dnsName = opts.dns ?? cfg.default_dns ?? "cloudflare";
41748
42789
  console.log(`
41749
42790
  Setting up ${name}`);
41750
42791
  console.log(` Registrar: ${registrarName} | DNS: ${dnsName}
41751
42792
  `);
41752
42793
  try {
41753
- process.stdout.write("[1/4] Checking availability... ");
42794
+ process.stdout.write(opts.wait ? "[1/5] Checking availability... " : "[1/4] Checking availability... ");
41754
42795
  const avail = await checkAvailability3(name);
41755
42796
  if (!avail.available) {
41756
42797
  console.log("not available");
@@ -41770,8 +42811,8 @@ Setting up ${name}`);
41770
42811
  console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
41771
42812
  process.exit(1);
41772
42813
  }
41773
- process.stdout.write("[2/4] Registering domain... ");
41774
- const reg = await registerDomain(name, contact, parseInt(opts.years));
42814
+ process.stdout.write(opts.wait ? "[2/5] Registering domain... " : "[2/4] Registering domain... ");
42815
+ const reg = await registerDomain2(name, contact, parseInt(opts.years));
41775
42816
  console.log(`submitted (${reg.operationId})`);
41776
42817
  if (opts.wait) {
41777
42818
  let status = "IN_PROGRESS";
@@ -41788,19 +42829,38 @@ Setting up ${name}`);
41788
42829
  }
41789
42830
  console.log(" Registration confirmed");
41790
42831
  }
41791
- process.stdout.write("[3/4] Creating DNS zone... ");
42832
+ process.stdout.write(opts.wait ? "[3/5] Creating DNS zone... " : "[3/4] Creating DNS zone... ");
41792
42833
  let nameservers = [];
41793
42834
  if (dnsName === "cloudflare") {
41794
- const zone = await createZone(name);
42835
+ const zone = await ensureZone(name);
41795
42836
  nameservers = zone.nameservers ?? [];
41796
- console.log(`created (${zone.id})`);
42837
+ console.log("ready (" + zone.id + ")");
41797
42838
  } else {
41798
42839
  const zone = await createHostedZone(name, `Managed by @hasna/domains`);
41799
42840
  nameservers = zone.name_servers ?? [];
41800
42841
  console.log(`created (${zone.id})`);
41801
42842
  }
41802
- process.stdout.write("[4/4] Adding to portfolio... ");
41803
- createDomain({ name, registrar: `AWS Route 53`, status: "active", auto_renew: true, nameservers });
42843
+ if (nameservers.length > 0) {
42844
+ if (opts.wait) {
42845
+ process.stdout.write("[4/5] Updating registrar nameservers... ");
42846
+ if (registrarName !== "route53") {
42847
+ console.log("skipped");
42848
+ console.error("Only Route53 nameserver delegation is currently implemented for setup.");
42849
+ process.exit(1);
42850
+ }
42851
+ const nsUpdate = await updateNameservers2(name, nameservers);
42852
+ console.log("submitted (" + nsUpdate.operationId + ")");
42853
+ } else {
42854
+ console.log(" Nameserver delegation skipped until registration completes; re-run with --wait or use domains r53 domain-info/status first.");
42855
+ }
42856
+ }
42857
+ process.stdout.write(opts.wait ? "[5/5] Adding to portfolio... " : "[4/4] Adding to portfolio... ");
42858
+ const existing = getDomainByName(name);
42859
+ const dbInput = { registrar: `AWS Route 53`, status: "active", auto_renew: true, nameservers };
42860
+ if (existing)
42861
+ updateDomain(existing.id, dbInput);
42862
+ else
42863
+ createDomain({ name, ...dbInput });
41804
42864
  console.log("done");
41805
42865
  console.log(`
41806
42866
  \u2713 Setup complete for ${name}`);
@@ -42315,22 +43375,23 @@ Registrar / DNS Providers:`);
42315
43375
  console.log();
42316
43376
  });
42317
43377
  provider.command("test <name>").description("Live-test a provider's credentials").option("-j, --json", "Output JSON").action(async (name, opts) => {
43378
+ const providerName = name.toLowerCase();
42318
43379
  const providers = getAvailableProviders();
42319
- const info = providers.find((p3) => p3.name === name);
43380
+ const info = providers.find((p3) => p3.name === providerName);
42320
43381
  if (!info) {
42321
43382
  const error = `Unknown provider: ${name}`;
42322
43383
  if (opts.json) {
42323
- console.log(JSON.stringify({ provider: name, ok: false, error }, null, 2));
43384
+ console.log(JSON.stringify({ provider: providerName, ok: false, error }, null, 2));
42324
43385
  } else {
42325
43386
  console.error(error);
42326
43387
  }
42327
43388
  process.exit(1);
42328
43389
  }
42329
43390
  if (!info.configured) {
42330
- const error = `${name} is not configured. Set: ${info.envVars.join(", ")}`;
43391
+ const error = `${providerName} is not configured. Set: ${info.envVars.join(", ")}`;
42331
43392
  if (opts.json) {
42332
43393
  console.log(JSON.stringify({
42333
- provider: name,
43394
+ provider: providerName,
42334
43395
  ok: false,
42335
43396
  configured: false,
42336
43397
  required_env: info.envVars,
@@ -42342,33 +43403,44 @@ Registrar / DNS Providers:`);
42342
43403
  process.exit(1);
42343
43404
  }
42344
43405
  if (!opts.json) {
42345
- console.log(`Testing ${name}...`);
43406
+ console.log(`Testing ${providerName}...`);
42346
43407
  }
42347
43408
  let registrarOk = null;
42348
43409
  let dnsOk = null;
43410
+ let marketplaceOk = null;
42349
43411
  try {
42350
43412
  if (info.type === "registrar" || info.type === "full") {
42351
- const reg = getRegistrarProvider(name);
43413
+ const reg = getRegistrarProvider(providerName);
42352
43414
  await reg.listDomains();
42353
43415
  registrarOk = true;
42354
43416
  if (!opts.json)
42355
43417
  console.log("\u2713 Registrar connection OK");
42356
43418
  }
42357
43419
  if (info.type === "dns" || info.type === "full") {
42358
- const dns = getDnsProvider(name);
43420
+ const dns = getDnsProvider(providerName);
42359
43421
  await dns.getDnsRecords("__test_nonexistent_domain__.invalid");
42360
43422
  dnsOk = true;
42361
43423
  if (!opts.json)
42362
43424
  console.log("\u2713 DNS connection OK");
42363
43425
  }
43426
+ if (info.type === "marketplace") {
43427
+ if (providerName !== "sedo")
43428
+ throw new Error("No marketplace tester for provider: " + providerName);
43429
+ const { listSedoPortfolio: listSedoPortfolio2 } = await Promise.resolve().then(() => (init_sedo(), exports_sedo));
43430
+ await listSedoPortfolio2({ limit: 1 });
43431
+ marketplaceOk = true;
43432
+ if (!opts.json)
43433
+ console.log("\u2713 Marketplace connection OK");
43434
+ }
42364
43435
  if (opts.json) {
42365
43436
  console.log(JSON.stringify({
42366
- provider: name,
43437
+ provider: providerName,
42367
43438
  ok: true,
42368
43439
  configured: true,
42369
43440
  type: info.type,
42370
43441
  registrar_ok: registrarOk,
42371
- dns_ok: dnsOk
43442
+ dns_ok: dnsOk,
43443
+ marketplace_ok: marketplaceOk
42372
43444
  }, null, 2));
42373
43445
  }
42374
43446
  } catch (e3) {
@@ -42376,12 +43448,13 @@ Registrar / DNS Providers:`);
42376
43448
  if (msg.includes("not found") || msg.includes("No hosted zone") || msg.includes("NXDOMAIN")) {
42377
43449
  if (opts.json) {
42378
43450
  console.log(JSON.stringify({
42379
- provider: name,
43451
+ provider: providerName,
42380
43452
  ok: true,
42381
43453
  configured: true,
42382
43454
  type: info.type,
42383
43455
  registrar_ok: registrarOk,
42384
43456
  dns_ok: true,
43457
+ marketplace_ok: marketplaceOk,
42385
43458
  note: "connection-ok-no-domains-yet"
42386
43459
  }, null, 2));
42387
43460
  } else {
@@ -42391,16 +43464,17 @@ Registrar / DNS Providers:`);
42391
43464
  }
42392
43465
  if (opts.json) {
42393
43466
  console.log(JSON.stringify({
42394
- provider: name,
43467
+ provider: providerName,
42395
43468
  ok: false,
42396
43469
  configured: true,
42397
43470
  type: info.type,
42398
43471
  registrar_ok: registrarOk,
42399
43472
  dns_ok: dnsOk,
43473
+ marketplace_ok: marketplaceOk,
42400
43474
  error: msg
42401
43475
  }, null, 2));
42402
43476
  } else {
42403
- console.error(`\u2717 ${name} test failed: ${msg}`);
43477
+ console.error(`\u2717 ${providerName} test failed: ${msg}`);
42404
43478
  }
42405
43479
  process.exit(1);
42406
43480
  }
@@ -42410,23 +43484,35 @@ Registrar / DNS Providers:`);
42410
43484
  // src/cli/commands/providers.ts
42411
43485
  init_config();
42412
43486
  init_domains();
43487
+ function registrarProviderNames() {
43488
+ return getAvailableProviders().filter((p3) => providerHasRegistrar(p3.name)).map((p3) => p3.name).join(", ");
43489
+ }
43490
+ function requireRegistrarProvider(name) {
43491
+ const info = getProviderInfo(name);
43492
+ if (!info) {
43493
+ throw new Error(`Unknown provider: ${name}. Supported registrars: ${registrarProviderNames()}`);
43494
+ }
43495
+ if (!providerHasRegistrar(name)) {
43496
+ throw new Error(`${name} is a ${info.type} provider, not a registrar. Supported registrars: ${registrarProviderNames()}`);
43497
+ }
43498
+ }
42413
43499
  function registerProviderCommands(program2) {
42414
- program2.command("providers").description("Show which registrar providers are configured").option("--json", "Output as JSON", false).action((opts) => {
43500
+ program2.command("providers").description("Show which providers are configured").option("--json", "Output as JSON", false).action((opts) => {
42415
43501
  const providers = getAvailableProviders();
42416
43502
  if (opts.json) {
42417
43503
  console.log(JSON.stringify(providers, null, 2));
42418
43504
  } else {
42419
- console.log("Registrar Providers:");
43505
+ console.log("Providers:");
42420
43506
  for (const p3 of providers) {
42421
43507
  const status = p3.configured ? "CONFIGURED" : "not configured";
42422
- console.log(` ${p3.name}: ${status}`);
43508
+ console.log(` ${p3.name} [${p3.type}]: ${status}`);
42423
43509
  if (!p3.configured) {
42424
- console.log(` Missing: ${p3.envVars.join(", ")}`);
43510
+ console.log(` Accepted env: ${p3.envVars.join(", ")}`);
42425
43511
  }
42426
43512
  }
42427
43513
  }
42428
43514
  });
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) => {
43515
+ program2.command("sync").description("Sync domains from a registrar provider to local DB").option("--provider <provider>", "Provider name").option("--all", "Sync from all configured registrar providers").option("--json", "Output as JSON", false).action(async (opts) => {
42430
43516
  if (opts.all) {
42431
43517
  try {
42432
43518
  const result = await syncAll({
@@ -42439,7 +43525,7 @@ function registerProviderCommands(program2) {
42439
43525
  } else {
42440
43526
  console.log(`Synced ${result.totalSynced} domain(s) from ${result.providers.length} provider(s)`);
42441
43527
  for (const p3 of result.providers) {
42442
- console.log(` ${p3.name}: ${p3.result.synced} synced`);
43528
+ console.log(` ${p3.name}: ${p3.result.synced} synced (${p3.result.created} new, ${p3.result.updated} updated)`);
42443
43529
  }
42444
43530
  if (result.totalErrors.length > 0) {
42445
43531
  console.log("Errors:");
@@ -42459,59 +43545,29 @@ function registerProviderCommands(program2) {
42459
43545
  console.error("Specify --provider <name> or --all");
42460
43546
  process.exit(1);
42461
43547
  }
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
- }
43548
+ try {
43549
+ requireRegistrarProvider(provider);
43550
+ const result = await getRegistrarProvider(provider).syncToLocalDb({
43551
+ getDomainByName,
43552
+ createDomain,
43553
+ updateDomain
43554
+ });
43555
+ if (opts.json) {
43556
+ console.log(JSON.stringify({ provider, ...result }, null, 2));
43557
+ } else {
43558
+ console.log(`Synced ${result.synced} domain(s) from ${provider} (${result.created} new, ${result.updated} updated)`);
43559
+ if (result.errors.length > 0) {
43560
+ console.log("Errors:");
43561
+ for (const e3 of result.errors)
43562
+ console.log(` - ${e3}`);
42504
43563
  }
42505
- } catch (error) {
42506
- console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
42507
- process.exit(1);
42508
43564
  }
42509
- } else {
42510
- console.error(`Unsupported provider: ${provider}. Supported: namecheap, godaddy`);
43565
+ } catch (error) {
43566
+ console.error(`Sync failed: ${error instanceof Error ? error.message : String(error)}`);
42511
43567
  process.exit(1);
42512
43568
  }
42513
43569
  });
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) => {
43570
+ 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
43571
  let provider = (opts.provider || "").toLowerCase();
42516
43572
  if (!provider) {
42517
43573
  const detected = autoDetectRegistrar(name, getDomainByName);
@@ -42520,53 +43576,44 @@ function registerProviderCommands(program2) {
42520
43576
  process.exit(1);
42521
43577
  }
42522
43578
  provider = detected;
42523
- console.log(`Auto-detected registrar: ${provider}`);
43579
+ if (!opts.json)
43580
+ console.log(`Auto-detected registrar: ${provider}`);
42524
43581
  }
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);
43582
+ try {
43583
+ requireRegistrarProvider(provider);
43584
+ const result = await getRegistrarProvider(provider).renewDomain(name, parseInt(opts.years, 10));
43585
+ if (!result.success) {
43586
+ throw new Error(`Renewal failed or is not supported for ${provider}`);
42540
43587
  }
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);
43588
+ if (opts.json) {
43589
+ console.log(JSON.stringify({ provider, ...result }, null, 2));
43590
+ } else {
43591
+ console.log(`Renewed ${result.domain} successfully via ${provider}`);
43592
+ if (result.chargedAmount)
43593
+ console.log(` Charged: $${result.chargedAmount}`);
43594
+ if (result.orderId)
43595
+ console.log(` Order ID: ${result.orderId}`);
42556
43596
  }
42557
- } else {
42558
- console.error(`Unsupported provider: ${provider}. Supported: namecheap, godaddy`);
43597
+ } catch (error) {
43598
+ console.error(`Renewal failed: ${error instanceof Error ? error.message : String(error)}`);
42559
43599
  process.exit(1);
42560
43600
  }
42561
43601
  });
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) => {
43602
+ 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
43603
  try {
42564
- const providerName = opts.provider ?? loadConfig3().default_registrar ?? "namecheap";
42565
- const result = providerName === "namecheap" ? await checkAvailability(name) : await getRegistrarProvider(providerName).checkAvailability(name);
43604
+ const providerName = (opts.provider ?? loadConfig3().default_registrar ?? "route53").toLowerCase();
43605
+ requireRegistrarProvider(providerName);
43606
+ const result = await getRegistrarProvider(providerName).checkAvailability(name);
42566
43607
  if (opts.json) {
42567
43608
  console.log(JSON.stringify({ provider: providerName, ...result }, null, 2));
42568
43609
  } else {
42569
43610
  console.log(`${result.domain} is ${result.available ? "AVAILABLE" : "NOT available"} (via ${providerName})`);
43611
+ if (result.is_premium)
43612
+ console.log(` Premium ask: ${result.premium_price ?? "unknown"}`);
43613
+ if (result.standard_price !== undefined) {
43614
+ const currency = result.currency ? ` ${result.currency}` : "";
43615
+ console.log(` Standard price: ${result.standard_price}${currency}`);
43616
+ }
42570
43617
  }
42571
43618
  } catch (error) {
42572
43619
  console.error(`Availability check failed: ${error instanceof Error ? error.message : String(error)}`);
@@ -42580,6 +43627,7 @@ init_config();
42580
43627
  var VALID_KEYS = [
42581
43628
  "default-registrar",
42582
43629
  "default-dns",
43630
+ "purchase-aws-profile",
42583
43631
  "contact.first-name",
42584
43632
  "contact.last-name",
42585
43633
  "contact.email",
@@ -42601,8 +43649,9 @@ function registerConfigCommands(program2) {
42601
43649
  }
42602
43650
  console.log(`
42603
43651
  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)"}`);
43652
+ console.log(` default-registrar: ${cfg.default_registrar ?? "(not set)"}`);
43653
+ console.log(` default-dns: ${cfg.default_dns ?? "(not set)"}`);
43654
+ console.log(` purchase-aws-profile: ${cfg.purchase_aws_profile ?? "(not set)"}`);
42606
43655
  if (cfg.contact && Object.keys(cfg.contact).length > 0) {
42607
43656
  console.log(`
42608
43657
  contact:`);
@@ -42703,7 +43752,7 @@ function registerDoctorCommand(program2) {
42703
43752
  fail(`${c3.provider}: not configured (${c3.detail})`);
42704
43753
  }
42705
43754
  section("Providers");
42706
- const providers = getAvailableProviders().filter((p3) => p3.name !== "brandsight");
43755
+ const providers = getAvailableProviders().filter((p3) => p3.type !== "marketplace");
42707
43756
  for (const p3 of providers) {
42708
43757
  if (!p3.configured) {
42709
43758
  fail(`${p3.name}: not configured`, `Set: ${p3.envVars.join(", ")}`);
@@ -42753,15 +43802,15 @@ ${"\u2500".repeat(45)}`);
42753
43802
  }
42754
43803
 
42755
43804
  // 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";
43805
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
43806
+ import { homedir as homedir5 } from "os";
43807
+ import { dirname as dirname4, join as join5 } from "path";
42759
43808
  import { execSync as execSync3 } from "child_process";
42760
43809
  var MCP_SERVER_NAME = "domains";
42761
43810
  function getClaudeConfigPaths() {
42762
43811
  return {
42763
- global: join4(homedir4(), ".claude", "claude_desktop_config.json"),
42764
- project: join4(process.cwd(), ".claude", "settings.json")
43812
+ global: join5(homedir5(), ".claude", "claude_desktop_config.json"),
43813
+ project: join5(process.cwd(), ".claude", "settings.json")
42765
43814
  };
42766
43815
  }
42767
43816
  function getMcpBinaryPath() {
@@ -42773,12 +43822,12 @@ function getMcpBinaryPath() {
42773
43822
  }
42774
43823
  function ensureConfigDir(configPath2) {
42775
43824
  const dir = dirname4(configPath2);
42776
- if (!existsSync3(dir)) {
43825
+ if (!existsSync4(dir)) {
42777
43826
  mkdirSync3(dir, { recursive: true });
42778
43827
  }
42779
43828
  }
42780
43829
  function readConfig(configPath2) {
42781
- if (!existsSync3(configPath2))
43830
+ if (!existsSync4(configPath2))
42782
43831
  return {};
42783
43832
  try {
42784
43833
  return JSON.parse(readFileSync5(configPath2, "utf-8"));
@@ -42807,7 +43856,7 @@ function registerMcpCommand(program2) {
42807
43856
  mcp.command("uninstall").description("Remove domains MCP server from Claude Code config").option("--project", "Remove from project config instead of global").action((opts) => {
42808
43857
  const paths = getClaudeConfigPaths();
42809
43858
  const configPath2 = opts.project ? paths.project : paths.global;
42810
- if (!existsSync3(configPath2)) {
43859
+ if (!existsSync4(configPath2)) {
42811
43860
  console.log("Config file not found \u2014 nothing to remove.");
42812
43861
  return;
42813
43862
  }
@@ -42825,7 +43874,7 @@ function registerMcpCommand(program2) {
42825
43874
  const paths = getClaudeConfigPaths();
42826
43875
  const status = [];
42827
43876
  for (const [scope, configPath2] of [["global", paths.global], ["project", paths.project]]) {
42828
- if (!existsSync3(configPath2)) {
43877
+ if (!existsSync4(configPath2)) {
42829
43878
  status.push({ scope, config_path: configPath2, exists: false, registered: false });
42830
43879
  continue;
42831
43880
  }
@@ -43049,7 +44098,7 @@ function registerRoute53Commands(program2) {
43049
44098
  process.exit(1);
43050
44099
  }
43051
44100
  console.log(`Registering ${domain}...`);
43052
- const result = await registerDomain(domain, contact, parseInt(opts.years), opts.autoRenew);
44101
+ const result = await registerDomain2(domain, contact, parseInt(opts.years), opts.autoRenew);
43053
44102
  console.log(`\u2713 Registration submitted: ${domain}`);
43054
44103
  console.log(` Operation ID: ${result.operationId}`);
43055
44104
  console.log(` Check status: domains r53 status ${result.operationId}`);
@@ -43346,7 +44395,7 @@ DNS Records for ${domain}:`);
43346
44395
  console.error(`Error: ${e3 instanceof Error ? e3.message : String(e3)}`);
43347
44396
  process.exit(1);
43348
44397
  }
43349
- const reg = await registerDomain(domain, contact, parseInt(opts.years));
44398
+ const reg = await registerDomain2(domain, contact, parseInt(opts.years));
43350
44399
  console.log(` \u2713 Submitted (operation: ${reg.operationId})`);
43351
44400
  console.log(` Waiting for registration to complete...`);
43352
44401
  let status = "IN_PROGRESS";
@@ -43377,7 +44426,7 @@ DNS Records for ${domain}:`);
43377
44426
  },
43378
44427
  getRegistrarNs: async (d3) => (await getDomainDetail(d3)).nameservers,
43379
44428
  setRegistrarNs: async (d3, ns) => {
43380
- await updateNameservers(d3, ns);
44429
+ await updateNameservers2(d3, ns);
43381
44430
  }
43382
44431
  });
43383
44432
  console.log(` \u2713 Zone ${setup.created ? "created" : "reused"} (${setup.zoneId})` + (setup.nsUpdated ? ` \u2014 registry NS repointed to this zone` : ``));
@@ -45554,10 +46603,10 @@ function registerInteractiveCommand(program2) {
45554
46603
  });
45555
46604
  }
45556
46605
 
45557
- // src/cli/commands/cloud.ts
46606
+ // src/cli/commands/storage.ts
45558
46607
  import chalk2 from "chalk";
45559
46608
 
45560
- // src/db/cloud-sync.ts
46609
+ // src/db/storage-sync.ts
45561
46610
  init_database();
45562
46611
 
45563
46612
  // src/db/pg-migrations.ts
@@ -45734,8 +46783,8 @@ class PgAdapterAsync {
45734
46783
  }
45735
46784
  }
45736
46785
 
45737
- // src/db/cloud-sync.ts
45738
- var CLOUD_TABLES = [
46786
+ // src/db/storage-sync.ts
46787
+ var STORAGE_TABLES = [
45739
46788
  "domains",
45740
46789
  "dns_records",
45741
46790
  "alerts",
@@ -45755,25 +46804,50 @@ var PRIMARY_KEYS = {
45755
46804
  domain_history: ["id"],
45756
46805
  domain_reputation: ["id"]
45757
46806
  };
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;
46807
+ var DOMAINS_STORAGE_ENV = "HASNA_DOMAINS_DATABASE_URL";
46808
+ var DOMAINS_STORAGE_FALLBACK_ENV = "DOMAINS_DATABASE_URL";
46809
+ var DOMAINS_STORAGE_MODE_ENV = "HASNA_DOMAINS_STORAGE_MODE";
46810
+ var DOMAINS_STORAGE_MODE_FALLBACK_ENV = "DOMAINS_STORAGE_MODE";
46811
+ var STORAGE_DATABASE_ENV = [DOMAINS_STORAGE_ENV, DOMAINS_STORAGE_FALLBACK_ENV];
46812
+ var STORAGE_MODE_ENV = [DOMAINS_STORAGE_MODE_ENV, DOMAINS_STORAGE_MODE_FALLBACK_ENV];
46813
+ function firstEnv2(names) {
46814
+ for (const name of names) {
46815
+ const value = process.env[name];
46816
+ if (value)
46817
+ return value;
46818
+ }
46819
+ return null;
46820
+ }
46821
+ function normalizeStorageMode(value) {
46822
+ if (!value)
46823
+ return null;
46824
+ const normalized = value.trim().toLowerCase();
46825
+ if (normalized === "local" || normalized === "remote" || normalized === "hybrid")
46826
+ return normalized;
46827
+ return null;
45760
46828
  }
45761
- async function getCloudPg() {
45762
- const url = getCloudDatabaseUrl();
46829
+ function getStorageDatabaseUrl() {
46830
+ return firstEnv2(STORAGE_DATABASE_ENV);
46831
+ }
46832
+ function getStorageMode() {
46833
+ return normalizeStorageMode(firstEnv2(STORAGE_MODE_ENV)) ?? (getStorageDatabaseUrl() ? "remote" : "local");
46834
+ }
46835
+ async function getStoragePg() {
46836
+ const url = getStorageDatabaseUrl();
45763
46837
  if (!url) {
45764
- throw new Error("Missing HASNA_DOMAINS_CLOUD_DATABASE_URL, OPEN_DOMAINS_CLOUD_DATABASE_URL, or DOMAINS_CLOUD_DATABASE_URL");
46838
+ throw new Error("Missing HASNA_DOMAINS_DATABASE_URL or DOMAINS_DATABASE_URL");
45765
46839
  }
45766
46840
  return new PgAdapterAsync(url);
45767
46841
  }
45768
- async function runCloudMigrations(remote) {
46842
+ async function runStorageMigrations(remote) {
45769
46843
  for (const sql of PG_MIGRATIONS)
45770
46844
  await remote.run(sql);
45771
46845
  }
45772
- async function cloudPush(options) {
45773
- const remote = await getCloudPg();
46846
+ async function storagePush(options) {
46847
+ const remote = await getStoragePg();
45774
46848
  const db = getDatabase();
45775
46849
  try {
45776
- await runCloudMigrations(remote);
46850
+ await runStorageMigrations(remote);
45777
46851
  const results = [];
45778
46852
  for (const table of resolveTables(options?.tables))
45779
46853
  results.push(await pushTable(db, remote, table));
@@ -45783,11 +46857,11 @@ async function cloudPush(options) {
45783
46857
  await remote.close();
45784
46858
  }
45785
46859
  }
45786
- async function cloudPull(options) {
45787
- const remote = await getCloudPg();
46860
+ async function storagePull(options) {
46861
+ const remote = await getStoragePg();
45788
46862
  const db = getDatabase();
45789
46863
  try {
45790
- await runCloudMigrations(remote);
46864
+ await runStorageMigrations(remote);
45791
46865
  const results = [];
45792
46866
  for (const table of resolveTables(options?.tables))
45793
46867
  results.push(await pullTable(remote, db, table));
@@ -45797,20 +46871,30 @@ async function cloudPull(options) {
45797
46871
  await remote.close();
45798
46872
  }
45799
46873
  }
45800
- async function cloudSync(options) {
45801
- const pull = await cloudPull(options);
45802
- const push = await cloudPush(options);
46874
+ async function storageSync(options) {
46875
+ const pull = await storagePull(options);
46876
+ const push = await storagePush(options);
45803
46877
  return { pull, push };
45804
46878
  }
45805
- function getSyncMetaAll() {
46879
+ function getStorageSyncMetaAll() {
45806
46880
  const db = getDatabase();
45807
46881
  ensureSyncMetaTable(db);
45808
46882
  return db.query("SELECT table_name, last_synced_at, direction FROM _domains_sync_meta ORDER BY table_name, direction").all();
45809
46883
  }
46884
+ function getStorageStatus() {
46885
+ return {
46886
+ configured: Boolean(getStorageDatabaseUrl()),
46887
+ env: STORAGE_DATABASE_ENV,
46888
+ mode: getStorageMode(),
46889
+ service: "domains",
46890
+ tables: STORAGE_TABLES,
46891
+ sync: getStorageSyncMetaAll()
46892
+ };
46893
+ }
45810
46894
  function resolveTables(tables) {
45811
46895
  if (!tables || tables.length === 0)
45812
- return [...CLOUD_TABLES];
45813
- const allowed = new Set(CLOUD_TABLES);
46896
+ return [...STORAGE_TABLES];
46897
+ const allowed = new Set(STORAGE_TABLES);
45814
46898
  const requested = tables.map((table) => table.trim()).filter(Boolean);
45815
46899
  const invalid = requested.filter((table) => !allowed.has(table));
45816
46900
  if (invalid.length > 0)
@@ -45946,12 +47030,7 @@ function coerceForSqlite(value) {
45946
47030
  return String(value);
45947
47031
  }
45948
47032
 
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
- ];
47033
+ // src/cli/commands/storage.ts
45955
47034
  function parseTables(value) {
45956
47035
  if (!value)
45957
47036
  return;
@@ -45968,21 +47047,15 @@ function printResults(results, label) {
45968
47047
  }
45969
47048
  console.log(`Done. ${total} rows ${label}.`);
45970
47049
  }
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
- };
47050
+ function installStorageSubcommands(storage) {
47051
+ storage.command("status").description("Show remote storage config and local sync state").option("--json", "Output as JSON").action((opts) => {
47052
+ const info = getStorageStatus();
45981
47053
  if (opts.json) {
45982
47054
  printJson(info);
45983
47055
  return;
45984
47056
  }
45985
- console.log(`Cloud configured: ${info.configured ? "yes" : "no"}`);
47057
+ console.log(`Storage mode: ${info.mode}`);
47058
+ console.log(`Remote storage configured: ${info.configured ? "yes" : "no"}`);
45986
47059
  console.log(`Tables: ${info.tables.join(", ")}`);
45987
47060
  if (info.sync.length === 0)
45988
47061
  console.log("Sync: no local sync history");
@@ -45990,9 +47063,9 @@ function registerCloudCommand(program2) {
45990
47063
  console.log(` ${entry.table_name} ${entry.direction}: ${entry.last_synced_at ?? "never"}`);
45991
47064
  }
45992
47065
  });
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) => {
47066
+ 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
47067
  try {
45995
- const results = await cloudPush({ tables: parseTables(opts.tables) });
47068
+ const results = await storagePush({ tables: parseTables(opts.tables) });
45996
47069
  if (opts.json) {
45997
47070
  printJson(results);
45998
47071
  return;
@@ -46003,9 +47076,9 @@ function registerCloudCommand(program2) {
46003
47076
  process.exit(1);
46004
47077
  }
46005
47078
  });
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) => {
47079
+ 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
47080
  try {
46008
- const results = await cloudPull({ tables: parseTables(opts.tables) });
47081
+ const results = await storagePull({ tables: parseTables(opts.tables) });
46009
47082
  if (opts.json) {
46010
47083
  printJson(results);
46011
47084
  return;
@@ -46016,9 +47089,9 @@ function registerCloudCommand(program2) {
46016
47089
  process.exit(1);
46017
47090
  }
46018
47091
  });
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) => {
47092
+ 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
47093
  try {
46021
- const result = await cloudSync({ tables: parseTables(opts.tables) });
47094
+ const result = await storageSync({ tables: parseTables(opts.tables) });
46022
47095
  if (opts.json) {
46023
47096
  printJson(result);
46024
47097
  return;
@@ -46031,6 +47104,9 @@ function registerCloudCommand(program2) {
46031
47104
  }
46032
47105
  });
46033
47106
  }
47107
+ function registerStorageCommand(program2) {
47108
+ installStorageSubcommands(program2.command("storage").description("Manage domains local/remote storage sync"));
47109
+ }
46034
47110
 
46035
47111
  // src/cli/index.ts
46036
47112
  init_version();
@@ -46056,6 +47132,7 @@ registerOutreachCommand(program2);
46056
47132
  registerSedoCommand(program2);
46057
47133
  registerWalletCommand(program2);
46058
47134
  registerProvisionCommand(program2);
46059
- registerCloudCommand(program2);
47135
+ registerStorageCommand(program2);
47136
+ registerEventsCommands(program2, { source: "domains" });
46060
47137
  registerInteractiveCommand(program2);
46061
47138
  program2.parse(process.argv);