@mks2508/coolify-mks-cli-mcp 0.6.2 → 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 +22228 -11529
  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 +19 -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.js CHANGED
@@ -5766,8 +5766,8 @@ var CoolifyService = class {
5766
5766
  environment_uuid: options.environmentUuid,
5767
5767
  server_uuid: options.serverUuid
5768
5768
  };
5769
+ if (options.githubRepoUrl) body.git_repository = options.githubRepoUrl.replace(/^https?:\/\/github\.com\//, "").replace(/\.git$/, "");
5769
5770
  if (appType === "public" || appType === "private-github-app" || appType === "private-deploy-key") {
5770
- if (options.githubRepoUrl) body.git_repository = options.githubRepoUrl.replace(/^https?:\/\/github\.com\//, "").replace(/\.git$/, "");
5771
5771
  if (options.githubAppUuid) body.github_app_uuid = options.githubAppUuid;
5772
5772
  body.git_branch = options.branch || "main";
5773
5773
  body.build_pack = options.buildPack || "dockerfile";
@@ -5809,7 +5809,8 @@ var CoolifyService = class {
5809
5809
  body: JSON.stringify({
5810
5810
  key,
5811
5811
  value,
5812
- is_preview: false
5812
+ is_preview: false,
5813
+ is_buildtime: false
5813
5814
  })
5814
5815
  });
5815
5816
  if (result.error && result.error.includes("already exists")) {
@@ -5822,7 +5823,8 @@ var CoolifyService = class {
5822
5823
  body: JSON.stringify({
5823
5824
  key,
5824
5825
  value,
5825
- is_preview: false
5826
+ is_preview: false,
5827
+ is_buildtime: false
5826
5828
  })
5827
5829
  });
5828
5830
  if (repost.error) {
@@ -5868,8 +5870,8 @@ var CoolifyService = class {
5868
5870
  * @param isBuildTime - Whether the variable is available at build time (only for new vars)
5869
5871
  * @returns Result indicating success or error
5870
5872
  */
5871
- async setEnvironmentVariable(appUuid, key, value, _isBuildTime = false) {
5872
- log.info(`Setting environment variable ${key} for ${appUuid}`);
5873
+ async setEnvironmentVariable(appUuid, key, value, isBuildTime = false) {
5874
+ log.info(`Setting environment variable ${key} for ${appUuid} (buildtime: ${isBuildTime})`);
5873
5875
  const existingVars = await this.getEnvironmentVariables(appUuid);
5874
5876
  if (isErr(existingVars)) return err(existingVars.error);
5875
5877
  const exists = existingVars.value.some((ev) => ev.key === key);
@@ -5879,7 +5881,8 @@ var CoolifyService = class {
5879
5881
  method: "PATCH",
5880
5882
  body: JSON.stringify({
5881
5883
  key,
5882
- value
5884
+ value,
5885
+ is_buildtime: isBuildTime
5883
5886
  })
5884
5887
  });
5885
5888
  if (result.error) {
@@ -5892,7 +5895,8 @@ var CoolifyService = class {
5892
5895
  method: "POST",
5893
5896
  body: JSON.stringify({
5894
5897
  key,
5895
- value
5898
+ value,
5899
+ is_buildtime: isBuildTime
5896
5900
  })
5897
5901
  });
5898
5902
  if (result.error) {
@@ -6149,6 +6153,15 @@ var CoolifyService = class {
6149
6153
  if (options.dockerComposeDomains) body.docker_compose_domains = options.dockerComposeDomains;
6150
6154
  if (options.isForceHttpsEnabled !== void 0) body.is_force_https_enabled = options.isForceHttpsEnabled;
6151
6155
  if (options.isAutoDeployEnabled !== void 0) body.is_auto_deploy_enabled = options.isAutoDeployEnabled;
6156
+ if (options.healthCheckEnabled !== void 0) body.health_check_enabled = options.healthCheckEnabled;
6157
+ if (options.healthCheckPath) body.health_check_path = options.healthCheckPath;
6158
+ if (options.healthCheckPort) body.health_check_port = String(options.healthCheckPort);
6159
+ if (options.healthCheckMethod) body.health_check_method = options.healthCheckMethod;
6160
+ if (options.healthCheckInterval) body.health_check_interval = options.healthCheckInterval;
6161
+ if (options.healthCheckTimeout) body.health_check_timeout = options.healthCheckTimeout;
6162
+ if (options.healthCheckRetries) body.health_check_retries = options.healthCheckRetries;
6163
+ if (options.healthCheckStartPeriod) body.health_check_start_period = options.healthCheckStartPeriod;
6164
+ if (options.healthCheckReturnCode) body.health_check_return_code = options.healthCheckReturnCode;
6152
6165
  const result = await this.request(`/applications/${appUuid}`, {
6153
6166
  method: "PATCH",
6154
6167
  body: JSON.stringify(body)
@@ -6607,6 +6620,125 @@ var CoolifyService = class {
6607
6620
  });
6608
6621
  }
6609
6622
  /**
6623
+ * Gets the full infrastructure tree: Projects → Environments → Resources.
6624
+ *
6625
+ * Fetches all projects, apps, databases, and services in parallel,
6626
+ * then groups them by environment_id into a hierarchical tree.
6627
+ *
6628
+ * @returns Result with the full infrastructure tree or error
6629
+ */
6630
+ async getInfrastructureTree() {
6631
+ log.info("Building infrastructure tree");
6632
+ const [projectsResult, appsResult, dbsResult, svcsResult, serversResult] = await Promise.all([
6633
+ this.listProjects(),
6634
+ this.listApplications(),
6635
+ this.listDatabases(),
6636
+ this.listServices(),
6637
+ this.listServers()
6638
+ ]);
6639
+ if (isErr(projectsResult)) return err(projectsResult.error);
6640
+ if (isErr(appsResult)) return err(appsResult.error);
6641
+ if (isErr(serversResult)) return err(serversResult.error);
6642
+ const projects = projectsResult.value;
6643
+ const apps = appsResult.value;
6644
+ const dbs = isErr(dbsResult) ? [] : dbsResult.value;
6645
+ const svcs = isErr(svcsResult) ? [] : svcsResult.value;
6646
+ const servers = serversResult.value;
6647
+ const envResults = await Promise.allSettled(projects.map((p) => this.getProjectEnvironments(p.uuid)));
6648
+ const envIdMap = new Map();
6649
+ for (let i = 0; i < projects.length; i++) {
6650
+ const envResult = envResults[i];
6651
+ if (envResult.status === "fulfilled" && !isErr(envResult.value)) for (const env of envResult.value.value) envIdMap.set(env.id, {
6652
+ projectUuid: projects[i].uuid,
6653
+ envName: env.name,
6654
+ envUuid: env.uuid
6655
+ });
6656
+ }
6657
+ const projectNodes = projects.map((p) => ({
6658
+ uuid: p.uuid,
6659
+ name: p.name,
6660
+ description: p.description,
6661
+ environments: []
6662
+ }));
6663
+ const projectMap = new Map();
6664
+ for (const node of projectNodes) projectMap.set(node.uuid, node);
6665
+ const envNodeMap = new Map();
6666
+ for (const [envId, info] of envIdMap) {
6667
+ const project = projectMap.get(info.projectUuid);
6668
+ if (!project) continue;
6669
+ let envNode = project.environments.find((e) => e.id === envId);
6670
+ if (!envNode) {
6671
+ envNode = {
6672
+ id: envId,
6673
+ uuid: info.envUuid,
6674
+ name: info.envName,
6675
+ resources: []
6676
+ };
6677
+ project.environments.push(envNode);
6678
+ }
6679
+ envNodeMap.set(envId, envNode);
6680
+ }
6681
+ for (const app of apps) {
6682
+ const envNode = app.environment_id ? envNodeMap.get(app.environment_id) : void 0;
6683
+ const resource = {
6684
+ uuid: app.uuid,
6685
+ name: app.name,
6686
+ kind: "app",
6687
+ status: app.status,
6688
+ fqdn: app.fqdn
6689
+ };
6690
+ if (envNode) envNode.resources.push(resource);
6691
+ }
6692
+ for (const db of dbs) {
6693
+ const envNode = db.environment_id ? envNodeMap.get(db.environment_id) : void 0;
6694
+ const resource = {
6695
+ uuid: db.uuid,
6696
+ name: db.name,
6697
+ kind: "database",
6698
+ status: db.status,
6699
+ dbType: db.type
6700
+ };
6701
+ if (envNode) envNode.resources.push(resource);
6702
+ }
6703
+ for (const svc of svcs) {
6704
+ const envNode = svc.environment_id ? envNodeMap.get(svc.environment_id) : void 0;
6705
+ const resource = {
6706
+ uuid: svc.uuid,
6707
+ name: svc.name,
6708
+ kind: "service",
6709
+ status: svc.status
6710
+ };
6711
+ if (envNode) envNode.resources.push(resource);
6712
+ }
6713
+ const populatedProjects = projectNodes.filter((p) => p.environments.some((e) => e.resources.length > 0));
6714
+ const allStatuses = [
6715
+ ...apps.map((a) => a.status),
6716
+ ...dbs.map((d) => d.status),
6717
+ ...svcs.map((s) => s.status)
6718
+ ];
6719
+ const counts = {
6720
+ projects: populatedProjects.length,
6721
+ apps: apps.length,
6722
+ databases: dbs.length,
6723
+ services: svcs.length,
6724
+ healthy: allStatuses.filter((s) => s.includes("healthy")).length,
6725
+ running: allStatuses.filter((s) => s.startsWith("running") && !s.includes("healthy")).length,
6726
+ stopped: allStatuses.filter((s) => s.includes("exited")).length,
6727
+ unhealthy: allStatuses.filter((s) => s.includes("unhealthy")).length
6728
+ };
6729
+ const server = servers[0] || { name: "Unknown" };
6730
+ log.success(`Infrastructure tree built: ${counts.projects} projects, ${counts.apps} apps, ${counts.databases} dbs, ${counts.services} svcs`);
6731
+ return ok({
6732
+ server: {
6733
+ name: server.name,
6734
+ ip: server.ip,
6735
+ uuid: server.uuid
6736
+ },
6737
+ projects: populatedProjects,
6738
+ counts
6739
+ });
6740
+ }
6741
+ /**
6610
6742
  * Starts a service.
6611
6743
  * Note: Coolify API uses GET for service start/stop/restart.
6612
6744
  *
@@ -7045,6 +7177,22 @@ var CoolifyService = class {
7045
7177
  return ok(deployments);
7046
7178
  }
7047
7179
  /**
7180
+ * Gets full application details by UUID.
7181
+ *
7182
+ * Makes a direct GET request to /api/v1/applications/{uuid} which returns
7183
+ * complete application data including project_uuid and environment_uuid.
7184
+ *
7185
+ * @param appUuid - Application UUID
7186
+ * @returns Result with full application details or error
7187
+ */
7188
+ async getApplication(appUuid) {
7189
+ log.info(`Getting application details: ${appUuid}`);
7190
+ const result = await this.request(`/applications/${appUuid}`);
7191
+ if (result.error) return err(new Error(result.error));
7192
+ if (!result.data) return err(new Error("Application not found"));
7193
+ return ok(result.data);
7194
+ }
7195
+ /**
7048
7196
  * Resolves an application by UUID, name, or domain (FQDN).
7049
7197
  *
7050
7198
  * @param query - UUID, name, or domain to search for
@@ -7437,6 +7585,117 @@ var ApplicationsResource = class {
7437
7585
  async deleteEnv(uuid, key) {
7438
7586
  return unwrap(await this.svc.deleteEnvironmentVariable(uuid, key));
7439
7587
  }
7588
+ /**
7589
+ * Sync environment variables from a local .env file to Coolify.
7590
+ *
7591
+ * @param uuid - Application UUID
7592
+ * @param options - Sync options
7593
+ * @returns Sync result with changes applied
7594
+ */
7595
+ async syncEnv(uuid, options = {}) {
7596
+ const { filePath, dryRun = false, prune = false, onProgress } = options;
7597
+ let envContent;
7598
+ try {
7599
+ envContent = await Bun.file(filePath || ".env").text();
7600
+ } catch {
7601
+ throw new Error(filePath ? `Cannot read file: ${filePath}` : "No .env file found in current directory");
7602
+ }
7603
+ const localVars = this.parseEnvContent(envContent);
7604
+ if (localVars.size === 0) return {
7605
+ added: [],
7606
+ updated: [],
7607
+ removed: [],
7608
+ skipped: 0
7609
+ };
7610
+ const currentVarsList = await this.envVars(uuid);
7611
+ const currentVars = new Map(currentVarsList.map((v) => [v.key, v.value]));
7612
+ const toAdd = [];
7613
+ const toUpdate = [];
7614
+ const toRemove = [];
7615
+ for (const [key, value] of localVars.entries()) {
7616
+ const currentValue = currentVars.get(key);
7617
+ if (!currentValue) toAdd.push({
7618
+ key,
7619
+ value
7620
+ });
7621
+ else if (currentValue !== value) toUpdate.push({
7622
+ key,
7623
+ value,
7624
+ oldValue: currentValue
7625
+ });
7626
+ }
7627
+ if (prune) {
7628
+ for (const key of currentVars.keys()) if (!localVars.has(key)) toRemove.push(key);
7629
+ }
7630
+ if (!dryRun) {
7631
+ for (const { key, value } of toAdd) {
7632
+ await this.setEnv(uuid, key, value, false);
7633
+ onProgress?.({
7634
+ type: "add",
7635
+ key,
7636
+ value
7637
+ });
7638
+ }
7639
+ for (const { key, value } of toUpdate) {
7640
+ await this.setEnv(uuid, key, value, false);
7641
+ onProgress?.({
7642
+ type: "update",
7643
+ key,
7644
+ value
7645
+ });
7646
+ }
7647
+ for (const key of toRemove) {
7648
+ await this.deleteEnv(uuid, key);
7649
+ onProgress?.({
7650
+ type: "remove",
7651
+ key
7652
+ });
7653
+ }
7654
+ } else {
7655
+ for (const { key, value } of toAdd) onProgress?.({
7656
+ type: "add",
7657
+ key,
7658
+ value
7659
+ });
7660
+ for (const { key, value } of toUpdate) onProgress?.({
7661
+ type: "update",
7662
+ key,
7663
+ value
7664
+ });
7665
+ for (const key of toRemove) onProgress?.({
7666
+ type: "remove",
7667
+ key
7668
+ });
7669
+ }
7670
+ return {
7671
+ added: toAdd,
7672
+ updated: toUpdate,
7673
+ removed: toRemove,
7674
+ skipped: currentVars.size - toUpdate.length - toRemove.length
7675
+ };
7676
+ }
7677
+ /**
7678
+ * Parse .env file content into a Map.
7679
+ * Handles comments, empty lines, and quoted values.
7680
+ *
7681
+ * @param content - The .env file content
7682
+ * @returns Map of environment variables
7683
+ */
7684
+ parseEnvContent(content) {
7685
+ const envVars = new Map();
7686
+ const lines = content.split("\n");
7687
+ for (const line of lines) {
7688
+ const trimmedLine = line.trim();
7689
+ if (!trimmedLine || trimmedLine.startsWith("#")) continue;
7690
+ const eqIndex = trimmedLine.indexOf("=");
7691
+ if (eqIndex === -1) continue;
7692
+ const key = trimmedLine.slice(0, eqIndex).trim();
7693
+ let value = trimmedLine.slice(eqIndex + 1).trim();
7694
+ if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
7695
+ envVars.set(key, value);
7696
+ }
7697
+ return envVars;
7698
+ }
7440
7699
  };
7441
7700
  var DatabasesResource = class {
7442
7701
  constructor(svc) {
@@ -9308,7 +9567,10 @@ var init_network = __esm({ "src/network.ts"() {
9308
9567
  //#region src/cli/coolify-state.ts
9309
9568
  const STATE_FILE = ".coolify.json";
9310
9569
  /**
9311
- * Loads .coolify.json from the current working directory.
9570
+ * Loads .coolify.json from the current working directory (single-app format).
9571
+ *
9572
+ * Handles both single-app and multi-app formats. For multi-app state with
9573
+ * exactly one app, returns a synthesized single-app state for backward compat.
9312
9574
  *
9313
9575
  * @returns The deploy state if found, null otherwise
9314
9576
  */
@@ -9318,6 +9580,27 @@ function loadCoolifyState() {
9318
9580
  try {
9319
9581
  const content = readFileSync(statePath, "utf-8");
9320
9582
  const state = JSON.parse(content);
9583
+ if (Array.isArray(state.apps)) {
9584
+ if (state.apps.length === 1) {
9585
+ const app = state.apps[0];
9586
+ return {
9587
+ appUuid: app.uuid,
9588
+ appName: app.name,
9589
+ serverUuid: state.serverUuid,
9590
+ serverName: state.serverName,
9591
+ projectUuid: state.projectUuid,
9592
+ projectName: state.projectName,
9593
+ environmentUuid: state.environmentUuid,
9594
+ environmentName: state.environmentName,
9595
+ domain: app.domain,
9596
+ branch: state.branch,
9597
+ gitRepository: state.gitRepository,
9598
+ coolifyUrl: state.coolifyUrl,
9599
+ updatedAt: state.updatedAt
9600
+ };
9601
+ }
9602
+ return null;
9603
+ }
9321
9604
  if (!state.appUuid) return null;
9322
9605
  return state;
9323
9606
  } catch {
@@ -9325,18 +9608,45 @@ function loadCoolifyState() {
9325
9608
  }
9326
9609
  }
9327
9610
  /**
9611
+ * Loads .coolify.json in multi-app format.
9612
+ *
9613
+ * If the file is in single-app format, returns null (use loadCoolifyState instead).
9614
+ *
9615
+ * @returns The multi-app state if found, null otherwise
9616
+ */
9617
+ function loadMultiAppState() {
9618
+ const statePath = join(process.cwd(), STATE_FILE);
9619
+ if (!existsSync(statePath)) return null;
9620
+ try {
9621
+ const content = readFileSync(statePath, "utf-8");
9622
+ const state = JSON.parse(content);
9623
+ if (!Array.isArray(state.apps)) return null;
9624
+ return state;
9625
+ } catch {
9626
+ return null;
9627
+ }
9628
+ }
9629
+ /**
9328
9630
  * Resolves a UUID argument — if not provided, tries to read from .coolify.json.
9329
9631
  *
9632
+ * Supports both single-app and multi-app state formats. For multi-app state
9633
+ * with exactly one app, auto-selects that app. For multiple apps, returns null
9634
+ * (user must specify explicitly).
9635
+ *
9330
9636
  * @param uuid - UUID from CLI argument (may be undefined)
9331
9637
  * @param field - Which field to read from .coolify.json (default: appUuid)
9332
9638
  * @returns The resolved UUID or null if not found
9333
9639
  */
9334
9640
  function resolveUuid(uuid, field = "appUuid") {
9335
- if (uuid) return uuid;
9641
+ if (uuid && /^[a-z0-9]{16,40}$/.test(uuid)) return uuid;
9336
9642
  const state = loadCoolifyState();
9337
- if (!state) return null;
9338
- const value = state[field];
9339
- return typeof value === "string" ? value : null;
9643
+ if (state) {
9644
+ const value = state[field];
9645
+ return typeof value === "string" ? value : null;
9646
+ }
9647
+ const multiState = loadMultiAppState();
9648
+ if (multiState && multiState.apps.length === 1 && field === "appUuid") return multiState.apps[0].uuid;
9649
+ return null;
9340
9650
  }
9341
9651
 
9342
9652
  //#endregion