@mks2508/coolify-mks-cli-mcp 0.6.3 → 0.8.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 (60) hide show
  1. package/dist/cli/coolify-state.d.ts +92 -4
  2. package/dist/cli/coolify-state.d.ts.map +1 -1
  3. package/dist/cli/index.js +22149 -11456
  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/index.d.ts +22 -3
  13. package/dist/coolify/index.d.ts.map +1 -1
  14. package/dist/coolify/types.d.ts +99 -1
  15. package/dist/coolify/types.d.ts.map +1 -1
  16. package/dist/examples/demo-ui.d.ts +8 -0
  17. package/dist/examples/demo-ui.d.ts.map +1 -0
  18. package/dist/index.cjs +322 -12
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.js +322 -12
  21. package/dist/index.js.map +1 -1
  22. package/dist/sdk.d.ts +41 -0
  23. package/dist/sdk.d.ts.map +1 -1
  24. package/dist/server/stdio.js +258 -9
  25. package/package.json +16 -4
  26. package/src/cli/actions.ts +9 -2
  27. package/src/cli/commands/create.ts +71 -5
  28. package/src/cli/commands/db.ts +37 -0
  29. package/src/cli/commands/delete.ts +6 -2
  30. package/src/cli/commands/deploy.ts +347 -49
  31. package/src/cli/commands/deployments.ts +6 -2
  32. package/src/cli/commands/diagnose.ts +3 -3
  33. package/src/cli/commands/env.ts +121 -22
  34. package/src/cli/commands/exec.ts +6 -2
  35. package/src/cli/commands/init.ts +937 -0
  36. package/src/cli/commands/logs.ts +224 -24
  37. package/src/cli/commands/main-menu.ts +21 -0
  38. package/src/cli/commands/projects.ts +312 -29
  39. package/src/cli/commands/restart.ts +6 -2
  40. package/src/cli/commands/service-logs.ts +14 -0
  41. package/src/cli/commands/show.ts +6 -2
  42. package/src/cli/commands/start.ts +6 -2
  43. package/src/cli/commands/status.ts +538 -0
  44. package/src/cli/commands/stop.ts +6 -2
  45. package/src/cli/commands/update.ts +27 -2
  46. package/src/cli/coolify-state.ts +164 -11
  47. package/src/cli/index.ts +91 -10
  48. package/src/cli/name-resolver.ts +228 -0
  49. package/src/cli/ui/banner.ts +276 -0
  50. package/src/cli/ui/highlighter.ts +176 -0
  51. package/src/cli/ui/index.ts +9 -0
  52. package/src/cli/ui/prompts.ts +155 -0
  53. package/src/cli/ui/screen.ts +606 -0
  54. package/src/cli/ui/select.ts +280 -0
  55. package/src/cli/ui/spinners.ts +256 -0
  56. package/src/cli/ui/tables.ts +407 -0
  57. package/src/coolify/index.ts +257 -12
  58. package/src/coolify/types.ts +103 -1
  59. package/src/examples/demo-ui.ts +78 -0
  60. package/src/sdk.ts +162 -0
package/dist/index.cjs CHANGED
@@ -5784,8 +5784,8 @@ var CoolifyService = class {
5784
5784
  environment_uuid: options.environmentUuid,
5785
5785
  server_uuid: options.serverUuid
5786
5786
  };
5787
+ if (options.githubRepoUrl) body.git_repository = options.githubRepoUrl.replace(/^https?:\/\/github\.com\//, "").replace(/\.git$/, "");
5787
5788
  if (appType === "public" || appType === "private-github-app" || appType === "private-deploy-key") {
5788
- if (options.githubRepoUrl) body.git_repository = options.githubRepoUrl.replace(/^https?:\/\/github\.com\//, "").replace(/\.git$/, "");
5789
5789
  if (options.githubAppUuid) body.github_app_uuid = options.githubAppUuid;
5790
5790
  body.git_branch = options.branch || "main";
5791
5791
  body.build_pack = options.buildPack || "dockerfile";
@@ -5827,7 +5827,8 @@ var CoolifyService = class {
5827
5827
  body: JSON.stringify({
5828
5828
  key,
5829
5829
  value,
5830
- is_preview: false
5830
+ is_preview: false,
5831
+ is_buildtime: false
5831
5832
  })
5832
5833
  });
5833
5834
  if (result.error && result.error.includes("already exists")) {
@@ -5840,7 +5841,8 @@ var CoolifyService = class {
5840
5841
  body: JSON.stringify({
5841
5842
  key,
5842
5843
  value,
5843
- is_preview: false
5844
+ is_preview: false,
5845
+ is_buildtime: false
5844
5846
  })
5845
5847
  });
5846
5848
  if (repost.error) {
@@ -5886,8 +5888,8 @@ var CoolifyService = class {
5886
5888
  * @param isBuildTime - Whether the variable is available at build time (only for new vars)
5887
5889
  * @returns Result indicating success or error
5888
5890
  */
5889
- async setEnvironmentVariable(appUuid, key, value, _isBuildTime = false) {
5890
- log.info(`Setting environment variable ${key} for ${appUuid}`);
5891
+ async setEnvironmentVariable(appUuid, key, value, isBuildTime = false) {
5892
+ log.info(`Setting environment variable ${key} for ${appUuid} (buildtime: ${isBuildTime})`);
5891
5893
  const existingVars = await this.getEnvironmentVariables(appUuid);
5892
5894
  if (isErr(existingVars)) return err(existingVars.error);
5893
5895
  const exists = existingVars.value.some((ev) => ev.key === key);
@@ -5897,7 +5899,8 @@ var CoolifyService = class {
5897
5899
  method: "PATCH",
5898
5900
  body: JSON.stringify({
5899
5901
  key,
5900
- value
5902
+ value,
5903
+ is_buildtime: isBuildTime
5901
5904
  })
5902
5905
  });
5903
5906
  if (result.error) {
@@ -5910,7 +5913,8 @@ var CoolifyService = class {
5910
5913
  method: "POST",
5911
5914
  body: JSON.stringify({
5912
5915
  key,
5913
- value
5916
+ value,
5917
+ is_buildtime: isBuildTime
5914
5918
  })
5915
5919
  });
5916
5920
  if (result.error) {
@@ -6167,6 +6171,15 @@ var CoolifyService = class {
6167
6171
  if (options.dockerComposeDomains) body.docker_compose_domains = options.dockerComposeDomains;
6168
6172
  if (options.isForceHttpsEnabled !== void 0) body.is_force_https_enabled = options.isForceHttpsEnabled;
6169
6173
  if (options.isAutoDeployEnabled !== void 0) body.is_auto_deploy_enabled = options.isAutoDeployEnabled;
6174
+ if (options.healthCheckEnabled !== void 0) body.health_check_enabled = options.healthCheckEnabled;
6175
+ if (options.healthCheckPath) body.health_check_path = options.healthCheckPath;
6176
+ if (options.healthCheckPort) body.health_check_port = String(options.healthCheckPort);
6177
+ if (options.healthCheckMethod) body.health_check_method = options.healthCheckMethod;
6178
+ if (options.healthCheckInterval) body.health_check_interval = options.healthCheckInterval;
6179
+ if (options.healthCheckTimeout) body.health_check_timeout = options.healthCheckTimeout;
6180
+ if (options.healthCheckRetries) body.health_check_retries = options.healthCheckRetries;
6181
+ if (options.healthCheckStartPeriod) body.health_check_start_period = options.healthCheckStartPeriod;
6182
+ if (options.healthCheckReturnCode) body.health_check_return_code = options.healthCheckReturnCode;
6170
6183
  const result = await this.request(`/applications/${appUuid}`, {
6171
6184
  method: "PATCH",
6172
6185
  body: JSON.stringify(body)
@@ -6625,6 +6638,125 @@ var CoolifyService = class {
6625
6638
  });
6626
6639
  }
6627
6640
  /**
6641
+ * Gets the full infrastructure tree: Projects → Environments → Resources.
6642
+ *
6643
+ * Fetches all projects, apps, databases, and services in parallel,
6644
+ * then groups them by environment_id into a hierarchical tree.
6645
+ *
6646
+ * @returns Result with the full infrastructure tree or error
6647
+ */
6648
+ async getInfrastructureTree() {
6649
+ log.info("Building infrastructure tree");
6650
+ const [projectsResult, appsResult, dbsResult, svcsResult, serversResult] = await Promise.all([
6651
+ this.listProjects(),
6652
+ this.listApplications(),
6653
+ this.listDatabases(),
6654
+ this.listServices(),
6655
+ this.listServers()
6656
+ ]);
6657
+ if (isErr(projectsResult)) return err(projectsResult.error);
6658
+ if (isErr(appsResult)) return err(appsResult.error);
6659
+ if (isErr(serversResult)) return err(serversResult.error);
6660
+ const projects = projectsResult.value;
6661
+ const apps = appsResult.value;
6662
+ const dbs = isErr(dbsResult) ? [] : dbsResult.value;
6663
+ const svcs = isErr(svcsResult) ? [] : svcsResult.value;
6664
+ const servers = serversResult.value;
6665
+ const envResults = await Promise.allSettled(projects.map((p) => this.getProjectEnvironments(p.uuid)));
6666
+ const envIdMap = new Map();
6667
+ for (let i = 0; i < projects.length; i++) {
6668
+ const envResult = envResults[i];
6669
+ if (envResult.status === "fulfilled" && !isErr(envResult.value)) for (const env of envResult.value.value) envIdMap.set(env.id, {
6670
+ projectUuid: projects[i].uuid,
6671
+ envName: env.name,
6672
+ envUuid: env.uuid
6673
+ });
6674
+ }
6675
+ const projectNodes = projects.map((p) => ({
6676
+ uuid: p.uuid,
6677
+ name: p.name,
6678
+ description: p.description,
6679
+ environments: []
6680
+ }));
6681
+ const projectMap = new Map();
6682
+ for (const node of projectNodes) projectMap.set(node.uuid, node);
6683
+ const envNodeMap = new Map();
6684
+ for (const [envId, info] of envIdMap) {
6685
+ const project = projectMap.get(info.projectUuid);
6686
+ if (!project) continue;
6687
+ let envNode = project.environments.find((e) => e.id === envId);
6688
+ if (!envNode) {
6689
+ envNode = {
6690
+ id: envId,
6691
+ uuid: info.envUuid,
6692
+ name: info.envName,
6693
+ resources: []
6694
+ };
6695
+ project.environments.push(envNode);
6696
+ }
6697
+ envNodeMap.set(envId, envNode);
6698
+ }
6699
+ for (const app of apps) {
6700
+ const envNode = app.environment_id ? envNodeMap.get(app.environment_id) : void 0;
6701
+ const resource = {
6702
+ uuid: app.uuid,
6703
+ name: app.name,
6704
+ kind: "app",
6705
+ status: app.status,
6706
+ fqdn: app.fqdn
6707
+ };
6708
+ if (envNode) envNode.resources.push(resource);
6709
+ }
6710
+ for (const db of dbs) {
6711
+ const envNode = db.environment_id ? envNodeMap.get(db.environment_id) : void 0;
6712
+ const resource = {
6713
+ uuid: db.uuid,
6714
+ name: db.name,
6715
+ kind: "database",
6716
+ status: db.status,
6717
+ dbType: db.type
6718
+ };
6719
+ if (envNode) envNode.resources.push(resource);
6720
+ }
6721
+ for (const svc of svcs) {
6722
+ const envNode = svc.environment_id ? envNodeMap.get(svc.environment_id) : void 0;
6723
+ const resource = {
6724
+ uuid: svc.uuid,
6725
+ name: svc.name,
6726
+ kind: "service",
6727
+ status: svc.status
6728
+ };
6729
+ if (envNode) envNode.resources.push(resource);
6730
+ }
6731
+ const populatedProjects = projectNodes.filter((p) => p.environments.some((e) => e.resources.length > 0));
6732
+ const allStatuses = [
6733
+ ...apps.map((a) => a.status),
6734
+ ...dbs.map((d) => d.status),
6735
+ ...svcs.map((s) => s.status)
6736
+ ];
6737
+ const counts = {
6738
+ projects: populatedProjects.length,
6739
+ apps: apps.length,
6740
+ databases: dbs.length,
6741
+ services: svcs.length,
6742
+ healthy: allStatuses.filter((s) => s.includes("healthy")).length,
6743
+ running: allStatuses.filter((s) => s.startsWith("running") && !s.includes("healthy")).length,
6744
+ stopped: allStatuses.filter((s) => s.includes("exited")).length,
6745
+ unhealthy: allStatuses.filter((s) => s.includes("unhealthy")).length
6746
+ };
6747
+ const server = servers[0] || { name: "Unknown" };
6748
+ log.success(`Infrastructure tree built: ${counts.projects} projects, ${counts.apps} apps, ${counts.databases} dbs, ${counts.services} svcs`);
6749
+ return ok({
6750
+ server: {
6751
+ name: server.name,
6752
+ ip: server.ip,
6753
+ uuid: server.uuid
6754
+ },
6755
+ projects: populatedProjects,
6756
+ counts
6757
+ });
6758
+ }
6759
+ /**
6628
6760
  * Starts a service.
6629
6761
  * Note: Coolify API uses GET for service start/stop/restart.
6630
6762
  *
@@ -7063,6 +7195,22 @@ var CoolifyService = class {
7063
7195
  return ok(deployments);
7064
7196
  }
7065
7197
  /**
7198
+ * Gets full application details by UUID.
7199
+ *
7200
+ * Makes a direct GET request to /api/v1/applications/{uuid} which returns
7201
+ * complete application data including project_uuid and environment_uuid.
7202
+ *
7203
+ * @param appUuid - Application UUID
7204
+ * @returns Result with full application details or error
7205
+ */
7206
+ async getApplication(appUuid) {
7207
+ log.info(`Getting application details: ${appUuid}`);
7208
+ const result = await this.request(`/applications/${appUuid}`);
7209
+ if (result.error) return err(new Error(result.error));
7210
+ if (!result.data) return err(new Error("Application not found"));
7211
+ return ok(result.data);
7212
+ }
7213
+ /**
7066
7214
  * Resolves an application by UUID, name, or domain (FQDN).
7067
7215
  *
7068
7216
  * @param query - UUID, name, or domain to search for
@@ -7455,6 +7603,117 @@ var ApplicationsResource = class {
7455
7603
  async deleteEnv(uuid, key) {
7456
7604
  return unwrap(await this.svc.deleteEnvironmentVariable(uuid, key));
7457
7605
  }
7606
+ /**
7607
+ * Sync environment variables from a local .env file to Coolify.
7608
+ *
7609
+ * @param uuid - Application UUID
7610
+ * @param options - Sync options
7611
+ * @returns Sync result with changes applied
7612
+ */
7613
+ async syncEnv(uuid, options = {}) {
7614
+ const { filePath, dryRun = false, prune = false, onProgress } = options;
7615
+ let envContent;
7616
+ try {
7617
+ envContent = await Bun.file(filePath || ".env").text();
7618
+ } catch {
7619
+ throw new Error(filePath ? `Cannot read file: ${filePath}` : "No .env file found in current directory");
7620
+ }
7621
+ const localVars = this.parseEnvContent(envContent);
7622
+ if (localVars.size === 0) return {
7623
+ added: [],
7624
+ updated: [],
7625
+ removed: [],
7626
+ skipped: 0
7627
+ };
7628
+ const currentVarsList = await this.envVars(uuid);
7629
+ const currentVars = new Map(currentVarsList.map((v) => [v.key, v.value]));
7630
+ const toAdd = [];
7631
+ const toUpdate = [];
7632
+ const toRemove = [];
7633
+ for (const [key, value] of localVars.entries()) {
7634
+ const currentValue = currentVars.get(key);
7635
+ if (!currentValue) toAdd.push({
7636
+ key,
7637
+ value
7638
+ });
7639
+ else if (currentValue !== value) toUpdate.push({
7640
+ key,
7641
+ value,
7642
+ oldValue: currentValue
7643
+ });
7644
+ }
7645
+ if (prune) {
7646
+ for (const key of currentVars.keys()) if (!localVars.has(key)) toRemove.push(key);
7647
+ }
7648
+ if (!dryRun) {
7649
+ for (const { key, value } of toAdd) {
7650
+ await this.setEnv(uuid, key, value, false);
7651
+ onProgress?.({
7652
+ type: "add",
7653
+ key,
7654
+ value
7655
+ });
7656
+ }
7657
+ for (const { key, value } of toUpdate) {
7658
+ await this.setEnv(uuid, key, value, false);
7659
+ onProgress?.({
7660
+ type: "update",
7661
+ key,
7662
+ value
7663
+ });
7664
+ }
7665
+ for (const key of toRemove) {
7666
+ await this.deleteEnv(uuid, key);
7667
+ onProgress?.({
7668
+ type: "remove",
7669
+ key
7670
+ });
7671
+ }
7672
+ } else {
7673
+ for (const { key, value } of toAdd) onProgress?.({
7674
+ type: "add",
7675
+ key,
7676
+ value
7677
+ });
7678
+ for (const { key, value } of toUpdate) onProgress?.({
7679
+ type: "update",
7680
+ key,
7681
+ value
7682
+ });
7683
+ for (const key of toRemove) onProgress?.({
7684
+ type: "remove",
7685
+ key
7686
+ });
7687
+ }
7688
+ return {
7689
+ added: toAdd,
7690
+ updated: toUpdate,
7691
+ removed: toRemove,
7692
+ skipped: currentVars.size - toUpdate.length - toRemove.length
7693
+ };
7694
+ }
7695
+ /**
7696
+ * Parse .env file content into a Map.
7697
+ * Handles comments, empty lines, and quoted values.
7698
+ *
7699
+ * @param content - The .env file content
7700
+ * @returns Map of environment variables
7701
+ */
7702
+ parseEnvContent(content) {
7703
+ const envVars = new Map();
7704
+ const lines = content.split("\n");
7705
+ for (const line of lines) {
7706
+ const trimmedLine = line.trim();
7707
+ if (!trimmedLine || trimmedLine.startsWith("#")) continue;
7708
+ const eqIndex = trimmedLine.indexOf("=");
7709
+ if (eqIndex === -1) continue;
7710
+ const key = trimmedLine.slice(0, eqIndex).trim();
7711
+ let value = trimmedLine.slice(eqIndex + 1).trim();
7712
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
7713
+ envVars.set(key, value);
7714
+ }
7715
+ return envVars;
7716
+ }
7458
7717
  };
7459
7718
  var DatabasesResource = class {
7460
7719
  constructor(svc) {
@@ -9326,7 +9585,10 @@ var init_network = __esm({ "src/network.ts"() {
9326
9585
  //#region src/cli/coolify-state.ts
9327
9586
  const STATE_FILE = ".coolify.json";
9328
9587
  /**
9329
- * Loads .coolify.json from the current working directory.
9588
+ * Loads .coolify.json from the current working directory (single-app format).
9589
+ *
9590
+ * Handles both single-app and multi-app formats. For multi-app state with
9591
+ * exactly one app, returns a synthesized single-app state for backward compat.
9330
9592
  *
9331
9593
  * @returns The deploy state if found, null otherwise
9332
9594
  */
@@ -9336,6 +9598,27 @@ function loadCoolifyState() {
9336
9598
  try {
9337
9599
  const content = (0, node_fs.readFileSync)(statePath, "utf-8");
9338
9600
  const state = JSON.parse(content);
9601
+ if (Array.isArray(state.apps)) {
9602
+ if (state.apps.length === 1) {
9603
+ const app = state.apps[0];
9604
+ return {
9605
+ appUuid: app.uuid,
9606
+ appName: app.name,
9607
+ serverUuid: state.serverUuid,
9608
+ serverName: state.serverName,
9609
+ projectUuid: state.projectUuid,
9610
+ projectName: state.projectName,
9611
+ environmentUuid: state.environmentUuid,
9612
+ environmentName: state.environmentName,
9613
+ domain: app.domain,
9614
+ branch: state.branch,
9615
+ gitRepository: state.gitRepository,
9616
+ coolifyUrl: state.coolifyUrl,
9617
+ updatedAt: state.updatedAt
9618
+ };
9619
+ }
9620
+ return null;
9621
+ }
9339
9622
  if (!state.appUuid) return null;
9340
9623
  return state;
9341
9624
  } catch {
@@ -9343,18 +9626,45 @@ function loadCoolifyState() {
9343
9626
  }
9344
9627
  }
9345
9628
  /**
9629
+ * Loads .coolify.json in multi-app format.
9630
+ *
9631
+ * If the file is in single-app format, returns null (use loadCoolifyState instead).
9632
+ *
9633
+ * @returns The multi-app state if found, null otherwise
9634
+ */
9635
+ function loadMultiAppState() {
9636
+ const statePath = (0, node_path.join)(process.cwd(), STATE_FILE);
9637
+ if (!(0, node_fs.existsSync)(statePath)) return null;
9638
+ try {
9639
+ const content = (0, node_fs.readFileSync)(statePath, "utf-8");
9640
+ const state = JSON.parse(content);
9641
+ if (!Array.isArray(state.apps)) return null;
9642
+ return state;
9643
+ } catch {
9644
+ return null;
9645
+ }
9646
+ }
9647
+ /**
9346
9648
  * Resolves a UUID argument — if not provided, tries to read from .coolify.json.
9347
9649
  *
9650
+ * Supports both single-app and multi-app state formats. For multi-app state
9651
+ * with exactly one app, auto-selects that app. For multiple apps, returns null
9652
+ * (user must specify explicitly).
9653
+ *
9348
9654
  * @param uuid - UUID from CLI argument (may be undefined)
9349
9655
  * @param field - Which field to read from .coolify.json (default: appUuid)
9350
9656
  * @returns The resolved UUID or null if not found
9351
9657
  */
9352
9658
  function resolveUuid(uuid, field = "appUuid") {
9353
- if (uuid) return uuid;
9659
+ if (uuid && /^[a-z0-9]{16,40}$/.test(uuid)) return uuid;
9354
9660
  const state = loadCoolifyState();
9355
- if (!state) return null;
9356
- const value = state[field];
9357
- return typeof value === "string" ? value : null;
9661
+ if (state) {
9662
+ const value = state[field];
9663
+ return typeof value === "string" ? value : null;
9664
+ }
9665
+ const multiState = loadMultiAppState();
9666
+ if (multiState && multiState.apps.length === 1 && field === "appUuid") return multiState.apps[0].uuid;
9667
+ return null;
9358
9668
  }
9359
9669
 
9360
9670
  //#endregion