@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.
Files changed (75) hide show
  1. package/dist/cli/coolify-state.d.ts +101 -5
  2. package/dist/cli/coolify-state.d.ts.map +1 -1
  3. package/dist/cli/index.js +23165 -11543
  4. package/dist/cli/ui/highlighter.d.ts +28 -0
  5. package/dist/cli/ui/highlighter.d.ts.map +1 -0
  6. package/dist/cli/ui/index.d.ts +9 -0
  7. package/dist/cli/ui/index.d.ts.map +1 -0
  8. package/dist/cli/ui/spinners.d.ts +100 -0
  9. package/dist/cli/ui/spinners.d.ts.map +1 -0
  10. package/dist/cli/ui/tables.d.ts +103 -0
  11. package/dist/cli/ui/tables.d.ts.map +1 -0
  12. package/dist/coolify/config.d.ts +25 -0
  13. package/dist/coolify/config.d.ts.map +1 -1
  14. package/dist/coolify/index.d.ts +139 -12
  15. package/dist/coolify/index.d.ts.map +1 -1
  16. package/dist/coolify/types.d.ts +160 -2
  17. package/dist/coolify/types.d.ts.map +1 -1
  18. package/dist/examples/demo-ui.d.ts +8 -0
  19. package/dist/examples/demo-ui.d.ts.map +1 -0
  20. package/dist/index.cjs +2580 -230
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.js +2598 -226
  23. package/dist/index.js.map +1 -1
  24. package/dist/sdk.d.ts +96 -7
  25. package/dist/sdk.d.ts.map +1 -1
  26. package/dist/server/stdio.js +475 -73
  27. package/dist/tools/definitions.d.ts.map +1 -1
  28. package/dist/tools/handlers.d.ts.map +1 -1
  29. package/dist/utils/env-parser.d.ts +24 -0
  30. package/dist/utils/env-parser.d.ts.map +1 -0
  31. package/dist/utils/format.d.ts +32 -0
  32. package/dist/utils/format.d.ts.map +1 -1
  33. package/package.json +17 -4
  34. package/src/cli/actions.ts +9 -2
  35. package/src/cli/commands/create.ts +332 -24
  36. package/src/cli/commands/db.ts +37 -0
  37. package/src/cli/commands/delete.ts +6 -2
  38. package/src/cli/commands/deploy.ts +347 -49
  39. package/src/cli/commands/deployments.ts +6 -2
  40. package/src/cli/commands/diagnose.ts +3 -3
  41. package/src/cli/commands/env.ts +424 -31
  42. package/src/cli/commands/exec.ts +6 -2
  43. package/src/cli/commands/init.ts +991 -0
  44. package/src/cli/commands/logs.ts +224 -24
  45. package/src/cli/commands/main-menu.ts +21 -0
  46. package/src/cli/commands/projects.ts +312 -29
  47. package/src/cli/commands/restart.ts +6 -2
  48. package/src/cli/commands/service-logs.ts +14 -0
  49. package/src/cli/commands/show.ts +45 -12
  50. package/src/cli/commands/start.ts +6 -2
  51. package/src/cli/commands/status.ts +554 -0
  52. package/src/cli/commands/stop.ts +6 -2
  53. package/src/cli/commands/svc.ts +7 -1
  54. package/src/cli/commands/update.ts +79 -2
  55. package/src/cli/commands/volumes.ts +293 -0
  56. package/src/cli/coolify-state.ts +203 -12
  57. package/src/cli/index.ts +138 -11
  58. package/src/cli/name-resolver.ts +228 -0
  59. package/src/cli/ui/banner.ts +276 -0
  60. package/src/cli/ui/highlighter.ts +176 -0
  61. package/src/cli/ui/index.ts +9 -0
  62. package/src/cli/ui/prompts.ts +155 -0
  63. package/src/cli/ui/screen.ts +630 -0
  64. package/src/cli/ui/select.ts +280 -0
  65. package/src/cli/ui/spinners.ts +256 -0
  66. package/src/cli/ui/tables.ts +407 -0
  67. package/src/coolify/config.ts +75 -0
  68. package/src/coolify/index.ts +565 -101
  69. package/src/coolify/types.ts +165 -2
  70. package/src/examples/demo-ui.ts +78 -0
  71. package/src/sdk.ts +211 -1
  72. package/src/tools/definitions.ts +22 -0
  73. package/src/tools/handlers.ts +19 -0
  74. package/src/utils/env-parser.ts +45 -0
  75. package/src/utils/format.ts +178 -0
@@ -7,16 +7,59 @@
7
7
  import { isErr } from "@mks2508/no-throw";
8
8
  import chalk from "chalk";
9
9
  import { getCoolifyService } from "../../coolify/index.js";
10
+ import { getCliSdk } from "../actions.js";
10
11
  import { resolveUuid } from "../coolify-state.js";
12
+ import { resolveAppNameOrUuid } from "../name-resolver.js";
13
+ import { parseEnvContent } from "../../utils/env-parser.js";
14
+ import {
15
+ createEnvTable,
16
+ createChangeSummary,
17
+ createSpinner,
18
+ highlightEnvLine,
19
+ createDiff,
20
+ } from "../ui/index.js";
11
21
 
22
+ /** Options accepted by the `env` command. */
12
23
  interface IEnvCommandOptions {
13
- set?: string;
24
+ /** One or more `KEY=VALUE` pairs to set (commander accumulator → array). */
25
+ set?: string[];
26
+ /** Single key to delete. */
14
27
  delete?: string;
28
+ /** Single key to read (prints value only, no decoration). */
29
+ get?: string;
30
+ /** Mark variables as build-time only. */
15
31
  buildtime?: boolean;
32
+ /** Mark variables as runtime only. */
33
+ "runtime-only"?: boolean;
34
+ /** Sync from a file (string path) or stdin (`true` + non-TTY) or default `.env`. */
35
+ sync?: boolean | string;
36
+ /** Preview sync changes without applying. */
37
+ "dry-run"?: boolean;
38
+ /** Delete vars not present in the sync source. */
39
+ prune?: boolean;
40
+ /** Force table view instead of highlighted list. */
41
+ table?: boolean;
42
+ /** Emit machine-parseable JSON instead of human-friendly output. */
43
+ json?: boolean;
44
+ }
45
+
46
+ /** Result shape for `--json` output of single-key ops (`--get`, `--set`, `--delete`). */
47
+ interface IJsonEnvAction {
48
+ /** Action performed. */
49
+ action: "get" | "set" | "delete";
50
+ /** Variable key. */
51
+ key: string;
52
+ /** Variable value (undefined for delete). */
53
+ value?: string;
54
+ /** Whether the value was build-time only. */
55
+ is_buildtime?: boolean;
56
+ /** Whether the value was runtime-only. */
57
+ is_runtime?: boolean;
16
58
  }
17
59
 
18
60
  /**
19
61
  * Env vars command handler.
62
+ *
20
63
  * If no UUID is provided, reads from .coolify.json in the current directory.
21
64
  *
22
65
  * @param uuid - Application UUID (optional if .coolify.json exists)
@@ -26,10 +69,13 @@ export async function envCommand(
26
69
  uuid: string | undefined,
27
70
  options: IEnvCommandOptions = {},
28
71
  ) {
29
- const resolvedUuid = resolveUuid(uuid);
72
+ let resolvedUuid = resolveUuid(uuid);
73
+ if (!resolvedUuid && uuid) {
74
+ resolvedUuid = await resolveAppNameOrUuid(uuid);
75
+ }
30
76
  if (!resolvedUuid) {
31
77
  console.error(
32
- chalk.red("Error: No UUID provided and no .coolify.json found"),
78
+ chalk.red("Error: No UUID/name provided and no .coolify.json found"),
33
79
  );
34
80
  return;
35
81
  }
@@ -42,21 +88,96 @@ export async function envCommand(
42
88
  return;
43
89
  }
44
90
 
45
- // Handle --set KEY=VALUE
46
- if (options.set) {
47
- const [key, ...valueParts] = options.set.split("=");
48
- const value = valueParts.join("="); // Handle values with = in them
91
+ // Handle --set KEY=VALUE (one or many)
92
+ if (options.set && options.set.length > 0) {
93
+ const isBuildTime = options["runtime-only"] ? false : (options.buildtime ?? false);
94
+
95
+ // Parse all pairs first so a malformed one fails the whole batch fast.
96
+ const parsed: Array<{ key: string; value: string }> = [];
97
+ for (const raw of options.set) {
98
+ const eq = raw.indexOf("=");
99
+ // Allow empty value via leading '='? No — require at least key.
100
+ if (eq === -1) {
101
+ console.error(
102
+ chalk.red(`Error: Invalid --set format: "${raw}". Use KEY=VALUE`),
103
+ );
104
+ return;
105
+ }
106
+ const key = raw.slice(0, eq);
107
+ const value = raw.slice(eq + 1);
108
+ if (!key) {
109
+ console.error(
110
+ chalk.red(`Error: Invalid --set format: "${raw}". Empty key.`),
111
+ );
112
+ return;
113
+ }
114
+ parsed.push({ key, value });
115
+ }
116
+
117
+ // Multi-set path: go straight to bulk so we make one round trip and
118
+ // also bypass the singular-PATCH bug for any pre-existing vars.
119
+ if (parsed.length > 1) {
120
+ const bulkResult = await coolify.bulkUpdateEnvironmentVariables(uuid, [
121
+ ...parsed.map(({ key, value }) => ({
122
+ key,
123
+ value,
124
+ is_buildtime: isBuildTime,
125
+ is_runtime: !isBuildTime,
126
+ })),
127
+ // If --delete was also provided, fold it into the bulk by setting
128
+ // the key to empty string — Coolify treats empty value as cleared.
129
+ ...(options.delete
130
+ ? [
131
+ {
132
+ key: options.delete,
133
+ value: "",
134
+ is_buildtime: false,
135
+ is_runtime: true,
136
+ },
137
+ ]
138
+ : []),
139
+ ]);
140
+
141
+ if (isErr(bulkResult)) {
142
+ console.error(chalk.red(`Error: ${bulkResult.error.message}`));
143
+ return;
144
+ }
49
145
 
50
- if (!key || value === undefined) {
51
- console.error(chalk.red("Error: Invalid format. Use --set KEY=VALUE"));
146
+ if (options.json) {
147
+ const action: IJsonEnvAction[] = parsed.map(({ key, value }) => ({
148
+ action: "set",
149
+ key,
150
+ value,
151
+ is_buildtime: isBuildTime,
152
+ is_runtime: !isBuildTime,
153
+ }));
154
+ if (options.delete) {
155
+ action.push({ action: "delete", key: options.delete });
156
+ }
157
+ console.log(JSON.stringify(action));
158
+ } else {
159
+ for (const { key } of parsed) {
160
+ console.log(chalk.green(`✓ Set ${chalk.bold(key)} for ${uuid}`));
161
+ }
162
+ if (options.delete) {
163
+ console.log(
164
+ chalk.green(
165
+ `✓ Cleared ${chalk.bold(options.delete)} for ${uuid}`,
166
+ ),
167
+ );
168
+ }
169
+ }
52
170
  return;
53
171
  }
54
172
 
173
+ // Single --set path: delegate to setEnvironmentVariable (which itself
174
+ // delegates to bulk internally — fix for Bug #1).
175
+ const [{ key, value }] = parsed;
55
176
  const result = await coolify.setEnvironmentVariable(
56
177
  uuid,
57
178
  key,
58
179
  value,
59
- options.buildtime ?? false,
180
+ isBuildTime,
60
181
  );
61
182
 
62
183
  if (isErr(result)) {
@@ -64,7 +185,41 @@ export async function envCommand(
64
185
  return;
65
186
  }
66
187
 
67
- console.log(chalk.green(`✓ Set ${chalk.bold(key)} for ${uuid}`));
188
+ if (options.json) {
189
+ const out: IJsonEnvAction = {
190
+ action: "set",
191
+ key,
192
+ value,
193
+ is_buildtime: isBuildTime,
194
+ is_runtime: !isBuildTime,
195
+ };
196
+ console.log(JSON.stringify(out));
197
+ } else {
198
+ console.log(chalk.green(`✓ Set ${chalk.bold(key)} for ${uuid}`));
199
+ }
200
+ return;
201
+ }
202
+
203
+ // Handle --get KEY — prints the value only (or full JSON with --json).
204
+ if (options.get) {
205
+ const result = await coolify.getEnvironmentVariables(uuid);
206
+ if (isErr(result)) {
207
+ console.error(chalk.red(`Error: ${result.error.message}`));
208
+ return;
209
+ }
210
+ const found = result.value.find((ev) => ev.key === options.get);
211
+ if (!found) {
212
+ console.error(
213
+ chalk.red(`Error: Variable ${options.get} not found`),
214
+ );
215
+ return;
216
+ }
217
+ if (options.json) {
218
+ console.log(JSON.stringify(found));
219
+ } else {
220
+ // Plain value to stdout so it composes well in shell pipelines.
221
+ process.stdout.write(found.value + "\n");
222
+ }
68
223
  return;
69
224
  }
70
225
 
@@ -80,9 +235,90 @@ export async function envCommand(
80
235
  return;
81
236
  }
82
237
 
83
- console.log(
84
- chalk.green(`✓ Deleted ${chalk.bold(options.delete)} from ${uuid}`),
85
- );
238
+ if (options.json) {
239
+ const out: IJsonEnvAction = {
240
+ action: "delete",
241
+ key: options.delete,
242
+ };
243
+ console.log(JSON.stringify(out));
244
+ } else {
245
+ console.log(
246
+ chalk.green(
247
+ `✓ Deleted ${chalk.bold(options.delete)} from ${uuid}`,
248
+ ),
249
+ );
250
+ }
251
+ return;
252
+ }
253
+
254
+ // Handle --sync [file|-]
255
+ if (options.sync !== undefined) {
256
+ let envFile: string;
257
+ if (options.sync === true) {
258
+ // Bare `--sync` (no arg): read from stdin if piped, else from `.env`.
259
+ if (!process.stdin.isTTY) {
260
+ envFile = "-";
261
+ } else {
262
+ envFile = ".env";
263
+ }
264
+ } else {
265
+ // options.sync is now narrowed to string | false. The `false` case
266
+ // shouldn't happen in practice (commander passes true/string/undefined)
267
+ // but the type allows it. Treat any non-string as "use .env".
268
+ envFile = typeof options.sync === "string" ? options.sync : ".env";
269
+ }
270
+
271
+ console.log("");
272
+ console.log(chalk.cyan.bold(" 🔄 Env Sync"));
273
+
274
+ try {
275
+ const sdk = getCliSdk();
276
+
277
+ // Build a custom sync source when reading stdin.
278
+ let stdinContent: string | undefined;
279
+ if (envFile === "-") {
280
+ const { readFileSync } = await import("node:fs");
281
+ stdinContent = readFileSync(0, "utf-8");
282
+ }
283
+
284
+ const result = await (envFile === "-"
285
+ ? syncFromContent(sdk, uuid, stdinContent ?? "", {
286
+ dryRun: options["dry-run"] ?? false,
287
+ prune: options.prune ?? false,
288
+ onProgress: makeSyncProgressHandler(uuid),
289
+ })
290
+ : sdk.applications.syncEnv(uuid, {
291
+ filePath: envFile,
292
+ dryRun: options["dry-run"] ?? false,
293
+ prune: options.prune ?? false,
294
+ onProgress: makeSyncProgressHandler(uuid),
295
+ }));
296
+
297
+ if (options.json) {
298
+ console.log(JSON.stringify(result));
299
+ return;
300
+ }
301
+
302
+ const totalChanges =
303
+ result.added.length + result.updated.length + result.removed.length;
304
+
305
+ if (totalChanges === 0) {
306
+ console.log("");
307
+ console.log(chalk.green(" ✓ All variables are already in sync"));
308
+ console.log("");
309
+ } else {
310
+ console.log(createChangeSummary(result));
311
+
312
+ if (options["dry-run"]) {
313
+ console.log(chalk.yellow(" ⚠ Dry run mode - no changes applied"));
314
+ } else {
315
+ console.log(chalk.green(` ✓ Synced ${totalChanges} variable(s)`));
316
+ }
317
+ console.log("");
318
+ }
319
+ } catch (error) {
320
+ console.error(chalk.red(` ✗ Error: ${error}`));
321
+ }
86
322
  return;
87
323
  }
88
324
 
@@ -97,31 +333,188 @@ export async function envCommand(
97
333
  const envVars = result.value;
98
334
 
99
335
  if (envVars.length === 0) {
100
- console.log(chalk.yellow("No environment variables found"));
336
+ if (options.json) {
337
+ console.log("[]");
338
+ } else {
339
+ console.log(chalk.yellow("No environment variables found"));
340
+ }
101
341
  return;
102
342
  }
103
343
 
104
- console.log(chalk.cyan(`Environment variables (${envVars.length}):\n`));
344
+ // --json short-circuits all human formatting. Use this for piping into jq etc.
345
+ if (options.json) {
346
+ console.log(JSON.stringify(envVars));
347
+ return;
348
+ }
349
+
350
+ // Use table view if --table flag, otherwise use highlighted list view
351
+ if (options.table) {
352
+ console.log(
353
+ createEnvTable(envVars, {
354
+ compact: true,
355
+ showType: true,
356
+ }),
357
+ );
358
+ } else {
359
+ console.log(chalk.cyan(`Environment variables (${envVars.length}):\n`));
360
+
361
+ // Separar runtime de buildtime
362
+ const runtimeVars = envVars.filter((ev) => ev.is_runtime);
363
+ const buildtimeVars = envVars.filter((ev) => ev.is_buildtime);
105
364
 
106
- // Separar runtime de buildtime
107
- const runtimeVars = envVars.filter((ev) => ev.is_runtime);
108
- const buildtimeVars = envVars.filter((ev) => ev.is_buildtime);
365
+ if (runtimeVars.length > 0) {
366
+ console.log(chalk.yellow.bold("Runtime:"));
367
+ for (const ev of runtimeVars) {
368
+ const required = ev.is_required ? chalk.red(" *") : "";
369
+ const line = `${ev.key}=${ev.value}`;
370
+ console.log(` ${highlightEnvLine(line)}${required}`);
371
+ }
372
+ console.log();
373
+ }
109
374
 
110
- if (runtimeVars.length > 0) {
111
- console.log(chalk.yellow.bold("Runtime:"));
112
- for (const ev of runtimeVars) {
113
- const required = ev.is_required ? chalk.red(" *") : "";
114
- console.log(
115
- ` ${chalk.green(ev.key)}${required} = ${chalk.gray(ev.value)}`,
116
- );
375
+ if (buildtimeVars.length > 0) {
376
+ console.log(chalk.blue.bold("Buildtime:"));
377
+ for (const ev of buildtimeVars) {
378
+ const line = `${ev.key}=${ev.value}`;
379
+ console.log(` ${highlightEnvLine(line)}`);
380
+ }
381
+ }
382
+ }
383
+ }
384
+
385
+ /**
386
+ * Sync env vars from raw .env-formatted text instead of a file path.
387
+ *
388
+ * Mirrors ApplicationsResource.syncEnv but reads from a string, so we can
389
+ * support `--sync -` reading from stdin.
390
+ *
391
+ * @param sdk - Coolify SDK instance
392
+ * @param uuid - Application UUID
393
+ * @param content - Raw .env file contents
394
+ * @param options - Sync options (dryRun, prune, onProgress)
395
+ * @returns Sync result with added/updated/removed changes
396
+ */
397
+ async function syncFromContent(
398
+ sdk: ReturnType<typeof getCliSdk>,
399
+ uuid: string,
400
+ content: string,
401
+ options: {
402
+ dryRun: boolean;
403
+ prune: boolean;
404
+ onProgress?: (update: {
405
+ type: "add" | "update" | "remove";
406
+ key: string;
407
+ value?: string;
408
+ }) => void;
409
+ },
410
+ ) {
411
+ const localVars = parseEnvContent(content);
412
+ if (localVars.size === 0) {
413
+ return { added: [], updated: [], removed: [], skipped: 0 };
414
+ }
415
+ const currentVarsList = await sdk.applications.envVars(uuid);
416
+ const currentVars = new Map(currentVarsList.map((v) => [v.key, v.value]));
417
+
418
+ const toAdd: Array<{ key: string; value: string }> = [];
419
+ const toUpdate: Array<{ key: string; value: string; oldValue: string }> = [];
420
+ const toRemove: string[] = [];
421
+
422
+ for (const [key, value] of localVars.entries()) {
423
+ const current = currentVars.get(key);
424
+ if (!current) toAdd.push({ key, value });
425
+ else if (current !== value) toUpdate.push({ key, value, oldValue: current });
426
+ }
427
+ if (options.prune) {
428
+ for (const key of currentVars.keys()) {
429
+ if (!localVars.has(key)) toRemove.push(key);
117
430
  }
118
- console.log();
119
431
  }
120
432
 
121
- if (buildtimeVars.length > 0) {
122
- console.log(chalk.blue.bold("Buildtime:"));
123
- for (const ev of buildtimeVars) {
124
- console.log(` ${chalk.green(ev.key)} = ${chalk.gray(ev.value)}`);
433
+ if (!options.dryRun) {
434
+ // Use bulk directly to avoid N+1 PATCH calls (and the per-var bulk
435
+ // delegation already fixes Bug #1 + #2 for the post-delete case).
436
+ if (toAdd.length > 0 || toUpdate.length > 0) {
437
+ // bulkSetEnv throws on API error via the SDK's Result.unwrap(), so
438
+ // no extra error handling needed here — any failure propagates to
439
+ // the outer try/catch.
440
+ await sdk.applications.bulkSetEnv(
441
+ uuid,
442
+ [...toAdd, ...toUpdate].map(({ key, value }) => ({
443
+ key,
444
+ value,
445
+ is_runtime: true,
446
+ is_buildtime: false,
447
+ })),
448
+ );
449
+ for (const { key, value } of toAdd) {
450
+ options.onProgress?.({ type: "add", key, value });
451
+ }
452
+ for (const { key, value } of toUpdate) {
453
+ options.onProgress?.({ type: "update", key, value });
454
+ }
455
+ }
456
+ for (const key of toRemove) {
457
+ await sdk.applications.deleteEnv(uuid, key);
458
+ options.onProgress?.({ type: "remove", key });
459
+ }
460
+ } else {
461
+ for (const { key, value } of toAdd) {
462
+ options.onProgress?.({ type: "add", key, value });
463
+ }
464
+ for (const { key, value } of toUpdate) {
465
+ options.onProgress?.({ type: "update", key, value });
466
+ }
467
+ for (const key of toRemove) {
468
+ options.onProgress?.({ type: "remove", key });
125
469
  }
126
470
  }
471
+
472
+ return {
473
+ added: toAdd,
474
+ updated: toUpdate,
475
+ removed: toRemove,
476
+ skipped: currentVars.size - toUpdate.length - toRemove.length,
477
+ };
478
+ }
479
+
480
+ /**
481
+ * Builds a progress handler compatible with syncEnv/syncFromContent. The
482
+ * spinner lifecycle is owned by the caller; we only print colored per-key
483
+ * lines (no JSON mode here — JSON mode is handled at the print layer).
484
+ *
485
+ * @param uuid - Application UUID (used for spinner text)
486
+ * @returns Progress callback
487
+ */
488
+ function makeSyncProgressHandler(uuid: string) {
489
+ let spinner: ReturnType<typeof createSpinner> | undefined;
490
+ return (update: {
491
+ type: "add" | "update" | "remove";
492
+ key: string;
493
+ value?: string;
494
+ }) => {
495
+ if (!spinner) {
496
+ spinner = createSpinner({ text: `Syncing ${uuid}...`, color: "cyan" });
497
+ spinner.start();
498
+ }
499
+ if (update.type === "add") {
500
+ spinner.succeed(
501
+ ` ${chalk.green("+")} ${highlightEnvLine(`${update.key}=${update.value ?? ""}`)}`,
502
+ );
503
+ spinner.text = "Syncing...";
504
+ spinner.start();
505
+ } else if (update.type === "update") {
506
+ spinner.succeed(
507
+ ` ${chalk.yellow("~")} ${chalk.bold(update.key)} updated`,
508
+ );
509
+ spinner.text = "Syncing...";
510
+ spinner.start();
511
+ } else {
512
+ spinner.succeed(` ${chalk.red("-")} ${chalk.bold(update.key)} removed`);
513
+ spinner.text = "Syncing...";
514
+ spinner.start();
515
+ }
516
+ // Reference `createDiff` so unused-import lint stays quiet without
517
+ // removing the import (kept for parity with original spinner diff UX).
518
+ void createDiff;
519
+ };
127
520
  }
@@ -6,16 +6,20 @@
6
6
 
7
7
  import { chalk, getCliSdk } from "../actions.js";
8
8
  import { resolveUuid } from "../coolify-state.js";
9
+ import { resolveAppNameOrUuid } from "../name-resolver.js";
9
10
 
10
11
  /** Execute a command on an application's running container. */
11
12
  export async function execCommand(
12
13
  uuid: string | undefined,
13
14
  command: string,
14
15
  ): Promise<void> {
15
- const resolved = resolveUuid(uuid);
16
+ let resolved = resolveUuid(uuid);
17
+ if (!resolved && uuid) {
18
+ resolved = await resolveAppNameOrUuid(uuid);
19
+ }
16
20
  if (!resolved) {
17
21
  console.error(
18
- chalk.red("Error: No UUID provided and no .coolify.json found"),
22
+ chalk.red("Error: No UUID/name provided and no .coolify.json found"),
19
23
  );
20
24
  return;
21
25
  }