@jango-blockchained/hoox-cli 0.8.0 → 0.9.1

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 +170 -59
  2. package/dist/index.js +471 -129
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,27 +1,32 @@
1
1
  # @jango-blockchained/hoox-cli
2
2
 
3
- > **See also:** [Hoox User Guide](../docs/home.md) · [CLI Command Reference](../docs/reference/cli-commands.md)
3
+ > **See also:** [Hoox User Guide](../docs/home.md) · [CLI Command Reference](../docs/reference/cli-commands.mdx)
4
4
 
5
5
  > Hoox CLI — manage Cloudflare Workers, infrastructure, secrets, and deployments.
6
6
 
7
7
  > **Runtime requirement:** Bun ≥ 1.2. The bin shebang and bundle target are Bun-only; `npm install -g` will install the package but the CLI will not run under Node.js.
8
8
 
9
- 528 unit tests, 21 command groups, 60+ subcommands.
9
+ **v0.9.1** — 411 unit tests, 25 command groups, 60+ subcommands. Modern-minimal visual refresh, completion footer, did-you-mean suggestions, custom help. **v0.9.0** had a banner version-lookup bug in global installs — fixed in 0.9.1.
10
10
 
11
11
  ## Features
12
12
 
13
- - **Interactive Setup**: `hoox init` guides you through project initialization with AI provider support
14
- - **Infrastructure as Code**: Manage D1, KV, R2, Queues, Vectorize, and Analytics via `hoox infra`
15
- - **Environment Management**: Declarative 27-key env matrix via `hoox config env`
16
- - **KV Config Sync**: 16-key manifest with `apply-manifest` for CONFIG_KV
17
- - **Database Operations**: Schema apply, migrations, query, export, reset via `hoox db`
18
- - **Deploy Automation**: Workers + dashboard + telegram webhook + KV config in one flow
19
- - **Operational Monitoring**: Health checks, recent trades, kill switch, queue depth, backup
20
- - **Repair & Recovery**: Comprehensive system check, guided rebuild, per-component repair
21
- - **Prerequisites Checks**: 7 tool/account/repository validations
22
- - **Secret Management**: Sync, check, and rotate Cloudflare secrets
23
- - **Interactive TUI**: Launch an interactive terminal UI when running `hoox` with no arguments
24
- - **Worker Management**: Initialize, develop, and deploy Cloudflare Workers
13
+ - **One-shot Onboarding** (`hoox onboard`): Full bootstrap from a fresh clone collects credentials, configures workers, provisions D1/KV, generates keys, pushes secrets, deploys dashboard. The recommended entry point for new users.
14
+ - **Config & Setup Wizards** (`hoox init`, `hoox setup`): Split into two steps — `init` writes `wrangler.jsonc` and collects integration secrets; `setup` generates keys, applies D1 schema, and pushes secrets to Cloudflare. Run separately when you need fine-grained control.
15
+ - **Infrastructure as Code**: Manage D1, KV, R2, Queues, Vectorize, and Analytics via `hoox infra`.
16
+ - **Environment Management**: Declarative 27-key env matrix via `hoox config env`.
17
+ - **KV Config Sync**: 16-key manifest with `apply-manifest` for CONFIG_KV.
18
+ - **Database Operations**: Schema apply, migrations, query, export, reset via `hoox db`.
19
+ - **Deploy Automation**: Workers + dashboard + telegram webhook + KV config in one flow.
20
+ - **Performance Measurement** (`hoox perf`): `fastpath` subcommand sends synthetic probes and reports p50/p95/p99 per-hop latency.
21
+ - **Observability** (`hoox trace`): Query Cloudflare Workers Observability REST API for events, metrics, and traces.
22
+ - **Health Checks** (`hoox check health`): The single source of truth for worker health probes. Run after deployments or in CI.
23
+ - **Operational Monitoring** (`hoox monitor`): Recent trades, kill switch, queue depth, backup, system logs.
24
+ - **Repair & Recovery** (`hoox repair`): Comprehensive system check, guided rebuild, per-component repair.
25
+ - **Top-level Secrets & Keys**: `hoox secrets` and `hoox keys` for Cloudflare Worker secrets and internal auth keys.
26
+ - **Unified Dashboard** (`hoox dashboard dev | deploy`): Start the dev server or build & deploy the Next.js dashboard.
27
+ - **Interactive TUI**: Launch an interactive terminal UI when running `hoox` with no arguments.
28
+ - **Modern-minimal output polish (v0.9.0)**: Refined zinc/indigo palette, completion footer with "next step" suggestions after every command, "did you mean …" for typos, custom help formatter, `--no-color` flag, `NO_COLOR` / `TERM=dumb` honored, `formatNumber` and `formatBytes` for compact tables. All available to plugin authors via the shared `format*` exports.
29
+ - **Shell Completions**: bash, zsh, and fish completion scripts via `hoox completion <shell>`.
25
30
 
26
31
  ## Installation
27
32
 
@@ -45,43 +50,55 @@ bun run build
45
50
  ## Quick Start
46
51
 
47
52
  ```bash
48
- # Launch interactive TUI (no arguments)
49
- hoox
53
+ # One-shot onboarding (collects credentials, provisions, deploys)
54
+ hoox onboard
50
55
 
51
- # Or use specific commands
52
- hoox --help
56
+ # Or step-by-step: write config first, then provision
53
57
  hoox init
54
- hoox dev
55
- hoox deploy
56
- hoox monitor status
58
+ hoox setup
59
+
60
+ # Verify the system
61
+ hoox check setup
62
+ hoox check health
63
+
64
+ # Deploy to Cloudflare
65
+ hoox deploy all --auto
66
+
67
+ # Measure fast-path latency
68
+ hoox perf fastpath run --n 50
57
69
  ```
58
70
 
59
71
  ## Available Commands
60
72
 
61
- | Command | Description |
62
- | ----------------- | ---------------------------------------------------------------- |
63
- | `hoox init` | Initialize a new Hoox project with worker configuration |
64
- | `hoox setup` | Auto-bootstrap infrastructure (auth, keys, D1, secrets) |
65
- | `hoox clone` | Clone worker repositories as git submodules |
66
- | `hoox dev` | Start local development environment for all workers |
67
- | `hoox deploy` | Deploy workers, dashboard, telegram webhook, and KV config |
68
- | `hoox infra` | Manage infrastructure (D1, KV, R2, Queues, Vectorize, Analytics) |
69
- | `hoox config` | Manage wrangler.jsonc, env vars, KV keys, and secrets |
70
- | `hoox check` | Validate setup, prerequisites, and worker health |
71
- | `hoox db` | Manage D1 databases (apply, migrate, query, export, reset) |
72
- | `hoox monitor` | Monitor health, trades, logs, kill switch, queue, backup |
73
- | `hoox workers` | Per-worker operations (list, status, dev, logs) |
74
- | `hoox repair` | Diagnose and repair the system (check, rebuild, per-component) |
75
- | `hoox schema` | Manage D1 schema and migrations |
76
- | `hoox update` | Self-update the CLI and check wrangler versions |
77
- | `hoox logs` | Stream and filter Cloudflare Worker logs |
78
- | `hoox test` | Run tests and CI pipeline |
79
- | `hoox waf` | Manage Cloudflare WAF rules and policies |
80
- | `hoox dashboard` | Launch or deploy the Next.js dashboard |
81
- | `hoox tui` | Launch the OpenTUI terminal dashboard |
82
- | `hoox agent` | AI agent operations (health probe) |
83
- | `hoox disclaimer` | Display legal disclaimer |
84
- | `hoox completion` | Generate shell completion script (bash, zsh, fish) |
73
+ | Command | Description |
74
+ | ----------------- | -------------------------------------------------------------------- |
75
+ | `hoox onboard` | **Recommended entry point.** One-shot full bootstrap (init + setup) |
76
+ | `hoox init` | Interactive setup wizard (writes `wrangler.jsonc`, collects secrets) |
77
+ | `hoox setup` | Auto-bootstrap infrastructure (auth, keys, D1, secrets, dashboard) |
78
+ | `hoox clone` | Clone worker repositories as git submodules |
79
+ | `hoox dev` | Start local development environment for all workers |
80
+ | `hoox deploy` | Deploy workers, dashboard, telegram webhook, and KV config |
81
+ | `hoox infra` | Manage infrastructure (D1, KV, R2, Queues, Vectorize, Analytics) |
82
+ | `hoox config` | Manage `wrangler.jsonc` configuration |
83
+ | `hoox secrets` | Manage Cloudflare Worker secrets |
84
+ | `hoox keys` | Manage internal auth keys |
85
+ | `hoox check` | Validate setup, prerequisites, and worker health |
86
+ | `hoox db` | Manage D1 databases (apply, migrate, query, export, reset) |
87
+ | `hoox monitor` | Monitor trades, logs, kill switch, queue, backup |
88
+ | `hoox workers` | Per-worker operations (list, dev, logs) |
89
+ | `hoox repair` | Diagnose and repair the system (check, rebuild, per-component) |
90
+ | `hoox schema` | Manage D1 schema and migrations |
91
+ | `hoox update` | Self-update the CLI and check wrangler versions |
92
+ | `hoox logs` | Stream and filter Cloudflare Worker logs |
93
+ | `hoox test` | Run tests and CI pipeline |
94
+ | `hoox waf` | Manage Cloudflare WAF rules and policies |
95
+ | `hoox dashboard` | Dashboard operations (`dev`, `deploy`) |
96
+ | `hoox tui` | Launch the OpenTUI terminal dashboard |
97
+ | `hoox agent` | AI agent operations (health probe) |
98
+ | `hoox trace` | Query Cloudflare Workers Observability (events, metrics, traces) |
99
+ | `hoox perf` | Performance measurement tools (`fastpath` probe-based latency) |
100
+ | `hoox disclaimer` | Display legal disclaimer |
101
+ | `hoox completion` | Generate shell completion script (bash, zsh, fish) |
85
102
 
86
103
  ### Global Options
87
104
 
@@ -94,13 +111,27 @@ All commands support these global options:
94
111
 
95
112
  ## Usage Examples
96
113
 
97
- ### Initialize a New Project
114
+ ### Onboard a New Project
98
115
 
99
116
  ```bash
100
- hoox init
117
+ # One-shot (recommended) — fully automatic, no separate setup step
118
+ hoox onboard --token cfut_xxx --account xxx --preset full
119
+
120
+ # Interactive
121
+ hoox onboard
101
122
  ```
102
123
 
103
- Interactive prompts guide you through AI provider setup, Cloudflare credentials, and exchange API keys.
124
+ Interactive prompts collect your Cloudflare Account ID, API Token, and subdomain prefix; then it writes the config and provisions infrastructure end-to-end.
125
+
126
+ ### Step-by-Step Onboarding
127
+
128
+ ```bash
129
+ # 1. Write workspace config (wrangler.jsonc, secrets)
130
+ hoox init
131
+
132
+ # 2. Provision infrastructure (keys, D1 schema, push secrets, deploy dashboard)
133
+ hoox setup
134
+ ```
104
135
 
105
136
  ### Check Prerequisites
106
137
 
@@ -114,6 +145,32 @@ hoox check prerequisites --tool bun
114
145
 
115
146
  Validates bun ≥1.2, git ≥2.40, wrangler, Docker, Cloudflare auth, and repository integrity.
116
147
 
148
+ ### Manage Secrets
149
+
150
+ ```bash
151
+ # List secrets for a worker
152
+ hoox secrets list trade-worker
153
+
154
+ # Set a secret
155
+ hoox secrets set trade-worker BINANCE_KEY_BINDING
156
+
157
+ # Delete a secret
158
+ hoox secrets delete trade-worker BINANCE_KEY_BINDING
159
+
160
+ # Sync local .dev.vars to Cloudflare
161
+ hoox secrets sync
162
+ ```
163
+
164
+ ### Manage Internal Keys
165
+
166
+ ```bash
167
+ # Generate new internal keys (writes to .keys/)
168
+ hoox keys generate
169
+
170
+ # List existing keys
171
+ hoox keys list
172
+ ```
173
+
117
174
  ### Configure Environment
118
175
 
119
176
  ```bash
@@ -130,6 +187,16 @@ hoox config env validate
130
187
  hoox config env generate-dev-vars
131
188
  ```
132
189
 
190
+ ### Manage wrangler.jsonc
191
+
192
+ ```bash
193
+ # Display current configuration
194
+ hoox config show
195
+
196
+ # Update a config value
197
+ hoox config set workers.agent-worker.enabled false
198
+ ```
199
+
133
200
  ### Manage KV Keys
134
201
 
135
202
  ```bash
@@ -171,7 +238,35 @@ hoox deploy kv-config
171
238
  hoox deploy worker trade-worker
172
239
  ```
173
240
 
174
- ### Manage Database
241
+ ### Dashboard Operations
242
+
243
+ ```bash
244
+ # Start dashboard dev server
245
+ hoox dashboard dev
246
+ # equivalent to: hoox dev dashboard
247
+
248
+ # Build and deploy the dashboard
249
+ hoox dashboard deploy
250
+ # equivalent to: hoox deploy dashboard
251
+
252
+ # Force rebuild before deploy
253
+ hoox dashboard deploy --rebuild
254
+ ```
255
+
256
+ ### Health Checks
257
+
258
+ ```bash
259
+ # Check all worker health endpoints (the single source of truth)
260
+ hoox check health
261
+
262
+ # Try to auto-fix any issues found
263
+ hoox check health --fix
264
+
265
+ # Full system validation (config, infra, secrets, database)
266
+ hoox check setup
267
+ ```
268
+
269
+ ### Database Operations
175
270
 
176
271
  ```bash
177
272
  # Apply schema to local or remote
@@ -213,9 +308,6 @@ hoox infra analytics list
213
308
  ### Monitor Operations
214
309
 
215
310
  ```bash
216
- # Check all worker health endpoints
217
- hoox monitor status
218
-
219
311
  # Show recent trades
220
312
  hoox monitor trades 20
221
313
 
@@ -235,6 +327,19 @@ hoox monitor queue-depth
235
327
  hoox monitor backup
236
328
  ```
237
329
 
330
+ ### Performance Measurement
331
+
332
+ ```bash
333
+ # Send 50 synthetic probes and report p50/p95/p99 latency
334
+ hoox perf fastpath run --n 50
335
+
336
+ # Continuous probing for 60 seconds
337
+ hoox perf fastpath tail --duration 60
338
+
339
+ # Query past probe events
340
+ hoox perf fastpath report --from 1h
341
+ ```
342
+
238
343
  ### Repair and Recovery
239
344
 
240
345
  ```bash
@@ -263,8 +368,8 @@ hoox repair rebuild
263
368
  ### JSON Output for Scripting
264
369
 
265
370
  ```bash
371
+ hoox check health --json
266
372
  hoox check prerequisites --json
267
- hoox monitor status --json
268
373
  hoox infra d1 list --json
269
374
  ```
270
375
 
@@ -330,16 +435,20 @@ packages/cli/
330
435
  ├── src/
331
436
  │ ├── index.ts # Main entry point
332
437
  │ ├── commands/
333
- │ │ ├── init/ # Interactive setup wizard
438
+ │ │ ├── init/ # Interactive setup wizard (config)
334
439
  │ │ ├── setup/ # Auto-bootstrap infrastructure
440
+ │ │ ├── onboard/ # One-shot init + setup
441
+ │ │ ├── secrets/ # Top-level secrets management
442
+ │ │ ├── keys/ # Top-level internal keys management
335
443
  │ │ ├── dev/ # Local development
336
444
  │ │ ├── deploy/ # Deploy, telegram-webhook, update-internal-urls
445
+ │ │ ├── dashboard/ # Dashboard dev/deploy
337
446
  │ │ ├── infra/ # D1, KV, R2, Queues, Vectorize, Analytics
338
- │ │ ├── config/ # Wrangler config, env, kv, secrets
339
- │ │ ├── check/ # Prerequisites, setup, health
447
+ │ │ ├── config/ # wrangler.jsonc, env, kv
448
+ │ │ ├── check/ # Prerequisites, setup, health, fix
340
449
  │ │ ├── db/ # Database operations
341
- │ │ ├── monitor/ # Health, trades, logs, kill-switch
342
- │ │ ├── workers/ # Per-worker operations
450
+ │ │ ├── monitor/ # Trades, logs, kill-switch, backup
451
+ │ │ ├── workers/ # List, dev, logs
343
452
  │ │ ├── repair/ # Check, worker, infra, secrets, rebuild
344
453
  │ │ ├── schema/ # Schema management
345
454
  │ │ ├── update/ # Self-update
@@ -347,9 +456,10 @@ packages/cli/
347
456
  │ │ ├── test/ # CI pipeline
348
457
  │ │ ├── waf/ # WAF management
349
458
  │ │ ├── clone/ # Submodule cloning
350
- │ │ ├── dashboard/ # Dashboard operations
351
459
  │ │ ├── tui/ # TUI launcher
352
460
  │ │ ├── agent/ # AI agent operations
461
+ │ │ ├── trace/ # Cloudflare Observability queries
462
+ │ │ ├── perf/ # Performance measurement
353
463
  │ │ └── disclaimer/ # Legal disclaimer
354
464
  │ ├── services/
355
465
  │ │ ├── cloudflare/ # Wrangler CLI wrapper
@@ -358,6 +468,7 @@ packages/cli/
358
468
  │ │ ├── docker/ # Docker compose wrapper
359
469
  │ │ ├── env/ # Environment definitions
360
470
  │ │ ├── kv/ # KV key management
471
+ │ │ ├── perf/ # Percentile, probe-sender, observability-reader
361
472
  │ │ ├── prerequisites/ # Tool version checks
362
473
  │ │ └── secrets/ # Secret management
363
474
  │ ├── ui/ # Interactive TUI
package/dist/index.js CHANGED
@@ -24169,7 +24169,7 @@ var init_schema_service = __esm(() => {
24169
24169
  });
24170
24170
 
24171
24171
  // src/index.ts
24172
- import { readFileSync as readFileSync6 } from "fs";
24172
+ import { readFileSync as readFileSync7 } from "fs";
24173
24173
 
24174
24174
  // ../../node_modules/.bun/commander@15.0.0/node_modules/commander/lib/error.js
24175
24175
  class CommanderError extends Error {
@@ -26249,59 +26249,65 @@ var import__ = __toESM(require_ansis(), 1);
26249
26249
  var ansis_default = import__.default;
26250
26250
 
26251
26251
  // src/utils/theme.ts
26252
- var green = ansis_default.green;
26253
- var red = ansis_default.red;
26254
- var yellow = ansis_default.yellow;
26255
- var blue = ansis_default.blue;
26256
- var cyan = ansis_default.cyan;
26257
- var magenta = ansis_default.magenta;
26258
- var dim = ansis_default.dim;
26259
- var bold = ansis_default.bold;
26260
- var italic = ansis_default.italic;
26261
- var underline = ansis_default.underline;
26262
- var gray = ansis_default.gray;
26263
- var white = ansis_default.white;
26264
- var black = ansis_default.black;
26265
- var bgGreen = ansis_default.bgGreen;
26266
- var bgRed = ansis_default.bgRed;
26267
- var bgYellow = ansis_default.bgYellow;
26268
- var bgBlue = ansis_default.bgBlue;
26252
+ var HEX = {
26253
+ text200: "#e4e4e7",
26254
+ text400: "#a1a1aa",
26255
+ text500: "#71717a",
26256
+ text600: "#52525b",
26257
+ text700: "#3f3f46",
26258
+ indigo300: "#a5b4fc",
26259
+ indigo400: "#818cf8",
26260
+ emerald400: "#34d399",
26261
+ rose400: "#fb7185",
26262
+ amber400: "#fbbf24",
26263
+ sky400: "#38bdf8"
26264
+ };
26269
26265
  var theme = {
26270
- success: green,
26271
- error: red,
26272
- warning: yellow,
26273
- info: blue,
26274
- accent: cyan,
26275
- highlight: magenta,
26276
- dim,
26277
- bold,
26278
- italic,
26279
- underline,
26280
- gray,
26281
- white,
26282
- black,
26283
- heading: bold.cyan,
26284
- label: dim,
26285
- subtle: gray,
26286
- muted: dim.gray,
26287
- value: bold.white,
26288
- key: dim.yellow,
26289
- statusOk: bgGreen.black(" OK "),
26290
- statusWarn: bgYellow.black(" WARN "),
26291
- statusError: bgRed.white(" FAIL "),
26292
- statusInfo: bgBlue.white(" INFO "),
26293
- separator: dim("\u2500"),
26294
- pipe: dim("\u2502"),
26295
- corner: dim("\u250C\u2510\u2514\u2518")
26266
+ text: ansis_default.hex(HEX.text200),
26267
+ textMuted: ansis_default.hex(HEX.text400),
26268
+ textSubtle: ansis_default.hex(HEX.text500),
26269
+ textFaint: ansis_default.hex(HEX.text600),
26270
+ border: ansis_default.hex(HEX.text700),
26271
+ borderStrong: ansis_default.hex(HEX.text600),
26272
+ success: ansis_default.hex(HEX.emerald400),
26273
+ error: ansis_default.hex(HEX.rose400),
26274
+ warning: ansis_default.hex(HEX.amber400),
26275
+ info: ansis_default.hex(HEX.sky400),
26276
+ accent: ansis_default.hex(HEX.indigo400),
26277
+ highlight: ansis_default.hex(HEX.indigo300),
26278
+ surface: ansis_default.hex("#18181b"),
26279
+ dim: ansis_default.hex(HEX.text400),
26280
+ bold: ansis_default.bold,
26281
+ italic: ansis_default.italic,
26282
+ underline: ansis_default.underline,
26283
+ gray: ansis_default.gray,
26284
+ white: ansis_default.white,
26285
+ black: ansis_default.black,
26286
+ heading: ansis_default.hex(HEX.indigo400).bold,
26287
+ label: ansis_default.hex(HEX.text500),
26288
+ subtle: ansis_default.hex(HEX.text500),
26289
+ muted: ansis_default.hex(HEX.text600),
26290
+ value: ansis_default.hex(HEX.text200),
26291
+ key: ansis_default.hex(HEX.text500),
26292
+ statusOk: ansis_default.hex(HEX.emerald400)("\u2713") + " " + ansis_default.hex(HEX.text200)("ok"),
26293
+ statusWarn: ansis_default.hex(HEX.amber400)("\u26A0") + " " + ansis_default.hex(HEX.text200)("warn"),
26294
+ statusError: ansis_default.hex(HEX.rose400)("\u2717") + " " + ansis_default.hex(HEX.text200)("error"),
26295
+ statusInfo: ansis_default.hex(HEX.sky400)("\u2139") + " " + ansis_default.hex(HEX.text200)("info"),
26296
+ separator: ansis_default.hex(HEX.text600)("\u2500"),
26297
+ pipe: ansis_default.hex(HEX.text600)("\u2502"),
26298
+ corner: ansis_default.hex(HEX.text600)("\u250C\u2510\u2514\u2518")
26296
26299
  };
26297
26300
  var icons = {
26298
26301
  success: "\u2713",
26299
26302
  error: "\u2717",
26300
- warning: "!",
26301
- info: "i",
26302
- arrow: "->",
26303
- spinner: ["-", "\\", "|", "/"],
26304
- dot: "\u25CF"
26303
+ warning: "\u26A0",
26304
+ info: "\u2139",
26305
+ arrow: "\u2192",
26306
+ spinner: ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"],
26307
+ dot: "\u25CF",
26308
+ sep: "\xB7",
26309
+ bullet: "\u2022",
26310
+ pipe: "\u2502"
26305
26311
  };
26306
26312
  function stripAnsi(str) {
26307
26313
  return ansis_default.strip(str);
@@ -26334,7 +26340,11 @@ function startTimer() {
26334
26340
 
26335
26341
  // src/utils/format-mode.ts
26336
26342
  function isRichMode(opts) {
26337
- if (opts?.json || opts?.quiet)
26343
+ if (opts?.json || opts?.quiet || opts?.noColor)
26344
+ return false;
26345
+ if (process.env.NO_COLOR)
26346
+ return false;
26347
+ if (process.env.TERM === "dumb")
26338
26348
  return false;
26339
26349
  return Boolean(process.stdout.isTTY);
26340
26350
  }
@@ -26375,45 +26385,72 @@ function formatSuccess(message, opts) {
26375
26385
  process.stdout.write(`${theme.success(icons.success)} ${message}
26376
26386
  `);
26377
26387
  }
26378
- function formatError2(error51, opts) {
26388
+ function formatError2(error51, opts = {}) {
26379
26389
  const message = typeof error51 === "string" ? error51 : error51.message;
26380
26390
  const cliError = error51 instanceof CLIError ? error51 : null;
26381
- if (opts?.json) {
26391
+ if (opts.json) {
26382
26392
  const output = {
26383
26393
  success: false,
26384
26394
  error: message,
26385
26395
  code: cliError?.code ?? 1 /* ERROR */
26386
26396
  };
26387
- if (cliError?.details) {
26397
+ if (cliError?.details)
26388
26398
  output.details = cliError.details;
26389
- }
26390
- if (cliError?.hint) {
26399
+ if (cliError?.hint)
26391
26400
  output.hint = cliError.hint;
26401
+ if (opts.suggestions && opts.suggestions.length > 0) {
26402
+ output.suggestions = opts.suggestions;
26392
26403
  }
26393
26404
  process.stdout.write(JSON.stringify(output) + `
26394
26405
  `);
26395
26406
  return;
26396
26407
  }
26397
- if (opts?.quiet) {
26408
+ if (opts.quiet) {
26398
26409
  process.stdout.write(`${message}
26399
26410
  `);
26400
26411
  return;
26401
26412
  }
26402
- process.stdout.write(`${theme.error(icons.error)} ${message}
26413
+ const inCard = opts.inCard !== false;
26414
+ const titleParts = [theme.error(icons.error)];
26415
+ if (cliError?.code !== undefined) {
26416
+ titleParts.push(theme.textSubtle(`[${cliError.code}]`));
26417
+ }
26418
+ titleParts.push(theme.text(message));
26419
+ process.stdout.write(`${titleParts.join(" ")}
26403
26420
  `);
26404
26421
  if (cliError?.details) {
26405
- process.stdout.write(` ${theme.dim(cliError.details)}
26422
+ process.stdout.write(` ${theme.textMuted(cliError.details)}
26406
26423
  `);
26407
26424
  }
26408
- if (cliError?.hint) {
26409
- process.stdout.write(`${theme.dim("\u21B3 hint:")} ${theme.value(cliError.hint)}
26425
+ const hasHint = !!cliError?.hint;
26426
+ const hasSuggestions = !!opts.suggestions && opts.suggestions.length > 0;
26427
+ if (hasHint || hasSuggestions) {
26428
+ if (inCard) {
26429
+ process.stdout.write(` ${theme.border(icons.pipe)}
26430
+ `);
26431
+ }
26432
+ if (hasHint) {
26433
+ process.stdout.write(` ${inCard ? theme.border(icons.pipe) : " "} ${theme.dim("\u21B3 hint:")} ${theme.value(cliError.hint)}
26434
+ `);
26435
+ }
26436
+ if (hasSuggestions) {
26437
+ const sugText = opts.suggestions.map((s) => `'${s}'`).join(", ");
26438
+ process.stdout.write(` ${inCard ? theme.border(icons.pipe) : " "} ${theme.dim("did you mean:")} ${theme.accent(sugText)} ${theme.dim("?")}
26439
+ `);
26440
+ }
26441
+ if (inCard) {
26442
+ process.stdout.write(` ${theme.border(icons.pipe)}
26443
+ `);
26444
+ }
26445
+ } else if (inCard) {
26446
+ process.stdout.write(` ${theme.border(icons.pipe)}
26410
26447
  `);
26411
26448
  }
26412
26449
  }
26413
- function formatTable(rows, opts) {
26414
- if (opts?.quiet)
26450
+ function formatTable(rows, opts = {}) {
26451
+ if (opts.quiet)
26415
26452
  return;
26416
- if (opts?.json) {
26453
+ if (opts.json) {
26417
26454
  process.stdout.write(JSON.stringify(rows, null, 2) + `
26418
26455
  `);
26419
26456
  return;
@@ -26423,11 +26460,42 @@ function formatTable(rows, opts) {
26423
26460
  `);
26424
26461
  return;
26425
26462
  }
26463
+ const zebra = opts.zebra !== false;
26464
+ const alignNumbers = opts.alignNumbers !== false;
26465
+ const colorizeStatus = opts.colorizeStatus !== false;
26466
+ const compact = opts.compact === true;
26426
26467
  const keys = Object.keys(rows[0]);
26468
+ const processed = rows.map((row) => {
26469
+ const out = {};
26470
+ for (const k of keys) {
26471
+ const raw = row[k] ?? "";
26472
+ out[k] = colorizeCell(raw, colorizeStatus);
26473
+ }
26474
+ return out;
26475
+ });
26476
+ const numericColumns = new Set;
26477
+ if (alignNumbers) {
26478
+ for (const k of keys) {
26479
+ let allNumeric = true;
26480
+ let anyNonEmpty = false;
26481
+ for (const row of processed) {
26482
+ const v = stripAnsi(row[k] ?? "");
26483
+ if (v.length === 0)
26484
+ continue;
26485
+ anyNonEmpty = true;
26486
+ if (!/^-?\d+(\.\d+)?$/.test(v)) {
26487
+ allNumeric = false;
26488
+ break;
26489
+ }
26490
+ }
26491
+ if (allNumeric && anyNonEmpty)
26492
+ numericColumns.add(k);
26493
+ }
26494
+ }
26427
26495
  const widths = {};
26428
26496
  for (const key of keys) {
26429
26497
  widths[key] = stripAnsi(key).length;
26430
- for (const row of rows) {
26498
+ for (const row of processed) {
26431
26499
  const value = row[key] ?? "";
26432
26500
  const stripped = stripAnsi(value).length;
26433
26501
  widths[key] = Math.max(widths[key], stripped);
@@ -26439,44 +26507,285 @@ function formatTable(rows, opts) {
26439
26507
  const botBorder = `\u2514${topParts.join("\u2534")}\u2518`;
26440
26508
  const headerCells = keys.map((k) => theme.bold(k.padEnd(widths[k])).toString());
26441
26509
  const headerRow = `\u2502 ${headerCells.join(" \u2502 ")} \u2502`;
26442
- const dataRows = rows.map((row) => {
26510
+ const dataRows = processed.map((row, rowIdx) => {
26511
+ const isAlt = zebra && rowIdx % 2 === 0;
26443
26512
  const cells = keys.map((k) => {
26444
26513
  const value = row[k] ?? "";
26445
- return value.padEnd(widths[k]);
26514
+ const padded = numericColumns.has(k) ? value.padStart(widths[k]) : value.padEnd(widths[k]);
26515
+ if (isAlt && !value.includes("\x1B")) {
26516
+ return theme.textMuted(padded);
26517
+ }
26518
+ return padded;
26446
26519
  });
26447
26520
  return `\u2502 ${cells.join(" \u2502 ")} \u2502`;
26448
26521
  });
26449
- process.stdout.write(`${theme.dim(topBorder)}
26450
- ` + `${headerRow}
26451
- ` + `${theme.dim(sepBorder)}
26452
- ` + `${dataRows.join(`
26522
+ const borderOut = compact ? `${headerRow}
26523
+ ${theme.dim("\u2500".repeat(stripAnsi(headerRow).length))}
26524
+ ${dataRows.join(`
26453
26525
  `)}
26454
- ` + `${theme.dim(botBorder)}
26455
- `);
26526
+ ` : `${theme.dim(topBorder)}
26527
+ ${headerRow}
26528
+ ${theme.dim(sepBorder)}
26529
+ ${dataRows.join(`
26530
+ `)}
26531
+ ${theme.dim(botBorder)}
26532
+ `;
26533
+ process.stdout.write(borderOut);
26534
+ }
26535
+ function colorizeCell(value, colorizeStatus) {
26536
+ if (!colorizeStatus)
26537
+ return value;
26538
+ const v = value.trim();
26539
+ const lower = v.toLowerCase();
26540
+ if ([
26541
+ "ok",
26542
+ "pass",
26543
+ "healthy",
26544
+ "operational",
26545
+ "up",
26546
+ "active",
26547
+ "enabled"
26548
+ ].includes(lower)) {
26549
+ return `${theme.success(icons.success)} ${theme.text(v)}`;
26550
+ }
26551
+ if (["warn", "warning", "degraded", "partial", "pending"].includes(lower)) {
26552
+ return `${theme.warning(icons.warning)} ${theme.text(v)}`;
26553
+ }
26554
+ if ([
26555
+ "err",
26556
+ "error",
26557
+ "fail",
26558
+ "failed",
26559
+ "down",
26560
+ "unreachable",
26561
+ "disabled"
26562
+ ].includes(lower)) {
26563
+ return `${theme.error(icons.error)} ${theme.text(v)}`;
26564
+ }
26565
+ return value;
26456
26566
  }
26457
26567
  function formatJson(data, opts) {
26458
26568
  const json2 = opts?.quiet ? JSON.stringify(data) : JSON.stringify(data, null, 2);
26459
26569
  process.stdout.write(`${json2}
26460
26570
  `);
26461
26571
  }
26462
- function formatKeyValue(pairs, opts) {
26463
- if (opts?.quiet)
26572
+ function formatKeyValue(pairs, opts = {}) {
26573
+ if (opts.quiet)
26464
26574
  return;
26465
- if (opts?.json) {
26575
+ if (opts.json) {
26466
26576
  process.stdout.write(JSON.stringify(pairs, null, 2) + `
26467
26577
  `);
26468
26578
  return;
26469
26579
  }
26470
- const maxKeyLen = Math.max(...Object.keys(pairs).map((k) => stripAnsi(k).length));
26580
+ const maxKeyLen = Math.max(...Object.keys(pairs).map((k) => stripAnsi(k).length), 0);
26471
26581
  for (const [key, value] of Object.entries(pairs)) {
26472
26582
  const paddedKey = key.padEnd(maxKeyLen);
26473
- process.stdout.write(` ${theme.key(paddedKey)} ${theme.dim(":")} ${value}
26583
+ process.stdout.write(` ${theme.key(paddedKey)} ${theme.textMuted(":")} ${value}
26474
26584
  `);
26475
26585
  }
26476
26586
  }
26477
26587
  function getFormatOptions(cmd) {
26478
26588
  const opts = cmd.optsWithGlobals();
26479
- return { json: Boolean(opts.json), quiet: Boolean(opts.quiet) };
26589
+ return {
26590
+ json: Boolean(opts.json),
26591
+ quiet: Boolean(opts.quiet),
26592
+ noColor: Boolean(opts.color === false)
26593
+ };
26594
+ }
26595
+ function formatCompletion(message, opts = {}) {
26596
+ if (opts.json || opts.quiet)
26597
+ return;
26598
+ if (process.exitCode && process.exitCode !== 0)
26599
+ return;
26600
+ if (!isRichMode(opts))
26601
+ return;
26602
+ const parts = [theme.success(icons.success), theme.text(message)];
26603
+ if (opts.durationMs !== undefined) {
26604
+ parts.push(theme.textMuted(formatDuration3(opts.durationMs)));
26605
+ }
26606
+ process.stdout.write(`${parts.join(" ")}
26607
+ `);
26608
+ if (opts.suggestion) {
26609
+ const reason = opts.suggestion.reason ? ` ${theme.textMuted(`(${opts.suggestion.reason})`)}` : "";
26610
+ process.stdout.write(` ${theme.textMuted("\u2192")} ${theme.textMuted("next:")} ${theme.accent(opts.suggestion.command)}${reason}
26611
+ `);
26612
+ }
26613
+ }
26614
+
26615
+ // src/utils/string.ts
26616
+ function levenshtein(a2, b) {
26617
+ if (a2 === b)
26618
+ return 0;
26619
+ if (a2.length === 0)
26620
+ return b.length;
26621
+ if (b.length === 0)
26622
+ return a2.length;
26623
+ const [s, t] = a2.length <= b.length ? [a2, b] : [b, a2];
26624
+ const m = s.length;
26625
+ const n = t.length;
26626
+ let prev = new Array(m + 1);
26627
+ for (let i = 0;i <= m; i++)
26628
+ prev[i] = i;
26629
+ let curr = new Array(m + 1);
26630
+ for (let j = 1;j <= n; j++) {
26631
+ curr[0] = j;
26632
+ for (let i = 1;i <= m; i++) {
26633
+ const cost = s.charCodeAt(i - 1) === t.charCodeAt(j - 1) ? 0 : 1;
26634
+ curr[i] = Math.min(curr[i - 1] + 1, prev[i] + 1, prev[i - 1] + cost);
26635
+ }
26636
+ [prev, curr] = [curr, prev];
26637
+ }
26638
+ return prev[m];
26639
+ }
26640
+
26641
+ // src/utils/error-handler.ts
26642
+ var SUGGESTION_THRESHOLD = 2;
26643
+ function collectCommandNames(cmd, acc = new Set) {
26644
+ for (const sub of cmd.commands) {
26645
+ if (sub.name() && !sub.hidden) {
26646
+ acc.add(sub.name());
26647
+ collectCommandNames(sub, acc);
26648
+ }
26649
+ }
26650
+ return acc;
26651
+ }
26652
+ function suggestForCommand(program2, unknown2) {
26653
+ if (unknown2.length < 3)
26654
+ return;
26655
+ const candidates = collectCommandNames(program2);
26656
+ let best;
26657
+ for (const candidate of candidates) {
26658
+ const d = levenshtein(unknown2, candidate);
26659
+ if (d > SUGGESTION_THRESHOLD)
26660
+ continue;
26661
+ if (!best || d < best.dist) {
26662
+ best = { name: candidate, dist: d };
26663
+ }
26664
+ }
26665
+ return best?.name;
26666
+ }
26667
+ function withErrorHandling(handler, options) {
26668
+ return async (...args) => {
26669
+ try {
26670
+ await handler(...args);
26671
+ } catch (error51) {
26672
+ const service = options?.service ?? "cli";
26673
+ if (error51 instanceof CLIError) {
26674
+ formatError2(error51, options?.opts);
26675
+ process.exitCode = error51.code;
26676
+ process.exit(error51.code);
26677
+ return;
26678
+ } else if (error51 instanceof Error) {
26679
+ formatError2(`[${service}] ${error51.message}`, options?.opts);
26680
+ process.exitCode = 1 /* ERROR */;
26681
+ process.exit(1 /* ERROR */);
26682
+ return;
26683
+ } else {
26684
+ formatError2(`[${service}] Unknown error: ${String(error51)}`, options?.opts);
26685
+ process.exitCode = -1 /* CommandFailed */;
26686
+ process.exit(-1 /* CommandFailed */);
26687
+ return;
26688
+ }
26689
+ }
26690
+ };
26691
+ }
26692
+
26693
+ // src/utils/help-formatter.ts
26694
+ function renderHelp(cmd, helper) {
26695
+ const subcommands = cmd.commands.filter((c) => !c._hidden).map((c) => ({ name: c.name(), desc: c.description() ?? "" }));
26696
+ const blocks = [];
26697
+ const argNames = cmd.registeredArguments.map((a2) => a2.name()).join(" ");
26698
+ const titleName = argNames ? `${cmd.name()} ${argNames}` : cmd.name();
26699
+ blocks.push(theme.heading(` ${titleName}`));
26700
+ if (cmd.description()) {
26701
+ blocks.push(` ${theme.textMuted(cmd.description())}`);
26702
+ }
26703
+ blocks.push(theme.textFaint("\u2500".repeat(60)));
26704
+ const usageLine = buildUsage(cmd);
26705
+ blocks.push(headerLine("Usage"));
26706
+ blocks.push(` ${theme.accent(usageLine)}`);
26707
+ const options = cmd.options.filter((o) => !o.hidden);
26708
+ if (options.length > 0) {
26709
+ blocks.push("");
26710
+ blocks.push(headerLine("Options"));
26711
+ const flagWidth = Math.max(...options.map((o) => o.flags.length));
26712
+ for (const o of options) {
26713
+ const padded = o.flags.padEnd(flagWidth);
26714
+ const desc = o.description ? theme.textMuted(o.description) : "";
26715
+ blocks.push(` ${theme.text(padded)} ${desc}`);
26716
+ }
26717
+ }
26718
+ if (subcommands.length > 0) {
26719
+ blocks.push("");
26720
+ blocks.push(headerLine("Commands"));
26721
+ const nameWidth = Math.max(...subcommands.map((s) => s.name.length));
26722
+ for (const s of subcommands) {
26723
+ const padded = s.name.padEnd(nameWidth);
26724
+ blocks.push(` ${theme.text(padded)} ${theme.textMuted(s.desc)}`);
26725
+ }
26726
+ }
26727
+ const afterText = captureHelpEvent(cmd, "afterHelp");
26728
+ if (afterText.trim()) {
26729
+ blocks.push("");
26730
+ blocks.push(headerLine("Examples"));
26731
+ blocks.push(` ${theme.textMuted(afterText.trim())}`);
26732
+ }
26733
+ const out = blocks.join(`
26734
+ `) + `
26735
+ `;
26736
+ return isRichMode() ? out : stripAnsi(out);
26737
+ }
26738
+ function headerLine(text) {
26739
+ return ` ${theme.textSubtle(text)}`;
26740
+ }
26741
+ function buildUsage(cmd) {
26742
+ const parts = [];
26743
+ let c = cmd;
26744
+ while (c && c.name()) {
26745
+ parts.unshift(c.name());
26746
+ c = c.parent;
26747
+ }
26748
+ for (const arg of cmd.registeredArguments) {
26749
+ parts.push(arg.name());
26750
+ }
26751
+ return `${parts.join(" ")} [options]`;
26752
+ }
26753
+ function captureHelpEvent(cmd, eventName) {
26754
+ const parts = [];
26755
+ const context = {
26756
+ error: false,
26757
+ command: cmd,
26758
+ write: (str) => {
26759
+ parts.push(str);
26760
+ }
26761
+ };
26762
+ const emitter = cmd;
26763
+ emitter.emit(eventName, context);
26764
+ return parts.join("");
26765
+ }
26766
+
26767
+ // src/utils/completion.ts
26768
+ var SUGGEST_NEXT = {
26769
+ init: { command: "hoox setup", reason: "provision infrastructure" },
26770
+ setup: { command: "hoox deploy all", reason: "ship to Cloudflare" },
26771
+ "deploy all": { command: "hoox check health", reason: "verify the deploy" },
26772
+ "check health": { command: "hoox monitor status", reason: "keep watching" },
26773
+ "monitor kill-switch off": {
26774
+ command: "hoox check health",
26775
+ reason: "re-enable trading"
26776
+ }
26777
+ };
26778
+ function suggestNextCommand(commandPath) {
26779
+ return SUGGEST_NEXT[commandPath];
26780
+ }
26781
+ function getCmdPath(cmd) {
26782
+ const parts = [];
26783
+ let current2 = cmd;
26784
+ while (current2 && current2.name() && current2.parent) {
26785
+ parts.unshift(current2.name());
26786
+ current2 = current2.parent;
26787
+ }
26788
+ return parts.join(" ");
26480
26789
  }
26481
26790
 
26482
26791
  // src/commands/init/init-command.ts
@@ -26781,30 +27090,6 @@ class CloudflareService {
26781
27090
  return;
26782
27091
  }
26783
27092
  }
26784
- // src/utils/error-handler.ts
26785
- function withErrorHandling(handler, options) {
26786
- return async (...args) => {
26787
- try {
26788
- await handler(...args);
26789
- } catch (error51) {
26790
- const service = options?.service ?? "cli";
26791
- if (error51 instanceof CLIError) {
26792
- formatError2(error51, options?.opts);
26793
- process.exitCode = error51.code;
26794
- process.exit(error51.code);
26795
- } else if (error51 instanceof Error) {
26796
- formatError2(`[${service}] ${error51.message}`, options?.opts);
26797
- process.exitCode = 1 /* ERROR */;
26798
- process.exit(1 /* ERROR */);
26799
- } else {
26800
- formatError2(`[${service}] Unknown error: ${String(error51)}`, options?.opts);
26801
- process.exitCode = -1 /* CommandFailed */;
26802
- process.exit(-1 /* CommandFailed */);
26803
- }
26804
- }
26805
- };
26806
- }
26807
-
26808
27093
  // src/commands/init/cli-provisioner.ts
26809
27094
  class CLIProvisioner {
26810
27095
  async provision(plan) {
@@ -29046,7 +29331,7 @@ function renderDefaultSummary(results) {
29046
29331
  if (results.length === 0)
29047
29332
  return;
29048
29333
  const rows = results.map((r2) => ({
29049
- Task: r2.title,
29334
+ Task: theme.text(r2.title),
29050
29335
  Status: r2.ok ? theme.success(icons.success) : theme.error(icons.error),
29051
29336
  Duration: formatDuration2(r2.ms)
29052
29337
  }));
@@ -36764,8 +37049,30 @@ EXAMPLES:
36764
37049
  registerFastpathCommand(cmd);
36765
37050
  }
36766
37051
  // src/ui/banner.ts
37052
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
37053
+ import { fileURLToPath as fileURLToPath2 } from "url";
37054
+ import { dirname as dirname3, join as join8 } from "path";
36767
37055
  var TAGLINE = "Cloudflare Workers Platform";
36768
- var VERSION = "0.3.0";
37056
+ function findCliVersion() {
37057
+ const PKG_NAME = "@jango-blockchained/hoox-cli";
37058
+ let dir = dirname3(fileURLToPath2(import.meta.url));
37059
+ for (let i2 = 0;i2 < 5; i2++) {
37060
+ const pkgPath = join8(dir, "package.json");
37061
+ if (existsSync8(pkgPath)) {
37062
+ try {
37063
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf-8"));
37064
+ if (pkg.name === PKG_NAME)
37065
+ return pkg.version;
37066
+ } catch {}
37067
+ }
37068
+ const parent = dirname3(dir);
37069
+ if (parent === dir)
37070
+ break;
37071
+ dir = parent;
37072
+ }
37073
+ return "unknown";
37074
+ }
37075
+ var VERSION = findCliVersion();
36769
37076
  var DISCLAIMER2 = "DISCLAIMER: Trading cryptocurrencies involves substantial risk of loss. Use at your own risk.";
36770
37077
  var LEGACY_LINES = [
36771
37078
  "\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557",
@@ -36777,12 +37084,12 @@ var LEGACY_LINES = [
36777
37084
  ];
36778
37085
  function renderLegacy() {
36779
37086
  const bw = 52;
36780
- const line = ` ${theme.dim("\u2500").repeat(bw - 2)}`;
36781
- const top = ` ${theme.dim("\u250C")}${line.slice(2)}${theme.dim("\u2510")}`;
36782
- const bottom = ` ${theme.dim("\u2514")}${line.slice(2)}${theme.dim("\u2518")}`;
37087
+ const line = ` ${theme.textFaint("\u2500").repeat(bw - 2)}`;
37088
+ const top = ` ${theme.textFaint("\u250C")}${line.slice(2)}${theme.textFaint("\u2510")}`;
37089
+ const bottom = ` ${theme.textFaint("\u2514")}${line.slice(2)}${theme.textFaint("\u2518")}`;
36783
37090
  const ascii = LEGACY_LINES.map((l2) => ` ${theme.heading(l2)}`);
36784
37091
  const gap = Math.floor((bw - TAGLINE.length - VERSION.length - 2) / 2);
36785
- const tag = ` ${" ".repeat(gap)}${theme.dim(TAGLINE)} ${theme.dim(`v${VERSION}`)}`;
37092
+ const tag = ` ${" ".repeat(gap)}${theme.textMuted(TAGLINE)} ${theme.textMuted(`v${VERSION}`)}`;
36786
37093
  return [top, ...ascii, line, tag, bottom].join(`
36787
37094
  `);
36788
37095
  }
@@ -36796,13 +37103,19 @@ var HORIZON_LINES = [
36796
37103
  ];
36797
37104
  function renderBannerHorizon() {
36798
37105
  const bw = 56;
36799
- const inner = theme.dim("\u2500").repeat(bw - 2);
36800
- const top = ` ${theme.dim("\u256D")}${inner}${theme.dim("\u256E")}`;
36801
- const bottom = ` ${theme.dim("\u2570")}${inner}${theme.dim("\u256F")}`;
37106
+ const inner = theme.textFaint("\u2500").repeat(bw - 2);
37107
+ const top = ` ${theme.textFaint("\u256D")}${inner}${theme.textFaint("\u256E")}`;
37108
+ const bottom = ` ${theme.textFaint("\u2570")}${inner}${theme.textFaint("\u256F")}`;
36802
37109
  const ascii = HORIZON_LINES.map((l2) => ` ${theme.accent(l2)}`);
36803
37110
  const gap = Math.floor((bw - TAGLINE.length - VERSION.length - 4) / 2);
36804
- const tag = ` ${" ".repeat(gap)}${theme.dim(TAGLINE)} ${theme.dim(`v${VERSION}`)}`;
36805
- return [top, ...ascii, theme.dim("\u2500").repeat(bw), tag, bottom].join(`
37111
+ const tag = ` ${" ".repeat(gap)}${theme.textMuted(TAGLINE)} ${theme.textMuted(`v${VERSION}`)}`;
37112
+ return [
37113
+ top,
37114
+ ...ascii,
37115
+ ` ${theme.textFaint("\u2500").repeat(bw)}`,
37116
+ tag,
37117
+ bottom
37118
+ ].join(`
36806
37119
  `);
36807
37120
  }
36808
37121
  var SIGNAL_LINES = [
@@ -36814,38 +37127,44 @@ var SIGNAL_LINES = [
36814
37127
  ];
36815
37128
  function renderBannerSignal() {
36816
37129
  const bw = 54;
36817
- const line = theme.dim("\u2500").repeat(bw);
36818
- const top = ` ${theme.dim("\u250C")}${line.slice(2)}${theme.dim("\u2510")}`;
36819
- const bottom = ` ${theme.dim("\u2514")}${line.slice(2)}${theme.dim("\u2518")}`;
37130
+ const line = theme.textFaint("\u2500").repeat(bw);
37131
+ const top = ` ${theme.textFaint("\u250C")}${line.slice(2)}${theme.textFaint("\u2510")}`;
37132
+ const bottom = ` ${theme.textFaint("\u2514")}${line.slice(2)}${theme.textFaint("\u2518")}`;
36820
37133
  const wordmark = SIGNAL_LINES.map((l2) => {
36821
- return ` ${theme.heading(l2.slice(0, 26))}${theme.dim(l2.slice(26))}`;
37134
+ return ` ${theme.heading(l2.slice(0, 26))}${theme.textFaint(l2.slice(26))}`;
36822
37135
  });
36823
- const wave = ` ${theme.accent("~~")}${theme.dim("~")}${theme.accent("_")}${theme.dim(".")}${theme.accent("/\\")}${theme.dim("~")}${theme.accent("\\/")}${theme.dim("..")}${theme.accent("/~~\\")}${theme.dim("~")} ${theme.dim(TAGLINE)} ${theme.dim(`v${VERSION}`)}`;
36824
- return [top, ...wordmark, line, wave, bottom].join(`
37136
+ const wave = ` ${theme.accent("~~")}${theme.textFaint("~")}${theme.accent("_")}${theme.textFaint(".")}${theme.accent("/\\")}${theme.textFaint("~")}${theme.accent("\\/")}${theme.textFaint("..")}${theme.accent("/~~\\")}${theme.textFaint("~")} ${theme.textMuted(TAGLINE)} ${theme.textMuted(`v${VERSION}`)}`;
37137
+ return [
37138
+ top,
37139
+ ...wordmark,
37140
+ ` ${theme.textFaint("\u2500").repeat(bw)}`,
37141
+ wave,
37142
+ bottom
37143
+ ].join(`
36825
37144
  `);
36826
37145
  }
36827
37146
  function renderBannerMinimal() {
36828
37147
  const bw = 50;
36829
- const rule = theme.dim("\u2501").repeat(bw);
37148
+ const rule = theme.textFaint("\u2501").repeat(bw);
36830
37149
  const leftPad = Math.floor((bw - TAGLINE.length - VERSION.length - 8) / 2);
36831
- const titleLine = " ".repeat(leftPad) + theme.heading("H O O X") + " " + theme.dim(TAGLINE) + " " + theme.dim(`v${VERSION}`);
37150
+ const titleLine = " ".repeat(leftPad) + theme.heading("H O O X") + " " + theme.textMuted(TAGLINE) + " " + theme.textMuted(`v${VERSION}`);
36832
37151
  return [
36833
37152
  ` ${rule}`,
36834
- ` ${theme.dim("\u2502")}${" ".repeat(bw - 2)}${theme.dim("\u2502")}`,
36835
- `${theme.dim("\u2502")}${titleLine}${" ".repeat(Math.max(0, bw - titleLine.length - 2))}${theme.dim("\u2502")}`,
36836
- ` ${theme.dim("\u2502")}${" ".repeat(bw - 2)}${theme.dim("\u2502")}`,
37153
+ ` ${theme.textFaint("\u2502")}${" ".repeat(bw - 2)}${theme.textFaint("\u2502")}`,
37154
+ ` ${theme.textFaint("\u2502")}${titleLine}${" ".repeat(Math.max(0, bw - stripAnsi(titleLine).length - 2))}${theme.textFaint("\u2502")}`,
37155
+ ` ${theme.textFaint("\u2502")}${" ".repeat(bw - 2)}${theme.textFaint("\u2502")}`,
36837
37156
  ` ${rule}`
36838
37157
  ].join(`
36839
37158
  `);
36840
37159
  }
36841
37160
  var BANNER_VARIANTS = {
37161
+ minimal: renderBannerMinimal,
36842
37162
  legacy: renderLegacy,
36843
37163
  horizon: renderBannerHorizon,
36844
- signal: renderBannerSignal,
36845
- minimal: renderBannerMinimal
37164
+ signal: renderBannerSignal
36846
37165
  };
36847
37166
  function renderBanner(variant) {
36848
- return variant ? BANNER_VARIANTS[variant]() : renderLegacy();
37167
+ return variant ? BANNER_VARIANTS[variant]() : renderBannerMinimal();
36849
37168
  }
36850
37169
  // src/ui/menu.ts
36851
37170
  init_dist4();
@@ -37197,13 +37516,14 @@ async function runCommand(program2, commandStr) {
37197
37516
  }
37198
37517
  }
37199
37518
  // src/index.ts
37200
- var pkgVersion = JSON.parse(readFileSync6(new URL("../package.json", import.meta.url), "utf-8")).version;
37519
+ var pkgVersion = JSON.parse(readFileSync7(new URL("../package.json", import.meta.url), "utf-8")).version;
37201
37520
  var program2 = new Command;
37202
37521
  program2.name("hoox").description("Hoox CLI \u2014 manage Cloudflare Workers, infrastructure, secrets, and deployments").version(`${pkgVersion}
37203
37522
 
37204
37523
  ${COPYRIGHT}`).addHelpText("beforeAll", theme.heading(`
37205
37524
  Hoox CLI \u2014 Cloudflare Workers Platform
37206
37525
  `)).configureHelp({
37526
+ formatHelp: (cmd, helper) => renderHelp(cmd, helper),
37207
37527
  styleTitle: (str) => theme.heading(str),
37208
37528
  styleCommandText: (str) => theme.bold(str),
37209
37529
  styleOptionText: (str) => str,
@@ -37211,7 +37531,22 @@ Hoox CLI \u2014 Cloudflare Workers Platform
37211
37531
  });
37212
37532
  program2.option("--json", "Output in JSON format");
37213
37533
  program2.option("--quiet", "Minimal output");
37534
+ program2.option("--no-color", "Disable color output");
37214
37535
  program2.option("-y, --yes", "Skip confirmation prompts");
37536
+ program2.hook("preAction", (thisCmd) => {
37537
+ thisCmd._hooxStartedAt = Date.now();
37538
+ });
37539
+ program2.hook("postAction", (thisCmd) => {
37540
+ if (process.exitCode && process.exitCode !== 0)
37541
+ return;
37542
+ const startedAt = thisCmd._hooxStartedAt;
37543
+ if (startedAt === undefined)
37544
+ return;
37545
+ const durationMs = Date.now() - startedAt;
37546
+ const path6 = getCmdPath(thisCmd);
37547
+ const suggestion = suggestNextCommand(path6);
37548
+ formatCompletion("Done", { durationMs, suggestion });
37549
+ });
37215
37550
  program2.exitOverride((err) => {
37216
37551
  if (err.code === "commander.helpDisplayed" || err.code === "commander.version") {
37217
37552
  process.exit(0 /* SUCCESS */);
@@ -37219,7 +37554,14 @@ program2.exitOverride((err) => {
37219
37554
  if (err.code === "commander.missingArgument" || err.message === "(outputHelp)" || err.message?.includes("outputHelp")) {
37220
37555
  process.exit(0 /* SUCCESS */);
37221
37556
  }
37222
- if (err.code === "commander.unknownCommand" || err.code === "commander.unknownOption") {
37557
+ if (err.code === "commander.unknownCommand") {
37558
+ const match = err.message.match(/'([^']+)'/);
37559
+ const badArg = match?.[1] ?? "";
37560
+ const suggestion = suggestForCommand(program2, badArg);
37561
+ formatError2(new CLIError(err.message, 2 /* INVALID_USAGE */, undefined, false), suggestion ? { suggestions: [suggestion] } : undefined);
37562
+ process.exit(2 /* INVALID_USAGE */);
37563
+ }
37564
+ if (err.code === "commander.unknownOption") {
37223
37565
  formatError2(new CLIError(err.message, 2 /* INVALID_USAGE */, undefined, false));
37224
37566
  process.exit(2 /* INVALID_USAGE */);
37225
37567
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jango-blockchained/hoox-cli",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "Hoox CLI — manage Cloudflare Workers, infrastructure, secrets, and deployments (@jango-blockchained/hoox-cli)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,7 +20,7 @@
20
20
  "ansis": "^4.2.0",
21
21
  "commander": "^15.0.0",
22
22
  "jsonc-parser": "^3.3.1",
23
- "wrangler": "^4.103.0"
23
+ "wrangler": "^4.104.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@jango-blockchained/hoox-shared": "^1.0.9",