@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.
- package/README.md +4 -0
- package/bin/zerct.js +182 -5
- 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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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'
|