@meetploy/cli 1.7.0 → 1.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 (2) hide show
  1. package/dist/index.js +1334 -11
  2. package/package.json +5 -2
package/dist/index.js CHANGED
@@ -1,19 +1,21 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'module';
3
- import { mkdir, writeFile, readFile, access } from 'fs/promises';
3
+ import { mkdir, writeFile, readFile, chmod, access } from 'fs/promises';
4
4
  import { join, dirname } from 'path';
5
5
  import { existsSync, readFileSync, readFile as readFile$1, mkdirSync, writeFileSync } from 'fs';
6
6
  import { promisify } from 'util';
7
7
  import { parse } from 'yaml';
8
8
  import { build } from 'esbuild';
9
9
  import { watch } from 'chokidar';
10
- import { fileURLToPath } from 'url';
11
- import { randomUUID, createHash } from 'crypto';
10
+ import { URL, fileURLToPath } from 'url';
11
+ import { randomBytes, randomUUID, createHash } from 'crypto';
12
12
  import { serve } from '@hono/node-server';
13
13
  import { Hono } from 'hono';
14
- import { tmpdir } from 'os';
14
+ import { homedir, tmpdir } from 'os';
15
15
  import Database from 'better-sqlite3';
16
- import { spawn } from 'child_process';
16
+ import { spawn, exec } from 'child_process';
17
+ import createClient from 'openapi-fetch';
18
+ import { createServer } from 'http';
17
19
 
18
20
  createRequire(import.meta.url);
19
21
  var __defProp = Object.defineProperty;
@@ -2597,6 +2599,1317 @@ var init_dist2 = __esm({
2597
2599
  init_db_runtime();
2598
2600
  }
2599
2601
  });
2602
+ var CONFIG_DIR = join(homedir(), ".ploy");
2603
+ var CONFIG_FILE = join(CONFIG_DIR, "config.json");
2604
+ function getConfigFile() {
2605
+ return CONFIG_FILE;
2606
+ }
2607
+ async function ensureConfigDir() {
2608
+ if (!existsSync(CONFIG_DIR)) {
2609
+ await mkdir(CONFIG_DIR, { recursive: true });
2610
+ }
2611
+ }
2612
+ async function readConfig() {
2613
+ try {
2614
+ if (!existsSync(CONFIG_FILE)) {
2615
+ return {};
2616
+ }
2617
+ const content = await readFile(CONFIG_FILE, "utf-8");
2618
+ return JSON.parse(content);
2619
+ } catch (error2) {
2620
+ console.warn(`Warning: Could not read config file: ${error2}`);
2621
+ return {};
2622
+ }
2623
+ }
2624
+ async function writeConfig(config) {
2625
+ await ensureConfigDir();
2626
+ await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
2627
+ await chmod(CONFIG_FILE, 384);
2628
+ }
2629
+ async function updateConfig(updates) {
2630
+ const config = await readConfig();
2631
+ const newConfig = { ...config, ...updates };
2632
+ await writeConfig(newConfig);
2633
+ return newConfig;
2634
+ }
2635
+ async function getAuthToken() {
2636
+ const envToken = process.env.PLOY_API_TOKEN || process.env.PLOY_API_KEY;
2637
+ if (envToken) {
2638
+ return envToken;
2639
+ }
2640
+ const config = await readConfig();
2641
+ if (config.auth?.accessToken) {
2642
+ if (config.auth.expiresAt) {
2643
+ const expiresAt = new Date(config.auth.expiresAt);
2644
+ if (expiresAt <= /* @__PURE__ */ new Date()) {
2645
+ console.warn(
2646
+ "Warning: Stored access token has expired. Please run 'ploy auth login' again."
2647
+ );
2648
+ await clearAuthToken();
2649
+ return void 0;
2650
+ }
2651
+ }
2652
+ return config.auth.accessToken;
2653
+ }
2654
+ return void 0;
2655
+ }
2656
+ async function setAuthToken(accessToken, expiresInSeconds) {
2657
+ const auth = {
2658
+ accessToken
2659
+ };
2660
+ if (expiresInSeconds) {
2661
+ const expiresAt = new Date(Date.now() + expiresInSeconds * 1e3);
2662
+ auth.expiresAt = expiresAt.toISOString();
2663
+ }
2664
+ await updateConfig({ auth });
2665
+ }
2666
+ async function clearAuthToken() {
2667
+ const config = await readConfig();
2668
+ delete config.auth;
2669
+ await writeConfig(config);
2670
+ }
2671
+ async function getApiUrl() {
2672
+ if (process.env.PLOY_API_URL) {
2673
+ return process.env.PLOY_API_URL;
2674
+ }
2675
+ const config = await readConfig();
2676
+ if (config.apiUrl) {
2677
+ return config.apiUrl;
2678
+ }
2679
+ return "https://api.meetploy.com";
2680
+ }
2681
+
2682
+ // src/lib/api-client.ts
2683
+ var ApiClientError = class extends Error {
2684
+ constructor(message, status, code) {
2685
+ super(message);
2686
+ this.status = status;
2687
+ this.code = code;
2688
+ this.name = "ApiClientError";
2689
+ }
2690
+ };
2691
+ async function requireAuthToken() {
2692
+ const token = await getAuthToken();
2693
+ if (!token) {
2694
+ console.error("Error: Authentication required");
2695
+ console.error("");
2696
+ console.error("Please authenticate using one of the following methods:");
2697
+ console.error("");
2698
+ console.error(" 1. Run 'ploy auth login' to authenticate via browser");
2699
+ console.error("");
2700
+ console.error(" 2. Set an environment variable:");
2701
+ console.error(" PLOY_API_TOKEN - Your Ploy API token");
2702
+ console.error(" PLOY_API_KEY - Your Ploy API key");
2703
+ console.error("");
2704
+ process.exit(1);
2705
+ }
2706
+ return token;
2707
+ }
2708
+ var clientInstance = null;
2709
+ async function getClient() {
2710
+ if (!clientInstance) {
2711
+ const baseUrl = await getApiUrl();
2712
+ const token = await requireAuthToken();
2713
+ clientInstance = createClient({
2714
+ baseUrl,
2715
+ headers: {
2716
+ Authorization: `Bearer ${token}`,
2717
+ "User-Agent": "ploy-cli/1.0.0"
2718
+ }
2719
+ });
2720
+ }
2721
+ return clientInstance;
2722
+ }
2723
+ async function getApiClient() {
2724
+ return await getClient();
2725
+ }
2726
+
2727
+ // src/commands/api/db.ts
2728
+ function printDbHelp() {
2729
+ console.log("Usage: ploy api db <action> [options]");
2730
+ console.log("");
2731
+ console.log("Database operations via the Ploy API");
2732
+ console.log("");
2733
+ console.log("Actions:");
2734
+ console.log(" tables <database> List tables in a database");
2735
+ console.log(" analytics <database> View database analytics");
2736
+ console.log(" list List all databases (requires --org)");
2737
+ console.log("");
2738
+ console.log("Options:");
2739
+ console.log(
2740
+ " --org <id> Organization ID (required for most actions)"
2741
+ );
2742
+ console.log(" --project <id> Project ID (optional filter for list)");
2743
+ console.log(
2744
+ " --period <period> Analytics period: 24h, 7d, 30d (default: 24h)"
2745
+ );
2746
+ console.log(" --limit <n> Limit number of results (default: 20)");
2747
+ console.log(
2748
+ " --schema Show table schema details (for tables action)"
2749
+ );
2750
+ console.log(" -h, --help Show this help message");
2751
+ console.log("");
2752
+ console.log("Examples:");
2753
+ console.log(" ploy api db tables my-database --org org123");
2754
+ console.log(" ploy api db tables my-database --org org123 --schema");
2755
+ console.log(" ploy api db analytics my-database --org org123");
2756
+ console.log(" ploy api db analytics my-database --org org123 --period 7d");
2757
+ console.log(" ploy api db list --org org123");
2758
+ }
2759
+ function parseDbArgs(args2) {
2760
+ const options = {};
2761
+ if (args2.length === 0 || args2[0] === "-h" || args2[0] === "--help") {
2762
+ printDbHelp();
2763
+ process.exit(0);
2764
+ }
2765
+ options.action = args2[0];
2766
+ if (args2[1] && !args2[1].startsWith("-")) {
2767
+ options.databaseName = args2[1];
2768
+ }
2769
+ for (let i = 1; i < args2.length; i++) {
2770
+ const arg = args2[i];
2771
+ if (arg === "--org") {
2772
+ const nextArg = args2[i + 1];
2773
+ if (!nextArg || nextArg.startsWith("-")) {
2774
+ console.error("Error: --org requires a value");
2775
+ process.exit(1);
2776
+ }
2777
+ options.orgId = nextArg;
2778
+ i++;
2779
+ } else if (arg === "--project") {
2780
+ const nextArg = args2[i + 1];
2781
+ if (!nextArg || nextArg.startsWith("-")) {
2782
+ console.error("Error: --project requires a value");
2783
+ process.exit(1);
2784
+ }
2785
+ options.projectId = nextArg;
2786
+ i++;
2787
+ } else if (arg === "--period") {
2788
+ const nextArg = args2[i + 1];
2789
+ if (!nextArg || nextArg.startsWith("-")) {
2790
+ console.error("Error: --period requires a value");
2791
+ process.exit(1);
2792
+ }
2793
+ const validPeriods = ["24h", "7d", "30d"];
2794
+ if (!validPeriods.includes(nextArg)) {
2795
+ console.error(
2796
+ `Error: Invalid period. Valid options: ${validPeriods.join(", ")}`
2797
+ );
2798
+ process.exit(1);
2799
+ }
2800
+ options.period = nextArg;
2801
+ i++;
2802
+ } else if (arg === "--limit") {
2803
+ const nextArg = args2[i + 1];
2804
+ if (!nextArg || nextArg.startsWith("-")) {
2805
+ console.error("Error: --limit requires a number");
2806
+ process.exit(1);
2807
+ }
2808
+ const limit = parseInt(nextArg, 10);
2809
+ if (Number.isNaN(limit)) {
2810
+ console.error("Error: --limit must be a valid number");
2811
+ process.exit(1);
2812
+ }
2813
+ options.limit = limit;
2814
+ i++;
2815
+ } else if (arg === "--schema") {
2816
+ options.showSchema = true;
2817
+ } else if (arg === "-h" || arg === "--help") {
2818
+ printDbHelp();
2819
+ process.exit(0);
2820
+ }
2821
+ }
2822
+ return options;
2823
+ }
2824
+ async function listTables(options) {
2825
+ const { databaseName, orgId, showSchema } = options;
2826
+ if (!databaseName) {
2827
+ console.error("Error: Database name/ID is required");
2828
+ console.error("");
2829
+ console.error("Usage: ploy api db tables <database>");
2830
+ process.exit(1);
2831
+ }
2832
+ if (!orgId) {
2833
+ console.error("Error: --org is required for database operations");
2834
+ console.error("");
2835
+ console.error("Usage: ploy api db tables <database> --org <org-id>");
2836
+ process.exit(1);
2837
+ }
2838
+ console.log(`Fetching tables for database: ${databaseName}...`);
2839
+ console.log("");
2840
+ try {
2841
+ const client = await getApiClient();
2842
+ const { data, error: error2, response } = await client.GET(
2843
+ "/projects/org/{orgId}/db/{databaseId}/tables",
2844
+ {
2845
+ params: { path: { orgId, databaseId: databaseName } }
2846
+ }
2847
+ );
2848
+ if (error2 || !data) {
2849
+ throw new ApiClientError(
2850
+ error2?.message || "Failed to fetch tables",
2851
+ response?.status || 500
2852
+ );
2853
+ }
2854
+ const tablesResult = data;
2855
+ if (!tablesResult.tables || tablesResult.tables.length === 0) {
2856
+ console.log("No tables found in this database.");
2857
+ return;
2858
+ }
2859
+ console.log("Tables");
2860
+ console.log("======");
2861
+ console.log("");
2862
+ if (showSchema) {
2863
+ const {
2864
+ data: schemaData,
2865
+ error: schemaError,
2866
+ response: schemaResponse
2867
+ } = await client.GET("/projects/org/{orgId}/db/{databaseId}/schema", {
2868
+ params: { path: { orgId, databaseId: databaseName } }
2869
+ });
2870
+ if (schemaError || !schemaData) {
2871
+ throw new ApiClientError(
2872
+ schemaError?.message || "Failed to fetch schema",
2873
+ schemaResponse?.status || 500
2874
+ );
2875
+ }
2876
+ const schemaResult = schemaData;
2877
+ for (const table of schemaResult.tables) {
2878
+ console.log(`Table: ${table.name}`);
2879
+ console.log("-".repeat(40));
2880
+ console.log(
2881
+ " " + "COLUMN".padEnd(20) + "TYPE".padEnd(15) + "NULLABLE".padEnd(10) + "PK"
2882
+ );
2883
+ console.log(" " + "-".repeat(50));
2884
+ for (const col of table.columns) {
2885
+ const nullable = col.notNull ? "NO" : "YES";
2886
+ const pk = col.primaryKey ? "YES" : "";
2887
+ console.log(
2888
+ " " + col.name.padEnd(20) + col.type.padEnd(15) + nullable.padEnd(10) + pk
2889
+ );
2890
+ }
2891
+ console.log("");
2892
+ }
2893
+ } else {
2894
+ console.log("NAME".padEnd(30) + "ROWS".padEnd(12) + "SIZE");
2895
+ console.log("-".repeat(55));
2896
+ for (const table of tablesResult.tables) {
2897
+ console.log(table.name.padEnd(30) + "-".padEnd(12) + "-");
2898
+ }
2899
+ }
2900
+ console.log("");
2901
+ } catch (error2) {
2902
+ if (error2 instanceof ApiClientError) {
2903
+ console.error(`Error: ${error2.message}`);
2904
+ if (error2.status === 404) {
2905
+ console.error(
2906
+ "The database was not found. Check the database name/ID."
2907
+ );
2908
+ }
2909
+ } else {
2910
+ throw error2;
2911
+ }
2912
+ process.exit(1);
2913
+ }
2914
+ }
2915
+ async function viewAnalytics(options) {
2916
+ const { databaseName, orgId, period = "24h", limit = 10 } = options;
2917
+ if (!databaseName) {
2918
+ console.error("Error: Database name/ID is required");
2919
+ console.error("");
2920
+ console.error("Usage: ploy api db analytics <database>");
2921
+ process.exit(1);
2922
+ }
2923
+ if (!orgId) {
2924
+ console.error("Error: --org is required for database operations");
2925
+ console.error("");
2926
+ console.error("Usage: ploy api db analytics <database> --org <org-id>");
2927
+ process.exit(1);
2928
+ }
2929
+ console.log(
2930
+ `Fetching analytics for database: ${databaseName} (${period})...`
2931
+ );
2932
+ console.log("");
2933
+ try {
2934
+ const client = await getApiClient();
2935
+ const { data, error: error2, response } = await client.GET(
2936
+ "/projects/org/{orgId}/db/{databaseId}/insights",
2937
+ {
2938
+ params: {
2939
+ path: { orgId, databaseId: databaseName },
2940
+ query: {
2941
+ timeRange: period,
2942
+ limit: limit.toString()
2943
+ }
2944
+ }
2945
+ }
2946
+ );
2947
+ if (error2 || !data) {
2948
+ throw new ApiClientError(
2949
+ error2?.message || "Failed to fetch analytics",
2950
+ response?.status || 500
2951
+ );
2952
+ }
2953
+ const insightsResult = data;
2954
+ console.log("Database Analytics");
2955
+ console.log("==================");
2956
+ console.log("");
2957
+ console.log(` Period: ${insightsResult.timeRange}`);
2958
+ console.log(
2959
+ ` Total Queries: ${insightsResult.summary.totalQueries.toLocaleString()}`
2960
+ );
2961
+ console.log(
2962
+ ` Total Rows Read: ${insightsResult.summary.totalRowsRead.toLocaleString()}`
2963
+ );
2964
+ console.log(
2965
+ ` Total Rows Written: ${insightsResult.summary.totalRowsWritten.toLocaleString()}`
2966
+ );
2967
+ const avgDuration = insightsResult.summary.totalQueries > 0 ? insightsResult.summary.totalDurationMs / insightsResult.summary.totalQueries : 0;
2968
+ console.log(` Avg Query Duration: ${avgDuration.toFixed(2)}ms`);
2969
+ console.log("");
2970
+ if (insightsResult.topQueries && insightsResult.topQueries.length > 0) {
2971
+ console.log("Top Queries by Total Duration");
2972
+ console.log("-----------------------------");
2973
+ console.log("");
2974
+ for (let i = 0; i < insightsResult.topQueries.length; i++) {
2975
+ const insight = insightsResult.topQueries[i];
2976
+ console.log(`${i + 1}. ${truncateQuery(insight.queryText, 60)}`);
2977
+ console.log(` Calls: ${insight.totalCalls.toLocaleString()}`);
2978
+ console.log(` Avg Duration: ${insight.avgDurationMs.toFixed(2)}ms`);
2979
+ console.log(
2980
+ ` Total Duration: ${insight.totalDurationMs.toFixed(2)}ms`
2981
+ );
2982
+ console.log(` Rows Read: ${insight.totalRowsRead.toLocaleString()}`);
2983
+ console.log(
2984
+ ` Rows Written: ${insight.totalRowsWritten.toLocaleString()}`
2985
+ );
2986
+ console.log("");
2987
+ }
2988
+ }
2989
+ } catch (error2) {
2990
+ if (error2 instanceof ApiClientError) {
2991
+ console.error(`Error: ${error2.message}`);
2992
+ if (error2.status === 404) {
2993
+ console.error(
2994
+ "The database was not found. Check the database name/ID."
2995
+ );
2996
+ }
2997
+ } else {
2998
+ throw error2;
2999
+ }
3000
+ process.exit(1);
3001
+ }
3002
+ }
3003
+ async function listDatabases(options) {
3004
+ if (!options.orgId) {
3005
+ console.error("Error: --org is required for listing databases");
3006
+ console.error("");
3007
+ console.error("Usage: ploy api db list --org <org-id>");
3008
+ process.exit(1);
3009
+ }
3010
+ try {
3011
+ const client = await getApiClient();
3012
+ const { data, error: error2, response } = await client.GET(
3013
+ "/orgs/{id}/databases",
3014
+ {
3015
+ params: {
3016
+ path: { id: options.orgId },
3017
+ query: options.projectId ? { projectId: options.projectId } : void 0
3018
+ }
3019
+ }
3020
+ );
3021
+ if (error2 || !data) {
3022
+ throw new ApiClientError(
3023
+ error2?.message || "Failed to list databases",
3024
+ response?.status || 500
3025
+ );
3026
+ }
3027
+ const databases = data.databases;
3028
+ if (!databases || databases.length === 0) {
3029
+ console.log("No databases found.");
3030
+ return;
3031
+ }
3032
+ console.log("");
3033
+ console.log("Databases");
3034
+ console.log("=========");
3035
+ console.log("");
3036
+ console.log(
3037
+ "ID".padEnd(26) + "NAME".padEnd(25) + "REGION".padEnd(15) + "CREATED"
3038
+ );
3039
+ console.log("-".repeat(80));
3040
+ for (const db of databases) {
3041
+ const id = db.id.substring(0, Math.min(24, db.id.length));
3042
+ const name = db.name.substring(0, Math.min(23, db.name.length)).padEnd(25);
3043
+ const region = (db.region || "-").padEnd(15);
3044
+ const created = db.createdAt ? new Date(db.createdAt).toLocaleDateString() : "-";
3045
+ console.log(`${id} ${name}${region}${created}`);
3046
+ }
3047
+ console.log("");
3048
+ } catch (error2) {
3049
+ if (error2 instanceof ApiClientError) {
3050
+ console.error(`Error: ${error2.message}`);
3051
+ } else {
3052
+ throw error2;
3053
+ }
3054
+ process.exit(1);
3055
+ }
3056
+ }
3057
+ function truncateQuery(query, maxLength) {
3058
+ const normalized = query.replace(/\s+/g, " ").trim();
3059
+ if (normalized.length <= maxLength) {
3060
+ return normalized;
3061
+ }
3062
+ return normalized.substring(0, maxLength - 3) + "...";
3063
+ }
3064
+ async function dbCommand(options) {
3065
+ const { action } = options;
3066
+ if (!action) {
3067
+ printDbHelp();
3068
+ process.exit(1);
3069
+ }
3070
+ switch (action) {
3071
+ case "tables":
3072
+ await listTables(options);
3073
+ break;
3074
+ case "analytics":
3075
+ await viewAnalytics(options);
3076
+ break;
3077
+ case "list":
3078
+ await listDatabases(options);
3079
+ break;
3080
+ case "-h":
3081
+ case "--help":
3082
+ printDbHelp();
3083
+ process.exit(0);
3084
+ break;
3085
+ default:
3086
+ console.error(`Unknown action: ${action}`);
3087
+ console.error("");
3088
+ console.error("Available actions: tables, analytics, list");
3089
+ console.error("");
3090
+ console.error("Run 'ploy api db --help' for more information");
3091
+ process.exit(1);
3092
+ }
3093
+ }
3094
+
3095
+ // src/commands/api/deployment.ts
3096
+ function printDeploymentHelp() {
3097
+ console.log("Usage: ploy api deployment <action> [options]");
3098
+ console.log("");
3099
+ console.log("Manage deployments via the Ploy API");
3100
+ console.log("");
3101
+ console.log("Actions:");
3102
+ console.log(" retry <id> Retry a failed deployment");
3103
+ console.log(" logs <id> View deployment build logs");
3104
+ console.log(" view <id> View deployment details");
3105
+ console.log(" list List deployments (requires --project)");
3106
+ console.log("");
3107
+ console.log("Options:");
3108
+ console.log(" --branch <name> Filter by branch name (for view/list)");
3109
+ console.log(" --commit <sha> Filter by commit SHA (for view)");
3110
+ console.log(" --project <id> Project ID (required for list)");
3111
+ console.log(" --limit <n> Limit number of results (default: 20)");
3112
+ console.log(" -h, --help Show this help message");
3113
+ console.log("");
3114
+ console.log("Examples:");
3115
+ console.log(" ploy api deployment retry abc123");
3116
+ console.log(" ploy api deployment logs abc123");
3117
+ console.log(" ploy api deployment view abc123");
3118
+ console.log(" ploy api deployment view abc123 --branch main");
3119
+ console.log(" ploy api deployment list --project proj123");
3120
+ }
3121
+ function parseDeploymentArgs(args2) {
3122
+ const options = {};
3123
+ if (args2.length === 0 || args2[0] === "-h" || args2[0] === "--help") {
3124
+ printDeploymentHelp();
3125
+ process.exit(0);
3126
+ }
3127
+ options.action = args2[0];
3128
+ if (args2[1] && !args2[1].startsWith("-")) {
3129
+ options.deploymentId = args2[1];
3130
+ }
3131
+ for (let i = 1; i < args2.length; i++) {
3132
+ const arg = args2[i];
3133
+ if (arg === "--branch") {
3134
+ const nextArg = args2[i + 1];
3135
+ if (!nextArg || nextArg.startsWith("-")) {
3136
+ console.error("Error: --branch requires a value");
3137
+ process.exit(1);
3138
+ }
3139
+ options.branch = nextArg;
3140
+ i++;
3141
+ } else if (arg === "--commit") {
3142
+ const nextArg = args2[i + 1];
3143
+ if (!nextArg || nextArg.startsWith("-")) {
3144
+ console.error("Error: --commit requires a value");
3145
+ process.exit(1);
3146
+ }
3147
+ options.commitSha = nextArg;
3148
+ i++;
3149
+ } else if (arg === "--project") {
3150
+ const nextArg = args2[i + 1];
3151
+ if (!nextArg || nextArg.startsWith("-")) {
3152
+ console.error("Error: --project requires a value");
3153
+ process.exit(1);
3154
+ }
3155
+ options.projectId = nextArg;
3156
+ i++;
3157
+ } else if (arg === "--limit") {
3158
+ const nextArg = args2[i + 1];
3159
+ if (!nextArg || nextArg.startsWith("-")) {
3160
+ console.error("Error: --limit requires a number");
3161
+ process.exit(1);
3162
+ }
3163
+ const limit = parseInt(nextArg, 10);
3164
+ if (Number.isNaN(limit)) {
3165
+ console.error("Error: --limit requires a valid number");
3166
+ process.exit(1);
3167
+ }
3168
+ options.limit = limit;
3169
+ i++;
3170
+ } else if (arg === "-f" || arg === "--follow") {
3171
+ options.follow = true;
3172
+ } else if (arg === "-h" || arg === "--help") {
3173
+ printDeploymentHelp();
3174
+ process.exit(0);
3175
+ }
3176
+ }
3177
+ return options;
3178
+ }
3179
+ async function retryDeployment(deploymentId) {
3180
+ console.log(`Retrying deployment ${deploymentId}...`);
3181
+ try {
3182
+ const client = await getApiClient();
3183
+ const { data, error: error2, response } = await client.POST(
3184
+ "/deployments/{deploymentId}/retry",
3185
+ {
3186
+ params: { path: { deploymentId } }
3187
+ }
3188
+ );
3189
+ if (error2 || !data) {
3190
+ throw new ApiClientError(
3191
+ error2?.message || "Failed to retry deployment",
3192
+ response?.status || 500
3193
+ );
3194
+ }
3195
+ const retryResult = data;
3196
+ console.log("");
3197
+ console.log("Deployment retry initiated successfully");
3198
+ console.log("");
3199
+ console.log(` New Deployment ID: ${retryResult.newDeploymentId}`);
3200
+ console.log(` Message: ${retryResult.message}`);
3201
+ } catch (error2) {
3202
+ if (error2 instanceof ApiClientError) {
3203
+ console.error(`Error: ${error2.message}`);
3204
+ if (error2.status === 404) {
3205
+ console.error("The deployment was not found. Check the deployment ID.");
3206
+ } else if (error2.status === 400) {
3207
+ console.error(
3208
+ "The deployment cannot be retried (may not be in a failed state)."
3209
+ );
3210
+ }
3211
+ } else {
3212
+ throw error2;
3213
+ }
3214
+ process.exit(1);
3215
+ }
3216
+ }
3217
+ async function viewDeploymentLogs(deploymentId, _options) {
3218
+ console.log(`Fetching logs for deployment ${deploymentId}...`);
3219
+ console.log("");
3220
+ try {
3221
+ const client = await getApiClient();
3222
+ const { data, error: error2, response } = await client.GET(
3223
+ "/deployments/{deploymentId}/logs",
3224
+ {
3225
+ params: { path: { deploymentId } }
3226
+ }
3227
+ );
3228
+ if (error2 || !data) {
3229
+ throw new ApiClientError(
3230
+ error2?.message || "Failed to fetch logs",
3231
+ response?.status || 500
3232
+ );
3233
+ }
3234
+ const logsResult = data;
3235
+ if (!logsResult.logs || logsResult.logs.length === 0) {
3236
+ console.log("No logs available for this deployment.");
3237
+ return;
3238
+ }
3239
+ for (const log2 of logsResult.logs) {
3240
+ const timestamp2 = new Date(log2.createdAt).toLocaleTimeString();
3241
+ const level = log2.level.toUpperCase().padEnd(5);
3242
+ const prefix = log2.command ? `[${log2.command}] ` : "";
3243
+ console.log(`${timestamp2} ${level} ${prefix}${log2.message}`);
3244
+ }
3245
+ } catch (error2) {
3246
+ if (error2 instanceof ApiClientError) {
3247
+ console.error(`Error: ${error2.message}`);
3248
+ if (error2.status === 404) {
3249
+ console.error("The deployment was not found. Check the deployment ID.");
3250
+ }
3251
+ } else {
3252
+ throw error2;
3253
+ }
3254
+ process.exit(1);
3255
+ }
3256
+ }
3257
+ async function viewDeployment(deploymentId, _options) {
3258
+ try {
3259
+ const client = await getApiClient();
3260
+ const { data, error: error2, response } = await client.GET(
3261
+ "/deployments/{deploymentId}",
3262
+ {
3263
+ params: { path: { deploymentId } }
3264
+ }
3265
+ );
3266
+ if (error2 || !data) {
3267
+ throw new ApiClientError(
3268
+ error2?.message || "Failed to fetch deployment",
3269
+ response?.status || 500
3270
+ );
3271
+ }
3272
+ const deployment = data.deployment;
3273
+ console.log("");
3274
+ console.log("Deployment Details");
3275
+ console.log("==================");
3276
+ console.log("");
3277
+ console.log(` ID: ${deployment.id}`);
3278
+ console.log(` Status: ${formatStatus(deployment.status)}`);
3279
+ if (deployment.project) {
3280
+ console.log(` Project: ${deployment.project.name}`);
3281
+ }
3282
+ if (deployment.repository) {
3283
+ console.log(` Repository: ${deployment.repository.fullName}`);
3284
+ }
3285
+ if (deployment.branch) {
3286
+ console.log(` Branch: ${deployment.branch}`);
3287
+ }
3288
+ if (deployment.commitSha) {
3289
+ const commitDisplay = deployment.commitSha.length >= 8 ? deployment.commitSha.substring(0, 8) : deployment.commitSha;
3290
+ console.log(` Commit: ${commitDisplay}`);
3291
+ }
3292
+ if (deployment.commitMessage) {
3293
+ console.log(` Message: ${deployment.commitMessage}`);
3294
+ }
3295
+ if (deployment.url) {
3296
+ console.log(` URL: ${deployment.url}`);
3297
+ }
3298
+ console.log(
3299
+ ` Created: ${new Date(deployment.createdAt).toLocaleString()}`
3300
+ );
3301
+ console.log(
3302
+ ` Updated: ${new Date(deployment.updatedAt).toLocaleString()}`
3303
+ );
3304
+ console.log("");
3305
+ } catch (error2) {
3306
+ if (error2 instanceof ApiClientError) {
3307
+ console.error(`Error: ${error2.message}`);
3308
+ if (error2.status === 404) {
3309
+ console.error("The deployment was not found. Check the deployment ID.");
3310
+ }
3311
+ } else {
3312
+ throw error2;
3313
+ }
3314
+ process.exit(1);
3315
+ }
3316
+ }
3317
+ async function listDeployments(options) {
3318
+ if (!options.projectId) {
3319
+ console.error("Error: --project is required for listing deployments");
3320
+ console.error("");
3321
+ console.error("Usage: ploy api deployment list --project <project-id>");
3322
+ process.exit(1);
3323
+ }
3324
+ try {
3325
+ const client = await getApiClient();
3326
+ const { data, error: error2, response } = await client.GET("/deployments", {
3327
+ params: {
3328
+ query: {
3329
+ projectId: options.projectId,
3330
+ branch: options.branch,
3331
+ limit: options.limit?.toString() || "20"
3332
+ }
3333
+ }
3334
+ });
3335
+ if (error2 || !data) {
3336
+ throw new ApiClientError(
3337
+ error2?.message || "Failed to list deployments",
3338
+ response?.status || 500
3339
+ );
3340
+ }
3341
+ const deployments = data.deployments;
3342
+ if (!deployments || deployments.length === 0) {
3343
+ console.log("No deployments found.");
3344
+ return;
3345
+ }
3346
+ console.log("");
3347
+ console.log("Deployments");
3348
+ console.log("===========");
3349
+ console.log("");
3350
+ console.log(
3351
+ "ID".padEnd(12) + "STATUS".padEnd(12) + "BRANCH".padEnd(20) + "COMMIT".padEnd(10) + "CREATED"
3352
+ );
3353
+ console.log("-".repeat(70));
3354
+ for (const d of deployments) {
3355
+ const id = d.id.substring(0, Math.min(10, d.id.length));
3356
+ const status = d.status.padEnd(10);
3357
+ const branchSource = d.branch || "-";
3358
+ const branch = branchSource.substring(0, Math.min(18, branchSource.length)).padEnd(20);
3359
+ const commit = d.commitSha ? d.commitSha.substring(0, Math.min(8, d.commitSha.length)) : "-";
3360
+ const created = new Date(d.createdAt).toLocaleDateString();
3361
+ console.log(`${id} ${status} ${branch}${commit.padEnd(10)}${created}`);
3362
+ }
3363
+ console.log("");
3364
+ } catch (error2) {
3365
+ if (error2 instanceof ApiClientError) {
3366
+ console.error(`Error: ${error2.message}`);
3367
+ } else {
3368
+ throw error2;
3369
+ }
3370
+ process.exit(1);
3371
+ }
3372
+ }
3373
+ function formatStatus(status) {
3374
+ if (!process.stdout.isTTY) {
3375
+ return status;
3376
+ }
3377
+ const statusColors = {
3378
+ success: "\x1B[32msuccess\x1B[0m",
3379
+ // green
3380
+ failure: "\x1B[31mfailure\x1B[0m",
3381
+ // red
3382
+ pending: "\x1B[33mpending\x1B[0m",
3383
+ // yellow
3384
+ building: "\x1B[34mbuilding\x1B[0m",
3385
+ // blue
3386
+ syncing: "\x1B[36msyncing\x1B[0m",
3387
+ // cyan
3388
+ uploading: "\x1B[36muploading\x1B[0m",
3389
+ // cyan
3390
+ timeout: "\x1B[31mtimeout\x1B[0m"
3391
+ // red
3392
+ };
3393
+ return statusColors[status] || status;
3394
+ }
3395
+ async function deploymentCommand(options) {
3396
+ const { action, deploymentId } = options;
3397
+ if (!action) {
3398
+ printDeploymentHelp();
3399
+ process.exit(1);
3400
+ }
3401
+ switch (action) {
3402
+ case "retry":
3403
+ if (!deploymentId) {
3404
+ console.error("Error: Deployment ID is required");
3405
+ console.error("");
3406
+ console.error("Usage: ploy api deployment retry <deployment-id>");
3407
+ process.exit(1);
3408
+ }
3409
+ await retryDeployment(deploymentId);
3410
+ break;
3411
+ case "logs":
3412
+ if (!deploymentId) {
3413
+ console.error("Error: Deployment ID is required");
3414
+ console.error("");
3415
+ console.error("Usage: ploy api deployment logs <deployment-id>");
3416
+ process.exit(1);
3417
+ }
3418
+ await viewDeploymentLogs(deploymentId);
3419
+ break;
3420
+ case "view":
3421
+ if (!deploymentId) {
3422
+ console.error("Error: Deployment ID is required");
3423
+ console.error("");
3424
+ console.error("Usage: ploy api deployment view <deployment-id>");
3425
+ process.exit(1);
3426
+ }
3427
+ await viewDeployment(deploymentId);
3428
+ break;
3429
+ case "list":
3430
+ await listDeployments(options);
3431
+ break;
3432
+ case "-h":
3433
+ case "--help":
3434
+ printDeploymentHelp();
3435
+ process.exit(0);
3436
+ break;
3437
+ default:
3438
+ console.error(`Unknown action: ${action}`);
3439
+ console.error("");
3440
+ console.error("Available actions: retry, logs, view, list");
3441
+ console.error("");
3442
+ console.error("Run 'ploy api deployment --help' for more information");
3443
+ process.exit(1);
3444
+ }
3445
+ }
3446
+
3447
+ // src/commands/api/index.ts
3448
+ function printApiHelp() {
3449
+ console.log("Usage: ploy api <subcommand> [options]");
3450
+ console.log("");
3451
+ console.log("Interact with the Ploy API from the command line");
3452
+ console.log("");
3453
+ console.log("Subcommands:");
3454
+ console.log(" deployment Manage deployments (retry, logs, view)");
3455
+ console.log(" db Database operations (tables, analytics)");
3456
+ console.log("");
3457
+ console.log("Options:");
3458
+ console.log(" -h, --help Show this help message");
3459
+ console.log("");
3460
+ console.log("Authentication:");
3461
+ console.log(
3462
+ " Run 'ploy auth login' to authenticate via browser (recommended)"
3463
+ );
3464
+ console.log("");
3465
+ console.log(" Or set environment variables:");
3466
+ console.log(" PLOY_API_TOKEN Your Ploy API token");
3467
+ console.log(
3468
+ " PLOY_API_URL API base URL (default: https://api.meetploy.com)"
3469
+ );
3470
+ console.log("");
3471
+ console.log("Examples:");
3472
+ console.log(" ploy api deployment retry abc123");
3473
+ console.log(" ploy api deployment logs abc123");
3474
+ console.log(" ploy api deployment view abc123 --branch main");
3475
+ console.log(" ploy api db tables my-database");
3476
+ console.log(" ploy api db analytics my-database");
3477
+ console.log("");
3478
+ console.log(
3479
+ "Run 'ploy api <subcommand> --help' for subcommand-specific help"
3480
+ );
3481
+ }
3482
+ function parseApiArgs(args2) {
3483
+ const options = {
3484
+ subArgs: []
3485
+ };
3486
+ if (args2.length === 0 || args2[0] === "-h" || args2[0] === "--help") {
3487
+ printApiHelp();
3488
+ process.exit(0);
3489
+ }
3490
+ options.subcommand = args2[0];
3491
+ options.subArgs = args2.slice(1);
3492
+ return options;
3493
+ }
3494
+ async function apiCommand(options) {
3495
+ const { subcommand, subArgs } = options;
3496
+ if (!subcommand) {
3497
+ printApiHelp();
3498
+ process.exit(1);
3499
+ }
3500
+ switch (subcommand) {
3501
+ case "deployment":
3502
+ case "deploy": {
3503
+ const deploymentOptions = parseDeploymentArgs(subArgs);
3504
+ await deploymentCommand(deploymentOptions);
3505
+ break;
3506
+ }
3507
+ case "db":
3508
+ case "database": {
3509
+ const dbOptions = parseDbArgs(subArgs);
3510
+ await dbCommand(dbOptions);
3511
+ break;
3512
+ }
3513
+ case "-h":
3514
+ case "--help":
3515
+ printApiHelp();
3516
+ process.exit(0);
3517
+ break;
3518
+ default:
3519
+ console.error(`Unknown subcommand: ${subcommand}`);
3520
+ console.error("");
3521
+ console.error("Available subcommands:");
3522
+ console.error(" deployment Manage deployments");
3523
+ console.error(" db Database operations");
3524
+ console.error("");
3525
+ console.error("Run 'ploy api --help' for more information");
3526
+ process.exit(1);
3527
+ }
3528
+ }
3529
+ var OAUTH_CLIENT_ID = "ploy-cli";
3530
+ var CALLBACK_PORT = 9876;
3531
+ var CALLBACK_HOST = "127.0.0.1";
3532
+ function printLoginHelp() {
3533
+ console.log("Usage: ploy login [options]");
3534
+ console.log("");
3535
+ console.log("Authenticate with the Ploy API via browser");
3536
+ console.log("");
3537
+ console.log("Options:");
3538
+ console.log(
3539
+ " --api-url <url> API base URL (default: https://api.meetploy.com)"
3540
+ );
3541
+ console.log(" -h, --help Show this help message");
3542
+ console.log("");
3543
+ console.log("This command will:");
3544
+ console.log(" 1. Open your browser to authenticate");
3545
+ console.log(" 2. Store the access token in ~/.ploy/config.json");
3546
+ console.log("");
3547
+ console.log("Environment Variables:");
3548
+ console.log(" PLOY_API_URL API base URL (alternative to --api-url)");
3549
+ }
3550
+ function parseLoginArgs(args2) {
3551
+ const options = {};
3552
+ for (let i = 0; i < args2.length; i++) {
3553
+ const arg = args2[i];
3554
+ if (arg === "--api-url") {
3555
+ const nextArg = args2[i + 1];
3556
+ if (!nextArg || nextArg.startsWith("-")) {
3557
+ console.error("Error: --api-url requires a value");
3558
+ process.exit(1);
3559
+ }
3560
+ options.apiUrl = nextArg;
3561
+ i++;
3562
+ } else if (arg === "-h" || arg === "--help") {
3563
+ printLoginHelp();
3564
+ process.exit(0);
3565
+ }
3566
+ }
3567
+ return options;
3568
+ }
3569
+ function openBrowser(url) {
3570
+ const platform = process.platform;
3571
+ let command2;
3572
+ if (platform === "darwin") {
3573
+ command2 = `open "${url}"`;
3574
+ } else if (platform === "win32") {
3575
+ command2 = `start "" "${url}"`;
3576
+ } else {
3577
+ command2 = `xdg-open "${url}"`;
3578
+ }
3579
+ exec(command2, (error2) => {
3580
+ if (error2) {
3581
+ console.log("");
3582
+ console.log("Could not open browser automatically.");
3583
+ console.log("Please open the following URL manually:");
3584
+ console.log("");
3585
+ console.log(` ${url}`);
3586
+ console.log("");
3587
+ }
3588
+ });
3589
+ }
3590
+ function generateState() {
3591
+ return randomBytes(16).toString("hex");
3592
+ }
3593
+ async function exchangeCodeForToken(apiUrl, code, redirectUri) {
3594
+ const tokenUrl = `${apiUrl}/oauth/token`;
3595
+ const response = await fetch(tokenUrl, {
3596
+ method: "POST",
3597
+ headers: {
3598
+ "Content-Type": "application/json"
3599
+ },
3600
+ body: JSON.stringify({
3601
+ grant_type: "authorization_code",
3602
+ code,
3603
+ redirect_uri: redirectUri,
3604
+ client_id: OAUTH_CLIENT_ID
3605
+ // Note: client_secret may not be needed for public clients
3606
+ })
3607
+ });
3608
+ if (!response.ok) {
3609
+ const errorBody = await response.text();
3610
+ throw new Error(`Token exchange failed: ${response.status} - ${errorBody}`);
3611
+ }
3612
+ return await response.json();
3613
+ }
3614
+ function createCallbackServer(expectedState, apiUrl) {
3615
+ let server = null;
3616
+ let resolveListening;
3617
+ let rejectListening;
3618
+ const waitForListening = new Promise((resolve, reject) => {
3619
+ resolveListening = resolve;
3620
+ rejectListening = reject;
3621
+ });
3622
+ const tokenPromise = new Promise((resolve, reject) => {
3623
+ const timeout = setTimeout(
3624
+ () => {
3625
+ if (server) {
3626
+ server.close();
3627
+ }
3628
+ reject(
3629
+ new Error("Login timeout - no response received within 5 minutes")
3630
+ );
3631
+ },
3632
+ 5 * 60 * 1e3
3633
+ );
3634
+ server = createServer(async (req, res) => {
3635
+ const reqUrl = new URL(
3636
+ req.url || "/",
3637
+ `http://${CALLBACK_HOST}:${CALLBACK_PORT}`
3638
+ );
3639
+ if (reqUrl.pathname !== "/callback") {
3640
+ res.writeHead(404);
3641
+ res.end("Not found");
3642
+ return;
3643
+ }
3644
+ const code = reqUrl.searchParams.get("code");
3645
+ const state = reqUrl.searchParams.get("state");
3646
+ const error2 = reqUrl.searchParams.get("error");
3647
+ const errorDescription = reqUrl.searchParams.get("error_description");
3648
+ if (error2) {
3649
+ clearTimeout(timeout);
3650
+ res.writeHead(400, { "Content-Type": "text/html" });
3651
+ res.end(`
3652
+ <!DOCTYPE html>
3653
+ <html>
3654
+ <head><title>Login Failed</title></head>
3655
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
3656
+ <h1>Login Failed</h1>
3657
+ <p>Error: ${error2}</p>
3658
+ <p>${errorDescription || ""}</p>
3659
+ <p>You can close this window.</p>
3660
+ </body>
3661
+ </html>
3662
+ `);
3663
+ server?.close();
3664
+ reject(new Error(`OAuth error: ${error2} - ${errorDescription || ""}`));
3665
+ return;
3666
+ }
3667
+ if (!code) {
3668
+ clearTimeout(timeout);
3669
+ res.writeHead(400, { "Content-Type": "text/html" });
3670
+ res.end(`
3671
+ <!DOCTYPE html>
3672
+ <html>
3673
+ <head><title>Login Failed</title></head>
3674
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
3675
+ <h1>Login Failed</h1>
3676
+ <p>No authorization code received.</p>
3677
+ <p>You can close this window.</p>
3678
+ </body>
3679
+ </html>
3680
+ `);
3681
+ server?.close();
3682
+ reject(new Error("No authorization code received"));
3683
+ return;
3684
+ }
3685
+ if (state !== expectedState) {
3686
+ clearTimeout(timeout);
3687
+ res.writeHead(400, { "Content-Type": "text/html" });
3688
+ res.end(`
3689
+ <!DOCTYPE html>
3690
+ <html>
3691
+ <head><title>Login Failed</title></head>
3692
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
3693
+ <h1>Login Failed</h1>
3694
+ <p>State mismatch - possible CSRF attack.</p>
3695
+ <p>You can close this window.</p>
3696
+ </body>
3697
+ </html>
3698
+ `);
3699
+ server?.close();
3700
+ reject(new Error("State mismatch - possible CSRF attack"));
3701
+ return;
3702
+ }
3703
+ try {
3704
+ const redirectUri = `http://${CALLBACK_HOST}:${CALLBACK_PORT}/callback`;
3705
+ const tokenResponse = await exchangeCodeForToken(
3706
+ apiUrl,
3707
+ code,
3708
+ redirectUri
3709
+ );
3710
+ clearTimeout(timeout);
3711
+ res.writeHead(200, { "Content-Type": "text/html" });
3712
+ res.end(`
3713
+ <!DOCTYPE html>
3714
+ <html>
3715
+ <head><title>Login Successful</title></head>
3716
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
3717
+ <h1>Login Successful!</h1>
3718
+ <p>You have been authenticated with Ploy.</p>
3719
+ <p>You can close this window and return to your terminal.</p>
3720
+ </body>
3721
+ </html>
3722
+ `);
3723
+ server?.close();
3724
+ resolve(tokenResponse);
3725
+ } catch (err) {
3726
+ clearTimeout(timeout);
3727
+ res.writeHead(500, { "Content-Type": "text/html" });
3728
+ res.end(`
3729
+ <!DOCTYPE html>
3730
+ <html>
3731
+ <head><title>Login Failed</title></head>
3732
+ <body style="font-family: system-ui; padding: 40px; text-align: center;">
3733
+ <h1>Login Failed</h1>
3734
+ <p>Failed to exchange authorization code for token.</p>
3735
+ <p>${err instanceof Error ? err.message : "Unknown error"}</p>
3736
+ <p>You can close this window.</p>
3737
+ </body>
3738
+ </html>
3739
+ `);
3740
+ server?.close();
3741
+ reject(err);
3742
+ }
3743
+ });
3744
+ server.on("error", (err) => {
3745
+ clearTimeout(timeout);
3746
+ if (err.code === "EADDRINUSE") {
3747
+ const portError = new Error(
3748
+ `Port ${CALLBACK_PORT} is already in use. Please close any other process using this port and try again.`
3749
+ );
3750
+ reject(portError);
3751
+ rejectListening(portError);
3752
+ } else {
3753
+ const serverError = new Error(
3754
+ `Failed to start callback server: ${err.message}`
3755
+ );
3756
+ reject(serverError);
3757
+ rejectListening(serverError);
3758
+ }
3759
+ });
3760
+ server.listen(CALLBACK_PORT, CALLBACK_HOST, () => {
3761
+ resolveListening();
3762
+ });
3763
+ });
3764
+ return { tokenPromise, waitForListening };
3765
+ }
3766
+ async function loginCommand(options = {}) {
3767
+ const apiUrl = options.apiUrl || await getApiUrl();
3768
+ const state = generateState();
3769
+ const redirectUri = `http://${CALLBACK_HOST}:${CALLBACK_PORT}/callback`;
3770
+ console.log("Starting login flow...");
3771
+ console.log("");
3772
+ const authUrl = new URL(`${apiUrl}/oauth/authorize`);
3773
+ authUrl.searchParams.set("response_type", "code");
3774
+ authUrl.searchParams.set("client_id", OAUTH_CLIENT_ID);
3775
+ authUrl.searchParams.set("redirect_uri", redirectUri);
3776
+ authUrl.searchParams.set("state", state);
3777
+ authUrl.searchParams.set("scope", "openid profile email");
3778
+ const { tokenPromise, waitForListening } = createCallbackServer(
3779
+ state,
3780
+ apiUrl
3781
+ );
3782
+ try {
3783
+ await waitForListening;
3784
+ } catch (error2) {
3785
+ console.error(
3786
+ `Error: ${error2 instanceof Error ? error2.message : "Failed to start server"}`
3787
+ );
3788
+ process.exit(1);
3789
+ }
3790
+ console.log("Opening browser for authentication...");
3791
+ openBrowser(authUrl.toString());
3792
+ console.log("");
3793
+ console.log("Waiting for authentication...");
3794
+ console.log("(Press Ctrl+C to cancel)");
3795
+ console.log("");
3796
+ try {
3797
+ const tokenResponse = await tokenPromise;
3798
+ await setAuthToken(tokenResponse.access_token, tokenResponse.expires_in);
3799
+ console.log("Login successful!");
3800
+ console.log("");
3801
+ console.log(`Credentials saved to: ${getConfigFile()}`);
3802
+ console.log("");
3803
+ console.log("You can now use 'ploy api' commands.");
3804
+ process.exit(0);
3805
+ } catch (error2) {
3806
+ console.error("");
3807
+ console.error(
3808
+ `Login failed: ${error2 instanceof Error ? error2.message : "Unknown error"}`
3809
+ );
3810
+ process.exit(1);
3811
+ }
3812
+ }
3813
+
3814
+ // src/commands/auth/logout.ts
3815
+ function printLogoutHelp() {
3816
+ console.log("Usage: ploy logout [options]");
3817
+ console.log("");
3818
+ console.log("Log out from the Ploy API");
3819
+ console.log("");
3820
+ console.log("Options:");
3821
+ console.log(" -h, --help Show this help message");
3822
+ console.log("");
3823
+ console.log(
3824
+ "This command removes stored credentials from ~/.ploy/config.json"
3825
+ );
3826
+ }
3827
+ function parseLogoutArgs(args2) {
3828
+ for (const arg of args2) {
3829
+ if (arg === "-h" || arg === "--help") {
3830
+ printLogoutHelp();
3831
+ process.exit(0);
3832
+ }
3833
+ }
3834
+ }
3835
+ async function logoutCommand() {
3836
+ const config = await readConfig();
3837
+ if (!config.auth?.accessToken) {
3838
+ console.log("You are not logged in.");
3839
+ return;
3840
+ }
3841
+ await clearAuthToken();
3842
+ console.log("Logged out successfully.");
3843
+ console.log("");
3844
+ console.log(`Credentials removed from: ${getConfigFile()}`);
3845
+ }
3846
+
3847
+ // src/commands/auth/index.ts
3848
+ function printAuthHelp() {
3849
+ console.log("Usage: ploy auth <subcommand> [options]");
3850
+ console.log("");
3851
+ console.log("Manage authentication with Ploy");
3852
+ console.log("");
3853
+ console.log("Subcommands:");
3854
+ console.log(" login Authenticate with Ploy via browser");
3855
+ console.log(" logout Clear stored credentials");
3856
+ console.log("");
3857
+ console.log("Options:");
3858
+ console.log(" -h, --help Show this help message");
3859
+ console.log("");
3860
+ console.log("Examples:");
3861
+ console.log(" ploy auth login");
3862
+ console.log(" ploy auth logout");
3863
+ console.log("");
3864
+ console.log(
3865
+ "Run 'ploy auth <subcommand> --help' for subcommand-specific help"
3866
+ );
3867
+ }
3868
+ function parseAuthArgs(args2) {
3869
+ const options = {
3870
+ subArgs: []
3871
+ };
3872
+ if (args2.length === 0 || args2[0] === "-h" || args2[0] === "--help") {
3873
+ printAuthHelp();
3874
+ process.exit(0);
3875
+ }
3876
+ options.subcommand = args2[0];
3877
+ options.subArgs = args2.slice(1);
3878
+ return options;
3879
+ }
3880
+ async function authCommand(options) {
3881
+ const { subcommand, subArgs } = options;
3882
+ if (!subcommand) {
3883
+ printAuthHelp();
3884
+ process.exit(1);
3885
+ }
3886
+ switch (subcommand) {
3887
+ case "login": {
3888
+ const loginOptions = parseLoginArgs(subArgs);
3889
+ await loginCommand(loginOptions);
3890
+ break;
3891
+ }
3892
+ case "logout": {
3893
+ parseLogoutArgs(subArgs);
3894
+ await logoutCommand();
3895
+ break;
3896
+ }
3897
+ case "-h":
3898
+ case "--help":
3899
+ printAuthHelp();
3900
+ process.exit(0);
3901
+ break;
3902
+ default:
3903
+ console.error(`Unknown subcommand: ${subcommand}`);
3904
+ console.error("");
3905
+ console.error("Available subcommands:");
3906
+ console.error(" login Authenticate with Ploy");
3907
+ console.error(" logout Clear stored credentials");
3908
+ console.error("");
3909
+ console.error("Run 'ploy auth --help' for more information");
3910
+ process.exit(1);
3911
+ }
3912
+ }
2600
3913
 
2601
3914
  // src/commands/build.ts
2602
3915
  init_cli();
@@ -2842,9 +4155,11 @@ var command = args[0];
2842
4155
  if (!command) {
2843
4156
  console.error("Usage: ploy <command>");
2844
4157
  console.error("\nAvailable commands:");
2845
- console.error(" build Build your Ploy project");
2846
- console.error(" dev Start development server");
2847
- console.error(" types Generate TypeScript types from ploy.yaml");
4158
+ console.error(" api Interact with the Ploy API");
4159
+ console.error(" auth Manage authentication (login, logout)");
4160
+ console.error(" build Build your Ploy project");
4161
+ console.error(" dev Start development server");
4162
+ console.error(" types Generate TypeScript types from ploy.yaml");
2848
4163
  process.exit(1);
2849
4164
  }
2850
4165
  function parseDevArgs(args2) {
@@ -2999,11 +4314,19 @@ if (command === "dev") {
2999
4314
  } else if (command === "build") {
3000
4315
  const options = parseBuildArgs(args.slice(1));
3001
4316
  await buildCommand(options);
4317
+ } else if (command === "api") {
4318
+ const options = parseApiArgs(args.slice(1));
4319
+ await apiCommand(options);
4320
+ } else if (command === "auth") {
4321
+ const options = parseAuthArgs(args.slice(1));
4322
+ await authCommand(options);
3002
4323
  } else {
3003
4324
  console.error(`Unknown command: ${command}`);
3004
4325
  console.error("\nAvailable commands:");
3005
- console.error(" build Build your Ploy project");
3006
- console.error(" dev Start development server");
3007
- console.error(" types Generate TypeScript types from ploy.yaml");
4326
+ console.error(" api Interact with the Ploy API");
4327
+ console.error(" auth Manage authentication (login, logout)");
4328
+ console.error(" build Build your Ploy project");
4329
+ console.error(" dev Start development server");
4330
+ console.error(" types Generate TypeScript types from ploy.yaml");
3008
4331
  process.exit(1);
3009
4332
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meetploy/cli",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,9 +28,10 @@
28
28
  "dist/"
29
29
  ],
30
30
  "scripts": {
31
- "build": "tsup",
31
+ "build": "tsc --noEmit && tsup",
32
32
  "dev": "tsup --watch",
33
33
  "format": "eslint --fix . && prettier --write .",
34
+ "generate": "openapi-typescript ../../apps/api/openapi.json -o ./src/lib/api/v1.ts",
34
35
  "lint": "eslint . && prettier --check ."
35
36
  },
36
37
  "dependencies": {
@@ -39,6 +40,7 @@
39
40
  "chokidar": "^4.0.0",
40
41
  "esbuild": "^0.24.0",
41
42
  "hono": "^4.7.0",
43
+ "openapi-fetch": "0.14.0",
42
44
  "workerd": "1.20260103.0",
43
45
  "yaml": "2.8.1"
44
46
  },
@@ -46,6 +48,7 @@
46
48
  "@meetploy/emulator": "*",
47
49
  "@meetploy/tools": "*",
48
50
  "@types/better-sqlite3": "^7.6.12",
51
+ "openapi-typescript": "7.8.0",
49
52
  "tsup": "^8.0.0"
50
53
  }
51
54
  }