domsniper 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.
- package/.env.example +40 -0
- package/LICENSE +21 -0
- package/README.md +246 -0
- package/package.json +72 -0
- package/src/app.tsx +2062 -0
- package/src/completions.ts +65 -0
- package/src/core/db.ts +1313 -0
- package/src/core/features/asn-lookup.ts +91 -0
- package/src/core/features/backlinks.ts +83 -0
- package/src/core/features/blacklist-check.ts +67 -0
- package/src/core/features/cert-transparency.ts +87 -0
- package/src/core/features/config.ts +81 -0
- package/src/core/features/cors-check.ts +90 -0
- package/src/core/features/dns-details.ts +27 -0
- package/src/core/features/domain-age.ts +33 -0
- package/src/core/features/domain-suggest.ts +87 -0
- package/src/core/features/drop-catch.ts +159 -0
- package/src/core/features/email-security.ts +112 -0
- package/src/core/features/expiring-feed.ts +160 -0
- package/src/core/features/export.ts +74 -0
- package/src/core/features/filter.ts +96 -0
- package/src/core/features/http-probe.ts +46 -0
- package/src/core/features/marketplace.ts +69 -0
- package/src/core/features/path-scanner.ts +123 -0
- package/src/core/features/port-scanner.ts +132 -0
- package/src/core/features/portfolio-bulk.ts +125 -0
- package/src/core/features/portfolio-monitor.ts +214 -0
- package/src/core/features/portfolio.ts +98 -0
- package/src/core/features/price-compare.ts +39 -0
- package/src/core/features/rdap.ts +128 -0
- package/src/core/features/reverse-ip.ts +73 -0
- package/src/core/features/s3-export.ts +99 -0
- package/src/core/features/scoring.ts +121 -0
- package/src/core/features/security-headers.ts +162 -0
- package/src/core/features/session.ts +74 -0
- package/src/core/features/snipe.ts +264 -0
- package/src/core/features/social-check.ts +81 -0
- package/src/core/features/ssl-check.ts +88 -0
- package/src/core/features/subdomain-discovery.ts +53 -0
- package/src/core/features/takeover-detect.ts +143 -0
- package/src/core/features/tech-stack.ts +135 -0
- package/src/core/features/tld-expand.ts +43 -0
- package/src/core/features/variations.ts +134 -0
- package/src/core/features/version-check.ts +58 -0
- package/src/core/features/waf-detect.ts +171 -0
- package/src/core/features/watch.ts +120 -0
- package/src/core/features/wayback.ts +64 -0
- package/src/core/features/webhooks.ts +126 -0
- package/src/core/features/whois-history.ts +99 -0
- package/src/core/features/zone-transfer.ts +75 -0
- package/src/core/index.ts +50 -0
- package/src/core/paths.ts +9 -0
- package/src/core/registrar.ts +413 -0
- package/src/core/theme.ts +140 -0
- package/src/core/types.ts +143 -0
- package/src/core/validate.ts +58 -0
- package/src/core/whois.ts +265 -0
- package/src/index.tsx +1888 -0
- package/src/market-client.ts +186 -0
- package/src/proxy/ca.ts +116 -0
- package/src/proxy/db.ts +175 -0
- package/src/proxy/server.ts +155 -0
- package/tsconfig.json +30 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Core modules
|
|
2
|
+
export * from "./types.js";
|
|
3
|
+
export * from "./validate.js";
|
|
4
|
+
export * from "./db.js";
|
|
5
|
+
export * from "./whois.js";
|
|
6
|
+
export * from "./registrar.js";
|
|
7
|
+
export * from "./theme.js";
|
|
8
|
+
export * from "./paths.js";
|
|
9
|
+
|
|
10
|
+
// Features
|
|
11
|
+
export * from "./features/dns-details.js";
|
|
12
|
+
export * from "./features/http-probe.js";
|
|
13
|
+
export * from "./features/wayback.js";
|
|
14
|
+
export * from "./features/domain-age.js";
|
|
15
|
+
export * from "./features/scoring.js";
|
|
16
|
+
export * from "./features/filter.js";
|
|
17
|
+
export * from "./features/export.js";
|
|
18
|
+
export * from "./features/session.js";
|
|
19
|
+
export * from "./features/tld-expand.js";
|
|
20
|
+
export * from "./features/variations.js";
|
|
21
|
+
export * from "./features/watch.js";
|
|
22
|
+
export * from "./features/config.js";
|
|
23
|
+
export * from "./features/rdap.js";
|
|
24
|
+
export * from "./features/ssl-check.js";
|
|
25
|
+
export * from "./features/subdomain-discovery.js";
|
|
26
|
+
export * from "./features/marketplace.js";
|
|
27
|
+
export * from "./features/webhooks.js";
|
|
28
|
+
export * from "./features/domain-suggest.js";
|
|
29
|
+
export * from "./features/portfolio.js";
|
|
30
|
+
export * from "./features/portfolio-monitor.js";
|
|
31
|
+
export * from "./features/portfolio-bulk.js";
|
|
32
|
+
export * from "./features/price-compare.js";
|
|
33
|
+
export * from "./features/social-check.js";
|
|
34
|
+
export * from "./features/tech-stack.js";
|
|
35
|
+
export * from "./features/blacklist-check.js";
|
|
36
|
+
export * from "./features/backlinks.js";
|
|
37
|
+
export * from "./features/whois-history.js";
|
|
38
|
+
export * from "./features/drop-catch.js";
|
|
39
|
+
export * from "./features/expiring-feed.js";
|
|
40
|
+
export * from "./features/port-scanner.js";
|
|
41
|
+
export * from "./features/reverse-ip.js";
|
|
42
|
+
export * from "./features/asn-lookup.js";
|
|
43
|
+
export * from "./features/email-security.js";
|
|
44
|
+
export * from "./features/zone-transfer.js";
|
|
45
|
+
export * from "./features/cert-transparency.js";
|
|
46
|
+
export * from "./features/takeover-detect.js";
|
|
47
|
+
export * from "./features/security-headers.js";
|
|
48
|
+
export * from "./features/waf-detect.js";
|
|
49
|
+
export * from "./features/path-scanner.js";
|
|
50
|
+
export * from "./features/cors-check.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { join } from "path";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
|
|
4
|
+
export const APP_DIR = join(homedir(), ".domain-sniper");
|
|
5
|
+
export const CONFIG_FILE = join(APP_DIR, "config.json");
|
|
6
|
+
export const PORTFOLIO_FILE = join(APP_DIR, "portfolio.json");
|
|
7
|
+
export const SESSION_DIR = join(APP_DIR, "sessions");
|
|
8
|
+
export const WHOIS_HISTORY_DIR = join(APP_DIR, "whois-history");
|
|
9
|
+
export const DB_FILE = join(APP_DIR, "domain-sniper.db");
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain registrar integrations
|
|
3
|
+
* Supports GoDaddy, Namecheap, and Cloudflare APIs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type RegistrarProvider = "godaddy" | "namecheap" | "cloudflare";
|
|
7
|
+
|
|
8
|
+
interface GodaddyAvailabilityResponse {
|
|
9
|
+
available: boolean;
|
|
10
|
+
price?: number;
|
|
11
|
+
currency?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface GodaddyPurchaseResponse {
|
|
15
|
+
orderId?: number;
|
|
16
|
+
message?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface CloudflareResponse<T> {
|
|
20
|
+
success: boolean;
|
|
21
|
+
result?: T;
|
|
22
|
+
errors?: { message: string }[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface RegistrarConfig {
|
|
26
|
+
provider: RegistrarProvider;
|
|
27
|
+
apiKey: string;
|
|
28
|
+
apiSecret?: string;
|
|
29
|
+
accountId?: string; // For Cloudflare
|
|
30
|
+
username?: string; // For Namecheap
|
|
31
|
+
clientIp?: string; // For Namecheap
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface RegistrationResult {
|
|
35
|
+
success: boolean;
|
|
36
|
+
domain: string;
|
|
37
|
+
provider: RegistrarProvider;
|
|
38
|
+
message: string;
|
|
39
|
+
orderId?: string;
|
|
40
|
+
error?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface AvailabilityCheckResult {
|
|
44
|
+
domain: string;
|
|
45
|
+
available: boolean;
|
|
46
|
+
price?: number;
|
|
47
|
+
currency?: string;
|
|
48
|
+
provider: RegistrarProvider;
|
|
49
|
+
error?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// ─── GoDaddy ──────────────────────────────────────────────
|
|
53
|
+
|
|
54
|
+
async function godaddyCheckAvailability(
|
|
55
|
+
domain: string,
|
|
56
|
+
config: RegistrarConfig
|
|
57
|
+
): Promise<AvailabilityCheckResult> {
|
|
58
|
+
try {
|
|
59
|
+
const resp = await fetch(
|
|
60
|
+
`https://api.godaddy.com/v1/domains/available?domain=${encodeURIComponent(domain)}`,
|
|
61
|
+
{
|
|
62
|
+
headers: {
|
|
63
|
+
Authorization: `sso-key ${config.apiKey}:${config.apiSecret}`,
|
|
64
|
+
"Content-Type": "application/json",
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
const data = (await resp.json()) as GodaddyAvailabilityResponse;
|
|
69
|
+
return {
|
|
70
|
+
domain,
|
|
71
|
+
available: data.available === true,
|
|
72
|
+
price: data.price ? data.price / 1000000 : undefined,
|
|
73
|
+
currency: data.currency || "USD",
|
|
74
|
+
provider: "godaddy" as const,
|
|
75
|
+
};
|
|
76
|
+
} catch (err: unknown) {
|
|
77
|
+
return {
|
|
78
|
+
domain,
|
|
79
|
+
available: false,
|
|
80
|
+
provider: "godaddy" as const,
|
|
81
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async function godaddyRegister(
|
|
87
|
+
domain: string,
|
|
88
|
+
config: RegistrarConfig
|
|
89
|
+
): Promise<RegistrationResult> {
|
|
90
|
+
try {
|
|
91
|
+
const body = {
|
|
92
|
+
domain,
|
|
93
|
+
consent: {
|
|
94
|
+
agreedAt: new Date().toISOString(),
|
|
95
|
+
agreedBy: config.clientIp || "127.0.0.1",
|
|
96
|
+
agreementKeys: ["DNRA"],
|
|
97
|
+
},
|
|
98
|
+
period: 1,
|
|
99
|
+
renewAuto: false,
|
|
100
|
+
nameServers: [],
|
|
101
|
+
privacy: false,
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const resp = await fetch("https://api.godaddy.com/v1/domains/purchase", {
|
|
105
|
+
method: "POST",
|
|
106
|
+
headers: {
|
|
107
|
+
Authorization: `sso-key ${config.apiKey}:${config.apiSecret}`,
|
|
108
|
+
"Content-Type": "application/json",
|
|
109
|
+
},
|
|
110
|
+
body: JSON.stringify(body),
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const data = (await resp.json()) as GodaddyPurchaseResponse;
|
|
114
|
+
if (resp.ok) {
|
|
115
|
+
return {
|
|
116
|
+
success: true,
|
|
117
|
+
domain,
|
|
118
|
+
provider: "godaddy" as const,
|
|
119
|
+
message: `Domain ${domain} registered successfully!`,
|
|
120
|
+
orderId: data.orderId?.toString(),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
success: false,
|
|
125
|
+
domain,
|
|
126
|
+
provider: "godaddy" as const,
|
|
127
|
+
message: "Registration failed",
|
|
128
|
+
error: data.message || JSON.stringify(data),
|
|
129
|
+
};
|
|
130
|
+
} catch (err: unknown) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
domain,
|
|
134
|
+
provider: "godaddy" as const,
|
|
135
|
+
message: "Registration failed",
|
|
136
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─── Namecheap ────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
function parseNamecheapDomain(domain: string): { sld: string; tld: string } {
|
|
144
|
+
const parts = domain.split(".");
|
|
145
|
+
const tld = parts.slice(1).join(".");
|
|
146
|
+
const sld = parts[0] || "";
|
|
147
|
+
return { sld, tld };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function namecheapCheckAvailability(
|
|
151
|
+
domain: string,
|
|
152
|
+
config: RegistrarConfig
|
|
153
|
+
): Promise<AvailabilityCheckResult> {
|
|
154
|
+
try {
|
|
155
|
+
const url = new URL("https://api.namecheap.com/xml.response");
|
|
156
|
+
url.searchParams.set("ApiUser", config.username || "");
|
|
157
|
+
url.searchParams.set("ApiKey", config.apiKey);
|
|
158
|
+
url.searchParams.set("UserName", config.username || "");
|
|
159
|
+
url.searchParams.set("ClientIp", config.clientIp || "127.0.0.1");
|
|
160
|
+
url.searchParams.set("Command", "namecheap.domains.check");
|
|
161
|
+
url.searchParams.set("DomainList", domain);
|
|
162
|
+
|
|
163
|
+
const resp = await fetch(url.toString());
|
|
164
|
+
const text = await resp.text();
|
|
165
|
+
|
|
166
|
+
const available = text.includes('Available="true"');
|
|
167
|
+
return {
|
|
168
|
+
domain,
|
|
169
|
+
available,
|
|
170
|
+
provider: "namecheap" as const,
|
|
171
|
+
};
|
|
172
|
+
} catch (err: unknown) {
|
|
173
|
+
return {
|
|
174
|
+
domain,
|
|
175
|
+
available: false,
|
|
176
|
+
provider: "namecheap" as const,
|
|
177
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function namecheapRegister(
|
|
183
|
+
domain: string,
|
|
184
|
+
config: RegistrarConfig
|
|
185
|
+
): Promise<RegistrationResult> {
|
|
186
|
+
try {
|
|
187
|
+
const { sld, tld } = parseNamecheapDomain(domain);
|
|
188
|
+
const url = new URL("https://api.namecheap.com/xml.response");
|
|
189
|
+
url.searchParams.set("ApiUser", config.username || "");
|
|
190
|
+
url.searchParams.set("ApiKey", config.apiKey);
|
|
191
|
+
url.searchParams.set("UserName", config.username || "");
|
|
192
|
+
url.searchParams.set("ClientIp", config.clientIp || "127.0.0.1");
|
|
193
|
+
url.searchParams.set("Command", "namecheap.domains.create");
|
|
194
|
+
url.searchParams.set("DomainName", domain);
|
|
195
|
+
url.searchParams.set("Years", "1");
|
|
196
|
+
// Registrant info would need to be configured
|
|
197
|
+
url.searchParams.set("AuxBillingFirstName", "Domain");
|
|
198
|
+
url.searchParams.set("AuxBillingLastName", "Sniper");
|
|
199
|
+
|
|
200
|
+
const resp = await fetch(url.toString());
|
|
201
|
+
const text = await resp.text();
|
|
202
|
+
|
|
203
|
+
if (text.includes('Status="OK"') || text.includes("DomainCreated")) {
|
|
204
|
+
return {
|
|
205
|
+
success: true,
|
|
206
|
+
domain,
|
|
207
|
+
provider: "namecheap" as const,
|
|
208
|
+
message: `Domain ${domain} registered via Namecheap!`,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return {
|
|
213
|
+
success: false,
|
|
214
|
+
domain,
|
|
215
|
+
provider: "namecheap" as const,
|
|
216
|
+
message: "Registration failed",
|
|
217
|
+
error: text.match(/<Error.*?>(.*?)<\/Error>/)?.[1] || "Unknown error",
|
|
218
|
+
};
|
|
219
|
+
} catch (err: unknown) {
|
|
220
|
+
return {
|
|
221
|
+
success: false,
|
|
222
|
+
domain,
|
|
223
|
+
provider: "namecheap" as const,
|
|
224
|
+
message: "Registration failed",
|
|
225
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// ─── Cloudflare ───────────────────────────────────────────
|
|
231
|
+
|
|
232
|
+
async function cloudflareCheckAvailability(
|
|
233
|
+
domain: string,
|
|
234
|
+
config: RegistrarConfig
|
|
235
|
+
): Promise<AvailabilityCheckResult> {
|
|
236
|
+
try {
|
|
237
|
+
if (!config.accountId) {
|
|
238
|
+
return {
|
|
239
|
+
domain,
|
|
240
|
+
available: false,
|
|
241
|
+
provider: "cloudflare",
|
|
242
|
+
error: "Account ID required for Cloudflare",
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const resp = await fetch(
|
|
247
|
+
`https://api.cloudflare.com/client/v4/accounts/${config.accountId}/registrar/domains/${domain}`,
|
|
248
|
+
{
|
|
249
|
+
headers: {
|
|
250
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
251
|
+
"Content-Type": "application/json",
|
|
252
|
+
},
|
|
253
|
+
}
|
|
254
|
+
);
|
|
255
|
+
const data = (await resp.json()) as CloudflareResponse<{ available?: boolean }>;
|
|
256
|
+
|
|
257
|
+
if (!data.success) {
|
|
258
|
+
return {
|
|
259
|
+
domain,
|
|
260
|
+
available: false,
|
|
261
|
+
provider: "cloudflare" as const,
|
|
262
|
+
error: data.errors?.[0]?.message || "API request failed",
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (data.result?.available) {
|
|
267
|
+
return {
|
|
268
|
+
domain,
|
|
269
|
+
available: true,
|
|
270
|
+
provider: "cloudflare" as const,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
domain,
|
|
276
|
+
available: false,
|
|
277
|
+
provider: "cloudflare" as const,
|
|
278
|
+
};
|
|
279
|
+
} catch (err: unknown) {
|
|
280
|
+
return {
|
|
281
|
+
domain,
|
|
282
|
+
available: false,
|
|
283
|
+
provider: "cloudflare" as const,
|
|
284
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function cloudflareRegister(
|
|
290
|
+
domain: string,
|
|
291
|
+
config: RegistrarConfig
|
|
292
|
+
): Promise<RegistrationResult> {
|
|
293
|
+
try {
|
|
294
|
+
if (!config.accountId) {
|
|
295
|
+
return {
|
|
296
|
+
success: false,
|
|
297
|
+
domain,
|
|
298
|
+
provider: "cloudflare" as const,
|
|
299
|
+
message: "Registration failed",
|
|
300
|
+
error: "Account ID required for Cloudflare",
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const resp = await fetch(
|
|
305
|
+
`https://api.cloudflare.com/client/v4/accounts/${config.accountId}/registrar/domains/${domain}/register`,
|
|
306
|
+
{
|
|
307
|
+
method: "POST",
|
|
308
|
+
headers: {
|
|
309
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
310
|
+
"Content-Type": "application/json",
|
|
311
|
+
},
|
|
312
|
+
body: JSON.stringify({
|
|
313
|
+
auto_renew: false,
|
|
314
|
+
}),
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
const data = (await resp.json()) as CloudflareResponse<{ id?: string }>;
|
|
318
|
+
|
|
319
|
+
if (data.success) {
|
|
320
|
+
return {
|
|
321
|
+
success: true,
|
|
322
|
+
domain,
|
|
323
|
+
provider: "cloudflare" as const,
|
|
324
|
+
message: `Domain ${domain} registered via Cloudflare!`,
|
|
325
|
+
orderId: data.result?.id,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
success: false,
|
|
331
|
+
domain,
|
|
332
|
+
provider: "cloudflare" as const,
|
|
333
|
+
message: "Registration failed",
|
|
334
|
+
error: data.errors?.[0]?.message || "Unknown error",
|
|
335
|
+
};
|
|
336
|
+
} catch (err: unknown) {
|
|
337
|
+
return {
|
|
338
|
+
success: false,
|
|
339
|
+
domain,
|
|
340
|
+
provider: "cloudflare" as const,
|
|
341
|
+
message: "Registration failed",
|
|
342
|
+
error: err instanceof Error ? err.message : "Unknown error",
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ─── Unified API ──────────────────────────────────────────
|
|
348
|
+
|
|
349
|
+
export async function checkAvailabilityViaRegistrar(
|
|
350
|
+
domain: string,
|
|
351
|
+
config: RegistrarConfig
|
|
352
|
+
): Promise<AvailabilityCheckResult> {
|
|
353
|
+
switch (config.provider) {
|
|
354
|
+
case "godaddy":
|
|
355
|
+
return godaddyCheckAvailability(domain, config);
|
|
356
|
+
case "namecheap":
|
|
357
|
+
return namecheapCheckAvailability(domain, config);
|
|
358
|
+
case "cloudflare":
|
|
359
|
+
return cloudflareCheckAvailability(domain, config);
|
|
360
|
+
default:
|
|
361
|
+
return {
|
|
362
|
+
domain,
|
|
363
|
+
available: false,
|
|
364
|
+
provider: config.provider,
|
|
365
|
+
error: `Unknown provider: ${config.provider}`,
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export async function registerDomain(
|
|
371
|
+
domain: string,
|
|
372
|
+
config: RegistrarConfig
|
|
373
|
+
): Promise<RegistrationResult> {
|
|
374
|
+
switch (config.provider) {
|
|
375
|
+
case "godaddy":
|
|
376
|
+
return godaddyRegister(domain, config);
|
|
377
|
+
case "namecheap":
|
|
378
|
+
return namecheapRegister(domain, config);
|
|
379
|
+
case "cloudflare":
|
|
380
|
+
return cloudflareRegister(domain, config);
|
|
381
|
+
default:
|
|
382
|
+
return {
|
|
383
|
+
success: false,
|
|
384
|
+
domain,
|
|
385
|
+
provider: config.provider,
|
|
386
|
+
message: "Unknown provider",
|
|
387
|
+
error: `Unknown provider: ${config.provider}`,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Load registrar config from environment variables
|
|
394
|
+
*/
|
|
395
|
+
export function loadConfigFromEnv(): RegistrarConfig | null {
|
|
396
|
+
const provider = (process.env.REGISTRAR_PROVIDER || "").toLowerCase() as RegistrarProvider;
|
|
397
|
+
|
|
398
|
+
if (!provider || !["godaddy", "namecheap", "cloudflare"].includes(provider)) {
|
|
399
|
+
return null;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const apiKey = process.env.REGISTRAR_API_KEY || "";
|
|
403
|
+
if (!apiKey) return null;
|
|
404
|
+
|
|
405
|
+
return {
|
|
406
|
+
provider,
|
|
407
|
+
apiKey,
|
|
408
|
+
apiSecret: process.env.REGISTRAR_API_SECRET || "",
|
|
409
|
+
accountId: process.env.CLOUDFLARE_ACCOUNT_ID || "",
|
|
410
|
+
username: process.env.NAMECHEAP_USERNAME || "",
|
|
411
|
+
clientIp: process.env.CLIENT_IP || "127.0.0.1",
|
|
412
|
+
};
|
|
413
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Theme system inspired by OpenCode's dark theme
|
|
3
|
+
* Uses a stepped grayscale palette with accent colors
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export const palette = {
|
|
7
|
+
// Grayscale steps (dark to light)
|
|
8
|
+
step1: "#0a0a0a", // Deepest background
|
|
9
|
+
step2: "#141414", // Panel background
|
|
10
|
+
step3: "#1e1e1e", // Elevated surface
|
|
11
|
+
step4: "#282828", // Subtle borders
|
|
12
|
+
step5: "#323232", // Borders
|
|
13
|
+
step6: "#3c3c3c", // Active borders
|
|
14
|
+
step7: "#464646", // Muted elements
|
|
15
|
+
step8: "#505050", // Disabled text
|
|
16
|
+
step9: "#6e6e6e", // Placeholder text
|
|
17
|
+
step10: "#8c8c8c", // Muted text
|
|
18
|
+
step11: "#b0b0b0", // Secondary text
|
|
19
|
+
step12: "#eeeeee", // Primary text
|
|
20
|
+
|
|
21
|
+
// Accent colors
|
|
22
|
+
green: "#00e88f",
|
|
23
|
+
greenDim: "#0a3d2a",
|
|
24
|
+
blue: "#5c9cf5",
|
|
25
|
+
blueDim: "#1a2d4a",
|
|
26
|
+
yellow: "#f5c542",
|
|
27
|
+
yellowDim: "#3d3018",
|
|
28
|
+
red: "#f55c5c",
|
|
29
|
+
redDim: "#3d1a1a",
|
|
30
|
+
orange: "#f5955c",
|
|
31
|
+
orangeDim: "#3d2818",
|
|
32
|
+
purple: "#9d7cd8",
|
|
33
|
+
purpleDim: "#2a1f3d",
|
|
34
|
+
cyan: "#56d4dd",
|
|
35
|
+
cyanDim: "#1a3335",
|
|
36
|
+
} as const;
|
|
37
|
+
|
|
38
|
+
export const theme = {
|
|
39
|
+
// Backgrounds
|
|
40
|
+
background: palette.step1,
|
|
41
|
+
backgroundPanel: palette.step2,
|
|
42
|
+
backgroundElevated: palette.step3,
|
|
43
|
+
|
|
44
|
+
// Text
|
|
45
|
+
text: palette.step12,
|
|
46
|
+
textSecondary: palette.step11,
|
|
47
|
+
textMuted: palette.step10,
|
|
48
|
+
textPlaceholder: palette.step9,
|
|
49
|
+
textDisabled: palette.step8,
|
|
50
|
+
|
|
51
|
+
// Borders
|
|
52
|
+
border: palette.step5,
|
|
53
|
+
borderActive: palette.step6,
|
|
54
|
+
borderSubtle: palette.step4,
|
|
55
|
+
|
|
56
|
+
// Semantic
|
|
57
|
+
primary: palette.green,
|
|
58
|
+
primaryDim: palette.greenDim,
|
|
59
|
+
secondary: palette.blue,
|
|
60
|
+
secondaryDim: palette.blueDim,
|
|
61
|
+
warning: palette.yellow,
|
|
62
|
+
warningDim: palette.yellowDim,
|
|
63
|
+
error: palette.red,
|
|
64
|
+
errorDim: palette.redDim,
|
|
65
|
+
info: palette.cyan,
|
|
66
|
+
infoDim: palette.cyanDim,
|
|
67
|
+
accent: palette.purple,
|
|
68
|
+
accentDim: palette.purpleDim,
|
|
69
|
+
pending: palette.orange,
|
|
70
|
+
pendingDim: palette.orangeDim,
|
|
71
|
+
} as const;
|
|
72
|
+
|
|
73
|
+
// ─── Border characters ───────────────────────────────────
|
|
74
|
+
|
|
75
|
+
export const borders = {
|
|
76
|
+
rounded: {
|
|
77
|
+
topLeft: "╭",
|
|
78
|
+
topRight: "╮",
|
|
79
|
+
bottomLeft: "╰",
|
|
80
|
+
bottomRight: "╯",
|
|
81
|
+
horizontal: "─",
|
|
82
|
+
vertical: "│",
|
|
83
|
+
topT: "┬",
|
|
84
|
+
bottomT: "┴",
|
|
85
|
+
leftT: "├",
|
|
86
|
+
rightT: "┤",
|
|
87
|
+
cross: "┼",
|
|
88
|
+
},
|
|
89
|
+
heavy: {
|
|
90
|
+
topLeft: "┏",
|
|
91
|
+
topRight: "┓",
|
|
92
|
+
bottomLeft: "┗",
|
|
93
|
+
bottomRight: "┛",
|
|
94
|
+
horizontal: "━",
|
|
95
|
+
vertical: "┃",
|
|
96
|
+
topT: "┳",
|
|
97
|
+
bottomT: "┻",
|
|
98
|
+
leftT: "┣",
|
|
99
|
+
rightT: "┫",
|
|
100
|
+
cross: "╋",
|
|
101
|
+
},
|
|
102
|
+
splitLeft: {
|
|
103
|
+
topLeft: "",
|
|
104
|
+
topRight: "",
|
|
105
|
+
bottomLeft: "",
|
|
106
|
+
bottomRight: "",
|
|
107
|
+
horizontal: " ",
|
|
108
|
+
vertical: "┃",
|
|
109
|
+
topT: "",
|
|
110
|
+
bottomT: "",
|
|
111
|
+
leftT: "",
|
|
112
|
+
rightT: "",
|
|
113
|
+
cross: "",
|
|
114
|
+
},
|
|
115
|
+
} as const;
|
|
116
|
+
|
|
117
|
+
// ─── Status styling ──────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
export type DomainStatus = "pending" | "checking" | "available" | "expired" | "taken" | "error" | "registered" | "registering";
|
|
120
|
+
|
|
121
|
+
export function statusStyle(status: DomainStatus) {
|
|
122
|
+
switch (status) {
|
|
123
|
+
case "pending":
|
|
124
|
+
return { icon: "○", fg: theme.textDisabled, label: "PENDING" };
|
|
125
|
+
case "checking":
|
|
126
|
+
return { icon: "◆", fg: theme.warning, label: "CHECKING" };
|
|
127
|
+
case "available":
|
|
128
|
+
return { icon: "●", fg: theme.primary, label: "AVAILABLE" };
|
|
129
|
+
case "expired":
|
|
130
|
+
return { icon: "◈", fg: theme.pending, label: "EXPIRED" };
|
|
131
|
+
case "taken":
|
|
132
|
+
return { icon: "✕", fg: theme.error, label: "TAKEN" };
|
|
133
|
+
case "registered":
|
|
134
|
+
return { icon: "◉", fg: theme.secondary, label: "REGISTERED" };
|
|
135
|
+
case "registering":
|
|
136
|
+
return { icon: "◌", fg: theme.info, label: "REGISTERING" };
|
|
137
|
+
case "error":
|
|
138
|
+
return { icon: "!", fg: theme.error, label: "ERROR" };
|
|
139
|
+
}
|
|
140
|
+
}
|