@jango-blockchained/hoox-cli 0.3.4

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 (94) hide show
  1. package/README.md +403 -0
  2. package/bin/hoox.js +12 -0
  3. package/package.json +60 -0
  4. package/src/commands/check/check-command.test.ts +468 -0
  5. package/src/commands/check/check-command.ts +1144 -0
  6. package/src/commands/check/index.ts +10 -0
  7. package/src/commands/check/prerequisites-command.test.ts +19 -0
  8. package/src/commands/check/prerequisites-command.ts +92 -0
  9. package/src/commands/check/types.ts +103 -0
  10. package/src/commands/clone/clone-command.test.ts +442 -0
  11. package/src/commands/clone/clone-command.ts +440 -0
  12. package/src/commands/clone/index.ts +1 -0
  13. package/src/commands/config/config-command.test.ts +583 -0
  14. package/src/commands/config/config-command.ts +901 -0
  15. package/src/commands/config/env-command.test.ts +43 -0
  16. package/src/commands/config/env-command.ts +314 -0
  17. package/src/commands/config/index.ts +3 -0
  18. package/src/commands/config/kv-command.test.ts +14 -0
  19. package/src/commands/config/kv-command.ts +329 -0
  20. package/src/commands/dashboard/dashboard-command.test.ts +47 -0
  21. package/src/commands/dashboard/dashboard-command.ts +127 -0
  22. package/src/commands/dashboard/index.ts +1 -0
  23. package/src/commands/db/db-command.test.ts +21 -0
  24. package/src/commands/db/db-command.ts +314 -0
  25. package/src/commands/db/index.ts +1 -0
  26. package/src/commands/deploy/deploy-command.test.ts +304 -0
  27. package/src/commands/deploy/deploy-command.ts +1053 -0
  28. package/src/commands/deploy/index.ts +2 -0
  29. package/src/commands/deploy/telegram-service.ts +61 -0
  30. package/src/commands/deploy/types.ts +34 -0
  31. package/src/commands/dev/dev-command.test.ts +383 -0
  32. package/src/commands/dev/dev-command.ts +407 -0
  33. package/src/commands/dev/index.ts +1 -0
  34. package/src/commands/infra/index.ts +5 -0
  35. package/src/commands/infra/infra-command.test.ts +719 -0
  36. package/src/commands/infra/infra-command.ts +940 -0
  37. package/src/commands/infra/types.ts +23 -0
  38. package/src/commands/init/index.ts +1 -0
  39. package/src/commands/init/init-command.test.ts +827 -0
  40. package/src/commands/init/init-command.ts +627 -0
  41. package/src/commands/init/types.ts +185 -0
  42. package/src/commands/monitor/index.ts +2 -0
  43. package/src/commands/monitor/monitor-command.test.ts +235 -0
  44. package/src/commands/monitor/monitor-command.ts +245 -0
  45. package/src/commands/monitor/monitor-service.ts +50 -0
  46. package/src/commands/monitor/types.ts +13 -0
  47. package/src/commands/repair/index.ts +2 -0
  48. package/src/commands/repair/repair-command.test.ts +204 -0
  49. package/src/commands/repair/repair-command.ts +199 -0
  50. package/src/commands/repair/repair-service.ts +102 -0
  51. package/src/commands/repair/types.ts +13 -0
  52. package/src/commands/test/index.ts +2 -0
  53. package/src/commands/test/test-command.test.ts +319 -0
  54. package/src/commands/test/test-command.ts +412 -0
  55. package/src/commands/waf/index.ts +2 -0
  56. package/src/commands/waf/types.ts +48 -0
  57. package/src/commands/waf/waf-command.test.ts +506 -0
  58. package/src/commands/waf/waf-command.ts +548 -0
  59. package/src/index.ts +198 -0
  60. package/src/services/cloudflare/cloudflare-service.test.ts +654 -0
  61. package/src/services/cloudflare/cloudflare-service.ts +435 -0
  62. package/src/services/cloudflare/index.ts +2 -0
  63. package/src/services/cloudflare/types.ts +29 -0
  64. package/src/services/config/config-service.test.ts +395 -0
  65. package/src/services/config/config-service.ts +207 -0
  66. package/src/services/config/index.ts +9 -0
  67. package/src/services/config/types.ts +66 -0
  68. package/src/services/db/db-service.test.ts +51 -0
  69. package/src/services/db/db-service.ts +140 -0
  70. package/src/services/db/index.ts +1 -0
  71. package/src/services/docker/docker-service.ts +155 -0
  72. package/src/services/docker/index.ts +1 -0
  73. package/src/services/env/env-service.test.ts +210 -0
  74. package/src/services/env/env-service.ts +156 -0
  75. package/src/services/env/index.ts +1 -0
  76. package/src/services/kv/index.ts +1 -0
  77. package/src/services/kv/kv-sync-service.test.ts +38 -0
  78. package/src/services/kv/kv-sync-service.ts +151 -0
  79. package/src/services/prerequisites/index.ts +1 -0
  80. package/src/services/prerequisites/prerequisites-service.test.ts +89 -0
  81. package/src/services/prerequisites/prerequisites-service.ts +269 -0
  82. package/src/services/prerequisites/types.ts +48 -0
  83. package/src/services/secrets/index.ts +12 -0
  84. package/src/services/secrets/secrets-service.test.ts +486 -0
  85. package/src/services/secrets/secrets-service.ts +293 -0
  86. package/src/services/secrets/types.ts +57 -0
  87. package/src/ui/banner.ts +52 -0
  88. package/src/ui/index.ts +8 -0
  89. package/src/ui/menu.ts +473 -0
  90. package/src/utils/errors.test.ts +69 -0
  91. package/src/utils/errors.ts +23 -0
  92. package/src/utils/formatters.test.ts +180 -0
  93. package/src/utils/formatters.ts +252 -0
  94. package/src/utils/theme.ts +94 -0
@@ -0,0 +1,407 @@
1
+ /**
2
+ * `hoox dev` command group — local development for workers and dashboard.
3
+ *
4
+ * Subcommands:
5
+ * start — Launch all workers with wrangler dev
6
+ * worker — Start a single worker with wrangler dev
7
+ * dashboard — Start the Next.js dashboard dev server
8
+ */
9
+
10
+ import { Command } from "commander";
11
+ import { select } from "@clack/prompts";
12
+ import path from "node:path";
13
+ import { ConfigService } from "../../services/config/index.js";
14
+ import { CloudflareService } from "../../services/cloudflare/index.js";
15
+ import { PrerequisitesService } from "../../services/prerequisites/index.js";
16
+ import { DockerService } from "../../services/docker/index.js";
17
+ import { formatSuccess, formatError } from "../../utils/formatters.js";
18
+ import { CLIError, ExitCode } from "../../utils/errors.js";
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Helpers
22
+ // ---------------------------------------------------------------------------
23
+
24
+ /**
25
+ * Build the format options for output, reading global --json / --quiet flags.
26
+ * Uses `optsWithGlobals()` to include options inherited from the top-level program.
27
+ */
28
+ function getFormatOptions(cmd: Command) {
29
+ const opts = cmd.optsWithGlobals();
30
+ return { json: Boolean(opts.json), quiet: Boolean(opts.quiet) };
31
+ }
32
+
33
+ // ---------------------------------------------------------------------------
34
+ // Command registration
35
+ // ---------------------------------------------------------------------------
36
+
37
+ /**
38
+ * Register the `hoox dev` command group with subcommands:
39
+ * start, worker <name>, dashboard.
40
+ */
41
+ export function registerDevCommand(program: Command): void {
42
+ const devCmd = program
43
+ .command("dev")
44
+ .summary("Local development commands for workers and dashboard")
45
+ .description(
46
+ `Start local development servers for your Hoox workers and dashboard.
47
+
48
+ RUNTIME OPTIONS:
49
+ native - Uses wrangler dev (default, recommended for most cases)
50
+ docker - Uses Docker Compose (requires Docker + docker-compose)
51
+
52
+ The CLI will prompt you to choose a runtime if Docker is available and no preference is saved. Your choice is saved to wrangler.jsonc for future runs.
53
+
54
+ EXAMPLES:
55
+ hoox dev start Start all workers (prompts for runtime)
56
+ hoox dev start --runtime native Force native runtime (wrangler dev)
57
+ hoox dev start --runtime docker Force Docker runtime
58
+ hoox dev worker trade-worker Start single worker
59
+ hoox dev dashboard Start dashboard dev server`
60
+ );
61
+
62
+ // -- dev start ------------------------------------------------------------
63
+ devCmd
64
+ .command("start")
65
+ .summary("Start all enabled workers for local development")
66
+ .description(
67
+ `Start all enabled workers using either wrangler dev (native) or Docker Compose.
68
+
69
+ The command checks for prerequisites (wrangler version, Docker availability) and starts workers on sequential ports starting at 8787.
70
+
71
+ PORT ASSIGNMENTS:
72
+ hoox → 8787
73
+ trade-worker → 8788
74
+ d1-worker → 8789
75
+ telegram-worker → 8790
76
+ web3-wallet-worker → 8792
77
+ (other workers) → 8800+
78
+
79
+ OPTIONS:
80
+ --runtime <native|docker> Choose dev runtime (overrides saved preference)
81
+
82
+ EXAMPLES:
83
+ hoox dev start
84
+ hoox dev start --runtime native
85
+ hoox dev start --runtime docker`
86
+ )
87
+ .option(
88
+ "--runtime <native|docker>",
89
+ "Choose dev runtime (overrides saved preference)"
90
+ )
91
+ .action(async (options: { runtime?: string }) => {
92
+ const fmt = getFormatOptions(program);
93
+ try {
94
+ const configService = new ConfigService();
95
+ const prereqs = new PrerequisitesService();
96
+ const docker = new DockerService();
97
+
98
+ // Load config early so we can read saved runtime preference
99
+ await configService.load();
100
+
101
+ // === Prerequisite checks ===
102
+
103
+ // 1. Wrangler version check (advisory)
104
+ const wranglerCheck = await prereqs.checkWranglerVersion();
105
+ if (wranglerCheck.outdated) {
106
+ process.stdout.write(
107
+ `\n! wrangler is outdated (${wranglerCheck.current} < ${wranglerCheck.minimum})\n` +
108
+ ` Run \`bunx wrangler update\` to update.\n` +
109
+ ` Press Enter to continue or Ctrl+C to abort.\n\n`
110
+ );
111
+ // Wait briefly — user can proceed with Enter or Ctrl+C
112
+ }
113
+
114
+ // 2. Check Docker availability
115
+ const dockerStatus = await docker.checkAvailability();
116
+ const dockerViable = dockerStatus.docker && dockerStatus.compose;
117
+ const composeExists = await docker.composeFileExists();
118
+
119
+ // 3. Determine runtime
120
+ let runtime: "native" | "docker" = "native";
121
+ if (options.runtime === "native" || options.runtime === "docker") {
122
+ runtime = options.runtime;
123
+ } else {
124
+ const savedRuntime = configService.getDevRuntime();
125
+ if (savedRuntime) {
126
+ runtime = savedRuntime;
127
+ } else if (dockerViable && composeExists) {
128
+ const choice = await select({
129
+ message: "Which runtime would you like to use?",
130
+ options: [
131
+ {
132
+ value: "native",
133
+ label: "Native (wrangler dev)",
134
+ hint: "run locally with wrangler",
135
+ },
136
+ {
137
+ value: "docker",
138
+ label: "Docker",
139
+ hint: "run via docker compose",
140
+ },
141
+ ],
142
+ });
143
+ runtime = choice;
144
+ await configService.setDevRuntime(runtime);
145
+ }
146
+ }
147
+
148
+ // Validate configuration
149
+ const validation = configService.validate();
150
+ if (!validation.valid) {
151
+ formatError(
152
+ new CLIError(
153
+ `Invalid configuration:\n${validation.errors.map((e) => ` - ${e}`).join("\n")}`,
154
+ ExitCode.INVALID_USAGE
155
+ ),
156
+ fmt
157
+ );
158
+ process.exitCode = ExitCode.INVALID_USAGE;
159
+ return;
160
+ }
161
+
162
+ // === Runtime branching ===
163
+
164
+ if (runtime === "docker") {
165
+ const profiles = ["workers"];
166
+ formatSuccess(
167
+ `Starting workers via Docker Compose (profiles: ${profiles.join(", ")})...`,
168
+ fmt
169
+ );
170
+ const result = await docker.composeUp(profiles, false);
171
+ if (!result.ok) {
172
+ formatError(
173
+ new CLIError(
174
+ result.error ?? "Docker compose failed",
175
+ ExitCode.ERROR
176
+ ),
177
+ fmt
178
+ );
179
+ process.exitCode = ExitCode.ERROR;
180
+ }
181
+ return;
182
+ }
183
+
184
+ // === Native runtime (wrangler dev) ===
185
+
186
+ const enabledWorkers = configService.listEnabledWorkers();
187
+ if (enabledWorkers.length === 0) {
188
+ formatError(
189
+ new CLIError(
190
+ "No enabled workers found in wrangler.jsonc. Enable at least one worker.",
191
+ ExitCode.INVALID_USAGE
192
+ ),
193
+ fmt
194
+ );
195
+ process.exitCode = ExitCode.INVALID_USAGE;
196
+ return;
197
+ }
198
+
199
+ // Port assignments for known workers (from local-dev.md)
200
+ const LOCAL_PORTS: Record<string, number> = {
201
+ hoox: 8787,
202
+ "trade-worker": 8788,
203
+ "d1-worker": 8789,
204
+ "telegram-worker": 8790,
205
+ "web3-wallet-worker": 8792,
206
+ };
207
+
208
+ let nextFallbackPort = 8800;
209
+ const cf = new CloudflareService();
210
+ let started = 0;
211
+ let failed = 0;
212
+
213
+ for (const name of enabledWorkers) {
214
+ const worker = configService.getWorker(name);
215
+ if (!worker) {
216
+ formatError(`Worker "${name}" not found in config, skipping.`, fmt);
217
+ failed++;
218
+ continue;
219
+ }
220
+
221
+ const port = LOCAL_PORTS[name] ?? nextFallbackPort++;
222
+ formatSuccess(
223
+ `Starting worker "${name}" (${worker.path}) on port ${port}...`,
224
+ fmt
225
+ );
226
+
227
+ const result = await cf.dev(worker.path, port);
228
+ if (!result.ok) {
229
+ formatError(
230
+ `Failed to start worker "${name}": ${result.error}`,
231
+ fmt
232
+ );
233
+ failed++;
234
+ } else {
235
+ formatSuccess(
236
+ `Worker "${name}" running on http://localhost:${result.data.port}`,
237
+ fmt
238
+ );
239
+ started++;
240
+ }
241
+ }
242
+
243
+ if (started > 0) {
244
+ formatSuccess(
245
+ `Started ${started} worker(s) — http://localhost:8787 (and adjacent ports)`,
246
+ fmt
247
+ );
248
+ }
249
+ if (failed > 0) {
250
+ process.exitCode = ExitCode.ERROR;
251
+ }
252
+ } catch (err) {
253
+ const message = err instanceof Error ? err.message : String(err);
254
+ formatError(message, fmt);
255
+ process.exitCode = ExitCode.ERROR;
256
+ }
257
+ });
258
+
259
+ // -- dev worker <name> [--port] -------------------------------------------
260
+ devCmd
261
+ .command("worker <name>")
262
+ .summary("Start a single worker for local development")
263
+ .description(
264
+ `Start a specific worker using wrangler dev.
265
+
266
+ ARGUMENTS:
267
+ name Worker name (e.g., trade-worker, agent-worker, hoox)
268
+
269
+ OPTIONS:
270
+ --port <port> Dev server port (default: 8787)
271
+ --runtime <native|docker> Choose dev runtime (overrides saved preference)
272
+
273
+ EXAMPLES:
274
+ hoox dev worker trade-worker
275
+ hoox dev worker agent-worker --port 8788
276
+ hoox dev worker hoox --runtime docker`
277
+ )
278
+ .option("--port <port>", "Dev server port (default: 8787)", (v: string) =>
279
+ parseInt(v, 10)
280
+ )
281
+ .option(
282
+ "--runtime <native|docker>",
283
+ "Choose dev runtime (overrides saved preference)"
284
+ )
285
+ .action(
286
+ async (name: string, options: { port?: number; runtime?: string }) => {
287
+ const fmt = getFormatOptions(program);
288
+ try {
289
+ const configService = new ConfigService();
290
+ await configService.load();
291
+
292
+ // Validate the worker exists in config
293
+ const worker = configService.getWorker(name);
294
+ if (!worker) {
295
+ formatError(
296
+ new CLIError(
297
+ `Worker "${name}" not found in wrangler.jsonc.\n` +
298
+ `Available workers: ${configService.listWorkers().join(", ")}`,
299
+ ExitCode.INVALID_USAGE
300
+ ),
301
+ fmt
302
+ );
303
+ process.exitCode = ExitCode.INVALID_USAGE;
304
+ return;
305
+ }
306
+
307
+ if (!worker.enabled) {
308
+ formatError(
309
+ new CLIError(
310
+ `Worker "${name}" is disabled. Enable it with the config command first.`,
311
+ ExitCode.INVALID_USAGE
312
+ ),
313
+ fmt
314
+ );
315
+ process.exitCode = ExitCode.INVALID_USAGE;
316
+ return;
317
+ }
318
+
319
+ const port = options.port ?? 8787;
320
+
321
+ formatSuccess(
322
+ `Starting worker "${name}" (${worker.path}) on port ${port}...`,
323
+ fmt
324
+ );
325
+
326
+ const cf = new CloudflareService();
327
+ const result = await cf.dev(worker.path, port);
328
+
329
+ if (!result.ok) {
330
+ formatError(
331
+ new CLIError(
332
+ `Failed to start worker "${name}": ${result.error}`,
333
+ ExitCode.ERROR
334
+ ),
335
+ fmt
336
+ );
337
+ process.exitCode = ExitCode.ERROR;
338
+ return;
339
+ }
340
+
341
+ formatSuccess(
342
+ `Worker "${name}" running on http://localhost:${result.data.port}`,
343
+ fmt
344
+ );
345
+ } catch (err) {
346
+ const message = err instanceof Error ? err.message : String(err);
347
+ formatError(message, fmt);
348
+ process.exitCode = ExitCode.ERROR;
349
+ }
350
+ }
351
+ );
352
+
353
+ // -- dev dashboard --------------------------------------------------------
354
+ devCmd
355
+ .command("dashboard")
356
+ .summary("Start the Next.js dashboard dev server")
357
+ .description(
358
+ `Start the Hoox dashboard development server using Next.js.
359
+
360
+ The dashboard runs on http://localhost:3000 with hot-reloading enabled.
361
+
362
+ OPTIONS:
363
+ --runtime <native|docker> Choose dev runtime (overrides saved preference)
364
+
365
+ EXAMPLES:
366
+ hoox dev dashboard
367
+ hoox dev dashboard --runtime docker`
368
+ )
369
+ .option(
370
+ "--runtime <native|docker>",
371
+ "Choose dev runtime (overrides saved preference)"
372
+ )
373
+ .action(async () => {
374
+ const fmt = getFormatOptions(program);
375
+ try {
376
+ const dashboardPath = path.resolve(process.cwd(), "workers/dashboard");
377
+ const dashboardDir = Bun.file(dashboardPath);
378
+
379
+ if (!(await dashboardDir.exists())) {
380
+ formatError(
381
+ new CLIError(
382
+ `Dashboard directory not found: ${dashboardPath}`,
383
+ ExitCode.INVALID_USAGE
384
+ ),
385
+ fmt
386
+ );
387
+ process.exitCode = ExitCode.INVALID_USAGE;
388
+ return;
389
+ }
390
+
391
+ formatSuccess("Starting Next.js dashboard dev server...", fmt);
392
+
393
+ // Fire-and-forget: spawn `bun run dev` inside pages/dashboard
394
+ Bun.spawn(["bun", "run", "dev"], {
395
+ cwd: dashboardPath,
396
+ stdout: "inherit",
397
+ stderr: "inherit",
398
+ });
399
+
400
+ formatSuccess("Dashboard starting on http://localhost:3000", fmt);
401
+ } catch (err) {
402
+ const message = err instanceof Error ? err.message : String(err);
403
+ formatError(message, fmt);
404
+ process.exitCode = ExitCode.ERROR;
405
+ }
406
+ });
407
+ }
@@ -0,0 +1 @@
1
+ export { registerDevCommand } from "./dev-command.js";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Barrel export for the `hoox infra` command group.
3
+ */
4
+ export { registerInfraCommand } from "./infra-command.js";
5
+ export type { InfraOptions, ProvisionItem, ProvisionResult } from "./types.js";