relight-cli 0.1.0 → 0.3.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 (45) hide show
  1. package/README.md +77 -34
  2. package/package.json +12 -4
  3. package/src/cli.js +350 -1
  4. package/src/commands/apps.js +128 -0
  5. package/src/commands/auth.js +13 -4
  6. package/src/commands/config.js +282 -0
  7. package/src/commands/cost.js +593 -0
  8. package/src/commands/db.js +775 -0
  9. package/src/commands/deploy.js +264 -0
  10. package/src/commands/doctor.js +69 -13
  11. package/src/commands/domains.js +223 -0
  12. package/src/commands/logs.js +111 -0
  13. package/src/commands/open.js +42 -0
  14. package/src/commands/ps.js +121 -0
  15. package/src/commands/scale.js +132 -0
  16. package/src/commands/service.js +227 -0
  17. package/src/lib/clouds/aws.js +309 -35
  18. package/src/lib/clouds/cf.js +401 -2
  19. package/src/lib/clouds/gcp.js +255 -4
  20. package/src/lib/clouds/neon.js +147 -0
  21. package/src/lib/clouds/slicervm.js +139 -0
  22. package/src/lib/config.js +200 -2
  23. package/src/lib/docker.js +34 -0
  24. package/src/lib/link.js +31 -5
  25. package/src/lib/providers/aws/app.js +481 -0
  26. package/src/lib/providers/aws/db.js +504 -0
  27. package/src/lib/providers/aws/dns.js +232 -0
  28. package/src/lib/providers/aws/registry.js +59 -0
  29. package/src/lib/providers/cf/app.js +596 -0
  30. package/src/lib/providers/cf/bundle.js +70 -0
  31. package/src/lib/providers/cf/db.js +181 -0
  32. package/src/lib/providers/cf/dns.js +148 -0
  33. package/src/lib/providers/cf/registry.js +17 -0
  34. package/src/lib/providers/gcp/app.js +429 -0
  35. package/src/lib/providers/gcp/db.js +372 -0
  36. package/src/lib/providers/gcp/dns.js +166 -0
  37. package/src/lib/providers/gcp/registry.js +30 -0
  38. package/src/lib/providers/neon/db.js +306 -0
  39. package/src/lib/providers/resolve.js +79 -0
  40. package/src/lib/providers/slicervm/app.js +396 -0
  41. package/src/lib/providers/slicervm/db.js +33 -0
  42. package/src/lib/providers/slicervm/dns.js +58 -0
  43. package/src/lib/providers/slicervm/registry.js +7 -0
  44. package/worker-template/package.json +10 -0
  45. package/worker-template/src/index.js +260 -0
package/README.md CHANGED
@@ -3,10 +3,10 @@
3
3
  Deploy Docker containers to your cloud with scale-to-zero. Apps sleep when idle and wake on the next request.
4
4
 
5
5
  ```
6
- $ relight deploy myapp . --compute gcp
6
+ $ relight deploy myapp . --cloud gcp
7
7
  Building image...
8
- Pushing to gcr.io/my-project/myapp:v1...
9
- Deploying to Cloud Run (us-east1)...
8
+ Pushing to us-docker.pkg.dev/my-project/relight/myapp:v1...
9
+ Deploying to Cloud Run (us-central1)...
10
10
 
11
11
  --> Live at https://myapp-abc123.run.app
12
12
  Sleeps after 30s idle. $0 when sleeping.
@@ -16,15 +16,14 @@ $ relight deploy myapp . --compute gcp
16
16
 
17
17
  Relight is a CLI that deploys and manages Docker containers across multiple cloud providers. It talks directly to each cloud's API using your own credentials. No vendor infrastructure gets installed in your account.
18
18
 
19
- Supported compute backends:
19
+ Supported backends:
20
20
 
21
- | Cloud | Backend | Scale to zero |
22
- |---|---|---|
23
- | GCP | Cloud Run | Yes |
24
- | AWS | App Runner | Yes |
25
- | Cloudflare | Containers (Workers + Durable Objects) | Yes |
26
-
27
- More backends planned: Azure Container Apps.
21
+ | Cloud | Backend | Scale to zero | Database | DNS |
22
+ |---|---|---|---|---|
23
+ | GCP | Cloud Run | Yes | Cloud SQL (PostgreSQL) | Cloud DNS |
24
+ | AWS | App Runner | Yes | RDS (PostgreSQL) | Route 53 |
25
+ | Cloudflare | Containers (Workers + Durable Objects) | Yes | D1 (SQLite) | Cloudflare DNS |
26
+ | SlicerVM | Self-hosted | Yes | - | - |
28
27
 
29
28
  ## Install
30
29
 
@@ -38,7 +37,7 @@ Requires Node.js 20+ and Docker.
38
37
 
39
38
  ```sh
40
39
  # Authenticate with a cloud provider
41
- relight auth --compute gcp
40
+ relight auth --cloud gcp
42
41
 
43
42
  # Deploy from a Dockerfile
44
43
  relight deploy myapp .
@@ -51,6 +50,9 @@ relight logs myapp
51
50
 
52
51
  # Open in browser
53
52
  relight open myapp
53
+
54
+ # Create a database
55
+ relight db create myapp
54
56
  ```
55
57
 
56
58
  The first deploy links the current directory to the app name. After that, `relight deploy` is enough.
@@ -74,7 +76,16 @@ relight scale [name] Show or adjust instance count and resources
74
76
  relight domains list [name] List custom domains
75
77
  relight domains add [domain] Add a custom domain with DNS setup
76
78
  relight domains remove [domain] Remove a custom domain
77
- relight regions [--compute <cloud>] List available regions for a cloud
79
+ relight db create [name] Create a managed database
80
+ relight db destroy [name] Destroy a database
81
+ relight db info [name] Show database details
82
+ relight db shell [name] Interactive SQL shell
83
+ relight db query [name] <sql> Run a SQL query
84
+ relight db import [name] <file> Import a SQL file
85
+ relight db export [name] Export database as SQL
86
+ relight db token [name] Show or rotate DB token
87
+ relight db reset [name] Drop all tables
88
+ relight regions [--cloud <cloud>] List available regions for a cloud
78
89
  relight cost [name] Show estimated costs
79
90
  relight doctor Check local setup and cloud connectivity
80
91
  ```
@@ -83,13 +94,13 @@ Config changes (`config set`, `config unset`, `scale`, `domains`) are applied li
83
94
 
84
95
  ## How it works
85
96
 
86
- 1. `relight auth` stores a scoped API token locally at `~/.relight/config.json`. One token per cloud provider. No cross-account IAM roles, no OAuth flows, no vendor access to your account.
97
+ 1. `relight auth` stores credentials locally at `~/.relight/config.json`. One set of credentials per cloud provider. No cross-account IAM roles, no OAuth flows, no vendor access to your account.
87
98
 
88
- 2. `relight deploy` builds a Docker image locally, pushes it to the cloud's container registry (GCR, Cloudflare Registry, ECR), and deploys it using the cloud's native container service.
99
+ 2. `relight deploy` builds a Docker image locally, pushes it to the cloud's container registry (Artifact Registry, ECR, or Cloudflare Registry), and deploys it using the cloud's native container service.
89
100
 
90
101
  3. The deployed app sleeps after a configurable idle period (default 30s). The next incoming request wakes it. Cold starts are typically 1-5 seconds depending on the cloud and image size.
91
102
 
92
- 4. All app state (config, scaling, domains) lives in the cloud provider's API. The only local files are your auth token and a `.relight` link file in your project directory. You can manage your apps from any machine.
103
+ 4. All app state (config, scaling, domains, database bindings) lives in the cloud provider's API. The only local files are your auth credentials and a `.relight.yaml` link file in your project directory. You can manage your apps from any machine.
93
104
 
94
105
  ## Fleet view
95
106
 
@@ -101,18 +112,38 @@ $ relight apps
101
112
  NAME CLOUD STATUS INSTANCES COST/MTD LAST ACTIVE
102
113
  myapi gcp sleeping 0/3 $0.12 2h ago
103
114
  frontend cf active 2/5 $1.84 now
115
+ worker aws sleeping 0/1 $0.05 1h ago
104
116
  dashboard gcp sleeping 0/1 $0.00 3d ago
105
- ───────────────────────────────────────────────────────────────
106
- TOTAL $1.96
117
+ -----------------------------------------------------------------
118
+ TOTAL $2.01
119
+ ```
120
+
121
+ ## Databases
122
+
123
+ Relight manages databases alongside your apps. Each cloud uses its native database service:
124
+
125
+ ```sh
126
+ # Create a database for your app
127
+ relight db create myapp
128
+
129
+ # Interactive SQL shell
130
+ relight db shell myapp
131
+
132
+ # Run a query
133
+ relight db query myapp "SELECT * FROM users"
107
134
  ```
108
135
 
136
+ GCP and AWS use PostgreSQL (Cloud SQL and RDS). Cloudflare uses D1 (SQLite). The connection URL and credentials are automatically injected into your app's environment as `DATABASE_URL` and `DB_TOKEN`.
137
+
138
+ Cross-cloud databases are supported - you can attach an AWS RDS database to a GCP Cloud Run app by specifying `--db aws`.
139
+
109
140
  ## BYOC model
110
141
 
111
142
  Relight deploys to cloud accounts you own. You pay the cloud provider directly at their published rates. Relight itself is free for individual use.
112
143
 
113
144
  What this means in practice:
114
145
 
115
- - **Your credentials stay local.** The API token is stored on your machine. Relight has no backend service that holds your cloud credentials.
146
+ - **Your credentials stay local.** Credentials are stored on your machine. Relight has no backend service that holds your cloud credentials.
116
147
  - **Nothing is installed in your account.** No Kubernetes clusters, no CloudFormation stacks, no VPCs, no agent processes. Relight uses the cloud's existing container services.
117
148
  - **You can stop using Relight anytime.** Your apps are standard Cloud Run services / App Runner services / CF Workers. They continue running without Relight. There's nothing to uninstall.
118
149
 
@@ -140,12 +171,12 @@ Relight does not autoscale. You set the maximum instance count and the cloud pro
140
171
  relight domains add myapp.example.com
141
172
 
142
173
  # Relight creates the DNS record if your domain's DNS is on a supported provider
174
+ # (Cloud DNS, Route 53, or Cloudflare DNS)
143
175
  # Otherwise it prints the record for you to create manually
144
176
  ```
145
177
 
146
178
  ## What Relight doesn't do
147
179
 
148
- - **Not a general infrastructure tool.** Relight deploys Docker containers with scale-to-zero. It doesn't manage databases, queues, cron jobs, or other infrastructure. Use Terraform, Pulumi, or cloud consoles for those.
149
180
  - **No autoscaling.** You set max instances. The cloud scales between 0 and your max based on traffic. There's no custom autoscaling logic.
150
181
  - **No CI/CD.** Relight is a deployment tool, not a build pipeline. Integrate it into your CI by running `relight deploy` in your workflow.
151
182
  - **Cold starts.** Sleeping apps take 1-5 seconds to respond to the first request. This is inherent to scale-to-zero and varies by cloud and image size.
@@ -154,24 +185,35 @@ relight domains add myapp.example.com
154
185
 
155
186
  ### GCP (Cloud Run)
156
187
 
157
- - Requires a GCP project with Cloud Run and Artifact Registry APIs enabled.
158
- - Token: service account key or `gcloud auth print-access-token`.
159
- - Regions: any Cloud Run region. Run `relight regions --compute gcp` to list.
188
+ - Requires a GCP project with Cloud Run, Artifact Registry, Cloud SQL Admin, Cloud DNS, Logging, and Monitoring APIs enabled.
189
+ - Credentials: service account key JSON file.
190
+ - Regions: any Cloud Run region. Run `relight regions --cloud gcp` to list.
160
191
  - Scale-to-zero is native. Minimum instances can be set to 0.
192
+ - Database: Cloud SQL PostgreSQL 15 (`db-f1-micro` by default).
161
193
 
162
194
  ### AWS (App Runner)
163
195
 
164
- - Requires an AWS account with App Runner and ECR access.
165
- - Token: IAM access key with `apprunner:*` and `ecr:*` permissions, or use `aws configure`.
166
- - Regions: any App Runner region. Run `relight regions --compute aws` to list.
167
- - Scale-to-zero is native. Services pause when idle and resume on the next request.
196
+ - Requires an AWS account with IAM user credentials.
197
+ - Required IAM policies: `AWSAppRunnerFullAccess`, `AmazonEC2ContainerRegistryFullAccess`, `AmazonRDSFullAccess`, `AmazonRoute53FullAccess`, `AmazonEC2ReadOnlyAccess`, `CloudWatchLogsReadOnlyAccess`, `IAMFullAccess`.
198
+ - Credentials: IAM access key ID and secret access key.
199
+ - Regions: 9 App Runner regions. Run `relight regions --cloud aws` to list.
200
+ - Scale-to-zero: App Runner pauses services when idle. Minimum 1 provisioned instance ($0.007/vCPU-hr when idle).
201
+ - Database: RDS PostgreSQL 15 (`db.t4g.micro` by default). Provisioning takes 5-15 minutes.
202
+ - Registry: ECR. Repositories and IAM access roles are created automatically.
168
203
 
169
204
  ### Cloudflare (Containers)
170
205
 
171
206
  - Requires a Cloudflare account with Containers access (paid Workers plan).
172
- - Token: Cloudflare API token with `workers_scripts:edit` and `containers:edit` permissions.
207
+ - Credentials: Cloudflare API token with Workers and Containers permissions.
173
208
  - Each app is a Worker backed by Durable Objects running your container. The CLI bundles and uploads the Worker template automatically.
174
- - Regions use Durable Object `locationHints` placement is best-effort, not guaranteed.
209
+ - Regions use Durable Object `locationHints` - placement is best-effort, not guaranteed.
210
+ - Database: D1 (SQLite).
211
+
212
+ ### SlicerVM (Self-hosted)
213
+
214
+ - Requires a running SlicerVM host.
215
+ - Credentials: API URL + token, or a Unix socket for local development.
216
+ - Images are uploaded directly to the host (no external registry needed).
175
217
 
176
218
  ## Configuration
177
219
 
@@ -180,18 +222,19 @@ Auth config is stored at `~/.relight/config.json`:
180
222
  ```json
181
223
  {
182
224
  "clouds": {
183
- "gcp": { "token": "...", "project": "my-project" },
225
+ "gcp": { "clientEmail": "...", "privateKey": "...", "project": "my-project" },
184
226
  "aws": { "accessKeyId": "...", "secretAccessKey": "...", "region": "us-east-1" },
185
227
  "cf": { "token": "...", "accountId": "..." }
186
228
  },
187
- "default_compute": "gcp"
229
+ "default_cloud": "gcp"
188
230
  }
189
231
  ```
190
232
 
191
- Per-project app linking is stored in a `.relight` file in your project directory:
233
+ Per-project app linking is stored in a `.relight.yaml` file in your project directory:
192
234
 
193
- ```json
194
- { "app": "myapp", "compute": "gcp" }
235
+ ```yaml
236
+ app: myapp
237
+ cloud: gcp
195
238
  ```
196
239
 
197
240
  ## License
package/package.json CHANGED
@@ -1,14 +1,19 @@
1
1
  {
2
2
  "name": "relight-cli",
3
- "version": "0.1.0",
4
- "description": "Deploy and manage Docker containers across clouds with scale-to-zero",
3
+ "version": "0.3.0",
4
+ "description": "Deploy web apps across clouds with scale-to-zero",
5
5
  "bin": {
6
6
  "relight": "./src/cli.js"
7
7
  },
8
8
  "type": "module",
9
+ "exports": {
10
+ ".": "./src/cli.js",
11
+ "./worker-template": "./worker-template/src/index.js"
12
+ },
9
13
  "license": "MIT",
10
14
  "files": [
11
- "src"
15
+ "src",
16
+ "worker-template"
12
17
  ],
13
18
  "keywords": [
14
19
  "docker",
@@ -22,6 +27,9 @@
22
27
  ],
23
28
  "dependencies": {
24
29
  "commander": "^13.0.0",
25
- "kleur": "^4.1.5"
30
+ "esbuild": "^0.25.0",
31
+ "kleur": "^4.1.5",
32
+ "pg": "^8.13.0",
33
+ "yaml": "^2.8.2"
26
34
  }
27
35
  }
package/src/cli.js CHANGED
@@ -3,13 +3,38 @@
3
3
  import { Command } from "commander";
4
4
  import { auth } from "./commands/auth.js";
5
5
  import { doctor } from "./commands/doctor.js";
6
+ import { deploy } from "./commands/deploy.js";
7
+ import { appsList, appsInfo, appsDestroy } from "./commands/apps.js";
8
+ import {
9
+ configShow,
10
+ configSet,
11
+ configGet,
12
+ configUnset,
13
+ configImport,
14
+ } from "./commands/config.js";
15
+ import { scale } from "./commands/scale.js";
16
+ import { domainsList, domainsAdd, domainsRemove } from "./commands/domains.js";
17
+ import { ps } from "./commands/ps.js";
18
+ import { logs } from "./commands/logs.js";
19
+ import { open } from "./commands/open.js";
20
+ import { cost } from "./commands/cost.js";
21
+ import { serviceList, serviceAdd, serviceRemove } from "./commands/service.js";
22
+ import {
23
+ dbCreate, dbDestroy, dbList, dbInfo, dbAttach, dbDetach,
24
+ dbShell, dbQuery, dbImport, dbExport, dbToken, dbReset,
25
+ } from "./commands/db.js";
26
+ import { fmt } from "./lib/output.js";
27
+ import { createRequire } from "module";
28
+
29
+ var require = createRequire(import.meta.url);
30
+ var { version } = require("../package.json");
6
31
 
7
32
  var program = new Command();
8
33
 
9
34
  program
10
35
  .name("relight")
11
36
  .description("Deploy and manage Docker containers across clouds with scale-to-zero")
12
- .version("0.1.0");
37
+ .version(version);
13
38
 
14
39
  // --- Auth ---
15
40
 
@@ -22,6 +47,320 @@ program
22
47
  )
23
48
  .action(auth);
24
49
 
50
+ // --- Service ---
51
+
52
+ var serviceCmd = program.command("service").description("Manage services (compute, databases, etc.)");
53
+
54
+ serviceCmd
55
+ .command("list", { isDefault: true })
56
+ .description("List registered services")
57
+ .action(serviceList);
58
+
59
+ serviceCmd
60
+ .command("add [name]")
61
+ .description("Register a service")
62
+ .action(serviceAdd);
63
+
64
+ serviceCmd
65
+ .command("remove <name>")
66
+ .description("Remove a service")
67
+ .action(serviceRemove);
68
+
69
+ // --- Deploy ---
70
+
71
+ program
72
+ .command("deploy [name] [path]")
73
+ .description("Deploy an app from a Dockerfile (name auto-generated if omitted)")
74
+ .option("-c, --cloud <cloud>", "Cloud provider (cf, gcp, aws)")
75
+ .option("--compute <name>", "Compute service name")
76
+ .option("-t, --tag <tag>", "Image tag (default: deploy-<timestamp>)")
77
+ .option("-e, --env <vars...>", "Set env vars (KEY=VALUE)")
78
+ .option(
79
+ "--regions <hints>",
80
+ "Comma-separated location hints (wnam,enam,sam,weur,eeur,apac,oc,afr,me)"
81
+ )
82
+ .option("-i, --instances <n>", "Instances per region", parseInt)
83
+ .option("--port <port>", "Container port", parseInt)
84
+ .option("--sleep <duration>", "Sleep after idle (e.g. 5m, 30s, never)", "30s")
85
+ .option("--instance-type <type>", "Instance type (lite, base, standard, large)")
86
+ .option("--vcpu <n>", "vCPU allocation (e.g. 0.0625, 0.5, 1, 2)", parseFloat)
87
+ .option("--memory <mb>", "Memory in MiB (e.g. 256, 512, 1024)", parseInt)
88
+ .option("--disk <mb>", "Disk in MB (e.g. 2000, 5000)", parseInt)
89
+ .option("--dns <cloud>", "Cloud provider for DNS records (cross-cloud)")
90
+ .option("--no-observability", "Disable Workers observability/logs")
91
+ .option("--json", "Output result as JSON")
92
+ .option("-y, --yes", "Skip confirmation prompt")
93
+ .action(deploy);
94
+
95
+ // --- Apps (topic root = list) ---
96
+
97
+ var apps = program.command("apps").description("Manage apps");
98
+
99
+ apps
100
+ .command("list", { isDefault: true })
101
+ .description("List all deployed apps")
102
+ .option("-c, --cloud <cloud>", "Cloud provider")
103
+ .option("--compute <name>", "Compute service name")
104
+ .option("--json", "Output as JSON")
105
+ .action(appsList);
106
+
107
+ apps
108
+ .command("info [name]")
109
+ .description("Show detailed app information")
110
+ .option("-c, --cloud <cloud>", "Cloud provider")
111
+ .option("--compute <name>", "Compute service name")
112
+ .option("--json", "Output as JSON")
113
+ .action(appsInfo);
114
+
115
+ apps
116
+ .command("destroy [name]")
117
+ .description("Destroy an app and its resources")
118
+ .option("-c, --cloud <cloud>", "Cloud provider")
119
+ .option("--compute <name>", "Compute service name")
120
+ .option("--confirm <name>", "Confirm by providing the app name")
121
+ .action(appsDestroy);
122
+
123
+ // --- Config (topic root = show) ---
124
+
125
+ var configCmd = program
126
+ .command("config")
127
+ .description("Manage app config/env vars")
128
+ .option("-c, --cloud <cloud>", "Cloud provider")
129
+ .option("--compute <name>", "Compute service name");
130
+
131
+ function configOpts(cmd) {
132
+ var parentOpts = cmd?.parent?.opts() || {};
133
+ return { cloud: parentOpts.cloud, compute: parentOpts.compute };
134
+ }
135
+
136
+ configCmd
137
+ .command("show [name]", { isDefault: true })
138
+ .description("Show all env vars for an app")
139
+ .option("--json", "Output as JSON")
140
+ .action((name, options, cmd) => configShow(name, { ...options, ...configOpts(cmd) }));
141
+
142
+ configCmd
143
+ .command("set <args...>")
144
+ .description("Set env vars ([name] KEY=VALUE ...) - applies live")
145
+ .option("-s, --secret", "Store values as encrypted secrets (write-only)")
146
+ .action((args, options, cmd) => configSet(args, { ...options, ...configOpts(cmd) }));
147
+
148
+ configCmd
149
+ .command("get <args...>")
150
+ .description("Get a single env var value ([name] KEY)")
151
+ .action((args, options, cmd) => configGet(args, { ...options, ...configOpts(cmd) }));
152
+
153
+ configCmd
154
+ .command("unset <args...>")
155
+ .description("Remove env vars ([name] KEY ...) - applies live")
156
+ .action((args, options, cmd) => configUnset(args, { ...options, ...configOpts(cmd) }));
157
+
158
+ configCmd
159
+ .command("import [name]")
160
+ .description("Import env vars from .env file or stdin")
161
+ .option("-f, --file <path>", "Path to .env file")
162
+ .option("-s, --secret", "Store values as encrypted secrets (write-only)")
163
+ .action((name, options, cmd) => configImport(name, { ...options, ...configOpts(cmd) }));
164
+
165
+ // --- Scale ---
166
+
167
+ program
168
+ .command("scale [name]")
169
+ .description("Show or adjust app scaling")
170
+ .option("-c, --cloud <cloud>", "Cloud provider")
171
+ .option("--compute <name>", "Compute service name")
172
+ .option(
173
+ "-r, --regions <hints>",
174
+ "Comma-separated location hints (wnam,enam,sam,weur,eeur,apac,oc,afr,me)"
175
+ )
176
+ .option("-i, --instances <n>", "Instances per region", parseInt)
177
+ .option("--instance-type <type>", "Instance type (lite, base, standard, large)")
178
+ .option("--vcpu <n>", "vCPU allocation (e.g. 0.0625, 0.5, 1, 2)", parseFloat)
179
+ .option("--memory <mb>", "Memory in MiB (e.g. 256, 512, 1024)", parseInt)
180
+ .option("--disk <mb>", "Disk in MB (e.g. 2000, 5000)", parseInt)
181
+ .option("--json", "Output as JSON")
182
+ .action(scale);
183
+
184
+ // --- Domains (topic root = list) ---
185
+
186
+ var domainsCmd = program
187
+ .command("domains")
188
+ .description("Manage custom domains")
189
+ .option("-c, --cloud <cloud>", "Cloud provider")
190
+ .option("--compute <name>", "Compute service name")
191
+ .option("--dns <cloud>", "Cloud provider for DNS records (cross-cloud)");
192
+
193
+ domainsCmd
194
+ .command("list [name]", { isDefault: true })
195
+ .description("List custom domains for an app")
196
+ .option("-c, --cloud <cloud>", "Cloud provider")
197
+ .option("--compute <name>", "Compute service name")
198
+ .option("--json", "Output as JSON")
199
+ .action(domainsList);
200
+
201
+ function domainsOpts(cmd) {
202
+ var parentOpts = cmd?.parent?.opts() || {};
203
+ return { cloud: parentOpts.cloud, compute: parentOpts.compute, dns: parentOpts.dns };
204
+ }
205
+
206
+ domainsCmd
207
+ .command("add [args...]")
208
+ .description("Add a custom domain (interactive if no domain given)")
209
+ .action((args, options, cmd) => domainsAdd(args, { ...options, ...domainsOpts(cmd) }));
210
+
211
+ domainsCmd
212
+ .command("remove <args...>")
213
+ .description("Remove a custom domain ([name] domain) - applies live")
214
+ .action((args, options, cmd) => domainsRemove(args, { ...options, ...domainsOpts(cmd) }));
215
+
216
+ // --- DB (topic root = list) ---
217
+
218
+ var dbCmd = program
219
+ .command("db")
220
+ .description("Manage databases");
221
+
222
+ dbCmd
223
+ .command("list", { isDefault: true })
224
+ .description("List all databases")
225
+ .option("--json", "Output as JSON")
226
+ .action(dbList);
227
+
228
+ dbCmd
229
+ .command("create <name>")
230
+ .description("Create a standalone database")
231
+ .option("--provider <id>", "Provider: cf, gcp, aws, or service name (e.g. neon)")
232
+ .option("--location <hint>", "Location hint (wnam, enam, weur, eeur, apac)")
233
+ .option("--jurisdiction <j>", "Jurisdiction (eu, fedramp)")
234
+ .option("--json", "Output as JSON")
235
+ .action(dbCreate);
236
+
237
+ dbCmd
238
+ .command("destroy <name>")
239
+ .description("Destroy a database")
240
+ .option("--confirm <name>", "Confirm by providing the database name")
241
+ .action(dbDestroy);
242
+
243
+ dbCmd
244
+ .command("info <name>")
245
+ .description("Show database details")
246
+ .option("--json", "Output as JSON")
247
+ .action(dbInfo);
248
+
249
+ dbCmd
250
+ .command("attach <name> [app]")
251
+ .description("Attach database to an app (injects env vars)")
252
+ .option("-c, --cloud <cloud>", "App cloud")
253
+ .option("--compute <name>", "App compute service")
254
+ .action(dbAttach);
255
+
256
+ dbCmd
257
+ .command("detach [app]")
258
+ .description("Detach database from an app (removes env vars)")
259
+ .option("-c, --cloud <cloud>", "App cloud")
260
+ .option("--compute <name>", "App compute service")
261
+ .action(dbDetach);
262
+
263
+ dbCmd
264
+ .command("shell <name>")
265
+ .description("Interactive SQL REPL")
266
+ .action(dbShell);
267
+
268
+ dbCmd
269
+ .command("query <args...>")
270
+ .description("Run a single SQL query ([name] <sql>)")
271
+ .option("--json", "Output as JSON")
272
+ .action(dbQuery);
273
+
274
+ dbCmd
275
+ .command("import <args...>")
276
+ .description("Import a .sql file (<name> <path>)")
277
+ .action(dbImport);
278
+
279
+ dbCmd
280
+ .command("export <name>")
281
+ .description("Export database as SQL dump")
282
+ .option("-o, --output <path>", "Write to file instead of stdout")
283
+ .action(dbExport);
284
+
285
+ dbCmd
286
+ .command("token <name>")
287
+ .description("Show or rotate auth token")
288
+ .option("--rotate", "Generate a new token")
289
+ .action(dbToken);
290
+
291
+ dbCmd
292
+ .command("reset <name>")
293
+ .description("Drop all tables (confirmation required)")
294
+ .option("--confirm <name>", "Confirm by providing the database name")
295
+ .action(dbReset);
296
+
297
+ // --- PS ---
298
+
299
+ program
300
+ .command("ps [name]")
301
+ .description("Show app containers and status")
302
+ .option("-c, --cloud <cloud>", "Cloud provider")
303
+ .option("--compute <name>", "Compute service name")
304
+ .option("--json", "Output as JSON")
305
+ .action(ps);
306
+
307
+ // --- Logs ---
308
+
309
+ program
310
+ .command("logs [name]")
311
+ .description("Stream live logs from an app")
312
+ .option("-c, --cloud <cloud>", "Cloud provider")
313
+ .option("--compute <name>", "Compute service name")
314
+ .action(logs);
315
+
316
+ // --- Open ---
317
+
318
+ program
319
+ .command("open [name]")
320
+ .description("Open app in browser")
321
+ .option("-c, --cloud <cloud>", "Cloud provider")
322
+ .option("--compute <name>", "Compute service name")
323
+ .action(open);
324
+
325
+ // --- Regions ---
326
+
327
+ program
328
+ .command("regions")
329
+ .description("List available deployment regions")
330
+ .option("-c, --cloud <cloud>", "Cloud provider")
331
+ .option("--compute <name>", "Compute service name")
332
+ .option("--json", "Output as JSON")
333
+ .action(async function (options) {
334
+ var { resolveTarget } = await import("./lib/providers/resolve.js");
335
+ var target = await resolveTarget(options);
336
+ var appProvider = await target.provider("app");
337
+
338
+ var regions = appProvider.getRegions();
339
+ if (options.json) {
340
+ console.log(JSON.stringify(regions, null, 2));
341
+ return;
342
+ }
343
+ console.log("");
344
+ for (var r of regions) {
345
+ console.log(` ${fmt.bold(r.code.padEnd(6))} ${r.name.padEnd(25)} ${fmt.dim(r.location)}`);
346
+ }
347
+ console.log("");
348
+ console.log(fmt.dim(" These are location hints. The cloud provider will attempt to place"));
349
+ console.log(fmt.dim(" containers near the specified region but exact placement is not guaranteed."));
350
+ console.log("");
351
+ });
352
+
353
+ // --- Cost ---
354
+
355
+ program
356
+ .command("cost [name]")
357
+ .description("Show estimated costs for an app or all apps")
358
+ .option("-c, --cloud <cloud>", "Cloud provider")
359
+ .option("--compute <name>", "Compute service name")
360
+ .option("--since <period>", "Date range: Nd (e.g. 7d) or YYYY-MM-DD (default: month to date)")
361
+ .option("--json", "Output as JSON")
362
+ .action(cost);
363
+
25
364
  // --- Doctor ---
26
365
 
27
366
  program
@@ -29,4 +368,14 @@ program
29
368
  .description("Check system setup and cloud connectivity")
30
369
  .action(doctor);
31
370
 
371
+ // --- Top-level aliases ---
372
+
373
+ program
374
+ .command("destroy [name]")
375
+ .description("Destroy an app (alias for apps destroy)")
376
+ .option("-c, --cloud <cloud>", "Cloud provider")
377
+ .option("--compute <name>", "Compute service name")
378
+ .option("--confirm <name>", "Confirm by providing the app name")
379
+ .action(appsDestroy);
380
+
32
381
  program.parse();