@vucinatim/agentic-devtools 0.1.0

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 (38) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/SECURITY.md +47 -0
  4. package/adapters/claude/namecheap/README.md +13 -0
  5. package/adapters/claude/npm/README.md +11 -0
  6. package/adapters/claude/railway/README.md +11 -0
  7. package/adapters/codex/namecheap/.codex-plugin/plugin.json +40 -0
  8. package/adapters/codex/namecheap/.mcp.json +21 -0
  9. package/adapters/codex/namecheap/SKILL.md +40 -0
  10. package/adapters/codex/npm/.codex-plugin/plugin.json +41 -0
  11. package/adapters/codex/npm/.mcp.json +18 -0
  12. package/adapters/codex/npm/SKILL.md +54 -0
  13. package/adapters/codex/railway/.codex-plugin/plugin.json +39 -0
  14. package/adapters/codex/railway/.mcp.json +20 -0
  15. package/adapters/codex/railway/SKILL.md +44 -0
  16. package/docs/README.md +14 -0
  17. package/docs/architecture.md +208 -0
  18. package/docs/auth-and-setup-guidelines.md +261 -0
  19. package/docs/migration-plan.md +55 -0
  20. package/docs/open-source-readiness.md +119 -0
  21. package/docs/publishing.md +211 -0
  22. package/docs/testing.md +61 -0
  23. package/docs/usage.md +144 -0
  24. package/package.json +78 -0
  25. package/src/cli.mjs +158 -0
  26. package/src/core/config-store.mjs +106 -0
  27. package/src/core/result.mjs +13 -0
  28. package/src/core/tool-registry.mjs +29 -0
  29. package/src/index.mjs +47 -0
  30. package/src/tools/namecheap/auth.mjs +429 -0
  31. package/src/tools/namecheap/client.mjs +655 -0
  32. package/src/tools/namecheap/mcp.mjs +298 -0
  33. package/src/tools/npm/auth.mjs +367 -0
  34. package/src/tools/npm/client.mjs +317 -0
  35. package/src/tools/npm/mcp.mjs +343 -0
  36. package/src/tools/railway/auth.mjs +402 -0
  37. package/src/tools/railway/client.mjs +388 -0
  38. package/src/tools/railway/mcp.mjs +282 -0
@@ -0,0 +1,655 @@
1
+ import { XMLParser } from "fast-xml-parser";
2
+ import { getResolvedAuthConfig } from "./auth.mjs";
3
+
4
+ const DEFAULT_BASE_URL = "https://api.namecheap.com/xml.response";
5
+ const SANDBOX_BASE_URL = "https://api.sandbox.namecheap.com/xml.response";
6
+ const DEFAULT_PAGE_SIZE = 20;
7
+ const MAX_PAGE_SIZE = 100;
8
+ const DEFAULT_TTL = 1800;
9
+
10
+ const XML_OPTIONS = {
11
+ ignoreAttributes: false,
12
+ attributeNamePrefix: "",
13
+ trimValues: true,
14
+ parseTagValue: false,
15
+ parseAttributeValue: false,
16
+ removeNSPrefix: true,
17
+ };
18
+
19
+ const parser = new XMLParser(XML_OPTIONS);
20
+
21
+ const asArray = (value) =>
22
+ value == null ? [] : Array.isArray(value) ? value : [value];
23
+
24
+ const textOf = (value) => {
25
+ if (typeof value === "string") {
26
+ return value;
27
+ }
28
+ if (typeof value === "number" || typeof value === "boolean") {
29
+ return String(value);
30
+ }
31
+ if (value && typeof value === "object" && "#text" in value) {
32
+ return String(value["#text"]);
33
+ }
34
+ return "";
35
+ };
36
+
37
+ const isTruthyEnv = (value) =>
38
+ typeof value === "string" &&
39
+ ["1", "true", "yes", "on"].includes(value.trim().toLowerCase());
40
+
41
+ const toInteger = (value) => {
42
+ if (value == null || value === "") {
43
+ return undefined;
44
+ }
45
+ const parsed = Number.parseInt(String(value), 10);
46
+ return Number.isFinite(parsed) ? parsed : undefined;
47
+ };
48
+
49
+ const normalizeDomainName = (domain) =>
50
+ String(domain).trim().toLowerCase().replace(/\.$/, "");
51
+
52
+ const splitRegisteredDomain = (domainName) => {
53
+ const normalized = normalizeDomainName(domainName);
54
+ const firstDot = normalized.indexOf(".");
55
+
56
+ if (firstDot <= 0 || firstDot === normalized.length - 1) {
57
+ throw new Error(
58
+ `Invalid domain "${domainName}". Expected a registered domain like "example.com".`,
59
+ );
60
+ }
61
+
62
+ return {
63
+ domain: normalized,
64
+ sld: normalized.slice(0, firstDot),
65
+ tld: normalized.slice(firstDot + 1),
66
+ };
67
+ };
68
+
69
+ const normalizeDnsRecord = (record) => ({
70
+ id: toInteger(record.id ?? record.HostId),
71
+ name: String(record.name ?? record.Name ?? "@").trim(),
72
+ type: String(record.type ?? record.Type ?? "").trim().toUpperCase(),
73
+ address: String(record.address ?? record.Address ?? "").trim(),
74
+ mxPref: toInteger(record.mxPref ?? record.MXPref),
75
+ ttl: toInteger(record.ttl ?? record.TTL) ?? DEFAULT_TTL,
76
+ emailType:
77
+ (record.emailType ?? record.EmailType)
78
+ ? String(record.emailType ?? record.EmailType).trim().toUpperCase()
79
+ : undefined,
80
+ flag: toInteger(record.flag ?? record.Flag),
81
+ tag:
82
+ (record.tag ?? record.Tag)
83
+ ? String(record.tag ?? record.Tag).trim().toLowerCase()
84
+ : undefined,
85
+ });
86
+
87
+ const recordIdentity = (record) =>
88
+ JSON.stringify({
89
+ name: normalizeDnsRecord(record).name,
90
+ type: normalizeDnsRecord(record).type,
91
+ address: normalizeDnsRecord(record).address,
92
+ mxPref: normalizeDnsRecord(record).mxPref ?? null,
93
+ ttl: normalizeDnsRecord(record).ttl ?? DEFAULT_TTL,
94
+ emailType: normalizeDnsRecord(record).emailType ?? null,
95
+ flag: normalizeDnsRecord(record).flag ?? null,
96
+ tag: normalizeDnsRecord(record).tag ?? null,
97
+ });
98
+
99
+ const matchesDnsRecord = (existingRecord, targetRecord) => {
100
+ const existing = normalizeDnsRecord(existingRecord);
101
+ const target = normalizeDnsRecord(targetRecord);
102
+
103
+ if (existing.name !== target.name) {
104
+ return false;
105
+ }
106
+ if (existing.type !== target.type) {
107
+ return false;
108
+ }
109
+ if (existing.address !== target.address) {
110
+ return false;
111
+ }
112
+
113
+ if (target.type === "MX" && target.mxPref != null) {
114
+ return existing.mxPref === target.mxPref;
115
+ }
116
+ if (target.type === "CAA") {
117
+ if (target.flag != null && existing.flag !== target.flag) {
118
+ return false;
119
+ }
120
+ if (target.tag && existing.tag !== target.tag) {
121
+ return false;
122
+ }
123
+ }
124
+ if (target.ttl != null && existing.ttl !== target.ttl) {
125
+ return false;
126
+ }
127
+
128
+ return true;
129
+ };
130
+
131
+ const findMatchingRecordIndexes = (records, targetRecord) =>
132
+ records.reduce((matches, existingRecord, index) => {
133
+ if (matchesDnsRecord(existingRecord, targetRecord)) {
134
+ matches.push(index);
135
+ }
136
+ return matches;
137
+ }, []);
138
+
139
+ const formatErrors = (errorsNode) =>
140
+ asArray(errorsNode?.Error)
141
+ .map((error) => {
142
+ if (typeof error === "string") {
143
+ return { message: error };
144
+ }
145
+
146
+ if (!error || typeof error !== "object") {
147
+ return { message: String(error) };
148
+ }
149
+
150
+ return {
151
+ number:
152
+ "Number" in error && error.Number != null
153
+ ? String(error.Number)
154
+ : undefined,
155
+ message: textOf(error) || "Unknown Namecheap API error",
156
+ };
157
+ })
158
+ .filter((error) => error.message);
159
+
160
+ const serializeForTool = (value) => JSON.stringify(value, null, 2);
161
+
162
+ const validateMutationRecords = (records) => {
163
+ if (!Array.isArray(records) || records.length === 0) {
164
+ throw new Error("Expected at least one DNS record.");
165
+ }
166
+
167
+ const emailTypes = new Set();
168
+
169
+ for (const rawRecord of records) {
170
+ const record = normalizeDnsRecord(rawRecord);
171
+
172
+ if (!record.name) {
173
+ throw new Error("Each DNS record requires a non-empty name.");
174
+ }
175
+ if (!record.type) {
176
+ throw new Error(`DNS record "${record.name}" is missing a type.`);
177
+ }
178
+ if (!record.address) {
179
+ throw new Error(
180
+ `DNS record "${record.name}" (${record.type}) is missing an address.`,
181
+ );
182
+ }
183
+ if (record.ttl != null && (record.ttl < 60 || record.ttl > 60000)) {
184
+ throw new Error(
185
+ `DNS record "${record.name}" (${record.type}) has invalid TTL ${record.ttl}. Expected 60-60000.`,
186
+ );
187
+ }
188
+ if (record.type === "MX" && record.mxPref == null) {
189
+ throw new Error(
190
+ `DNS record "${record.name}" (${record.type}) requires mxPref.`,
191
+ );
192
+ }
193
+ if (record.type === "CAA") {
194
+ if (record.flag == null || !record.tag) {
195
+ throw new Error(
196
+ `DNS record "${record.name}" (${record.type}) requires both flag and tag.`,
197
+ );
198
+ }
199
+ }
200
+ if (record.emailType) {
201
+ emailTypes.add(record.emailType);
202
+ }
203
+ }
204
+
205
+ if (emailTypes.size > 1) {
206
+ throw new Error(
207
+ "Namecheap only accepts one EmailType value per setHosts request. Do not mix multiple emailType values in one mutation.",
208
+ );
209
+ }
210
+ };
211
+
212
+ const buildSetHostsPayload = (records, { emailType } = {}) => {
213
+ validateMutationRecords(records);
214
+ const payload = {};
215
+ let resolvedEmailType = emailType;
216
+
217
+ records.map(normalizeDnsRecord).forEach((record, index) => {
218
+ const item = index + 1;
219
+ payload[`HostName${item}`] = record.name;
220
+ payload[`RecordType${item}`] = record.type;
221
+ payload[`Address${item}`] = record.address;
222
+ payload[`TTL${item}`] = String(record.ttl ?? DEFAULT_TTL);
223
+
224
+ if (record.mxPref != null) {
225
+ payload[`MXPref${item}`] = String(record.mxPref);
226
+ }
227
+ if (record.flag != null) {
228
+ payload[`Flag${item}`] = String(record.flag);
229
+ }
230
+ if (record.tag) {
231
+ payload[`Tag${item}`] = record.tag;
232
+ }
233
+ if (record.emailType) {
234
+ resolvedEmailType = record.emailType;
235
+ }
236
+ });
237
+
238
+ if (resolvedEmailType) {
239
+ payload.EmailType = resolvedEmailType;
240
+ }
241
+
242
+ return payload;
243
+ };
244
+
245
+ const parseDomainListResponse = (data) => {
246
+ const commandResponse = data?.ApiResponse?.CommandResponse ?? {};
247
+ const paging = commandResponse.Paging ?? {};
248
+
249
+ return {
250
+ domains: asArray(commandResponse.DomainGetListResult?.Domain).map(
251
+ (domain) => ({
252
+ id: toInteger(domain.ID),
253
+ name: normalizeDomainName(domain.Name),
254
+ createdAt: domain.Created,
255
+ expiresAt: domain.Expires,
256
+ isExpired: String(domain.IsExpired).toLowerCase() === "true",
257
+ isLocked: String(domain.IsLocked).toLowerCase() === "true",
258
+ autoRenew: String(domain.AutoRenew).toLowerCase() === "true",
259
+ whoisGuard: domain.WhoisGuard,
260
+ isPremium: String(domain.IsPremium).toLowerCase() === "true",
261
+ isOurDns: String(domain.IsOurDNS).toLowerCase() === "true",
262
+ }),
263
+ ),
264
+ paging: {
265
+ totalItems: toInteger(paging.TotalItems) ?? 0,
266
+ currentPage: toInteger(paging.CurrentPage) ?? 1,
267
+ pageSize: toInteger(paging.PageSize) ?? DEFAULT_PAGE_SIZE,
268
+ },
269
+ };
270
+ };
271
+
272
+ const parseDnsListResponse = (data) => {
273
+ const result = data?.ApiResponse?.CommandResponse?.DomainDNSGetListResult ?? {};
274
+
275
+ return {
276
+ domain: normalizeDomainName(result.Domain ?? ""),
277
+ isUsingOurDns: String(result.IsUsingOurDNS).toLowerCase() === "true",
278
+ nameservers: asArray(result.Nameserver).map((value) => String(value)),
279
+ };
280
+ };
281
+
282
+ const parseDnsHostsResponse = (data) => {
283
+ const result =
284
+ data?.ApiResponse?.CommandResponse?.DomainDNSGetHostsResult ?? {};
285
+
286
+ return {
287
+ domain: normalizeDomainName(result.Domain ?? ""),
288
+ isUsingOurDns: String(result.IsUsingOurDNS).toLowerCase() === "true",
289
+ emailType: result.EmailType ? String(result.EmailType).trim().toUpperCase() : undefined,
290
+ records: asArray(result.Host ?? result.host).map(normalizeDnsRecord),
291
+ };
292
+ };
293
+
294
+ export class NamecheapApiError extends Error {
295
+ constructor(message, { command, errors = [], response } = {}) {
296
+ super(message);
297
+ this.name = "NamecheapApiError";
298
+ this.command = command;
299
+ this.errors = errors;
300
+ this.response = response;
301
+ }
302
+ }
303
+
304
+ export const createNamecheapClient = ({
305
+ apiUser,
306
+ apiKey,
307
+ username,
308
+ clientIp,
309
+ baseUrl,
310
+ sandbox,
311
+ fetchImpl = globalThis.fetch,
312
+ } = {}) => {
313
+ if (!apiUser) {
314
+ throw new Error("Missing NAMECHEAP_API_USER.");
315
+ }
316
+ if (!apiKey) {
317
+ throw new Error("Missing NAMECHEAP_API_KEY.");
318
+ }
319
+ if (!username) {
320
+ throw new Error("Missing NAMECHEAP_USERNAME.");
321
+ }
322
+ if (!clientIp) {
323
+ throw new Error("Missing NAMECHEAP_CLIENT_IP.");
324
+ }
325
+ if (typeof fetchImpl !== "function") {
326
+ throw new Error("Global fetch is unavailable in this Node runtime.");
327
+ }
328
+
329
+ const resolvedBaseUrl =
330
+ baseUrl || (isTruthyEnv(sandbox) ? SANDBOX_BASE_URL : DEFAULT_BASE_URL);
331
+
332
+ const call = async (command, params = {}) => {
333
+ const body = new URLSearchParams({
334
+ ApiUser: apiUser,
335
+ ApiKey: apiKey,
336
+ UserName: username,
337
+ ClientIp: clientIp,
338
+ Command: command,
339
+ ...Object.fromEntries(
340
+ Object.entries(params).map(([key, value]) => [key, String(value)]),
341
+ ),
342
+ });
343
+
344
+ const response = await fetchImpl(resolvedBaseUrl, {
345
+ method: "POST",
346
+ headers: {
347
+ "content-type": "application/x-www-form-urlencoded",
348
+ accept: "application/xml, text/xml",
349
+ },
350
+ body,
351
+ });
352
+
353
+ const xml = await response.text();
354
+
355
+ if (!response.ok) {
356
+ throw new NamecheapApiError(
357
+ `Namecheap API request failed with HTTP ${response.status}.`,
358
+ { command, response: xml },
359
+ );
360
+ }
361
+
362
+ const data = parser.parse(xml);
363
+ const apiResponse = data?.ApiResponse;
364
+
365
+ if (!apiResponse) {
366
+ throw new NamecheapApiError("Failed to parse Namecheap API response.", {
367
+ command,
368
+ response: xml,
369
+ });
370
+ }
371
+
372
+ const errors = formatErrors(apiResponse.Errors);
373
+ if (String(apiResponse.Status).toUpperCase() !== "OK" || errors.length > 0) {
374
+ const errorMessage =
375
+ errors
376
+ .map((error) =>
377
+ error.number
378
+ ? `${error.number}: ${error.message}`
379
+ : error.message,
380
+ )
381
+ .join("; ") || `Namecheap API command ${command} failed.`;
382
+
383
+ throw new NamecheapApiError(errorMessage, {
384
+ command,
385
+ errors,
386
+ response: xml,
387
+ });
388
+ }
389
+
390
+ return data;
391
+ };
392
+
393
+ const listDomains = async ({
394
+ searchTerm,
395
+ page = 1,
396
+ pageSize = DEFAULT_PAGE_SIZE,
397
+ listType = "ALL",
398
+ sortBy = "NAME",
399
+ } = {}) => {
400
+ const normalizedPageSize = Math.min(
401
+ Math.max(10, Number(pageSize) || DEFAULT_PAGE_SIZE),
402
+ MAX_PAGE_SIZE,
403
+ );
404
+
405
+ const data = await call("namecheap.domains.getList", {
406
+ ListType: listType,
407
+ Page: page,
408
+ PageSize: normalizedPageSize,
409
+ SortBy: sortBy,
410
+ ...(searchTerm ? { SearchTerm: searchTerm } : {}),
411
+ });
412
+
413
+ return parseDomainListResponse(data);
414
+ };
415
+
416
+ const resolveRegisteredDomain = async (domain) => {
417
+ const normalized = normalizeDomainName(domain);
418
+ let page = 1;
419
+
420
+ while (true) {
421
+ const result = await listDomains({
422
+ searchTerm: normalized,
423
+ page,
424
+ pageSize: DEFAULT_PAGE_SIZE,
425
+ });
426
+
427
+ const match = result.domains.find((entry) => entry.name === normalized);
428
+ if (match) {
429
+ return {
430
+ ...match,
431
+ ...splitRegisteredDomain(match.name),
432
+ };
433
+ }
434
+
435
+ const seen = result.paging.currentPage * result.paging.pageSize;
436
+ if (seen >= result.paging.totalItems || result.domains.length === 0) {
437
+ break;
438
+ }
439
+ page += 1;
440
+ }
441
+
442
+ throw new Error(
443
+ `Domain "${normalized}" was not found in the configured Namecheap account.`,
444
+ );
445
+ };
446
+
447
+ const getDnsStatus = async (domain) => {
448
+ const resolved = await resolveRegisteredDomain(domain);
449
+ const data = await call("namecheap.domains.dns.getList", {
450
+ SLD: resolved.sld,
451
+ TLD: resolved.tld,
452
+ });
453
+
454
+ return {
455
+ ...resolved,
456
+ ...parseDnsListResponse(data),
457
+ };
458
+ };
459
+
460
+ const getDomainDns = async (domain) => {
461
+ const status = await getDnsStatus(domain);
462
+ if (!status.isUsingOurDns) {
463
+ return {
464
+ ...status,
465
+ records: [],
466
+ };
467
+ }
468
+
469
+ const data = await call("namecheap.domains.dns.getHosts", {
470
+ SLD: status.sld,
471
+ TLD: status.tld,
472
+ });
473
+
474
+ return {
475
+ ...status,
476
+ ...parseDnsHostsResponse(data),
477
+ };
478
+ };
479
+
480
+ const replaceDomainDns = async ({ domain, records, emailType }) => {
481
+ const status = await getDnsStatus(domain);
482
+
483
+ if (!status.isUsingOurDns) {
484
+ throw new Error(
485
+ `Domain "${status.domain}" is not using Namecheap DNS. Current nameservers: ${status.nameservers.join(", ")}`,
486
+ );
487
+ }
488
+
489
+ const normalizedRecords = records.map(normalizeDnsRecord);
490
+ const resolvedEmailType =
491
+ emailType ??
492
+ normalizedRecords.find((record) => record.emailType)?.emailType;
493
+
494
+ await call("namecheap.domains.dns.setHosts", {
495
+ SLD: status.sld,
496
+ TLD: status.tld,
497
+ ...buildSetHostsPayload(normalizedRecords, {
498
+ emailType: resolvedEmailType,
499
+ }),
500
+ });
501
+
502
+ return getDomainDns(status.domain);
503
+ };
504
+
505
+ const addDomainDnsRecord = async ({ domain, record }) => {
506
+ const current = await getDomainDns(domain);
507
+ const nextRecord = normalizeDnsRecord(record);
508
+ const exists = current.records.some((existingRecord) =>
509
+ matchesDnsRecord(existingRecord, nextRecord),
510
+ );
511
+
512
+ if (exists) {
513
+ return {
514
+ ...current,
515
+ changed: false,
516
+ };
517
+ }
518
+
519
+ const next = await replaceDomainDns({
520
+ domain: current.domain,
521
+ emailType: current.emailType,
522
+ records: [...current.records, nextRecord],
523
+ });
524
+
525
+ return {
526
+ ...next,
527
+ changed: true,
528
+ };
529
+ };
530
+
531
+ const removeDomainDnsRecord = async ({ domain, record }) => {
532
+ const current = await getDomainDns(domain);
533
+ const matchingIndexes = findMatchingRecordIndexes(current.records, record);
534
+ const remainingRecords = current.records.filter(
535
+ (_existingRecord, index) => !matchingIndexes.includes(index),
536
+ );
537
+
538
+ if (remainingRecords.length === current.records.length) {
539
+ return {
540
+ ...current,
541
+ changed: false,
542
+ };
543
+ }
544
+
545
+ const next =
546
+ remainingRecords.length === 0
547
+ ? await replaceDomainDns({ domain: current.domain, records: [] }).catch(
548
+ async (error) => {
549
+ if (
550
+ error instanceof Error &&
551
+ /Expected at least one DNS record/.test(error.message)
552
+ ) {
553
+ throw new Error(
554
+ "Refusing to remove the final DNS record through removeDomainDnsRecord. Use replaceDomainDns with the exact target state if you intentionally want an empty zone.",
555
+ );
556
+ }
557
+ throw error;
558
+ },
559
+ )
560
+ : await replaceDomainDns({
561
+ domain: current.domain,
562
+ emailType: current.emailType,
563
+ records: remainingRecords,
564
+ });
565
+
566
+ return {
567
+ ...next,
568
+ changed: true,
569
+ };
570
+ };
571
+
572
+ const updateDomainDnsRecord = async ({
573
+ domain,
574
+ matchRecord,
575
+ newRecord,
576
+ }) => {
577
+ const current = await getDomainDns(domain);
578
+ const matchingIndexes = findMatchingRecordIndexes(current.records, matchRecord);
579
+
580
+ if (matchingIndexes.length === 0) {
581
+ throw new Error(
582
+ `No DNS record matched the requested update target on "${current.domain}".`,
583
+ );
584
+ }
585
+
586
+ if (matchingIndexes.length > 1) {
587
+ throw new Error(
588
+ `Update target on "${current.domain}" is ambiguous. ${matchingIndexes.length} records matched.`,
589
+ );
590
+ }
591
+
592
+ const targetIndex = matchingIndexes[0];
593
+ const existingRecord = current.records[targetIndex];
594
+ const normalizedNewRecord = normalizeDnsRecord(newRecord);
595
+
596
+ if (matchesDnsRecord(existingRecord, normalizedNewRecord)) {
597
+ return {
598
+ ...current,
599
+ changed: false,
600
+ };
601
+ }
602
+
603
+ const nextRecords = current.records.map((record, index) =>
604
+ index === targetIndex ? normalizedNewRecord : record,
605
+ );
606
+
607
+ const next = await replaceDomainDns({
608
+ domain: current.domain,
609
+ emailType: current.emailType,
610
+ records: nextRecords,
611
+ });
612
+
613
+ return {
614
+ ...next,
615
+ changed: true,
616
+ };
617
+ };
618
+
619
+ return {
620
+ baseUrl: resolvedBaseUrl,
621
+ listDomains,
622
+ resolveRegisteredDomain,
623
+ getDnsStatus,
624
+ getDomainDns,
625
+ replaceDomainDns,
626
+ addDomainDnsRecord,
627
+ removeDomainDnsRecord,
628
+ updateDomainDnsRecord,
629
+ serializeForTool,
630
+ helpers: {
631
+ normalizeDomainName,
632
+ splitRegisteredDomain,
633
+ normalizeDnsRecord,
634
+ recordIdentity,
635
+ buildSetHostsPayload,
636
+ matchesDnsRecord,
637
+ findMatchingRecordIndexes,
638
+ },
639
+ };
640
+ };
641
+
642
+ export const createResolvedNamecheapClient = async (options = {}) => {
643
+ const auth = await getResolvedAuthConfig();
644
+
645
+ return createNamecheapClient({
646
+ apiUser: options.apiUser ?? auth.apiUser,
647
+ apiKey: options.apiKey ?? auth.apiKey,
648
+ username: options.username ?? auth.username ?? auth.apiUser,
649
+ clientIp: options.clientIp ?? auth.clientIp,
650
+ baseUrl: options.baseUrl ?? auth.baseUrl,
651
+ sandbox:
652
+ options.sandbox ?? (typeof auth.sandbox === "boolean" ? auth.sandbox : false),
653
+ fetchImpl: options.fetchImpl,
654
+ });
655
+ };