@masonator/coolify-mcp 0.2.18 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Stu Mason
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,92 +1,22 @@
1
- # Coolify MCP Server
2
-
3
- A Model Context Protocol (MCP) server implementation for [Coolify](https://coolify.io/), enabling AI assistants to interact with your Coolify instances through natural language.
4
-
5
- ## Example Prompts
6
-
7
- Here are example prompts you can use with MCP-compatible AI assistants to interact with your Coolify instance:
8
-
9
- ### Server Management
10
-
11
- ```
12
- # List and Inspect Servers
13
- - Show me all Coolify servers in my instance
14
- - What's the status of server {uuid}?
15
- - Show me the resources running on server {uuid}
16
- - What domains are configured for server {uuid}?
17
- - Can you validate the connection to server {uuid}?
18
-
19
- # Resource Monitoring
20
- - How much CPU and memory is server {uuid} using?
21
- - List all resources running on server {uuid}
22
- - Show me the current status of all servers
23
- ```
24
-
25
- ### Project Management
26
-
27
- ```
28
- # Project Operations
29
- - List all my Coolify projects
30
- - Create a new project called "my-webapp" with description "My web application"
31
- - Show me the details of project {uuid}
32
- - Update project {uuid} to change its name to "new-name"
33
- - Delete project {uuid}
34
-
35
- # Environment Management
36
- - Show me the environments in project {uuid}
37
- - Get details of the production environment in project {uuid}
38
- - What variables are set in the staging environment of project {uuid}?
39
- ```
40
-
41
- ### Application and Service Management
1
+ [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/stumason-coolify-mcp-badge.png)](https://mseep.ai/app/stumason-coolify-mcp)
42
2
 
43
- ```
44
- # Application Management
45
- - List all applications
46
- - Show me details of application {uuid}
47
- - Create a new application called "my-nodejs-app"
48
- - Delete application {uuid}
49
-
50
- # Service Operations
51
- - Show me all running services
52
- - Create a new WordPress service:
53
- - Name: my-blog
54
- - Project UUID: {project_uuid}
55
- - Server UUID: {server_uuid}
56
- - Type: wordpress-with-mysql
57
- - What's the status of service {uuid}?
58
- - Delete service {uuid} and clean up its resources
59
- ```
3
+ # Coolify MCP Server
60
4
 
61
- ### Database Management
5
+ A Model Context Protocol (MCP) server for [Coolify](https://coolify.io/), enabling AI assistants to manage and debug your Coolify instances through natural language.
62
6
 
63
- ```
64
- # Database Operations
65
- - List all databases
66
- - Show me the configuration of database {uuid}
67
- - Update database {uuid}:
68
- - Increase memory limit to 1GB
69
- - Change public port to 5432
70
- - Update password
71
- - Delete database {uuid} and clean up volumes
72
-
73
- # Database Types
74
- - Create a PostgreSQL database
75
- - Set up a Redis instance
76
- - Configure a MongoDB database
77
- - Initialize a MySQL database
78
- ```
7
+ ## Features
79
8
 
80
- ### Deployment Management
9
+ This MCP server provides 46 tools focused on **debugging, management, and deployment**:
81
10
 
82
- ```
83
- # Deployment Operations
84
- - Show me all active deployments
85
- - What's the status of deployment {uuid}?
86
- - Deploy application {uuid}
87
- - Force rebuild and deploy application {uuid}
88
- - List recent deployments for application {uuid}
89
- ```
11
+ | Category | Tools |
12
+ | ---------------- | -------------------------------------------------------------------------------------------------------- |
13
+ | **Servers** | list, get, validate, resources, domains |
14
+ | **Projects** | list, get, create, update, delete |
15
+ | **Environments** | list, get, create, delete |
16
+ | **Applications** | list, get, update, delete, start, stop, restart, logs, env vars (CRUD), create (private-gh, private-key) |
17
+ | **Databases** | list, get, start, stop, restart |
18
+ | **Services** | list, get, start, stop, restart, env vars (list, create, delete) |
19
+ | **Deployments** | list, get, deploy, list by application |
90
20
 
91
21
  ## Installation
92
22
 
@@ -94,121 +24,171 @@ Here are example prompts you can use with MCP-compatible AI assistants to intera
94
24
 
95
25
  - Node.js >= 18
96
26
  - A running Coolify instance
97
- - Coolify API access token
27
+ - Coolify API access token (generate in Coolify Settings > API)
98
28
 
99
- ### Setup in AI Tools
29
+ ### Claude Desktop
100
30
 
101
- #### Claude Desktop
31
+ Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
102
32
 
103
33
  ```json
104
- "coolify": {
105
- "command": "npx",
106
- "args": [
107
- "-y", "@masonator/coolify-mcp"
108
- ],
109
- "env": {
110
- "COOLIFY_ACCESS_TOKEN": "0|your-secret-token",
34
+ {
35
+ "mcpServers": {
36
+ "coolify": {
37
+ "command": "npx",
38
+ "args": ["-y", "@masonator/coolify-mcp"],
39
+ "env": {
40
+ "COOLIFY_ACCESS_TOKEN": "your-api-token",
111
41
  "COOLIFY_BASE_URL": "https://your-coolify-instance.com"
42
+ }
112
43
  }
44
+ }
113
45
  }
114
46
  ```
115
47
 
116
- #### Cursor
48
+ ### Claude Code
117
49
 
118
50
  ```bash
119
- env COOLIFY_ACCESS_TOKEN:0|your-secret-token COOLIFY_BASE_URL:https://your-coolify-instance.com npx -y @stumason/coolify-mcp
51
+ claude mcp add coolify \
52
+ --env COOLIFY_BASE_URL="https://your-coolify-instance.com" \
53
+ --env COOLIFY_ACCESS_TOKEN="your-api-token" \
54
+ -- npx -y @masonator/coolify-mcp
120
55
  ```
121
56
 
122
- ## Development
123
-
124
- ### Local Setup
57
+ ### Cursor
125
58
 
126
59
  ```bash
127
- # Clone the repository
128
- git clone https://github.com/stumason/coolify-mcp.git
129
- cd coolify-mcp
60
+ env COOLIFY_ACCESS_TOKEN=your-api-token COOLIFY_BASE_URL=https://your-coolify-instance.com npx -y @masonator/coolify-mcp
61
+ ```
130
62
 
131
- # Install dependencies
132
- npm install
63
+ ## Example Prompts
133
64
 
134
- # Build the project
135
- npm run build
65
+ ### Debugging & Monitoring
136
66
 
137
- # Run tests
138
- npm test
67
+ ```
68
+ Show me all servers and their status
69
+ What resources are running on server {uuid}?
70
+ Get the logs for application {uuid}
71
+ What environment variables are set for application {uuid}?
72
+ Show me recent deployments for application {uuid}
139
73
  ```
140
74
 
141
- ### Environment Variables
142
-
143
- ```bash
144
- # Required
145
- COOLIFY_ACCESS_TOKEN=your_access_token_here
75
+ ### Application Management
146
76
 
147
- # Optional (defaults to http://localhost:3000)
148
- COOLIFY_BASE_URL=https://your.coolify.instance
77
+ ```
78
+ Restart application {uuid}
79
+ Stop the database {uuid}
80
+ Start service {uuid}
81
+ Deploy application {uuid} with force rebuild
82
+ Update the DATABASE_URL env var for application {uuid}
149
83
  ```
150
84
 
151
- ## API Reference
85
+ ### Project Setup
152
86
 
153
- ### Resource Types
87
+ ```
88
+ Create a new project called "my-app"
89
+ Create a staging environment in project {uuid}
90
+ Deploy my app from private GitHub repo org/repo on branch main
91
+ ```
154
92
 
155
- #### Application
93
+ ## Environment Variables
156
94
 
157
- ```typescript
158
- interface Application {
159
- uuid: string;
160
- name: string;
161
- // Additional properties based on your Coolify instance
162
- }
163
- ```
95
+ | Variable | Required | Default | Description |
96
+ | ---------------------- | -------- | ----------------------- | ------------------------- |
97
+ | `COOLIFY_ACCESS_TOKEN` | Yes | - | Your Coolify API token |
98
+ | `COOLIFY_BASE_URL` | No | `http://localhost:3000` | Your Coolify instance URL |
164
99
 
165
- #### Service
166
-
167
- ```typescript
168
- interface Service {
169
- id: number;
170
- uuid: string;
171
- name: string;
172
- type: ServiceType; // Various types like 'wordpress', 'mysql', etc.
173
- status: 'running' | 'stopped' | 'error';
174
- project_uuid: string;
175
- environment_uuid: string;
176
- server_uuid: string;
177
- domains?: string[];
178
- }
179
- ```
100
+ ## Development
180
101
 
181
- #### Database
182
-
183
- ```typescript
184
- interface Database {
185
- id: number;
186
- uuid: string;
187
- name: string;
188
- type: 'postgresql' | 'mysql' | 'mongodb' | 'redis' | /* other types */;
189
- status: 'running' | 'stopped' | 'error';
190
- is_public: boolean;
191
- public_port?: number;
192
- // Additional configuration based on database type
193
- }
194
- ```
102
+ ```bash
103
+ # Clone and install
104
+ git clone https://github.com/stumason/coolify-mcp.git
105
+ cd coolify-mcp
106
+ npm install
195
107
 
196
- #### Deployment
108
+ # Build
109
+ npm run build
197
110
 
198
- ```typescript
199
- interface Deployment {
200
- id: number;
201
- uuid: string;
202
- application_uuid: string;
203
- status: string;
204
- created_at: string;
205
- updated_at: string;
206
- }
207
- ```
111
+ # Test
112
+ npm test
113
+
114
+ # Run locally
115
+ COOLIFY_BASE_URL="https://your-coolify.com" \
116
+ COOLIFY_ACCESS_TOKEN="your-token" \
117
+ node dist/index.js
118
+ ```
119
+
120
+ ## Available Tools
121
+
122
+ ### Servers
123
+
124
+ - `get_version` - Get Coolify API version
125
+ - `list_servers` - List all servers
126
+ - `get_server` - Get server details
127
+ - `get_server_resources` - Get resources running on a server
128
+ - `get_server_domains` - Get domains configured on a server
129
+ - `validate_server` - Validate server connection
130
+
131
+ ### Projects
132
+
133
+ - `list_projects` - List all projects
134
+ - `get_project` - Get project details
135
+ - `create_project` - Create a new project
136
+ - `update_project` - Update a project
137
+ - `delete_project` - Delete a project
138
+
139
+ ### Environments
140
+
141
+ - `list_environments` - List environments in a project
142
+ - `get_environment` - Get environment details
143
+ - `create_environment` - Create environment in a project
144
+ - `delete_environment` - Delete an environment
145
+
146
+ ### Applications
147
+
148
+ - `list_applications` - List all applications
149
+ - `get_application` - Get application details
150
+ - `create_application_private_gh` - Create app from private GitHub repo (GitHub App)
151
+ - `create_application_private_key` - Create app from private repo using deploy key
152
+ - `update_application` - Update an application
153
+ - `delete_application` - Delete an application
154
+ - `start_application` - Start an application
155
+ - `stop_application` - Stop an application
156
+ - `restart_application` - Restart an application
157
+ - `get_application_logs` - Get application logs
158
+ - `list_application_envs` - List application environment variables
159
+ - `create_application_env` - Create application environment variable
160
+ - `update_application_env` - Update application environment variable
161
+ - `delete_application_env` - Delete application environment variable
162
+
163
+ ### Databases
164
+
165
+ - `list_databases` - List all databases
166
+ - `get_database` - Get database details
167
+ - `start_database` - Start a database
168
+ - `stop_database` - Stop a database
169
+ - `restart_database` - Restart a database
170
+
171
+ ### Services
172
+
173
+ - `list_services` - List all services
174
+ - `get_service` - Get service details
175
+ - `start_service` - Start a service
176
+ - `stop_service` - Stop a service
177
+ - `restart_service` - Restart a service
178
+ - `list_service_envs` - List service environment variables
179
+ - `create_service_env` - Create service environment variable
180
+ - `delete_service_env` - Delete service environment variable
181
+
182
+ ### Deployments
183
+
184
+ - `list_deployments` - List running deployments
185
+ - `get_deployment` - Get deployment details
186
+ - `deploy` - Deploy by tag or UUID
187
+ - `list_application_deployments` - List deployments for an application
208
188
 
209
189
  ## Contributing
210
190
 
211
- Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
191
+ Contributions welcome! Please open an issue first to discuss major changes.
212
192
 
213
193
  ## License
214
194
 
@@ -216,8 +196,5 @@ MIT
216
196
 
217
197
  ## Support
218
198
 
219
- For support, please:
220
-
221
- 1. Check the [issues](https://github.com/stumason/coolify-mcp/issues) page
222
- 2. Create a new issue if needed
223
- 3. Join the Coolify community
199
+ - [GitHub Issues](https://github.com/stumason/coolify-mcp/issues)
200
+ - [Coolify Community](https://coolify.io/docs/contact)
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,286 @@
1
+ import { jest, describe, it, expect, beforeEach } from '@jest/globals';
2
+ import { CoolifyClient } from '../lib/coolify-client.js';
3
+ // Helper to create mock response
4
+ function mockResponse(data, ok = true, status = 200) {
5
+ return {
6
+ ok,
7
+ status,
8
+ statusText: ok ? 'OK' : 'Error',
9
+ text: async () => JSON.stringify(data),
10
+ };
11
+ }
12
+ const mockFetch = jest.fn();
13
+ describe('CoolifyClient', () => {
14
+ let client;
15
+ const mockServers = [
16
+ {
17
+ id: 1,
18
+ uuid: 'test-uuid',
19
+ name: 'test-server',
20
+ ip: '192.168.1.1',
21
+ user: 'root',
22
+ port: 22,
23
+ created_at: '2024-01-01',
24
+ updated_at: '2024-01-01',
25
+ },
26
+ ];
27
+ const mockServerInfo = {
28
+ id: 1,
29
+ uuid: 'test-uuid',
30
+ name: 'test-server',
31
+ ip: '192.168.1.1',
32
+ user: 'root',
33
+ port: 22,
34
+ created_at: '2024-01-01',
35
+ updated_at: '2024-01-01',
36
+ };
37
+ const mockServerResources = [
38
+ {
39
+ id: 1,
40
+ uuid: 'resource-uuid',
41
+ name: 'test-app',
42
+ type: 'application',
43
+ status: 'running',
44
+ created_at: '2024-01-01',
45
+ updated_at: '2024-01-01',
46
+ },
47
+ ];
48
+ const mockService = {
49
+ id: 1,
50
+ uuid: 'test-uuid',
51
+ name: 'test-service',
52
+ type: 'code-server',
53
+ status: 'running',
54
+ created_at: '2024-01-01',
55
+ updated_at: '2024-01-01',
56
+ };
57
+ const errorResponse = {
58
+ message: 'Resource not found',
59
+ };
60
+ beforeEach(() => {
61
+ mockFetch.mockClear();
62
+ global.fetch = mockFetch;
63
+ client = new CoolifyClient({
64
+ baseUrl: 'http://localhost:3000',
65
+ accessToken: 'test-api-key',
66
+ });
67
+ });
68
+ describe('constructor', () => {
69
+ it('should throw error if baseUrl is missing', () => {
70
+ expect(() => new CoolifyClient({ baseUrl: '', accessToken: 'test' })).toThrow('Coolify base URL is required');
71
+ });
72
+ it('should throw error if accessToken is missing', () => {
73
+ expect(() => new CoolifyClient({ baseUrl: 'http://localhost', accessToken: '' })).toThrow('Coolify access token is required');
74
+ });
75
+ it('should strip trailing slash from baseUrl', () => {
76
+ const c = new CoolifyClient({
77
+ baseUrl: 'http://localhost:3000/',
78
+ accessToken: 'test',
79
+ });
80
+ mockFetch.mockResolvedValueOnce(mockResponse({ version: '1.0.0' }));
81
+ c.getVersion();
82
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/version', expect.any(Object));
83
+ });
84
+ });
85
+ describe('listServers', () => {
86
+ it('should return a list of servers', async () => {
87
+ mockFetch.mockResolvedValueOnce(mockResponse(mockServers));
88
+ const servers = await client.listServers();
89
+ expect(servers).toEqual(mockServers);
90
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/servers', expect.objectContaining({
91
+ headers: expect.objectContaining({
92
+ 'Content-Type': 'application/json',
93
+ Authorization: 'Bearer test-api-key',
94
+ }),
95
+ }));
96
+ });
97
+ it('should handle errors', async () => {
98
+ mockFetch.mockResolvedValueOnce(mockResponse(errorResponse, false, 404));
99
+ await expect(client.listServers()).rejects.toThrow('Resource not found');
100
+ });
101
+ });
102
+ describe('getServer', () => {
103
+ it('should get server info', async () => {
104
+ mockFetch.mockResolvedValueOnce(mockResponse(mockServerInfo));
105
+ const result = await client.getServer('test-uuid');
106
+ expect(result).toEqual(mockServerInfo);
107
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/servers/test-uuid', expect.any(Object));
108
+ });
109
+ it('should handle errors', async () => {
110
+ mockFetch.mockResolvedValueOnce(mockResponse(errorResponse, false, 404));
111
+ await expect(client.getServer('test-uuid')).rejects.toThrow('Resource not found');
112
+ });
113
+ });
114
+ describe('getServerResources', () => {
115
+ it('should get server resources', async () => {
116
+ mockFetch.mockResolvedValueOnce(mockResponse(mockServerResources));
117
+ const result = await client.getServerResources('test-uuid');
118
+ expect(result).toEqual(mockServerResources);
119
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/servers/test-uuid/resources', expect.any(Object));
120
+ });
121
+ it('should handle errors', async () => {
122
+ mockFetch.mockResolvedValueOnce(mockResponse(errorResponse, false, 404));
123
+ await expect(client.getServerResources('test-uuid')).rejects.toThrow('Resource not found');
124
+ });
125
+ });
126
+ describe('listServices', () => {
127
+ it('should list services', async () => {
128
+ mockFetch.mockResolvedValueOnce(mockResponse([mockService]));
129
+ const result = await client.listServices();
130
+ expect(result).toEqual([mockService]);
131
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services', expect.any(Object));
132
+ });
133
+ });
134
+ describe('getService', () => {
135
+ it('should get service info', async () => {
136
+ mockFetch.mockResolvedValueOnce(mockResponse(mockService));
137
+ const result = await client.getService('test-uuid');
138
+ expect(result).toEqual(mockService);
139
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/test-uuid', expect.any(Object));
140
+ });
141
+ });
142
+ describe('createService', () => {
143
+ it('should create a service', async () => {
144
+ const responseData = {
145
+ uuid: 'test-uuid',
146
+ domains: ['test.com'],
147
+ };
148
+ mockFetch.mockResolvedValueOnce(mockResponse(responseData));
149
+ const createData = {
150
+ name: 'test-service',
151
+ type: 'code-server',
152
+ project_uuid: 'project-uuid',
153
+ environment_uuid: 'env-uuid',
154
+ server_uuid: 'server-uuid',
155
+ };
156
+ const result = await client.createService(createData);
157
+ expect(result).toEqual(responseData);
158
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services', expect.objectContaining({
159
+ method: 'POST',
160
+ body: JSON.stringify(createData),
161
+ }));
162
+ });
163
+ });
164
+ describe('deleteService', () => {
165
+ it('should delete a service', async () => {
166
+ mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Service deleted' }));
167
+ const result = await client.deleteService('test-uuid');
168
+ expect(result).toEqual({ message: 'Service deleted' });
169
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/test-uuid', expect.objectContaining({
170
+ method: 'DELETE',
171
+ }));
172
+ });
173
+ it('should delete a service with options', async () => {
174
+ mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Service deleted' }));
175
+ await client.deleteService('test-uuid', {
176
+ deleteVolumes: true,
177
+ dockerCleanup: true,
178
+ });
179
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/services/test-uuid?delete_volumes=true&docker_cleanup=true', expect.objectContaining({
180
+ method: 'DELETE',
181
+ }));
182
+ });
183
+ });
184
+ describe('applications', () => {
185
+ it('should list applications', async () => {
186
+ const mockApps = [{ id: 1, uuid: 'app-uuid', name: 'test-app' }];
187
+ mockFetch.mockResolvedValueOnce(mockResponse(mockApps));
188
+ const result = await client.listApplications();
189
+ expect(result).toEqual(mockApps);
190
+ });
191
+ it('should start an application', async () => {
192
+ mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Started', deployment_uuid: 'dep-uuid' }));
193
+ const result = await client.startApplication('app-uuid', {
194
+ force: true,
195
+ });
196
+ expect(result).toEqual({
197
+ message: 'Started',
198
+ deployment_uuid: 'dep-uuid',
199
+ });
200
+ });
201
+ it('should stop an application', async () => {
202
+ mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Stopped' }));
203
+ const result = await client.stopApplication('app-uuid');
204
+ expect(result).toEqual({ message: 'Stopped' });
205
+ });
206
+ });
207
+ describe('databases', () => {
208
+ it('should list databases', async () => {
209
+ const mockDbs = [{ id: 1, uuid: 'db-uuid', name: 'test-db' }];
210
+ mockFetch.mockResolvedValueOnce(mockResponse(mockDbs));
211
+ const result = await client.listDatabases();
212
+ expect(result).toEqual(mockDbs);
213
+ });
214
+ it('should start a database', async () => {
215
+ mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Started' }));
216
+ const result = await client.startDatabase('db-uuid');
217
+ expect(result).toEqual({ message: 'Started' });
218
+ });
219
+ });
220
+ describe('teams', () => {
221
+ it('should list teams', async () => {
222
+ const mockTeams = [{ id: 1, name: 'test-team', personal_team: false }];
223
+ mockFetch.mockResolvedValueOnce(mockResponse(mockTeams));
224
+ const result = await client.listTeams();
225
+ expect(result).toEqual(mockTeams);
226
+ });
227
+ it('should get current team', async () => {
228
+ const mockTeam = { id: 1, name: 'my-team', personal_team: true };
229
+ mockFetch.mockResolvedValueOnce(mockResponse(mockTeam));
230
+ const result = await client.getCurrentTeam();
231
+ expect(result).toEqual(mockTeam);
232
+ });
233
+ });
234
+ describe('deployments', () => {
235
+ it('should list deployments', async () => {
236
+ const mockDeps = [
237
+ {
238
+ id: 1,
239
+ uuid: 'dep-uuid',
240
+ deployment_uuid: 'dep-123',
241
+ status: 'finished',
242
+ },
243
+ ];
244
+ mockFetch.mockResolvedValueOnce(mockResponse(mockDeps));
245
+ const result = await client.listDeployments();
246
+ expect(result).toEqual(mockDeps);
247
+ });
248
+ it('should deploy by tag', async () => {
249
+ mockFetch.mockResolvedValueOnce(mockResponse({ message: 'Deployed' }));
250
+ const result = await client.deployByTagOrUuid('my-tag', true);
251
+ expect(result).toEqual({ message: 'Deployed' });
252
+ expect(mockFetch).toHaveBeenCalledWith('http://localhost:3000/api/v1/deploy?tag=my-tag&force=true', expect.any(Object));
253
+ });
254
+ });
255
+ describe('private keys', () => {
256
+ it('should list private keys', async () => {
257
+ const mockKeys = [{ id: 1, uuid: 'key-uuid', name: 'my-key' }];
258
+ mockFetch.mockResolvedValueOnce(mockResponse(mockKeys));
259
+ const result = await client.listPrivateKeys();
260
+ expect(result).toEqual(mockKeys);
261
+ });
262
+ it('should create a private key', async () => {
263
+ mockFetch.mockResolvedValueOnce(mockResponse({ uuid: 'new-key-uuid' }));
264
+ const result = await client.createPrivateKey({
265
+ name: 'new-key',
266
+ private_key: 'ssh-rsa AAAA...',
267
+ });
268
+ expect(result).toEqual({ uuid: 'new-key-uuid' });
269
+ });
270
+ });
271
+ describe('error handling', () => {
272
+ it('should handle network errors', async () => {
273
+ mockFetch.mockRejectedValueOnce(new TypeError('fetch failed'));
274
+ await expect(client.listServers()).rejects.toThrow('Failed to connect to Coolify server');
275
+ });
276
+ it('should handle empty responses', async () => {
277
+ mockFetch.mockResolvedValueOnce({
278
+ ok: true,
279
+ status: 204,
280
+ text: async () => '',
281
+ });
282
+ const result = await client.deleteServer('test-uuid');
283
+ expect(result).toEqual({});
284
+ });
285
+ });
286
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};