@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
@@ -32,6 +32,10 @@ import {
32
32
  type ICoolifyTeam,
33
33
  type ICoolifyUpdateOptions,
34
34
  type ICoolifyVersion,
35
+ type ICoolifyInfrastructureTree,
36
+ type ICoolifyProjectNode,
37
+ type ICoolifyEnvironmentNode,
38
+ type ICoolifyResource,
35
39
  type IProgressCallback,
36
40
  } from "./types.js";
37
41
 
@@ -333,18 +337,20 @@ export class CoolifyService {
333
337
  server_uuid: options.serverUuid,
334
338
  };
335
339
 
340
+ // Git repository — applies to all types that have a repo URL
341
+ if (options.githubRepoUrl) {
342
+ // Coolify expects 'user/repo' format, not full URL
343
+ body.git_repository = options.githubRepoUrl
344
+ .replace(/^https?:\/\/github\.com\//, "")
345
+ .replace(/\.git$/, "");
346
+ }
347
+
336
348
  // Type-specific fields
337
349
  if (
338
350
  appType === "public" ||
339
351
  appType === "private-github-app" ||
340
352
  appType === "private-deploy-key"
341
353
  ) {
342
- if (options.githubRepoUrl) {
343
- // Coolify expects 'user/repo' format, not full URL
344
- body.git_repository = options.githubRepoUrl
345
- .replace(/^https?:\/\/github\.com\//, "")
346
- .replace(/\.git$/, "");
347
- }
348
354
  if (options.githubAppUuid) {
349
355
  body.github_app_uuid = options.githubAppUuid;
350
356
  }
@@ -410,7 +416,7 @@ export class CoolifyService {
410
416
  for (const [key, value] of Object.entries(envVars)) {
411
417
  const result = await this.request(`/applications/${appUuid}/envs`, {
412
418
  method: "POST",
413
- body: JSON.stringify({ key, value, is_preview: false }),
419
+ body: JSON.stringify({ key, value, is_preview: false, is_buildtime: false }),
414
420
  });
415
421
 
416
422
  if (result.error && result.error.includes("already exists")) {
@@ -425,7 +431,7 @@ export class CoolifyService {
425
431
  });
426
432
  const repost = await this.request(`/applications/${appUuid}/envs`, {
427
433
  method: "POST",
428
- body: JSON.stringify({ key, value, is_preview: false }),
434
+ body: JSON.stringify({ key, value, is_preview: false, is_buildtime: false }),
429
435
  });
430
436
  if (repost.error) {
431
437
  log.error(`Failed to update env var ${key}: ${repost.error}`);
@@ -484,9 +490,9 @@ export class CoolifyService {
484
490
  appUuid: string,
485
491
  key: string,
486
492
  value: string,
487
- _isBuildTime: boolean = false,
493
+ isBuildTime: boolean = false,
488
494
  ): Promise<Result<void, Error>> {
489
- log.info(`Setting environment variable ${key} for ${appUuid}`);
495
+ log.info(`Setting environment variable ${key} for ${appUuid} (buildtime: ${isBuildTime})`);
490
496
 
491
497
  // Check if variable already exists
492
498
  const existingVars = await this.getEnvironmentVariables(appUuid);
@@ -503,7 +509,11 @@ export class CoolifyService {
503
509
  `/applications/${appUuid}/envs`,
504
510
  {
505
511
  method: "PATCH",
506
- body: JSON.stringify({ key, value }),
512
+ body: JSON.stringify({
513
+ key,
514
+ value,
515
+ is_buildtime: isBuildTime,
516
+ }),
507
517
  },
508
518
  );
509
519
 
@@ -518,7 +528,11 @@ export class CoolifyService {
518
528
  `/applications/${appUuid}/envs`,
519
529
  {
520
530
  method: "POST",
521
- body: JSON.stringify({ key, value }),
531
+ body: JSON.stringify({
532
+ key,
533
+ value,
534
+ is_buildtime: isBuildTime,
535
+ }),
522
536
  },
523
537
  );
524
538
 
@@ -910,6 +924,24 @@ export class CoolifyService {
910
924
  body.is_force_https_enabled = options.isForceHttpsEnabled;
911
925
  if (options.isAutoDeployEnabled !== undefined)
912
926
  body.is_auto_deploy_enabled = options.isAutoDeployEnabled;
927
+ if (options.healthCheckEnabled !== undefined)
928
+ body.health_check_enabled = options.healthCheckEnabled;
929
+ if (options.healthCheckPath)
930
+ body.health_check_path = options.healthCheckPath;
931
+ if (options.healthCheckPort)
932
+ body.health_check_port = String(options.healthCheckPort);
933
+ if (options.healthCheckMethod)
934
+ body.health_check_method = options.healthCheckMethod;
935
+ if (options.healthCheckInterval)
936
+ body.health_check_interval = options.healthCheckInterval;
937
+ if (options.healthCheckTimeout)
938
+ body.health_check_timeout = options.healthCheckTimeout;
939
+ if (options.healthCheckRetries)
940
+ body.health_check_retries = options.healthCheckRetries;
941
+ if (options.healthCheckStartPeriod)
942
+ body.health_check_start_period = options.healthCheckStartPeriod;
943
+ if (options.healthCheckReturnCode)
944
+ body.health_check_return_code = options.healthCheckReturnCode;
913
945
 
914
946
  const result = await this.request<ICoolifyApplication>(
915
947
  `/applications/${appUuid}`,
@@ -1620,6 +1652,186 @@ export class CoolifyService {
1620
1652
  return ok({ success: true, message: "Service deleted" });
1621
1653
  }
1622
1654
 
1655
+ /**
1656
+ * Gets the full infrastructure tree: Projects → Environments → Resources.
1657
+ *
1658
+ * Fetches all projects, apps, databases, and services in parallel,
1659
+ * then groups them by environment_id into a hierarchical tree.
1660
+ *
1661
+ * @returns Result with the full infrastructure tree or error
1662
+ */
1663
+ async getInfrastructureTree(): Promise<
1664
+ Result<ICoolifyInfrastructureTree, Error>
1665
+ > {
1666
+ log.info("Building infrastructure tree");
1667
+
1668
+ // Fetch all data in parallel
1669
+ const [projectsResult, appsResult, dbsResult, svcsResult, serversResult] =
1670
+ await Promise.all([
1671
+ this.listProjects(),
1672
+ this.listApplications(),
1673
+ this.listDatabases(),
1674
+ this.listServices(),
1675
+ this.listServers(),
1676
+ ]);
1677
+
1678
+ if (isErr(projectsResult)) return err(projectsResult.error);
1679
+ if (isErr(appsResult)) return err(appsResult.error);
1680
+ if (isErr(serversResult)) return err(serversResult.error);
1681
+
1682
+ const projects = projectsResult.value;
1683
+ const apps = appsResult.value;
1684
+ const dbs = isErr(dbsResult) ? [] : dbsResult.value;
1685
+ const svcs = isErr(svcsResult) ? [] : svcsResult.value;
1686
+ const servers = serversResult.value;
1687
+
1688
+ // Fetch environments for each project in parallel
1689
+ const envResults = await Promise.allSettled(
1690
+ projects.map((p) => this.getProjectEnvironments(p.uuid)),
1691
+ );
1692
+
1693
+ // Build environment_id → { projectUuid, envName, envUuid } mapping
1694
+ const envIdMap = new Map<
1695
+ number,
1696
+ { projectUuid: string; envName: string; envUuid: string }
1697
+ >();
1698
+
1699
+ for (let i = 0; i < projects.length; i++) {
1700
+ const envResult = envResults[i];
1701
+ if (envResult.status === "fulfilled" && !isErr(envResult.value)) {
1702
+ for (const env of envResult.value.value) {
1703
+ envIdMap.set(env.id, {
1704
+ projectUuid: projects[i].uuid,
1705
+ envName: env.name,
1706
+ envUuid: env.uuid,
1707
+ });
1708
+ }
1709
+ }
1710
+ }
1711
+
1712
+ // Build project nodes
1713
+ const projectNodes: ICoolifyProjectNode[] = projects.map((p) => ({
1714
+ uuid: p.uuid,
1715
+ name: p.name,
1716
+ description: p.description,
1717
+ environments: [],
1718
+ }));
1719
+
1720
+ const projectMap = new Map<string, ICoolifyProjectNode>();
1721
+ for (const node of projectNodes) {
1722
+ projectMap.set(node.uuid, node);
1723
+ }
1724
+
1725
+ // Populate environments from envIdMap
1726
+ const envNodeMap = new Map<number, ICoolifyEnvironmentNode>();
1727
+ for (const [envId, info] of envIdMap) {
1728
+ const project = projectMap.get(info.projectUuid);
1729
+ if (!project) continue;
1730
+
1731
+ let envNode = project.environments.find((e) => e.id === envId);
1732
+ if (!envNode) {
1733
+ envNode = {
1734
+ id: envId,
1735
+ uuid: info.envUuid,
1736
+ name: info.envName,
1737
+ resources: [],
1738
+ };
1739
+ project.environments.push(envNode);
1740
+ }
1741
+ envNodeMap.set(envId, envNode);
1742
+ }
1743
+
1744
+ // Assign apps to environments
1745
+ for (const app of apps) {
1746
+ const envNode = app.environment_id
1747
+ ? envNodeMap.get(app.environment_id)
1748
+ : undefined;
1749
+ const resource: ICoolifyResource = {
1750
+ uuid: app.uuid,
1751
+ name: app.name,
1752
+ kind: "app",
1753
+ status: app.status,
1754
+ fqdn: app.fqdn,
1755
+ };
1756
+ if (envNode) {
1757
+ envNode.resources.push(resource);
1758
+ }
1759
+ }
1760
+
1761
+ // Assign databases to environments
1762
+ for (const db of dbs) {
1763
+ const envNode = (db as any).environment_id
1764
+ ? envNodeMap.get((db as any).environment_id)
1765
+ : undefined;
1766
+ const resource: ICoolifyResource = {
1767
+ uuid: db.uuid,
1768
+ name: db.name,
1769
+ kind: "database",
1770
+ status: db.status,
1771
+ dbType: db.type,
1772
+ };
1773
+ if (envNode) {
1774
+ envNode.resources.push(resource);
1775
+ }
1776
+ }
1777
+
1778
+ // Assign services to environments
1779
+ for (const svc of svcs) {
1780
+ const envNode = (svc as any).environment_id
1781
+ ? envNodeMap.get((svc as any).environment_id)
1782
+ : undefined;
1783
+ const resource: ICoolifyResource = {
1784
+ uuid: svc.uuid,
1785
+ name: svc.name,
1786
+ kind: "service",
1787
+ status: svc.status,
1788
+ };
1789
+ if (envNode) {
1790
+ envNode.resources.push(resource);
1791
+ }
1792
+ }
1793
+
1794
+ // Filter out empty projects
1795
+ const populatedProjects = projectNodes.filter(
1796
+ (p) => p.environments.some((e) => e.resources.length > 0),
1797
+ );
1798
+
1799
+ // Aggregate counts
1800
+ const allStatuses = [
1801
+ ...apps.map((a) => a.status),
1802
+ ...dbs.map((d) => d.status),
1803
+ ...svcs.map((s) => s.status),
1804
+ ];
1805
+ const counts = {
1806
+ projects: populatedProjects.length,
1807
+ apps: apps.length,
1808
+ databases: dbs.length,
1809
+ services: svcs.length,
1810
+ healthy: allStatuses.filter((s) => s.includes("healthy")).length,
1811
+ running: allStatuses.filter(
1812
+ (s) => s.startsWith("running") && !s.includes("healthy"),
1813
+ ).length,
1814
+ stopped: allStatuses.filter((s) => s.includes("exited")).length,
1815
+ unhealthy: allStatuses.filter((s) => s.includes("unhealthy")).length,
1816
+ };
1817
+
1818
+ const server = servers[0] || { name: "Unknown" };
1819
+
1820
+ log.success(
1821
+ `Infrastructure tree built: ${counts.projects} projects, ${counts.apps} apps, ${counts.databases} dbs, ${counts.services} svcs`,
1822
+ );
1823
+
1824
+ return ok({
1825
+ server: {
1826
+ name: server.name,
1827
+ ip: server.ip,
1828
+ uuid: server.uuid,
1829
+ },
1830
+ projects: populatedProjects,
1831
+ counts,
1832
+ });
1833
+ }
1834
+
1623
1835
  /**
1624
1836
  * Starts a service.
1625
1837
  * Note: Coolify API uses GET for service start/stop/restart.
@@ -2295,6 +2507,35 @@ export class CoolifyService {
2295
2507
  // Smart Resolution Helpers
2296
2508
  // ===========================================================================
2297
2509
 
2510
+ /**
2511
+ * Gets full application details by UUID.
2512
+ *
2513
+ * Makes a direct GET request to /api/v1/applications/{uuid} which returns
2514
+ * complete application data including project_uuid and environment_uuid.
2515
+ *
2516
+ * @param appUuid - Application UUID
2517
+ * @returns Result with full application details or error
2518
+ */
2519
+ async getApplication(
2520
+ appUuid: string,
2521
+ ): Promise<Result<ICoolifyApplication, Error>> {
2522
+ log.info(`Getting application details: ${appUuid}`);
2523
+
2524
+ const result = await this.request<ICoolifyApplication>(
2525
+ `/applications/${appUuid}`,
2526
+ );
2527
+
2528
+ if (result.error) {
2529
+ return err(new Error(result.error));
2530
+ }
2531
+
2532
+ if (!result.data) {
2533
+ return err(new Error("Application not found"));
2534
+ }
2535
+
2536
+ return ok(result.data);
2537
+ }
2538
+
2298
2539
  /**
2299
2540
  * Resolves an application by UUID, name, or domain (FQDN).
2300
2541
  *
@@ -2878,4 +3119,8 @@ export type {
2878
3119
  ICoolifyLogsOptions,
2879
3120
  ICoolifyLogs,
2880
3121
  IProgressCallback,
3122
+ ICoolifyInfrastructureTree,
3123
+ ICoolifyProjectNode,
3124
+ ICoolifyEnvironmentNode,
3125
+ ICoolifyResource,
2881
3126
  } from "./types.js";
@@ -106,6 +106,24 @@ export interface ICoolifyUpdateOptions {
106
106
  isForceHttpsEnabled?: boolean;
107
107
  /** Enable auto deploy on git push */
108
108
  isAutoDeployEnabled?: boolean;
109
+ /** Enable health check for the application */
110
+ healthCheckEnabled?: boolean;
111
+ /** Health check endpoint path (e.g., "/health") */
112
+ healthCheckPath?: string;
113
+ /** Health check port (defaults to application port) */
114
+ healthCheckPort?: string | number;
115
+ /** Health check HTTP method (e.g., "GET") */
116
+ healthCheckMethod?: string;
117
+ /** Health check interval in seconds */
118
+ healthCheckInterval?: number;
119
+ /** Health check timeout in seconds */
120
+ healthCheckTimeout?: number;
121
+ /** Number of retries before marking unhealthy */
122
+ healthCheckRetries?: number;
123
+ /** Grace period in seconds before health checks start */
124
+ healthCheckStartPeriod?: number;
125
+ /** Expected HTTP return code for healthy status */
126
+ healthCheckReturnCode?: number;
109
127
  }
110
128
 
111
129
  /**
@@ -224,8 +242,16 @@ export interface ICoolifyApplication {
224
242
  base_directory?: string | null;
225
243
  /** Server status (boolean) */
226
244
  server_status?: boolean;
227
- /** Environment ID */
245
+ /** Environment ID (numeric) */
228
246
  environment_id?: number;
247
+ /** Project UUID */
248
+ project_uuid?: string;
249
+ /** Environment UUID */
250
+ environment_uuid?: string;
251
+ /** Source type (e.g., "App\\Models\\GithubApp", "App\\Models\\DeployKey") */
252
+ source_type?: string;
253
+ /** Type (legacy, may be null) */
254
+ type?: string;
229
255
  /** Destination info */
230
256
  destination?: {
231
257
  uuid: string;
@@ -361,6 +387,8 @@ export interface ICoolifyDatabase {
361
387
  status: string;
362
388
  version?: string;
363
389
  description?: string | null;
390
+ /** Environment ID (numeric, for grouping under projects) */
391
+ environment_id?: number;
364
392
  destination?: {
365
393
  uuid: string;
366
394
  name: string;
@@ -382,6 +410,8 @@ export interface ICoolifyService {
382
410
  status: string;
383
411
  version?: string;
384
412
  description?: string | null;
413
+ /** Environment ID (numeric, for grouping under projects) */
414
+ environment_id?: number;
385
415
  project_uuid?: string;
386
416
  environment_uuid?: string;
387
417
  server_uuid?: string;
@@ -439,3 +469,75 @@ export interface ICoolifyVersion {
439
469
  latest_version?: string;
440
470
  is_latest?: boolean;
441
471
  }
472
+
473
+ /**
474
+ * A resource (app, database, or service) within an environment.
475
+ */
476
+ export interface ICoolifyResource {
477
+ /** Resource UUID */
478
+ uuid: string;
479
+ /** Resource name */
480
+ name: string;
481
+ /** Resource type: app, database, or service */
482
+ kind: "app" | "database" | "service";
483
+ /** Current status */
484
+ status: string;
485
+ /** FQDN/domain if configured */
486
+ fqdn?: string | null;
487
+ /** Database engine type (only for databases) */
488
+ dbType?: string;
489
+ }
490
+
491
+ /**
492
+ * An environment within a project, containing resources.
493
+ */
494
+ export interface ICoolifyEnvironmentNode {
495
+ /** Environment ID (numeric) */
496
+ id: number;
497
+ /** Environment UUID */
498
+ uuid: string;
499
+ /** Environment name (e.g., "production", "staging") */
500
+ name: string;
501
+ /** Resources in this environment */
502
+ resources: ICoolifyResource[];
503
+ }
504
+
505
+ /**
506
+ * A project node in the infrastructure tree.
507
+ */
508
+ export interface ICoolifyProjectNode {
509
+ /** Project UUID */
510
+ uuid: string;
511
+ /** Project name */
512
+ name: string;
513
+ /** Project description */
514
+ description?: string | null;
515
+ /** Environments in this project */
516
+ environments: ICoolifyEnvironmentNode[];
517
+ }
518
+
519
+ /**
520
+ * Full infrastructure tree: projects → environments → resources.
521
+ * Also includes global server info and aggregate counts.
522
+ */
523
+ export interface ICoolifyInfrastructureTree {
524
+ /** Server info */
525
+ server: {
526
+ name: string;
527
+ ip?: string;
528
+ uuid?: string;
529
+ };
530
+ /** Projects with their environments and resources */
531
+ projects: ICoolifyProjectNode[];
532
+ /** Aggregate counts */
533
+ counts: {
534
+ projects: number;
535
+ apps: number;
536
+ databases: number;
537
+ services: number;
538
+ healthy: number;
539
+ running: number;
540
+ stopped: number;
541
+ unhealthy: number;
542
+ };
543
+ }
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Demo script showing new CLI UI features.
4
+ *
5
+ * @module
6
+ */
7
+
8
+ import { highlightEnvBlock, createEnvTable, createChangeSummary } from "../cli/ui/index.js";
9
+
10
+ const sampleEnvVars = [
11
+ {
12
+ key: "DATABASE_URL",
13
+ value: "postgresql://user:pass@localhost:5432/mydb",
14
+ is_runtime: true,
15
+ is_buildtime: false,
16
+ is_required: true,
17
+ },
18
+ {
19
+ key: "API_KEY",
20
+ value: "sk_test_1234567890abcdef",
21
+ is_runtime: true,
22
+ is_buildtime: false,
23
+ is_required: false,
24
+ },
25
+ {
26
+ key: "NODE_ENV",
27
+ value: "production",
28
+ is_runtime: true,
29
+ is_buildtime: true,
30
+ is_required: false,
31
+ },
32
+ {
33
+ key: "BUILD_ARG",
34
+ value: "some_value",
35
+ is_runtime: false,
36
+ is_buildtime: true,
37
+ is_required: false,
38
+ },
39
+ ];
40
+
41
+ const sampleChanges = {
42
+ added: [
43
+ { key: "NEW_VAR", value: "new_value" },
44
+ { key: "ANOTHER_NEW", value: "another" },
45
+ ],
46
+ updated: [
47
+ { key: "DATABASE_URL", value: "new_db_url", oldValue: "old_db_url" },
48
+ { key: "API_KEY", value: "new_key", oldValue: "old_key" },
49
+ ],
50
+ removed: ["DEPRECATED_VAR", "OLD_SETTING"],
51
+ };
52
+
53
+ console.log("\n" + "=".repeat(60));
54
+ console.log(" CLI UI Demo - Syntax Highlighting");
55
+ console.log("=".repeat(60) + "\n");
56
+
57
+ // Demo 1: Syntax highlighted .env file
58
+ console.log("1️⃣ Syntax Highlighted .env File");
59
+ console.log("─".repeat(60));
60
+ const envContent = await Bun.file("/tmp/test.env").text();
61
+ console.log(highlightEnvBlock(envContent));
62
+ console.log();
63
+
64
+ // Demo 2: Table view
65
+ console.log("2️⃣ Table View with cli-table3");
66
+ console.log("─".repeat(60));
67
+ console.log(createEnvTable(sampleEnvVars, { compact: true, showType: true }));
68
+ console.log();
69
+
70
+ // Demo 3: Change summary
71
+ console.log("3️⃣ Change Summary for Sync");
72
+ console.log("─".repeat(60));
73
+ console.log(createChangeSummary(sampleChanges));
74
+ console.log();
75
+
76
+ console.log("=".repeat(60));
77
+ console.log(" ✨ Demo complete!");
78
+ console.log("=".repeat(60) + "\n");