@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.
Files changed (4) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +575 -0
  3. package/dist/index.js +9125 -0
  4. 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