@mks2508/coolify-mks-cli-mcp 0.4.2 → 0.5.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.
@@ -6,9 +6,9 @@
6
6
  * @module
7
7
  */
8
8
 
9
- import { ok, err, isErr, type Result } from '@mks2508/no-throw'
10
- import { component } from '@mks2508/better-logger'
11
- import { loadConfig, type ICoolifyConfig } from './config.js'
9
+ import { ok, err, isErr, type Result } from "@mks2508/no-throw";
10
+ import { component } from "@mks2508/better-logger";
11
+ import { loadConfig, type ICoolifyConfig } from "./config.js";
12
12
  import {
13
13
  type ICoolifyAppOptions,
14
14
  type ICoolifyAppResult,
@@ -26,31 +26,31 @@ import {
26
26
  type ICoolifyTeam,
27
27
  type ICoolifyUpdateOptions,
28
28
  type IProgressCallback,
29
- } from './types.js'
29
+ } from "./types.js";
30
30
 
31
- const log = component('CoolifyService')
31
+ const log = component("CoolifyService");
32
32
 
33
33
  /**
34
34
  * Coolify API response type.
35
35
  */
36
36
  interface ICoolifyApiResponse<T> {
37
- data?: T
38
- error?: string
39
- status: number
40
- durationMs?: number
37
+ data?: T;
38
+ error?: string;
39
+ status: number;
40
+ durationMs?: number;
41
41
  }
42
42
 
43
43
  /**
44
44
  * Environment variable from Coolify API.
45
45
  */
46
46
  export interface ICoolifyEnvVar {
47
- uuid: string
48
- key: string
49
- value: string
50
- real_value?: string
51
- is_buildtime: boolean
52
- is_runtime: boolean
53
- is_required: boolean
47
+ uuid: string;
48
+ key: string;
49
+ value: string;
50
+ real_value?: string;
51
+ is_buildtime: boolean;
52
+ is_runtime: boolean;
53
+ is_required: boolean;
54
54
  }
55
55
 
56
56
  /**
@@ -72,9 +72,9 @@ export interface ICoolifyEnvVar {
72
72
  * ```
73
73
  */
74
74
  export class CoolifyService {
75
- private baseUrl: string | undefined
76
- private token: string | undefined
77
- private config: ICoolifyConfig = {}
75
+ private baseUrl: string | undefined;
76
+ private token: string | undefined;
77
+ private config: ICoolifyConfig = {};
78
78
 
79
79
  /**
80
80
  * Checks if the service is configured with URL and token.
@@ -82,9 +82,9 @@ export class CoolifyService {
82
82
  * @returns true if both URL and token are set
83
83
  */
84
84
  isConfigured(): boolean {
85
- const hasUrl = !!this.baseUrl || !!process.env.COOLIFY_URL
86
- const hasToken = !!this.token || !!process.env.COOLIFY_TOKEN
87
- return hasUrl && hasToken
85
+ const hasUrl = !!this.baseUrl || !!process.env.COOLIFY_URL;
86
+ const hasToken = !!this.token || !!process.env.COOLIFY_TOKEN;
87
+ return hasUrl && hasToken;
88
88
  }
89
89
 
90
90
  /**
@@ -93,31 +93,43 @@ export class CoolifyService {
93
93
  * @returns Result indicating success or error
94
94
  */
95
95
  async init(): Promise<Result<void, Error>> {
96
- const configResult = await loadConfig()
96
+ const configResult = await loadConfig();
97
97
 
98
98
  if (isErr(configResult)) {
99
- log.error('Failed to load config')
100
- return err(configResult.error)
99
+ log.error("Failed to load config");
100
+ return err(configResult.error);
101
101
  }
102
102
 
103
- this.config = configResult.value
104
- this.baseUrl = this.config.url || process.env.COOLIFY_URL
105
- this.token = this.config.token || process.env.COOLIFY_TOKEN
103
+ this.config = configResult.value;
104
+ this.baseUrl = this.config.url || process.env.COOLIFY_URL;
105
+ this.token = this.config.token || process.env.COOLIFY_TOKEN;
106
106
 
107
107
  if (!this.baseUrl) {
108
- log.error('No Coolify URL configured')
109
- log.info('Set COOLIFY_URL environment variable or run: coolify-mcp config set url <url>')
110
- return err(new Error('No Coolify URL configured. Set COOLIFY_URL or use config command.'))
108
+ log.error("No Coolify URL configured");
109
+ log.info(
110
+ "Set COOLIFY_URL environment variable or run: coolify-mcp config set url <url>",
111
+ );
112
+ return err(
113
+ new Error(
114
+ "No Coolify URL configured. Set COOLIFY_URL or use config command.",
115
+ ),
116
+ );
111
117
  }
112
118
 
113
119
  if (!this.token) {
114
- log.error('No Coolify token configured')
115
- log.info('Set COOLIFY_TOKEN environment variable or run: coolify-mcp config set token <token>')
116
- return err(new Error('No Coolify token configured. Set COOLIFY_TOKEN or use config command.'))
120
+ log.error("No Coolify token configured");
121
+ log.info(
122
+ "Set COOLIFY_TOKEN environment variable or run: coolify-mcp config set token <token>",
123
+ );
124
+ return err(
125
+ new Error(
126
+ "No Coolify token configured. Set COOLIFY_TOKEN or use config command.",
127
+ ),
128
+ );
117
129
  }
118
130
 
119
- log.debug('Coolify connection configured')
120
- return ok(undefined)
131
+ log.debug("Coolify connection configured");
132
+ return ok(undefined);
121
133
  }
122
134
 
123
135
  /**
@@ -129,57 +141,70 @@ export class CoolifyService {
129
141
  */
130
142
  private async request<T>(
131
143
  endpoint: string,
132
- options: RequestInit = {}
144
+ options: RequestInit = {},
133
145
  ): Promise<ICoolifyApiResponse<T>> {
134
- const startTime = Date.now()
146
+ const startTime = Date.now();
135
147
 
136
148
  if (!this.baseUrl || !this.token) {
137
- return { error: 'Coolify not configured', status: 0, durationMs: Date.now() - startTime }
149
+ return {
150
+ error: "Coolify not configured",
151
+ status: 0,
152
+ durationMs: Date.now() - startTime,
153
+ };
138
154
  }
139
155
 
140
156
  try {
141
- const baseUrl = this.baseUrl.replace(/\/+$/, '')
142
- const url = `${baseUrl}/api/v1${endpoint}`
157
+ const baseUrl = this.baseUrl.replace(/\/+$/, "");
158
+ const url = `${baseUrl}/api/v1${endpoint}`;
143
159
 
144
160
  const response = await fetch(url, {
145
161
  ...options,
146
162
  headers: {
147
163
  Authorization: `Bearer ${this.token}`,
148
- 'Content-Type': 'application/json',
149
- Accept: 'application/json',
164
+ "Content-Type": "application/json",
165
+ Accept: "application/json",
150
166
  ...options.headers,
151
167
  },
152
- })
168
+ });
153
169
 
154
- const text = await response.text()
155
- const durationMs = Date.now() - startTime
156
- let data: T | undefined
170
+ const text = await response.text();
171
+ const durationMs = Date.now() - startTime;
172
+ let data: T | undefined;
157
173
 
158
174
  try {
159
- data = text ? JSON.parse(text) : undefined
175
+ data = text ? JSON.parse(text) : undefined;
160
176
  } catch {
161
177
  if (!response.ok) {
162
- return { error: text || `HTTP ${response.status}`, status: response.status, durationMs }
178
+ return {
179
+ error: text || `HTTP ${response.status}`,
180
+ status: response.status,
181
+ durationMs,
182
+ };
163
183
  }
164
184
  }
165
185
 
166
186
  if (!response.ok) {
167
- const parsed = data as { message?: string; errors?: Record<string, string[]> } | undefined
168
- let errorMessage = parsed?.message || `HTTP ${response.status}`
187
+ const parsed = data as
188
+ | { message?: string; errors?: Record<string, string[]> }
189
+ | undefined;
190
+ let errorMessage = parsed?.message || `HTTP ${response.status}`;
169
191
  // Include validation errors if present (Coolify returns { message, errors: { field: [reasons] } })
170
192
  if (parsed?.errors) {
171
193
  const details = Object.entries(parsed.errors)
172
- .map(([field, reasons]) => `${field}: ${Array.isArray(reasons) ? reasons.join(', ') : String(reasons)}`)
173
- .join('; ')
174
- errorMessage += ` ${details}`
194
+ .map(
195
+ ([field, reasons]) =>
196
+ `${field}: ${Array.isArray(reasons) ? reasons.join(", ") : String(reasons)}`,
197
+ )
198
+ .join("; ");
199
+ errorMessage += ` — ${details}`;
175
200
  }
176
- return { error: errorMessage, status: response.status, durationMs }
201
+ return { error: errorMessage, status: response.status, durationMs };
177
202
  }
178
203
 
179
- return { data, status: response.status, durationMs }
204
+ return { data, status: response.status, durationMs };
180
205
  } catch (error) {
181
- const message = error instanceof Error ? error.message : 'Unknown error'
182
- return { error: message, status: 0, durationMs: Date.now() - startTime }
206
+ const message = error instanceof Error ? error.message : "Unknown error";
207
+ return { error: message, status: 0, durationMs: Date.now() - startTime };
183
208
  }
184
209
  }
185
210
 
@@ -192,61 +217,63 @@ export class CoolifyService {
192
217
  */
193
218
  async deploy(
194
219
  options: ICoolifyDeployOptions,
195
- onProgress?: IProgressCallback
220
+ onProgress?: IProgressCallback,
196
221
  ): Promise<Result<ICoolifyDeployResult, Error>> {
197
222
  if (!options.uuid && !options.tag) {
198
- return err(new Error('Either uuid or tag is required'))
223
+ return err(new Error("Either uuid or tag is required"));
199
224
  }
200
225
 
201
- const appId = options.uuid?.slice(0, 8) || options.tag || 'unknown'
202
- onProgress?.(5, `Preparing deployment for ${appId}...`)
226
+ const appId = options.uuid?.slice(0, 8) || options.tag || "unknown";
227
+ onProgress?.(5, `Preparing deployment for ${appId}...`);
203
228
 
204
- log.info(`Deploying application ${options.uuid || options.tag}`)
229
+ log.info(`Deploying application ${options.uuid || options.tag}`);
205
230
 
206
- onProgress?.(25, 'Validating deployment configuration')
207
- onProgress?.(50, 'Triggering build pipeline...')
231
+ onProgress?.(25, "Validating deployment configuration");
232
+ onProgress?.(50, "Triggering build pipeline...");
208
233
 
209
234
  // Build query parameters for deploy endpoint
210
- const params = new URLSearchParams()
211
- if (options.uuid) params.set('uuid', options.uuid)
212
- if (options.tag) params.set('tag', options.tag)
213
- if (options.force) params.set('force', 'true')
235
+ const params = new URLSearchParams();
236
+ if (options.uuid) params.set("uuid", options.uuid);
237
+ if (options.tag) params.set("tag", options.tag);
238
+ if (options.force) params.set("force", "true");
214
239
 
215
- const endpoint = `/deploy${params.toString() ? `?${params.toString()}` : ''}`
240
+ const endpoint = `/deploy${params.toString() ? `?${params.toString()}` : ""}`;
216
241
 
217
242
  const result = await this.request<{
218
243
  deployments: Array<{
219
- message: string
220
- resource_uuid: string
221
- deployment_uuid: string
222
- }>
244
+ message: string;
245
+ resource_uuid: string;
246
+ deployment_uuid: string;
247
+ }>;
223
248
  }>(endpoint, {
224
- method: 'POST',
225
- })
249
+ method: "GET",
250
+ });
226
251
 
227
252
  if (result.error) {
228
- log.error(`Deployment failed: ${result.error}`)
229
- return err(new Error(result.error))
253
+ log.error(`Deployment failed: ${result.error}`);
254
+ return err(new Error(result.error));
230
255
  }
231
256
 
232
257
  // Response is { deployments: [{ message, resource_uuid, deployment_uuid }] }
233
- const deployments = result.data?.deployments || []
258
+ const deployments = result.data?.deployments || [];
234
259
  if (deployments.length === 0) {
235
- log.error('No deployments started')
236
- return err(new Error('No deployments started - check application configuration'))
260
+ log.error("No deployments started");
261
+ return err(
262
+ new Error("No deployments started - check application configuration"),
263
+ );
237
264
  }
238
265
 
239
- const deployment = deployments[0]
266
+ const deployment = deployments[0];
240
267
 
241
- onProgress?.(90, 'Build started on Coolify server')
242
- onProgress?.(100, 'Deployment triggered')
268
+ onProgress?.(90, "Build started on Coolify server");
269
+ onProgress?.(100, "Deployment triggered");
243
270
 
244
- log.success(`Deployment started: ${deployment.deployment_uuid}`)
271
+ log.success(`Deployment started: ${deployment.deployment_uuid}`);
245
272
  return ok({
246
273
  success: true,
247
274
  deploymentUuid: deployment.deployment_uuid,
248
275
  resourceUuid: deployment.resource_uuid,
249
- })
276
+ });
250
277
  }
251
278
 
252
279
  /**
@@ -266,27 +293,29 @@ export class CoolifyService {
266
293
  */
267
294
  async createApplication(
268
295
  options: ICoolifyAppOptions,
269
- onProgress?: IProgressCallback
296
+ onProgress?: IProgressCallback,
270
297
  ): Promise<Result<ICoolifyAppResult, Error>> {
271
- onProgress?.(5, `Preparing app "${options.name}"`)
298
+ onProgress?.(5, `Preparing app "${options.name}"`);
272
299
 
273
- const appType = options.type || 'public'
274
- log.info(`Creating application ${options.name} (type: ${appType})`)
300
+ const appType = options.type || "public";
301
+ log.info(`Creating application ${options.name} (type: ${appType})`);
275
302
 
276
- onProgress?.(25, `Validating server ${options.serverUuid.slice(0, 8)}...`)
277
- onProgress?.(50, 'Sending creation request to Coolify API...')
303
+ onProgress?.(25, `Validating server ${options.serverUuid.slice(0, 8)}...`);
304
+ onProgress?.(50, "Sending creation request to Coolify API...");
278
305
 
279
306
  // Determine endpoint based on application type
280
307
  const endpointMap: Record<string, string> = {
281
- 'public': '/applications/public',
282
- 'private-github-app': '/applications/private-github-app',
283
- 'private-deploy-key': '/applications/private-deploy-key',
284
- 'dockerfile': '/applications/dockerfile',
285
- 'docker-image': '/applications/docker-image',
286
- 'docker-compose': '/applications/docker-compose',
287
- }
288
-
289
- const endpoint = endpointMap[appType] || '/applications/public'
308
+ public: "/applications/public",
309
+ "private-github-app": "/applications/private-github-app",
310
+ "private-deploy-key": "/applications/private-deploy-key",
311
+ dockerfile: "/applications/dockerfile",
312
+ "docker-image": "/applications/docker-image",
313
+ "docker-compose": "/applications/docker-compose",
314
+ dockerimage: "/applications/dockerimage",
315
+ dockercompose: "/applications/dockercompose",
316
+ };
317
+
318
+ const endpoint = endpointMap[appType] || "/applications/public";
290
319
 
291
320
  // Build request body based on application type
292
321
  const body: Record<string, unknown> = {
@@ -295,60 +324,64 @@ export class CoolifyService {
295
324
  project_uuid: options.projectUuid,
296
325
  environment_uuid: options.environmentUuid,
297
326
  server_uuid: options.serverUuid,
298
- }
327
+ };
299
328
 
300
329
  // Type-specific fields
301
- if (appType === 'public' || appType === 'private-github-app' || appType === 'private-deploy-key') {
330
+ if (
331
+ appType === "public" ||
332
+ appType === "private-github-app" ||
333
+ appType === "private-deploy-key"
334
+ ) {
302
335
  if (options.githubRepoUrl) {
303
336
  // Coolify expects 'user/repo' format, not full URL
304
337
  body.git_repository = options.githubRepoUrl
305
- .replace(/^https?:\/\/github\.com\//, '')
306
- .replace(/\.git$/, '')
338
+ .replace(/^https?:\/\/github\.com\//, "")
339
+ .replace(/\.git$/, "");
307
340
  }
308
341
  if (options.githubAppUuid) {
309
- body.github_app_uuid = options.githubAppUuid
342
+ body.github_app_uuid = options.githubAppUuid;
310
343
  }
311
- body.git_branch = options.branch || 'main'
312
- body.build_pack = options.buildPack || 'dockerfile'
344
+ body.git_branch = options.branch || "main";
345
+ body.build_pack = options.buildPack || "dockerfile";
313
346
  if (options.portsExposes) {
314
- body.ports_exposes = options.portsExposes
347
+ body.ports_exposes = options.portsExposes;
315
348
  }
316
349
  // Dockerfile / Docker Compose configuration
317
350
  if (options.dockerfileLocation) {
318
- body.dockerfile_location = options.dockerfileLocation
351
+ body.dockerfile_location = options.dockerfileLocation;
319
352
  }
320
353
  if (options.dockerComposeLocation) {
321
- body.docker_compose_location = options.dockerComposeLocation
354
+ body.docker_compose_location = options.dockerComposeLocation;
322
355
  }
323
356
  if (options.baseDirectory) {
324
- body.base_directory = options.baseDirectory
357
+ body.base_directory = options.baseDirectory;
325
358
  }
326
- } else if (appType === 'docker-image' && options.dockerImage) {
327
- body.docker_image = options.dockerImage
328
- } else if (appType === 'docker-compose' && options.dockerCompose) {
329
- body.docker_compose = options.dockerCompose
359
+ } else if (appType === "docker-image" && options.dockerImage) {
360
+ body.docker_image = options.dockerImage;
361
+ } else if (appType === "docker-compose" && options.dockerCompose) {
362
+ body.docker_compose = options.dockerCompose;
330
363
  }
331
364
 
332
- log.debug(`Create application body: ${JSON.stringify(body, null, 2)}`)
333
- log.debug(`Endpoint: POST ${endpoint}`)
365
+ log.debug(`Create application body: ${JSON.stringify(body, null, 2)}`);
366
+ log.debug(`Endpoint: POST ${endpoint}`);
334
367
 
335
368
  const result = await this.request<{ uuid: string }>(endpoint, {
336
- method: 'POST',
369
+ method: "POST",
337
370
  body: JSON.stringify(body),
338
- })
371
+ });
339
372
 
340
373
  if (result.error) {
341
- log.error(`Failed to create application: ${result.error}`)
342
- return err(new Error(result.error))
374
+ log.error(`Failed to create application: ${result.error}`);
375
+ return err(new Error(result.error));
343
376
  }
344
377
 
345
- onProgress?.(100, `Application "${options.name}" created`)
378
+ onProgress?.(100, `Application "${options.name}" created`);
346
379
 
347
- log.success(`Application created: ${result.data?.uuid}`)
380
+ log.success(`Application created: ${result.data?.uuid}`);
348
381
  return ok({
349
382
  success: true,
350
383
  uuid: result.data?.uuid,
351
- })
384
+ });
352
385
  }
353
386
 
354
387
  /**
@@ -360,43 +393,50 @@ export class CoolifyService {
360
393
  */
361
394
  async setEnvironmentVariables(
362
395
  appUuid: string,
363
- envVars: Record<string, string>
396
+ envVars: Record<string, string>,
364
397
  ): Promise<Result<void, Error>> {
365
- log.info(`Setting ${Object.keys(envVars).length} environment variables for ${appUuid}`)
398
+ log.info(
399
+ `Setting ${Object.keys(envVars).length} environment variables for ${appUuid}`,
400
+ );
366
401
 
367
402
  // Coolify API: POST to create, PATCH to update existing
368
403
  for (const [key, value] of Object.entries(envVars)) {
369
404
  const result = await this.request(`/applications/${appUuid}/envs`, {
370
- method: 'POST',
405
+ method: "POST",
371
406
  body: JSON.stringify({ key, value, is_preview: false }),
372
- })
373
-
374
- if (result.error && result.error.includes('already exists')) {
375
- // Var exists — find its UUID and PATCH
376
- const listResult = await this.request<Array<{ uuid: string; key: string }>>(`/applications/${appUuid}/envs`)
377
- const existing = listResult.data?.find((v) => v.key === key)
407
+ });
408
+
409
+ if (result.error && result.error.includes("already exists")) {
410
+ // Var exists — DELETE + re-POST (Coolify PATCH on envs returns 404)
411
+ const listResult = await this.request<
412
+ Array<{ uuid: string; key: string }>
413
+ >(`/applications/${appUuid}/envs`);
414
+ const existing = listResult.data?.find((v) => v.key === key);
378
415
  if (existing) {
379
- const patchResult = await this.request(`/applications/${appUuid}/envs/${existing.uuid}`, {
380
- method: 'PATCH',
416
+ await this.request(`/applications/${appUuid}/envs/${existing.uuid}`, {
417
+ method: "DELETE",
418
+ });
419
+ const repost = await this.request(`/applications/${appUuid}/envs`, {
420
+ method: "POST",
381
421
  body: JSON.stringify({ key, value, is_preview: false }),
382
- })
383
- if (patchResult.error) {
384
- log.error(`Failed to update env var ${key}: ${patchResult.error}`)
385
- return err(new Error(`Failed to update ${key}: ${patchResult.error}`))
422
+ });
423
+ if (repost.error) {
424
+ log.error(`Failed to update env var ${key}: ${repost.error}`);
425
+ return err(new Error(`Failed to update ${key}: ${repost.error}`));
386
426
  }
387
- log.debug(`Updated existing env var: ${key}`)
427
+ log.debug(`Updated existing env var: ${key}`);
388
428
  } else {
389
- log.error(`Failed to set env var ${key}: ${result.error}`)
390
- return err(new Error(`Failed to set ${key}: ${result.error}`))
429
+ log.error(`Failed to set env var ${key}: ${result.error}`);
430
+ return err(new Error(`Failed to set ${key}: ${result.error}`));
391
431
  }
392
432
  } else if (result.error) {
393
- log.error(`Failed to set env var ${key}: ${result.error}`)
394
- return err(new Error(`Failed to set ${key}: ${result.error}`))
433
+ log.error(`Failed to set env var ${key}: ${result.error}`);
434
+ return err(new Error(`Failed to set ${key}: ${result.error}`));
395
435
  }
396
436
  }
397
437
 
398
- log.success(`${Object.keys(envVars).length} environment variables set`)
399
- return ok(undefined)
438
+ log.success(`${Object.keys(envVars).length} environment variables set`);
439
+ return ok(undefined);
400
440
  }
401
441
 
402
442
  /**
@@ -406,21 +446,21 @@ export class CoolifyService {
406
446
  * @returns Result with environment variables or error
407
447
  */
408
448
  async getEnvironmentVariables(
409
- appUuid: string
449
+ appUuid: string,
410
450
  ): Promise<Result<ICoolifyEnvVar[], Error>> {
411
- log.info(`Getting environment variables for ${appUuid}`)
451
+ log.info(`Getting environment variables for ${appUuid}`);
412
452
 
413
453
  const result = await this.request<ICoolifyEnvVar[]>(
414
- `/applications/${appUuid}/envs`
415
- )
454
+ `/applications/${appUuid}/envs`,
455
+ );
416
456
 
417
457
  if (result.error) {
418
- log.error(`Failed to get env vars: ${result.error}`)
419
- return err(new Error(result.error))
458
+ log.error(`Failed to get env vars: ${result.error}`);
459
+ return err(new Error(result.error));
420
460
  }
421
461
 
422
- log.success(`Environment variables retrieved for ${appUuid}`)
423
- return ok(result.data || [])
462
+ log.success(`Environment variables retrieved for ${appUuid}`);
463
+ return ok(result.data || []);
424
464
  }
425
465
 
426
466
  /**
@@ -437,46 +477,52 @@ export class CoolifyService {
437
477
  appUuid: string,
438
478
  key: string,
439
479
  value: string,
440
- isBuildTime: boolean = false
480
+ isBuildTime: boolean = false,
441
481
  ): Promise<Result<void, Error>> {
442
- log.info(`Setting environment variable ${key} for ${appUuid}`)
482
+ log.info(`Setting environment variable ${key} for ${appUuid}`);
443
483
 
444
484
  // Check if variable already exists
445
- const existingVars = await this.getEnvironmentVariables(appUuid)
485
+ const existingVars = await this.getEnvironmentVariables(appUuid);
446
486
  if (isErr(existingVars)) {
447
- return err(existingVars.error)
487
+ return err(existingVars.error);
448
488
  }
449
489
 
450
- const exists = existingVars.value.some(ev => ev.key === key)
490
+ const exists = existingVars.value.some((ev) => ev.key === key);
451
491
 
452
492
  if (exists) {
453
493
  // Use PATCH to update existing variable
454
- log.debug(`Variable ${key} exists, using PATCH to update`)
455
- const result = await this.request<{ uuid: string }>(`/applications/${appUuid}/envs`, {
456
- method: 'PATCH',
457
- body: JSON.stringify({ key, value }),
458
- })
494
+ log.debug(`Variable ${key} exists, using PATCH to update`);
495
+ const result = await this.request<{ uuid: string }>(
496
+ `/applications/${appUuid}/envs`,
497
+ {
498
+ method: "PATCH",
499
+ body: JSON.stringify({ key, value }),
500
+ },
501
+ );
459
502
 
460
503
  if (result.error) {
461
- log.error(`Failed to update env var: ${result.error}`)
462
- return err(new Error(result.error))
504
+ log.error(`Failed to update env var: ${result.error}`);
505
+ return err(new Error(result.error));
463
506
  }
464
507
  } else {
465
508
  // Use POST to create new variable
466
- log.debug(`Variable ${key} does not exist, using POST to create`)
467
- const result = await this.request<{ uuid: string }>(`/applications/${appUuid}/envs`, {
468
- method: 'POST',
469
- body: JSON.stringify({ key, value }),
470
- })
509
+ log.debug(`Variable ${key} does not exist, using POST to create`);
510
+ const result = await this.request<{ uuid: string }>(
511
+ `/applications/${appUuid}/envs`,
512
+ {
513
+ method: "POST",
514
+ body: JSON.stringify({ key, value }),
515
+ },
516
+ );
471
517
 
472
518
  if (result.error) {
473
- log.error(`Failed to create env var: ${result.error}`)
474
- return err(new Error(result.error))
519
+ log.error(`Failed to create env var: ${result.error}`);
520
+ return err(new Error(result.error));
475
521
  }
476
522
  }
477
523
 
478
- log.success(`Environment variable ${key} set for ${appUuid}`)
479
- return ok(undefined)
524
+ log.success(`Environment variable ${key} set for ${appUuid}`);
525
+ return ok(undefined);
480
526
  }
481
527
 
482
528
  /**
@@ -488,33 +534,36 @@ export class CoolifyService {
488
534
  */
489
535
  async deleteEnvironmentVariable(
490
536
  appUuid: string,
491
- key: string
537
+ key: string,
492
538
  ): Promise<Result<void, Error>> {
493
- log.info(`Deleting environment variable ${key} from ${appUuid}`)
539
+ log.info(`Deleting environment variable ${key} from ${appUuid}`);
494
540
 
495
541
  // First get all env vars to find the UUID of the one to delete
496
- const envVarsResult = await this.getEnvironmentVariables(appUuid)
542
+ const envVarsResult = await this.getEnvironmentVariables(appUuid);
497
543
  if (isErr(envVarsResult)) {
498
- return err(envVarsResult.error)
544
+ return err(envVarsResult.error);
499
545
  }
500
546
 
501
- const envVar = envVarsResult.value.find(ev => ev.key === key)
547
+ const envVar = envVarsResult.value.find((ev) => ev.key === key);
502
548
  if (!envVar) {
503
- log.error(`Environment variable ${key} not found`)
504
- return err(new Error(`Environment variable ${key} not found`))
549
+ log.error(`Environment variable ${key} not found`);
550
+ return err(new Error(`Environment variable ${key} not found`));
505
551
  }
506
552
 
507
- const result = await this.request(`/applications/${appUuid}/envs/${envVar.uuid}`, {
508
- method: 'DELETE',
509
- })
553
+ const result = await this.request(
554
+ `/applications/${appUuid}/envs/${envVar.uuid}`,
555
+ {
556
+ method: "DELETE",
557
+ },
558
+ );
510
559
 
511
560
  if (result.error) {
512
- log.error(`Failed to delete env var: ${result.error}`)
513
- return err(new Error(result.error))
561
+ log.error(`Failed to delete env var: ${result.error}`);
562
+ return err(new Error(result.error));
514
563
  }
515
564
 
516
- log.success(`Environment variable ${key} deleted from ${appUuid}`)
517
- return ok(undefined)
565
+ log.success(`Environment variable ${key} deleted from ${appUuid}`);
566
+ return ok(undefined);
518
567
  }
519
568
 
520
569
  /**
@@ -523,18 +572,16 @@ export class CoolifyService {
523
572
  * @param appUuid - Application UUID
524
573
  * @returns Result with status or error
525
574
  */
526
- async getApplicationStatus(
527
- appUuid: string
528
- ): Promise<Result<string, Error>> {
575
+ async getApplicationStatus(appUuid: string): Promise<Result<string, Error>> {
529
576
  const result = await this.request<{ status: string }>(
530
- `/applications/${appUuid}`
531
- )
577
+ `/applications/${appUuid}`,
578
+ );
532
579
 
533
580
  if (result.error) {
534
- return err(new Error(result.error))
581
+ return err(new Error(result.error));
535
582
  }
536
583
 
537
- return ok(result.data?.status || 'unknown')
584
+ return ok(result.data?.status || "unknown");
538
585
  }
539
586
 
540
587
  /**
@@ -543,13 +590,13 @@ export class CoolifyService {
543
590
  * @returns Result with servers or error
544
591
  */
545
592
  async listServers(): Promise<Result<ICoolifyServer[], Error>> {
546
- const result = await this.request<ICoolifyServer[]>('/servers')
593
+ const result = await this.request<ICoolifyServer[]>("/servers");
547
594
 
548
595
  if (result.error) {
549
- return err(new Error(result.error))
596
+ return err(new Error(result.error));
550
597
  }
551
598
 
552
- return ok(result.data || [])
599
+ return ok(result.data || []);
553
600
  }
554
601
 
555
602
  /**
@@ -558,20 +605,18 @@ export class CoolifyService {
558
605
  * @param serverUuid - Server UUID
559
606
  * @returns Result with server details or error
560
607
  */
561
- async getServer(
562
- serverUuid: string
563
- ): Promise<Result<ICoolifyServer, Error>> {
564
- log.info(`Getting server details for ${serverUuid}`)
608
+ async getServer(serverUuid: string): Promise<Result<ICoolifyServer, Error>> {
609
+ log.info(`Getting server details for ${serverUuid}`);
565
610
 
566
- const result = await this.request<ICoolifyServer>(`/servers/${serverUuid}`)
611
+ const result = await this.request<ICoolifyServer>(`/servers/${serverUuid}`);
567
612
 
568
613
  if (result.error) {
569
- log.error(`Failed to get server: ${result.error}`)
570
- return err(new Error(result.error))
614
+ log.error(`Failed to get server: ${result.error}`);
615
+ return err(new Error(result.error));
571
616
  }
572
617
 
573
- log.success(`Server details retrieved: ${serverUuid}`)
574
- return ok(result.data as ICoolifyServer)
618
+ log.success(`Server details retrieved: ${serverUuid}`);
619
+ return ok(result.data as ICoolifyServer);
575
620
  }
576
621
 
577
622
  /**
@@ -579,10 +624,18 @@ export class CoolifyService {
579
624
  *
580
625
  * @returns Result with GitHub Apps list or error
581
626
  */
582
- async listGithubApps(): Promise<Result<Array<{ id: number; uuid: string; name: string; is_public: boolean }>, Error>> {
583
- const result = await this.request<Array<{ id: number; uuid: string; name: string; is_public: boolean }>>('/github-apps')
584
- if (result.error) return err(new Error(result.error))
585
- return ok(result.data || [])
627
+ async listGithubApps(): Promise<
628
+ Result<
629
+ Array<{ id: number; uuid: string; name: string; is_public: boolean }>,
630
+ Error
631
+ >
632
+ > {
633
+ const result =
634
+ await this.request<
635
+ Array<{ id: number; uuid: string; name: string; is_public: boolean }>
636
+ >("/github-apps");
637
+ if (result.error) return err(new Error(result.error));
638
+ return ok(result.data || []);
586
639
  }
587
640
 
588
641
  /**
@@ -591,13 +644,13 @@ export class CoolifyService {
591
644
  * @returns Result with projects list or error
592
645
  */
593
646
  async listProjects(): Promise<Result<ICoolifyProject[], Error>> {
594
- const result = await this.request<ICoolifyProject[]>('/projects')
647
+ const result = await this.request<ICoolifyProject[]>("/projects");
595
648
 
596
649
  if (result.error) {
597
- return err(new Error(result.error))
650
+ return err(new Error(result.error));
598
651
  }
599
652
 
600
- return ok(result.data || [])
653
+ return ok(result.data || []);
601
654
  }
602
655
 
603
656
  /**
@@ -609,22 +662,22 @@ export class CoolifyService {
609
662
  */
610
663
  async createProject(
611
664
  name: string,
612
- description?: string
665
+ description?: string,
613
666
  ): Promise<Result<ICoolifyProject, Error>> {
614
- log.info(`Creating project: ${name}`)
667
+ log.info(`Creating project: ${name}`);
615
668
 
616
- const result = await this.request<ICoolifyProject>('/projects', {
617
- method: 'POST',
618
- body: JSON.stringify({ name, description: description || '' }),
619
- })
669
+ const result = await this.request<ICoolifyProject>("/projects", {
670
+ method: "POST",
671
+ body: JSON.stringify({ name, description: description || "" }),
672
+ });
620
673
 
621
674
  if (result.error) {
622
- log.error(`Failed to create project: ${result.error}`)
623
- return err(new Error(result.error))
675
+ log.error(`Failed to create project: ${result.error}`);
676
+ return err(new Error(result.error));
624
677
  }
625
678
 
626
- log.success(`Project created: ${result.data?.uuid}`)
627
- return ok(result.data!)
679
+ log.success(`Project created: ${result.data?.uuid}`);
680
+ return ok(result.data!);
628
681
  }
629
682
 
630
683
  /**
@@ -634,21 +687,21 @@ export class CoolifyService {
634
687
  * @returns Result with environments list or error
635
688
  */
636
689
  async getProjectEnvironments(
637
- projectUuid: string
690
+ projectUuid: string,
638
691
  ): Promise<Result<ICoolifyEnvironment[], Error>> {
639
- log.info(`Getting environments for project ${projectUuid}`)
692
+ log.info(`Getting environments for project ${projectUuid}`);
640
693
 
641
694
  const result = await this.request<{ environments: ICoolifyEnvironment[] }>(
642
- `/projects/${projectUuid}`
643
- )
695
+ `/projects/${projectUuid}`,
696
+ );
644
697
 
645
698
  if (result.error) {
646
- log.error(`Failed to get environments: ${result.error}`)
647
- return err(new Error(result.error))
699
+ log.error(`Failed to get environments: ${result.error}`);
700
+ return err(new Error(result.error));
648
701
  }
649
702
 
650
- log.success(`Environments retrieved for project ${projectUuid}`)
651
- return ok(result.data?.environments || [])
703
+ log.success(`Environments retrieved for project ${projectUuid}`);
704
+ return ok(result.data?.environments || []);
652
705
  }
653
706
 
654
707
  /**
@@ -657,13 +710,13 @@ export class CoolifyService {
657
710
  * @returns Result with teams list or error
658
711
  */
659
712
  async listTeams(): Promise<Result<ICoolifyTeam[], Error>> {
660
- const result = await this.request<ICoolifyTeam[]>('/teams')
713
+ const result = await this.request<ICoolifyTeam[]>("/teams");
661
714
 
662
715
  if (result.error) {
663
- return err(new Error(result.error))
716
+ return err(new Error(result.error));
664
717
  }
665
718
 
666
- return ok(result.data || [])
719
+ return ok(result.data || []);
667
720
  }
668
721
 
669
722
  /**
@@ -673,17 +726,17 @@ export class CoolifyService {
673
726
  * @returns Result with destinations or error
674
727
  */
675
728
  async getServerDestinations(
676
- serverUuid: string
729
+ serverUuid: string,
677
730
  ): Promise<Result<ICoolifyDestination[], Error>> {
678
731
  const result = await this.request<{
679
- destinations: ICoolifyDestination[]
680
- }>(`/servers/${serverUuid}`)
732
+ destinations: ICoolifyDestination[];
733
+ }>(`/servers/${serverUuid}`);
681
734
 
682
735
  if (result.error) {
683
- return err(new Error(result.error))
736
+ return err(new Error(result.error));
684
737
  }
685
738
 
686
- return ok(result.data?.destinations || [])
739
+ return ok(result.data?.destinations || []);
687
740
  }
688
741
 
689
742
  /**
@@ -695,27 +748,27 @@ export class CoolifyService {
695
748
  */
696
749
  async listApplications(
697
750
  teamId?: string,
698
- projectId?: string
751
+ projectId?: string,
699
752
  ): Promise<Result<ICoolifyApplication[], Error>> {
700
- log.info('Listing applications')
753
+ log.info("Listing applications");
701
754
 
702
- let endpoint = '/applications'
703
- const params = new URLSearchParams()
704
- if (teamId) params.set('team_id', teamId)
705
- if (projectId) params.set('project_id', projectId)
755
+ let endpoint = "/applications";
756
+ const params = new URLSearchParams();
757
+ if (teamId) params.set("team_id", teamId);
758
+ if (projectId) params.set("project_id", projectId);
706
759
  if (params.toString()) {
707
- endpoint += `?${params.toString()}`
760
+ endpoint += `?${params.toString()}`;
708
761
  }
709
762
 
710
- const result = await this.request<ICoolifyApplication[]>(endpoint)
763
+ const result = await this.request<ICoolifyApplication[]>(endpoint);
711
764
 
712
765
  if (result.error) {
713
- log.error(`Failed to list applications: ${result.error}`)
714
- return err(new Error(result.error))
766
+ log.error(`Failed to list applications: ${result.error}`);
767
+ return err(new Error(result.error));
715
768
  }
716
769
 
717
- log.success(`Listed ${result.data?.length || 0} applications`)
718
- return ok(result.data || [])
770
+ log.success(`Listed ${result.data?.length || 0} applications`);
771
+ return ok(result.data || []);
719
772
  }
720
773
 
721
774
  /**
@@ -725,21 +778,24 @@ export class CoolifyService {
725
778
  * @returns Result indicating success or error
726
779
  */
727
780
  async deleteApplication(
728
- appUuid: string
781
+ appUuid: string,
729
782
  ): Promise<Result<ICoolifyDeleteResult, Error>> {
730
- log.info(`Deleting application ${appUuid}`)
783
+ log.info(`Deleting application ${appUuid}`);
731
784
 
732
- const result = await this.request<ICoolifyDeleteResult>(`/applications/${appUuid}`, {
733
- method: 'DELETE',
734
- })
785
+ const result = await this.request<ICoolifyDeleteResult>(
786
+ `/applications/${appUuid}`,
787
+ {
788
+ method: "DELETE",
789
+ },
790
+ );
735
791
 
736
792
  if (result.error) {
737
- log.error(`Failed to delete application: ${result.error}`)
738
- return err(new Error(result.error))
793
+ log.error(`Failed to delete application: ${result.error}`);
794
+ return err(new Error(result.error));
739
795
  }
740
796
 
741
- log.success(`Application deleted: ${appUuid}`)
742
- return ok({ success: true, message: 'Application deleted' })
797
+ log.success(`Application deleted: ${appUuid}`);
798
+ return ok({ success: true, message: "Application deleted" });
743
799
  }
744
800
 
745
801
  /**
@@ -751,38 +807,45 @@ export class CoolifyService {
751
807
  */
752
808
  async updateApplication(
753
809
  appUuid: string,
754
- options: ICoolifyUpdateOptions
810
+ options: ICoolifyUpdateOptions,
755
811
  ): Promise<Result<ICoolifyApplication, Error>> {
756
- log.info(`Updating application ${appUuid}`)
757
-
758
- const body: Record<string, unknown> = {}
759
- if (options.name) body.name = options.name
760
- if (options.description) body.description = options.description
761
- if (options.buildPack) body.build_pack = options.buildPack
762
- if (options.gitBranch) body.git_branch = options.gitBranch
763
- if (options.portsExposes) body.ports_exposes = options.portsExposes
764
- if (options.installCommand) body.install_command = options.installCommand
765
- if (options.buildCommand) body.build_command = options.buildCommand
766
- if (options.startCommand) body.start_command = options.startCommand
767
- if (options.dockerfileLocation) body.dockerfile_location = options.dockerfileLocation
768
- if (options.baseDirectory) body.base_directory = options.baseDirectory
769
- if (options.domains) body.domains = options.domains
770
- if (options.dockerComposeDomains) body.docker_compose_domains = options.dockerComposeDomains
771
- if (options.isForceHttpsEnabled !== undefined) body.is_force_https_enabled = options.isForceHttpsEnabled
772
- if (options.isAutoDeployEnabled !== undefined) body.is_auto_deploy_enabled = options.isAutoDeployEnabled
773
-
774
- const result = await this.request<ICoolifyApplication>(`/applications/${appUuid}`, {
775
- method: 'PATCH',
776
- body: JSON.stringify(body),
777
- })
812
+ log.info(`Updating application ${appUuid}`);
813
+
814
+ const body: Record<string, unknown> = {};
815
+ if (options.name) body.name = options.name;
816
+ if (options.description) body.description = options.description;
817
+ if (options.buildPack) body.build_pack = options.buildPack;
818
+ if (options.gitBranch) body.git_branch = options.gitBranch;
819
+ if (options.portsExposes) body.ports_exposes = options.portsExposes;
820
+ if (options.installCommand) body.install_command = options.installCommand;
821
+ if (options.buildCommand) body.build_command = options.buildCommand;
822
+ if (options.startCommand) body.start_command = options.startCommand;
823
+ if (options.dockerfileLocation)
824
+ body.dockerfile_location = options.dockerfileLocation;
825
+ if (options.baseDirectory) body.base_directory = options.baseDirectory;
826
+ if (options.domains) body.domains = options.domains;
827
+ if (options.dockerComposeDomains)
828
+ body.docker_compose_domains = options.dockerComposeDomains;
829
+ if (options.isForceHttpsEnabled !== undefined)
830
+ body.is_force_https_enabled = options.isForceHttpsEnabled;
831
+ if (options.isAutoDeployEnabled !== undefined)
832
+ body.is_auto_deploy_enabled = options.isAutoDeployEnabled;
833
+
834
+ const result = await this.request<ICoolifyApplication>(
835
+ `/applications/${appUuid}`,
836
+ {
837
+ method: "PATCH",
838
+ body: JSON.stringify(body),
839
+ },
840
+ );
778
841
 
779
842
  if (result.error) {
780
- log.error(`Failed to update application: ${result.error}`)
781
- return err(new Error(result.error))
843
+ log.error(`Failed to update application: ${result.error}`);
844
+ return err(new Error(result.error));
782
845
  }
783
846
 
784
- log.success(`Application updated: ${appUuid}`)
785
- return ok(result.data as ICoolifyApplication)
847
+ log.success(`Application updated: ${appUuid}`);
848
+ return ok(result.data as ICoolifyApplication);
786
849
  }
787
850
 
788
851
  /**
@@ -794,37 +857,38 @@ export class CoolifyService {
794
857
  */
795
858
  async getApplicationLogs(
796
859
  appUuid: string,
797
- options: ICoolifyLogsOptions = {}
860
+ options: ICoolifyLogsOptions = {},
798
861
  ): Promise<Result<ICoolifyLogs, Error>> {
799
- log.info(`Getting logs for application ${appUuid}`)
862
+ log.info(`Getting logs for application ${appUuid}`);
800
863
 
801
- const params = new URLSearchParams()
802
- if (options.follow) params.set('follow', 'true')
803
- if (options.tail) params.set('tail', options.tail.toString())
864
+ const params = new URLSearchParams();
865
+ if (options.follow) params.set("follow", "true");
866
+ if (options.tail) params.set("lines", options.tail.toString());
867
+ if (options.serviceName) params.set("service_name", options.serviceName);
804
868
 
805
- const endpoint = `/applications/${appUuid}/logs${params.toString() ? `?${params.toString()}` : ''}`
869
+ const endpoint = `/applications/${appUuid}/logs${params.toString() ? `?${params.toString()}` : ""}`;
806
870
 
807
- const result = await this.request<{ logs: string | string[] }>(endpoint)
871
+ const result = await this.request<{ logs: string | string[] }>(endpoint);
808
872
 
809
873
  if (result.error) {
810
- log.error(`Failed to get logs: ${result.error}`)
811
- return err(new Error(result.error))
874
+ log.error(`Failed to get logs: ${result.error}`);
875
+ return err(new Error(result.error));
812
876
  }
813
877
 
814
878
  // Coolify API returns logs as a single newline-delimited string for
815
879
  // docker-compose apps, but as string[] for single-container apps.
816
- const rawLogs = result.data?.logs
880
+ const rawLogs = result.data?.logs;
817
881
  const logsArray: string[] = Array.isArray(rawLogs)
818
882
  ? rawLogs
819
- : typeof rawLogs === 'string'
820
- ? rawLogs.split('\n').filter((l: string) => l.length > 0)
821
- : []
883
+ : typeof rawLogs === "string"
884
+ ? rawLogs.split("\n").filter((l: string) => l.length > 0)
885
+ : [];
822
886
 
823
- log.success(`Logs retrieved for application: ${appUuid}`)
887
+ log.success(`Logs retrieved for application: ${appUuid}`);
824
888
  return ok({
825
889
  logs: logsArray,
826
890
  timestamp: new Date().toISOString(),
827
- })
891
+ });
828
892
  }
829
893
 
830
894
  /**
@@ -834,20 +898,24 @@ export class CoolifyService {
834
898
  * @returns Result with deployment history or error
835
899
  */
836
900
  async getApplicationDeploymentHistory(
837
- appUuid: string
901
+ appUuid: string,
838
902
  ): Promise<Result<ICoolifyDeployment[], Error>> {
839
- log.info(`Getting deployment history for ${appUuid}`)
903
+ log.info(`Getting deployment history for ${appUuid}`);
840
904
 
841
- // According to Coolify API docs, endpoint is /applications/{app_uuid}/deployments
842
- const result = await this.request<{ deployments: ICoolifyDeployment[] }>(`/applications/${appUuid}/deployments`)
905
+ // Coolify API: /deployments/applications/{appUuid}
906
+ // Response: { count: number, deployments: ICoolifyDeployment[] }
907
+ const result = await this.request<{
908
+ count: number;
909
+ deployments: ICoolifyDeployment[];
910
+ }>(`/deployments/applications/${appUuid}`);
843
911
 
844
912
  if (result.error) {
845
- log.error(`Failed to get deployment history: ${result.error}`)
846
- return err(new Error(result.error))
913
+ log.error(`Failed to get deployment history: ${result.error}`);
914
+ return err(new Error(result.error));
847
915
  }
848
916
 
849
- log.success(`Deployment history retrieved for ${appUuid}`)
850
- return ok(result.data?.deployments || [])
917
+ log.success(`Deployment history retrieved for ${appUuid}`);
918
+ return ok(result.data?.deployments || []);
851
919
  }
852
920
 
853
921
  /**
@@ -857,21 +925,24 @@ export class CoolifyService {
857
925
  * @returns Result with application status or error
858
926
  */
859
927
  async startApplication(
860
- appUuid: string
928
+ appUuid: string,
861
929
  ): Promise<Result<ICoolifyApplication, Error>> {
862
- log.info(`Starting application ${appUuid}`)
930
+ log.info(`Starting application ${appUuid}`);
863
931
 
864
- const result = await this.request<ICoolifyApplication>(`/applications/${appUuid}/start`, {
865
- method: 'POST',
866
- })
932
+ const result = await this.request<ICoolifyApplication>(
933
+ `/applications/${appUuid}/start`,
934
+ {
935
+ method: "POST",
936
+ },
937
+ );
867
938
 
868
939
  if (result.error) {
869
- log.error(`Failed to start application: ${result.error}`)
870
- return err(new Error(result.error))
940
+ log.error(`Failed to start application: ${result.error}`);
941
+ return err(new Error(result.error));
871
942
  }
872
943
 
873
- log.success(`Application started: ${appUuid}`)
874
- return ok(result.data as ICoolifyApplication)
944
+ log.success(`Application started: ${appUuid}`);
945
+ return ok(result.data as ICoolifyApplication);
875
946
  }
876
947
 
877
948
  /**
@@ -881,21 +952,24 @@ export class CoolifyService {
881
952
  * @returns Result with application status or error
882
953
  */
883
954
  async stopApplication(
884
- appUuid: string
955
+ appUuid: string,
885
956
  ): Promise<Result<ICoolifyApplication, Error>> {
886
- log.info(`Stopping application ${appUuid}`)
957
+ log.info(`Stopping application ${appUuid}`);
887
958
 
888
- const result = await this.request<ICoolifyApplication>(`/applications/${appUuid}/stop`, {
889
- method: 'POST',
890
- })
959
+ const result = await this.request<ICoolifyApplication>(
960
+ `/applications/${appUuid}/stop`,
961
+ {
962
+ method: "POST",
963
+ },
964
+ );
891
965
 
892
966
  if (result.error) {
893
- log.error(`Failed to stop application: ${result.error}`)
894
- return err(new Error(result.error))
967
+ log.error(`Failed to stop application: ${result.error}`);
968
+ return err(new Error(result.error));
895
969
  }
896
970
 
897
- log.success(`Application stopped: ${appUuid}`)
898
- return ok(result.data as ICoolifyApplication)
971
+ log.success(`Application stopped: ${appUuid}`);
972
+ return ok(result.data as ICoolifyApplication);
899
973
  }
900
974
 
901
975
  /**
@@ -905,21 +979,24 @@ export class CoolifyService {
905
979
  * @returns Result with application status or error
906
980
  */
907
981
  async restartApplication(
908
- appUuid: string
982
+ appUuid: string,
909
983
  ): Promise<Result<ICoolifyApplication, Error>> {
910
- log.info(`Restarting application ${appUuid}`)
984
+ log.info(`Restarting application ${appUuid}`);
911
985
 
912
- const result = await this.request<ICoolifyApplication>(`/applications/${appUuid}/restart`, {
913
- method: 'POST',
914
- })
986
+ const result = await this.request<ICoolifyApplication>(
987
+ `/applications/${appUuid}/restart`,
988
+ {
989
+ method: "POST",
990
+ },
991
+ );
915
992
 
916
993
  if (result.error) {
917
- log.error(`Failed to restart application: ${result.error}`)
918
- return err(new Error(result.error))
994
+ log.error(`Failed to restart application: ${result.error}`);
995
+ return err(new Error(result.error));
919
996
  }
920
997
 
921
- log.success(`Application restarted: ${appUuid}`)
922
- return ok(result.data as ICoolifyApplication)
998
+ log.success(`Application restarted: ${appUuid}`);
999
+ return ok(result.data as ICoolifyApplication);
923
1000
  }
924
1001
 
925
1002
  /**
@@ -928,17 +1005,17 @@ export class CoolifyService {
928
1005
  * @returns Result with deployments list or error
929
1006
  */
930
1007
  async listDeployments(): Promise<Result<ICoolifyDeployment[], Error>> {
931
- log.info('Listing active deployments')
1008
+ log.info("Listing active deployments");
932
1009
 
933
- const result = await this.request<ICoolifyDeployment[]>('/deployments')
1010
+ const result = await this.request<ICoolifyDeployment[]>("/deployments");
934
1011
 
935
1012
  if (result.error) {
936
- log.error(`Failed to list deployments: ${result.error}`)
937
- return err(new Error(result.error))
1013
+ log.error(`Failed to list deployments: ${result.error}`);
1014
+ return err(new Error(result.error));
938
1015
  }
939
1016
 
940
- log.success(`Listed ${result.data?.length || 0} active deployments`)
941
- return ok(result.data || [])
1017
+ log.success(`Listed ${result.data?.length || 0} active deployments`);
1018
+ return ok(result.data || []);
942
1019
  }
943
1020
 
944
1021
  /**
@@ -948,19 +1025,53 @@ export class CoolifyService {
948
1025
  * @returns Result with deployment details or error
949
1026
  */
950
1027
  async getDeployment(
951
- deploymentUuid: string
1028
+ deploymentUuid: string,
952
1029
  ): Promise<Result<ICoolifyDeployment, Error>> {
953
- log.info(`Getting deployment details for ${deploymentUuid}`)
1030
+ log.info(`Getting deployment details for ${deploymentUuid}`);
954
1031
 
955
- const result = await this.request<ICoolifyDeployment>(`/deployments/${deploymentUuid}`)
1032
+ const result = await this.request<ICoolifyDeployment>(
1033
+ `/deployments/${deploymentUuid}`,
1034
+ );
956
1035
 
957
1036
  if (result.error) {
958
- log.error(`Failed to get deployment: ${result.error}`)
959
- return err(new Error(result.error))
1037
+ log.error(`Failed to get deployment: ${result.error}`);
1038
+ return err(new Error(result.error));
960
1039
  }
961
1040
 
962
- log.success(`Deployment details retrieved: ${deploymentUuid}`)
963
- return ok(result.data as ICoolifyDeployment)
1041
+ log.success(`Deployment details retrieved: ${deploymentUuid}`);
1042
+ return ok(result.data as ICoolifyDeployment);
1043
+ }
1044
+
1045
+ /**
1046
+ * Gets logs for a specific deployment.
1047
+ *
1048
+ * @param deploymentUuid - Deployment UUID
1049
+ * @returns Result with deployment status and logs or error
1050
+ */
1051
+ async getDeploymentLogs(
1052
+ deploymentUuid: string,
1053
+ ): Promise<
1054
+ Result<{ status: string; logs: string; deployment_uuid: string }, Error>
1055
+ > {
1056
+ log.info(`Getting deployment logs for ${deploymentUuid}`);
1057
+
1058
+ const result = await this.request<{
1059
+ status: string;
1060
+ logs: string;
1061
+ deployment_uuid: string;
1062
+ }>(`/deployments/${deploymentUuid}`);
1063
+
1064
+ if (result.error) {
1065
+ log.error(`Failed to get deployment logs: ${result.error}`);
1066
+ return err(new Error(result.error));
1067
+ }
1068
+
1069
+ log.success(`Deployment logs retrieved: ${deploymentUuid}`);
1070
+ return ok({
1071
+ status: result.data?.status || "unknown",
1072
+ logs: result.data?.logs || "",
1073
+ deployment_uuid: result.data?.deployment_uuid || deploymentUuid,
1074
+ });
964
1075
  }
965
1076
 
966
1077
  /**
@@ -974,29 +1085,63 @@ export class CoolifyService {
974
1085
  async getApplicationDeployments(
975
1086
  appUuid: string,
976
1087
  skip: number = 0,
977
- take: number = 10
1088
+ take: number = 10,
978
1089
  ): Promise<Result<ICoolifyDeployment[], Error>> {
979
- log.info(`Getting deployments for application ${appUuid}`)
1090
+ log.info(`Getting deployments for application ${appUuid}`);
1091
+
1092
+ const params = new URLSearchParams();
1093
+ if (skip > 0) params.set("skip", skip.toString());
1094
+ if (take !== 10) params.set("take", take.toString());
1095
+
1096
+ const endpoint = `/applications/${appUuid}/deployments${params.toString() ? `?${params.toString()}` : ""}`;
1097
+ const result = await this.request<{
1098
+ count: number;
1099
+ deployments: ICoolifyDeployment[];
1100
+ }>(endpoint);
1101
+
1102
+ if (result.error) {
1103
+ log.error(`Failed to get application deployments: ${result.error}`);
1104
+ return err(new Error(result.error));
1105
+ }
980
1106
 
981
- const params = new URLSearchParams()
982
- if (skip > 0) params.set('skip', skip.toString())
983
- if (take !== 10) params.set('take', take.toString())
1107
+ const deployments = result.data?.deployments || [];
1108
+ log.success(`Retrieved ${deployments.length} deployments for ${appUuid}`);
1109
+ return ok(deployments);
1110
+ }
984
1111
 
985
- const endpoint = `/applications/${appUuid}/deployments${params.toString() ? `?${params.toString()}` : ''}`
986
- const result = await this.request<{ count: number; deployments: ICoolifyDeployment[] }>(endpoint)
1112
+ /**
1113
+ * Lists deployments for a specific application.
1114
+ *
1115
+ * Uses the /deployments/applications/{appUuid} endpoint which returns
1116
+ * all deployments (active, queued, and completed) for a single application.
1117
+ * This differs from listDeployments() which returns ALL deployments globally.
1118
+ *
1119
+ * @param appUuid - Application UUID
1120
+ * @returns Result with deployments list or error
1121
+ */
1122
+ async listApplicationDeployments(
1123
+ appUuid: string,
1124
+ ): Promise<Result<ICoolifyDeployment[], Error>> {
1125
+ log.info(`Listing deployments for application ${appUuid}`);
1126
+
1127
+ // API returns { count: number, deployments: ICoolifyDeployment[] }
1128
+ const result = await this.request<{
1129
+ count: number;
1130
+ deployments: ICoolifyDeployment[];
1131
+ }>(`/deployments/applications/${appUuid}`);
987
1132
 
988
1133
  if (result.error) {
989
- log.error(`Failed to get application deployments: ${result.error}`)
990
- return err(new Error(result.error))
1134
+ log.error(`Failed to list application deployments: ${result.error}`);
1135
+ return err(new Error(result.error));
991
1136
  }
992
1137
 
993
- const deployments = result.data?.deployments || []
994
- log.success(`Retrieved ${deployments.length} deployments for ${appUuid}`)
995
- return ok(deployments)
1138
+ const deployments = result.data?.deployments || [];
1139
+ log.success(`Listed ${deployments.length} deployments for ${appUuid}`);
1140
+ return ok(deployments);
996
1141
  }
997
1142
  }
998
1143
 
999
- let instance: CoolifyService | null = null
1144
+ let instance: CoolifyService | null = null;
1000
1145
 
1001
1146
  /**
1002
1147
  * Gets the singleton CoolifyService instance.
@@ -1005,9 +1150,9 @@ let instance: CoolifyService | null = null
1005
1150
  */
1006
1151
  export function getCoolifyService(): CoolifyService {
1007
1152
  if (!instance) {
1008
- instance = new CoolifyService()
1153
+ instance = new CoolifyService();
1009
1154
  }
1010
- return instance
1155
+ return instance;
1011
1156
  }
1012
1157
 
1013
1158
  // Re-export types
@@ -1027,4 +1172,4 @@ export type {
1027
1172
  ICoolifyLogsOptions,
1028
1173
  ICoolifyLogs,
1029
1174
  IProgressCallback,
1030
- } from './types.js'
1175
+ } from "./types.js";