@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/tools/definitions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAE/D;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,IAAI,
|
|
1
|
+
{"version":3,"file":"definitions.d.ts","sourceRoot":"","sources":["../../src/tools/definitions.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,oCAAoC,CAAC;AAE/D;;GAEG;AACH,eAAO,MAAM,YAAY,EAAE,IAAI,EAyrC9B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/tools/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AA+DzE;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"handlers.d.ts","sourceRoot":"","sources":["../../src/tools/handlers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AA+DzE;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,OAAO,CAAC,cAAc,CAAC,CAmgBzB"}
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
* Parses .env-style content into a Map.
|
|
12
|
+
*
|
|
13
|
+
* Handles:
|
|
14
|
+
* - Comments (lines starting with `#`)
|
|
15
|
+
* - Empty lines (skipped)
|
|
16
|
+
* - Quoted values (`"value"` or `'value'`)
|
|
17
|
+
* - Values containing `=` (only the first `=` is the separator)
|
|
18
|
+
* - Lines without `=` (skipped — invalid)
|
|
19
|
+
*
|
|
20
|
+
* @param content - File contents in KEY=VALUE format
|
|
21
|
+
* @returns Map of key → value (empty string for `KEY=` with no value)
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseEnvContent(content: string): Map<string, string>;
|
|
24
|
+
//# sourceMappingURL=env-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env-parser.d.ts","sourceRoot":"","sources":["../../src/utils/env-parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;;;;;GAYG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAqBpE"}
|
package/dist/utils/format.d.ts
CHANGED
|
@@ -28,6 +28,38 @@ export declare function formatStatus(status: string): string;
|
|
|
28
28
|
* @returns Formatted string with unit
|
|
29
29
|
*/
|
|
30
30
|
export declare function formatBytes(bytes: number): string;
|
|
31
|
+
/**
|
|
32
|
+
* Validates a port number string (single or comma-separated).
|
|
33
|
+
* Valid: "3000", "3000,3001", "80,443,8080"
|
|
34
|
+
* Invalid: "abc", "99999", "0", "-1", "3000,", ",3000"
|
|
35
|
+
*
|
|
36
|
+
* @param ports - Port string to validate
|
|
37
|
+
* @returns Object with valid flag, parsed ports, and error message
|
|
38
|
+
*/
|
|
39
|
+
export declare function validatePorts(ports: string): {
|
|
40
|
+
valid: boolean;
|
|
41
|
+
ports: number[];
|
|
42
|
+
error?: string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Parses EXPOSE directives from a Dockerfile.
|
|
46
|
+
*
|
|
47
|
+
* @param dockerfilePath - Path to the Dockerfile
|
|
48
|
+
* @returns Array of exposed port numbers, empty if none found or file unreadable
|
|
49
|
+
*/
|
|
50
|
+
export declare function parseDockerfileExpose(dockerfilePath: string): number[];
|
|
51
|
+
/**
|
|
52
|
+
* Validates a .coolify.json state object (single-app format).
|
|
53
|
+
* Checks required fields, types, and port validity.
|
|
54
|
+
*
|
|
55
|
+
* @param state - The parsed JSON object
|
|
56
|
+
* @returns Object with valid flag, warnings, and errors
|
|
57
|
+
*/
|
|
58
|
+
export declare function validateCoolifyState(state: Record<string, unknown>): {
|
|
59
|
+
valid: boolean;
|
|
60
|
+
errors: string[];
|
|
61
|
+
warnings: string[];
|
|
62
|
+
};
|
|
31
63
|
/**
|
|
32
64
|
* Formats timestamp to relative time.
|
|
33
65
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,YAAY,CAAC;AAY/B;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,eAOlE;AAmBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAWnD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAY5D"}
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../../src/utils/format.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,YAAY,CAAC;AAY/B;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,EAAE,eAOlE;AAmBD;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAWnD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD;AAID;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG;IAC5C,KAAK,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAqCA;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAqBtE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IACpE,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB,CAmFA;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAY5D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mks2508/coolify-mks-cli-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "MCP server and CLI for Coolify deployment management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
"types": "./dist/index.d.ts",
|
|
9
9
|
"bin": {
|
|
10
10
|
"coolify-mcp": "dist/cli/index.js",
|
|
11
|
-
"coolify-mcp-server": "dist/server/stdio.js"
|
|
11
|
+
"coolify-mcp-server": "dist/server/stdio.js",
|
|
12
|
+
"coolify-cli": "dist/cli/index.js"
|
|
12
13
|
},
|
|
13
14
|
"exports": {
|
|
14
15
|
".": {
|
|
@@ -52,16 +53,20 @@
|
|
|
52
53
|
"typecheck": "tsgo --noEmit"
|
|
53
54
|
},
|
|
54
55
|
"dependencies": {
|
|
56
|
+
"@clack/prompts": "^1.2.0",
|
|
55
57
|
"@mks2508/better-logger": "^4.0.0",
|
|
56
58
|
"@mks2508/no-throw": "^0.1.0",
|
|
57
59
|
"@modelcontextprotocol/sdk": "^1.25.2",
|
|
58
60
|
"chalk": "^5.4.1",
|
|
59
61
|
"cli-table3": "^0.6.5",
|
|
60
62
|
"commander": "^12.1.0",
|
|
63
|
+
"diff": "^8.0.4",
|
|
61
64
|
"ora": "^8.1.1",
|
|
62
|
-
"prompts": "^2.4.2"
|
|
65
|
+
"prompts": "^2.4.2",
|
|
66
|
+
"shiki": "^4.0.2"
|
|
63
67
|
},
|
|
64
68
|
"devDependencies": {
|
|
69
|
+
"@types/bun": "^1.3.14",
|
|
65
70
|
"@types/node": "^22.10.5",
|
|
66
71
|
"@types/prompts": "^2.4.9",
|
|
67
72
|
"rolldown": "^1.0.0-beta.58",
|
|
@@ -72,5 +77,13 @@
|
|
|
72
77
|
},
|
|
73
78
|
"publishConfig": {
|
|
74
79
|
"access": "public"
|
|
75
|
-
}
|
|
80
|
+
},
|
|
81
|
+
"jsonSchema": [
|
|
82
|
+
{
|
|
83
|
+
"name": ".coolify.json",
|
|
84
|
+
"description": "Coolify deployment configuration",
|
|
85
|
+
"fileMatch": [".coolify.json"],
|
|
86
|
+
"url": "./coolify.json.schema.json"
|
|
87
|
+
}
|
|
88
|
+
]
|
|
76
89
|
}
|
package/src/cli/actions.ts
CHANGED
|
@@ -13,6 +13,7 @@ import Table from "cli-table3";
|
|
|
13
13
|
import { Coolify } from "../sdk.js";
|
|
14
14
|
import { formatStatus } from "../utils/format.js";
|
|
15
15
|
import { resolveUuid } from "./coolify-state.js";
|
|
16
|
+
import { resolveNameOrUuid } from "./name-resolver.js";
|
|
16
17
|
|
|
17
18
|
/** Singleton SDK instance for CLI (uses env vars / config file). */
|
|
18
19
|
let _sdk: Coolify | null = null;
|
|
@@ -42,7 +43,10 @@ export async function runAction(
|
|
|
42
43
|
callFn: (sdk: Coolify, uuid: string) => Promise<unknown>,
|
|
43
44
|
successMsg: (uuid: string) => string,
|
|
44
45
|
): Promise<void> {
|
|
45
|
-
|
|
46
|
+
let resolved = resolveUuid(uuid);
|
|
47
|
+
if (!resolved && uuid) {
|
|
48
|
+
resolved = await resolveNameOrUuid(uuid);
|
|
49
|
+
}
|
|
46
50
|
if (!resolved) {
|
|
47
51
|
console.error(
|
|
48
52
|
chalk.red("Error: No UUID provided and no .coolify.json found"),
|
|
@@ -129,7 +133,10 @@ export async function runGet<T>(
|
|
|
129
133
|
callFn: (sdk: Coolify, uuid: string) => Promise<T>,
|
|
130
134
|
formatFn: (item: T) => void,
|
|
131
135
|
): Promise<void> {
|
|
132
|
-
|
|
136
|
+
let resolved = resolveUuid(uuid);
|
|
137
|
+
if (!resolved && uuid) {
|
|
138
|
+
resolved = await resolveNameOrUuid(uuid);
|
|
139
|
+
}
|
|
133
140
|
if (!resolved) {
|
|
134
141
|
console.error(
|
|
135
142
|
chalk.red("Error: No UUID provided and no .coolify.json found"),
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
* @module
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import * as p from "@clack/prompts";
|
|
7
8
|
import { isOk, isErr } from "@mks2508/no-throw";
|
|
8
9
|
import ora from "ora";
|
|
9
10
|
import chalk from "chalk";
|
|
10
11
|
import { getCoolifyService } from "../../coolify/index.js";
|
|
12
|
+
import { validatePorts } from "../../utils/format.js";
|
|
13
|
+
import type { ICoolifyGithubApp } from "../../coolify/types.js";
|
|
11
14
|
|
|
12
15
|
/**
|
|
13
16
|
* Create command options.
|
|
@@ -34,6 +37,177 @@ interface ICreateOptions {
|
|
|
34
37
|
dockerComposeLocation?: string;
|
|
35
38
|
dockerfileLocation?: string;
|
|
36
39
|
baseDirectory?: string;
|
|
40
|
+
githubAppUuid?: string;
|
|
41
|
+
privateKeyUuid?: string;
|
|
42
|
+
domain?: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Identifier format accepted by Coolify.
|
|
47
|
+
*
|
|
48
|
+
* Coolify uses two ID formats:
|
|
49
|
+
* - Standard UUID v4 (8-4-4-4-12 hex with dashes)
|
|
50
|
+
* - Laravel-style 24-char alphanumeric IDs (e.g. `awgcco0k48g4kgw8cckkc808`)
|
|
51
|
+
*
|
|
52
|
+
* Both are accepted; we reject only clearly invalid input to fail fast
|
|
53
|
+
* before hitting the API.
|
|
54
|
+
*/
|
|
55
|
+
const ID_REGEX =
|
|
56
|
+
/^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}|[a-z0-9]{20,30})$/i;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Valid --type values.
|
|
60
|
+
*/
|
|
61
|
+
const VALID_APP_TYPES = [
|
|
62
|
+
"public",
|
|
63
|
+
"private-github-app",
|
|
64
|
+
"private-deploy-key",
|
|
65
|
+
"dockerfile",
|
|
66
|
+
"docker-image",
|
|
67
|
+
"docker-compose",
|
|
68
|
+
] as const;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Valid --build-pack values.
|
|
72
|
+
*/
|
|
73
|
+
const VALID_BUILD_PACKS = ["nixpacks", "static", "dockerfile", "dockercompose"] as const;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* TTY detection — used to guard interactive prompts.
|
|
77
|
+
*/
|
|
78
|
+
const isTTY = process.stdout.isTTY === true;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Validates create command options and exits with code 1 on failure.
|
|
82
|
+
* Called before any API calls.
|
|
83
|
+
*
|
|
84
|
+
* @param options - Create options to validate
|
|
85
|
+
*/
|
|
86
|
+
function validateCreateOptions(options: ICreateOptions): void {
|
|
87
|
+
// --type validation
|
|
88
|
+
if (options.type && !VALID_APP_TYPES.includes(options.type)) {
|
|
89
|
+
console.error(chalk.red(`Invalid --type: '${options.type}'`));
|
|
90
|
+
console.error(chalk.gray(` Valid values: ${VALID_APP_TYPES.join(", ")}`));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --build_pack validation
|
|
95
|
+
if (options.buildPack && !VALID_BUILD_PACKS.includes(options.buildPack)) {
|
|
96
|
+
console.error(chalk.red(`Invalid --build-pack: '${options.buildPack}'`));
|
|
97
|
+
console.error(chalk.gray(` Valid values: ${VALID_BUILD_PACKS.join(", ")}`));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Identifier format validations (UUID v4 OR Coolify 24-char ID)
|
|
102
|
+
const idFields: Array<[string, string]> = [
|
|
103
|
+
["--server", options.server],
|
|
104
|
+
["--project", options.project],
|
|
105
|
+
["--environment", options.environment ?? ""],
|
|
106
|
+
["--github-app-uuid", options.githubAppUuid ?? ""],
|
|
107
|
+
];
|
|
108
|
+
|
|
109
|
+
for (const [flag, value] of idFields) {
|
|
110
|
+
if (value && !ID_REGEX.test(value)) {
|
|
111
|
+
console.error(chalk.red(`Invalid ID format for ${flag}: '${value}'`));
|
|
112
|
+
console.error(
|
|
113
|
+
chalk.gray(
|
|
114
|
+
` Expected: UUID v4 (8-4-4-4-12 hex) or Coolify 24-char ID (e.g. a1b2c3d4-e5f6-7890-abcd-ef1234567890 or awgcco0k48g4kgw8cckkc808)`,
|
|
115
|
+
),
|
|
116
|
+
);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Required fields
|
|
122
|
+
if (!options.name) {
|
|
123
|
+
console.error(chalk.red("--name is required"));
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
if (!options.server) {
|
|
127
|
+
console.error(chalk.red("--server is required"));
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
if (!options.project) {
|
|
131
|
+
console.error(chalk.red("--project is required"));
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Prompts the user to select a GitHub App from a list of private apps.
|
|
138
|
+
* Skips prompt entirely if --github-app-uuid is passed explicitly.
|
|
139
|
+
*
|
|
140
|
+
* @param apps - List of GitHub apps (should already be filtered to private)
|
|
141
|
+
* @param explicitUuid - The --github-app-uuid passed by the user (optional)
|
|
142
|
+
* @returns The selected app's uuid, or null if cancelled
|
|
143
|
+
*/
|
|
144
|
+
async function promptGithubAppSelection(
|
|
145
|
+
apps: ICoolifyGithubApp[],
|
|
146
|
+
explicitUuid?: string,
|
|
147
|
+
): Promise<string | null> {
|
|
148
|
+
// If --github-app-uuid was passed, validate it exists in the list
|
|
149
|
+
if (explicitUuid) {
|
|
150
|
+
const found = apps.find((a) => a.uuid === explicitUuid);
|
|
151
|
+
if (!found) {
|
|
152
|
+
console.error(
|
|
153
|
+
chalk.red(`GitHub App '${explicitUuid}' not found among configured private apps`),
|
|
154
|
+
);
|
|
155
|
+
if (apps.length > 0) {
|
|
156
|
+
console.error(
|
|
157
|
+
chalk.gray(
|
|
158
|
+
` Available: ${apps.map((a) => `${a.name} (${a.uuid})`).join(", ")}`,
|
|
159
|
+
),
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
process.exit(1);
|
|
163
|
+
}
|
|
164
|
+
return explicitUuid;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// 0 private apps → loud error
|
|
168
|
+
if (apps.length === 0) {
|
|
169
|
+
console.error(
|
|
170
|
+
chalk.red("No private GitHub Apps found. Cannot create private-github-app deployment."),
|
|
171
|
+
);
|
|
172
|
+
console.error(
|
|
173
|
+
chalk.gray(" Configure a private GitHub App in Coolify: Settings → Sources → GitHub App"),
|
|
174
|
+
);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 1 private app → silently use it
|
|
179
|
+
if (apps.length === 1) {
|
|
180
|
+
return apps[0].uuid;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// 2+ private apps → interactive picker
|
|
184
|
+
if (!isTTY) {
|
|
185
|
+
console.error(
|
|
186
|
+
chalk.red(
|
|
187
|
+
"Multiple private GitHub Apps found but stdin is not a TTY. " +
|
|
188
|
+
"Pass --github-app-uuid <uuid> to select one explicitly.",
|
|
189
|
+
),
|
|
190
|
+
);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const choices = apps.map((app) => ({
|
|
195
|
+
label: app.name,
|
|
196
|
+
value: app.uuid,
|
|
197
|
+
hint: app.organization ?? undefined,
|
|
198
|
+
}));
|
|
199
|
+
|
|
200
|
+
const selected = await p.select({
|
|
201
|
+
message: "Select a GitHub App for this deployment:",
|
|
202
|
+
options: choices,
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
if (p.isCancel(selected)) {
|
|
206
|
+
console.error(chalk.yellow("Cancelled."));
|
|
207
|
+
process.exit(1);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return selected as string;
|
|
37
211
|
}
|
|
38
212
|
|
|
39
213
|
/**
|
|
@@ -45,45 +219,145 @@ export async function createCommand(options: ICreateOptions) {
|
|
|
45
219
|
const spinner = ora("Initializing Coolify connection...").start();
|
|
46
220
|
|
|
47
221
|
try {
|
|
222
|
+
// Fail-fast validation before any API calls
|
|
223
|
+
validateCreateOptions(options);
|
|
224
|
+
|
|
48
225
|
const coolify = getCoolifyService();
|
|
49
226
|
const initResult = await coolify.init();
|
|
50
227
|
|
|
51
228
|
if (isErr(initResult)) {
|
|
52
|
-
spinner.fail(
|
|
53
|
-
|
|
54
|
-
);
|
|
55
|
-
return;
|
|
229
|
+
spinner.fail(chalk.red(`Failed to initialize: ${initResult.error.message}`));
|
|
230
|
+
process.exit(1);
|
|
56
231
|
}
|
|
57
232
|
|
|
58
|
-
//
|
|
233
|
+
// ── Environment resolution ─────────────────────────────────────────────
|
|
59
234
|
let environmentUuid: string | undefined = options.environment;
|
|
235
|
+
let environmentName: string | undefined;
|
|
60
236
|
|
|
61
237
|
if (!environmentUuid) {
|
|
62
238
|
spinner.text = "Fetching project environments...";
|
|
63
|
-
|
|
64
239
|
const envResult = await coolify.getProjectEnvironments(options.project);
|
|
65
240
|
|
|
66
|
-
if (
|
|
67
|
-
|
|
241
|
+
if (isErr(envResult)) {
|
|
242
|
+
spinner.fail(
|
|
243
|
+
chalk.red(`Failed to fetch environments: ${envResult.error.message}`),
|
|
244
|
+
);
|
|
245
|
+
process.exit(1);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
if (envResult.value.length === 0) {
|
|
249
|
+
spinner.fail(
|
|
250
|
+
chalk.red("No environments found for project. Specify --environment <uuid>"),
|
|
251
|
+
);
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (envResult.value.length === 1) {
|
|
68
256
|
environmentUuid = envResult.value[0].uuid;
|
|
69
|
-
|
|
257
|
+
environmentName = envResult.value[0].name;
|
|
70
258
|
spinner.info(
|
|
71
|
-
chalk.cyan(`Using environment: ${
|
|
259
|
+
chalk.cyan(`Using only environment: ${environmentName} (${environmentUuid})`),
|
|
72
260
|
);
|
|
73
261
|
} else {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
262
|
+
// Interactive environment picker
|
|
263
|
+
if (!isTTY) {
|
|
264
|
+
spinner.fail(
|
|
265
|
+
chalk.red(
|
|
266
|
+
"No --environment specified and stdin is not a TTY. " +
|
|
267
|
+
"Pass --environment <uuid> explicitly.",
|
|
268
|
+
),
|
|
269
|
+
);
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const choices = envResult.value.map((env) => ({
|
|
274
|
+
label: env.name,
|
|
275
|
+
value: env.uuid,
|
|
276
|
+
hint: env.description || undefined,
|
|
277
|
+
}));
|
|
278
|
+
|
|
279
|
+
const selected = await p.select({
|
|
280
|
+
message: "Select an environment:",
|
|
281
|
+
options: choices,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
if (p.isCancel(selected)) {
|
|
285
|
+
spinner.stop("Cancelled.");
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
environmentUuid = selected as string;
|
|
290
|
+
environmentName = envResult.value.find((e) => e.uuid === environmentUuid)?.name;
|
|
291
|
+
spinner.info(chalk.cyan(`Selected environment: ${environmentName} (${environmentUuid})`));
|
|
292
|
+
}
|
|
293
|
+
} else {
|
|
294
|
+
// Environment was provided — validate it exists and get its name
|
|
295
|
+
spinner.text = "Validating environment...";
|
|
296
|
+
const envResult = await coolify.getProjectEnvironments(options.project);
|
|
297
|
+
if (isOk(envResult)) {
|
|
298
|
+
const env = envResult.value.find((e) => e.uuid === environmentUuid);
|
|
299
|
+
if (env) {
|
|
300
|
+
environmentName = env.name;
|
|
301
|
+
} else {
|
|
302
|
+
spinner.fail(
|
|
303
|
+
chalk.red(`Environment '${environmentUuid}' not found in project.`) +
|
|
304
|
+
chalk.gray(
|
|
305
|
+
`\n Available: ${envResult.value.map((e) => `${e.name} (${e.uuid})`).join(", ")}`,
|
|
306
|
+
),
|
|
307
|
+
);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
80
310
|
}
|
|
81
311
|
}
|
|
82
312
|
|
|
83
|
-
// Ensure environmentUuid is defined before creating application
|
|
84
313
|
if (!environmentUuid) {
|
|
85
314
|
spinner.fail(chalk.red("Environment UUID is required"));
|
|
86
|
-
|
|
315
|
+
process.exit(1);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ── GitHub App resolution ───────────────────────────────────────────────
|
|
319
|
+
let appType = options.type || "public";
|
|
320
|
+
let githubAppUuid = options.githubAppUuid;
|
|
321
|
+
|
|
322
|
+
if (!githubAppUuid && (appType === "private-github-app" || !options.type)) {
|
|
323
|
+
spinner.text = "Detecting GitHub Apps...";
|
|
324
|
+
const ghAppsResult = await coolify.listGithubAppsAll();
|
|
325
|
+
|
|
326
|
+
if (isOk(ghAppsResult)) {
|
|
327
|
+
// Filter to private (non-public) GitHub Apps only.
|
|
328
|
+
const privateApps = ghAppsResult.value.filter((app) => !app.is_public);
|
|
329
|
+
|
|
330
|
+
githubAppUuid = await promptGithubAppSelection(privateApps, options.githubAppUuid);
|
|
331
|
+
|
|
332
|
+
if (githubAppUuid === null) {
|
|
333
|
+
// Should not reach here — promptGithubAppSelection exits on cancel
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
appType = "private-github-app";
|
|
338
|
+
const usedApp = privateApps.find((a) => a.uuid === githubAppUuid);
|
|
339
|
+
if (usedApp) {
|
|
340
|
+
spinner.info(
|
|
341
|
+
chalk.cyan(
|
|
342
|
+
`Using GitHub App: ${usedApp.name}` +
|
|
343
|
+
(usedApp.organization ? ` (${usedApp.organization})` : ""),
|
|
344
|
+
),
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
} else if (appType === "private-github-app") {
|
|
348
|
+
spinner.fail(
|
|
349
|
+
chalk.red(`Could not list GitHub Apps: ${ghAppsResult.error.message}`),
|
|
350
|
+
);
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// ── Port validation ─────────────────────────────────────────────────────
|
|
356
|
+
const portsStr = options.ports || "3000";
|
|
357
|
+
const portValidation = validatePorts(portsStr);
|
|
358
|
+
if (!portValidation.valid) {
|
|
359
|
+
spinner.fail(chalk.red(`Invalid ports: ${portValidation.error}`));
|
|
360
|
+
process.exit(1);
|
|
87
361
|
}
|
|
88
362
|
|
|
89
363
|
spinner.text = "Creating application...";
|
|
@@ -94,17 +368,20 @@ export async function createCommand(options: ICreateOptions) {
|
|
|
94
368
|
description: options.description,
|
|
95
369
|
projectUuid: options.project,
|
|
96
370
|
environmentUuid,
|
|
371
|
+
environmentName,
|
|
97
372
|
serverUuid: options.server,
|
|
98
|
-
type:
|
|
373
|
+
type: appType,
|
|
374
|
+
githubAppUuid,
|
|
99
375
|
githubRepoUrl: options.repo,
|
|
100
376
|
branch: options.branch || "main",
|
|
101
377
|
buildPack: options.buildPack || "dockerfile",
|
|
102
|
-
portsExposes:
|
|
378
|
+
portsExposes: portsStr,
|
|
103
379
|
dockerImage: options.dockerImage,
|
|
104
380
|
dockerCompose: options.dockerCompose,
|
|
105
381
|
dockerComposeLocation: options.dockerComposeLocation,
|
|
106
382
|
dockerfileLocation: options.dockerfileLocation,
|
|
107
383
|
baseDirectory: options.baseDirectory,
|
|
384
|
+
privateKeyUuid: options.privateKeyUuid,
|
|
108
385
|
},
|
|
109
386
|
(percent, message) => {
|
|
110
387
|
spinner.text = `${chalk.bold(`[${percent}%]`)} ${message}`;
|
|
@@ -112,22 +389,52 @@ export async function createCommand(options: ICreateOptions) {
|
|
|
112
389
|
);
|
|
113
390
|
|
|
114
391
|
if (isOk(result)) {
|
|
392
|
+
const createdUuid = result.value.uuid;
|
|
115
393
|
spinner.succeed(
|
|
116
394
|
chalk.green(
|
|
117
|
-
`Application created! UUID: ${chalk.cyan(
|
|
395
|
+
`Application created! UUID: ${chalk.cyan(createdUuid)}`,
|
|
118
396
|
),
|
|
119
397
|
);
|
|
398
|
+
|
|
399
|
+
// Set domain via PATCH if --domain was provided
|
|
400
|
+
if (options.domain && createdUuid) {
|
|
401
|
+
const domainSpinner = ora("Setting domain...").start();
|
|
402
|
+
const domainValue = options.domain.startsWith("http")
|
|
403
|
+
? options.domain
|
|
404
|
+
: `https://${options.domain}`;
|
|
405
|
+
|
|
406
|
+
const updateResult = await coolify.updateApplication(createdUuid, {
|
|
407
|
+
domains: domainValue,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
if (isOk(updateResult)) {
|
|
411
|
+
domainSpinner.succeed(chalk.green(`Domain set: ${chalk.cyan(domainValue)}`));
|
|
412
|
+
} else {
|
|
413
|
+
// Non-fatal — domain setting failed but app was created
|
|
414
|
+
domainSpinner.fail(
|
|
415
|
+
chalk.red(`Failed to set domain: ${updateResult.error.message}`),
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
120
420
|
console.log(` Name: ${chalk.cyan(options.name)}`);
|
|
121
|
-
console.log(` Type: ${chalk.cyan(
|
|
421
|
+
console.log(` Type: ${chalk.cyan(appType)}`);
|
|
422
|
+
if (options.domain) {
|
|
423
|
+
const domainValue = options.domain.startsWith("http")
|
|
424
|
+
? options.domain
|
|
425
|
+
: `https://${options.domain}`;
|
|
426
|
+
console.log(` Domain: ${chalk.cyan(domainValue)}`);
|
|
427
|
+
}
|
|
122
428
|
console.log(` Next steps:`);
|
|
123
429
|
console.log(
|
|
124
|
-
` 1. Set environment variables: ${chalk.yellow("coolify-mcp env " +
|
|
430
|
+
` 1. Set environment variables: ${chalk.yellow("coolify-mcp env " + createdUuid)}`,
|
|
125
431
|
);
|
|
126
432
|
console.log(
|
|
127
|
-
` 2. Deploy application: ${chalk.yellow("coolify-mcp deploy " +
|
|
433
|
+
` 2. Deploy application: ${chalk.yellow("coolify-mcp deploy " + createdUuid)}`,
|
|
128
434
|
);
|
|
129
435
|
} else {
|
|
130
436
|
spinner.fail(chalk.red(`Creation failed: ${result.error.message}`));
|
|
437
|
+
process.exit(1);
|
|
131
438
|
}
|
|
132
439
|
} catch (error) {
|
|
133
440
|
spinner.fail(
|
|
@@ -135,5 +442,6 @@ export async function createCommand(options: ICreateOptions) {
|
|
|
135
442
|
`Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
136
443
|
),
|
|
137
444
|
);
|
|
445
|
+
process.exit(1);
|
|
138
446
|
}
|
|
139
447
|
}
|
package/src/cli/commands/db.ts
CHANGED
|
@@ -16,6 +16,7 @@ import type {
|
|
|
16
16
|
ICoolifyDatabase,
|
|
17
17
|
ICoolifyDatabaseBackup,
|
|
18
18
|
} from "../../coolify/types.js";
|
|
19
|
+
import { resolveDbNameOrUuid } from "../name-resolver.js";
|
|
19
20
|
|
|
20
21
|
/** List all databases. */
|
|
21
22
|
export const dbListCommand = () =>
|
|
@@ -110,6 +111,42 @@ export const dbDeleteCommand = (uuid: string) =>
|
|
|
110
111
|
(u) => `Database deleted: ${u}`,
|
|
111
112
|
);
|
|
112
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Update a database configuration.
|
|
116
|
+
*
|
|
117
|
+
* @param uuid - Database UUID
|
|
118
|
+
* @param options - Update options (publicPort, isPublic)
|
|
119
|
+
*/
|
|
120
|
+
export async function dbUpdateCommand(
|
|
121
|
+
uuidOrName: string,
|
|
122
|
+
options: { publicPort?: string; isPublic?: boolean },
|
|
123
|
+
): Promise<void> {
|
|
124
|
+
try {
|
|
125
|
+
const uuid = await resolveDbNameOrUuid(uuidOrName) ?? uuidOrName;
|
|
126
|
+
const data: Record<string, unknown> = {};
|
|
127
|
+
|
|
128
|
+
if (options.publicPort !== undefined) data.public_port = options.publicPort;
|
|
129
|
+
if (options.isPublic !== undefined) data.is_public = options.isPublic;
|
|
130
|
+
|
|
131
|
+
if (Object.keys(data).length === 0) {
|
|
132
|
+
console.warn(
|
|
133
|
+
chalk.yellow("No update options provided. Use --help to see available options."),
|
|
134
|
+
);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
console.log(chalk.cyan(`Updating database ${chalk.bold(uuid)}...`));
|
|
139
|
+
await getCliSdk().databases.update(uuid, data);
|
|
140
|
+
console.log(chalk.green("Database updated successfully"));
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error(
|
|
143
|
+
chalk.red(
|
|
144
|
+
`Error: ${error instanceof Error ? error.message : String(error)}`,
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
113
150
|
/** List backups for a database. */
|
|
114
151
|
export const dbBackupsCommand = (uuid: string) =>
|
|
115
152
|
runList<ICoolifyDatabaseBackup>(
|
|
@@ -10,6 +10,7 @@ import { isErr } from "@mks2508/no-throw";
|
|
|
10
10
|
import chalk from "chalk";
|
|
11
11
|
import { getCoolifyService } from "../../coolify/index.js";
|
|
12
12
|
import { resolveUuid } from "../coolify-state.js";
|
|
13
|
+
import { resolveAppNameOrUuid } from "../name-resolver.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Executes the delete command.
|
|
@@ -22,10 +23,13 @@ export async function deleteCommand(
|
|
|
22
23
|
uuid: string | undefined,
|
|
23
24
|
options: { force?: boolean; yes?: boolean } = {},
|
|
24
25
|
): Promise<void> {
|
|
25
|
-
|
|
26
|
+
let resolvedUuid = resolveUuid(uuid);
|
|
27
|
+
if (!resolvedUuid && uuid) {
|
|
28
|
+
resolvedUuid = await resolveAppNameOrUuid(uuid);
|
|
29
|
+
}
|
|
26
30
|
if (!resolvedUuid) {
|
|
27
31
|
console.error(
|
|
28
|
-
chalk.red("Error: No UUID provided and no .coolify.json found"),
|
|
32
|
+
chalk.red("Error: No UUID/name provided and no .coolify.json found"),
|
|
29
33
|
);
|
|
30
34
|
return;
|
|
31
35
|
}
|