@zerct/zerct 0.1.5 → 0.1.7

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 (3) hide show
  1. package/README.md +4 -0
  2. package/bin/zerct.js +182 -5
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -20,6 +20,10 @@ on `0.0.0.0:$PORT` and expose the configured health endpoint.
20
20
  From a full-stack repo root, the same deploy command discovers nested
21
21
  `zerct.toml` files and deploys the whole workspace in one command.
22
22
 
23
+ Agents can also inspect API capabilities, account identity, usage, apps,
24
+ deploys, builds, app/deploy/build logs, env metadata, custom domains, and
25
+ billing portal links through the same CLI.
26
+
23
27
  On first deploy, the CLI opens browser login, waits for GitHub or Google, stores
24
28
  the Zerct session in the OS credential store when available, and continues the
25
29
  deploy. Later commands reuse that session.
package/bin/zerct.js CHANGED
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSy
4
4
  import { homedir } from 'node:os'
5
5
  import path from 'node:path'
6
6
 
7
- const VERSION = '0.1.5'
7
+ const VERSION = '0.1.7'
8
8
  const DEFAULT_API_URL = 'https://api.zerct.com'
9
9
  const ARCHIVE_LIMIT_BYTES = 48 * 1024 * 1024
10
10
  const SESSION_DIR = '.zerct'
@@ -65,12 +65,23 @@ Usage:
65
65
  zerct doctor [path] [--json]
66
66
  zerct login [--token <token>] [--api <url>]
67
67
  zerct deploy [path] [--database] [--api <url>] [--json]
68
- zerct logs --app <app_id> [--api <url>] [--json]
68
+ zerct capabilities [--api <url>] [--json]
69
+ zerct me [--api <url>] [--json]
70
+ zerct usage [--api <url>] [--json]
71
+ zerct apps [--api <url>] [--json]
72
+ zerct deploys [--app <app_id>] [--limit <n>] [--cursor <cursor>] [--api <url>] [--json]
73
+ zerct builds [--app <app_id>] [--limit <n>] [--cursor <cursor>] [--api <url>] [--json]
74
+ zerct logs --app <app_id> [--deploy <deploy_id>] [--build <build_id>] [--limit <n>] [--cursor <cursor>] [--api <url>] [--json]
69
75
  zerct status --app <app_id> [--api <url>] [--json]
70
76
  zerct inspect --app <app_id> [--api <url>] [--json]
71
77
  zerct db --app <app_id> [--api <url>] [--json]
78
+ zerct env list --app <app_id> [--api <url>] [--json]
72
79
  zerct env set --app <app_id> KEY=value [--api <url>] [--json]
73
- zerct billing [--api <url>] [--json]
80
+ zerct env delete --app <app_id> KEY [--api <url>] [--json]
81
+ zerct domains list --app <app_id> [--api <url>] [--json]
82
+ zerct domains add --app <app_id> <domain> [--api <url>] [--json]
83
+ zerct domains delete --app <app_id> <domain> [--api <url>] [--json]
84
+ zerct billing [portal] [--api <url>] [--json]
74
85
 
75
86
  Agent contract:
76
87
  - Rust backends keep Cargo.lock committed, listen on 0.0.0.0:$PORT, and return HTTP 200 from health.
@@ -108,6 +119,24 @@ async function main() {
108
119
  case 'deploy':
109
120
  await deploy(projectPath(cli.args[0]), cli)
110
121
  break
122
+ case 'capabilities':
123
+ await capabilities(cli)
124
+ break
125
+ case 'me':
126
+ await me(cli)
127
+ break
128
+ case 'usage':
129
+ await usage(cli)
130
+ break
131
+ case 'apps':
132
+ await apps(cli)
133
+ break
134
+ case 'deploys':
135
+ await deploys(cli)
136
+ break
137
+ case 'builds':
138
+ await builds(cli)
139
+ break
111
140
  case 'logs':
112
141
  await logs(cli)
113
142
  break
@@ -124,6 +153,9 @@ async function main() {
124
153
  case 'env':
125
154
  await envCommand(cli)
126
155
  break
156
+ case 'domains':
157
+ await domainsCommand(cli)
158
+ break
127
159
  case 'billing':
128
160
  await billing(cli)
129
161
  break
@@ -138,6 +170,10 @@ function parseArgs(argv) {
138
170
  args: [],
139
171
  apiUrl: DEFAULT_API_URL,
140
172
  app: '',
173
+ build: '',
174
+ deploy: '',
175
+ limit: '',
176
+ cursor: '',
141
177
  token: '',
142
178
  json: false,
143
179
  database: false,
@@ -164,6 +200,18 @@ function parseArgs(argv) {
164
200
  } else if (arg === '--app') {
165
201
  cli.app = requireValue(argv, index, '--app')
166
202
  index += 1
203
+ } else if (arg === '--build') {
204
+ cli.build = requireValue(argv, index, '--build')
205
+ index += 1
206
+ } else if (arg === '--deploy') {
207
+ cli.deploy = requireValue(argv, index, '--deploy')
208
+ index += 1
209
+ } else if (arg === '--limit') {
210
+ cli.limit = requireValue(argv, index, '--limit')
211
+ index += 1
212
+ } else if (arg === '--cursor') {
213
+ cli.cursor = requireValue(argv, index, '--cursor')
214
+ index += 1
167
215
  } else if (arg === '--token') {
168
216
  cli.token = requireValue(argv, index, '--token')
169
217
  index += 1
@@ -471,7 +519,17 @@ function printWorkspaceDeployResults(projectDir, results, cli) {
471
519
  }
472
520
 
473
521
  async function logs(cli) {
474
- const response = await appGet(cli, 'logs')
522
+ const token = await readOrLoginToken(process.cwd(), cli)
523
+ const page = pageQuery(cli)
524
+ let route = ''
525
+ if (cli.build) {
526
+ route = `/v1/builds/${encodeURIComponent(cli.build)}/logs${page}`
527
+ } else if (cli.deploy) {
528
+ route = `/v1/deploys/${encodeURIComponent(cli.deploy)}/logs${page}`
529
+ } else {
530
+ route = `/v1/apps/${encodeURIComponent(requireApp(cli))}/logs${page}`
531
+ }
532
+ const response = await apiRequest(cli, 'GET', route, token, null)
475
533
  if (cli.json) {
476
534
  console.log(JSON.stringify(response, null, 2))
477
535
  return
@@ -479,6 +537,55 @@ async function logs(cli) {
479
537
  for (const line of response.lines || []) {
480
538
  console.log(`[${line.timestamp}] ${line.stream}: ${line.message}`)
481
539
  }
540
+ if (response.has_more && response.next_cursor) {
541
+ const target = cli.build
542
+ ? `--build ${cli.build}`
543
+ : cli.deploy
544
+ ? `--deploy ${cli.deploy}`
545
+ : `--app ${requireApp(cli)}`
546
+ console.log(`next npx @zerct/zerct logs ${target} --cursor ${response.next_cursor}`)
547
+ }
548
+ }
549
+
550
+ async function apps(cli) {
551
+ const token = await readOrLoginToken(process.cwd(), cli)
552
+ const response = await apiRequest(cli, 'GET', '/v1/apps', token, null)
553
+ printJsonOrPretty(cli, response)
554
+ }
555
+
556
+ async function capabilities(cli) {
557
+ const response = await apiRequest(cli, 'GET', '/v1/capabilities', null, null)
558
+ printJsonOrPretty(cli, response)
559
+ }
560
+
561
+ async function me(cli) {
562
+ const token = await readOrLoginToken(process.cwd(), cli)
563
+ const response = await apiRequest(cli, 'GET', '/v1/me', token, null)
564
+ printJsonOrPretty(cli, response)
565
+ }
566
+
567
+ async function usage(cli) {
568
+ const token = await readOrLoginToken(process.cwd(), cli)
569
+ const response = await apiRequest(cli, 'GET', '/v1/usage', token, null)
570
+ printJsonOrPretty(cli, response)
571
+ }
572
+
573
+ async function deploys(cli) {
574
+ const token = await readOrLoginToken(process.cwd(), cli)
575
+ const route = cli.app
576
+ ? `/v1/apps/${encodeURIComponent(cli.app)}/deploys${pageQuery(cli)}`
577
+ : `/v1/deploys${pageQuery(cli)}`
578
+ const response = await apiRequest(cli, 'GET', route, token, null)
579
+ printJsonOrPretty(cli, response)
580
+ }
581
+
582
+ async function builds(cli) {
583
+ const token = await readOrLoginToken(process.cwd(), cli)
584
+ const route = cli.app
585
+ ? `/v1/apps/${encodeURIComponent(cli.app)}/builds${pageQuery(cli)}`
586
+ : `/v1/builds${pageQuery(cli)}`
587
+ const response = await apiRequest(cli, 'GET', route, token, null)
588
+ printJsonOrPretty(cli, response)
482
589
  }
483
590
 
484
591
  async function status(cli) {
@@ -497,8 +604,26 @@ async function database(cli) {
497
604
  }
498
605
 
499
606
  async function envCommand(cli) {
607
+ if (cli.args[0] === 'list') {
608
+ const response = await appGet(cli, 'env')
609
+ printJsonOrPretty(cli, response)
610
+ return
611
+ }
612
+
613
+ if (cli.args[0] === 'delete') {
614
+ const name = cli.args[1] || ''
615
+ if (!name) {
616
+ throw agentError('invalid_env', 'Environment variable name is required.', 'Use `npx @zerct/zerct env delete --app <app_id> KEY`.', cli.json)
617
+ }
618
+ const token = await readOrLoginToken(process.cwd(), cli)
619
+ const app = requireApp(cli)
620
+ const response = await apiRequest(cli, 'DELETE', `/v1/apps/${encodeURIComponent(app)}/env/${encodeURIComponent(name)}`, token, null)
621
+ printJsonOrPretty(cli, response)
622
+ return
623
+ }
624
+
500
625
  if (cli.args[0] !== 'set') {
501
- throw agentError('unknown_command', 'Unknown env command.', 'Use `npx @zerct/zerct env set --app <app_id> KEY=value`.', cli.json)
626
+ throw agentError('unknown_command', 'Unknown env command.', 'Use `npx @zerct/zerct env list`, `env set`, or `env delete`.', cli.json)
502
627
  }
503
628
 
504
629
  const assignment = cli.args[1] || ''
@@ -515,8 +640,48 @@ async function envCommand(cli) {
515
640
  printJsonOrPretty(cli, response)
516
641
  }
517
642
 
643
+ async function domainsCommand(cli) {
644
+ const action = cli.args[0] || 'list'
645
+ if (action === 'list') {
646
+ const response = await appGet(cli, 'domains')
647
+ printJsonOrPretty(cli, response)
648
+ return
649
+ }
650
+
651
+ const domain = cli.args[1] || ''
652
+ if (!domain) {
653
+ throw agentError('missing_domain', 'Domain is required.', 'Use `npx @zerct/zerct domains add --app <app_id> api.example.com`.', cli.json)
654
+ }
655
+
656
+ const token = await readOrLoginToken(process.cwd(), cli)
657
+ const app = requireApp(cli)
658
+ if (action === 'add') {
659
+ const response = await apiRequest(cli, 'POST', `/v1/apps/${encodeURIComponent(app)}/domains`, token, { domain })
660
+ printJsonOrPretty(cli, response)
661
+ return
662
+ }
663
+ if (action === 'delete') {
664
+ const response = await apiRequest(cli, 'DELETE', `/v1/apps/${encodeURIComponent(app)}/domains/${encodeURIComponent(domain)}`, token, null)
665
+ printJsonOrPretty(cli, response)
666
+ return
667
+ }
668
+
669
+ throw agentError('unknown_command', 'Unknown domains command.', 'Use `domains list`, `domains add`, or `domains delete`.', cli.json)
670
+ }
671
+
518
672
  async function billing(cli) {
519
673
  const token = await readOrLoginToken(process.cwd(), cli)
674
+ if (cli.args[0] === 'portal') {
675
+ const response = await apiRequest(cli, 'POST', '/v1/billing/portal', token, null)
676
+ if (cli.json) {
677
+ console.log(JSON.stringify(response, null, 2))
678
+ return
679
+ }
680
+ console.log(response.checkout.url)
681
+ openUrl(response.checkout.url)
682
+ return
683
+ }
684
+
520
685
  const response = await apiRequest(cli, 'POST', '/v1/billing/checkout', token, {
521
686
  target_plan: 'pro',
522
687
  reason: 'Upgrade to Zerct Pro.'
@@ -599,6 +764,18 @@ function requireApp(cli) {
599
764
  return cli.app
600
765
  }
601
766
 
767
+ function pageQuery(cli) {
768
+ const params = new URLSearchParams()
769
+ if (cli.limit) {
770
+ params.set('limit', cli.limit)
771
+ }
772
+ if (cli.cursor) {
773
+ params.set('cursor', cli.cursor)
774
+ }
775
+ const value = params.toString()
776
+ return value ? `?${value}` : ''
777
+ }
778
+
602
779
  async function apiRequest(cli, method, route, token, body) {
603
780
  const headers = {
604
781
  accept: 'application/json'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zerct/zerct",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Deploy Rust backends and static frontends to Zerct.",
5
5
  "type": "module",
6
6
  "bin": {