@hyze-cloud/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.
- package/LICENSE +21 -0
- package/README.md +575 -0
- package/dist/index.js +9125 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Hyze Cloud
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
# Hyze Cloud CLI
|
|
2
|
+
|
|
3
|
+
`hyze` is the command-line client for the Hyze Cloud public API. It covers the
|
|
4
|
+
deploy loop end to end: log in, list projects, deploy, watch the build, read the
|
|
5
|
+
logs and check what is running — without leaving the terminal.
|
|
6
|
+
|
|
7
|
+
- **Seven commands, no API mirror.** Each one answers a question a person or a
|
|
8
|
+
script actually asks.
|
|
9
|
+
- **An interactive screen for the bare `hyze`**: the project list, one project's
|
|
10
|
+
facts and the actions you run on it — deploy, restart, start/stop with a
|
|
11
|
+
confirmation, open in the browser — plus a deploy's live progress and the
|
|
12
|
+
project's logs, with the keys taught on screen. `hyze --help` still prints help.
|
|
13
|
+
- Three runtime dependencies: `commander`, plus `ink` and `react` for that
|
|
14
|
+
screen; runs on **Node 18+** and **Bun**
|
|
15
|
+
- **Machine-readable output**: `--json` pipes the API response untouched, and a
|
|
16
|
+
pipe (no TTY) defaults to JSON, so `hyze projects --json | jq` is safe
|
|
17
|
+
- **Human errors**: one line, the fix, and a stable exit code — never a stack trace
|
|
18
|
+
- ZIP builds from a directory, honoring `.hyzeignore`, `git ls-files` or built-in ignores
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install -g @hyze-cloud/cli # or: bun add -g @hyze-cloud/cli
|
|
24
|
+
hyze --version
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Standalone binary (no Node needed; on macOS the script ad-hoc signs the output):
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bun run build:compile # produces dist/hyze
|
|
31
|
+
sudo mv dist/hyze /usr/local/bin/hyze
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quickstart
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
hyze login # paste a key created in the dashboard (Settings → Developer)
|
|
38
|
+
hyze projects # what exists in this workspace
|
|
39
|
+
hyze deploy . --name my-api --runtime bun --port 3000 --subdomain my-api.hyzecloud.app
|
|
40
|
+
hyze status my-api # is it up? unknown when the platform cannot see the container
|
|
41
|
+
hyze deployments my-api # build history: queue place, waiting reason, cause of death
|
|
42
|
+
hyze logs my-api # container output; `hyze logs my-api <deploymentId>` for the build log
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Commands
|
|
46
|
+
|
|
47
|
+
| Command | Talks to | What it does |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| `login [--key <key>] [--no-verify]` | `GET /apps/`, `GET /plans/current` | store an API key, after checking it against the API |
|
|
50
|
+
| `logout` | — | remove the stored key of the active profile |
|
|
51
|
+
| `projects [--query] [--page] [--limit]` | `GET /apps/` | list the workspace's projects |
|
|
52
|
+
| `deploy [path] [--name] [--app] [--repo] …` | `GET /platform/config`, `POST /apps/inspect-env`, `POST /apps/deploy`, `GET /apps/:appId/deployments/:id` | zip (or send a `.zip`), upload, follow the build |
|
|
53
|
+
| `deployments [appId] [deploymentId]` | `GET /apps/:appId/deployments[/:id]` | build history, or one deployment with its stage timeline |
|
|
54
|
+
| `logs [appId] [deploymentId]` | `GET /apps/:appId/logs`, `GET /apps/:appId/deployments/:id` | container output, or the persisted build log |
|
|
55
|
+
| `status [appId]` | `GET /apps/:appId` | the project's current state |
|
|
56
|
+
|
|
57
|
+
`appId` is optional wherever a project is the subject: `.hyzerc.json` binds one
|
|
58
|
+
to the directory, so inside a project `hyze status`, `hyze logs` and
|
|
59
|
+
`hyze deploy .` need no arguments.
|
|
60
|
+
|
|
61
|
+
```json
|
|
62
|
+
// .hyzerc.json — nearest ancestor wins
|
|
63
|
+
{ "appId": "app_abc123", "workspaceId": "org_abc", "apiUrl": "https://api.hyzecloud.com/api" }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Interactive screen
|
|
67
|
+
|
|
68
|
+
In a terminal, `hyze` with no arguments opens a small screen instead of printing
|
|
69
|
+
help (`hyze --help` still prints help). It reads the same API through the same
|
|
70
|
+
client as the commands above:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
Projects 4 projects
|
|
74
|
+
|
|
75
|
+
> ● Running shop-api shop-api.hyzecloud.app
|
|
76
|
+
○ Stopped docs docs.hyzecloud.app
|
|
77
|
+
◐ Deploying worker worker.hyzecloud.app
|
|
78
|
+
✕ Error billing billing.hyzecloud.app
|
|
79
|
+
|
|
80
|
+
↑↓ Navigate · Enter Open · / Search · ? Help · q Quit
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
`Enter` opens the highlighted project — its state, its URL when it last shipped,
|
|
84
|
+
the hostnames it answers on, the variables it runs with — and everything you do
|
|
85
|
+
to it from here: deploy it, restart it, start or stop it, open it in the browser,
|
|
86
|
+
read its builds, set a variable, add a domain:
|
|
87
|
+
|
|
88
|
+
```
|
|
89
|
+
Projects › shop-api
|
|
90
|
+
|
|
91
|
+
● Running
|
|
92
|
+
|
|
93
|
+
URL https://shop-api.hyzecloud.app
|
|
94
|
+
Last deploy Success · 3h ago
|
|
95
|
+
|
|
96
|
+
Domains
|
|
97
|
+
shop.example.com Active · certificate active
|
|
98
|
+
CNAME apps.hyzecloud.app
|
|
99
|
+
|
|
100
|
+
Environment
|
|
101
|
+
API_KEY hyze_abcd…wxyz
|
|
102
|
+
DATABASE_URL postgre…hop
|
|
103
|
+
|
|
104
|
+
d Deploy · r Restart · s Stop · b Builds · o Open in the browser · l Logs
|
|
105
|
+
e Set a variable · a Add a domain · v Show the values · Esc Back · ? Help · q Quit
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The variables are masked — recognisable, not usable — and `v` shows them in
|
|
109
|
+
full and hides them again; a variable with no value keeps its name and gets no
|
|
110
|
+
value. `e` sets one, spelled `KEY=value` the way `-e` is spelled everywhere
|
|
111
|
+
else, and it asks before it writes because the app restarts to apply it:
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
Set KEY=value: LOG_LEVEL=debug
|
|
115
|
+
|
|
116
|
+
╭───────────────────────────────────────────────────╮
|
|
117
|
+
│ Set LOG_LEVEL? │
|
|
118
|
+
│ Saves it and restarts the app to apply it. │
|
|
119
|
+
╰───────────────────────────────────────────────────╯
|
|
120
|
+
|
|
121
|
+
y/Enter Save · n/Esc Cancel
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`a` adds a hostname, and the line it lands with is the one that finishes the
|
|
125
|
+
job — where to point DNS, taken from the API, with the certificate's own state
|
|
126
|
+
beside each hostname on the list above:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
● Added api.example.com — point a CNAME at apps.hyzecloud.app
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`r` restarts the app and says where it got to — `◐ Restarting…`, then
|
|
133
|
+
`● Restarted` — and the project's state on the line above is re-read, so the
|
|
134
|
+
screen shows what the API sees. `s` means the verb that changes something: `Stop`
|
|
135
|
+
while the app is up, `Start` when it is not. Stopping takes the app off the air,
|
|
136
|
+
so it asks first, and nothing goes out until you answer:
|
|
137
|
+
|
|
138
|
+
```
|
|
139
|
+
Projects › shop-api
|
|
140
|
+
|
|
141
|
+
● Running
|
|
142
|
+
|
|
143
|
+
URL https://shop-api.hyzecloud.app
|
|
144
|
+
Last deploy Success · 3h ago
|
|
145
|
+
|
|
146
|
+
╭──────────────────────────────────────────────────────────╮
|
|
147
|
+
│ Stop shop-api? │
|
|
148
|
+
│ The app stops serving until it is started again. │
|
|
149
|
+
╰──────────────────────────────────────────────────────────╯
|
|
150
|
+
|
|
151
|
+
y/Enter Stop · n/Esc Cancel
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
`o` hands the URL to the browser this machine has; on a box without one it
|
|
155
|
+
prints the URL instead of failing:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
● No browser here — open it yourself: https://shop-api.hyzecloud.app
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
An action that fails reads as a sentence with the next step under it, and `r`
|
|
162
|
+
runs it again — a stop that failed asks again, the way the first one did:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
Restart failed
|
|
166
|
+
DOCKER_NOT_AVAILABLE · Docker daemon connection refused
|
|
167
|
+
→ Press r to ask again.
|
|
168
|
+
|
|
169
|
+
d Deploy · r Retry · s Stop · o Open in the browser · l Logs · Esc Back · ? Help · q Quit
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
From there, `d` deploys the directory you are standing in to that project — one
|
|
173
|
+
keystroke that uploads whatever folder the CLI was opened in, so it asks first,
|
|
174
|
+
and the question names the folder in full. A wrong directory is obvious before
|
|
175
|
+
anything is sent, and nothing goes out until you answer:
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
Projects › shop-api
|
|
179
|
+
|
|
180
|
+
● Running
|
|
181
|
+
|
|
182
|
+
URL https://shop-api.hyzecloud.app
|
|
183
|
+
Last deploy Success · 3h ago
|
|
184
|
+
|
|
185
|
+
╭──────────────────────────────────────────────────────────╮
|
|
186
|
+
│ Deploy to shop-api? │
|
|
187
|
+
│ /Users/me/projects/checkout-api │
|
|
188
|
+
│ This folder is uploaded and becomes a real deployment. │
|
|
189
|
+
╰──────────────────────────────────────────────────────────╯
|
|
190
|
+
|
|
191
|
+
y/Enter Deploy · n/Esc Cancel
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
`b` opens the project's build history — what is serving production, what is
|
|
195
|
+
building now, and the cause of the row you are looking at, when the API
|
|
196
|
+
reported one. Each control is taught only for the row that can take it: `x`
|
|
197
|
+
cancels a queued build, `t` redeploys a failed one, `v` reverts to the selected
|
|
198
|
+
one — which is the flow for 3am, when production is broken and the build that
|
|
199
|
+
broke it has to go:
|
|
200
|
+
|
|
201
|
+
```
|
|
202
|
+
Projects › shop-api › Builds 7 builds
|
|
203
|
+
|
|
204
|
+
Queued 7h ago 9f9f9f9f
|
|
205
|
+
Queued 8h ago 1a2b3c4d
|
|
206
|
+
Building 9h ago 2b3c4d5e in flight
|
|
207
|
+
> Success 1d ago 3c4d5e6f production
|
|
208
|
+
Failed 2d ago 4d5e6f70
|
|
209
|
+
Cancelled 3d ago 5e6f7081
|
|
210
|
+
|
|
211
|
+
Cause BUILD_FAILED · The build ran out of time.
|
|
212
|
+
|
|
213
|
+
↑↓ Navigate · t Redeploy · v Revert · Esc Back · ? Help · q Quit
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Reverting and cancelling ask first, the way stopping does, and neither claims
|
|
217
|
+
more than it did — a revert is queued, not live:
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
╭──────────────────────────────────────────────────────╮
|
|
221
|
+
│ Revert to 4d5e6f70? │
|
|
222
|
+
│ That source is rebuilt and goes live when it succeeds.│
|
|
223
|
+
╰──────────────────────────────────────────────────────╯
|
|
224
|
+
|
|
225
|
+
y/Enter Revert · n/Esc Cancel
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`d` on the deploy screen is the same path the command line walks: the stages are
|
|
229
|
+
the ones the platform reports; the bar beside them is decoration, and it is the
|
|
230
|
+
first thing to yield when the line is tight:
|
|
231
|
+
Queue #2 (1st on this machine) in queue · waiting for capacity: memory
|
|
232
|
+
Source /Users/me/projects/shop-api
|
|
233
|
+
|
|
234
|
+
Esc Back · ? Help · q Quit
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
When the build ends, the screen says where it is serving, or why it died —
|
|
238
|
+
`stopped by the platform (exit 137)` is Hyze stopping the container, not a
|
|
239
|
+
crash, and a deployment whose worker reported no cause gets no cause line:
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
Projects › shop-api › Deploy
|
|
243
|
+
|
|
244
|
+
✓ Preparing · ✓ Uploading · ✓ Queued · ✓ Source · ✓ Installing · ✕ Building
|
|
245
|
+
Source /Users/me/projects/shop-api
|
|
246
|
+
Cause stopped by the platform (exit 137)
|
|
247
|
+
Error The build ran out of time.
|
|
248
|
+
Build log: hyze logs app_1 dep_9f3
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
`l` follows the project's logs: the live console over the API's WebSocket when
|
|
252
|
+
the runtime can open one, and the persisted logs the API serves when it cannot
|
|
253
|
+
— either way the screen says which one you are reading:
|
|
254
|
+
|
|
255
|
+
```
|
|
256
|
+
Projects › shop-api › Logs 128 lines · following
|
|
257
|
+
|
|
258
|
+
● Live · WebSocket
|
|
259
|
+
[shop-api] listening on 3000
|
|
260
|
+
[shop-api] GET /health 200
|
|
261
|
+
|
|
262
|
+
↑↓ Scroll · PgUp/PgDn Page · / Search · f Follow · r Reconnect · Esc Back · ? Help · q Quit
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
| Key | What it does |
|
|
266
|
+
| --- | --- |
|
|
267
|
+
| `↑` `↓` | move the selection, or scroll the logs |
|
|
268
|
+
| `PgUp` `PgDn` | page through the logs |
|
|
269
|
+
| `Enter` | open the highlighted project, or answer the question on screen |
|
|
270
|
+
| `d` | deploy the current directory to that project — the question names the folder first |
|
|
271
|
+
| `r` | restart the app — or retry the load, deploy or action that failed |
|
|
272
|
+
| `s` | start or stop the app: whichever changes its state (`s` twice is not a stop — the stop is confirmed) |
|
|
273
|
+
| `b` | the project's build history, where a build is redeployed, reverted or cancelled |
|
|
274
|
+
| `e` | set an environment variable, typed as `KEY=value` (it asks: the app restarts to apply it) |
|
|
275
|
+
| `a` | add a custom domain — the DNS target to point at is on the screen |
|
|
276
|
+
| `v` | show or hide the variable values; in the build history, revert to the selected build |
|
|
277
|
+
| `t` | in the build history, redeploy the failed build that is selected |
|
|
278
|
+
| `x` | in the build history, cancel the queued build that is selected |
|
|
279
|
+
| `o` | open the app's URL in the browser |
|
|
280
|
+
| `l` | open that project's logs |
|
|
281
|
+
| `Esc` | back one screen (on the list it quits, in a question it cancels) |
|
|
282
|
+
| `/` | search by name, id or domain — in the logs, filter lines (`Enter` keeps the filter, `Esc` clears) |
|
|
283
|
+
| `f` | in the logs, follow or pause the incoming lines |
|
|
284
|
+
| `?` | the shortcut map |
|
|
285
|
+
| `q` `Ctrl+C` | quit |
|
|
286
|
+
|
|
287
|
+
- **The state is a word, not a colour.** `● Running` reads the same over SSH, in
|
|
288
|
+
a 16-colour terminal or with `NO_COLOR` set; colour only reinforces it.
|
|
289
|
+
- **It fits the terminal it is in.** Columns shrink with the window (the
|
|
290
|
+
subdomain is the first thing to drop) and rows are windowed around the
|
|
291
|
+
selection, so 60 columns over SSH behaves like 200 locally. It also stops
|
|
292
|
+
growing at 100 columns, so a very wide terminal keeps its labels next to their
|
|
293
|
+
values instead of a metre apart. The footer wraps a whole shortcut at a time.
|
|
294
|
+
- **What the API did not say, the screen does not say.** A variable with no
|
|
295
|
+
value keeps its name, a domain with no certificate gets no certificate line,
|
|
296
|
+
and a build that reported no cause gets no cause line. Secrets are masked by
|
|
297
|
+
default and `v` is the deliberate exception.
|
|
298
|
+
- **Failures stay human.** A rejected key says so and points at `hyze login`, a
|
|
299
|
+
dead connection says the API is unreachable — one line and the next step,
|
|
300
|
+
never a stack trace. `r` retries without leaving the screen. A live socket
|
|
301
|
+
that drops says so, reconnects on its own, and falls back to the persisted
|
|
302
|
+
logs; it never takes the screen down with it.
|
|
303
|
+
- **No terminal, no screen.** Over a pipe or in CI the bare `hyze` prints one
|
|
304
|
+
line saying the screen needs a TTY and exits `2`; every command above keeps
|
|
305
|
+
working with no TTY at all.
|
|
306
|
+
|
|
307
|
+
## Authentication and configuration
|
|
308
|
+
|
|
309
|
+
`hyze login` verifies the key against the API before storing it. In a terminal the
|
|
310
|
+
input is masked per keystroke (`*`), so a paste is visible without exposing the
|
|
311
|
+
secret; piped stdin and `--key <hyze_...>` skip the prompt entirely — which is
|
|
312
|
+
how CI feeds it:
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
printf '%s' "$HYZE_API_KEY" | hyze login --no-verify
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```
|
|
319
|
+
$ hyze login
|
|
320
|
+
Paste your Hyze API key (hyze_...) and press Enter:
|
|
321
|
+
Input is hidden: each character shows as * (backspace works, Ctrl-C cancels).
|
|
322
|
+
> ****************************
|
|
323
|
+
Checking the key against https://api.hyzecloud.com/api…
|
|
324
|
+
✔ Logged in as hyze_fBGv…DJhq (profile "default")
|
|
325
|
+
Config written to /Users/you/.config/hyze/config.json
|
|
326
|
+
Workspace: iHZsZubLpZOAHnKebXpW7MibIB12Y2UV
|
|
327
|
+
Apps visible to this key: 4
|
|
328
|
+
Plan: Enterprise
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
| Location | Purpose |
|
|
332
|
+
| --- | --- |
|
|
333
|
+
| `~/.config/hyze/config.json` | profiles, active profile, default output (mode `0600`) |
|
|
334
|
+
| `.hyzerc.json` (nearest ancestor) | per-project defaults: `appId`, `apiUrl`, `workspaceId`, `profile` |
|
|
335
|
+
|
|
336
|
+
Precedence, highest first: **CLI flag → environment variable → `.hyzerc.json` → active profile → default**.
|
|
337
|
+
An empty variable counts as absent: `HYZE_API_KEY=""` does not shadow the profile.
|
|
338
|
+
|
|
339
|
+
| Variable | Effect |
|
|
340
|
+
| --- | --- |
|
|
341
|
+
| `HYZE_API_KEY` | API key |
|
|
342
|
+
| `HYZE_API_URL` / `HYZE_API_BASE_URL` | API base (a bare host gets `/api` appended) |
|
|
343
|
+
| `HYZE_WORKSPACE_ID` | workspace to scope requests to |
|
|
344
|
+
| `HYZE_PROFILE` | profile name |
|
|
345
|
+
| `HYZE_OUTPUT` | `table` \| `json` \| `ndjson` \| `text` |
|
|
346
|
+
| `HYZE_CONFIG` / `HYZE_CONFIG_DIR` | config file / config directory |
|
|
347
|
+
| `NO_COLOR` | disable colors |
|
|
348
|
+
|
|
349
|
+
## Global options
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
--profile <name> config profile to use
|
|
353
|
+
--api-key <key> API key (overrides env and the stored profile)
|
|
354
|
+
--api-url <url> API base URL
|
|
355
|
+
--workspace <id> workspace to scope requests to
|
|
356
|
+
--config <path> config file path
|
|
357
|
+
--timeout <seconds> per-request timeout (default 60)
|
|
358
|
+
-o, --output <format> table | json | ndjson | text
|
|
359
|
+
--json shorthand for -o json (the API response, untouched)
|
|
360
|
+
--no-color disable colors
|
|
361
|
+
-q, --quiet suppress informational output (stdout stays clean)
|
|
362
|
+
--verbose log every HTTP request and extra detail
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
`table` is the default when stdout is a terminal, `json` otherwise — piping always
|
|
366
|
+
yields JSON without asking. Payloads go to **stdout**, progress and messages to
|
|
367
|
+
**stderr**, so `hyze deployments app_1 --json | jq '.deployments[0].status'` is safe.
|
|
368
|
+
|
|
369
|
+
### What `--json` prints
|
|
370
|
+
|
|
371
|
+
The API response, untouched — no re-shaped "CLI format" to keep in sync:
|
|
372
|
+
|
|
373
|
+
| Command | Payload |
|
|
374
|
+
| --- | --- |
|
|
375
|
+
| `projects` | `GET /apps/`: `{ success, apps, meta }` |
|
|
376
|
+
| `deployments <appId>` | `GET /apps/:appId/deployments`: `{ success, deployments, currentDeploymentId, activeDeploymentId, meta }` |
|
|
377
|
+
| `deployments <appId> <id>` | `GET /apps/:appId/deployments/:id`: `{ success, deployment, timeline }` |
|
|
378
|
+
| `logs <appId>` | `GET /apps/:appId/logs`: `{ success, logs }` |
|
|
379
|
+
| `logs <appId> <id>` | `GET /apps/:appId/deployments/:id`: `{ success, deployment, timeline }` |
|
|
380
|
+
| `deploy` | waiting: the settled `{ success, deployment, timeline }`; `--no-wait`: the `POST /apps/deploy` response |
|
|
381
|
+
| `status <appId>` | the state the CLI resolved (below), one shape with or without a container |
|
|
382
|
+
|
|
383
|
+
`status` is the one command that resolves instead of forwarding: the API cannot
|
|
384
|
+
always see the container, and a made-up `stopped` would read as "the app is down".
|
|
385
|
+
|
|
386
|
+
```json
|
|
387
|
+
{ "success": true, "appId": "app_abc", "name": "api", "status": "running",
|
|
388
|
+
"runtime": "bun", "url": "https://api.hyzecloud.app", "memoryMB": 512,
|
|
389
|
+
"uptimeSeconds": 3661, "reason": null }
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Reading a deployment
|
|
393
|
+
|
|
394
|
+
`hyze deployments <appId>` prints one row per attempt, with the two facts the API
|
|
395
|
+
exposes about a build that is not simply running or done:
|
|
396
|
+
|
|
397
|
+
```
|
|
398
|
+
created status deployment queue waiting cause branch commit title
|
|
399
|
+
─────────────────── ─────── ─────────── ──────────────────────── ──────────── ────────────────────────────────── ────── ──────── ──────
|
|
400
|
+
2026-09-16 09:12:04 queued dep_queued #3 (2nd on this machine) machine-full — deploy in flight
|
|
401
|
+
2026-09-16 09:01:55 failed dep_stopped stopped by the platform (exit 137) main a1b2c3d4 deploy —
|
|
402
|
+
2026-09-16 08:44:10 success dep_done main d4e5f6a7 deploy production
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
- **Queue**: `#3 in the queue (2nd on this machine)` is the API's 1-based place,
|
|
406
|
+
global and per machine. `null` means "not queued", and the column stays empty.
|
|
407
|
+
- **Cause of death**: `exitCode`/`oomKilled` come from the worker. `null` means it
|
|
408
|
+
was never reported — the CLI draws **no cause line** rather than guessing.
|
|
409
|
+
`137`/`143` are our own drain/deploy stop, so they read as *stopped by the
|
|
410
|
+
platform*, not as a crash; an OOM kill says so.
|
|
411
|
+
- **Production / in flight** markers come from `currentDeploymentId` and
|
|
412
|
+
`activeDeploymentId` in the same response.
|
|
413
|
+
|
|
414
|
+
## Deploy details
|
|
415
|
+
|
|
416
|
+
Key flags: `--name`, `--app <appId>` (redeploy, defaults to the project in
|
|
417
|
+
`.hyzerc.json`), `--runtime node|bun|python`, `--memory <mb>`, `--port`/`--subdomain`
|
|
418
|
+
(must be passed together), `--env KEY=VALUE` (repeatable), `--env-file`,
|
|
419
|
+
`--root-dir`, `--start-command`, `--install-command`, `--build-command`,
|
|
420
|
+
`--app-type`, `--machine`, `--exclude`/`--include`/`--include-ignored`,
|
|
421
|
+
`--no-wait`, and `--repo <owner/name> [--branch] [--auto-deploy]` for GitHub.
|
|
422
|
+
|
|
423
|
+
When `--runtime` is omitted the CLI asks the API to inspect the upload and uses the
|
|
424
|
+
detected runtime. With waiting enabled (default) it follows the deployment stage
|
|
425
|
+
timeline on stderr and prints the settled deployment on stdout; a failed deployment
|
|
426
|
+
exits `1` with the API error and a pointer to `hyze logs <appId> <deploymentId>`.
|
|
427
|
+
|
|
428
|
+
**The wait is a block, not a spinner.** On a terminal the CLI redraws it in place, from
|
|
429
|
+
facts the API sent: the row's own clock, every stage the platform stamped with its own
|
|
430
|
+
duration (a stage still running reads the time since `startedAt`), the phase the worker
|
|
431
|
+
wrote for *this* build (`GET /apps/:appId/deployments/:id/build-progress`, drawn only when
|
|
432
|
+
the worker scopes it to this deployment) and, while the queue holds it, the place and the
|
|
433
|
+
reason — which the detail route does not carry, so they are read from the deployment's row
|
|
434
|
+
in the list. A fact the API did not send is a line that is not drawn; there is no
|
|
435
|
+
percentage, because the contract has none.
|
|
436
|
+
|
|
437
|
+
```
|
|
438
|
+
shop-api · building · 14s
|
|
439
|
+
✔ queue 4s
|
|
440
|
+
✔ install 5s
|
|
441
|
+
● build 4s
|
|
442
|
+
worker · building · updated 2s ago
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
The block belongs to a terminal: in a pipe it writes nothing — no `\r`, no ANSI, no spinner
|
|
446
|
+
— and `--json` / `-o json` / `ndjson` keep their exact payload. What ended the wait is
|
|
447
|
+
printed as a block of its own, and every line of it is a field the API sent:
|
|
448
|
+
|
|
449
|
+
```
|
|
450
|
+
✔ shop-api is now running
|
|
451
|
+
project shop-api
|
|
452
|
+
deployment dep_7f3a
|
|
453
|
+
status success
|
|
454
|
+
url https://shop-api.hyzecloud.app
|
|
455
|
+
duration 16s
|
|
456
|
+
created 2026-09-17T10:09:57.719Z
|
|
457
|
+
source zip
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
```
|
|
461
|
+
project shop-api
|
|
462
|
+
deployment dep_7f3b
|
|
463
|
+
status failed
|
|
464
|
+
stage install
|
|
465
|
+
cause exited with code 1
|
|
466
|
+
error npm install failed (exit 1)
|
|
467
|
+
code INSTALL_FAILED
|
|
468
|
+
logs hyze logs app_9c1 dep_7f3b
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
`stage` is the stage the timeline records as failed, `cause` is what the worker reported
|
|
472
|
+
about the process (`exitCode`/`oomKilled` — a deployment whose worker reported neither gets
|
|
473
|
+
no cause line) and `duration` is the newest stage end minus the row's `createdAt`.
|
|
474
|
+
|
|
475
|
+
**The upload limit is data, not a constant.** Before zipping, the CLI reads
|
|
476
|
+
`GET /platform/config` (`maxZipUploadMb`) and refuses an oversized archive itself,
|
|
477
|
+
naming the platform's own number:
|
|
478
|
+
|
|
479
|
+
```
|
|
480
|
+
✖ ZIP is too large: 812.4 MB (the platform limit is 256 MB)
|
|
481
|
+
Remove build artifacts (node_modules, .next, dist) from the archive and try again.
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
**What gets uploaded** — in this order: `--include`/`--exclude` patterns, `.hyzeignore`
|
|
485
|
+
(gitignore-style: `*`, `**`, `dir/`, `!keep`), `git ls-files --cached --others
|
|
486
|
+
--exclude-standard` inside a work tree, otherwise a walk with built-in ignores
|
|
487
|
+
(`.git`, `node_modules`, dist caches, `*.log`, …). `.git` and `node_modules` are never uploaded.
|
|
488
|
+
|
|
489
|
+
## Logs
|
|
490
|
+
|
|
491
|
+
`hyze logs <appId>` prints the container output (`--tail 1-1000`, `--timestamps`).
|
|
492
|
+
`hyze logs <appId> <deploymentId>` prints the install/build log the API persisted
|
|
493
|
+
for that attempt. `--follow` polls and prints only what is new — for a deployment
|
|
494
|
+
it stops when the build settles, for a container it runs until interrupted:
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
hyze logs my-api --follow -o text # --follow writes plain lines, so it needs text output
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
The live socket (streaming without polling) is not implemented yet.
|
|
501
|
+
|
|
502
|
+
## Errors and exit codes
|
|
503
|
+
|
|
504
|
+
Every failure is one line plus the fix, on stderr, with no stack trace (add
|
|
505
|
+
`--verbose` when you want one):
|
|
506
|
+
|
|
507
|
+
```
|
|
508
|
+
$ hyze deploy . --name api
|
|
509
|
+
✖ PAYLOAD_TOO_LARGE · ZIP is too large: the limit is 256MB.
|
|
510
|
+
The platform accepts up to 256 MB. Remove build artifacts (node_modules, .next, dist) from the archive and try again.
|
|
511
|
+
|
|
512
|
+
$ hyze projects
|
|
513
|
+
✖ UNAUTHORIZED · Unauthorized
|
|
514
|
+
The key is missing, expired or revoked. Run `hyze login` to store a new one (Settings → Developer).
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
| Code | Meaning |
|
|
518
|
+
| --- | --- |
|
|
519
|
+
| 0 | success |
|
|
520
|
+
| 1 | operation failed (including a failed deployment) |
|
|
521
|
+
| 2 | usage error: bad flags, missing key, no project selected, the screen without a TTY |
|
|
522
|
+
| 3 | unauthenticated / forbidden (including plan limits) |
|
|
523
|
+
| 4 | not found |
|
|
524
|
+
| 5 | rate limited (including the workspace build slot) |
|
|
525
|
+
| 6 | validation error (400/409/413/422) |
|
|
526
|
+
| 7 | server error (5xx) |
|
|
527
|
+
| 8 | network error or timeout |
|
|
528
|
+
| 130 | interrupted (Ctrl-C) |
|
|
529
|
+
|
|
530
|
+
Codes the CLI translates into advice: `PLAN_LIMIT` (plan memory), `PLAN_BUILD_LIMIT`
|
|
531
|
+
/ `TOO_MANY_DEPLOYS` (workspace build slot), `PAYLOAD_TOO_LARGE` (the limit comes
|
|
532
|
+
from `details.maxZipUploadMb`), `UNAUTHORIZED` (expired session), `APP_NOT_DEPLOYED`
|
|
533
|
+
(no container yet), `ZIP_ENCRYPTED`.
|
|
534
|
+
|
|
535
|
+
### Status and the "unknown" state
|
|
536
|
+
|
|
537
|
+
`hyze status <appId>` prints what the platform reports: `running`, `stopped`,
|
|
538
|
+
`paused`, `restarting`, `deploying`, `error`, `exited`, `created` — or `unknown`
|
|
539
|
+
when it cannot see the container at all (`APP_NOT_DEPLOYED`: no worker binding,
|
|
540
|
+
or `SERVICE_UNAVAILABLE`: the worker is down). `unknown` exits `0`: the question
|
|
541
|
+
was answered, the answer is that the platform does not know. A read that fails
|
|
542
|
+
(auth, 5xx, network) keeps its mapped exit code instead.
|
|
543
|
+
|
|
544
|
+
## Development
|
|
545
|
+
|
|
546
|
+
```bash
|
|
547
|
+
bun install
|
|
548
|
+
bun run dev -- --help # run from source
|
|
549
|
+
bun run typecheck
|
|
550
|
+
bun run lint
|
|
551
|
+
bun run test # unit + end-to-end tests (stub HTTP server, real binary)
|
|
552
|
+
bun run build # dist/index.js for npm
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
Layout: `src/core` (config, HTTP client, output, errors, progress, the deploy
|
|
556
|
+
path and the log helpers), `src/commands` (one module per command, each exporting
|
|
557
|
+
a `register*` function), `src/tui` (the interactive screen: `app.tsx` holds the
|
|
558
|
+
state, `keys.ts` declares every key of every screen — the footer is derived from
|
|
559
|
+
the same tables — `action.ts` is the vocabulary an action reports in,
|
|
560
|
+
`use-action.ts` runs the lifecycle calls and `browser.ts` opens a URL,
|
|
561
|
+
`stages.ts` and `format.ts` do the text and column maths, `screens/` renders),
|
|
562
|
+
`src/tests` (unit tests plus `cli.e2e.test.ts`, which drives the real binary
|
|
563
|
+
against a stub server — every spawn piped, every run with its own config
|
|
564
|
+
directory, so the suite is also the no-TTY path). The screen is covered three
|
|
565
|
+
times: `tui.render.test.tsx` renders it into fake streams at 60/80/120 columns
|
|
566
|
+
and presses keys, `tui.deploy.test.tsx`, `tui.logs.test.tsx` and
|
|
567
|
+
`tui.actions.test.tsx` do the same for a deploy's stages, its queue line and its
|
|
568
|
+
failures, a stubbed console socket, and the restart, the stop dialog, the
|
|
569
|
+
browser and an action that failed — and `tui.e2e.test.ts` runs the real binary
|
|
570
|
+
inside a PTY to check what the terminal ends up showing, including the lifecycle
|
|
571
|
+
calls it really sent and the cursor being handed back on exit.
|
|
572
|
+
|
|
573
|
+
## License
|
|
574
|
+
|
|
575
|
+
MIT
|