@hasna/domains 0.0.21 → 0.0.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp/index.js CHANGED
@@ -39325,7 +39325,8 @@ function getConfig2() {
39325
39325
  return {
39326
39326
  region: process.env["AWS_REGION"] || "us-east-1",
39327
39327
  accessKeyId: process.env["AWS_ACCESS_KEY_ID"],
39328
- secretAccessKey: process.env["AWS_SECRET_ACCESS_KEY"]
39328
+ secretAccessKey: process.env["AWS_SECRET_ACCESS_KEY"],
39329
+ sessionToken: process.env["AWS_SESSION_TOKEN"]
39329
39330
  };
39330
39331
  }
39331
39332
  function checkCredentials(cfg) {
@@ -39341,7 +39342,7 @@ function makeClients(config) {
39341
39342
  const cfg = config ?? getConfig2();
39342
39343
  checkCredentials(cfg);
39343
39344
  const region = cfg.region || "us-east-1";
39344
- const credentials = cfg.accessKeyId && cfg.secretAccessKey ? { accessKeyId: cfg.accessKeyId, secretAccessKey: cfg.secretAccessKey } : undefined;
39345
+ const credentials = cfg.accessKeyId && cfg.secretAccessKey ? { accessKeyId: cfg.accessKeyId, secretAccessKey: cfg.secretAccessKey, sessionToken: cfg.sessionToken } : undefined;
39345
39346
  return {
39346
39347
  route53: new Route53Client({ region, credentials }),
39347
39348
  domains: new Route53DomainsClient({ region: "us-east-1", credentials })
@@ -39481,7 +39482,8 @@ async function listHostedZones(config) {
39481
39482
  id: cleanZoneId(z2.Id ?? ""),
39482
39483
  name: z2.Name ?? "",
39483
39484
  record_count: z2.ResourceRecordSetCount ?? 0,
39484
- comment: z2.Config?.Comment
39485
+ comment: z2.Config?.Comment,
39486
+ private_zone: z2.Config?.PrivateZone
39485
39487
  });
39486
39488
  }
39487
39489
  marker = result.IsTruncated ? result.NextMarker : undefined;
@@ -39496,7 +39498,8 @@ async function getHostedZone(hostedZoneId, config) {
39496
39498
  name: result.HostedZone?.Name ?? "",
39497
39499
  record_count: result.HostedZone?.ResourceRecordSetCount ?? 0,
39498
39500
  comment: result.HostedZone?.Config?.Comment,
39499
- name_servers: result.DelegationSet?.NameServers ?? []
39501
+ name_servers: result.DelegationSet?.NameServers ?? [],
39502
+ private_zone: result.HostedZone?.Config?.PrivateZone
39500
39503
  };
39501
39504
  }
39502
39505
  async function deleteHostedZone(hostedZoneId, config) {
@@ -39506,7 +39509,15 @@ async function deleteHostedZone(hostedZoneId, config) {
39506
39509
  async function findHostedZoneByDomain(domain, config) {
39507
39510
  const zones = await listHostedZones(config);
39508
39511
  const normalized = domain.endsWith(".") ? domain : `${domain}.`;
39509
- return zones.find((z2) => z2.name === normalized) ?? null;
39512
+ const matches = zones.filter((z2) => z2.name === normalized);
39513
+ if (matches.length === 0)
39514
+ return null;
39515
+ const publicMatches = matches.filter((z2) => !z2.private_zone);
39516
+ const candidates = publicMatches.length > 0 ? publicMatches : matches;
39517
+ if (candidates.length > 1) {
39518
+ throw new Error(`Multiple Route 53 hosted zones found for ${domain}; specify hosted zone id`);
39519
+ }
39520
+ return candidates[0] ?? null;
39510
39521
  }
39511
39522
  function rrsToRecord(rrs) {
39512
39523
  if (rrs.AliasTarget) {
@@ -39606,19 +39617,53 @@ function createRoute53Provider(config) {
39606
39617
  const cfg = config ?? getConfig2();
39607
39618
  const registerWithRoute53 = registerDomain2;
39608
39619
  const updateRoute53Nameservers = updateNameservers2;
39620
+ async function listDomainInventory() {
39621
+ const byDomain = new Map;
39622
+ try {
39623
+ const registered = await listRegisteredDomains(cfg);
39624
+ for (const d3 of registered) {
39625
+ byDomain.set(d3.domain, {
39626
+ domain: d3.domain,
39627
+ registrar: "AWS Route 53",
39628
+ created: "",
39629
+ expires: d3.expiry,
39630
+ nameservers: [],
39631
+ status: "active",
39632
+ auto_renew: d3.auto_renew
39633
+ });
39634
+ }
39635
+ } catch (error) {
39636
+ const message = error instanceof Error ? error.message : String(error);
39637
+ if (!message.includes("route53domains:ListDomains") && !message.includes("AccessDenied")) {
39638
+ throw error;
39639
+ }
39640
+ }
39641
+ const zones = await listHostedZones(cfg);
39642
+ for (const z2 of zones) {
39643
+ const zone = z2.name_servers?.length ? z2 : await getHostedZone(z2.id, cfg).catch(() => z2);
39644
+ const domain = zone.name.replace(/\.$/, "");
39645
+ const nameservers = zone.name_servers ?? [];
39646
+ const existing = byDomain.get(domain);
39647
+ if (existing) {
39648
+ existing.nameservers = nameservers.length > 0 ? nameservers : existing.nameservers;
39649
+ continue;
39650
+ }
39651
+ byDomain.set(domain, {
39652
+ domain,
39653
+ registrar: "AWS Route 53 DNS",
39654
+ created: "",
39655
+ expires: "",
39656
+ nameservers,
39657
+ status: "active",
39658
+ auto_renew: false
39659
+ });
39660
+ }
39661
+ return Array.from(byDomain.values());
39662
+ }
39609
39663
  return {
39610
39664
  name: "route53",
39611
39665
  async listDomains() {
39612
- const domains = await listRegisteredDomains(cfg);
39613
- return domains.map((d3) => ({
39614
- domain: d3.domain,
39615
- registrar: "AWS Route 53",
39616
- created: "",
39617
- expires: d3.expiry,
39618
- nameservers: [],
39619
- status: "active",
39620
- auto_renew: d3.auto_renew
39621
- }));
39666
+ return listDomainInventory();
39622
39667
  },
39623
39668
  async getDomainInfo(domain) {
39624
39669
  const detail = await getDomainDetail(domain, cfg);
@@ -39687,7 +39732,7 @@ function createRoute53Provider(config) {
39687
39732
  };
39688
39733
  },
39689
39734
  async syncToLocalDb(dbFns) {
39690
- const domains = await listRegisteredDomains(cfg);
39735
+ const domains = await listDomainInventory();
39691
39736
  let synced = 0;
39692
39737
  let created = 0;
39693
39738
  let updated = 0;
@@ -39696,20 +39741,39 @@ function createRoute53Provider(config) {
39696
39741
  try {
39697
39742
  const existing = dbFns.getDomainByName(d3.domain);
39698
39743
  if (existing) {
39744
+ const existingRoute53 = existing.metadata["route53"];
39745
+ const staleDnsOnlyRegistrar = d3.registrar !== "AWS Route 53" && (existing.registrar === "AWS Route 53 DNS" || existing.registrar === "AWS Route 53" && existingRoute53?.source === "route53:hosted_zones");
39699
39746
  dbFns.updateDomain(existing.id, {
39700
- registrar: "AWS Route 53",
39701
- expires_at: d3.expiry || undefined,
39747
+ ...d3.registrar === "AWS Route 53" ? { registrar: "AWS Route 53" } : {},
39748
+ ...staleDnsOnlyRegistrar ? { registrar: null } : {},
39749
+ expires_at: d3.expires || undefined,
39702
39750
  auto_renew: d3.auto_renew,
39751
+ nameservers: d3.nameservers.length > 0 ? d3.nameservers : existing.nameservers,
39752
+ metadata: {
39753
+ ...existing.metadata,
39754
+ route53: {
39755
+ source: d3.registrar === "AWS Route 53" ? "route53domains+hosted_zones" : "route53:hosted_zones",
39756
+ synced_at: new Date().toISOString()
39757
+ }
39758
+ },
39703
39759
  status: "active"
39704
39760
  });
39705
39761
  updated++;
39706
39762
  } else {
39707
39763
  dbFns.createDomain({
39708
39764
  name: d3.domain,
39709
- registrar: "AWS Route 53",
39710
- expires_at: d3.expiry || undefined,
39765
+ ...d3.registrar === "AWS Route 53" ? { registrar: "AWS Route 53" } : {},
39766
+ expires_at: d3.expires || undefined,
39711
39767
  auto_renew: d3.auto_renew,
39712
- status: "active"
39768
+ nameservers: d3.nameservers,
39769
+ status: "active",
39770
+ notes: d3.registrar === "AWS Route 53 DNS" ? "Discovered from Route 53 hosted zones; registrar ownership was not inferred." : undefined,
39771
+ metadata: {
39772
+ route53: {
39773
+ source: d3.registrar === "AWS Route 53" ? "route53domains+hosted_zones" : "route53:hosted_zones",
39774
+ synced_at: new Date().toISOString()
39775
+ }
39776
+ }
39713
39777
  });
39714
39778
  created++;
39715
39779
  }
@@ -39882,6 +39946,22 @@ async function cfFetch(path, opts = {}) {
39882
39946
  }
39883
39947
  return json.result;
39884
39948
  }
39949
+ async function listZones(config) {
39950
+ const zones = [];
39951
+ let page = 1;
39952
+ while (true) {
39953
+ const result = await cfFetch(`/zones?per_page=50&page=${page}`, { config });
39954
+ if (!result || result.length === 0)
39955
+ break;
39956
+ for (const z2 of result) {
39957
+ zones.push({ id: z2.id, name: z2.name, status: z2.status, nameservers: z2.name_servers, original_nameservers: z2.original_name_servers });
39958
+ }
39959
+ if (result.length < 50)
39960
+ break;
39961
+ page++;
39962
+ }
39963
+ return zones;
39964
+ }
39885
39965
  async function getZone(domain, config) {
39886
39966
  const result = await cfFetch(`/zones?name=${encodeURIComponent(domain)}`, { config });
39887
39967
  if (!result || result.length === 0)
@@ -39923,26 +40003,111 @@ async function listRecords2(zoneId, config) {
39923
40003
  }
39924
40004
  return records;
39925
40005
  }
39926
- async function upsertRecord2(zoneId, record, config) {
39927
- const existing = await cfFetch(`/zones/${zoneId}/dns_records?type=${record.type}&name=${encodeURIComponent(record.name)}`, { config });
39928
- const body = {
39929
- type: record.type,
39930
- name: record.name,
39931
- content: record.content,
39932
- ttl: record.ttl ?? 1,
39933
- priority: record.priority,
39934
- proxied: record.proxied ?? false
39935
- };
39936
- if (existing && existing.length > 0) {
39937
- await cfFetch(`/zones/${zoneId}/dns_records/${existing[0].id}`, { method: "PUT", body, config });
39938
- } else {
39939
- await cfFetch(`/zones/${zoneId}/dns_records`, { method: "POST", body, config });
40006
+ async function listRecordsByNameType(zoneId, type, name, config) {
40007
+ const result = await cfFetch(`/zones/${zoneId}/dns_records?type=${encodeURIComponent(type)}&name=${encodeURIComponent(name)}`, { config });
40008
+ return (result ?? []).map((r3) => ({
40009
+ id: r3.id,
40010
+ type: r3.type,
40011
+ name: r3.name,
40012
+ content: r3.content,
40013
+ ttl: r3.ttl,
40014
+ priority: r3.priority,
40015
+ proxied: r3.proxied
40016
+ }));
40017
+ }
40018
+ async function replaceRecordsByNameType(zoneId, records, config) {
40019
+ if (records.length === 0)
40020
+ return;
40021
+ const { type, name } = records[0];
40022
+ const existing = await listRecordsByNameType(zoneId, type, name, config);
40023
+ for (const record of existing) {
40024
+ if (record.id)
40025
+ await deleteRecord2(zoneId, record.id, config);
40026
+ }
40027
+ for (const record of records) {
40028
+ await cfFetch(`/zones/${zoneId}/dns_records`, {
40029
+ method: "POST",
40030
+ body: {
40031
+ type: record.type,
40032
+ name: record.name,
40033
+ content: record.content,
40034
+ ttl: record.ttl ?? 1,
40035
+ priority: record.priority,
40036
+ proxied: record.proxied ?? false
40037
+ },
40038
+ config
40039
+ });
39940
40040
  }
39941
40041
  }
40042
+ async function deleteRecord2(zoneId, recordId, config) {
40043
+ await cfFetch(`/zones/${zoneId}/dns_records/${recordId}`, { method: "DELETE", config });
40044
+ }
40045
+ function zoneToDomainInfo(zone) {
40046
+ return {
40047
+ domain: zone.name,
40048
+ registrar: "Cloudflare DNS",
40049
+ created: "",
40050
+ expires: "",
40051
+ nameservers: zone.nameservers,
40052
+ status: zone.status === "active" ? "active" : "discovered",
40053
+ auto_renew: false
40054
+ };
40055
+ }
40056
+ function withCloudflareMetadata(existing, zone) {
40057
+ return {
40058
+ ...existing,
40059
+ cloudflare: {
40060
+ zone_id: zone.id,
40061
+ zone_status: zone.status,
40062
+ source: "cloudflare:zones",
40063
+ synced_at: new Date().toISOString()
40064
+ }
40065
+ };
40066
+ }
39942
40067
  function createCloudflareProvider(config) {
39943
40068
  const cfg = config ?? getConfig3();
39944
40069
  return {
39945
40070
  name: "cloudflare",
40071
+ async listDomains() {
40072
+ const zones = await listZones(cfg);
40073
+ return zones.map(zoneToDomainInfo);
40074
+ },
40075
+ async syncToLocalDb(dbFns) {
40076
+ const zones = await listZones(cfg);
40077
+ let synced = 0;
40078
+ let created = 0;
40079
+ let updated = 0;
40080
+ const errors2 = [];
40081
+ for (const zone of zones) {
40082
+ try {
40083
+ const info = zoneToDomainInfo(zone);
40084
+ const existing = dbFns.getDomainByName(zone.name);
40085
+ if (existing) {
40086
+ dbFns.updateDomain(existing.id, {
40087
+ ...existing.registrar === "Cloudflare DNS" ? { registrar: null } : {},
40088
+ status: existing.status === "discovered" && info.status === "active" ? "active" : existing.status,
40089
+ nameservers: zone.nameservers,
40090
+ metadata: withCloudflareMetadata(existing.metadata, zone)
40091
+ });
40092
+ updated++;
40093
+ } else {
40094
+ dbFns.createDomain({
40095
+ name: zone.name,
40096
+ status: info.status === "active" ? "active" : "discovered",
40097
+ auto_renew: false,
40098
+ nameservers: zone.nameservers,
40099
+ notes: "Discovered from Cloudflare zones; registrar ownership was not inferred.",
40100
+ metadata: withCloudflareMetadata({}, zone)
40101
+ });
40102
+ created++;
40103
+ }
40104
+ synced++;
40105
+ } catch (err) {
40106
+ errors2.push(`${zone.name}: ${err instanceof Error ? err.message : String(err)}`);
40107
+ }
40108
+ }
40109
+ return { synced, created, updated, errors: errors2 };
40110
+ },
39946
40111
  async getDnsRecords(domain) {
39947
40112
  const zone = await getZone(domain, cfg);
39948
40113
  if (!zone)
@@ -39960,8 +40125,15 @@ function createCloudflareProvider(config) {
39960
40125
  const zone = await getZone(domain, cfg);
39961
40126
  if (!zone)
39962
40127
  throw new Error(`No Cloudflare zone found for ${domain}`);
40128
+ const grouped = new Map;
39963
40129
  for (const r3 of records) {
39964
- await upsertRecord2(zone.id, { type: r3.type, name: r3.name, content: r3.value, ttl: r3.ttl || 1, priority: r3.priority }, cfg);
40130
+ const key = `${r3.type}|${r3.name}`;
40131
+ const existing = grouped.get(key) ?? [];
40132
+ existing.push({ type: r3.type, name: r3.name, content: r3.value, ttl: r3.ttl || 1, priority: r3.priority });
40133
+ grouped.set(key, existing);
40134
+ }
40135
+ for (const group of grouped.values()) {
40136
+ await replaceRecordsByNameType(zone.id, group, cfg);
39965
40137
  }
39966
40138
  return true;
39967
40139
  }
@@ -40006,7 +40178,8 @@ function getConfig4() {
40006
40178
  return resolveBrandsightConfig();
40007
40179
  }
40008
40180
  var BRANDSIGHT_BASE = "https://api.brandsight.com/v1";
40009
- async function apiRequest3(method, path, apiKey, body, baseUrl = BRANDSIGHT_BASE) {
40181
+ var BRANDSIGHT_DOMAIN_BASE = "https://api.godaddy.com/v2";
40182
+ async function apiGet(path, apiKey, baseUrl = BRANDSIGHT_BASE) {
40010
40183
  const fetchFn = _fetchFn || globalThis.fetch;
40011
40184
  const url = `${baseUrl}${path}`;
40012
40185
  const headers = {
@@ -40017,13 +40190,12 @@ async function apiRequest3(method, path, apiKey, body, baseUrl = BRANDSIGHT_BASE
40017
40190
  };
40018
40191
  try {
40019
40192
  const response = await fetchFn(url, {
40020
- method,
40193
+ method: "GET",
40021
40194
  headers,
40022
- body: body ? JSON.stringify(body) : undefined,
40023
40195
  signal: AbortSignal.timeout(15000)
40024
40196
  });
40025
40197
  if (!response.ok) {
40026
- throw new BrandsightApiError(`Brandsight API ${method} ${path} failed with status ${response.status}`, response.status, await response.text());
40198
+ throw new BrandsightApiError(`Brandsight API GET ${path} failed with status ${response.status}`, response.status, await response.text());
40027
40199
  }
40028
40200
  const data = await response.json();
40029
40201
  return { data, stub: false };
@@ -40033,31 +40205,102 @@ async function apiRequest3(method, path, apiKey, body, baseUrl = BRANDSIGHT_BASE
40033
40205
  return { data: null, stub: true };
40034
40206
  }
40035
40207
  }
40036
- async function apiGet(path, apiKey, baseUrl = BRANDSIGHT_BASE) {
40037
- const fetchFn = _fetchFn || globalThis.fetch;
40038
- const url = `${baseUrl}${path}`;
40039
- const headers = {
40040
- Authorization: `Bearer ${apiKey}`,
40208
+ function requireDomainConfig(config) {
40209
+ const cfg = config ?? getConfig4();
40210
+ if (!cfg.apiKey || !cfg.apiSecret || !cfg.customerId) {
40211
+ throw new BrandsightApiError("Brandsight Domain API credentials are not configured. Set BRANDSIGHT_API_KEY, BRANDSIGHT_API_SECRET, and BRANDSIGHT_CUSTOMER_ID (or HASNAXYZ_BRANDSIGHT_LIVE_* aliases).");
40212
+ }
40213
+ return cfg;
40214
+ }
40215
+ function domainBaseUrl(cfg) {
40216
+ return cfg.baseUrl ?? BRANDSIGHT_DOMAIN_BASE;
40217
+ }
40218
+ function domainHeaders(cfg) {
40219
+ return {
40220
+ Authorization: `sso-key ${cfg.apiKey}:${cfg.apiSecret}`,
40041
40221
  "Content-Type": "application/json",
40042
40222
  Accept: "application/json",
40043
40223
  "User-Agent": USER_AGENT
40044
40224
  };
40045
- try {
40046
- const response = await fetchFn(url, {
40047
- method: "GET",
40048
- headers,
40049
- signal: AbortSignal.timeout(15000)
40050
- });
40051
- if (!response.ok) {
40052
- throw new BrandsightApiError(`Brandsight API GET ${path} failed with status ${response.status}`, response.status, await response.text());
40053
- }
40054
- const data = await response.json();
40055
- return { data, stub: false };
40056
- } catch (error) {
40057
- if (error instanceof BrandsightApiError)
40058
- throw error;
40059
- return { data: null, stub: true };
40225
+ }
40226
+ async function domainApiRequest(method, path, config, body) {
40227
+ const cfg = requireDomainConfig(config);
40228
+ const fetchFn = _fetchFn || globalThis.fetch;
40229
+ const response = await fetchFn(`${domainBaseUrl(cfg)}${path}`, {
40230
+ method,
40231
+ headers: domainHeaders(cfg),
40232
+ body: body ? JSON.stringify(body) : undefined,
40233
+ signal: AbortSignal.timeout(30000)
40234
+ });
40235
+ const text = await response.text();
40236
+ if (!response.ok) {
40237
+ throw new BrandsightApiError(`Brandsight Domain API ${method} ${path} failed with status ${response.status}`, response.status, text);
40060
40238
  }
40239
+ if (!text.trim())
40240
+ return {};
40241
+ return JSON.parse(text);
40242
+ }
40243
+ function normalizeBrandsightDomain(raw) {
40244
+ const nameServers = raw.nameServers ?? raw.nameservers ?? [];
40245
+ const expiresAt = raw.expiresAt ?? raw.expires ?? "";
40246
+ const createdAt = raw.createdAt ?? raw.created ?? "";
40247
+ const renewAuto = raw.renewAuto ?? raw.auto_renew ?? false;
40248
+ return {
40249
+ ...raw,
40250
+ domain: String(raw.domain ?? ""),
40251
+ status: String(raw.status ?? "UNKNOWN"),
40252
+ created: createdAt,
40253
+ expires: expiresAt,
40254
+ auto_renew: renewAuto,
40255
+ locked: Boolean(raw.locked),
40256
+ nameservers: nameServers,
40257
+ createdAt,
40258
+ expiresAt,
40259
+ nameServers,
40260
+ renewAuto
40261
+ };
40262
+ }
40263
+ function customerPath(cfg, path) {
40264
+ return `/customers/${encodeURIComponent(cfg.customerId)}${path}`;
40265
+ }
40266
+ function domainTld(domain) {
40267
+ const parts = domain.split(".").filter(Boolean);
40268
+ if (parts.length < 2)
40269
+ throw new BrandsightApiError(`Invalid domain name: ${domain}`);
40270
+ return parts.slice(1).join(".");
40271
+ }
40272
+ function brandsightContact(contact) {
40273
+ return {
40274
+ addressMailing: {
40275
+ address1: contact.address_line_1,
40276
+ city: contact.city,
40277
+ country: contact.country_code,
40278
+ postalCode: contact.zip_code,
40279
+ state: contact.state
40280
+ },
40281
+ email: contact.email,
40282
+ encoding: "ASCII",
40283
+ nameFirst: contact.first_name,
40284
+ nameLast: contact.last_name,
40285
+ organization: contact.organization_name,
40286
+ phone: contact.phone
40287
+ };
40288
+ }
40289
+ async function domainAvailability(domain, cfg, type, period = 1) {
40290
+ const params = new URLSearchParams({
40291
+ domain,
40292
+ period: String(period),
40293
+ type,
40294
+ optimizeFor: "ACCURACY"
40295
+ });
40296
+ const result = await domainApiRequest("GET", `/domains/available?${params.toString()}`, cfg);
40297
+ return {
40298
+ domain: result.domain ?? domain,
40299
+ available: Boolean(result.available),
40300
+ price: result.price,
40301
+ currency: result.currency,
40302
+ registryPremiumPricing: result.registryPremiumPricing
40303
+ };
40061
40304
  }
40062
40305
  function generateStubAlerts(brandName) {
40063
40306
  const now = new Date().toISOString();
@@ -40111,32 +40354,160 @@ async function getThreatAssessment(domain) {
40111
40354
  return { ...result.data, stub: false };
40112
40355
  }
40113
40356
  async function listDomains2(config) {
40114
- const cfg = config ?? getConfig4();
40115
- const result = await apiGet("/portfolio/domains", cfg.apiKey);
40116
- if (result.stub)
40117
- return [];
40118
- return result.data.domains ?? [];
40357
+ const cfg = requireDomainConfig(config);
40358
+ const domains = [];
40359
+ const seenMarkers = new Set;
40360
+ let marker;
40361
+ while (true) {
40362
+ const params = new URLSearchParams({ limit: "500" });
40363
+ if (marker)
40364
+ params.set("marker", marker);
40365
+ const batch = await domainApiRequest("GET", customerPath(cfg, `/domains?${params.toString()}`), cfg);
40366
+ if (!Array.isArray(batch) || batch.length === 0)
40367
+ break;
40368
+ domains.push(...batch.map(normalizeBrandsightDomain).filter((d3) => d3.domain));
40369
+ if (batch.length < 500)
40370
+ break;
40371
+ const nextMarker = String(batch[batch.length - 1]?.domain ?? "");
40372
+ if (!nextMarker || seenMarkers.has(nextMarker))
40373
+ break;
40374
+ seenMarkers.add(nextMarker);
40375
+ marker = nextMarker;
40376
+ }
40377
+ return domains;
40119
40378
  }
40120
40379
  async function getDomainInfo3(domain, config) {
40121
- const cfg = config ?? getConfig4();
40122
- const result = await apiGet(`/portfolio/domains/${encodeURIComponent(domain)}`, cfg.apiKey);
40123
- if (result.stub)
40124
- return null;
40125
- return result.data;
40380
+ const cfg = requireDomainConfig(config);
40381
+ const result = await domainApiRequest("GET", customerPath(cfg, `/domains/${encodeURIComponent(domain)}`), cfg);
40382
+ return normalizeBrandsightDomain(result);
40126
40383
  }
40127
40384
  async function checkAvailability4(domain, config) {
40128
- const cfg = config ?? getConfig4();
40129
- const result = await apiGet(`/domains/check?domain=${encodeURIComponent(domain)}`, cfg.apiKey);
40130
- if (result.stub)
40131
- return { domain, available: false };
40132
- return result.data;
40385
+ const cfg = requireDomainConfig(config);
40386
+ return domainAvailability(domain, cfg, "REGISTRATION", 1);
40133
40387
  }
40134
40388
  async function renewDomain3(domain, years = 1, config) {
40135
- const cfg = config ?? getConfig4();
40136
- const result = await apiRequest3("POST", `/portfolio/domains/${encodeURIComponent(domain)}/renew`, cfg.apiKey, { years });
40137
- if (result.stub)
40138
- return { success: false };
40139
- return { success: true, orderId: result.data.orderId };
40389
+ const cfg = requireDomainConfig(config);
40390
+ const current = await getDomainInfo3(domain, cfg);
40391
+ if (!current?.expires)
40392
+ throw new BrandsightApiError(`Cannot renew ${domain}: current expiry is unavailable`);
40393
+ const quote = await domainAvailability(domain, cfg, "RENEWAL", years);
40394
+ const price = quote.price ?? current.renewal?.price;
40395
+ const currency = quote.currency ?? current.renewal?.currency;
40396
+ if (price == null || !currency) {
40397
+ throw new BrandsightApiError(`Cannot renew ${domain}: renewal quote did not include an exact price and currency`);
40398
+ }
40399
+ const result = await domainApiRequest("POST", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/renew`), cfg, {
40400
+ consent: {
40401
+ agreedAt: new Date().toISOString(),
40402
+ agreedBy: cfg.shopperId ?? "domains-cli",
40403
+ currency,
40404
+ price,
40405
+ registryPremiumPricing: quote.registryPremiumPricing ?? false
40406
+ },
40407
+ expires: current.expires,
40408
+ period: years
40409
+ });
40410
+ return { success: true, orderId: result.orderId ?? result.id };
40411
+ }
40412
+ async function getLegalAgreements(tld, privacy = false, config) {
40413
+ const cfg = requireDomainConfig(config);
40414
+ const params = new URLSearchParams({
40415
+ privacy: String(privacy),
40416
+ tlds: tld
40417
+ });
40418
+ return domainApiRequest("GET", customerPath(cfg, `/domains/agreements?${params.toString()}`), cfg);
40419
+ }
40420
+ async function getRegistrationSchema(tld, config) {
40421
+ const cfg = requireDomainConfig(config);
40422
+ return domainApiRequest("GET", customerPath(cfg, `/domains/register/schema/${encodeURIComponent(tld)}`), cfg);
40423
+ }
40424
+ async function validateRegistrationRequest(payload, config) {
40425
+ const cfg = requireDomainConfig(config);
40426
+ await domainApiRequest("POST", customerPath(cfg, "/domains/register/validate"), cfg, payload);
40427
+ return true;
40428
+ }
40429
+ async function registerBrandsightDomain(domain, contact, options = {}, config) {
40430
+ const cfg = requireDomainConfig(config);
40431
+ const period = options.years ?? 1;
40432
+ const availability = await domainAvailability(domain, cfg, "REGISTRATION", period);
40433
+ if (!availability.available)
40434
+ throw new BrandsightApiError(`${domain} is not available for registration`);
40435
+ const price = options.premiumPrice ?? availability.price;
40436
+ if (price == null || !availability.currency) {
40437
+ throw new BrandsightApiError(`Cannot register ${domain}: availability quote did not include an exact price and currency`);
40438
+ }
40439
+ const tld = domainTld(domain);
40440
+ const schema = await getRegistrationSchema(tld, cfg);
40441
+ const required = Array.isArray(schema["required"]) ? schema["required"] : [];
40442
+ if (required.includes("metadata") && !options.metadata) {
40443
+ throw new BrandsightApiError(`Cannot register ${domain}: ${tld} requires TLD-specific metadata; pass registration metadata before validation`);
40444
+ }
40445
+ const privacy = options.privacy ?? false;
40446
+ const agreements = await getLegalAgreements(tld, privacy, cfg);
40447
+ const agreementKeys = agreements.map((a3) => a3.agreementKey).filter(Boolean);
40448
+ const c3 = brandsightContact(contact);
40449
+ const payload = {
40450
+ consent: {
40451
+ agreedAt: new Date().toISOString(),
40452
+ agreedBy: contact.email || cfg.shopperId || "domains-cli",
40453
+ agreementKeys,
40454
+ currency: availability.currency,
40455
+ price,
40456
+ registryPremiumPricing: availability.registryPremiumPricing ?? !!options.premiumPrice
40457
+ },
40458
+ contacts: {
40459
+ admin: c3,
40460
+ billing: c3,
40461
+ registrant: c3,
40462
+ tech: c3
40463
+ },
40464
+ domain,
40465
+ metadata: options.metadata ?? {},
40466
+ nameServers: options.nameservers ?? [],
40467
+ period,
40468
+ privacy,
40469
+ renewAuto: options.autoRenew ?? true
40470
+ };
40471
+ await validateRegistrationRequest(payload, cfg);
40472
+ const result = await domainApiRequest("POST", customerPath(cfg, "/domains/register"), cfg, payload);
40473
+ return {
40474
+ success: true,
40475
+ orderId: result.orderId ?? result.id,
40476
+ operationId: result.operationId ?? result.id,
40477
+ chargedAmount: String(price)
40478
+ };
40479
+ }
40480
+ async function updateNameservers3(domain, nameservers, config) {
40481
+ const cfg = requireDomainConfig(config);
40482
+ const result = await domainApiRequest("PUT", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/nameServers`), cfg, { nameServers: nameservers });
40483
+ return { success: true, operationId: result.operationId ?? result.id };
40484
+ }
40485
+ async function getDnsRecords3(domain, config) {
40486
+ const cfg = requireDomainConfig(config);
40487
+ const records = [];
40488
+ let offset = 0;
40489
+ const limit = 1000;
40490
+ while (true) {
40491
+ const batch = await domainApiRequest("GET", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/records?offset=${offset}&limit=${limit}`), cfg);
40492
+ if (!Array.isArray(batch) || batch.length === 0)
40493
+ break;
40494
+ records.push(...batch);
40495
+ if (batch.length < limit)
40496
+ break;
40497
+ offset++;
40498
+ }
40499
+ return records;
40500
+ }
40501
+ function normalizeBrandsightDnsRecord(record) {
40502
+ return {
40503
+ ...record,
40504
+ ttl: Math.max(record.ttl || 600, 600)
40505
+ };
40506
+ }
40507
+ async function setDnsRecords3(domain, records, config) {
40508
+ const cfg = requireDomainConfig(config);
40509
+ await domainApiRequest("PUT", customerPath(cfg, `/domains/${encodeURIComponent(domain)}/records`), cfg, records.map(normalizeBrandsightDnsRecord));
40510
+ return true;
40140
40511
  }
40141
40512
  async function syncToLocalDb3(dbFns, config) {
40142
40513
  const domains = await listDomains2(config);
@@ -40181,7 +40552,7 @@ function createBrandsightProvider(config) {
40181
40552
  return domains.map((d3) => ({
40182
40553
  domain: d3.domain,
40183
40554
  registrar: "Brandsight",
40184
- created: "",
40555
+ created: d3.created ?? "",
40185
40556
  expires: d3.expires,
40186
40557
  nameservers: d3.nameservers,
40187
40558
  status: d3.status === "ACTIVE" ? "active" : d3.status.toLowerCase(),
@@ -40195,7 +40566,7 @@ function createBrandsightProvider(config) {
40195
40566
  return {
40196
40567
  domain: d3.domain,
40197
40568
  registrar: "Brandsight",
40198
- created: "",
40569
+ created: d3.created ?? "",
40199
40570
  expires: d3.expires,
40200
40571
  nameservers: d3.nameservers,
40201
40572
  status: d3.status === "ACTIVE" ? "active" : d3.status.toLowerCase(),
@@ -40206,12 +40577,47 @@ function createBrandsightProvider(config) {
40206
40577
  const result = await renewDomain3(domain, years, cfg);
40207
40578
  return { domain, success: result.success, orderId: result.orderId };
40208
40579
  },
40580
+ async registerDomain(domain, contact, options) {
40581
+ const result = await registerBrandsightDomain(domain, contact, options, cfg);
40582
+ return {
40583
+ domain,
40584
+ success: result.success,
40585
+ orderId: result.orderId,
40586
+ operationId: result.operationId,
40587
+ chargedAmount: result.chargedAmount
40588
+ };
40589
+ },
40590
+ async updateNameservers(domain, nameservers) {
40591
+ const result = await updateNameservers3(domain, nameservers, cfg);
40592
+ return { domain, success: result.success, operationId: result.operationId };
40593
+ },
40594
+ async getDnsRecords(domain) {
40595
+ const records = await getDnsRecords3(domain, cfg);
40596
+ return records.map((r3) => ({
40597
+ type: r3.type,
40598
+ name: r3.name,
40599
+ value: r3.data,
40600
+ ttl: r3.ttl,
40601
+ priority: r3.priority
40602
+ }));
40603
+ },
40604
+ async setDnsRecords(domain, records) {
40605
+ return setDnsRecords3(domain, records.map((r3) => ({
40606
+ type: r3.type,
40607
+ name: r3.name,
40608
+ data: r3.value,
40609
+ ttl: r3.ttl,
40610
+ priority: r3.priority
40611
+ })), cfg);
40612
+ },
40209
40613
  async checkAvailability(domain) {
40210
40614
  const result = await checkAvailability4(domain, cfg);
40211
40615
  return {
40212
40616
  domain: result.domain,
40213
40617
  available: result.available,
40214
- standard_price: result.price,
40618
+ is_premium: result.registryPremiumPricing,
40619
+ premium_price: result.registryPremiumPricing ? result.price : undefined,
40620
+ standard_price: result.registryPremiumPricing ? undefined : result.price,
40215
40621
  currency: result.currency
40216
40622
  };
40217
40623
  },
@@ -40409,6 +40815,7 @@ var providerRegistry = new Map([
40409
40815
  configured: false,
40410
40816
  envVars: providerEnvNames("namecheap")
40411
40817
  },
40818
+ createInventory: createNamecheapProvider,
40412
40819
  createRegistrar: createNamecheapProvider,
40413
40820
  createDns: createNamecheapProvider
40414
40821
  }],
@@ -40419,6 +40826,7 @@ var providerRegistry = new Map([
40419
40826
  configured: false,
40420
40827
  envVars: providerEnvNames("godaddy")
40421
40828
  },
40829
+ createInventory: createGoDaddyProvider,
40422
40830
  createRegistrar: createGoDaddyProvider,
40423
40831
  createDns: createGoDaddyProvider
40424
40832
  }],
@@ -40429,6 +40837,7 @@ var providerRegistry = new Map([
40429
40837
  configured: false,
40430
40838
  envVars: providerEnvNames("route53")
40431
40839
  },
40840
+ createInventory: () => createRoute53Provider(),
40432
40841
  createRegistrar: () => createRoute53Provider(),
40433
40842
  createDns: () => createRoute53Provider()
40434
40843
  }],
@@ -40439,16 +40848,19 @@ var providerRegistry = new Map([
40439
40848
  configured: false,
40440
40849
  envVars: providerEnvNames("cloudflare")
40441
40850
  },
40851
+ createInventory: () => createCloudflareProvider(),
40442
40852
  createDns: createCloudflareProvider
40443
40853
  }],
40444
40854
  ["brandsight", {
40445
40855
  info: {
40446
40856
  name: "brandsight",
40447
- type: "registrar",
40857
+ type: "full",
40448
40858
  configured: false,
40449
40859
  envVars: providerEnvNames("brandsight")
40450
40860
  },
40451
- createRegistrar: createBrandsightProvider
40861
+ createInventory: createBrandsightProvider,
40862
+ createRegistrar: createBrandsightProvider,
40863
+ createDns: createBrandsightProvider
40452
40864
  }],
40453
40865
  ["sedo", {
40454
40866
  info: {
@@ -40465,11 +40877,18 @@ function isConfigured(providerName) {
40465
40877
  function getAvailableProviders() {
40466
40878
  return Array.from(providerRegistry.values()).map((e3) => ({
40467
40879
  ...e3.info,
40468
- configured: isConfigured(e3.info.name)
40880
+ configured: isConfigured(e3.info.name),
40881
+ inventory: !!e3.createInventory
40469
40882
  }));
40470
40883
  }
40471
- function providerHasRegistrar(name) {
40472
- return !!providerRegistry.get(name.toLowerCase())?.createRegistrar;
40884
+ function providerHasInventory(name) {
40885
+ return !!providerRegistry.get(name.toLowerCase())?.createInventory;
40886
+ }
40887
+ function getDomainInventoryProvider(name) {
40888
+ const entry = providerRegistry.get(name.toLowerCase());
40889
+ if (!entry?.createInventory)
40890
+ throw new Error(`No domain inventory provider: ${name}`);
40891
+ return entry.createInventory();
40473
40892
  }
40474
40893
  function getRegistrarProvider(name) {
40475
40894
  const entry = providerRegistry.get(name.toLowerCase());
@@ -40484,11 +40903,11 @@ function getDnsProvider(name) {
40484
40903
  return entry.createDns();
40485
40904
  }
40486
40905
  async function syncAll(dbFns) {
40487
- const available = getAvailableProviders().filter((p3) => p3.configured && providerHasRegistrar(p3.name));
40906
+ const available = getAvailableProviders().filter((p3) => p3.configured && providerHasInventory(p3.name));
40488
40907
  const result = { providers: [], totalSynced: 0, totalErrors: [] };
40489
40908
  for (const info of available) {
40490
40909
  try {
40491
- const provider = getRegistrarProvider(info.name);
40910
+ const provider = getDomainInventoryProvider(info.name);
40492
40911
  const syncResult = await provider.syncToLocalDb(dbFns);
40493
40912
  result.providers.push({ name: info.name, result: syncResult });
40494
40913
  result.totalSynced += syncResult.synced;
@@ -41769,7 +42188,7 @@ function buildServer() {
41769
42188
  });
41770
42189
  server.registerTool("sync_all_providers", {
41771
42190
  title: "Sync All Providers",
41772
- description: "Sync domains from all configured registrar providers (Namecheap, GoDaddy, Route 53) to local database.",
42191
+ description: "Sync domains from all configured domain inventory providers (Route 53, Cloudflare zones, Namecheap, GoDaddy, Brandsight) to local database.",
41773
42192
  inputSchema: {}
41774
42193
  }, async () => {
41775
42194
  try {