@mseep/mcp-swarmpit 0.1.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.
Files changed (98) hide show
  1. package/CLAUDE.md +128 -0
  2. package/README.md +416 -0
  3. package/dist/client.d.ts +107 -0
  4. package/dist/client.js +297 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/config.d.ts +8 -0
  7. package/dist/config.js +41 -0
  8. package/dist/config.js.map +1 -0
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.js +41 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/sanitize.d.ts +41 -0
  13. package/dist/sanitize.js +165 -0
  14. package/dist/sanitize.js.map +1 -0
  15. package/dist/test/config.test.d.ts +1 -0
  16. package/dist/test/config.test.js +103 -0
  17. package/dist/test/config.test.js.map +1 -0
  18. package/dist/test/file-ref.test.d.ts +1 -0
  19. package/dist/test/file-ref.test.js +163 -0
  20. package/dist/test/file-ref.test.js.map +1 -0
  21. package/dist/test/helpers.test.d.ts +1 -0
  22. package/dist/test/helpers.test.js +133 -0
  23. package/dist/test/helpers.test.js.map +1 -0
  24. package/dist/test/sanitize.test.d.ts +1 -0
  25. package/dist/test/sanitize.test.js +207 -0
  26. package/dist/test/sanitize.test.js.map +1 -0
  27. package/dist/tools/admin.d.ts +3 -0
  28. package/dist/tools/admin.js +64 -0
  29. package/dist/tools/admin.js.map +1 -0
  30. package/dist/tools/configs.d.ts +4 -0
  31. package/dist/tools/configs.js +70 -0
  32. package/dist/tools/configs.js.map +1 -0
  33. package/dist/tools/dashboard.d.ts +3 -0
  34. package/dist/tools/dashboard.js +41 -0
  35. package/dist/tools/dashboard.js.map +1 -0
  36. package/dist/tools/helpers.d.ts +16 -0
  37. package/dist/tools/helpers.js +74 -0
  38. package/dist/tools/helpers.js.map +1 -0
  39. package/dist/tools/networks.d.ts +3 -0
  40. package/dist/tools/networks.js +70 -0
  41. package/dist/tools/networks.js.map +1 -0
  42. package/dist/tools/nodes.d.ts +3 -0
  43. package/dist/tools/nodes.js +59 -0
  44. package/dist/tools/nodes.js.map +1 -0
  45. package/dist/tools/register.d.ts +3 -0
  46. package/dist/tools/register.js +30 -0
  47. package/dist/tools/register.js.map +1 -0
  48. package/dist/tools/secrets.d.ts +4 -0
  49. package/dist/tools/secrets.js +70 -0
  50. package/dist/tools/secrets.js.map +1 -0
  51. package/dist/tools/services.d.ts +4 -0
  52. package/dist/tools/services.js +198 -0
  53. package/dist/tools/services.js.map +1 -0
  54. package/dist/tools/stacks.d.ts +4 -0
  55. package/dist/tools/stacks.js +196 -0
  56. package/dist/tools/stacks.js.map +1 -0
  57. package/dist/tools/tasks.d.ts +3 -0
  58. package/dist/tools/tasks.js +23 -0
  59. package/dist/tools/tasks.js.map +1 -0
  60. package/dist/tools/timeseries.d.ts +3 -0
  61. package/dist/tools/timeseries.js +41 -0
  62. package/dist/tools/timeseries.js.map +1 -0
  63. package/dist/tools/util.d.ts +3 -0
  64. package/dist/tools/util.js +10 -0
  65. package/dist/tools/util.js.map +1 -0
  66. package/dist/tools/volumes.d.ts +3 -0
  67. package/dist/tools/volumes.js +59 -0
  68. package/dist/tools/volumes.js.map +1 -0
  69. package/dist/types.d.ts +119 -0
  70. package/dist/types.js +2 -0
  71. package/dist/types.js.map +1 -0
  72. package/package.json +43 -0
  73. package/src/client.ts +391 -0
  74. package/src/config.ts +57 -0
  75. package/src/index.ts +49 -0
  76. package/src/sanitize.ts +218 -0
  77. package/src/test/config.test.ts +118 -0
  78. package/src/test/file-ref.test.ts +191 -0
  79. package/src/test/helpers.test.ts +147 -0
  80. package/src/test/sanitize.test.ts +234 -0
  81. package/src/tools/admin.ts +93 -0
  82. package/src/tools/configs.ts +101 -0
  83. package/src/tools/dashboard.ts +65 -0
  84. package/src/tools/helpers.ts +91 -0
  85. package/src/tools/networks.ts +99 -0
  86. package/src/tools/nodes.ts +88 -0
  87. package/src/tools/register.ts +36 -0
  88. package/src/tools/secrets.ts +101 -0
  89. package/src/tools/services.ts +283 -0
  90. package/src/tools/stacks.ts +282 -0
  91. package/src/tools/tasks.ts +37 -0
  92. package/src/tools/timeseries.ts +65 -0
  93. package/src/tools/util.ts +20 -0
  94. package/src/tools/volumes.ts +88 -0
  95. package/src/types.ts +131 -0
  96. package/swagger.json +1 -0
  97. package/swarmpit-config.example.json +9 -0
  98. package/tsconfig.json +15 -0
@@ -0,0 +1,282 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import type { RedactMode } from "../config.js";
4
+ import { SwarmpitClient } from "../client.js";
5
+ import { sanitizeServices, sanitizeComposeYaml, resolveComposeEnvRefs, restoreRedactedValues } from "../sanitize.js";
6
+ import { toolResult, toolError, resolveData } from "./helpers.js";
7
+
8
+ export function registerStackTools(
9
+ server: McpServer,
10
+ client: SwarmpitClient,
11
+ redact: RedactMode
12
+ ): void {
13
+ server.tool(
14
+ "list_stacks",
15
+ "List all Docker Swarm stacks",
16
+ {},
17
+ async () => {
18
+ try {
19
+ const stacks = await client.listStacks();
20
+ return toolResult(stacks);
21
+ } catch (e) {
22
+ return toolError(e);
23
+ }
24
+ }
25
+ );
26
+
27
+ server.tool(
28
+ "get_stack",
29
+ "Get stack details including services and compose file",
30
+ { name: z.string().describe("Stack name") },
31
+ async ({ name }) => {
32
+ try {
33
+ const [services, file] = await Promise.all([
34
+ client.getStackServices(name),
35
+ client.getStackFile(name).catch(() => ({ compose: "" })),
36
+ ]);
37
+ if (redact === "none") {
38
+ return toolResult({ name, services, compose: file.compose });
39
+ }
40
+ const redactAll = redact === "all";
41
+ return toolResult({
42
+ name,
43
+ services: sanitizeServices(services, redactAll),
44
+ compose: sanitizeComposeYaml(file.compose, redactAll),
45
+ });
46
+ } catch (e) {
47
+ return toolError(e);
48
+ }
49
+ }
50
+ );
51
+
52
+ const composeInput = z.union([
53
+ z.string(),
54
+ z.object({ $file: z.string() }).describe('Read compose from local file path'),
55
+ ]);
56
+
57
+ server.tool(
58
+ "create_stack",
59
+ "Create a new Docker Swarm stack from compose YAML. Use $env:VAR_NAME for secret values, or { $file: /path } to read from a local file.",
60
+ {
61
+ name: z.string().describe("Stack name"),
62
+ compose: composeInput.describe("Compose YAML string or { $file: /path }"),
63
+ },
64
+ async ({ name, compose }) => {
65
+ try {
66
+ const yaml = resolveData(compose);
67
+ const resolved = resolveComposeEnvRefs(yaml);
68
+ await client.createStack({ name, spec: { compose: resolved } });
69
+ return toolResult({ created: true, name });
70
+ } catch (e) {
71
+ return toolError(e);
72
+ }
73
+ }
74
+ );
75
+
76
+ server.tool(
77
+ "update_stack",
78
+ "Update a Docker Swarm stack. [REDACTED] values are preserved. Use $env:VAR_NAME for new secrets, or { $file: /path } to read from a local file.",
79
+ {
80
+ name: z.string().describe("Stack name"),
81
+ compose: composeInput.describe("Compose YAML string or { $file: /path }. Leave [REDACTED] for unchanged secrets."),
82
+ },
83
+ async ({ name, compose }) => {
84
+ try {
85
+ const yaml = resolveData(compose);
86
+ const current = await client.getStackFile(name).catch(() => ({ compose: "" }));
87
+ const restored = restoreRedactedValues(yaml, current.compose);
88
+ const resolved = resolveComposeEnvRefs(restored);
89
+ await client.updateStack(name, resolved);
90
+ return toolResult({ updated: true, name });
91
+ } catch (e) {
92
+ return toolError(e);
93
+ }
94
+ }
95
+ );
96
+
97
+ server.tool(
98
+ "redeploy_stack",
99
+ "Redeploy all services in a Docker Swarm stack",
100
+ { name: z.string().describe("Stack name") },
101
+ async ({ name }) => {
102
+ try {
103
+ await client.redeployStack(name);
104
+ return toolResult({ redeployed: true, name });
105
+ } catch (e) {
106
+ return toolError(e);
107
+ }
108
+ }
109
+ );
110
+
111
+ server.tool(
112
+ "rollback_stack",
113
+ "Rollback all services in a Docker Swarm stack to their previous version",
114
+ { name: z.string().describe("Stack name") },
115
+ async ({ name }) => {
116
+ try {
117
+ await client.rollbackStack(name);
118
+ return toolResult({ rolledBack: true, name });
119
+ } catch (e) {
120
+ return toolError(e);
121
+ }
122
+ }
123
+ );
124
+
125
+ server.tool(
126
+ "deactivate_stack",
127
+ "Deactivate (stop) all services in a Docker Swarm stack",
128
+ { name: z.string().describe("Stack name") },
129
+ async ({ name }) => {
130
+ try {
131
+ await client.deactivateStack(name);
132
+ return toolResult({ deactivated: true, name });
133
+ } catch (e) {
134
+ return toolError(e);
135
+ }
136
+ }
137
+ );
138
+
139
+ server.tool(
140
+ "delete_stack",
141
+ "Delete a Docker Swarm stack. DESTRUCTIVE: requires confirm=true",
142
+ {
143
+ name: z.string().describe("Stack name"),
144
+ confirm: z.boolean().describe("Must be true to confirm deletion"),
145
+ },
146
+ async ({ name, confirm }) => {
147
+ if (!confirm) {
148
+ return toolError("Destructive operation: set confirm=true to delete this stack");
149
+ }
150
+ try {
151
+ await client.deleteStack(name);
152
+ return toolResult({ deleted: true, name });
153
+ } catch (e) {
154
+ return toolError(e);
155
+ }
156
+ }
157
+ );
158
+
159
+ server.tool(
160
+ "get_stack_tasks",
161
+ "List all tasks (containers) in a stack",
162
+ { name: z.string().describe("Stack name") },
163
+ async ({ name }) => {
164
+ try {
165
+ const tasks = await client.getStackTasks(name);
166
+ return toolResult(tasks);
167
+ } catch (e) {
168
+ return toolError(e);
169
+ }
170
+ }
171
+ );
172
+
173
+ server.tool(
174
+ "get_stack_volumes",
175
+ "List all volumes in a stack",
176
+ { name: z.string().describe("Stack name") },
177
+ async ({ name }) => {
178
+ try {
179
+ const volumes = await client.getStackVolumes(name);
180
+ return toolResult(volumes);
181
+ } catch (e) {
182
+ return toolError(e);
183
+ }
184
+ }
185
+ );
186
+
187
+ server.tool(
188
+ "get_stack_networks",
189
+ "List all networks in a stack",
190
+ { name: z.string().describe("Stack name") },
191
+ async ({ name }) => {
192
+ try {
193
+ const networks = await client.getStackNetworks(name);
194
+ return toolResult(networks);
195
+ } catch (e) {
196
+ return toolError(e);
197
+ }
198
+ }
199
+ );
200
+
201
+ server.tool(
202
+ "get_stack_compose",
203
+ "Get the generated compose YAML for a stack",
204
+ { name: z.string().describe("Stack name") },
205
+ async ({ name }) => {
206
+ try {
207
+ const compose = await client.getStackCompose(name);
208
+ if (redact === "none") return toolResult(compose);
209
+ return toolResult({ compose: sanitizeComposeYaml(compose.compose, redact === "all") });
210
+ } catch (e) {
211
+ return toolError(e);
212
+ }
213
+ }
214
+ );
215
+
216
+ server.tool(
217
+ "get_stack_secrets",
218
+ "List all secrets in a stack",
219
+ { name: z.string().describe("Stack name") },
220
+ async ({ name }) => {
221
+ try {
222
+ const secrets = await client.getStackSecrets(name);
223
+ return toolResult(secrets);
224
+ } catch (e) {
225
+ return toolError(e);
226
+ }
227
+ }
228
+ );
229
+
230
+ server.tool(
231
+ "get_stack_configs",
232
+ "List all configs in a stack",
233
+ { name: z.string().describe("Stack name") },
234
+ async ({ name }) => {
235
+ try {
236
+ const configs = await client.getStackConfigs(name);
237
+ return toolResult(configs);
238
+ } catch (e) {
239
+ return toolError(e);
240
+ }
241
+ }
242
+ );
243
+
244
+ server.tool(
245
+ "create_stack_file",
246
+ "Upload/create a compose file for a stack. Accepts a string or { $file: /path }.",
247
+ {
248
+ name: z.string().describe("Stack name"),
249
+ compose: composeInput.describe("Compose YAML string or { $file: /path }"),
250
+ },
251
+ async ({ name, compose }) => {
252
+ try {
253
+ const yaml = resolveData(compose);
254
+ const resolved = resolveComposeEnvRefs(yaml);
255
+ await client.createStackFile(name, resolved);
256
+ return toolResult({ created: true, name });
257
+ } catch (e) {
258
+ return toolError(e);
259
+ }
260
+ }
261
+ );
262
+
263
+ server.tool(
264
+ "delete_stack_file",
265
+ "Delete the compose file for a stack. DESTRUCTIVE: requires confirm=true",
266
+ {
267
+ name: z.string().describe("Stack name"),
268
+ confirm: z.boolean().describe("Must be true to confirm deletion"),
269
+ },
270
+ async ({ name, confirm }) => {
271
+ if (!confirm) {
272
+ return toolError("Destructive operation: set confirm=true to delete this stack file");
273
+ }
274
+ try {
275
+ await client.deleteStackFile(name);
276
+ return toolResult({ deleted: true, name });
277
+ } catch (e) {
278
+ return toolError(e);
279
+ }
280
+ }
281
+ );
282
+ }
@@ -0,0 +1,37 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { SwarmpitClient } from "../client.js";
4
+ import { toolResult, toolError } from "./helpers.js";
5
+
6
+ export function registerTaskTools(
7
+ server: McpServer,
8
+ client: SwarmpitClient
9
+ ): void {
10
+ server.tool(
11
+ "list_tasks",
12
+ "List all Docker Swarm tasks across all services",
13
+ {},
14
+ async () => {
15
+ try {
16
+ const tasks = await client.listTasks();
17
+ return toolResult(tasks);
18
+ } catch (e) {
19
+ return toolError(e);
20
+ }
21
+ }
22
+ );
23
+
24
+ server.tool(
25
+ "get_task",
26
+ "Get details of a specific Docker Swarm task",
27
+ { id: z.string().describe("Task ID") },
28
+ async ({ id }) => {
29
+ try {
30
+ const task = await client.getTask(id);
31
+ return toolResult(task);
32
+ } catch (e) {
33
+ return toolError(e);
34
+ }
35
+ }
36
+ );
37
+ }
@@ -0,0 +1,65 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { SwarmpitClient } from "../client.js";
4
+ import { toolResult, toolError } from "./helpers.js";
5
+
6
+ export function registerTimeseriesTools(
7
+ server: McpServer,
8
+ client: SwarmpitClient
9
+ ): void {
10
+ server.tool(
11
+ "get_nodes_timeseries",
12
+ "Get timeseries data for all nodes (CPU, memory, disk over time)",
13
+ {},
14
+ async () => {
15
+ try {
16
+ const ts = await client.getNodesTimeseries();
17
+ return toolResult(ts);
18
+ } catch (e) {
19
+ return toolError(e);
20
+ }
21
+ }
22
+ );
23
+
24
+ server.tool(
25
+ "get_services_cpu_timeseries",
26
+ "Get CPU usage timeseries for all services",
27
+ {},
28
+ async () => {
29
+ try {
30
+ const ts = await client.getServicesCpuTimeseries();
31
+ return toolResult(ts);
32
+ } catch (e) {
33
+ return toolError(e);
34
+ }
35
+ }
36
+ );
37
+
38
+ server.tool(
39
+ "get_services_memory_timeseries",
40
+ "Get memory usage timeseries for all services",
41
+ {},
42
+ async () => {
43
+ try {
44
+ const ts = await client.getServicesMemoryTimeseries();
45
+ return toolResult(ts);
46
+ } catch (e) {
47
+ return toolError(e);
48
+ }
49
+ }
50
+ );
51
+
52
+ server.tool(
53
+ "get_task_timeseries",
54
+ "Get timeseries data for a specific task",
55
+ { name: z.string().describe("Task name") },
56
+ async ({ name }) => {
57
+ try {
58
+ const ts = await client.getTaskTimeseries(name);
59
+ return toolResult(ts);
60
+ } catch (e) {
61
+ return toolError(e);
62
+ }
63
+ }
64
+ );
65
+ }
@@ -0,0 +1,20 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import type { SwarmpitConfig } from "../config.js";
3
+ import { toolResult } from "./helpers.js";
4
+
5
+ export function registerUtilTools(
6
+ server: McpServer,
7
+ config: SwarmpitConfig
8
+ ): void {
9
+ server.tool(
10
+ "swarmpit_info",
11
+ "Show the connected Swarmpit instance URL and redaction mode",
12
+ {},
13
+ async () => {
14
+ return toolResult({
15
+ url: config.url,
16
+ redact: config.redact,
17
+ });
18
+ }
19
+ );
20
+ }
@@ -0,0 +1,88 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { z } from "zod";
3
+ import { SwarmpitClient } from "../client.js";
4
+ import { toolResult, toolError } from "./helpers.js";
5
+
6
+ export function registerVolumeTools(
7
+ server: McpServer,
8
+ client: SwarmpitClient
9
+ ): void {
10
+ server.tool(
11
+ "list_volumes",
12
+ "List all Docker Swarm volumes",
13
+ {},
14
+ async () => {
15
+ try {
16
+ const volumes = await client.listVolumes();
17
+ return toolResult(volumes);
18
+ } catch (e) {
19
+ return toolError(e);
20
+ }
21
+ }
22
+ );
23
+
24
+ server.tool(
25
+ "get_volume",
26
+ "Get details of a specific Docker Swarm volume",
27
+ { name: z.string().describe("Volume name") },
28
+ async ({ name }) => {
29
+ try {
30
+ const volume = await client.getVolume(name);
31
+ return toolResult(volume);
32
+ } catch (e) {
33
+ return toolError(e);
34
+ }
35
+ }
36
+ );
37
+
38
+ server.tool(
39
+ "create_volume",
40
+ "Create a Docker Swarm volume",
41
+ {
42
+ volumeName: z.string().describe("Volume name"),
43
+ driver: z.string().default("local").describe("Volume driver (default: local)"),
44
+ },
45
+ async ({ volumeName, driver }) => {
46
+ try {
47
+ await client.createVolume({ volumeName, driver, options: [] });
48
+ return toolResult({ created: true, volumeName });
49
+ } catch (e) {
50
+ return toolError(e);
51
+ }
52
+ }
53
+ );
54
+
55
+ server.tool(
56
+ "delete_volume",
57
+ "Delete a Docker Swarm volume. DESTRUCTIVE: requires confirm=true",
58
+ {
59
+ name: z.string().describe("Volume name"),
60
+ confirm: z.boolean().describe("Must be true to confirm deletion"),
61
+ },
62
+ async ({ name, confirm }) => {
63
+ if (!confirm) {
64
+ return toolError("Destructive operation: set confirm=true to delete this volume");
65
+ }
66
+ try {
67
+ await client.deleteVolume(name);
68
+ return toolResult({ deleted: true, name });
69
+ } catch (e) {
70
+ return toolError(e);
71
+ }
72
+ }
73
+ );
74
+
75
+ server.tool(
76
+ "get_volume_services",
77
+ "List services using a specific volume",
78
+ { name: z.string().describe("Volume name") },
79
+ async ({ name }) => {
80
+ try {
81
+ const services = await client.getVolumeServices(name);
82
+ return toolResult(services);
83
+ } catch (e) {
84
+ return toolError(e);
85
+ }
86
+ }
87
+ );
88
+ }
package/src/types.ts ADDED
@@ -0,0 +1,131 @@
1
+ export interface SwarmpitPort {
2
+ containerPort: number;
3
+ hostPort: number;
4
+ protocol: string;
5
+ mode: string;
6
+ }
7
+
8
+ export interface SwarmpitMount {
9
+ containerPath: string;
10
+ host: string;
11
+ type: string;
12
+ readOnly: boolean;
13
+ }
14
+
15
+ export interface SwarmpitEnvVar {
16
+ name: string;
17
+ value: string;
18
+ }
19
+
20
+ export interface SwarmpitLabel {
21
+ name: string;
22
+ value: string;
23
+ }
24
+
25
+ export interface SwarmpitSecretRef {
26
+ id: string;
27
+ secretName: string;
28
+ secretTarget: string;
29
+ }
30
+
31
+ export interface SwarmpitConfigRef {
32
+ id: string;
33
+ configName: string;
34
+ configTarget: string;
35
+ }
36
+
37
+ export interface SwarmpitRepository {
38
+ name: string;
39
+ tag: string;
40
+ image: string;
41
+ imageDigest: string;
42
+ }
43
+
44
+ export interface SwarmpitResources {
45
+ cpu: number;
46
+ memory: number;
47
+ }
48
+
49
+ export interface SwarmpitServiceStatus {
50
+ tasks: { running: number; total: number };
51
+ update: string;
52
+ message: string;
53
+ }
54
+
55
+ export interface SwarmpitService {
56
+ id: string;
57
+ version: number;
58
+ createdAt: string;
59
+ updatedAt: string;
60
+ repository: SwarmpitRepository;
61
+ serviceName: string;
62
+ mode: string;
63
+ replicas: number;
64
+ state: string;
65
+ status: SwarmpitServiceStatus;
66
+ ports: SwarmpitPort[];
67
+ mounts: SwarmpitMount[];
68
+ networks: SwarmpitNetwork[];
69
+ secrets: SwarmpitSecretRef[];
70
+ configs: SwarmpitConfigRef[];
71
+ variables: SwarmpitEnvVar[];
72
+ labels: SwarmpitLabel[];
73
+ command: string[] | null;
74
+ stack: string;
75
+ resources: {
76
+ reservation: SwarmpitResources;
77
+ limit: SwarmpitResources;
78
+ };
79
+ }
80
+
81
+ export interface SwarmpitStack {
82
+ stackName: string;
83
+ stackFile: string;
84
+ services: SwarmpitService[];
85
+ }
86
+
87
+ export interface SwarmpitNetwork {
88
+ id: string;
89
+ networkName: string;
90
+ driver: string;
91
+ scope: string;
92
+ internal: boolean;
93
+ stack: string;
94
+ }
95
+
96
+ export interface SwarmpitNode {
97
+ id: string;
98
+ nodeName: string;
99
+ role: string;
100
+ state: string;
101
+ availability: string;
102
+ address: string;
103
+ engine: string;
104
+ }
105
+
106
+ export interface SwarmpitTask {
107
+ id: string;
108
+ taskName: string;
109
+ serviceName: string;
110
+ state: string;
111
+ status: string;
112
+ desiredState: string;
113
+ createdAt: string;
114
+ repository: SwarmpitRepository;
115
+ nodeName: string;
116
+ nodeId: string;
117
+ }
118
+
119
+ export interface SwarmpitVolume {
120
+ volumeName: string;
121
+ driver: string;
122
+ scope: string;
123
+ stack: string;
124
+ mountpoint: string;
125
+ }
126
+
127
+ export interface SwarmpitLogEntry {
128
+ line: string;
129
+ timestamp?: string;
130
+ taskName?: string;
131
+ }