@framers/agentos-ext-domain-porkbun 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/LICENSE +98 -0
- package/dist/PorkbunService.d.ts +119 -0
- package/dist/PorkbunService.d.ts.map +1 -0
- package/dist/PorkbunService.js +224 -0
- package/dist/PorkbunService.js.map +1 -0
- package/dist/index.d.ts +43 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/configureDns.d.ts +75 -0
- package/dist/tools/configureDns.d.ts.map +1 -0
- package/dist/tools/configureDns.js +91 -0
- package/dist/tools/configureDns.js.map +1 -0
- package/dist/tools/listDomains.d.ts +26 -0
- package/dist/tools/listDomains.d.ts.map +1 -0
- package/dist/tools/listDomains.js +32 -0
- package/dist/tools/listDomains.js.map +1 -0
- package/dist/tools/registerDomain.d.ts +89 -0
- package/dist/tools/registerDomain.d.ts.map +1 -0
- package/dist/tools/registerDomain.js +58 -0
- package/dist/tools/registerDomain.js.map +1 -0
- package/dist/tools/searchDomain.d.ts +34 -0
- package/dist/tools/searchDomain.d.ts.map +1 -0
- package/dist/tools/searchDomain.js +34 -0
- package/dist/tools/searchDomain.js.map +1 -0
- package/dist/tools/transferDomain.d.ts +39 -0
- package/dist/tools/transferDomain.d.ts.map +1 -0
- package/dist/tools/transferDomain.js +35 -0
- package/dist/tools/transferDomain.js.map +1 -0
- package/manifest.json +33 -0
- package/package.json +65 -0
- package/src/PorkbunService.ts +327 -0
- package/src/index.ts +133 -0
- package/src/tools/configureDns.ts +114 -0
- package/src/tools/listDomains.ts +33 -0
- package/src/tools/registerDomain.ts +74 -0
- package/src/tools/searchDomain.ts +39 -0
- package/src/tools/transferDomain.ts +41 -0
- package/test/PorkbunService.test.ts +434 -0
- package/test/index.test.ts +50 -0
- package/test/tools.test.ts +399 -0
- package/tsconfig.json +22 -0
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@framers/agentos-ext-domain-porkbun",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Porkbun domain registrar extension — search, register, and manage domains and DNS records via the Porkbun API v3.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": true,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"import": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
},
|
|
14
|
+
"./manifest.json": "./manifest.json",
|
|
15
|
+
"./package.json": "./package.json"
|
|
16
|
+
},
|
|
17
|
+
"keywords": [
|
|
18
|
+
"agentos",
|
|
19
|
+
"extension",
|
|
20
|
+
"domain",
|
|
21
|
+
"porkbun",
|
|
22
|
+
"registrar",
|
|
23
|
+
"dns",
|
|
24
|
+
"nameserver"
|
|
25
|
+
],
|
|
26
|
+
"author": {
|
|
27
|
+
"name": "Framers AI",
|
|
28
|
+
"email": "team@frame.dev",
|
|
29
|
+
"url": "https://frame.dev"
|
|
30
|
+
},
|
|
31
|
+
"contributors": [
|
|
32
|
+
{
|
|
33
|
+
"name": "Johnny Dunn",
|
|
34
|
+
"email": "johnnyfived@protonmail.com",
|
|
35
|
+
"url": "https://github.com/jddunn"
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/framersai/agentos-extensions.git",
|
|
42
|
+
"directory": "registry/curated/domains/porkbun"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"peerDependencies": {
|
|
48
|
+
"@framers/agentos": "^0.5.2"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@framers/agentos": "^0.6.6",
|
|
52
|
+
"@types/node": "^20.12.12",
|
|
53
|
+
"rimraf": "^5.0.7",
|
|
54
|
+
"typescript": "^5.4.5",
|
|
55
|
+
"vitest": "^1.6.0"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsc",
|
|
59
|
+
"dev": "tsc --watch",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"typecheck": "tsc --noEmit",
|
|
63
|
+
"clean": "rimraf dist"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Porkbun API v3 service layer.
|
|
4
|
+
*
|
|
5
|
+
* Wraps the Porkbun REST API for domain registration, availability checks,
|
|
6
|
+
* DNS record management, and domain transfers.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: Porkbun uses POST for ALL endpoints and requires apikey + secretapikey
|
|
9
|
+
* in every request body.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Types
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
|
|
16
|
+
export interface PorkbunConfig {
|
|
17
|
+
/** Porkbun API key (from https://porkbun.com/account/api) */
|
|
18
|
+
apiKey: string;
|
|
19
|
+
/** Porkbun secret API key */
|
|
20
|
+
secretApiKey: string;
|
|
21
|
+
/** API base URL (defaults to https://api.porkbun.com/api/json/v3) */
|
|
22
|
+
baseUrl?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DomainAvailability {
|
|
26
|
+
domain: string;
|
|
27
|
+
available: boolean;
|
|
28
|
+
pricing?: DomainPricing;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface DomainPricing {
|
|
32
|
+
registration: string;
|
|
33
|
+
renewal: string;
|
|
34
|
+
transfer: string;
|
|
35
|
+
couponed?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface OwnedDomain {
|
|
39
|
+
domain: string;
|
|
40
|
+
status: string;
|
|
41
|
+
tld: string;
|
|
42
|
+
createDate: string;
|
|
43
|
+
expireDate: string;
|
|
44
|
+
securityLock: boolean;
|
|
45
|
+
whoisPrivacy: boolean;
|
|
46
|
+
autoRenew: boolean;
|
|
47
|
+
notLocal: boolean;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface DnsRecord {
|
|
51
|
+
id: string;
|
|
52
|
+
name: string;
|
|
53
|
+
type: string;
|
|
54
|
+
content: string;
|
|
55
|
+
ttl: string;
|
|
56
|
+
prio: string | null;
|
|
57
|
+
notes: string | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface RegisterDomainOptions {
|
|
61
|
+
domain: string;
|
|
62
|
+
years?: number;
|
|
63
|
+
/** Registrant contact fields (optional — Porkbun uses account defaults) */
|
|
64
|
+
firstName?: string;
|
|
65
|
+
lastName?: string;
|
|
66
|
+
email?: string;
|
|
67
|
+
phone?: string;
|
|
68
|
+
city?: string;
|
|
69
|
+
state?: string;
|
|
70
|
+
zip?: string;
|
|
71
|
+
country?: string;
|
|
72
|
+
organization?: string;
|
|
73
|
+
address?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface RegisterDomainResult {
|
|
77
|
+
domain: string;
|
|
78
|
+
status: string;
|
|
79
|
+
message: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface DnsRecordInput {
|
|
83
|
+
name?: string;
|
|
84
|
+
type: 'A' | 'AAAA' | 'CNAME' | 'TXT' | 'MX' | 'NS' | 'SRV' | 'TLSA' | 'CAA' | 'ALIAS';
|
|
85
|
+
content: string;
|
|
86
|
+
ttl?: string;
|
|
87
|
+
prio?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface TransferDomainResult {
|
|
91
|
+
domain: string;
|
|
92
|
+
status: string;
|
|
93
|
+
message: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Service
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
export class PorkbunService {
|
|
101
|
+
private config: PorkbunConfig;
|
|
102
|
+
private running = false;
|
|
103
|
+
|
|
104
|
+
constructor(config: PorkbunConfig) {
|
|
105
|
+
this.config = {
|
|
106
|
+
...config,
|
|
107
|
+
baseUrl: config.baseUrl ?? 'https://api.porkbun.com/api/json/v3',
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async initialize(): Promise<void> {
|
|
112
|
+
// Validate auth by pinging the API
|
|
113
|
+
const res = await this.post('/ping', {});
|
|
114
|
+
if (res.status !== 'SUCCESS') {
|
|
115
|
+
throw new Error(`Porkbun auth failed: ${res.message ?? JSON.stringify(res)}`);
|
|
116
|
+
}
|
|
117
|
+
this.running = true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async shutdown(): Promise<void> {
|
|
121
|
+
this.running = false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
get isRunning(): boolean {
|
|
125
|
+
return this.running;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ── Domain Availability ─────────────────────────────────────────────────
|
|
129
|
+
|
|
130
|
+
/** Check if a domain is available for registration. */
|
|
131
|
+
async checkAvailability(domain: string): Promise<DomainAvailability> {
|
|
132
|
+
const res = await this.post('/domain/checkAvailability', { domain });
|
|
133
|
+
const available = res.avail === true || res.avail === 'true' || res.status === 'SUCCESS';
|
|
134
|
+
|
|
135
|
+
let pricing: DomainPricing | undefined;
|
|
136
|
+
// Also fetch pricing if available
|
|
137
|
+
try {
|
|
138
|
+
pricing = await this.getPricing(domain);
|
|
139
|
+
} catch {
|
|
140
|
+
// Pricing may fail for unavailable domains — that's okay
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
domain,
|
|
145
|
+
available,
|
|
146
|
+
pricing,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Get pricing information for a domain/TLD. */
|
|
151
|
+
async getPricing(domain: string): Promise<DomainPricing> {
|
|
152
|
+
const res = await this.post('/domain/pricing/get', { domain });
|
|
153
|
+
const p = res.pricing ?? res;
|
|
154
|
+
return {
|
|
155
|
+
registration: p.registration ?? p.register ?? '',
|
|
156
|
+
renewal: p.renewal ?? p.renew ?? '',
|
|
157
|
+
transfer: p.transfer ?? '',
|
|
158
|
+
couponed: p.couponed,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── Domain Registration ─────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
/** Register (purchase) a domain. */
|
|
165
|
+
async registerDomain(opts: RegisterDomainOptions): Promise<RegisterDomainResult> {
|
|
166
|
+
const body: Record<string, unknown> = {
|
|
167
|
+
domain: opts.domain,
|
|
168
|
+
years: opts.years ?? 1,
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Add optional contact fields
|
|
172
|
+
if (opts.firstName) body.firstName = opts.firstName;
|
|
173
|
+
if (opts.lastName) body.lastName = opts.lastName;
|
|
174
|
+
if (opts.email) body.email = opts.email;
|
|
175
|
+
if (opts.phone) body.phone = opts.phone;
|
|
176
|
+
if (opts.city) body.city = opts.city;
|
|
177
|
+
if (opts.state) body.stateProvince = opts.state;
|
|
178
|
+
if (opts.zip) body.zip = opts.zip;
|
|
179
|
+
if (opts.country) body.country = opts.country;
|
|
180
|
+
if (opts.organization) body.organization = opts.organization;
|
|
181
|
+
if (opts.address) body.addressLine1 = opts.address;
|
|
182
|
+
|
|
183
|
+
const res = await this.post('/domain/register', body);
|
|
184
|
+
return {
|
|
185
|
+
domain: opts.domain,
|
|
186
|
+
status: res.status ?? 'UNKNOWN',
|
|
187
|
+
message: res.message ?? `Domain ${opts.domain} registration ${res.status === 'SUCCESS' ? 'successful' : 'failed'}.`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ── Domain Listing ──────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
/** List all domains owned by the account. */
|
|
194
|
+
async listDomains(): Promise<OwnedDomain[]> {
|
|
195
|
+
const res = await this.post('/domain/listAll', {});
|
|
196
|
+
return (res.domains ?? []).map((d: any) => ({
|
|
197
|
+
domain: d.domain ?? '',
|
|
198
|
+
status: d.status ?? '',
|
|
199
|
+
tld: d.tld ?? '',
|
|
200
|
+
createDate: d.createDate ?? '',
|
|
201
|
+
expireDate: d.expireDate ?? '',
|
|
202
|
+
securityLock: d.securityLock === '1' || d.securityLock === true,
|
|
203
|
+
whoisPrivacy: d.whoisPrivacy === '1' || d.whoisPrivacy === true,
|
|
204
|
+
autoRenew: d.autoRenew === '1' || d.autoRenew === true,
|
|
205
|
+
notLocal: d.notLocal === '1' || d.notLocal === true,
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ── DNS Management ──────────────────────────────────────────────────────
|
|
210
|
+
|
|
211
|
+
/** Create a DNS record for a domain. */
|
|
212
|
+
async createDnsRecord(domain: string, record: DnsRecordInput): Promise<{ id: string }> {
|
|
213
|
+
const body: Record<string, unknown> = {
|
|
214
|
+
name: record.name ?? '',
|
|
215
|
+
type: record.type,
|
|
216
|
+
content: record.content,
|
|
217
|
+
ttl: record.ttl ?? '300',
|
|
218
|
+
};
|
|
219
|
+
if (record.prio) body.prio = record.prio;
|
|
220
|
+
|
|
221
|
+
const res = await this.post(`/dns/create/${domain}`, body);
|
|
222
|
+
return { id: String(res.id ?? '') };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Edit an existing DNS record. */
|
|
226
|
+
async editDnsRecord(domain: string, recordId: string, record: Partial<DnsRecordInput>): Promise<void> {
|
|
227
|
+
const body: Record<string, unknown> = {};
|
|
228
|
+
if (record.name !== undefined) body.name = record.name;
|
|
229
|
+
if (record.type) body.type = record.type;
|
|
230
|
+
if (record.content) body.content = record.content;
|
|
231
|
+
if (record.ttl) body.ttl = record.ttl;
|
|
232
|
+
if (record.prio) body.prio = record.prio;
|
|
233
|
+
|
|
234
|
+
await this.post(`/dns/edit/${domain}/${recordId}`, body);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Delete a DNS record. */
|
|
238
|
+
async deleteDnsRecord(domain: string, recordId: string): Promise<void> {
|
|
239
|
+
await this.post(`/dns/delete/${domain}/${recordId}`, {});
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** List all DNS records for a domain. */
|
|
243
|
+
async listDnsRecords(domain: string): Promise<DnsRecord[]> {
|
|
244
|
+
const res = await this.post(`/dns/retrieve/${domain}`, {});
|
|
245
|
+
return (res.records ?? []).map((r: any) => this.mapDnsRecord(r));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Retrieve DNS records by name and type. */
|
|
249
|
+
async getDnsRecordsByNameType(domain: string, type: string, subdomain?: string): Promise<DnsRecord[]> {
|
|
250
|
+
const sub = subdomain ?? '';
|
|
251
|
+
const res = await this.post(`/dns/retrieveByNameType/${domain}/${type}/${sub}`, {});
|
|
252
|
+
return (res.records ?? []).map((r: any) => this.mapDnsRecord(r));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ── Domain Transfer ─────────────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
/** Initiate a domain transfer to Porkbun. */
|
|
258
|
+
async transferDomain(domain: string, authCode: string): Promise<TransferDomainResult> {
|
|
259
|
+
const res = await this.post('/domain/transfer', {
|
|
260
|
+
domain,
|
|
261
|
+
authCode,
|
|
262
|
+
});
|
|
263
|
+
return {
|
|
264
|
+
domain,
|
|
265
|
+
status: res.status ?? 'UNKNOWN',
|
|
266
|
+
message: res.message ?? `Domain transfer for ${domain} ${res.status === 'SUCCESS' ? 'initiated' : 'failed'}.`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// ── Helpers ─────────────────────────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* POST helper — all Porkbun endpoints are POST and require
|
|
274
|
+
* apikey + secretapikey in the body.
|
|
275
|
+
*/
|
|
276
|
+
private async post(path: string, body: Record<string, unknown>): Promise<any> {
|
|
277
|
+
const url = `${this.config.baseUrl}${path}`;
|
|
278
|
+
const payload = {
|
|
279
|
+
apikey: this.config.apiKey,
|
|
280
|
+
secretapikey: this.config.secretApiKey,
|
|
281
|
+
...body,
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const res = await globalThis.fetch(url, {
|
|
285
|
+
method: 'POST',
|
|
286
|
+
headers: { 'Content-Type': 'application/json' },
|
|
287
|
+
body: JSON.stringify(payload),
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return this.handleResponse(res);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Parse the response JSON, throwing on HTTP errors. */
|
|
294
|
+
private async handleResponse(res: Response): Promise<any> {
|
|
295
|
+
const text = await res.text();
|
|
296
|
+
let data: any;
|
|
297
|
+
try {
|
|
298
|
+
data = JSON.parse(text);
|
|
299
|
+
} catch {
|
|
300
|
+
throw new Error(`Porkbun API ${res.status}: invalid JSON — ${text.slice(0, 500)}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!res.ok) {
|
|
304
|
+
throw new Error(
|
|
305
|
+
`Porkbun API ${res.status}: ${data.message ?? text.slice(0, 500)}`,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (data.status === 'ERROR') {
|
|
310
|
+
throw new Error(`Porkbun API error: ${data.message ?? 'unknown error'}`);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return data;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private mapDnsRecord(r: any): DnsRecord {
|
|
317
|
+
return {
|
|
318
|
+
id: String(r.id ?? ''),
|
|
319
|
+
name: r.name ?? '',
|
|
320
|
+
type: r.type ?? '',
|
|
321
|
+
content: r.content ?? '',
|
|
322
|
+
ttl: String(r.ttl ?? '300'),
|
|
323
|
+
prio: r.prio ? String(r.prio) : null,
|
|
324
|
+
notes: r.notes ?? null,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview Porkbun Domain Registrar Extension for AgentOS.
|
|
4
|
+
*
|
|
5
|
+
* Provides 5 tools for searching, registering, and managing domains and DNS
|
|
6
|
+
* records via the Porkbun API v3.
|
|
7
|
+
*
|
|
8
|
+
* @module @framers/agentos-ext-domain-porkbun
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { PorkbunService } from './PorkbunService.js';
|
|
12
|
+
import type { PorkbunConfig } from './PorkbunService.js';
|
|
13
|
+
import { PorkbunSearchDomainTool } from './tools/searchDomain.js';
|
|
14
|
+
import { PorkbunRegisterDomainTool } from './tools/registerDomain.js';
|
|
15
|
+
import { PorkbunListDomainsTool } from './tools/listDomains.js';
|
|
16
|
+
import { PorkbunConfigureDnsTool } from './tools/configureDns.js';
|
|
17
|
+
import { PorkbunTransferDomainTool } from './tools/transferDomain.js';
|
|
18
|
+
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
// Options
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
export interface PorkbunDomainOptions {
|
|
24
|
+
apiKey?: string;
|
|
25
|
+
secretApiKey?: string;
|
|
26
|
+
secrets?: Record<string, string>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Secret Resolution
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
function resolveConfig(opts: PorkbunDomainOptions, secrets: Record<string, string>): PorkbunConfig {
|
|
34
|
+
return {
|
|
35
|
+
apiKey:
|
|
36
|
+
opts.apiKey ?? secrets['porkbun.apiKey']
|
|
37
|
+
?? process.env.PORKBUN_API_KEY ?? '',
|
|
38
|
+
secretApiKey:
|
|
39
|
+
opts.secretApiKey ?? secrets['porkbun.secretApiKey']
|
|
40
|
+
?? process.env.PORKBUN_SECRET_API_KEY ?? '',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Extension Context
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
export interface ExtensionContext {
|
|
49
|
+
options?: Record<string, unknown>;
|
|
50
|
+
secrets?: Record<string, string>;
|
|
51
|
+
getSecret?: (key: string) => string | undefined;
|
|
52
|
+
logger?: { info: (msg: string) => void };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface ExtensionPack {
|
|
56
|
+
name: string;
|
|
57
|
+
version: string;
|
|
58
|
+
descriptors: Array<{ id: string; kind: string; priority?: number; payload: unknown }>;
|
|
59
|
+
onActivate?: () => Promise<void>;
|
|
60
|
+
onDeactivate?: () => Promise<void>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
64
|
+
// Factory
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
|
|
67
|
+
export function createExtensionPack(context: ExtensionContext): ExtensionPack {
|
|
68
|
+
const opts = (context.options ?? {}) as PorkbunDomainOptions;
|
|
69
|
+
const secrets = opts.secrets ?? context.secrets ?? {};
|
|
70
|
+
const config = resolveConfig(opts, secrets);
|
|
71
|
+
|
|
72
|
+
const service = new PorkbunService(config);
|
|
73
|
+
|
|
74
|
+
const searchDomainTool = new PorkbunSearchDomainTool(service);
|
|
75
|
+
const registerDomainTool = new PorkbunRegisterDomainTool(service);
|
|
76
|
+
const listDomainsTool = new PorkbunListDomainsTool(service);
|
|
77
|
+
const configureDnsTool = new PorkbunConfigureDnsTool(service);
|
|
78
|
+
const transferDomainTool = new PorkbunTransferDomainTool(service);
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
name: '@framers/agentos-ext-domain-porkbun',
|
|
82
|
+
version: '0.1.0',
|
|
83
|
+
descriptors: [
|
|
84
|
+
{ id: 'porkbunSearchDomain', kind: 'tool', priority: 40, payload: searchDomainTool },
|
|
85
|
+
{ id: 'porkbunRegisterDomain', kind: 'tool', priority: 40, payload: registerDomainTool },
|
|
86
|
+
{ id: 'porkbunListDomains', kind: 'tool', priority: 40, payload: listDomainsTool },
|
|
87
|
+
{ id: 'porkbunConfigureDns', kind: 'tool', priority: 40, payload: configureDnsTool },
|
|
88
|
+
{ id: 'porkbunTransferDomain', kind: 'tool', priority: 40, payload: transferDomainTool },
|
|
89
|
+
],
|
|
90
|
+
onActivate: async () => {
|
|
91
|
+
if (!config.apiKey) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
'Porkbun: no API key provided. Set PORKBUN_API_KEY environment variable, '
|
|
94
|
+
+ 'or provide it via secrets["porkbun.apiKey"].',
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
if (!config.secretApiKey) {
|
|
98
|
+
throw new Error(
|
|
99
|
+
'Porkbun: no secret API key provided. Set PORKBUN_SECRET_API_KEY environment variable, '
|
|
100
|
+
+ 'or provide it via secrets["porkbun.secretApiKey"].',
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
await service.initialize();
|
|
104
|
+
},
|
|
105
|
+
onDeactivate: async () => {
|
|
106
|
+
await service.shutdown();
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
// Public exports
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
export { PorkbunService } from './PorkbunService.js';
|
|
116
|
+
export type {
|
|
117
|
+
PorkbunConfig,
|
|
118
|
+
DomainAvailability,
|
|
119
|
+
DomainPricing,
|
|
120
|
+
OwnedDomain,
|
|
121
|
+
DnsRecord,
|
|
122
|
+
DnsRecordInput,
|
|
123
|
+
RegisterDomainOptions,
|
|
124
|
+
RegisterDomainResult,
|
|
125
|
+
TransferDomainResult,
|
|
126
|
+
} from './PorkbunService.js';
|
|
127
|
+
export { PorkbunSearchDomainTool } from './tools/searchDomain.js';
|
|
128
|
+
export { PorkbunRegisterDomainTool } from './tools/registerDomain.js';
|
|
129
|
+
export { PorkbunListDomainsTool } from './tools/listDomains.js';
|
|
130
|
+
export { PorkbunConfigureDnsTool } from './tools/configureDns.js';
|
|
131
|
+
export { PorkbunTransferDomainTool } from './tools/transferDomain.js';
|
|
132
|
+
|
|
133
|
+
export default createExtensionPack;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview PorkbunConfigureDnsTool — manage DNS records for a Porkbun domain.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PorkbunService, DnsRecord } from '../PorkbunService.js';
|
|
7
|
+
|
|
8
|
+
export interface ConfigureDnsInput {
|
|
9
|
+
domain: string;
|
|
10
|
+
action?: 'add' | 'remove' | 'list' | 'update';
|
|
11
|
+
recordId?: string;
|
|
12
|
+
type?: 'A' | 'AAAA' | 'CNAME' | 'TXT' | 'MX' | 'NS' | 'SRV' | 'TLSA' | 'CAA' | 'ALIAS';
|
|
13
|
+
name?: string;
|
|
14
|
+
content?: string;
|
|
15
|
+
ttl?: string;
|
|
16
|
+
prio?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class PorkbunConfigureDnsTool {
|
|
20
|
+
readonly id = 'porkbunConfigureDns';
|
|
21
|
+
readonly name = 'porkbunConfigureDns';
|
|
22
|
+
readonly displayName = 'Configure DNS Records';
|
|
23
|
+
readonly description = 'Manage DNS records for a domain on Porkbun. Supports adding, removing, listing, and updating records of type A, AAAA, CNAME, TXT, MX, NS, SRV, TLSA, CAA, and ALIAS.';
|
|
24
|
+
readonly category = 'domain';
|
|
25
|
+
readonly version = '0.1.0';
|
|
26
|
+
readonly hasSideEffects = true;
|
|
27
|
+
|
|
28
|
+
readonly inputSchema = {
|
|
29
|
+
type: 'object' as const,
|
|
30
|
+
properties: {
|
|
31
|
+
domain: { type: 'string', description: 'The domain name (e.g. "example.com")' },
|
|
32
|
+
action: { type: 'string', enum: ['add', 'remove', 'list', 'update'], description: 'Action to perform (default: list)' },
|
|
33
|
+
recordId: { type: 'string', description: 'The DNS record ID (required for remove and update actions)' },
|
|
34
|
+
type: {
|
|
35
|
+
type: 'string',
|
|
36
|
+
enum: ['A', 'AAAA', 'CNAME', 'TXT', 'MX', 'NS', 'SRV', 'TLSA', 'CAA', 'ALIAS'],
|
|
37
|
+
description: 'DNS record type (required for add action)',
|
|
38
|
+
},
|
|
39
|
+
name: { type: 'string', description: 'Subdomain or record name (e.g. "www", "mail"). Empty string or omit for root domain.' },
|
|
40
|
+
content: { type: 'string', description: 'Record value/content (e.g. IP address, hostname, TXT value)' },
|
|
41
|
+
ttl: { type: 'string', description: 'Time to live in seconds (default: "300")' },
|
|
42
|
+
prio: { type: 'string', description: 'Priority (required for MX records, optional for others)' },
|
|
43
|
+
},
|
|
44
|
+
required: ['domain'],
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
constructor(private service: PorkbunService) {}
|
|
48
|
+
|
|
49
|
+
async execute(args: ConfigureDnsInput): Promise<{
|
|
50
|
+
success: boolean;
|
|
51
|
+
data?: DnsRecord[] | DnsRecord | { id: string } | { deleted: boolean };
|
|
52
|
+
error?: string;
|
|
53
|
+
}> {
|
|
54
|
+
try {
|
|
55
|
+
const action = args.action ?? 'list';
|
|
56
|
+
|
|
57
|
+
switch (action) {
|
|
58
|
+
case 'list': {
|
|
59
|
+
const records = await this.service.listDnsRecords(args.domain);
|
|
60
|
+
return { success: true, data: records };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
case 'add': {
|
|
64
|
+
if (!args.type) {
|
|
65
|
+
return { success: false, error: 'Record type is required for the "add" action. Provide one of: A, AAAA, CNAME, TXT, MX, NS, SRV, TLSA, CAA, ALIAS.' };
|
|
66
|
+
}
|
|
67
|
+
if (!args.content) {
|
|
68
|
+
return { success: false, error: 'Record content is required for the "add" action (e.g. IP address, hostname, TXT value).' };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const created = await this.service.createDnsRecord(args.domain, {
|
|
72
|
+
name: args.name,
|
|
73
|
+
type: args.type,
|
|
74
|
+
content: args.content,
|
|
75
|
+
ttl: args.ttl,
|
|
76
|
+
prio: args.prio,
|
|
77
|
+
});
|
|
78
|
+
return { success: true, data: created };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case 'remove': {
|
|
82
|
+
if (!args.recordId) {
|
|
83
|
+
return { success: false, error: 'Record ID is required for the "remove" action. Use action "list" first to find the record ID.' };
|
|
84
|
+
}
|
|
85
|
+
await this.service.deleteDnsRecord(args.domain, args.recordId);
|
|
86
|
+
return { success: true, data: { deleted: true } };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
case 'update': {
|
|
90
|
+
if (!args.recordId) {
|
|
91
|
+
return { success: false, error: 'Record ID is required for the "update" action. Use action "list" first to find the record ID.' };
|
|
92
|
+
}
|
|
93
|
+
await this.service.editDnsRecord(args.domain, args.recordId, {
|
|
94
|
+
name: args.name,
|
|
95
|
+
type: args.type,
|
|
96
|
+
content: args.content,
|
|
97
|
+
ttl: args.ttl,
|
|
98
|
+
prio: args.prio,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Return the updated records for the domain so the caller can confirm
|
|
102
|
+
const records = await this.service.listDnsRecords(args.domain);
|
|
103
|
+
const updated = records.find(r => r.id === args.recordId);
|
|
104
|
+
return { success: true, data: updated ?? records };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
default:
|
|
108
|
+
return { success: false, error: `Unknown action: "${action}". Use one of: add, remove, list, update.` };
|
|
109
|
+
}
|
|
110
|
+
} catch (err: any) {
|
|
111
|
+
return { success: false, error: err.message };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
/**
|
|
3
|
+
* @fileoverview PorkbunListDomainsTool — list all domains owned by the Porkbun account.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { PorkbunService, OwnedDomain } from '../PorkbunService.js';
|
|
7
|
+
|
|
8
|
+
export class PorkbunListDomainsTool {
|
|
9
|
+
readonly id = 'porkbunListDomains';
|
|
10
|
+
readonly name = 'porkbunListDomains';
|
|
11
|
+
readonly displayName = 'List Owned Domains';
|
|
12
|
+
readonly description = 'List all domains owned by the connected Porkbun account. Shows domain name, status, TLD, creation and expiry dates, security lock, WHOIS privacy, and auto-renew settings.';
|
|
13
|
+
readonly category = 'domain';
|
|
14
|
+
readonly version = '0.1.0';
|
|
15
|
+
readonly hasSideEffects = false;
|
|
16
|
+
|
|
17
|
+
readonly inputSchema = {
|
|
18
|
+
type: 'object' as const,
|
|
19
|
+
properties: {},
|
|
20
|
+
required: [] as string[],
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
constructor(private service: PorkbunService) {}
|
|
24
|
+
|
|
25
|
+
async execute(_args: Record<string, never>): Promise<{ success: boolean; data?: OwnedDomain[]; error?: string }> {
|
|
26
|
+
try {
|
|
27
|
+
const domains = await this.service.listDomains();
|
|
28
|
+
return { success: true, data: domains };
|
|
29
|
+
} catch (err: any) {
|
|
30
|
+
return { success: false, error: err.message };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|