@mks2508/coolify-mks-cli-mcp 0.6.3 → 0.9.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/dist/cli/coolify-state.d.ts +101 -5
- package/dist/cli/coolify-state.d.ts.map +1 -1
- package/dist/cli/index.js +23165 -11543
- package/dist/cli/ui/highlighter.d.ts +28 -0
- package/dist/cli/ui/highlighter.d.ts.map +1 -0
- package/dist/cli/ui/index.d.ts +9 -0
- package/dist/cli/ui/index.d.ts.map +1 -0
- package/dist/cli/ui/spinners.d.ts +100 -0
- package/dist/cli/ui/spinners.d.ts.map +1 -0
- package/dist/cli/ui/tables.d.ts +103 -0
- package/dist/cli/ui/tables.d.ts.map +1 -0
- package/dist/coolify/config.d.ts +25 -0
- package/dist/coolify/config.d.ts.map +1 -1
- package/dist/coolify/index.d.ts +139 -12
- package/dist/coolify/index.d.ts.map +1 -1
- package/dist/coolify/types.d.ts +160 -2
- package/dist/coolify/types.d.ts.map +1 -1
- package/dist/examples/demo-ui.d.ts +8 -0
- package/dist/examples/demo-ui.d.ts.map +1 -0
- package/dist/index.cjs +2580 -230
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2598 -226
- package/dist/index.js.map +1 -1
- package/dist/sdk.d.ts +96 -7
- package/dist/sdk.d.ts.map +1 -1
- package/dist/server/stdio.js +475 -73
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/utils/env-parser.d.ts +24 -0
- package/dist/utils/env-parser.d.ts.map +1 -0
- package/dist/utils/format.d.ts +32 -0
- package/dist/utils/format.d.ts.map +1 -1
- package/package.json +17 -4
- package/src/cli/actions.ts +9 -2
- package/src/cli/commands/create.ts +332 -24
- package/src/cli/commands/db.ts +37 -0
- package/src/cli/commands/delete.ts +6 -2
- package/src/cli/commands/deploy.ts +347 -49
- package/src/cli/commands/deployments.ts +6 -2
- package/src/cli/commands/diagnose.ts +3 -3
- package/src/cli/commands/env.ts +424 -31
- package/src/cli/commands/exec.ts +6 -2
- package/src/cli/commands/init.ts +991 -0
- package/src/cli/commands/logs.ts +224 -24
- package/src/cli/commands/main-menu.ts +21 -0
- package/src/cli/commands/projects.ts +312 -29
- package/src/cli/commands/restart.ts +6 -2
- package/src/cli/commands/service-logs.ts +14 -0
- package/src/cli/commands/show.ts +45 -12
- package/src/cli/commands/start.ts +6 -2
- package/src/cli/commands/status.ts +554 -0
- package/src/cli/commands/stop.ts +6 -2
- package/src/cli/commands/svc.ts +7 -1
- package/src/cli/commands/update.ts +79 -2
- package/src/cli/commands/volumes.ts +293 -0
- package/src/cli/coolify-state.ts +203 -12
- package/src/cli/index.ts +138 -11
- package/src/cli/name-resolver.ts +228 -0
- package/src/cli/ui/banner.ts +276 -0
- package/src/cli/ui/highlighter.ts +176 -0
- package/src/cli/ui/index.ts +9 -0
- package/src/cli/ui/prompts.ts +155 -0
- package/src/cli/ui/screen.ts +630 -0
- package/src/cli/ui/select.ts +280 -0
- package/src/cli/ui/spinners.ts +256 -0
- package/src/cli/ui/tables.ts +407 -0
- package/src/coolify/config.ts +75 -0
- package/src/coolify/index.ts +565 -101
- package/src/coolify/types.ts +165 -2
- package/src/examples/demo-ui.ts +78 -0
- package/src/sdk.ts +211 -1
- package/src/tools/definitions.ts +22 -0
- package/src/tools/handlers.ts +19 -0
- package/src/utils/env-parser.ts +45 -0
- package/src/utils/format.ts +178 -0
package/src/coolify/types.ts
CHANGED
|
@@ -44,6 +44,8 @@ export interface ICoolifyAppOptions {
|
|
|
44
44
|
projectUuid: string;
|
|
45
45
|
/** Environment UUID */
|
|
46
46
|
environmentUuid: string;
|
|
47
|
+
/** Environment name (required by API alongside environment_uuid) */
|
|
48
|
+
environmentName?: string;
|
|
47
49
|
/** Server UUID */
|
|
48
50
|
serverUuid: string;
|
|
49
51
|
/** Destination UUID (optional, for specific destination targeting) */
|
|
@@ -52,7 +54,7 @@ export interface ICoolifyAppOptions {
|
|
|
52
54
|
type?: TCoolifyApplicationType;
|
|
53
55
|
/** GitHub App UUID (required for private-github-app type — get from listGithubApps) */
|
|
54
56
|
githubAppUuid?: string;
|
|
55
|
-
/**
|
|
57
|
+
/** Git repository URL (for git-based apps) */
|
|
56
58
|
githubRepoUrl?: string;
|
|
57
59
|
/** Git branch */
|
|
58
60
|
branch?: string;
|
|
@@ -72,6 +74,8 @@ export interface ICoolifyAppOptions {
|
|
|
72
74
|
dockerfileLocation?: string;
|
|
73
75
|
/** Base directory for build context (default: "/") */
|
|
74
76
|
baseDirectory?: string;
|
|
77
|
+
/** Private key UUID (required for private-deploy-key type) */
|
|
78
|
+
privateKeyUuid?: string;
|
|
75
79
|
}
|
|
76
80
|
|
|
77
81
|
/**
|
|
@@ -102,10 +106,33 @@ export interface ICoolifyUpdateOptions {
|
|
|
102
106
|
domains?: string;
|
|
103
107
|
/** Docker Compose domains - JSON object: { "service-name": { "domain": "https://..." } } — for dockercompose apps */
|
|
104
108
|
dockerComposeDomains?: string;
|
|
109
|
+
/** Raw docker-compose YAML content — for dockercompose buildPack only.
|
|
110
|
+
* Pass the full compose string; Coolify will replace the existing one. */
|
|
111
|
+
dockerComposeRaw?: string;
|
|
105
112
|
/** Force HTTPS redirect */
|
|
106
113
|
isForceHttpsEnabled?: boolean;
|
|
107
114
|
/** Enable auto deploy on git push */
|
|
108
115
|
isAutoDeployEnabled?: boolean;
|
|
116
|
+
/** Watch paths for selective auto-deploy (newline-separated globs, e.g. "src/**\npackages/**") */
|
|
117
|
+
watchPaths?: string | null;
|
|
118
|
+
/** Enable health check for the application */
|
|
119
|
+
healthCheckEnabled?: boolean;
|
|
120
|
+
/** Health check endpoint path (e.g., "/health") */
|
|
121
|
+
healthCheckPath?: string;
|
|
122
|
+
/** Health check port (defaults to application port) */
|
|
123
|
+
healthCheckPort?: string | number;
|
|
124
|
+
/** Health check HTTP method (e.g., "GET") */
|
|
125
|
+
healthCheckMethod?: string;
|
|
126
|
+
/** Health check interval in seconds */
|
|
127
|
+
healthCheckInterval?: number;
|
|
128
|
+
/** Health check timeout in seconds */
|
|
129
|
+
healthCheckTimeout?: number;
|
|
130
|
+
/** Number of retries before marking unhealthy */
|
|
131
|
+
healthCheckRetries?: number;
|
|
132
|
+
/** Grace period in seconds before health checks start */
|
|
133
|
+
healthCheckStartPeriod?: number;
|
|
134
|
+
/** Expected HTTP return code for healthy status */
|
|
135
|
+
healthCheckReturnCode?: number;
|
|
109
136
|
}
|
|
110
137
|
|
|
111
138
|
/**
|
|
@@ -222,10 +249,29 @@ export interface ICoolifyApplication {
|
|
|
222
249
|
dockerfile_location?: string | null;
|
|
223
250
|
/** Base directory */
|
|
224
251
|
base_directory?: string | null;
|
|
252
|
+
/** Watch paths for selective auto-deploy (newline-separated globs) */
|
|
253
|
+
watch_paths?: string | null;
|
|
254
|
+
/** Raw docker-compose YAML content (dockercompose buildPack only) */
|
|
255
|
+
docker_compose_raw?: string | null;
|
|
256
|
+
/** Application settings (returned from GET /applications/{uuid}) */
|
|
257
|
+
settings?: {
|
|
258
|
+
is_auto_deploy_enabled?: boolean;
|
|
259
|
+
is_force_https_enabled?: boolean;
|
|
260
|
+
is_preview_deployments_enabled?: boolean;
|
|
261
|
+
[key: string]: unknown;
|
|
262
|
+
} | null;
|
|
225
263
|
/** Server status (boolean) */
|
|
226
264
|
server_status?: boolean;
|
|
227
|
-
/** Environment ID */
|
|
265
|
+
/** Environment ID (numeric) */
|
|
228
266
|
environment_id?: number;
|
|
267
|
+
/** Project UUID */
|
|
268
|
+
project_uuid?: string;
|
|
269
|
+
/** Environment UUID */
|
|
270
|
+
environment_uuid?: string;
|
|
271
|
+
/** Source type (e.g., "App\\Models\\GithubApp", "App\\Models\\DeployKey") */
|
|
272
|
+
source_type?: string;
|
|
273
|
+
/** Type (legacy, may be null) */
|
|
274
|
+
type?: string;
|
|
229
275
|
/** Destination info */
|
|
230
276
|
destination?: {
|
|
231
277
|
uuid: string;
|
|
@@ -361,6 +407,8 @@ export interface ICoolifyDatabase {
|
|
|
361
407
|
status: string;
|
|
362
408
|
version?: string;
|
|
363
409
|
description?: string | null;
|
|
410
|
+
/** Environment ID (numeric, for grouping under projects) */
|
|
411
|
+
environment_id?: number;
|
|
364
412
|
destination?: {
|
|
365
413
|
uuid: string;
|
|
366
414
|
name: string;
|
|
@@ -382,6 +430,8 @@ export interface ICoolifyService {
|
|
|
382
430
|
status: string;
|
|
383
431
|
version?: string;
|
|
384
432
|
description?: string | null;
|
|
433
|
+
/** Environment ID (numeric, for grouping under projects) */
|
|
434
|
+
environment_id?: number;
|
|
385
435
|
project_uuid?: string;
|
|
386
436
|
environment_uuid?: string;
|
|
387
437
|
server_uuid?: string;
|
|
@@ -439,3 +489,116 @@ export interface ICoolifyVersion {
|
|
|
439
489
|
latest_version?: string;
|
|
440
490
|
is_latest?: boolean;
|
|
441
491
|
}
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* A resource (app, database, or service) within an environment.
|
|
495
|
+
*/
|
|
496
|
+
export interface ICoolifyResource {
|
|
497
|
+
/** Resource UUID */
|
|
498
|
+
uuid: string;
|
|
499
|
+
/** Resource name */
|
|
500
|
+
name: string;
|
|
501
|
+
/** Resource type: app, database, or service */
|
|
502
|
+
kind: "app" | "database" | "service";
|
|
503
|
+
/** Current status */
|
|
504
|
+
status: string;
|
|
505
|
+
/** FQDN/domain if configured */
|
|
506
|
+
fqdn?: string | null;
|
|
507
|
+
/** Database engine type (only for databases) */
|
|
508
|
+
dbType?: string;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* An environment within a project, containing resources.
|
|
513
|
+
*/
|
|
514
|
+
export interface ICoolifyEnvironmentNode {
|
|
515
|
+
/** Environment ID (numeric) */
|
|
516
|
+
id: number;
|
|
517
|
+
/** Environment UUID */
|
|
518
|
+
uuid: string;
|
|
519
|
+
/** Environment name (e.g., "production", "staging") */
|
|
520
|
+
name: string;
|
|
521
|
+
/** Resources in this environment */
|
|
522
|
+
resources: ICoolifyResource[];
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* A project node in the infrastructure tree.
|
|
527
|
+
*/
|
|
528
|
+
export interface ICoolifyProjectNode {
|
|
529
|
+
/** Project UUID */
|
|
530
|
+
uuid: string;
|
|
531
|
+
/** Project name */
|
|
532
|
+
name: string;
|
|
533
|
+
/** Project description */
|
|
534
|
+
description?: string | null;
|
|
535
|
+
/** Environments in this project */
|
|
536
|
+
environments: ICoolifyEnvironmentNode[];
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* Full infrastructure tree: projects → environments → resources.
|
|
541
|
+
* Also includes global server info and aggregate counts.
|
|
542
|
+
*/
|
|
543
|
+
export interface ICoolifyInfrastructureTree {
|
|
544
|
+
/** Server info */
|
|
545
|
+
server: {
|
|
546
|
+
name: string;
|
|
547
|
+
ip?: string;
|
|
548
|
+
uuid?: string;
|
|
549
|
+
};
|
|
550
|
+
/** Projects with their environments and resources */
|
|
551
|
+
projects: ICoolifyProjectNode[];
|
|
552
|
+
/** Aggregate counts */
|
|
553
|
+
counts: {
|
|
554
|
+
projects: number;
|
|
555
|
+
apps: number;
|
|
556
|
+
databases: number;
|
|
557
|
+
services: number;
|
|
558
|
+
healthy: number;
|
|
559
|
+
running: number;
|
|
560
|
+
stopped: number;
|
|
561
|
+
unhealthy: number;
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* GitHub App configuration from Coolify API (snake_case).
|
|
567
|
+
* OpenAPI schema: GET /github-apps returns ~16 fields.
|
|
568
|
+
*
|
|
569
|
+
* @see https://docs.coolify.io/api-reference/github-apps
|
|
570
|
+
*/
|
|
571
|
+
export interface ICoolifyGithubApp {
|
|
572
|
+
/** Coolify internal ID */
|
|
573
|
+
id: number;
|
|
574
|
+
/** Coolify UUID */
|
|
575
|
+
uuid: string;
|
|
576
|
+
/** App display name */
|
|
577
|
+
name: string;
|
|
578
|
+
/** GitHub organization or null for personal apps */
|
|
579
|
+
organization: string | null;
|
|
580
|
+
/** GitHub API URL (e.g., https://api.github.com) */
|
|
581
|
+
api_url: string;
|
|
582
|
+
/** GitHub App web URL (e.g., https://github.com/apps/my-app) */
|
|
583
|
+
html_url: string;
|
|
584
|
+
/** Custom SSH user for git operations (default: "git") */
|
|
585
|
+
custom_user: string;
|
|
586
|
+
/** Custom SSH port (default: 22) */
|
|
587
|
+
custom_port: number;
|
|
588
|
+
/** GitHub App ID (different from Coolify's internal id) */
|
|
589
|
+
app_id: number;
|
|
590
|
+
/** GitHub Installation ID */
|
|
591
|
+
installation_id: number;
|
|
592
|
+
/** GitHub OAuth App Client ID */
|
|
593
|
+
client_id: string;
|
|
594
|
+
/** GitHub private key ID on the Coolify server */
|
|
595
|
+
private_key_id: number;
|
|
596
|
+
/** Whether app is installed across all org repos (not just selected ones) */
|
|
597
|
+
is_system_wide: boolean;
|
|
598
|
+
/** Whether this is a public GitHub App (public installer) vs private */
|
|
599
|
+
is_public: boolean;
|
|
600
|
+
/** Coolify team ID that owns this app */
|
|
601
|
+
team_id: number;
|
|
602
|
+
/** App type string */
|
|
603
|
+
type: string;
|
|
604
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Demo script showing new CLI UI features.
|
|
4
|
+
*
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { highlightEnvBlock, createEnvTable, createChangeSummary } from "../cli/ui/index.js";
|
|
9
|
+
|
|
10
|
+
const sampleEnvVars = [
|
|
11
|
+
{
|
|
12
|
+
key: "DATABASE_URL",
|
|
13
|
+
value: "postgresql://user:pass@localhost:5432/mydb",
|
|
14
|
+
is_runtime: true,
|
|
15
|
+
is_buildtime: false,
|
|
16
|
+
is_required: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
key: "API_KEY",
|
|
20
|
+
value: "sk_test_1234567890abcdef",
|
|
21
|
+
is_runtime: true,
|
|
22
|
+
is_buildtime: false,
|
|
23
|
+
is_required: false,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
key: "NODE_ENV",
|
|
27
|
+
value: "production",
|
|
28
|
+
is_runtime: true,
|
|
29
|
+
is_buildtime: true,
|
|
30
|
+
is_required: false,
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
key: "BUILD_ARG",
|
|
34
|
+
value: "some_value",
|
|
35
|
+
is_runtime: false,
|
|
36
|
+
is_buildtime: true,
|
|
37
|
+
is_required: false,
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
const sampleChanges = {
|
|
42
|
+
added: [
|
|
43
|
+
{ key: "NEW_VAR", value: "new_value" },
|
|
44
|
+
{ key: "ANOTHER_NEW", value: "another" },
|
|
45
|
+
],
|
|
46
|
+
updated: [
|
|
47
|
+
{ key: "DATABASE_URL", value: "new_db_url", oldValue: "old_db_url" },
|
|
48
|
+
{ key: "API_KEY", value: "new_key", oldValue: "old_key" },
|
|
49
|
+
],
|
|
50
|
+
removed: ["DEPRECATED_VAR", "OLD_SETTING"],
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
console.log("\n" + "=".repeat(60));
|
|
54
|
+
console.log(" CLI UI Demo - Syntax Highlighting");
|
|
55
|
+
console.log("=".repeat(60) + "\n");
|
|
56
|
+
|
|
57
|
+
// Demo 1: Syntax highlighted .env file
|
|
58
|
+
console.log("1️⃣ Syntax Highlighted .env File");
|
|
59
|
+
console.log("─".repeat(60));
|
|
60
|
+
const envContent = await Bun.file("/tmp/test.env").text();
|
|
61
|
+
console.log(highlightEnvBlock(envContent));
|
|
62
|
+
console.log();
|
|
63
|
+
|
|
64
|
+
// Demo 2: Table view
|
|
65
|
+
console.log("2️⃣ Table View with cli-table3");
|
|
66
|
+
console.log("─".repeat(60));
|
|
67
|
+
console.log(createEnvTable(sampleEnvVars, { compact: true, showType: true }));
|
|
68
|
+
console.log();
|
|
69
|
+
|
|
70
|
+
// Demo 3: Change summary
|
|
71
|
+
console.log("3️⃣ Change Summary for Sync");
|
|
72
|
+
console.log("─".repeat(60));
|
|
73
|
+
console.log(createChangeSummary(sampleChanges));
|
|
74
|
+
console.log();
|
|
75
|
+
|
|
76
|
+
console.log("=".repeat(60));
|
|
77
|
+
console.log(" ✨ Demo complete!");
|
|
78
|
+
console.log("=".repeat(60) + "\n");
|
package/src/sdk.ts
CHANGED
|
@@ -32,6 +32,7 @@ import type {
|
|
|
32
32
|
IProgressCallback,
|
|
33
33
|
} from "./coolify/types.js";
|
|
34
34
|
import type { ICoolifyEnvVar } from "./coolify/index.js";
|
|
35
|
+
import { parseEnvContent } from "./utils/env-parser.js";
|
|
35
36
|
|
|
36
37
|
/** SDK configuration options. */
|
|
37
38
|
export interface ICoolifyOptions {
|
|
@@ -83,6 +84,11 @@ class ApplicationsResource {
|
|
|
83
84
|
return unwrap(await this.svc.listApplicationSummaries());
|
|
84
85
|
}
|
|
85
86
|
|
|
87
|
+
/** Get a single application by UUID with full details (including settings and watch_paths). */
|
|
88
|
+
async get(uuid: string): Promise<ICoolifyApplication> {
|
|
89
|
+
return unwrap(await this.svc.getApplication(uuid));
|
|
90
|
+
}
|
|
91
|
+
|
|
86
92
|
/** Resolve application by name, domain, or UUID. */
|
|
87
93
|
async resolve(query: string): Promise<ICoolifyApplication> {
|
|
88
94
|
return unwrap(await this.svc.resolveApplication(query));
|
|
@@ -180,6 +186,143 @@ class ApplicationsResource {
|
|
|
180
186
|
async deleteEnv(uuid: string, key: string) {
|
|
181
187
|
return unwrap(await this.svc.deleteEnvironmentVariable(uuid, key));
|
|
182
188
|
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Sync environment variables from a local .env file to Coolify.
|
|
192
|
+
*
|
|
193
|
+
* @param uuid - Application UUID
|
|
194
|
+
* @param options - Sync options
|
|
195
|
+
* @returns Sync result with changes applied
|
|
196
|
+
*/
|
|
197
|
+
async syncEnv(
|
|
198
|
+
uuid: string,
|
|
199
|
+
options: {
|
|
200
|
+
/** Path to .env file (default: reads from .env in cwd) */
|
|
201
|
+
filePath?: string;
|
|
202
|
+
/** Preview changes without applying */
|
|
203
|
+
dryRun?: boolean;
|
|
204
|
+
/** Delete vars not in file */
|
|
205
|
+
prune?: boolean;
|
|
206
|
+
/** Callback for progress updates (optional) */
|
|
207
|
+
onProgress?: (update: {
|
|
208
|
+
type: 'add' | 'update' | 'remove';
|
|
209
|
+
key: string;
|
|
210
|
+
value?: string;
|
|
211
|
+
}) => void;
|
|
212
|
+
} = {},
|
|
213
|
+
): Promise<{
|
|
214
|
+
added: Array<{ key: string; value: string }>;
|
|
215
|
+
updated: Array<{ key: string; value: string; oldValue: string }>;
|
|
216
|
+
removed: string[];
|
|
217
|
+
skipped: number;
|
|
218
|
+
}> {
|
|
219
|
+
const { filePath, dryRun = false, prune = false, onProgress } = options;
|
|
220
|
+
|
|
221
|
+
// 1. Read and parse .env file (absolute path resolution + Node fs fallback)
|
|
222
|
+
let envContent: string;
|
|
223
|
+
const { readFileSync } = await import('node:fs');
|
|
224
|
+
const { resolve, isAbsolute } = await import('node:path');
|
|
225
|
+
const target = filePath || '.env';
|
|
226
|
+
const absoluteTarget = isAbsolute(target) ? target : resolve(process.cwd(), target);
|
|
227
|
+
try {
|
|
228
|
+
// Prefer Node fs (more portable across Bun versions + clearer errors than Bun.file()).
|
|
229
|
+
envContent = readFileSync(absoluteTarget, 'utf-8');
|
|
230
|
+
} catch (err) {
|
|
231
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Cannot read env file at ${absoluteTarget} (resolved from ${target}): ${msg}`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const localVars = this.parseEnvContent(envContent);
|
|
238
|
+
|
|
239
|
+
if (localVars.size === 0) {
|
|
240
|
+
return { added: [], updated: [], removed: [], skipped: 0 };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// 2. Get current vars from Coolify
|
|
244
|
+
const currentVarsList = await this.envVars(uuid);
|
|
245
|
+
const currentVars = new Map(
|
|
246
|
+
currentVarsList.map((v) => [v.key, v.value]),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// 3. Calculate changes
|
|
250
|
+
const toAdd: Array<{ key: string; value: string }> = [];
|
|
251
|
+
const toUpdate: Array<{
|
|
252
|
+
key: string;
|
|
253
|
+
value: string;
|
|
254
|
+
oldValue: string;
|
|
255
|
+
}> = [];
|
|
256
|
+
const toRemove: string[] = [];
|
|
257
|
+
|
|
258
|
+
for (const [key, value] of localVars.entries()) {
|
|
259
|
+
const currentValue = currentVars.get(key);
|
|
260
|
+
if (!currentValue) {
|
|
261
|
+
toAdd.push({ key, value });
|
|
262
|
+
} else if (currentValue !== value) {
|
|
263
|
+
toUpdate.push({ key, value, oldValue: currentValue });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (prune) {
|
|
268
|
+
for (const key of currentVars.keys()) {
|
|
269
|
+
if (!localVars.has(key)) {
|
|
270
|
+
toRemove.push(key);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 4. Apply changes (unless dry-run)
|
|
276
|
+
if (!dryRun) {
|
|
277
|
+
// Add new variables
|
|
278
|
+
for (const { key, value } of toAdd) {
|
|
279
|
+
await this.setEnv(uuid, key, value, false);
|
|
280
|
+
onProgress?.({ type: 'add', key, value });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Update existing variables
|
|
284
|
+
for (const { key, value } of toUpdate) {
|
|
285
|
+
await this.setEnv(uuid, key, value, false);
|
|
286
|
+
onProgress?.({ type: 'update', key, value });
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Remove pruned variables
|
|
290
|
+
for (const key of toRemove) {
|
|
291
|
+
await this.deleteEnv(uuid, key);
|
|
292
|
+
onProgress?.({ type: 'remove', key });
|
|
293
|
+
}
|
|
294
|
+
} else {
|
|
295
|
+
// Report what would happen in dry-run
|
|
296
|
+
for (const { key, value } of toAdd) {
|
|
297
|
+
onProgress?.({ type: 'add', key, value });
|
|
298
|
+
}
|
|
299
|
+
for (const { key, value } of toUpdate) {
|
|
300
|
+
onProgress?.({ type: 'update', key, value });
|
|
301
|
+
}
|
|
302
|
+
for (const key of toRemove) {
|
|
303
|
+
onProgress?.({ type: 'remove', key });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
added: toAdd,
|
|
309
|
+
updated: toUpdate,
|
|
310
|
+
removed: toRemove,
|
|
311
|
+
skipped: currentVars.size - toUpdate.length - toRemove.length,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Parse .env file content into a Map.
|
|
317
|
+
* Delegates to the shared utility in `./utils/env-parser.js` so the SDK
|
|
318
|
+
* and CLI use a single implementation.
|
|
319
|
+
*
|
|
320
|
+
* @param content - The .env file content
|
|
321
|
+
* @returns Map of environment variables
|
|
322
|
+
*/
|
|
323
|
+
private parseEnvContent(content: string): Map<string, string> {
|
|
324
|
+
return parseEnvContent(content);
|
|
325
|
+
}
|
|
183
326
|
}
|
|
184
327
|
|
|
185
328
|
class DatabasesResource {
|
|
@@ -237,6 +380,36 @@ class DatabasesResource {
|
|
|
237
380
|
async deleteBackup(dbUuid: string, backupUuid: string) {
|
|
238
381
|
return unwrap(await this.svc.deleteDatabaseBackup(dbUuid, backupUuid));
|
|
239
382
|
}
|
|
383
|
+
|
|
384
|
+
/** List env vars for a database. */
|
|
385
|
+
async envVars(uuid: string): Promise<ICoolifyEnvVar[]> {
|
|
386
|
+
return unwrap(await this.svc.listDatabaseEnvVars(uuid));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Bulk set (create-or-update) env vars for a database.
|
|
391
|
+
*
|
|
392
|
+
* Note: the database schema is narrower than applications — only
|
|
393
|
+
* `is_literal`, `is_multiline`, `is_shown_once` are accepted. No
|
|
394
|
+
* `is_preview` / `is_buildtime` / `is_runtime`.
|
|
395
|
+
*/
|
|
396
|
+
async bulkSetEnv(
|
|
397
|
+
uuid: string,
|
|
398
|
+
vars: Array<{
|
|
399
|
+
key: string;
|
|
400
|
+
value: string;
|
|
401
|
+
is_literal?: boolean;
|
|
402
|
+
is_multiline?: boolean;
|
|
403
|
+
is_shown_once?: boolean;
|
|
404
|
+
}>,
|
|
405
|
+
) {
|
|
406
|
+
return unwrap(await this.svc.bulkUpdateDatabaseEnvVars(uuid, vars));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/** Delete a database env var by key. Resolves key → UUID, then DELETE. */
|
|
410
|
+
async deleteEnv(uuid: string, key: string) {
|
|
411
|
+
return unwrap(await this.svc.deleteDatabaseEnvVar(uuid, key));
|
|
412
|
+
}
|
|
240
413
|
}
|
|
241
414
|
|
|
242
415
|
class ServicesResource {
|
|
@@ -281,11 +454,48 @@ class ServicesResource {
|
|
|
281
454
|
return unwrap(await this.svc.listServiceEnvVars(uuid));
|
|
282
455
|
}
|
|
283
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Sets (creates or updates) a single env var for a service.
|
|
459
|
+
*
|
|
460
|
+
* Delegates to the bulk endpoint `PATCH /services/{uuid}/envs/bulk`,
|
|
461
|
+
* which has create-or-update semantics — calling this for the same key
|
|
462
|
+
* a second time updates the override value rather than failing with
|
|
463
|
+
* 409 "already exists" (which is what the raw `POST /services/{uuid}/envs`
|
|
464
|
+
* endpoint does). Mirrors `ApplicationsResource.setEnv`.
|
|
465
|
+
*
|
|
466
|
+
* @param uuid - Service UUID
|
|
467
|
+
* @param data - Env var data (key, value, is_preview)
|
|
468
|
+
*/
|
|
284
469
|
async setEnv(
|
|
285
470
|
uuid: string,
|
|
286
471
|
data: { key: string; value: string; is_preview?: boolean },
|
|
287
472
|
) {
|
|
288
|
-
return unwrap(await this.svc.
|
|
473
|
+
return unwrap(await this.svc.bulkUpdateServiceEnvVars(uuid, [data]));
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Bulk set (create-or-update) env vars for a service.
|
|
478
|
+
* Mirrors `ApplicationsResource.bulkSetEnv`.
|
|
479
|
+
*
|
|
480
|
+
* @param uuid - Service UUID
|
|
481
|
+
* @param vars - Array of env var definitions to upsert
|
|
482
|
+
*/
|
|
483
|
+
async bulkSetEnv(
|
|
484
|
+
uuid: string,
|
|
485
|
+
vars: Array<{
|
|
486
|
+
key: string;
|
|
487
|
+
value: string;
|
|
488
|
+
is_preview?: boolean;
|
|
489
|
+
is_buildtime?: boolean;
|
|
490
|
+
is_runtime?: boolean;
|
|
491
|
+
}>,
|
|
492
|
+
) {
|
|
493
|
+
return unwrap(await this.svc.bulkUpdateServiceEnvVars(uuid, vars));
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** Delete a service env var by key. Resolves key → UUID, then DELETE. */
|
|
497
|
+
async deleteEnv(uuid: string, key: string) {
|
|
498
|
+
return unwrap(await this.svc.deleteServiceEnvVar(uuid, key));
|
|
289
499
|
}
|
|
290
500
|
}
|
|
291
501
|
|
package/src/tools/definitions.ts
CHANGED
|
@@ -306,6 +306,12 @@ Redeploy after updating to apply changes.`,
|
|
|
306
306
|
type: "string" as const,
|
|
307
307
|
description: 'Base directory for build context (default: "/")',
|
|
308
308
|
},
|
|
309
|
+
watchPaths: {
|
|
310
|
+
type: "string" as const,
|
|
311
|
+
description:
|
|
312
|
+
'Watch paths for selective auto-deploy. Newline-separated globs (e.g. "src/**\\npackages/**"). Set to empty string or null to clear.',
|
|
313
|
+
nullable: true,
|
|
314
|
+
},
|
|
309
315
|
dockerComposeDomains: {
|
|
310
316
|
type: "string" as const,
|
|
311
317
|
description:
|
|
@@ -315,6 +321,22 @@ Redeploy after updating to apply changes.`,
|
|
|
315
321
|
required: ["uuid"],
|
|
316
322
|
},
|
|
317
323
|
},
|
|
324
|
+
{
|
|
325
|
+
name: "get_application",
|
|
326
|
+
description: `Get detailed information about a Coolify application including settings (auto-deploy, force HTTPS) and watch paths.
|
|
327
|
+
|
|
328
|
+
Returns full application details that list_applications doesn't include.`,
|
|
329
|
+
inputSchema: {
|
|
330
|
+
type: "object" as const,
|
|
331
|
+
properties: {
|
|
332
|
+
uuid: {
|
|
333
|
+
type: "string" as const,
|
|
334
|
+
description: "Application UUID",
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
required: ["uuid"],
|
|
338
|
+
},
|
|
339
|
+
},
|
|
318
340
|
{
|
|
319
341
|
name: "set_domains",
|
|
320
342
|
description: `Set domains/FQDN for a Coolify application.
|
package/src/tools/handlers.ts
CHANGED
|
@@ -159,6 +159,7 @@ export async function handleToolCall(
|
|
|
159
159
|
domains: a.domains,
|
|
160
160
|
isForceHttpsEnabled: a.isForceHttpsEnabled,
|
|
161
161
|
isAutoDeployEnabled: a.isAutoDeployEnabled,
|
|
162
|
+
watchPaths: a.watchPaths,
|
|
162
163
|
})
|
|
163
164
|
.then((app) => ({
|
|
164
165
|
message: `Application ${a.uuid} updated`,
|
|
@@ -166,6 +167,24 @@ export async function handleToolCall(
|
|
|
166
167
|
})),
|
|
167
168
|
"Failed to update application",
|
|
168
169
|
);
|
|
170
|
+
case "get_application":
|
|
171
|
+
return mcpCall(
|
|
172
|
+
(s) =>
|
|
173
|
+
s.applications.get(a.uuid).then((app) => ({
|
|
174
|
+
uuid: app.uuid,
|
|
175
|
+
name: app.name,
|
|
176
|
+
status: app.status,
|
|
177
|
+
fqdn: app.fqdn,
|
|
178
|
+
git_repository: app.git_repository,
|
|
179
|
+
git_branch: app.git_branch,
|
|
180
|
+
build_pack: app.build_pack,
|
|
181
|
+
dockerfile_location: app.dockerfile_location,
|
|
182
|
+
base_directory: app.base_directory,
|
|
183
|
+
watch_paths: app.watch_paths,
|
|
184
|
+
settings: app.settings,
|
|
185
|
+
})),
|
|
186
|
+
"Failed to get application",
|
|
187
|
+
);
|
|
169
188
|
case "get_application_logs":
|
|
170
189
|
return mcpCall(
|
|
171
190
|
(s) =>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* .env file parser shared between the SDK (syncEnv) and the CLI (--sync stdin).
|
|
3
|
+
*
|
|
4
|
+
* Kept as a standalone exported function (not a class method) so both the
|
|
5
|
+
* SDK's ApplicationsResource and the CLI's stdin sync handler can use the
|
|
6
|
+
* same implementation without coupling to either.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Parses .env-style content into a Map.
|
|
13
|
+
*
|
|
14
|
+
* Handles:
|
|
15
|
+
* - Comments (lines starting with `#`)
|
|
16
|
+
* - Empty lines (skipped)
|
|
17
|
+
* - Quoted values (`"value"` or `'value'`)
|
|
18
|
+
* - Values containing `=` (only the first `=` is the separator)
|
|
19
|
+
* - Lines without `=` (skipped — invalid)
|
|
20
|
+
*
|
|
21
|
+
* @param content - File contents in KEY=VALUE format
|
|
22
|
+
* @returns Map of key → value (empty string for `KEY=` with no value)
|
|
23
|
+
*/
|
|
24
|
+
export function parseEnvContent(content: string): Map<string, string> {
|
|
25
|
+
const envVars = new Map<string, string>();
|
|
26
|
+
for (const line of content.split("\n")) {
|
|
27
|
+
const trimmed = line.trim();
|
|
28
|
+
// Skip comments and empty lines.
|
|
29
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
30
|
+
const eq = trimmed.indexOf("=");
|
|
31
|
+
// Skip invalid lines (no `=`).
|
|
32
|
+
if (eq === -1) continue;
|
|
33
|
+
const key = trimmed.slice(0, eq).trim();
|
|
34
|
+
let value = trimmed.slice(eq + 1).trim();
|
|
35
|
+
// Strip matching surrounding quotes.
|
|
36
|
+
if (
|
|
37
|
+
(value.startsWith('"') && value.endsWith('"')) ||
|
|
38
|
+
(value.startsWith("'") && value.endsWith("'"))
|
|
39
|
+
) {
|
|
40
|
+
value = value.slice(1, -1);
|
|
41
|
+
}
|
|
42
|
+
if (key) envVars.set(key, value);
|
|
43
|
+
}
|
|
44
|
+
return envVars;
|
|
45
|
+
}
|