@jango-blockchained/hoox-cli 0.5.2 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/bin/hoox.js +3 -4
  2. package/dist/index.js +32989 -0
  3. package/package.json +10 -15
  4. package/src/commands/check/check-command.test.ts +0 -476
  5. package/src/commands/check/check-command.ts +0 -1087
  6. package/src/commands/check/index.ts +0 -10
  7. package/src/commands/check/prerequisites-command.test.ts +0 -19
  8. package/src/commands/check/prerequisites-command.ts +0 -97
  9. package/src/commands/check/types.ts +0 -103
  10. package/src/commands/clone/clone-command.test.ts +0 -440
  11. package/src/commands/clone/clone-command.ts +0 -431
  12. package/src/commands/clone/index.ts +0 -1
  13. package/src/commands/config/config-command.test.ts +0 -583
  14. package/src/commands/config/config-command.ts +0 -896
  15. package/src/commands/config/env-command.test.ts +0 -43
  16. package/src/commands/config/env-command.ts +0 -292
  17. package/src/commands/config/index.ts +0 -3
  18. package/src/commands/config/kv-command.test.ts +0 -14
  19. package/src/commands/config/kv-command.ts +0 -290
  20. package/src/commands/dashboard/dashboard-command.test.ts +0 -47
  21. package/src/commands/dashboard/dashboard-command.ts +0 -130
  22. package/src/commands/dashboard/index.ts +0 -1
  23. package/src/commands/db/db-command.test.ts +0 -21
  24. package/src/commands/db/db-command.ts +0 -311
  25. package/src/commands/db/index.ts +0 -1
  26. package/src/commands/deploy/deploy-command.test.ts +0 -324
  27. package/src/commands/deploy/deploy-command.ts +0 -1054
  28. package/src/commands/deploy/index.ts +0 -2
  29. package/src/commands/deploy/telegram-service.ts +0 -76
  30. package/src/commands/deploy/types.ts +0 -34
  31. package/src/commands/dev/dev-command.test.ts +0 -405
  32. package/src/commands/dev/dev-command.ts +0 -398
  33. package/src/commands/dev/index.ts +0 -1
  34. package/src/commands/disclaimer/disclaimer-command.ts +0 -21
  35. package/src/commands/disclaimer/index.ts +0 -1
  36. package/src/commands/infra/index.ts +0 -5
  37. package/src/commands/infra/infra-command.test.ts +0 -719
  38. package/src/commands/infra/infra-command.ts +0 -974
  39. package/src/commands/infra/types.ts +0 -23
  40. package/src/commands/init/__tests__/cli-provisioner.test.ts +0 -17
  41. package/src/commands/init/cli-provisioner.ts +0 -71
  42. package/src/commands/init/index.ts +0 -1
  43. package/src/commands/init/init-command.test.ts +0 -854
  44. package/src/commands/init/init-command.ts +0 -597
  45. package/src/commands/init/types.ts +0 -23
  46. package/src/commands/logs/index.ts +0 -1
  47. package/src/commands/logs/logs-command.test.ts +0 -293
  48. package/src/commands/logs/logs-command.ts +0 -452
  49. package/src/commands/monitor/index.ts +0 -2
  50. package/src/commands/monitor/monitor-command.test.ts +0 -271
  51. package/src/commands/monitor/monitor-command.ts +0 -269
  52. package/src/commands/monitor/monitor-service.ts +0 -65
  53. package/src/commands/monitor/types.ts +0 -13
  54. package/src/commands/repair/index.ts +0 -2
  55. package/src/commands/repair/repair-command.test.ts +0 -243
  56. package/src/commands/repair/repair-command.ts +0 -220
  57. package/src/commands/repair/repair-service.ts +0 -164
  58. package/src/commands/repair/types.ts +0 -13
  59. package/src/commands/schema/index.ts +0 -1
  60. package/src/commands/schema/schema-command.ts +0 -137
  61. package/src/commands/test/index.ts +0 -2
  62. package/src/commands/test/test-command.test.ts +0 -318
  63. package/src/commands/test/test-command.ts +0 -406
  64. package/src/commands/tui/index.ts +0 -1
  65. package/src/commands/tui/tui-command.ts +0 -87
  66. package/src/commands/update/index.ts +0 -1
  67. package/src/commands/update/update-command.ts +0 -109
  68. package/src/commands/waf/index.ts +0 -2
  69. package/src/commands/waf/types.ts +0 -48
  70. package/src/commands/waf/waf-command.test.ts +0 -506
  71. package/src/commands/waf/waf-command.ts +0 -545
  72. package/src/index.ts +0 -261
  73. package/src/services/cloudflare/cloudflare-service.test.ts +0 -658
  74. package/src/services/cloudflare/cloudflare-service.ts +0 -433
  75. package/src/services/cloudflare/index.ts +0 -2
  76. package/src/services/cloudflare/types.ts +0 -30
  77. package/src/services/config/config-service.test.ts +0 -395
  78. package/src/services/config/config-service.ts +0 -202
  79. package/src/services/config/index.ts +0 -9
  80. package/src/services/config/types.ts +0 -66
  81. package/src/services/db/db-service.test.ts +0 -63
  82. package/src/services/db/db-service.ts +0 -131
  83. package/src/services/db/index.ts +0 -1
  84. package/src/services/docker/docker-service.ts +0 -155
  85. package/src/services/docker/index.ts +0 -1
  86. package/src/services/env/env-service.test.ts +0 -259
  87. package/src/services/env/env-service.ts +0 -372
  88. package/src/services/env/index.ts +0 -1
  89. package/src/services/kv/index.ts +0 -1
  90. package/src/services/kv/kv-sync-service.test.ts +0 -38
  91. package/src/services/kv/kv-sync-service.ts +0 -243
  92. package/src/services/prerequisites/index.ts +0 -1
  93. package/src/services/prerequisites/prerequisites-service.test.ts +0 -89
  94. package/src/services/prerequisites/prerequisites-service.ts +0 -377
  95. package/src/services/prerequisites/types.ts +0 -48
  96. package/src/services/schema/index.ts +0 -1
  97. package/src/services/schema/schema-service.ts +0 -99
  98. package/src/services/secrets/index.ts +0 -11
  99. package/src/services/secrets/secrets-service.test.ts +0 -502
  100. package/src/services/secrets/secrets-service.ts +0 -287
  101. package/src/services/secrets/types.ts +0 -50
  102. package/src/services/update/index.ts +0 -2
  103. package/src/services/update/update-service.test.ts +0 -76
  104. package/src/services/update/update-service.ts +0 -193
  105. package/src/ui/banner.ts +0 -141
  106. package/src/ui/index.ts +0 -8
  107. package/src/ui/menu.ts +0 -490
  108. package/src/utils/error-handler.ts +0 -62
  109. package/src/utils/errors.test.ts +0 -69
  110. package/src/utils/errors.ts +0 -24
  111. package/src/utils/formatters.test.ts +0 -180
  112. package/src/utils/formatters.ts +0 -269
  113. package/src/utils/git.ts +0 -134
  114. package/src/utils/theme.ts +0 -92
package/src/ui/banner.ts DELETED
@@ -1,141 +0,0 @@
1
- /**
2
- * Hoox ASCII banner — variants for the interactive TUI.
3
- * Each variant provides a different visual style while maintaining
4
- * consistent branding and theme coloring.
5
- */
6
-
7
- import { theme } from "../utils/theme.js";
8
-
9
- const TAGLINE = "Cloudflare Workers Platform";
10
- const VERSION = "0.3.0";
11
-
12
- // ── Shared constants ──────────────────────────────────────────────
13
-
14
- /** Disclaimer line rendered below the banner and in the footer. */
15
- export const DISCLAIMER =
16
- "DISCLAIMER: Trading cryptocurrencies involves substantial risk of loss. Use at your own risk.";
17
-
18
- // ── Variant 0 — Default (legacy) ──────────────────────────────────
19
-
20
- const LEGACY_LINES = [
21
- "██╗ ██╗ ██████╗ ██████╗ ██╗ ██╗",
22
- "██║ ██║██╔═══██╗██╔═══██╗╚██╗██╔╝",
23
- "███████║██║ ██║██║ ██║ ╚███╔╝ ",
24
- "██╔══██║██║ ██║██║ ██║ ██╔██╗ ",
25
- "██║ ██║╚██████╔╝╚██████╔╝██╔╝ ██╗",
26
- "╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═╝",
27
- ];
28
-
29
- function renderLegacy(): string {
30
- const bw = 52;
31
- const line = ` ${theme.dim("─").repeat(bw - 2)}`;
32
- const top = ` ${theme.dim("┌")}${line.slice(2)}${theme.dim("┐")}`;
33
- const bottom = ` ${theme.dim("└")}${line.slice(2)}${theme.dim("┘")}`;
34
- const ascii = LEGACY_LINES.map((l) => ` ${theme.heading(l)}`);
35
- const gap = Math.floor((bw - TAGLINE.length - VERSION.length - 2) / 2);
36
- const tag = ` ${" ".repeat(gap)}${theme.dim(TAGLINE)} ${theme.dim(`v${VERSION}`)}`;
37
- return [top, ...ascii, line, tag, bottom].join("\n");
38
- }
39
-
40
- // ── Variant 1 — "Horizon" (architectural) ─────────────────────────
41
- // Uses double-line frame characters and a more open, architectural feel.
42
- // The "HOOX" wordmark is built from simple geometric blocks.
43
-
44
- const HORIZON_LINES = [
45
- "╔═══╗ ╔═══╗ ╔═══╗ ╔═══╗",
46
- "║ ║ ║ ║ ║ ║ ║ ║ ║ ║",
47
- "║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║",
48
- "║ ╚═╝ ║ ╚═╝ ║ ║ ║ ║ ╚═╝",
49
- "║ ║ ║ ╚═╝ ║ ",
50
- "╚═════╝ ╚═════╝ ╚═══╝ ╚═════╝",
51
- ];
52
-
53
- export function renderBannerHorizon(): string {
54
- const bw = 56;
55
- const inner = theme.dim("─").repeat(bw - 2);
56
- const top = ` ${theme.dim("╭")}${inner}${theme.dim("╮")}`;
57
- const bottom = ` ${theme.dim("╰")}${inner}${theme.dim("╯")}`;
58
- const ascii = HORIZON_LINES.map((l) => ` ${theme.accent(l)}`);
59
- const gap = Math.floor((bw - TAGLINE.length - VERSION.length - 4) / 2);
60
- const tag = ` ${" ".repeat(gap)}${theme.dim(TAGLINE)} ${theme.dim(`v${VERSION}`)}`;
61
- return [top, ...ascii, theme.dim("─").repeat(bw), tag, bottom].join("\n");
62
- }
63
-
64
- // ── Variant 2 — "Signal" (data / waveform) ────────────────────────
65
- // Evokes trading signals and monitoring. Uses a smaller wordmark
66
- // with a dynamic waveform motif beneath it.
67
-
68
- const SIGNAL_LINES = [
69
- " _ _ _ _ ",
70
- " | | | | ___ __| | | | ",
71
- " | |_| |/ _ \\ / _` | | | ",
72
- " | _ | (_) | (_| | | | ",
73
- " |_| |_|\\___/ \\__,_| |_| ",
74
- ];
75
-
76
- export function renderBannerSignal(): string {
77
- const bw = 54;
78
- const line = theme.dim("─").repeat(bw);
79
- const top = ` ${theme.dim("┌")}${line.slice(2)}${theme.dim("┐")}`;
80
- const bottom = ` ${theme.dim("└")}${line.slice(2)}${theme.dim("┘")}`;
81
-
82
- const wordmark = SIGNAL_LINES.map((l) => {
83
- // Colour the letters H O O X, dim the rest
84
- return ` ${theme.heading(l.slice(0, 26))}${theme.dim(l.slice(26))}`;
85
- });
86
-
87
- // Waveform line — sine-wave art
88
- 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}`)}`;
89
-
90
- return [top, ...wordmark, line, wave, bottom].join("\n");
91
- }
92
-
93
- // ── Variant 3 — "Minimal" (clean badge) ───────────────────────────
94
- // No ASCII art — just the project name, version, and a clean
95
- // double-rule header. Professional and understated.
96
-
97
- export function renderBannerMinimal(): string {
98
- const bw = 50;
99
- const rule = theme.dim("━").repeat(bw);
100
- const inner = theme.dim("─").repeat(bw);
101
-
102
- const leftPad = Math.floor((bw - TAGLINE.length - VERSION.length - 8) / 2);
103
- const titleLine =
104
- " ".repeat(leftPad) +
105
- theme.heading("H O O X") +
106
- " " +
107
- theme.dim(TAGLINE) +
108
- " " +
109
- theme.dim(`v${VERSION}`);
110
-
111
- return [
112
- ` ${rule}`,
113
- ` ${theme.dim("│")}${" ".repeat(bw - 2)}${theme.dim("│")}`,
114
- `${theme.dim("│")}${titleLine}${" ".repeat(Math.max(0, bw - titleLine.length - 2))}${theme.dim("│")}`,
115
- ` ${theme.dim("│")}${" ".repeat(bw - 2)}${theme.dim("│")}`,
116
- ` ${rule}`,
117
- ].join("\n");
118
- }
119
-
120
- // ── Exports ───────────────────────────────────────────────────────
121
-
122
- export const BANNER_VARIANTS = {
123
- legacy: renderLegacy,
124
- horizon: renderBannerHorizon,
125
- signal: renderBannerSignal,
126
- minimal: renderBannerMinimal,
127
- } as const;
128
-
129
- export type BannerVariant = keyof typeof BANNER_VARIANTS;
130
-
131
- /** Default banner — the legacy ASCII block style. */
132
- export function renderBanner(variant?: BannerVariant): string {
133
- return variant ? BANNER_VARIANTS[variant]() : renderLegacy();
134
- }
135
-
136
- /**
137
- * Render a compact one-line banner for inline display.
138
- */
139
- export function renderCompactBanner(): string {
140
- return `${theme.heading("Hoox CLI")} ${theme.dim(`${TAGLINE} ${VERSION}`)}`;
141
- }
package/src/ui/index.ts DELETED
@@ -1,8 +0,0 @@
1
- /**
2
- * Hoox CLI UI module — interactive TUI components.
3
- *
4
- * @module
5
- */
6
-
7
- export { renderBanner } from "./banner.js";
8
- export { runInteractiveTUI } from "./menu.js";
package/src/ui/menu.ts DELETED
@@ -1,490 +0,0 @@
1
- /**
2
- * Interactive TUI loop for the Hoox CLI.
3
- *
4
- * When `hoox` is called with no arguments, this module takes over:
5
- * shows the ASCII banner, an interactive clack menu, dispatches to
6
- * the appropriate Commander action, and returns to the menu.
7
- */
8
-
9
- import { Command } from "commander";
10
- import {
11
- intro,
12
- outro,
13
- select,
14
- text,
15
- confirm,
16
- log,
17
- isCancel,
18
- cancel,
19
- } from "@clack/prompts";
20
- import { renderBanner, DISCLAIMER } from "./banner.js";
21
- import { CLIError } from "../utils/errors.js";
22
- import { theme } from "../utils/theme.js";
23
-
24
- // ---------------------------------------------------------------------------
25
- // Main entry
26
- // ---------------------------------------------------------------------------
27
-
28
- /**
29
- * Run the interactive TUI loop.
30
- * - Shows the banner once
31
- * - Shows a category-selection menu
32
- * - Each category leads to sub-actions
33
- * - After each action, returns to the menu
34
- * - Exit or Ctrl+C to quit
35
- */
36
- export async function runInteractiveTUI(program: Command): Promise<void> {
37
- // Print banner directly, then start clack prompt session
38
- process.stdout.write(renderBanner() + "\n");
39
- intro("hoox");
40
-
41
- while (true) {
42
- const category = await showMainMenu();
43
- if (category === "__exit") break;
44
-
45
- const result = await handleCategory(category, program);
46
- if (result === "exit") break;
47
- // "back" or "continue" → loop back to main menu
48
- }
49
-
50
- // Legal footer
51
- process.stdout.write(
52
- theme.dim("─".repeat(50)) + "\n" + theme.muted(DISCLAIMER) + "\n"
53
- );
54
- outro("See you later!");
55
- }
56
-
57
- // ---------------------------------------------------------------------------
58
- // Main menu
59
- // ---------------------------------------------------------------------------
60
-
61
- const MAIN_CATEGORIES = [
62
- {
63
- value: "init",
64
- label: "⚡ Setup Wizard",
65
- hint: "bootstrap or reconfigure project",
66
- },
67
- { value: "deploy", label: "Deploy", hint: "workers, dashboard" },
68
- {
69
- value: "manage",
70
- label: "Manage",
71
- hint: "infrastructure, config, secrets",
72
- },
73
- {
74
- value: "monitor",
75
- label: "Monitor",
76
- hint: "diagnostics, health, logs, tests",
77
- },
78
- { value: "tools", label: "Tools", hint: "WAF, clone worker, dashboard UI" },
79
- { value: "develop", label: "Develop", hint: "dev server, start project" },
80
- { value: "__exit", label: "Exit" },
81
- ] as const;
82
-
83
- async function showMainMenu(): Promise<string> {
84
- const choice = await select({
85
- message: "What would you like to do?",
86
- options: [...MAIN_CATEGORIES],
87
- });
88
-
89
- if (isCancel(choice)) {
90
- cancel("Operation cancelled.");
91
- return "__exit";
92
- }
93
-
94
- return choice;
95
- }
96
-
97
- // ---------------------------------------------------------------------------
98
- // Category handlers
99
- // ---------------------------------------------------------------------------
100
-
101
- /**
102
- * Handle a selected category. Returns "back", "exit", or "continue".
103
- */
104
- async function handleCategory(
105
- category: string,
106
- program: Command
107
- ): Promise<"back" | "exit" | "continue"> {
108
- switch (category) {
109
- case "init":
110
- await runCommand(program, "init");
111
- return "continue";
112
- case "deploy":
113
- return showDeployMenu(program);
114
- case "develop":
115
- return showDevelopMenu(program);
116
- case "manage":
117
- return showManageMenu(program);
118
- case "monitor":
119
- return showMonitorMenu(program);
120
- case "tools":
121
- return showToolsMenu(program);
122
- default:
123
- return "back";
124
- }
125
- }
126
-
127
- // ---------------------------------------------------------------------------
128
- // Deploy sub-menu
129
- // ---------------------------------------------------------------------------
130
-
131
- async function showDeployMenu(
132
- program: Command
133
- ): Promise<"back" | "exit" | "continue"> {
134
- while (true) {
135
- const choice = await select({
136
- message: "Deploy",
137
- options: [
138
- { value: "all", label: "All workers + dashboard", hint: "recommended" },
139
- { value: "workers", label: "Workers only" },
140
- { value: "single", label: "Single worker..." },
141
- { value: "dashboard", label: "Dashboard only" },
142
- { value: "__back", label: "◀ Back to main menu" },
143
- ],
144
- });
145
-
146
- if (isCancel(choice)) return "back";
147
- if (choice === "__back") return "continue";
148
-
149
- if (choice === "single") {
150
- const name = await text({
151
- message: "Which worker?",
152
- placeholder: "e.g. hoox, trade-worker, d1-worker",
153
- validate: (v) =>
154
- v === undefined || v.length === 0
155
- ? "Worker name is required"
156
- : undefined,
157
- });
158
- if (isCancel(name)) continue;
159
- if (!name) continue;
160
- await runCommand(program, `deploy worker ${name}`);
161
- } else {
162
- const cmd = choice === "all" ? "deploy all" : `deploy ${choice}`;
163
- await runCommand(program, cmd);
164
- }
165
- }
166
- }
167
-
168
- // ---------------------------------------------------------------------------
169
- // Develop sub-menu
170
- // ---------------------------------------------------------------------------
171
-
172
- async function showDevelopMenu(
173
- program: Command
174
- ): Promise<"back" | "exit" | "continue"> {
175
- while (true) {
176
- const choice = await select({
177
- message: "Develop",
178
- options: [
179
- {
180
- value: "dev start",
181
- label: "Start dev server",
182
- hint: "runs all workers locally",
183
- },
184
- { value: "__back", label: "◀ Back to main menu" },
185
- ],
186
- });
187
-
188
- if (isCancel(choice)) return "back";
189
- if (choice === "__back") return "continue";
190
-
191
- await runCommand(program, choice);
192
- }
193
- }
194
-
195
- // ---------------------------------------------------------------------------
196
- // Manage sub-menu
197
- // ---------------------------------------------------------------------------
198
-
199
- async function showManageMenu(
200
- program: Command
201
- ): Promise<"back" | "exit" | "continue"> {
202
- while (true) {
203
- const choice = await select({
204
- message: "Manage",
205
- options: [
206
- { value: "infra", label: "Infrastructure", hint: "D1, R2, KV, Queues" },
207
- {
208
- value: "config",
209
- label: "Configuration",
210
- hint: "show / set wrangler.jsonc",
211
- },
212
- { value: "secrets", label: "Secrets", hint: "list, set, sync" },
213
- { value: "keys", label: "Auth keys", hint: "generate, list" },
214
- { value: "waf", label: "WAF rules" },
215
- { value: "__back", label: "◀ Back to main menu" },
216
- ],
217
- });
218
-
219
- if (isCancel(choice)) return "back";
220
- if (choice === "__back") return "continue";
221
-
222
- switch (choice) {
223
- case "infra":
224
- await runCommand(program, "infra");
225
- break;
226
- case "config":
227
- await showConfigSubMenu(program);
228
- break;
229
- case "secrets":
230
- await showSecretsSubMenu(program);
231
- break;
232
- case "keys":
233
- await showKeysSubMenu(program);
234
- break;
235
- case "waf":
236
- await runCommand(program, "waf");
237
- break;
238
- }
239
- }
240
- }
241
-
242
- // ---------------------------------------------------------------------------
243
- // Config sub-sub-menu
244
- // ---------------------------------------------------------------------------
245
-
246
- async function showConfigSubMenu(program: Command): Promise<void> {
247
- const choice = await select({
248
- message: "Configuration",
249
- options: [
250
- { value: "show", label: "Show config", hint: "display wrangler.jsonc" },
251
- { value: "set", label: "Set a value..." },
252
- { value: "__back", label: "◀ Back to Manage" },
253
- ],
254
- });
255
-
256
- if (isCancel(choice) || choice === "__back") return;
257
-
258
- if (choice === "set") {
259
- const key = await text({
260
- message: "Config key (dot-separated path)",
261
- placeholder: "e.g. global.cloudflare_account_id",
262
- validate: (v) =>
263
- v === undefined || v.length === 0 ? "Key is required" : undefined,
264
- });
265
- if (isCancel(key) || !key) return;
266
-
267
- const value = await text({
268
- message: `Value for "${key}"`,
269
- placeholder: "enter value",
270
- });
271
- if (isCancel(value) || value === undefined) return;
272
-
273
- await runCommand(program, `config set ${key} ${value ?? ""}`);
274
- } else {
275
- await runCommand(program, "config show");
276
- }
277
- }
278
-
279
- // ---------------------------------------------------------------------------
280
- // Secrets sub-sub-menu
281
- // ---------------------------------------------------------------------------
282
-
283
- async function showSecretsSubMenu(program: Command): Promise<void> {
284
- const choice = await select({
285
- message: "Secrets",
286
- options: [
287
- { value: "list", label: "List secrets" },
288
- { value: "set", label: "Set a secret..." },
289
- { value: "sync", label: "Sync to Cloudflare" },
290
- { value: "__back", label: "◀ Back to Manage" },
291
- ],
292
- });
293
-
294
- if (isCancel(choice) || choice === "__back") return;
295
-
296
- switch (choice) {
297
- case "list":
298
- await runCommand(program, "config secrets list");
299
- break;
300
- case "set": {
301
- const worker = await text({
302
- message: "Worker name",
303
- placeholder: "e.g. hoox, trade-worker",
304
- validate: (v) =>
305
- v === undefined || v.length === 0
306
- ? "Worker name is required"
307
- : undefined,
308
- });
309
- if (isCancel(worker) || !worker) return;
310
-
311
- const secretName = await text({
312
- message: "Secret name",
313
- placeholder: "e.g. API_KEY, DATABASE_URL",
314
- validate: (v) =>
315
- v === undefined || v.length === 0
316
- ? "Secret name is required"
317
- : undefined,
318
- });
319
- if (isCancel(secretName) || !secretName) return;
320
-
321
- await runCommand(program, `config secrets set ${worker} ${secretName}`);
322
- break;
323
- }
324
- case "sync":
325
- await runCommand(program, "config secrets sync");
326
- break;
327
- }
328
- }
329
-
330
- // ---------------------------------------------------------------------------
331
- // Keys sub-sub-menu
332
- // ---------------------------------------------------------------------------
333
-
334
- async function showKeysSubMenu(program: Command): Promise<void> {
335
- const choice = await select({
336
- message: "Auth Keys",
337
- options: [
338
- { value: "generate", label: "Generate new keys" },
339
- { value: "list", label: "List existing keys" },
340
- { value: "__back", label: "◀ Back to Manage" },
341
- ],
342
- });
343
-
344
- if (isCancel(choice) || choice === "__back") return;
345
-
346
- if (choice === "generate") {
347
- const proceed = await confirm({
348
- message:
349
- "Generate new internal auth keys? This will create .keys/*.env files.",
350
- initialValue: false,
351
- });
352
- if (isCancel(proceed) || !proceed) return;
353
- }
354
-
355
- await runCommand(program, `config keys ${choice}`);
356
- }
357
-
358
- // ---------------------------------------------------------------------------
359
- // Monitor sub-menu
360
- // ---------------------------------------------------------------------------
361
-
362
- async function showMonitorMenu(
363
- program: Command
364
- ): Promise<"back" | "exit" | "continue"> {
365
- while (true) {
366
- const choice = await select({
367
- message: "Monitor",
368
- options: [
369
- {
370
- value: "check setup",
371
- label: "Run diagnostics",
372
- hint: "full system validation",
373
- },
374
- {
375
- value: "check health",
376
- label: "Health check",
377
- hint: "worker connectivity",
378
- },
379
- { value: "check fix", label: "Auto-repair", hint: "fix common issues" },
380
- { value: "__logs", label: "View logs", hint: "real-time log tailing" },
381
- { value: "test", label: "Run tests" },
382
- { value: "__back", label: "◀ Back to main menu" },
383
- ],
384
- });
385
-
386
- if (isCancel(choice)) return "back";
387
- if (choice === "__back") return "continue";
388
-
389
- if (choice === "__logs") {
390
- const target = await select({
391
- message: "Tail logs for",
392
- options: [
393
- { value: "all", label: "All workers" },
394
- { value: "single", label: "A specific worker..." },
395
- { value: "__back", label: "◀ Back" },
396
- ],
397
- });
398
- if (isCancel(target) || target === "__back") continue;
399
- if (target === "single") {
400
- const name = await text({
401
- message: "Worker name",
402
- placeholder: "e.g. hoox, trade-worker, d1-worker",
403
- });
404
- if (isCancel(name) || !name) continue;
405
- await runCommand(program, `logs worker ${name}`);
406
- } else {
407
- await runCommand(program, "logs all");
408
- }
409
- continue;
410
- }
411
-
412
- await runCommand(program, choice);
413
- }
414
- }
415
-
416
- // ---------------------------------------------------------------------------
417
- // Tools sub-menu
418
- // ---------------------------------------------------------------------------
419
-
420
- async function showToolsMenu(
421
- program: Command
422
- ): Promise<"back" | "exit" | "continue"> {
423
- while (true) {
424
- const choice = await select({
425
- message: "Tools",
426
- options: [
427
- {
428
- value: "clone",
429
- label: "Clone worker",
430
- hint: "scaffold from template",
431
- },
432
- {
433
- value: "dashboard",
434
- label: "Dashboard UI",
435
- hint: "manage dashboard URLs",
436
- },
437
- {
438
- value: "tui",
439
- label: "Launch TUI Dashboard",
440
- hint: "OpenTUI terminal operations center",
441
- },
442
- { value: "__back", label: "◀ Back to main menu" },
443
- ],
444
- });
445
-
446
- if (isCancel(choice)) return "back";
447
- if (choice === "__back") return "continue";
448
-
449
- await runCommand(program, choice);
450
- }
451
- }
452
-
453
- // ---------------------------------------------------------------------------
454
- // Command execution
455
- // ---------------------------------------------------------------------------
456
-
457
- /**
458
- * Run a Commander command string inside the TUI loop.
459
- *
460
- * Delegates to `program.parseAsync()` with the given arguments.
461
- * Wraps in try-catch so errors don't terminate the loop.
462
- */
463
- async function runCommand(program: Command, commandStr: string): Promise<void> {
464
- const args = commandStr.split(" ");
465
-
466
- try {
467
- // from: "user" means Commander expects raw user arguments only —
468
- // NOT [node, script, ...args] format. So just pass the args directly.
469
- await program.parseAsync(args, {
470
- from: "user",
471
- });
472
- } catch (err) {
473
- // Check if it's a Commander informational exit (help/version)
474
- if (
475
- err instanceof Error &&
476
- "code" in err &&
477
- (err as { code: string }).code === "commander.helpDisplayed"
478
- ) {
479
- return;
480
- }
481
-
482
- const message =
483
- err instanceof CLIError
484
- ? err.message
485
- : err instanceof Error
486
- ? err.message
487
- : String(err);
488
- log.error(`Command failed: ${message}`);
489
- }
490
- }
@@ -1,62 +0,0 @@
1
- /**
2
- * Shared CLI error-handling wrapper.
3
- *
4
- * Provides a `withErrorHandling()` higher-order function that wraps command
5
- * handlers with standardized error formatting and exit-code management,
6
- * eliminating ~23 duplicated try/catch blocks across command files.
7
- *
8
- * Inner/recoverable try/catch blocks (e.g. JSON parse, per-key KV errors,
9
- * API network errors) are left in place — only the outermost handler-level
10
- * catch-all is replaced.
11
- */
12
-
13
- import { formatError } from "./formatters.js";
14
- import { CLIError, ExitCode } from "./errors.js";
15
- import type { FormatOptions } from "./formatters.js";
16
-
17
- export type CommandResult = Promise<void>;
18
-
19
- /**
20
- * Wraps a command handler with standardized error handling.
21
- *
22
- * @param handler - The command action function to wrap
23
- * @param options.service - Service name for error prefix (e.g. "db", "kv")
24
- * @param options.opts - Optional FormatOptions for JSON/quiet-aware output
25
- *
26
- * @returns A wrapped function that catches errors, formats them, and exits
27
- *
28
- * @example
29
- * ```ts
30
- * .action(withErrorHandling(async (cmd) => {
31
- * const opts = getFormatOptions(cmd);
32
- * // …command logic…
33
- * }, { service: "db" }))
34
- * ```
35
- */
36
- export function withErrorHandling<T extends unknown[]>(
37
- handler: (...args: T) => Promise<void>,
38
- options?: { service?: string; opts?: FormatOptions }
39
- ): (...args: T) => CommandResult {
40
- return async (...args: T) => {
41
- try {
42
- await handler(...args);
43
- } catch (error) {
44
- const service = options?.service ?? "cli";
45
-
46
- if (error instanceof CLIError) {
47
- // CLIError is already structured — pass through for proper formatting
48
- formatError(error, options?.opts);
49
- process.exit(error.code);
50
- } else if (error instanceof Error) {
51
- formatError(`[${service}] ${error.message}`, options?.opts);
52
- process.exit(ExitCode.ERROR);
53
- } else {
54
- formatError(
55
- `[${service}] Unknown error: ${String(error)}`,
56
- options?.opts
57
- );
58
- process.exit(ExitCode.CommandFailed);
59
- }
60
- }
61
- };
62
- }