@framers/agentos-ext-cloud-digitalocean 0.1.0 → 0.1.1
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/manifest.json +2 -2
- package/package.json +14 -6
- package/src/DigitalOceanService.ts +0 -514
- package/src/index.ts +0 -130
- package/src/tools/createApp.ts +0 -69
- package/src/tools/createDroplet.ts +0 -74
- package/src/tools/deleteResource.ts +0 -64
- package/src/tools/deployApp.ts +0 -41
- package/src/tools/listResources.ts +0 -66
- package/src/tools/manageDns.ts +0 -131
- package/test/DigitalOceanService.test.ts +0 -435
- package/test/index.test.ts +0 -58
- package/test/tools.test.ts +0 -478
- package/tsconfig.json +0 -22
package/manifest.json
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
"$schema": "https://agentos.sh/schemas/extension-manifest-v1.json",
|
|
3
3
|
"id": "com.framers.cloud.digitalocean",
|
|
4
4
|
"name": "DigitalOcean Cloud Management",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.1",
|
|
6
6
|
"description": "Manage DigitalOcean App Platform apps, Droplets, DNS records, and deployments. Supports creating and deploying PaaS apps from Git, provisioning VPS droplets, and full DNS record management.",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "Frame.dev",
|
|
9
9
|
"email": "support@frame.dev",
|
|
10
|
-
"url": "https://github.com/
|
|
10
|
+
"url": "https://github.com/framerslab"
|
|
11
11
|
},
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"keywords": ["cloud", "digitalocean", "droplet", "app-platform", "dns", "vps", "paas"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@framers/agentos-ext-cloud-digitalocean",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "DigitalOcean cloud extension — manage App Platform apps, Droplets, DNS records, and deployments via the DigitalOcean REST API.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -38,26 +38,34 @@
|
|
|
38
38
|
"license": "MIT",
|
|
39
39
|
"repository": {
|
|
40
40
|
"type": "git",
|
|
41
|
-
"url": "https://github.com/
|
|
41
|
+
"url": "git+https://github.com/framerslab/agentos-extensions.git",
|
|
42
42
|
"directory": "registry/curated/cloud/digitalocean"
|
|
43
43
|
},
|
|
44
44
|
"publishConfig": {
|
|
45
45
|
"access": "public"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@framers/agentos": "
|
|
48
|
+
"@framers/agentos": ">=0.10.40"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@framers/agentos": "^0.6.6",
|
|
52
51
|
"@types/node": "^20.12.12",
|
|
53
52
|
"rimraf": "^5.0.7",
|
|
54
53
|
"typescript": "^5.4.5",
|
|
55
|
-
"vitest": "^
|
|
54
|
+
"vitest": "^3.2.7",
|
|
55
|
+
"@framers/agentos": "^0.10.40"
|
|
56
56
|
},
|
|
57
|
+
"homepage": "https://github.com/framerslab/agentos-extensions/tree/master/registry/curated/cloud/digitalocean#readme",
|
|
58
|
+
"bugs": {
|
|
59
|
+
"url": "https://github.com/framerslab/agentos-extensions/issues"
|
|
60
|
+
},
|
|
61
|
+
"files": [
|
|
62
|
+
"dist",
|
|
63
|
+
"manifest.json"
|
|
64
|
+
],
|
|
57
65
|
"scripts": {
|
|
58
66
|
"build": "tsc",
|
|
59
67
|
"dev": "tsc --watch",
|
|
60
|
-
"test": "vitest run",
|
|
68
|
+
"test": "vitest run --passWithNoTests",
|
|
61
69
|
"test:watch": "vitest",
|
|
62
70
|
"typecheck": "tsc --noEmit",
|
|
63
71
|
"clean": "rimraf dist"
|
|
@@ -1,514 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview DigitalOcean REST API service layer.
|
|
4
|
-
*
|
|
5
|
-
* Wraps the DigitalOcean API v2 for App Platform management, Droplet
|
|
6
|
-
* provisioning, DNS record management, and deployment triggering.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Types
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
|
|
13
|
-
export interface DigitalOceanConfig {
|
|
14
|
-
/** DigitalOcean personal access token */
|
|
15
|
-
token: string;
|
|
16
|
-
/** API base URL (defaults to https://api.digitalocean.com/v2) */
|
|
17
|
-
baseUrl?: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface DOApp {
|
|
21
|
-
id: string;
|
|
22
|
-
defaultIngress: string;
|
|
23
|
-
liveUrl: string;
|
|
24
|
-
activeDeployment?: DODeployment;
|
|
25
|
-
spec: DOAppSpec;
|
|
26
|
-
createdAt: string;
|
|
27
|
-
updatedAt: string;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface DOAppSpec {
|
|
31
|
-
name: string;
|
|
32
|
-
region?: string;
|
|
33
|
-
services?: DOAppServiceSpec[];
|
|
34
|
-
staticSites?: DOAppStaticSiteSpec[];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface DOAppServiceSpec {
|
|
38
|
-
name: string;
|
|
39
|
-
gitSource?: { repoCloneUrl: string; branch: string };
|
|
40
|
-
buildCommand?: string;
|
|
41
|
-
runCommand?: string;
|
|
42
|
-
environmentSlug?: string;
|
|
43
|
-
instanceCount?: number;
|
|
44
|
-
instanceSizeSlug?: string;
|
|
45
|
-
envs?: DOAppEnvVar[];
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export interface DOAppStaticSiteSpec {
|
|
49
|
-
name: string;
|
|
50
|
-
gitSource?: { repoCloneUrl: string; branch: string };
|
|
51
|
-
buildCommand?: string;
|
|
52
|
-
outputDir?: string;
|
|
53
|
-
envs?: DOAppEnvVar[];
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface DOAppEnvVar {
|
|
57
|
-
key: string;
|
|
58
|
-
value?: string;
|
|
59
|
-
scope?: 'RUN_TIME' | 'BUILD_TIME' | 'RUN_AND_BUILD_TIME';
|
|
60
|
-
type?: 'GENERAL' | 'SECRET';
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export interface DODeployment {
|
|
64
|
-
id: string;
|
|
65
|
-
phase: 'UNKNOWN' | 'PENDING_BUILD' | 'BUILDING' | 'PENDING_DEPLOY' | 'DEPLOYING' | 'ACTIVE' | 'SUPERSEDED' | 'ERROR';
|
|
66
|
-
createdAt: string;
|
|
67
|
-
updatedAt: string;
|
|
68
|
-
cause: string;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export interface DODroplet {
|
|
72
|
-
id: number;
|
|
73
|
-
name: string;
|
|
74
|
-
status: 'new' | 'active' | 'off' | 'archive';
|
|
75
|
-
memory: number;
|
|
76
|
-
vcpus: number;
|
|
77
|
-
disk: number;
|
|
78
|
-
region: { slug: string; name: string };
|
|
79
|
-
image: { id: number; slug: string; name: string };
|
|
80
|
-
sizeSlug: string;
|
|
81
|
-
networks: {
|
|
82
|
-
v4: Array<{ ipAddress: string; type: string }>;
|
|
83
|
-
v6: Array<{ ipAddress: string; type: string }>;
|
|
84
|
-
};
|
|
85
|
-
tags: string[];
|
|
86
|
-
createdAt: string;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export interface DODomain {
|
|
90
|
-
name: string;
|
|
91
|
-
ttl: number;
|
|
92
|
-
zoneFile: string;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export interface DODomainRecord {
|
|
96
|
-
id: number;
|
|
97
|
-
type: string;
|
|
98
|
-
name: string;
|
|
99
|
-
data: string;
|
|
100
|
-
priority: number | null;
|
|
101
|
-
port: number | null;
|
|
102
|
-
ttl: number;
|
|
103
|
-
weight: number | null;
|
|
104
|
-
flags: number | null;
|
|
105
|
-
tag: string | null;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export interface CreateAppOptions {
|
|
109
|
-
/** App name */
|
|
110
|
-
name: string;
|
|
111
|
-
/** Git repository clone URL */
|
|
112
|
-
gitUrl: string;
|
|
113
|
-
/** Git branch (default: main) */
|
|
114
|
-
branch?: string;
|
|
115
|
-
/** Region slug (default: nyc) */
|
|
116
|
-
region?: string;
|
|
117
|
-
/** Build command */
|
|
118
|
-
buildCommand?: string;
|
|
119
|
-
/** Run command (for services) */
|
|
120
|
-
runCommand?: string;
|
|
121
|
-
/** Output directory (for static sites) */
|
|
122
|
-
outputDir?: string;
|
|
123
|
-
/** Whether this is a static site (default: false, treated as service) */
|
|
124
|
-
isStatic?: boolean;
|
|
125
|
-
/** Environment variables */
|
|
126
|
-
envVars?: Record<string, string>;
|
|
127
|
-
/** Instance size slug (e.g. "basic-xxs", "professional-xs") */
|
|
128
|
-
instanceSizeSlug?: string;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export interface CreateDropletOptions {
|
|
132
|
-
/** Droplet name */
|
|
133
|
-
name: string;
|
|
134
|
-
/** Region slug (e.g. "nyc1", "sfo3", "ams3") */
|
|
135
|
-
region: string;
|
|
136
|
-
/** Size slug (e.g. "s-1vcpu-1gb", "s-2vcpu-4gb") */
|
|
137
|
-
size: string;
|
|
138
|
-
/** Image slug or ID (e.g. "ubuntu-24-04-x64", "docker-20-04") */
|
|
139
|
-
image: string;
|
|
140
|
-
/** SSH key IDs or fingerprints */
|
|
141
|
-
sshKeys?: (string | number)[];
|
|
142
|
-
/** User data script (cloud-init) */
|
|
143
|
-
userData?: string;
|
|
144
|
-
/** Enable backups */
|
|
145
|
-
backups?: boolean;
|
|
146
|
-
/** Enable IPv6 */
|
|
147
|
-
ipv6?: boolean;
|
|
148
|
-
/** Tags */
|
|
149
|
-
tags?: string[];
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// ---------------------------------------------------------------------------
|
|
153
|
-
// Service
|
|
154
|
-
// ---------------------------------------------------------------------------
|
|
155
|
-
|
|
156
|
-
export class DigitalOceanService {
|
|
157
|
-
private config: DigitalOceanConfig;
|
|
158
|
-
private running = false;
|
|
159
|
-
|
|
160
|
-
constructor(config: DigitalOceanConfig) {
|
|
161
|
-
this.config = {
|
|
162
|
-
...config,
|
|
163
|
-
baseUrl: config.baseUrl ?? 'https://api.digitalocean.com/v2',
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
async initialize(): Promise<void> {
|
|
168
|
-
// Validate token by fetching account info
|
|
169
|
-
const res = await this.fetch('/account');
|
|
170
|
-
if (!res.ok) {
|
|
171
|
-
const body = await res.text();
|
|
172
|
-
throw new Error(`DigitalOcean auth failed: ${res.status} ${body}`);
|
|
173
|
-
}
|
|
174
|
-
this.running = true;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
async shutdown(): Promise<void> {
|
|
178
|
-
this.running = false;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
get isRunning(): boolean {
|
|
182
|
-
return this.running;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// -- Apps (App Platform) --------------------------------------------------
|
|
186
|
-
|
|
187
|
-
/** Create an App Platform app from a spec. */
|
|
188
|
-
async createApp(opts: CreateAppOptions): Promise<DOApp> {
|
|
189
|
-
const envs: DOAppEnvVar[] = opts.envVars
|
|
190
|
-
? Object.entries(opts.envVars).map(([key, value]) => ({
|
|
191
|
-
key,
|
|
192
|
-
value,
|
|
193
|
-
scope: 'RUN_AND_BUILD_TIME' as const,
|
|
194
|
-
type: 'GENERAL' as const,
|
|
195
|
-
}))
|
|
196
|
-
: [];
|
|
197
|
-
|
|
198
|
-
const gitSource = {
|
|
199
|
-
repo_clone_url: opts.gitUrl,
|
|
200
|
-
branch: opts.branch ?? 'main',
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
const spec: Record<string, unknown> = {
|
|
204
|
-
name: opts.name,
|
|
205
|
-
region: opts.region ?? 'nyc',
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
if (opts.isStatic) {
|
|
209
|
-
spec.static_sites = [{
|
|
210
|
-
name: opts.name,
|
|
211
|
-
git: gitSource,
|
|
212
|
-
build_command: opts.buildCommand,
|
|
213
|
-
output_dir: opts.outputDir ?? 'build',
|
|
214
|
-
envs: envs.length > 0 ? envs : undefined,
|
|
215
|
-
}];
|
|
216
|
-
} else {
|
|
217
|
-
spec.services = [{
|
|
218
|
-
name: opts.name,
|
|
219
|
-
git: gitSource,
|
|
220
|
-
build_command: opts.buildCommand,
|
|
221
|
-
run_command: opts.runCommand,
|
|
222
|
-
environment_slug: 'node-js',
|
|
223
|
-
instance_count: 1,
|
|
224
|
-
instance_size_slug: opts.instanceSizeSlug ?? 'basic-xxs',
|
|
225
|
-
envs: envs.length > 0 ? envs : undefined,
|
|
226
|
-
}];
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const res = await this.fetch('/apps', {
|
|
230
|
-
method: 'POST',
|
|
231
|
-
body: JSON.stringify({ spec }),
|
|
232
|
-
});
|
|
233
|
-
const data = await this.json(res);
|
|
234
|
-
return this.mapApp(data.app);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/** List all apps. */
|
|
238
|
-
async listApps(page = 1, perPage = 20): Promise<DOApp[]> {
|
|
239
|
-
const res = await this.fetch(`/apps?page=${page}&per_page=${perPage}`);
|
|
240
|
-
const data = await this.json(res);
|
|
241
|
-
return (data.apps ?? []).map((a: any) => this.mapApp(a));
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/** Get an app by ID. */
|
|
245
|
-
async getApp(appId: string): Promise<DOApp> {
|
|
246
|
-
const res = await this.fetch(`/apps/${appId}`);
|
|
247
|
-
const data = await this.json(res);
|
|
248
|
-
return this.mapApp(data.app);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
/** Delete an app. */
|
|
252
|
-
async deleteApp(appId: string): Promise<void> {
|
|
253
|
-
const res = await this.fetch(`/apps/${appId}`, { method: 'DELETE' });
|
|
254
|
-
if (!res.ok) {
|
|
255
|
-
const text = await res.text();
|
|
256
|
-
throw new Error(`DigitalOcean API ${res.status}: ${text.slice(0, 500)}`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/** Trigger a new deployment for an app. */
|
|
261
|
-
async createDeployment(appId: string, forceBuild = false): Promise<DODeployment> {
|
|
262
|
-
const body: Record<string, unknown> = {};
|
|
263
|
-
if (forceBuild) body.force_build = true;
|
|
264
|
-
|
|
265
|
-
const res = await this.fetch(`/apps/${appId}/deployments`, {
|
|
266
|
-
method: 'POST',
|
|
267
|
-
body: JSON.stringify(body),
|
|
268
|
-
});
|
|
269
|
-
const data = await this.json(res);
|
|
270
|
-
return this.mapDeployment(data.deployment);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// -- Droplets -------------------------------------------------------------
|
|
274
|
-
|
|
275
|
-
/** Create a new Droplet. */
|
|
276
|
-
async createDroplet(opts: CreateDropletOptions): Promise<DODroplet> {
|
|
277
|
-
const body: Record<string, unknown> = {
|
|
278
|
-
name: opts.name,
|
|
279
|
-
region: opts.region,
|
|
280
|
-
size: opts.size,
|
|
281
|
-
image: opts.image,
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
if (opts.sshKeys?.length) body.ssh_keys = opts.sshKeys;
|
|
285
|
-
if (opts.userData) body.user_data = opts.userData;
|
|
286
|
-
if (opts.backups) body.backups = true;
|
|
287
|
-
if (opts.ipv6) body.ipv6 = true;
|
|
288
|
-
if (opts.tags?.length) body.tags = opts.tags;
|
|
289
|
-
|
|
290
|
-
const res = await this.fetch('/droplets', {
|
|
291
|
-
method: 'POST',
|
|
292
|
-
body: JSON.stringify(body),
|
|
293
|
-
});
|
|
294
|
-
const data = await this.json(res);
|
|
295
|
-
return this.mapDroplet(data.droplet);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/** List all Droplets. */
|
|
299
|
-
async listDroplets(page = 1, perPage = 20, tag?: string): Promise<DODroplet[]> {
|
|
300
|
-
let url = `/droplets?page=${page}&per_page=${perPage}`;
|
|
301
|
-
if (tag) url += `&tag_name=${encodeURIComponent(tag)}`;
|
|
302
|
-
const res = await this.fetch(url);
|
|
303
|
-
const data = await this.json(res);
|
|
304
|
-
return (data.droplets ?? []).map((d: any) => this.mapDroplet(d));
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
/** Delete a Droplet by ID. */
|
|
308
|
-
async deleteDroplet(dropletId: number): Promise<void> {
|
|
309
|
-
const res = await this.fetch(`/droplets/${dropletId}`, { method: 'DELETE' });
|
|
310
|
-
if (!res.ok && res.status !== 204) {
|
|
311
|
-
const text = await res.text();
|
|
312
|
-
throw new Error(`DigitalOcean API ${res.status}: ${text.slice(0, 500)}`);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
// -- DNS ------------------------------------------------------------------
|
|
317
|
-
|
|
318
|
-
/** List all domains. */
|
|
319
|
-
async listDomains(): Promise<DODomain[]> {
|
|
320
|
-
const res = await this.fetch('/domains');
|
|
321
|
-
const data = await this.json(res);
|
|
322
|
-
return (data.domains ?? []).map((d: any) => ({
|
|
323
|
-
name: d.name,
|
|
324
|
-
ttl: d.ttl ?? 1800,
|
|
325
|
-
zoneFile: d.zone_file ?? '',
|
|
326
|
-
}));
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/** Add a domain. */
|
|
330
|
-
async addDomain(name: string): Promise<DODomain> {
|
|
331
|
-
const res = await this.fetch('/domains', {
|
|
332
|
-
method: 'POST',
|
|
333
|
-
body: JSON.stringify({ name }),
|
|
334
|
-
});
|
|
335
|
-
const data = await this.json(res);
|
|
336
|
-
return {
|
|
337
|
-
name: data.domain.name,
|
|
338
|
-
ttl: data.domain.ttl ?? 1800,
|
|
339
|
-
zoneFile: data.domain.zone_file ?? '',
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/** List DNS records for a domain. */
|
|
344
|
-
async listDomainRecords(domain: string): Promise<DODomainRecord[]> {
|
|
345
|
-
const res = await this.fetch(`/domains/${encodeURIComponent(domain)}/records`);
|
|
346
|
-
const data = await this.json(res);
|
|
347
|
-
return (data.domain_records ?? []).map((r: any) => this.mapDomainRecord(r));
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/** Create a DNS record. */
|
|
351
|
-
async createDomainRecord(domain: string, record: {
|
|
352
|
-
type: string;
|
|
353
|
-
name: string;
|
|
354
|
-
data: string;
|
|
355
|
-
ttl?: number;
|
|
356
|
-
priority?: number;
|
|
357
|
-
port?: number;
|
|
358
|
-
weight?: number;
|
|
359
|
-
flags?: number;
|
|
360
|
-
tag?: string;
|
|
361
|
-
}): Promise<DODomainRecord> {
|
|
362
|
-
const res = await this.fetch(`/domains/${encodeURIComponent(domain)}/records`, {
|
|
363
|
-
method: 'POST',
|
|
364
|
-
body: JSON.stringify(record),
|
|
365
|
-
});
|
|
366
|
-
const data = await this.json(res);
|
|
367
|
-
return this.mapDomainRecord(data.domain_record);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/** Update a DNS record. */
|
|
371
|
-
async updateDomainRecord(domain: string, recordId: number, fields: {
|
|
372
|
-
type?: string;
|
|
373
|
-
name?: string;
|
|
374
|
-
data?: string;
|
|
375
|
-
ttl?: number;
|
|
376
|
-
priority?: number;
|
|
377
|
-
port?: number;
|
|
378
|
-
weight?: number;
|
|
379
|
-
flags?: number;
|
|
380
|
-
tag?: string;
|
|
381
|
-
}): Promise<DODomainRecord> {
|
|
382
|
-
const res = await this.fetch(`/domains/${encodeURIComponent(domain)}/records/${recordId}`, {
|
|
383
|
-
method: 'PUT',
|
|
384
|
-
body: JSON.stringify(fields),
|
|
385
|
-
});
|
|
386
|
-
const data = await this.json(res);
|
|
387
|
-
return this.mapDomainRecord(data.domain_record);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/** Delete a DNS record. */
|
|
391
|
-
async deleteDomainRecord(domain: string, recordId: number): Promise<void> {
|
|
392
|
-
const res = await this.fetch(`/domains/${encodeURIComponent(domain)}/records/${recordId}`, {
|
|
393
|
-
method: 'DELETE',
|
|
394
|
-
});
|
|
395
|
-
if (!res.ok && res.status !== 204) {
|
|
396
|
-
const text = await res.text();
|
|
397
|
-
throw new Error(`DigitalOcean API ${res.status}: ${text.slice(0, 500)}`);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
// -- Helpers --------------------------------------------------------------
|
|
402
|
-
|
|
403
|
-
private async fetch(path: string, init?: RequestInit): Promise<Response> {
|
|
404
|
-
const url = `${this.config.baseUrl}${path}`;
|
|
405
|
-
return globalThis.fetch(url, {
|
|
406
|
-
...init,
|
|
407
|
-
headers: {
|
|
408
|
-
'Authorization': `Bearer ${this.config.token}`,
|
|
409
|
-
'Content-Type': 'application/json',
|
|
410
|
-
...(init?.headers as Record<string, string> ?? {}),
|
|
411
|
-
},
|
|
412
|
-
});
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
private async json(res: Response): Promise<any> {
|
|
416
|
-
if (!res.ok) {
|
|
417
|
-
const text = await res.text();
|
|
418
|
-
throw new Error(`DigitalOcean API ${res.status}: ${text.slice(0, 500)}`);
|
|
419
|
-
}
|
|
420
|
-
return res.json();
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
private mapApp(data: any): DOApp {
|
|
424
|
-
return {
|
|
425
|
-
id: data.id,
|
|
426
|
-
defaultIngress: data.default_ingress ?? '',
|
|
427
|
-
liveUrl: data.live_url ?? '',
|
|
428
|
-
activeDeployment: data.active_deployment
|
|
429
|
-
? this.mapDeployment(data.active_deployment)
|
|
430
|
-
: undefined,
|
|
431
|
-
spec: {
|
|
432
|
-
name: data.spec?.name ?? '',
|
|
433
|
-
region: data.spec?.region,
|
|
434
|
-
services: data.spec?.services?.map((s: any) => ({
|
|
435
|
-
name: s.name,
|
|
436
|
-
gitSource: s.git ? { repoCloneUrl: s.git.repo_clone_url, branch: s.git.branch } : undefined,
|
|
437
|
-
buildCommand: s.build_command,
|
|
438
|
-
runCommand: s.run_command,
|
|
439
|
-
environmentSlug: s.environment_slug,
|
|
440
|
-
instanceCount: s.instance_count,
|
|
441
|
-
instanceSizeSlug: s.instance_size_slug,
|
|
442
|
-
envs: s.envs,
|
|
443
|
-
})),
|
|
444
|
-
staticSites: data.spec?.static_sites?.map((s: any) => ({
|
|
445
|
-
name: s.name,
|
|
446
|
-
gitSource: s.git ? { repoCloneUrl: s.git.repo_clone_url, branch: s.git.branch } : undefined,
|
|
447
|
-
buildCommand: s.build_command,
|
|
448
|
-
outputDir: s.output_dir,
|
|
449
|
-
envs: s.envs,
|
|
450
|
-
})),
|
|
451
|
-
},
|
|
452
|
-
createdAt: data.created_at ?? '',
|
|
453
|
-
updatedAt: data.updated_at ?? '',
|
|
454
|
-
};
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
private mapDeployment(data: any): DODeployment {
|
|
458
|
-
return {
|
|
459
|
-
id: data.id,
|
|
460
|
-
phase: data.phase ?? 'UNKNOWN',
|
|
461
|
-
createdAt: data.created_at ?? '',
|
|
462
|
-
updatedAt: data.updated_at ?? '',
|
|
463
|
-
cause: data.cause ?? '',
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
private mapDroplet(data: any): DODroplet {
|
|
468
|
-
return {
|
|
469
|
-
id: data.id,
|
|
470
|
-
name: data.name,
|
|
471
|
-
status: data.status ?? 'new',
|
|
472
|
-
memory: data.memory ?? 0,
|
|
473
|
-
vcpus: data.vcpus ?? 0,
|
|
474
|
-
disk: data.disk ?? 0,
|
|
475
|
-
region: {
|
|
476
|
-
slug: data.region?.slug ?? '',
|
|
477
|
-
name: data.region?.name ?? '',
|
|
478
|
-
},
|
|
479
|
-
image: {
|
|
480
|
-
id: data.image?.id ?? 0,
|
|
481
|
-
slug: data.image?.slug ?? '',
|
|
482
|
-
name: data.image?.name ?? '',
|
|
483
|
-
},
|
|
484
|
-
sizeSlug: data.size_slug ?? data.size?.slug ?? '',
|
|
485
|
-
networks: {
|
|
486
|
-
v4: (data.networks?.v4 ?? []).map((n: any) => ({
|
|
487
|
-
ipAddress: n.ip_address,
|
|
488
|
-
type: n.type,
|
|
489
|
-
})),
|
|
490
|
-
v6: (data.networks?.v6 ?? []).map((n: any) => ({
|
|
491
|
-
ipAddress: n.ip_address,
|
|
492
|
-
type: n.type,
|
|
493
|
-
})),
|
|
494
|
-
},
|
|
495
|
-
tags: data.tags ?? [],
|
|
496
|
-
createdAt: data.created_at ?? '',
|
|
497
|
-
};
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
private mapDomainRecord(data: any): DODomainRecord {
|
|
501
|
-
return {
|
|
502
|
-
id: data.id,
|
|
503
|
-
type: data.type,
|
|
504
|
-
name: data.name,
|
|
505
|
-
data: data.data,
|
|
506
|
-
priority: data.priority ?? null,
|
|
507
|
-
port: data.port ?? null,
|
|
508
|
-
ttl: data.ttl ?? 1800,
|
|
509
|
-
weight: data.weight ?? null,
|
|
510
|
-
flags: data.flags ?? null,
|
|
511
|
-
tag: data.tag ?? null,
|
|
512
|
-
};
|
|
513
|
-
}
|
|
514
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
/**
|
|
3
|
-
* @fileoverview DigitalOcean Cloud Extension for AgentOS.
|
|
4
|
-
*
|
|
5
|
-
* Provides 6 tools for managing App Platform apps, Droplets, DNS records,
|
|
6
|
-
* and deployments via the DigitalOcean REST API v2.
|
|
7
|
-
*
|
|
8
|
-
* @module @framers/agentos-ext-cloud-digitalocean
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { DigitalOceanService } from './DigitalOceanService.js';
|
|
12
|
-
import type { DigitalOceanConfig } from './DigitalOceanService.js';
|
|
13
|
-
import { DOCreateAppTool } from './tools/createApp.js';
|
|
14
|
-
import { DOCreateDropletTool } from './tools/createDroplet.js';
|
|
15
|
-
import { DOListResourcesTool } from './tools/listResources.js';
|
|
16
|
-
import { DODeployAppTool } from './tools/deployApp.js';
|
|
17
|
-
import { DOManageDnsTool } from './tools/manageDns.js';
|
|
18
|
-
import { DODeleteResourceTool } from './tools/deleteResource.js';
|
|
19
|
-
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
|
-
// Options
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
|
|
24
|
-
export interface DigitalOceanCloudOptions {
|
|
25
|
-
token?: string;
|
|
26
|
-
secrets?: Record<string, string>;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
// Secret Resolution
|
|
31
|
-
// ---------------------------------------------------------------------------
|
|
32
|
-
|
|
33
|
-
function resolveConfig(opts: DigitalOceanCloudOptions, secrets: Record<string, string>): DigitalOceanConfig {
|
|
34
|
-
return {
|
|
35
|
-
token:
|
|
36
|
-
opts.token ?? secrets['digitalocean.token']
|
|
37
|
-
?? process.env.DIGITALOCEAN_TOKEN ?? process.env.DO_API_TOKEN ?? '',
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// ---------------------------------------------------------------------------
|
|
42
|
-
// Extension Context
|
|
43
|
-
// ---------------------------------------------------------------------------
|
|
44
|
-
|
|
45
|
-
export interface ExtensionContext {
|
|
46
|
-
options?: Record<string, unknown>;
|
|
47
|
-
secrets?: Record<string, string>;
|
|
48
|
-
getSecret?: (key: string) => string | undefined;
|
|
49
|
-
logger?: { info: (msg: string) => void };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface ExtensionPack {
|
|
53
|
-
name: string;
|
|
54
|
-
version: string;
|
|
55
|
-
descriptors: Array<{ id: string; kind: string; priority?: number; payload: unknown }>;
|
|
56
|
-
onActivate?: () => Promise<void>;
|
|
57
|
-
onDeactivate?: () => Promise<void>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// Factory
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
|
|
64
|
-
export function createExtensionPack(context: ExtensionContext): ExtensionPack {
|
|
65
|
-
const opts = (context.options ?? {}) as DigitalOceanCloudOptions;
|
|
66
|
-
const secrets = opts.secrets ?? context.secrets ?? {};
|
|
67
|
-
const config = resolveConfig(opts, secrets);
|
|
68
|
-
|
|
69
|
-
const service = new DigitalOceanService(config);
|
|
70
|
-
|
|
71
|
-
const createAppTool = new DOCreateAppTool(service);
|
|
72
|
-
const createDropletTool = new DOCreateDropletTool(service);
|
|
73
|
-
const listResourcesTool = new DOListResourcesTool(service);
|
|
74
|
-
const deployAppTool = new DODeployAppTool(service);
|
|
75
|
-
const manageDnsTool = new DOManageDnsTool(service);
|
|
76
|
-
const deleteResourceTool = new DODeleteResourceTool(service);
|
|
77
|
-
|
|
78
|
-
return {
|
|
79
|
-
name: '@framers/agentos-ext-cloud-digitalocean',
|
|
80
|
-
version: '0.1.0',
|
|
81
|
-
descriptors: [
|
|
82
|
-
{ id: 'doCreateApp', kind: 'tool', priority: 40, payload: createAppTool },
|
|
83
|
-
{ id: 'doCreateDroplet', kind: 'tool', priority: 40, payload: createDropletTool },
|
|
84
|
-
{ id: 'doListResources', kind: 'tool', priority: 40, payload: listResourcesTool },
|
|
85
|
-
{ id: 'doDeployApp', kind: 'tool', priority: 40, payload: deployAppTool },
|
|
86
|
-
{ id: 'doManageDns', kind: 'tool', priority: 40, payload: manageDnsTool },
|
|
87
|
-
{ id: 'doDeleteResource', kind: 'tool', priority: 40, payload: deleteResourceTool },
|
|
88
|
-
],
|
|
89
|
-
onActivate: async () => {
|
|
90
|
-
if (!config.token) {
|
|
91
|
-
throw new Error(
|
|
92
|
-
'DigitalOcean: no API token provided. Set DIGITALOCEAN_TOKEN or DO_API_TOKEN environment variable, '
|
|
93
|
-
+ 'or provide it via secrets["digitalocean.token"].',
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
await service.initialize();
|
|
97
|
-
},
|
|
98
|
-
onDeactivate: async () => {
|
|
99
|
-
await service.shutdown();
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ---------------------------------------------------------------------------
|
|
105
|
-
// Public exports
|
|
106
|
-
// ---------------------------------------------------------------------------
|
|
107
|
-
|
|
108
|
-
export { DigitalOceanService } from './DigitalOceanService.js';
|
|
109
|
-
export type {
|
|
110
|
-
DigitalOceanConfig,
|
|
111
|
-
DOApp,
|
|
112
|
-
DOAppSpec,
|
|
113
|
-
DOAppServiceSpec,
|
|
114
|
-
DOAppStaticSiteSpec,
|
|
115
|
-
DOAppEnvVar,
|
|
116
|
-
DODeployment,
|
|
117
|
-
DODroplet,
|
|
118
|
-
DODomain,
|
|
119
|
-
DODomainRecord,
|
|
120
|
-
CreateAppOptions,
|
|
121
|
-
CreateDropletOptions,
|
|
122
|
-
} from './DigitalOceanService.js';
|
|
123
|
-
export { DOCreateAppTool } from './tools/createApp.js';
|
|
124
|
-
export { DOCreateDropletTool } from './tools/createDroplet.js';
|
|
125
|
-
export { DOListResourcesTool } from './tools/listResources.js';
|
|
126
|
-
export { DODeployAppTool } from './tools/deployApp.js';
|
|
127
|
-
export { DOManageDnsTool } from './tools/manageDns.js';
|
|
128
|
-
export { DODeleteResourceTool } from './tools/deleteResource.js';
|
|
129
|
-
|
|
130
|
-
export default createExtensionPack;
|