@lizard-build/cli 0.1.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 (115) hide show
  1. package/dist/commands/add.d.ts +2 -0
  2. package/dist/commands/add.js +72 -0
  3. package/dist/commands/add.js.map +1 -0
  4. package/dist/commands/connect.d.ts +2 -0
  5. package/dist/commands/connect.js +117 -0
  6. package/dist/commands/connect.js.map +1 -0
  7. package/dist/commands/context.d.ts +2 -0
  8. package/dist/commands/context.js +71 -0
  9. package/dist/commands/context.js.map +1 -0
  10. package/dist/commands/deploy.d.ts +2 -0
  11. package/dist/commands/deploy.js +120 -0
  12. package/dist/commands/deploy.js.map +1 -0
  13. package/dist/commands/destroy.d.ts +2 -0
  14. package/dist/commands/destroy.js +51 -0
  15. package/dist/commands/destroy.js.map +1 -0
  16. package/dist/commands/git.d.ts +2 -0
  17. package/dist/commands/git.js +67 -0
  18. package/dist/commands/git.js.map +1 -0
  19. package/dist/commands/init.d.ts +2 -0
  20. package/dist/commands/init.js +107 -0
  21. package/dist/commands/init.js.map +1 -0
  22. package/dist/commands/link.d.ts +2 -0
  23. package/dist/commands/link.js +50 -0
  24. package/dist/commands/link.js.map +1 -0
  25. package/dist/commands/login.d.ts +7 -0
  26. package/dist/commands/login.js +123 -0
  27. package/dist/commands/login.js.map +1 -0
  28. package/dist/commands/logout.d.ts +2 -0
  29. package/dist/commands/logout.js +17 -0
  30. package/dist/commands/logout.js.map +1 -0
  31. package/dist/commands/logs.d.ts +2 -0
  32. package/dist/commands/logs.js +92 -0
  33. package/dist/commands/logs.js.map +1 -0
  34. package/dist/commands/open.d.ts +2 -0
  35. package/dist/commands/open.js +16 -0
  36. package/dist/commands/open.js.map +1 -0
  37. package/dist/commands/projects.d.ts +2 -0
  38. package/dist/commands/projects.js +28 -0
  39. package/dist/commands/projects.js.map +1 -0
  40. package/dist/commands/ps.d.ts +2 -0
  41. package/dist/commands/ps.js +52 -0
  42. package/dist/commands/ps.js.map +1 -0
  43. package/dist/commands/redeploy.d.ts +2 -0
  44. package/dist/commands/redeploy.js +69 -0
  45. package/dist/commands/redeploy.js.map +1 -0
  46. package/dist/commands/regions.d.ts +2 -0
  47. package/dist/commands/regions.js +23 -0
  48. package/dist/commands/regions.js.map +1 -0
  49. package/dist/commands/restart.d.ts +2 -0
  50. package/dist/commands/restart.js +21 -0
  51. package/dist/commands/restart.js.map +1 -0
  52. package/dist/commands/run.d.ts +2 -0
  53. package/dist/commands/run.js +33 -0
  54. package/dist/commands/run.js.map +1 -0
  55. package/dist/commands/secrets.d.ts +2 -0
  56. package/dist/commands/secrets.js +138 -0
  57. package/dist/commands/secrets.js.map +1 -0
  58. package/dist/commands/status.d.ts +2 -0
  59. package/dist/commands/status.js +51 -0
  60. package/dist/commands/status.js.map +1 -0
  61. package/dist/commands/update.d.ts +2 -0
  62. package/dist/commands/update.js +41 -0
  63. package/dist/commands/update.js.map +1 -0
  64. package/dist/commands/version.d.ts +2 -0
  65. package/dist/commands/version.js +37 -0
  66. package/dist/commands/version.js.map +1 -0
  67. package/dist/commands/whoami.d.ts +2 -0
  68. package/dist/commands/whoami.js +21 -0
  69. package/dist/commands/whoami.js.map +1 -0
  70. package/dist/index.d.ts +2 -0
  71. package/dist/index.js +151 -0
  72. package/dist/index.js.map +1 -0
  73. package/dist/lib/api.d.ts +19 -0
  74. package/dist/lib/api.js +114 -0
  75. package/dist/lib/api.js.map +1 -0
  76. package/dist/lib/auth.d.ts +23 -0
  77. package/dist/lib/auth.js +89 -0
  78. package/dist/lib/auth.js.map +1 -0
  79. package/dist/lib/config.d.ts +23 -0
  80. package/dist/lib/config.js +77 -0
  81. package/dist/lib/config.js.map +1 -0
  82. package/dist/lib/format.d.ts +11 -0
  83. package/dist/lib/format.js +86 -0
  84. package/dist/lib/format.js.map +1 -0
  85. package/install.sh +59 -0
  86. package/package.json +35 -0
  87. package/src/commands/add.ts +100 -0
  88. package/src/commands/connect.ts +145 -0
  89. package/src/commands/context.ts +93 -0
  90. package/src/commands/deploy.ts +153 -0
  91. package/src/commands/destroy.ts +51 -0
  92. package/src/commands/git.ts +80 -0
  93. package/src/commands/init.ts +139 -0
  94. package/src/commands/link.ts +63 -0
  95. package/src/commands/login.ts +158 -0
  96. package/src/commands/logout.ts +17 -0
  97. package/src/commands/logs.ts +104 -0
  98. package/src/commands/open.ts +17 -0
  99. package/src/commands/projects.ts +45 -0
  100. package/src/commands/ps.ts +80 -0
  101. package/src/commands/redeploy.ts +74 -0
  102. package/src/commands/regions.ts +38 -0
  103. package/src/commands/restart.ts +24 -0
  104. package/src/commands/run.ts +43 -0
  105. package/src/commands/secrets.ts +175 -0
  106. package/src/commands/status.ts +65 -0
  107. package/src/commands/update.ts +44 -0
  108. package/src/commands/version.ts +37 -0
  109. package/src/commands/whoami.ts +27 -0
  110. package/src/index.ts +168 -0
  111. package/src/lib/api.ts +134 -0
  112. package/src/lib/auth.ts +113 -0
  113. package/src/lib/config.ts +93 -0
  114. package/src/lib/format.ts +95 -0
  115. package/tsconfig.json +17 -0
@@ -0,0 +1,38 @@
1
+ import { Command } from "commander";
2
+ import { api } from "../lib/api.js";
3
+ import { isJSONMode, printJSON, table } from "../lib/format.js";
4
+
5
+ interface Region {
6
+ id: string;
7
+ name: string;
8
+ code: string;
9
+ endpoint?: string;
10
+ }
11
+
12
+ export function registerRegions(program: Command) {
13
+ const region = program
14
+ .command("region")
15
+ .description("Region management");
16
+
17
+ region
18
+ .command("list")
19
+ .description("List available regions")
20
+ .action(async () => {
21
+ const regions = await api.get<Region[]>("/api/regions");
22
+
23
+ if (isJSONMode()) {
24
+ printJSON(regions);
25
+ return;
26
+ }
27
+
28
+ if (regions.length === 0) {
29
+ console.log("No regions available.");
30
+ return;
31
+ }
32
+
33
+ table(
34
+ ["Code", "Name"],
35
+ regions.map((r) => [r.code || r.id, r.name]),
36
+ );
37
+ });
38
+ }
@@ -0,0 +1,24 @@
1
+ import chalk from "chalk";
2
+ import ora from "ora";
3
+ import { Command } from "commander";
4
+ import { api } from "../lib/api.js";
5
+ import { success, isJSONMode, printJSON } from "../lib/format.js";
6
+
7
+ export function registerRestart(program: Command) {
8
+ program
9
+ .command("restart")
10
+ .argument("<id>", "Service ID to restart")
11
+ .description("Restart a service")
12
+ .action(async (id: string) => {
13
+ const spinner = ora("Restarting...").start();
14
+
15
+ await api.post(`/api/apps/${id}/restart`);
16
+
17
+ spinner.stop();
18
+ if (isJSONMode()) {
19
+ printJSON({ id, status: "restarting" });
20
+ } else {
21
+ success(`Service ${id} restarting`);
22
+ }
23
+ });
24
+ }
@@ -0,0 +1,43 @@
1
+ import { execSync } from "node:child_process";
2
+ import { Command } from "commander";
3
+ import { api } from "../lib/api.js";
4
+ import { resolveProjectId } from "../lib/config.js";
5
+
6
+ interface Secret {
7
+ key: string;
8
+ value: string;
9
+ }
10
+
11
+ export function registerRun(program: Command) {
12
+ program
13
+ .command("run")
14
+ .argument("<command...>", "Command to run with project env vars")
15
+ .description("Run a command with project secrets as env vars")
16
+ .allowUnknownOption()
17
+ .action(async (args: string[]) => {
18
+ const projectId = resolveProjectId(program.opts().project);
19
+
20
+ // Fetch secrets
21
+ const secrets = await api.get<Secret[]>(
22
+ `/api/projects/${projectId}/secrets`,
23
+ );
24
+
25
+ // Build env
26
+ const env: Record<string, string> = { ...process.env as Record<string, string> };
27
+ for (const s of secrets) {
28
+ env[s.key] = s.value;
29
+ }
30
+
31
+ // Run command
32
+ const cmd = args.join(" ");
33
+ try {
34
+ execSync(cmd, {
35
+ env,
36
+ stdio: "inherit",
37
+ shell: process.env.SHELL || "/bin/sh",
38
+ });
39
+ } catch (err: any) {
40
+ process.exit(err.status || 1);
41
+ }
42
+ });
43
+ }
@@ -0,0 +1,175 @@
1
+ import chalk from "chalk";
2
+ import { Command } from "commander";
3
+ import { api } from "../lib/api.js";
4
+ import { resolveProjectId } from "../lib/config.js";
5
+ import { success, isJSONMode, printJSON, table } from "../lib/format.js";
6
+
7
+ interface Secret {
8
+ key: string;
9
+ value: string;
10
+ }
11
+
12
+ export function registerSecrets(program: Command) {
13
+ const secret = program
14
+ .command("secret")
15
+ .description("Manage project secrets");
16
+
17
+ secret
18
+ .command("list")
19
+ .description("List project secrets")
20
+ .option("--show", "Reveal secret values")
21
+ .action(async (opts) => {
22
+ const projectId = resolveProjectId(program.opts().project);
23
+ const secrets = await api.get<Secret[]>(
24
+ `/api/projects/${projectId}/secrets`,
25
+ );
26
+
27
+ if (isJSONMode()) {
28
+ printJSON(
29
+ opts.show
30
+ ? secrets
31
+ : secrets.map((s) => ({ key: s.key, value: "***" })),
32
+ );
33
+ return;
34
+ }
35
+
36
+ if (secrets.length === 0) {
37
+ console.log("No secrets. Use `lizard secret set KEY=value`.");
38
+ return;
39
+ }
40
+
41
+ table(
42
+ ["Key", "Value"],
43
+ secrets.map((s) => [
44
+ s.key,
45
+ opts.show ? s.value : chalk.dim("•".repeat(Math.min(s.value.length, 20))),
46
+ ]),
47
+ );
48
+ });
49
+
50
+ secret
51
+ .command("set")
52
+ .argument("<pairs...>", "KEY=value pairs")
53
+ .description("Set one or more secrets")
54
+ .option("--no-redeploy", "Don't trigger redeploy")
55
+ .action(async (pairs: string[], opts) => {
56
+ const projectId = resolveProjectId(program.opts().project);
57
+
58
+ // Parse KEY=value pairs
59
+ const newSecrets: Record<string, string> = {};
60
+ for (const pair of pairs) {
61
+ const eqIdx = pair.indexOf("=");
62
+ if (eqIdx < 1) {
63
+ throw new Error(`Invalid format: "${pair}". Use KEY=value`);
64
+ }
65
+ newSecrets[pair.slice(0, eqIdx)] = pair.slice(eqIdx + 1);
66
+ }
67
+
68
+ // Get existing secrets, merge, update
69
+ const existing = await api.get<Secret[]>(
70
+ `/api/projects/${projectId}/secrets`,
71
+ );
72
+ const merged: Secret[] = [];
73
+ const existingKeys = new Set<string>();
74
+
75
+ for (const s of existing) {
76
+ if (newSecrets[s.key] !== undefined) {
77
+ merged.push({ key: s.key, value: newSecrets[s.key] });
78
+ existingKeys.add(s.key);
79
+ } else {
80
+ merged.push(s);
81
+ }
82
+ }
83
+ // Add new keys
84
+ for (const [key, value] of Object.entries(newSecrets)) {
85
+ if (!existingKeys.has(key)) {
86
+ merged.push({ key, value });
87
+ }
88
+ }
89
+
90
+ await api.put(`/api/projects/${projectId}/secrets`, merged);
91
+
92
+ if (isJSONMode()) {
93
+ printJSON({ updated: Object.keys(newSecrets) });
94
+ } else {
95
+ success(
96
+ `${Object.keys(newSecrets).length} secret(s) updated`,
97
+ );
98
+ }
99
+ });
100
+
101
+ secret
102
+ .command("delete")
103
+ .argument("<keys...>", "Secret keys to delete")
104
+ .description("Delete one or more secrets")
105
+ .action(async (keys: string[]) => {
106
+ const projectId = resolveProjectId(program.opts().project);
107
+ const existing = await api.get<Secret[]>(
108
+ `/api/projects/${projectId}/secrets`,
109
+ );
110
+
111
+ const keysSet = new Set(keys);
112
+ const filtered = existing.filter((s) => !keysSet.has(s.key));
113
+
114
+ if (filtered.length === existing.length) {
115
+ throw new Error(`Secret(s) not found: ${keys.join(", ")}`);
116
+ }
117
+
118
+ await api.put(`/api/projects/${projectId}/secrets`, filtered);
119
+
120
+ if (isJSONMode()) {
121
+ printJSON({ deleted: keys });
122
+ } else {
123
+ success(`${keys.length} secret(s) deleted`);
124
+ }
125
+ });
126
+
127
+ secret
128
+ .command("import")
129
+ .description("Import secrets from stdin (KEY=value format, one per line)")
130
+ .option("--no-redeploy", "Don't trigger redeploy")
131
+ .action(async () => {
132
+ const projectId = resolveProjectId(program.opts().project);
133
+
134
+ // Read stdin
135
+ const chunks: Buffer[] = [];
136
+ for await (const chunk of process.stdin) {
137
+ chunks.push(chunk as Buffer);
138
+ }
139
+ const input = Buffer.concat(chunks).toString("utf-8");
140
+
141
+ const newSecrets: Record<string, string> = {};
142
+ for (const line of input.split("\n")) {
143
+ const trimmed = line.trim();
144
+ if (!trimmed || trimmed.startsWith("#")) continue;
145
+ const eqIdx = trimmed.indexOf("=");
146
+ if (eqIdx < 1) continue;
147
+ newSecrets[trimmed.slice(0, eqIdx)] = trimmed.slice(eqIdx + 1);
148
+ }
149
+
150
+ if (Object.keys(newSecrets).length === 0) {
151
+ throw new Error("No valid KEY=value pairs found in input");
152
+ }
153
+
154
+ // Merge with existing
155
+ const existing = await api.get<Secret[]>(
156
+ `/api/projects/${projectId}/secrets`,
157
+ );
158
+ const existingMap = new Map(existing.map((s) => [s.key, s.value]));
159
+ for (const [k, v] of Object.entries(newSecrets)) {
160
+ existingMap.set(k, v);
161
+ }
162
+ const merged = Array.from(existingMap.entries()).map(([key, value]) => ({
163
+ key,
164
+ value,
165
+ }));
166
+
167
+ await api.put(`/api/projects/${projectId}/secrets`, merged);
168
+
169
+ if (isJSONMode()) {
170
+ printJSON({ imported: Object.keys(newSecrets) });
171
+ } else {
172
+ success(`${Object.keys(newSecrets).length} secret(s) imported`);
173
+ }
174
+ });
175
+ }
@@ -0,0 +1,65 @@
1
+ import chalk from "chalk";
2
+ import { Command } from "commander";
3
+ import { api } from "../lib/api.js";
4
+ import { resolveProjectId, findProjectConfig } from "../lib/config.js";
5
+ import { isJSONMode, printJSON, statusColor, table } from "../lib/format.js";
6
+
7
+ export function registerStatus(program: Command) {
8
+ program
9
+ .command("status")
10
+ .description("Show project status")
11
+ .action(async () => {
12
+ const projectId = resolveProjectId(program.opts().project);
13
+ const config = findProjectConfig();
14
+
15
+ const [project, services] = await Promise.all([
16
+ api.get<{ id: string; name: string; slug: string }>(
17
+ `/api/projects/${projectId}`,
18
+ ),
19
+ api.get<{ apps: any[]; addons: any[] }>(
20
+ `/api/projects/${projectId}/services`,
21
+ ),
22
+ ]);
23
+
24
+ if (isJSONMode()) {
25
+ printJSON({ project, services, environment: config?.environment || "production" });
26
+ return;
27
+ }
28
+
29
+ console.log(chalk.bold(project.name) + chalk.dim(` (${project.id})`));
30
+ if (config?.environment) {
31
+ console.log(chalk.dim(`Environment: ${config.environment}`));
32
+ }
33
+ console.log();
34
+
35
+ const allServices = [
36
+ ...(services.apps || []).map((a: any) => ({
37
+ name: a.name,
38
+ type: "app",
39
+ status: a.status,
40
+ url: a.domain ? `https://${a.domain}` : "",
41
+ })),
42
+ ...(services.addons || []).map((a: any) => ({
43
+ name: a.name || a.addonType,
44
+ type: a.addonType || "addon",
45
+ status: a.status,
46
+ url: a.hostname || "",
47
+ })),
48
+ ];
49
+
50
+ if (allServices.length === 0) {
51
+ console.log(chalk.dim("No services"));
52
+ return;
53
+ }
54
+
55
+ table(
56
+ ["Name", "Type", "Status", "URL"],
57
+ allServices.map((s) => [
58
+ s.name,
59
+ s.type,
60
+ statusColor(s.status),
61
+ s.url || chalk.dim("—"),
62
+ ]),
63
+ );
64
+ });
65
+ }
@@ -0,0 +1,44 @@
1
+ import chalk from "chalk";
2
+ import { Command } from "commander";
3
+ import { info, isJSONMode, printJSON } from "../lib/format.js";
4
+
5
+ export function registerUpdate(program: Command) {
6
+ program
7
+ .command("update")
8
+ .description("Update Lizard CLI to latest version")
9
+ .option("--check", "Only check for updates without installing")
10
+ .action(async (opts) => {
11
+ // Check npm for latest version
12
+ try {
13
+ const res = await fetch("https://registry.npmjs.org/@lizard/cli/latest");
14
+ if (!res.ok) throw new Error("Failed to check for updates");
15
+ const data = (await res.json()) as { version: string };
16
+
17
+ if (isJSONMode()) {
18
+ printJSON({
19
+ currentVersion: "0.1.0",
20
+ latestVersion: data.version,
21
+ updateAvailable: data.version !== "0.1.0",
22
+ });
23
+ return;
24
+ }
25
+
26
+ if (data.version === "0.1.0") {
27
+ info("Already up to date (v0.1.0)");
28
+ return;
29
+ }
30
+
31
+ if (opts.check) {
32
+ info(`Update available: v0.1.0 → v${data.version}`);
33
+ info(chalk.dim(`Run \`lizard update\` to install`));
34
+ return;
35
+ }
36
+
37
+ info(`Updating to v${data.version}...`);
38
+ const { execSync } = await import("node:child_process");
39
+ execSync("npm install -g @lizard/cli@latest", { stdio: "inherit" });
40
+ } catch {
41
+ info("Could not check for updates. Run `npm update -g @lizard/cli` manually.");
42
+ }
43
+ });
44
+ }
@@ -0,0 +1,37 @@
1
+ import { Command } from "commander";
2
+ import { isJSONMode, printJSON } from "../lib/format.js";
3
+ import { readFileSync } from "node:fs";
4
+ import { join, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+
7
+ function getVersion(): string {
8
+ try {
9
+ // Try to read from package.json
10
+ const __dirname = dirname(fileURLToPath(import.meta.url));
11
+ const pkgPath = join(__dirname, "..", "..", "package.json");
12
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
13
+ return pkg.version || "0.1.0";
14
+ } catch {
15
+ return "0.1.0";
16
+ }
17
+ }
18
+
19
+ export function registerVersion(program: Command) {
20
+ program
21
+ .command("version")
22
+ .description("Show CLI version")
23
+ .action(() => {
24
+ const version = getVersion();
25
+ if (isJSONMode()) {
26
+ printJSON({
27
+ version,
28
+ platform: process.platform,
29
+ arch: process.arch,
30
+ node: process.version,
31
+ });
32
+ } else {
33
+ console.log(`lizard v${version}`);
34
+ console.log(`${process.platform}/${process.arch} node/${process.version}`);
35
+ }
36
+ });
37
+ }
@@ -0,0 +1,27 @@
1
+ import chalk from "chalk";
2
+ import { Command } from "commander";
3
+ import { api } from "../lib/api.js";
4
+ import { isJSONMode, printJSON } from "../lib/format.js";
5
+
6
+ export function registerWhoami(program: Command) {
7
+ program
8
+ .command("whoami")
9
+ .description("Show current user")
10
+ .action(async () => {
11
+ const user = await api.get<{
12
+ id: string;
13
+ username: string;
14
+ avatarUrl?: string;
15
+ hasGithubApp?: boolean;
16
+ }>("/api/auth/me");
17
+
18
+ if (isJSONMode()) {
19
+ printJSON(user);
20
+ } else {
21
+ console.log(chalk.bold(user.username));
22
+ if (user.hasGithubApp) {
23
+ console.log(chalk.dim("GitHub App: connected"));
24
+ }
25
+ }
26
+ });
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import { setJSONMode, isJSONMode, error } from "./lib/format.js";
5
+ import { setTokenOverride, requireAuth, isLoggedIn } from "./lib/auth.js";
6
+ import { setBaseURL, setAccessToken } from "./lib/api.js";
7
+
8
+ // Commands
9
+ import { registerLogin } from "./commands/login.js";
10
+ import { registerLogout } from "./commands/logout.js";
11
+ import { registerWhoami } from "./commands/whoami.js";
12
+ import { registerInit } from "./commands/init.js";
13
+ import { registerLink } from "./commands/link.js";
14
+ import { registerProjects } from "./commands/projects.js";
15
+ import { registerDeploy } from "./commands/deploy.js";
16
+ import { registerPs } from "./commands/ps.js";
17
+ import { registerAdd } from "./commands/add.js";
18
+ import { registerDestroy } from "./commands/destroy.js";
19
+ import { registerRestart } from "./commands/restart.js";
20
+ import { registerRedeploy } from "./commands/redeploy.js";
21
+ import { registerLogs } from "./commands/logs.js";
22
+ import { registerSecrets } from "./commands/secrets.js";
23
+ import { registerRegions } from "./commands/regions.js";
24
+ import { registerStatus } from "./commands/status.js";
25
+ import { registerOpen } from "./commands/open.js";
26
+ import { registerRun } from "./commands/run.js";
27
+ import { registerConnect } from "./commands/connect.js";
28
+ import { registerContext } from "./commands/context.js";
29
+ import { registerGit } from "./commands/git.js";
30
+ import { registerVersion } from "./commands/version.js";
31
+ import { registerUpdate } from "./commands/update.js";
32
+
33
+ const program = new Command();
34
+
35
+ program
36
+ .name("lizard")
37
+ .description("Lizard CLI — deploy and manage apps on Lizard")
38
+ .version("0.1.0")
39
+ .option("--json", "Output in JSON format")
40
+ .option("-y, --yes", "Skip confirmation prompts")
41
+ .option("--workspace <id>", "Workspace name or ID")
42
+ .option("--project <id>", "Project ID")
43
+ .option("--environment <name>", "Environment name")
44
+ .option("--region <region>", "Region for creating services")
45
+ .option("--token <token>", "API token")
46
+ .option("--no-color", "Disable colors")
47
+ .option("--verbose", "Verbose output")
48
+ .hook("preAction", async (thisCommand, actionCommand) => {
49
+ const opts = thisCommand.opts();
50
+
51
+ // JSON mode: explicit flag or non-TTY stdout
52
+ if (opts.json || !process.stdout.isTTY) {
53
+ setJSONMode(true);
54
+ }
55
+
56
+ // Token override
57
+ if (opts.token) {
58
+ setTokenOverride(opts.token);
59
+ }
60
+
61
+ // API URL override
62
+ if (process.env.LIZARD_API_URL) {
63
+ setBaseURL(process.env.LIZARD_API_URL);
64
+ }
65
+
66
+ // Commands that don't need auth
67
+ const noAuth = new Set(["login", "logout", "version", "completion", "update", "help"]);
68
+ if (noAuth.has(actionCommand.name())) return;
69
+
70
+ // Require auth — auto-triggers login flow if not logged in
71
+ const creds = await requireAuth();
72
+ setAccessToken(creds.accessToken);
73
+ });
74
+
75
+ // Register all commands
76
+ registerLogin(program);
77
+ registerLogout(program);
78
+ registerWhoami(program);
79
+ registerInit(program);
80
+ registerLink(program);
81
+ registerProjects(program);
82
+ registerDeploy(program);
83
+ registerPs(program);
84
+ registerAdd(program);
85
+ registerDestroy(program);
86
+ registerRestart(program);
87
+ registerRedeploy(program);
88
+ registerLogs(program);
89
+ registerSecrets(program);
90
+ registerRegions(program);
91
+ registerStatus(program);
92
+ registerOpen(program);
93
+ registerRun(program);
94
+ registerConnect(program);
95
+ registerContext(program);
96
+ registerGit(program);
97
+ registerVersion(program);
98
+ registerUpdate(program);
99
+
100
+ // Shell completion
101
+ program
102
+ .command("completion")
103
+ .argument("<shell>", "Shell type (bash, zsh, fish)")
104
+ .description("Generate shell completion script")
105
+ .action((shell: string) => {
106
+ // Commander doesn't have built-in completion like Cobra
107
+ // Point users to manual setup
108
+ console.log(`# Add to your .${shell}rc:`);
109
+ if (shell === "bash") {
110
+ console.log(`eval "$(lizard completion bash)"`);
111
+ } else if (shell === "zsh") {
112
+ console.log(`# lizard completion is not yet available for zsh`);
113
+ console.log(`# Coming soon`);
114
+ } else if (shell === "fish") {
115
+ console.log(`# lizard completion is not yet available for fish`);
116
+ console.log(`# Coming soon`);
117
+ }
118
+ });
119
+
120
+ // Error handling
121
+ program.exitOverride();
122
+
123
+ async function main() {
124
+ try {
125
+ await program.parseAsync(process.argv);
126
+ } catch (err: any) {
127
+ // Commander throws for --help, --version, etc. — ignore those
128
+ if (err.code === "commander.helpDisplayed" || err.code === "commander.version") {
129
+ process.exit(0);
130
+ }
131
+ if (err.code === "commander.help") {
132
+ process.exit(0);
133
+ }
134
+
135
+ const msg = err.message || String(err);
136
+
137
+ if (isJSONMode()) {
138
+ console.log(
139
+ JSON.stringify(
140
+ {
141
+ error: {
142
+ code: err.code || "ERROR",
143
+ message: msg,
144
+ },
145
+ },
146
+ null,
147
+ 2,
148
+ ),
149
+ );
150
+ } else {
151
+ error(msg);
152
+ }
153
+
154
+ // Exit codes per spec
155
+ if (msg.includes("Not authenticated") || msg.includes("Invalid token")) {
156
+ process.exit(2);
157
+ }
158
+ if (msg.includes("not found") || msg.includes("Not found")) {
159
+ process.exit(3);
160
+ }
161
+ if (msg.includes("timeout") || msg.includes("Timeout")) {
162
+ process.exit(4);
163
+ }
164
+ process.exit(1);
165
+ }
166
+ }
167
+
168
+ main();