@jango-blockchained/hoox-cli 0.5.6 → 0.5.8

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 (2) hide show
  1. package/dist/index.js +1373 -929
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -23437,6 +23437,9 @@ var init_schema_service = __esm(() => {
23437
23437
  init_src();
23438
23438
  });
23439
23439
 
23440
+ // src/index.ts
23441
+ import { readFileSync as readFileSync5 } from "fs";
23442
+
23440
23443
  // ../../node_modules/.bun/commander@14.0.3/node_modules/commander/esm.mjs
23441
23444
  var import__ = __toESM(require_commander(), 1);
23442
23445
  var {
@@ -25147,6 +25150,52 @@ class CloudflareService {
25147
25150
  }
25148
25151
  return { ok: true, value: deployResult };
25149
25152
  }
25153
+ async versionsList(workerName) {
25154
+ const result = await this.runWrangler([
25155
+ "versions",
25156
+ "list",
25157
+ "--name",
25158
+ workerName,
25159
+ "--json"
25160
+ ]);
25161
+ if (!result.ok) {
25162
+ return result;
25163
+ }
25164
+ try {
25165
+ const parsed = JSON.parse(result.value);
25166
+ if (!Array.isArray(parsed)) {
25167
+ return {
25168
+ ok: false,
25169
+ error: `Expected array from wrangler versions list, got ${typeof parsed}`
25170
+ };
25171
+ }
25172
+ const versions2 = parsed.map((v2) => ({
25173
+ id: String(v2.id ?? ""),
25174
+ number: typeof v2.number === "number" ? v2.number : undefined,
25175
+ created_on: typeof v2.metadata === "object" && v2.metadata !== null ? String(v2.metadata.created_on ?? "") : undefined,
25176
+ author: typeof v2.metadata === "object" && v2.metadata !== null ? String(v2.metadata.author ?? "") : undefined,
25177
+ message: typeof v2.metadata === "object" && v2.metadata !== null ? String(v2.metadata.message ?? "") : undefined,
25178
+ source: typeof v2.metadata === "object" && v2.metadata !== null ? String(v2.metadata.source ?? "") : undefined
25179
+ }));
25180
+ return { ok: true, value: versions2 };
25181
+ } catch (err) {
25182
+ const message = err instanceof Error ? err.message : String(err);
25183
+ return {
25184
+ ok: false,
25185
+ error: `Failed to parse versions list: ${message}`
25186
+ };
25187
+ }
25188
+ }
25189
+ async versionsRollback(workerName, versionId) {
25190
+ return this.runWrangler([
25191
+ "versions",
25192
+ "rollback",
25193
+ "--name",
25194
+ workerName,
25195
+ "--version-id",
25196
+ versionId
25197
+ ]);
25198
+ }
25150
25199
  async dev(workerPath, port) {
25151
25200
  const devPort = port ?? 8787;
25152
25201
  const resolvedPath = path.resolve(this.cwd, workerPath);
@@ -25291,6 +25340,27 @@ class CloudflareService {
25291
25340
  return;
25292
25341
  }
25293
25342
  }
25343
+ // src/utils/error-handler.ts
25344
+ function withErrorHandling(handler, options) {
25345
+ return async (...args) => {
25346
+ try {
25347
+ await handler(...args);
25348
+ } catch (error51) {
25349
+ const service = options?.service ?? "cli";
25350
+ if (error51 instanceof CLIError) {
25351
+ formatError2(error51, options?.opts);
25352
+ process.exitCode = error51.code;
25353
+ } else if (error51 instanceof Error) {
25354
+ formatError2(`[${service}] ${error51.message}`, options?.opts);
25355
+ process.exitCode = 1 /* ERROR */;
25356
+ } else {
25357
+ formatError2(`[${service}] Unknown error: ${String(error51)}`, options?.opts);
25358
+ process.exitCode = -1 /* CommandFailed */;
25359
+ }
25360
+ }
25361
+ };
25362
+ }
25363
+
25294
25364
  // src/commands/init/cli-provisioner.ts
25295
25365
  class CLIProvisioner {
25296
25366
  async provision(plan) {
@@ -25341,9 +25411,7 @@ class CLIProvisioner {
25341
25411
  async check(plan) {
25342
25412
  const created = [
25343
25413
  ...plan.d1Databases.map((d) => `D1:${d}`),
25344
- ...plan.kvNamespaces.map((k2) => `KV:${k2}`),
25345
- ...plan.r2Buckets.map((b2) => `R2:${b2}`),
25346
- ...plan.queues.map((q2) => `Queue:${q2}`)
25414
+ ...plan.kvNamespaces.map((k2) => `KV:${k2}`)
25347
25415
  ];
25348
25416
  return { success: true, created, errors: [] };
25349
25417
  }
@@ -25483,18 +25551,11 @@ OPTIONS:
25483
25551
 
25484
25552
  EXAMPLES:
25485
25553
  hoox init Interactive wizard
25486
- hoox init --token cfut_xxx --account xxx Non-interactive`).option("--token <token>", "Cloudflare API token (non-interactive)").option("--account <id>", "Cloudflare Account ID (non-interactive)").option("--secret-store <id>", "Secret Store ID (non-interactive)").option("--prefix <prefix>", "Subdomain prefix (non-interactive, default: cryptolinx)").option("--preset <name>", "Worker preset (minimal, standard, full)").option("--resume", "Resume from saved wizard state").option("--accept-risk", "Skip the risk acknowledgment confirmation").action(async (options) => {
25554
+ hoox init --token cfut_xxx --account xxx Non-interactive`).option("--token <token>", "Cloudflare API token (non-interactive)").option("--account <id>", "Cloudflare Account ID (non-interactive)").option("--secret-store <id>", "Secret Store ID (non-interactive)").option("--prefix <prefix>", "Subdomain prefix (non-interactive, default: cryptolinx)").option("--preset <name>", "Worker preset (minimal, standard, full)").option("--resume", "Resume from saved wizard state").option("--accept-risk", "Skip the risk acknowledgment confirmation").action(withErrorHandling(async (options) => {
25487
25555
  const globalOpts = getFormatOptions(program2);
25488
25556
  const isNonInteractive = Boolean(options.token && options.account);
25489
- try {
25490
- await runInitCommand(options, globalOpts, isNonInteractive);
25491
- } catch (err) {
25492
- const message = err instanceof Error ? err.message : String(err);
25493
- R2.error(message);
25494
- formatError2(new CLIError(message, 1 /* ERROR */), globalOpts);
25495
- process.exitCode = 1 /* ERROR */;
25496
- }
25497
- });
25557
+ await runInitCommand(options, globalOpts, isNonInteractive);
25558
+ }, { service: "init" }));
25498
25559
  }
25499
25560
  async function runInitCommand(options, globalOpts, isNonInteractive) {
25500
25561
  if (isNonInteractive) {
@@ -26188,119 +26249,113 @@ OPTIONS:
26188
26249
  EXAMPLES:
26189
26250
  hoox dev start
26190
26251
  hoox dev start --runtime native
26191
- hoox dev start --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(async (options) => {
26252
+ hoox dev start --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (options) => {
26192
26253
  const fmt = getFormatOptions(program2);
26193
- try {
26194
- const configService = new ConfigService;
26195
- const prereqs = new PrerequisitesService;
26196
- const docker = new DockerService;
26197
- await configService.load();
26198
- const wranglerCheck = await prereqs.checkWranglerVersion();
26199
- if (wranglerCheck.outdated) {
26200
- process.stdout.write(`
26254
+ const configService = new ConfigService;
26255
+ const prereqs = new PrerequisitesService;
26256
+ const docker = new DockerService;
26257
+ await configService.load();
26258
+ const wranglerCheck = await prereqs.checkWranglerVersion();
26259
+ if (wranglerCheck.outdated) {
26260
+ process.stdout.write(`
26201
26261
  ! wrangler is outdated (${wranglerCheck.current} < ${wranglerCheck.minimum})
26202
26262
  ` + ` Run \`bunx wrangler update\` to update.
26203
26263
  ` + ` Press Enter to continue or Ctrl+C to abort.
26204
26264
 
26205
26265
  `);
26206
- }
26207
- const dockerStatus = await docker.checkAvailability();
26208
- const dockerViable = dockerStatus.docker && dockerStatus.compose;
26209
- const composeExists = await docker.composeFileExists();
26210
- let runtime = "native";
26211
- if (options.runtime === "native" || options.runtime === "docker") {
26212
- runtime = options.runtime;
26213
- } else {
26214
- const savedRuntime = configService.getDevRuntime();
26215
- if (savedRuntime) {
26216
- runtime = savedRuntime;
26217
- } else if (dockerViable && composeExists) {
26218
- const choice = await Ee({
26219
- message: "Which runtime would you like to use?",
26220
- options: [
26221
- {
26222
- value: "native",
26223
- label: "Native (wrangler dev)",
26224
- hint: "run locally with wrangler"
26225
- },
26226
- {
26227
- value: "docker",
26228
- label: "Docker",
26229
- hint: "run via docker compose"
26230
- }
26231
- ]
26232
- });
26233
- if (choice === "native" || choice === "docker") {
26234
- runtime = choice;
26235
- await configService.setDevRuntime(runtime);
26236
- }
26266
+ }
26267
+ const dockerStatus = await docker.checkAvailability();
26268
+ const dockerViable = dockerStatus.docker && dockerStatus.compose;
26269
+ const composeExists = await docker.composeFileExists();
26270
+ let runtime = "native";
26271
+ if (options.runtime === "native" || options.runtime === "docker") {
26272
+ runtime = options.runtime;
26273
+ } else {
26274
+ const savedRuntime = configService.getDevRuntime();
26275
+ if (savedRuntime) {
26276
+ runtime = savedRuntime;
26277
+ } else if (dockerViable && composeExists) {
26278
+ const choice = await Ee({
26279
+ message: "Which runtime would you like to use?",
26280
+ options: [
26281
+ {
26282
+ value: "native",
26283
+ label: "Native (wrangler dev)",
26284
+ hint: "run locally with wrangler"
26285
+ },
26286
+ {
26287
+ value: "docker",
26288
+ label: "Docker",
26289
+ hint: "run via docker compose"
26290
+ }
26291
+ ]
26292
+ });
26293
+ if (choice === "native" || choice === "docker") {
26294
+ runtime = choice;
26295
+ await configService.setDevRuntime(runtime);
26237
26296
  }
26238
26297
  }
26239
- const validation = configService.validate();
26240
- if (!validation.valid) {
26241
- formatError2(new CLIError(`Invalid configuration:
26298
+ }
26299
+ const validation = configService.validate();
26300
+ if (!validation.valid) {
26301
+ formatError2(new CLIError(`Invalid configuration:
26242
26302
  ${validation.errors.map((e2) => ` - ${e2}`).join(`
26243
26303
  `)}`, 2 /* INVALID_USAGE */), fmt);
26244
- process.exitCode = 2 /* INVALID_USAGE */;
26245
- return;
26246
- }
26247
- if (runtime === "docker") {
26248
- const profiles = ["workers"];
26249
- formatSuccess(`Starting workers via Docker Compose (profiles: ${profiles.join(", ")})...`, fmt);
26250
- const result = await docker.composeUp(profiles, false);
26251
- if (!result.ok) {
26252
- formatError2(new CLIError(result.error ?? "Docker compose failed", 1 /* ERROR */), fmt);
26253
- process.exitCode = 1 /* ERROR */;
26254
- }
26255
- return;
26256
- }
26257
- const enabledWorkers = configService.listEnabledWorkers();
26258
- if (enabledWorkers.length === 0) {
26259
- formatError2(new CLIError("No enabled workers found in wrangler.jsonc. Enable at least one worker.", 2 /* INVALID_USAGE */), fmt);
26260
- process.exitCode = 2 /* INVALID_USAGE */;
26261
- return;
26262
- }
26263
- const LOCAL_PORTS = {
26264
- hoox: 8787,
26265
- "trade-worker": 8788,
26266
- "d1-worker": 8789,
26267
- "telegram-worker": 8790,
26268
- "web3-wallet-worker": 8792
26269
- };
26270
- let nextFallbackPort = 8800;
26271
- const cf = new CloudflareService;
26272
- let started = 0;
26273
- let failed = 0;
26274
- for (const name of enabledWorkers) {
26275
- const worker = configService.getWorker(name);
26276
- if (!worker) {
26277
- formatError2(`Worker "${name}" not found in config, skipping.`, fmt);
26278
- failed++;
26279
- continue;
26280
- }
26281
- const port = LOCAL_PORTS[name] ?? nextFallbackPort++;
26282
- formatSuccess(`Starting worker "${name}" (${worker.path}) on port ${port}...`, fmt);
26283
- const result = await cf.dev(worker.path, port);
26284
- if (!result.ok) {
26285
- formatError2(`Failed to start worker "${name}": ${result.error}`, fmt);
26286
- failed++;
26287
- } else {
26288
- formatSuccess(`Worker "${name}" running on http://localhost:${result.value.port}`, fmt);
26289
- started++;
26290
- }
26304
+ process.exitCode = 2 /* INVALID_USAGE */;
26305
+ return;
26306
+ }
26307
+ if (runtime === "docker") {
26308
+ const profiles = ["workers"];
26309
+ formatSuccess(`Starting workers via Docker Compose (profiles: ${profiles.join(", ")})...`, fmt);
26310
+ const result = await docker.composeUp(profiles, false);
26311
+ if (!result.ok) {
26312
+ formatError2(new CLIError(result.error ?? "Docker compose failed", 1 /* ERROR */), fmt);
26313
+ process.exitCode = 1 /* ERROR */;
26291
26314
  }
26292
- if (started > 0) {
26293
- formatSuccess(`Started ${started} worker(s) \u2014 http://localhost:8787 (and adjacent ports)`, fmt);
26315
+ return;
26316
+ }
26317
+ const enabledWorkers = configService.listEnabledWorkers();
26318
+ if (enabledWorkers.length === 0) {
26319
+ formatError2(new CLIError("No enabled workers found in wrangler.jsonc. Enable at least one worker.", 2 /* INVALID_USAGE */), fmt);
26320
+ process.exitCode = 2 /* INVALID_USAGE */;
26321
+ return;
26322
+ }
26323
+ const LOCAL_PORTS = {
26324
+ hoox: 8787,
26325
+ "trade-worker": 8788,
26326
+ "d1-worker": 8789,
26327
+ "telegram-worker": 8790,
26328
+ "web3-wallet-worker": 8792
26329
+ };
26330
+ let nextFallbackPort = 8800;
26331
+ const cf = new CloudflareService;
26332
+ let started = 0;
26333
+ let failed = 0;
26334
+ for (const name of enabledWorkers) {
26335
+ const worker = configService.getWorker(name);
26336
+ if (!worker) {
26337
+ formatError2(`Worker "${name}" not found in config, skipping.`, fmt);
26338
+ failed++;
26339
+ continue;
26294
26340
  }
26295
- if (failed > 0) {
26296
- process.exitCode = 1 /* ERROR */;
26341
+ const port = LOCAL_PORTS[name] ?? nextFallbackPort++;
26342
+ formatSuccess(`Starting worker "${name}" (${worker.path}) on port ${port}...`, fmt);
26343
+ const result = await cf.dev(worker.path, port);
26344
+ if (!result.ok) {
26345
+ formatError2(`Failed to start worker "${name}": ${result.error}`, fmt);
26346
+ failed++;
26347
+ } else {
26348
+ formatSuccess(`Worker "${name}" running on http://localhost:${result.value.port}`, fmt);
26349
+ started++;
26297
26350
  }
26298
- } catch (err) {
26299
- const message = err instanceof Error ? err.message : String(err);
26300
- formatError2(message, fmt);
26351
+ }
26352
+ if (started > 0) {
26353
+ formatSuccess(`Started ${started} worker(s) \u2014 http://localhost:8787 (and adjacent ports)`, fmt);
26354
+ }
26355
+ if (failed > 0) {
26301
26356
  process.exitCode = 1 /* ERROR */;
26302
26357
  }
26303
- });
26358
+ }, { service: "dev" }));
26304
26359
  devCmd.command("worker <name>").summary("Start a single worker for local development").description(`Start a specific worker using wrangler dev.
26305
26360
 
26306
26361
  ARGUMENTS:
@@ -26313,39 +26368,33 @@ OPTIONS:
26313
26368
  EXAMPLES:
26314
26369
  hoox dev worker trade-worker
26315
26370
  hoox dev worker agent-worker --port 8788
26316
- hoox dev worker hoox --runtime docker`).option("--port <port>", "Dev server port (default: 8787)", (v2) => parseInt(v2, 10)).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(async (name, options) => {
26371
+ hoox dev worker hoox --runtime docker`).option("--port <port>", "Dev server port (default: 8787)", (v2) => parseInt(v2, 10)).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (name, options) => {
26317
26372
  const fmt = getFormatOptions(program2);
26318
- try {
26319
- const configService = new ConfigService;
26320
- await configService.load();
26321
- const worker = configService.getWorker(name);
26322
- if (!worker) {
26323
- formatError2(new CLIError(`Worker "${name}" not found in wrangler.jsonc.
26373
+ const configService = new ConfigService;
26374
+ await configService.load();
26375
+ const worker = configService.getWorker(name);
26376
+ if (!worker) {
26377
+ formatError2(new CLIError(`Worker "${name}" not found in wrangler.jsonc.
26324
26378
  ` + `Available workers: ${configService.listWorkers().join(", ")}`, 2 /* INVALID_USAGE */), fmt);
26325
- process.exitCode = 2 /* INVALID_USAGE */;
26326
- return;
26327
- }
26328
- if (!worker.enabled) {
26329
- formatError2(new CLIError(`Worker "${name}" is disabled. Enable it with the config command first.`, 2 /* INVALID_USAGE */), fmt);
26330
- process.exitCode = 2 /* INVALID_USAGE */;
26331
- return;
26332
- }
26333
- const port = options.port ?? 8787;
26334
- formatSuccess(`Starting worker "${name}" (${worker.path}) on port ${port}...`, fmt);
26335
- const cf = new CloudflareService;
26336
- const result = await cf.dev(worker.path, port);
26337
- if (!result.ok) {
26338
- formatError2(new CLIError(`Failed to start worker "${name}": ${result.error}`, 1 /* ERROR */), fmt);
26339
- process.exitCode = 1 /* ERROR */;
26340
- return;
26341
- }
26342
- formatSuccess(`Worker "${name}" running on http://localhost:${result.value.port}`, fmt);
26343
- } catch (err) {
26344
- const message = err instanceof Error ? err.message : String(err);
26345
- formatError2(message, fmt);
26379
+ process.exitCode = 2 /* INVALID_USAGE */;
26380
+ return;
26381
+ }
26382
+ if (!worker.enabled) {
26383
+ formatError2(new CLIError(`Worker "${name}" is disabled. Enable it with the config command first.`, 2 /* INVALID_USAGE */), fmt);
26384
+ process.exitCode = 2 /* INVALID_USAGE */;
26385
+ return;
26386
+ }
26387
+ const port = options.port ?? 8787;
26388
+ formatSuccess(`Starting worker "${name}" (${worker.path}) on port ${port}...`, fmt);
26389
+ const cf = new CloudflareService;
26390
+ const result = await cf.dev(worker.path, port);
26391
+ if (!result.ok) {
26392
+ formatError2(new CLIError(`Failed to start worker "${name}": ${result.error}`, 1 /* ERROR */), fmt);
26346
26393
  process.exitCode = 1 /* ERROR */;
26394
+ return;
26347
26395
  }
26348
- });
26396
+ formatSuccess(`Worker "${name}" running on http://localhost:${result.value.port}`, fmt);
26397
+ }, { service: "dev" }));
26349
26398
  devCmd.command("dashboard").summary("Start the Next.js dashboard dev server").description(`Start the Hoox dashboard development server using Next.js.
26350
26399
 
26351
26400
  The dashboard runs on http://localhost:3000 with hot-reloading enabled.
@@ -26355,29 +26404,23 @@ OPTIONS:
26355
26404
 
26356
26405
  EXAMPLES:
26357
26406
  hoox dev dashboard
26358
- hoox dev dashboard --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(async () => {
26407
+ hoox dev dashboard --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async () => {
26359
26408
  const fmt = getFormatOptions(program2);
26360
- try {
26361
- const dashboardPath = path2.resolve(process.cwd(), "workers/dashboard");
26362
- const dashboardDir = Bun.file(dashboardPath);
26363
- if (!await dashboardDir.exists()) {
26364
- formatError2(new CLIError(`Dashboard directory not found: ${dashboardPath}`, 2 /* INVALID_USAGE */), fmt);
26365
- process.exitCode = 2 /* INVALID_USAGE */;
26366
- return;
26367
- }
26368
- formatSuccess("Starting Next.js dashboard dev server...", fmt);
26369
- Bun.spawn(["bun", "run", "dev"], {
26370
- cwd: dashboardPath,
26371
- stdout: "inherit",
26372
- stderr: "inherit"
26373
- });
26374
- formatSuccess("Dashboard starting on http://localhost:3000", fmt);
26375
- } catch (err) {
26376
- const message = err instanceof Error ? err.message : String(err);
26377
- formatError2(message, fmt);
26378
- process.exitCode = 1 /* ERROR */;
26409
+ const dashboardPath = path2.resolve(process.cwd(), "workers/dashboard");
26410
+ const dashboardDir = Bun.file(dashboardPath);
26411
+ if (!await dashboardDir.exists()) {
26412
+ formatError2(new CLIError(`Dashboard directory not found: ${dashboardPath}`, 2 /* INVALID_USAGE */), fmt);
26413
+ process.exitCode = 2 /* INVALID_USAGE */;
26414
+ return;
26379
26415
  }
26380
- });
26416
+ formatSuccess("Starting Next.js dashboard dev server...", fmt);
26417
+ Bun.spawn(["bun", "run", "dev"], {
26418
+ cwd: dashboardPath,
26419
+ stdout: "inherit",
26420
+ stderr: "inherit"
26421
+ });
26422
+ formatSuccess("Dashboard starting on http://localhost:3000", fmt);
26423
+ }, { service: "dev" }));
26381
26424
  }
26382
26425
  // src/commands/deploy/deploy-command.ts
26383
26426
  init_config2();
@@ -26997,7 +27040,7 @@ ${theme.heading("Deploying Dashboard")}
26997
27040
  }
26998
27041
  return {
26999
27042
  worker: "dashboard",
27000
- url: urlMatch?.[0] || "https://dashboard.cryptolinx.workers.dev",
27043
+ url: urlMatch?.[0],
27001
27044
  success: true,
27002
27045
  size: sizeMatch ? `${sizeMatch[1]} ${sizeMatch[2]}` : undefined,
27003
27046
  startupTime: startupMatch ? `${startupMatch[1]} ms` : undefined
@@ -27034,7 +27077,7 @@ ${theme.heading("Deploying Dashboard")}
27034
27077
  }
27035
27078
  return {
27036
27079
  worker: "dashboard",
27037
- url: urlMatch?.[0] || "https://dashboard.cryptolinx.workers.dev",
27080
+ url: urlMatch?.[0],
27038
27081
  success: true
27039
27082
  };
27040
27083
  }
@@ -27116,12 +27159,9 @@ async function doUpdateInternalUrls(fmt) {
27116
27159
  const global = config2.getGlobal();
27117
27160
  const prefix = global.subdomain_prefix ?? "hoox";
27118
27161
  const workers = config2.listEnabledWorkers();
27119
- let filePath = resolve(process.cwd(), "pages", "dashboard", "wrangler.jsonc");
27120
- if (!existsSync2(filePath)) {
27121
- filePath = resolve(process.cwd(), "workers", "dashboard", "wrangler.jsonc");
27122
- }
27162
+ const filePath = resolve(process.cwd(), "workers", "dashboard", "wrangler.jsonc");
27123
27163
  if (!existsSync2(filePath)) {
27124
- formatError2(new CLIError("Dashboard wrangler.jsonc not found (checked pages/dashboard and workers/dashboard)", 1 /* ERROR */), fmt);
27164
+ formatError2(new CLIError("Dashboard wrangler.jsonc not found at workers/dashboard/wrangler.jsonc", 1 /* ERROR */), fmt);
27125
27165
  process.exitCode = 1 /* ERROR */;
27126
27166
  return;
27127
27167
  }
@@ -27204,6 +27244,100 @@ ${setCount} key(s) set, ${errorCount} error(s)
27204
27244
  process.exitCode = 1 /* ERROR */;
27205
27245
  }
27206
27246
  }
27247
+ async function doVersionHistory(worker, fmt) {
27248
+ const s = vt();
27249
+ s.start(`Fetching deployment history for ${worker}...`);
27250
+ const cf = new CloudflareService;
27251
+ const result = await cf.versionsList(worker);
27252
+ if (!result.ok) {
27253
+ s.stop(`${theme.error(icons.error)} Failed to fetch history`);
27254
+ formatError2(new CLIError(result.error, 1 /* ERROR */), fmt);
27255
+ process.exitCode = 1 /* ERROR */;
27256
+ return;
27257
+ }
27258
+ const versions2 = result.value;
27259
+ s.stop(`${theme.success(icons.success)} ${versions2.length} version(s) found`);
27260
+ if (versions2.length === 0) {
27261
+ if (!fmt.quiet) {
27262
+ process.stdout.write(`${theme.dim(`No deployment history found.
27263
+ `)}`);
27264
+ }
27265
+ return;
27266
+ }
27267
+ const rows = versions2.map((v2) => ({
27268
+ "Version ID": v2.id,
27269
+ Number: v2.number !== undefined ? String(v2.number) : "-",
27270
+ Created: v2.created_on ? new Date(v2.created_on).toLocaleString() : "-",
27271
+ Author: v2.author ?? "-",
27272
+ Source: v2.source ?? "-"
27273
+ }));
27274
+ formatTable(rows, fmt);
27275
+ }
27276
+ async function doVersionRollback(worker, version2, fmt, yes) {
27277
+ const cf = new CloudflareService;
27278
+ let targetVersion = version2;
27279
+ if (!targetVersion) {
27280
+ const s2 = vt();
27281
+ s2.start(`Fetching recent versions for ${worker}...`);
27282
+ const result = await cf.versionsList(worker);
27283
+ if (!result.ok) {
27284
+ s2.stop(`${theme.error(icons.error)} Failed to fetch versions`);
27285
+ formatError2(new CLIError(result.error, 1 /* ERROR */), fmt);
27286
+ process.exitCode = 1 /* ERROR */;
27287
+ return;
27288
+ }
27289
+ const versions2 = result.value;
27290
+ if (versions2.length === 0) {
27291
+ s2.stop(`${theme.dim("No versions found")}`);
27292
+ process.stdout.write(`${theme.dim(`No deployment history found for this worker.
27293
+ `)}`);
27294
+ return;
27295
+ }
27296
+ s2.stop(`${theme.success(icons.success)} Versions fetched`);
27297
+ const recent = versions2.slice(0, 5);
27298
+ const selected = await Ee({
27299
+ message: `Select a version to roll ${worker} back to:`,
27300
+ options: recent.map((v2) => ({
27301
+ value: v2.id,
27302
+ label: `v${v2.number ?? "?"} ${v2.id.slice(0, 8)}... ${v2.created_on ? new Date(v2.created_on).toLocaleDateString() : "?"}`,
27303
+ hint: v2.source ? `via ${v2.source}` : undefined
27304
+ }))
27305
+ });
27306
+ if (R(selected)) {
27307
+ process.stdout.write(`${theme.dim(`Rollback cancelled.
27308
+ `)}`);
27309
+ return;
27310
+ }
27311
+ targetVersion = selected;
27312
+ }
27313
+ if (!yes) {
27314
+ const confirmed = await le({
27315
+ message: `Roll back ${worker} to version ${targetVersion}? This will replace the current deployment.`
27316
+ });
27317
+ if (R(confirmed) || !confirmed) {
27318
+ process.stdout.write(`${theme.dim(`Rollback cancelled.
27319
+ `)}`);
27320
+ return;
27321
+ }
27322
+ }
27323
+ const s = vt();
27324
+ s.start(`Rolling back ${worker} to version ${targetVersion}...`);
27325
+ const rollResult = await cf.versionsRollback(worker, targetVersion);
27326
+ if (!rollResult.ok) {
27327
+ s.stop(`${theme.error(icons.error)} Rollback failed`);
27328
+ formatError2(new CLIError(rollResult.error, 1 /* ERROR */), fmt);
27329
+ process.exitCode = 1 /* ERROR */;
27330
+ return;
27331
+ }
27332
+ s.stop(`${theme.success(icons.success)} ${worker} rolled back to ${targetVersion.slice(0, 8)}...`);
27333
+ if (rollResult.value && !fmt.quiet) {
27334
+ const snippet = rollResult.value.split(`
27335
+ `).find((l) => l.trim())?.trim().slice(0, 120);
27336
+ if (snippet) {
27337
+ R2.step(` ${theme.dim("Output:")} ${snippet}`);
27338
+ }
27339
+ }
27340
+ }
27207
27341
  function registerDeployCommand(program2) {
27208
27342
  const deployCmd = program2.command("deploy").summary("Deploy workers and/or dashboard to Cloudflare Workers").description(`Deploy your Hoox trading system to Cloudflare's edge network.
27209
27343
 
@@ -27234,19 +27368,43 @@ EXAMPLES:
27234
27368
  hoox deploy all
27235
27369
  hoox deploy all --env production
27236
27370
  hoox deploy all --rebuild
27237
- hoox deploy all --auto`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").option("--rebuild", "Force rebuild of dashboard before deploying").option("--auto", "Skip dashboard rebuild prompt, use existing build if available").action(async (options) => {
27238
- const fmt = getFormatOptions(program2);
27239
- try {
27240
- const configService = new ConfigService;
27241
- await configService.load();
27242
- const cf = new CloudflareService;
27243
- await deployAll(configService, cf, options.env, options.rebuild ?? false, options.auto ?? false);
27244
- } catch (err) {
27245
- const message = err instanceof Error ? err.message : String(err);
27246
- formatError2(message, fmt);
27247
- process.exitCode = 1 /* ERROR */;
27371
+ hoox deploy all --auto`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").option("--rebuild", "Force rebuild of dashboard before deploying").option("--auto", "Skip dashboard rebuild prompt, use existing build if available").option("--dry-run", "Preview deployment plan without executing").action(withErrorHandling(async (options) => {
27372
+ const configService = new ConfigService;
27373
+ await configService.load();
27374
+ if (options.dryRun) {
27375
+ const enabled = configService.listEnabledWorkers();
27376
+ const workers = DEPLOY_ORDER.filter((w3) => w3 !== "dashboard" && enabled.includes(w3));
27377
+ const unknown2 = enabled.filter((w3) => w3 !== "dashboard" && !DEPLOY_ORDER.includes(w3));
27378
+ const ordered = [...workers, ...unknown2];
27379
+ process.stdout.write(`
27380
+ ${theme.heading("Deployment Plan (dry-run)")}
27381
+ `);
27382
+ process.stdout.write(`${theme.dim("\u2500".repeat(50))}
27383
+ `);
27384
+ process.stdout.write(`Workers to deploy (${ordered.length + unknown2.length}):
27385
+ `);
27386
+ for (const w3 of [...ordered, ...unknown2]) {
27387
+ process.stdout.write(` ${theme.dim("\u25CB")} ${w3}
27388
+ `);
27389
+ }
27390
+ const buildInfo = getDashboardBuildInfo("workers/dashboard");
27391
+ if (buildInfo.exists) {
27392
+ process.stdout.write(`
27393
+ Dashboard: ${buildInfo.age ? `build exists (${buildInfo.age})` : "build exists"}
27394
+ `);
27395
+ } else {
27396
+ process.stdout.write(`
27397
+ Dashboard: no build found (will be built)
27398
+ `);
27399
+ }
27400
+ process.stdout.write(`
27401
+ ${theme.dim("Run without --dry-run to deploy.")}
27402
+ `);
27403
+ return;
27248
27404
  }
27249
- });
27405
+ const cf = new CloudflareService;
27406
+ await deployAll(configService, cf, options.env, options.rebuild ?? false, options.auto ?? false);
27407
+ }, { service: "deploy" }));
27250
27408
  deployCmd.command("workers").summary("Deploy all enabled workers to Cloudflare").description(`Deploy all enabled workers to Cloudflare Workers.
27251
27409
 
27252
27410
  Workers are deployed in the correct order based on their dependencies. This command skips the dashboard deployment.
@@ -27256,81 +27414,74 @@ OPTIONS:
27256
27414
 
27257
27415
  EXAMPLES:
27258
27416
  hoox deploy workers
27259
- hoox deploy workers --env staging`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(async (options) => {
27260
- try {
27261
- const configService = new ConfigService;
27262
- await configService.load();
27263
- const cf = new CloudflareService;
27264
- const enabled = configService.listEnabledWorkers();
27265
- const workers = DEPLOY_ORDER.filter((w3) => w3 !== "dashboard" && enabled.includes(w3));
27266
- const unknown2 = enabled.filter((w3) => w3 !== "dashboard" && !DEPLOY_ORDER.includes(w3));
27267
- const ordered = [...workers, ...unknown2];
27268
- const results = [];
27269
- if (ordered.length === 0) {
27270
- process.stdout.write(`${theme.dim(`No enabled workers to deploy
27417
+ hoox deploy workers --env staging`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(withErrorHandling(async (options) => {
27418
+ const configService = new ConfigService;
27419
+ await configService.load();
27420
+ const cf = new CloudflareService;
27421
+ const enabled = configService.listEnabledWorkers();
27422
+ const workers = DEPLOY_ORDER.filter((w3) => w3 !== "dashboard" && enabled.includes(w3));
27423
+ const unknown2 = enabled.filter((w3) => w3 !== "dashboard" && !DEPLOY_ORDER.includes(w3));
27424
+ const ordered = [...workers, ...unknown2];
27425
+ const results = [];
27426
+ if (ordered.length === 0) {
27427
+ process.stdout.write(`${theme.dim(`No enabled workers to deploy
27271
27428
  `)}`);
27272
- return;
27273
- }
27274
- process.stdout.write(`
27429
+ return;
27430
+ }
27431
+ process.stdout.write(`
27275
27432
  ${theme.heading("Deploying Workers")}
27276
27433
  `);
27277
- process.stdout.write(`${theme.dim("\u2500".repeat(60))}
27434
+ process.stdout.write(`${theme.dim("\u2500".repeat(60))}
27278
27435
  `);
27279
- for (const name of ordered) {
27280
- process.stdout.write(`${theme.dim("\u25CB")} ${name.padEnd(25)} pending
27436
+ for (const name of ordered) {
27437
+ process.stdout.write(`${theme.dim("\u25CB")} ${name.padEnd(25)} pending
27281
27438
  `);
27282
- }
27283
- process.stdout.write(`${theme.dim("\u2500".repeat(60))}
27439
+ }
27440
+ process.stdout.write(`${theme.dim("\u2500".repeat(60))}
27284
27441
 
27285
27442
  `);
27286
- for (const name of ordered) {
27287
- const s = vt();
27288
- s.start(`Deploying ${name}...`);
27289
- const result = await deploySingle(configService, cf, name, options.env);
27290
- results.push(result);
27291
- if (result.success) {
27292
- s.stop(`${theme.success(icons.success)} ${name} deployed`);
27293
- if (result.url)
27294
- process.stdout.write(` ${theme.dim("URL:")} ${result.url}
27295
- `);
27296
- if (result.size)
27297
- process.stdout.write(` ${theme.dim("Size:")} ${result.size}
27443
+ for (const name of ordered) {
27444
+ const s = vt();
27445
+ s.start(`Deploying ${name}...`);
27446
+ const result = await deploySingle(configService, cf, name, options.env);
27447
+ results.push(result);
27448
+ if (result.success) {
27449
+ s.stop(`${theme.success(icons.success)} ${name} deployed`);
27450
+ if (result.url)
27451
+ process.stdout.write(` ${theme.dim("URL:")} ${result.url}
27298
27452
  `);
27299
- if (result.startupTime)
27300
- process.stdout.write(` ${theme.dim("Startup:")} ${result.startupTime}
27453
+ if (result.size)
27454
+ process.stdout.write(` ${theme.dim("Size:")} ${result.size}
27301
27455
  `);
27302
- if (result.versionId)
27303
- process.stdout.write(` ${theme.dim("Version:")} ${result.versionId.slice(0, 8)}...
27456
+ if (result.startupTime)
27457
+ process.stdout.write(` ${theme.dim("Startup:")} ${result.startupTime}
27304
27458
  `);
27305
- if (!result.url && !result.size && !result.startupTime && !result.versionId && result.rawOutput) {
27306
- process.stdout.write(` ${theme.dim("Output:")} ${result.rawOutput.slice(0, 80)}
27459
+ if (result.versionId)
27460
+ process.stdout.write(` ${theme.dim("Version:")} ${result.versionId.slice(0, 8)}...
27307
27461
  `);
27308
- }
27309
- } else {
27310
- s.stop(`${theme.error(icons.error)} ${name} failed`);
27311
- if (result.error)
27312
- process.stdout.write(` ${theme.error("Error:")} ${result.error}
27462
+ if (!result.url && !result.size && !result.startupTime && !result.versionId && result.rawOutput) {
27463
+ process.stdout.write(` ${theme.dim("Output:")} ${result.rawOutput.slice(0, 80)}
27313
27464
  `);
27314
27465
  }
27466
+ } else {
27467
+ s.stop(`${theme.error(icons.error)} ${name} failed`);
27468
+ if (result.error)
27469
+ process.stdout.write(` ${theme.error("Error:")} ${result.error}
27470
+ `);
27315
27471
  }
27316
- const succeeded = results.filter((r) => r.success).length;
27317
- const failed = results.filter((r) => !r.success).length;
27318
- process.stdout.write(`
27472
+ }
27473
+ const succeeded = results.filter((r) => r.success).length;
27474
+ const failed = results.filter((r) => !r.success).length;
27475
+ process.stdout.write(`
27319
27476
  ${theme.heading("Summary:")} ${succeeded}/${ordered.length} deployed`);
27320
- if (failed > 0)
27321
- process.stdout.write(` ${theme.error(`(${failed} failed)`)}
27477
+ if (failed > 0)
27478
+ process.stdout.write(` ${theme.error(`(${failed} failed)`)}
27322
27479
  `);
27323
- else
27324
- process.stdout.write(` ${theme.success(" \u2713")}
27480
+ else
27481
+ process.stdout.write(` ${theme.success(" \u2713")}
27325
27482
 
27326
27483
  `);
27327
- } catch (err) {
27328
- const message = err instanceof Error ? err.message : String(err);
27329
- process.stdout.write(`${theme.error(`Error: ${message}`)}
27330
- `);
27331
- process.exitCode = 1 /* ERROR */;
27332
- }
27333
- });
27484
+ }, { service: "deploy" }));
27334
27485
  deployCmd.command("worker <name>").summary("Deploy a single worker by name").description(`Deploy a specific worker to Cloudflare Workers.
27335
27486
 
27336
27487
  ARGUMENTS:
@@ -27341,26 +27492,20 @@ OPTIONS:
27341
27492
 
27342
27493
  EXAMPLES:
27343
27494
  hoox deploy worker trade-worker
27344
- hoox deploy worker agent-worker --env production`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(async (name, options) => {
27495
+ hoox deploy worker agent-worker --env production`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(withErrorHandling(async (name, options) => {
27345
27496
  const fmt = getFormatOptions(program2);
27346
- try {
27347
- const configService = new ConfigService;
27348
- await configService.load();
27349
- const cf = new CloudflareService;
27350
- const result = await deploySingle(configService, cf, name, options.env);
27351
- if (result.success) {
27352
- const url2 = result.url ? ` \u2014 ${result.url}` : "";
27353
- formatSuccess(`Deployed ${name}${url2}`, fmt);
27354
- } else {
27355
- formatError2(new CLIError(`Failed to deploy "${name}": ${result.error}`, 1 /* ERROR */), fmt);
27356
- process.exitCode = 1 /* ERROR */;
27357
- }
27358
- } catch (err) {
27359
- const message = err instanceof Error ? err.message : String(err);
27360
- formatError2(message, fmt);
27497
+ const configService = new ConfigService;
27498
+ await configService.load();
27499
+ const cf = new CloudflareService;
27500
+ const result = await deploySingle(configService, cf, name, options.env);
27501
+ if (result.success) {
27502
+ const url2 = result.url ? ` \u2014 ${result.url}` : "";
27503
+ formatSuccess(`Deployed ${name}${url2}`, fmt);
27504
+ } else {
27505
+ formatError2(new CLIError(`Failed to deploy "${name}": ${result.error}`, 1 /* ERROR */), fmt);
27361
27506
  process.exitCode = 1 /* ERROR */;
27362
27507
  }
27363
- });
27508
+ }, { service: "deploy" }));
27364
27509
  deployCmd.command("dashboard").summary("Build and deploy the Next.js dashboard").description(`Build and deploy the Hoox dashboard to Cloudflare Workers.
27365
27510
 
27366
27511
  The dashboard is built using OpenNext which converts Next.js to Cloudflare Workers format. Before deploying, the CLI checks for an existing build and prompts you to rebuild or use the existing build.
@@ -27370,24 +27515,18 @@ OPTIONS:
27370
27515
 
27371
27516
  EXAMPLES:
27372
27517
  hoox deploy dashboard Interactive: choose to rebuild or use existing
27373
- hoox deploy dashboard --rebuild Force rebuild before deploying`).option("--rebuild", "Force rebuild of dashboard before deploying").action(async (options) => {
27518
+ hoox deploy dashboard --rebuild Force rebuild before deploying`).option("--rebuild", "Force rebuild of dashboard before deploying").action(withErrorHandling(async (options) => {
27374
27519
  const fmt = getFormatOptions(program2);
27375
- try {
27376
- const cf = new CloudflareService;
27377
- const result = await deployDashboard(cf, options.rebuild);
27378
- if (result.success) {
27379
- const url2 = result.url ? ` \u2014 ${result.url}` : "";
27380
- formatSuccess(`Dashboard deployed${url2}`, fmt);
27381
- } else {
27382
- formatError2(new CLIError(`Dashboard deployment failed: ${result.error}`, 1 /* ERROR */), fmt);
27383
- process.exitCode = 1 /* ERROR */;
27384
- }
27385
- } catch (err) {
27386
- const message = err instanceof Error ? err.message : String(err);
27387
- formatError2(message, fmt);
27520
+ const cf = new CloudflareService;
27521
+ const result = await deployDashboard(cf, options.rebuild);
27522
+ if (result.success) {
27523
+ const url2 = result.url ? ` \u2014 ${result.url}` : "";
27524
+ formatSuccess(`Dashboard deployed${url2}`, fmt);
27525
+ } else {
27526
+ formatError2(new CLIError(`Dashboard deployment failed: ${result.error}`, 1 /* ERROR */), fmt);
27388
27527
  process.exitCode = 1 /* ERROR */;
27389
27528
  }
27390
- });
27529
+ }, { service: "deploy" }));
27391
27530
  deployCmd.command("telegram-webhook").summary("Set Telegram bot webhook (post-deploy step)").description(`Configure the Telegram bot webhook after deploying telegram-worker.
27392
27531
 
27393
27532
  Calls the Telegram Bot API to set the webhook URL.
@@ -27403,30 +27542,62 @@ Use --token and --secret-token to override.
27403
27542
  EXAMPLES:
27404
27543
  hoox deploy telegram-webhook
27405
27544
  hoox deploy telegram-webhook --token 123456:ABC-DEF1234
27406
- hoox deploy telegram-webhook --subdomain myapp`).option("--token <token>", "Telegram bot token (from @BotFather)").option("--secret-token <secret>", "Telegram webhook secret token").option("--subdomain <prefix>", "Worker subdomain prefix (default: from config)").action(async (options) => {
27545
+ hoox deploy telegram-webhook --subdomain myapp`).option("--token <token>", "Telegram bot token (from @BotFather)").option("--secret-token <secret>", "Telegram webhook secret token").option("--subdomain <prefix>", "Worker subdomain prefix (default: from config)").action(withErrorHandling(async (options) => {
27407
27546
  const fmt = getFormatOptions(program2);
27408
27547
  await doTelegramWebhook(fmt, options.token, options.secretToken, options.subdomain);
27409
- });
27548
+ }, { service: "deploy" }));
27410
27549
  deployCmd.command("update-internal-urls").summary("Update dashboard wrangler.jsonc with current service URLs").description(`Update the dashboard's wrangler.jsonc with the current service URLs.
27411
27550
 
27412
27551
  This is a post-deployment step that ensures the dashboard has correct
27413
27552
  service binding URLs for all workers.
27414
27553
 
27415
27554
  EXAMPLES:
27416
- hoox deploy update-internal-urls`).action(async () => {
27555
+ hoox deploy update-internal-urls`).action(withErrorHandling(async () => {
27417
27556
  const fmt = getFormatOptions(program2);
27418
27557
  await doUpdateInternalUrls(fmt);
27419
- });
27558
+ }, { service: "deploy" }));
27420
27559
  deployCmd.command("kv-config").summary("Apply KV manifest keys post-deployment").description(`Apply the KV manifest key-value pairs after deploying workers.
27421
27560
 
27422
27561
  Sets all KV keys from the manifest to their default values.
27423
27562
  This post-deployment step initializes the CONFIG_KV namespace.
27424
27563
 
27425
27564
  EXAMPLES:
27426
- hoox deploy kv-config`).action(async () => {
27565
+ hoox deploy kv-config`).action(withErrorHandling(async () => {
27427
27566
  const fmt = getFormatOptions(program2);
27428
27567
  await doKvConfig(fmt);
27429
- });
27568
+ }, { service: "deploy" }));
27569
+ deployCmd.command("history <worker>").summary("Show deployment version history for a worker").description(`Display the deployment version history for a Cloudflare Worker.
27570
+
27571
+ Shows version ID, version number, creation date, author, and source for each deployment.
27572
+
27573
+ ARGUMENTS:
27574
+ name Worker name (e.g., trade-worker, agent-worker)
27575
+
27576
+ EXAMPLES:
27577
+ hoox deploy history trade-worker
27578
+ hoox deploy history hoox --json`).action(withErrorHandling(async (worker) => {
27579
+ const fmt = getFormatOptions(program2);
27580
+ await doVersionHistory(worker, fmt);
27581
+ }, { service: "deploy" }));
27582
+ deployCmd.command("rollback <worker>").argument("[version]", "Version ID to rollback to (if omitted, prompts to select)").summary("Rollback a worker to a previous version").description(`Rollback a Cloudflare Worker to a previous version.
27583
+
27584
+ If no version is specified, the CLI fetches the 5 most recent versions
27585
+ and prompts you to select one.
27586
+
27587
+ ARGUMENTS:
27588
+ name Worker name (e.g., trade-worker, agent-worker)
27589
+ version Version ID to rollback to (optional)
27590
+
27591
+ OPTIONS:
27592
+ --yes Skip confirmation prompt
27593
+
27594
+ EXAMPLES:
27595
+ hoox deploy rollback trade-worker
27596
+ hoox deploy rollback trade-worker <version-id>
27597
+ hoox deploy rollback trade-worker <version-id> --yes`).option("--yes", "Skip confirmation prompt").action(withErrorHandling(async (worker, version2, options) => {
27598
+ const fmt = getFormatOptions(program2);
27599
+ await doVersionRollback(worker, version2, fmt, options.yes ?? false);
27600
+ }, { service: "deploy" }));
27430
27601
  }
27431
27602
  // src/commands/infra/infra-command.ts
27432
27603
  init_main();
@@ -27558,6 +27729,95 @@ async function doQueueDelete(name, opts, cf) {
27558
27729
  const cloudflare = cf ?? new CloudflareService;
27559
27730
  await handleDelete(name, "Queue", (n) => cloudflare.queueDelete(n), opts);
27560
27731
  }
27732
+ async function doProvisionDryRun(opts) {
27733
+ const configService = new ConfigService;
27734
+ try {
27735
+ await configService.load();
27736
+ } catch (err) {
27737
+ const message = err instanceof Error ? err.message : String(err);
27738
+ formatError2(new CLIError(message, 1 /* ERROR */), opts);
27739
+ return;
27740
+ }
27741
+ const enabledWorkers = configService.listEnabledWorkers();
27742
+ if (enabledWorkers.length === 0) {
27743
+ if (!opts.quiet) {
27744
+ process.stdout.write(theme.dim(`No enabled workers found in wrangler.jsonc.
27745
+ `));
27746
+ }
27747
+ return;
27748
+ }
27749
+ const plan = [];
27750
+ for (const workerName of enabledWorkers) {
27751
+ const workerConfig = configService.getWorker(workerName);
27752
+ if (!workerConfig?.path)
27753
+ continue;
27754
+ const wranglerPath = resolve2(workerConfig.path, "wrangler.jsonc");
27755
+ const file2 = Bun.file(wranglerPath);
27756
+ if (!await file2.exists())
27757
+ continue;
27758
+ let wranglerConfig;
27759
+ try {
27760
+ const content = await file2.text();
27761
+ wranglerConfig = parse6(content) ?? {};
27762
+ } catch {
27763
+ continue;
27764
+ }
27765
+ const resources = [];
27766
+ const d1Databases = Array.isArray(wranglerConfig.d1_databases) ? wranglerConfig.d1_databases : [];
27767
+ for (const db of d1Databases) {
27768
+ const dbName = db.database_name ?? db.binding;
27769
+ if (dbName)
27770
+ resources.push(`D1: ${dbName}`);
27771
+ }
27772
+ const kvNamespaces = Array.isArray(wranglerConfig.kv_namespaces) ? wranglerConfig.kv_namespaces : [];
27773
+ for (const kv of kvNamespaces) {
27774
+ const kvBinding = kv.binding ?? `kv-${workerName}`;
27775
+ resources.push(`KV: ${kvBinding}`);
27776
+ }
27777
+ const r2Buckets = Array.isArray(wranglerConfig.r2_buckets) ? wranglerConfig.r2_buckets : [];
27778
+ for (const bucket of r2Buckets) {
27779
+ const bucketName = bucket.bucket_name ?? bucket.binding;
27780
+ if (bucketName)
27781
+ resources.push(`R2: ${bucketName}`);
27782
+ }
27783
+ let queueNames = [];
27784
+ if (wranglerConfig.queues && typeof wranglerConfig.queues === "object") {
27785
+ const queues = wranglerConfig.queues;
27786
+ const producers = Array.isArray(queues.producers) ? queues.producers : [];
27787
+ const consumers = Array.isArray(queues.consumers) ? queues.consumers : [];
27788
+ queueNames = [
27789
+ ...producers.map((q2) => q2.queue),
27790
+ ...consumers.map((q2) => q2.queue)
27791
+ ].filter(Boolean);
27792
+ }
27793
+ for (const qName of [...new Set(queueNames)]) {
27794
+ resources.push(`Queue: ${qName}`);
27795
+ }
27796
+ if (resources.length > 0) {
27797
+ plan.push({ worker: workerName, resources });
27798
+ }
27799
+ }
27800
+ if (plan.length === 0) {
27801
+ process.stdout.write(theme.dim(`No provisionable resources found in any worker config.
27802
+ `));
27803
+ return;
27804
+ }
27805
+ process.stdout.write(theme.heading(`Resources to provision:
27806
+
27807
+ `));
27808
+ for (const entry of plan) {
27809
+ process.stdout.write(`${theme.bold(entry.worker)}
27810
+ `);
27811
+ for (const r of entry.resources) {
27812
+ process.stdout.write(`${theme.dim(" \u2500")} ${r}
27813
+ `);
27814
+ }
27815
+ process.stdout.write(`
27816
+ `);
27817
+ }
27818
+ process.stdout.write(`${theme.dim(`Run without --dry-run to provision these resources.
27819
+ `)}`);
27820
+ }
27561
27821
  async function doProvision(opts, cf, config2) {
27562
27822
  const cloudflare = cf ?? new CloudflareService;
27563
27823
  const configService = config2 ?? new ConfigService;
@@ -27787,10 +28047,19 @@ This reads each worker's wrangler.jsonc and creates:
27787
28047
  - R2 buckets (from r2_buckets)
27788
28048
  - Queues (from queues)
27789
28049
 
28050
+ OPTIONS:
28051
+ --dry-run Preview what resources would be created without creating them
28052
+
27790
28053
  EXAMPLES:
27791
- hoox infra provision`).action(async function() {
27792
- const opts = getOptions(this);
27793
- await doProvision(opts);
28054
+ hoox infra provision
28055
+ hoox infra provision --dry-run`).option("--dry-run", "Preview what resources would be created without creating them").action(async (_, cmd) => {
28056
+ const opts = getOptions(cmd);
28057
+ const globalOpts = cmd.optsWithGlobals();
28058
+ if (globalOpts.dryRun) {
28059
+ await doProvisionDryRun(opts);
28060
+ } else {
28061
+ await doProvision(opts);
28062
+ }
27794
28063
  });
27795
28064
  const d1Cmd = infraCmd.command("d1").summary("Manage D1 SQL databases").description(`D1 is Cloudflare's serverless SQL database.
27796
28065
 
@@ -27801,8 +28070,8 @@ EXAMPLES:
27801
28070
  d1Cmd.command("list").summary("List all D1 databases").description(`List all D1 databases in your Cloudflare account.
27802
28071
 
27803
28072
  EXAMPLES:
27804
- hoox infra d1 list`).action(async function() {
27805
- const opts = getOptions(this);
28073
+ hoox infra d1 list`).action(async (_, cmd) => {
28074
+ const opts = getOptions(cmd);
27806
28075
  await doD1List(opts);
27807
28076
  });
27808
28077
  d1Cmd.command("create <name>").summary("Create a new D1 database").description(`Create a new D1 database.
@@ -27811,8 +28080,8 @@ ARGUMENTS:
27811
28080
  name Database name
27812
28081
 
27813
28082
  EXAMPLES:
27814
- hoox infra d1 create trade-data-db`).action(async function(name) {
27815
- const opts = getOptions(this);
28083
+ hoox infra d1 create trade-data-db`).action(async (name, _, cmd) => {
28084
+ const opts = getOptions(cmd);
27816
28085
  await doD1Create(name, opts);
27817
28086
  });
27818
28087
  d1Cmd.command("delete <name>").summary("Delete a D1 database").description(`Delete a D1 database (WARNING: destructive operation).
@@ -27821,8 +28090,8 @@ ARGUMENTS:
27821
28090
  name Database name
27822
28091
 
27823
28092
  EXAMPLES:
27824
- hoox infra d1 delete trade-data-db`).action(async function(name) {
27825
- const opts = getOptions(this);
28093
+ hoox infra d1 delete trade-data-db`).action(async (name, _, cmd) => {
28094
+ const opts = getOptions(cmd);
27826
28095
  await doD1Delete(name, opts);
27827
28096
  });
27828
28097
  const kvCmd = infraCmd.command("kv").summary("Manage KV namespaces").description(`KV (Key-Value) namespaces for configuration and caching.
@@ -27834,8 +28103,8 @@ EXAMPLES:
27834
28103
  kvCmd.command("list").summary("List all KV namespaces").description(`List all KV namespaces in your Cloudflare account.
27835
28104
 
27836
28105
  EXAMPLES:
27837
- hoox infra kv list`).action(async function() {
27838
- const opts = getOptions(this);
28106
+ hoox infra kv list`).action(async (_, cmd) => {
28107
+ const opts = getOptions(cmd);
27839
28108
  await doKvList(opts);
27840
28109
  });
27841
28110
  kvCmd.command("create <name>").summary("Create a new KV namespace").description(`Create a new KV namespace.
@@ -27844,8 +28113,8 @@ ARGUMENTS:
27844
28113
  name KV namespace title
27845
28114
 
27846
28115
  EXAMPLES:
27847
- hoox infra kv create CONFIG_KV`).action(async function(name) {
27848
- const opts = getOptions(this);
28116
+ hoox infra kv create CONFIG_KV`).action(async (name, _, cmd) => {
28117
+ const opts = getOptions(cmd);
27849
28118
  await doKvCreate(name, opts);
27850
28119
  });
27851
28120
  kvCmd.command("delete <id>").summary("Delete a KV namespace by ID").description(`Delete a KV namespace by its ID (WARNING: destructive operation).
@@ -27854,8 +28123,8 @@ ARGUMENTS:
27854
28123
  id KV namespace ID (get from 'kv list')
27855
28124
 
27856
28125
  EXAMPLES:
27857
- hoox infra kv delete c5917667a21745e390ff969f32b1847d`).action(async function(id) {
27858
- const opts = getOptions(this);
28126
+ hoox infra kv delete c5917667a21745e390ff969f32b1847d`).action(async (id, _, cmd) => {
28127
+ const opts = getOptions(cmd);
27859
28128
  await doKvDelete(id, opts);
27860
28129
  });
27861
28130
  const r2Cmd = infraCmd.command("r2").summary("Manage R2 object storage buckets").description(`R2 is Cloudflare's S3-compatible object storage.
@@ -27867,8 +28136,8 @@ EXAMPLES:
27867
28136
  r2Cmd.command("list").summary("List all R2 buckets").description(`List all R2 buckets in your Cloudflare account.
27868
28137
 
27869
28138
  EXAMPLES:
27870
- hoox infra r2 list`).action(async function() {
27871
- const opts = getOptions(this);
28139
+ hoox infra r2 list`).action(async (_, cmd) => {
28140
+ const opts = getOptions(cmd);
27872
28141
  await doR2List(opts);
27873
28142
  });
27874
28143
  r2Cmd.command("create <name>").summary("Create a new R2 bucket").description(`Create a new R2 bucket.
@@ -27877,8 +28146,8 @@ ARGUMENTS:
27877
28146
  name Bucket name
27878
28147
 
27879
28148
  EXAMPLES:
27880
- hoox infra r2 create trade-reports`).action(async function(name) {
27881
- const opts = getOptions(this);
28149
+ hoox infra r2 create trade-reports`).action(async (name, _, cmd) => {
28150
+ const opts = getOptions(cmd);
27882
28151
  await doR2Create(name, opts);
27883
28152
  });
27884
28153
  r2Cmd.command("delete <name>").summary("Delete an R2 bucket").description(`Delete an R2 bucket (WARNING: destructive operation).
@@ -27887,8 +28156,8 @@ ARGUMENTS:
27887
28156
  name Bucket name
27888
28157
 
27889
28158
  EXAMPLES:
27890
- hoox infra r2 delete trade-reports`).action(async function(name) {
27891
- const opts = getOptions(this);
28159
+ hoox infra r2 delete trade-reports`).action(async (name, _, cmd) => {
28160
+ const opts = getOptions(cmd);
27892
28161
  await doR2Delete(name, opts);
27893
28162
  });
27894
28163
  const queuesCmd = infraCmd.command("queues").summary("Manage Cloudflare Queues").description(`Queues for asynchronous message processing between workers.
@@ -27900,8 +28169,8 @@ EXAMPLES:
27900
28169
  queuesCmd.command("list").summary("List all Queues").description(`List all queues in your Cloudflare account.
27901
28170
 
27902
28171
  EXAMPLES:
27903
- hoox infra queues list`).action(async function() {
27904
- const opts = getOptions(this);
28172
+ hoox infra queues list`).action(async (_, cmd) => {
28173
+ const opts = getOptions(cmd);
27905
28174
  await doQueueList(opts);
27906
28175
  });
27907
28176
  queuesCmd.command("create <name>").summary("Create a new Queue").description(`Create a new queue.
@@ -27910,8 +28179,8 @@ ARGUMENTS:
27910
28179
  name Queue name
27911
28180
 
27912
28181
  EXAMPLES:
27913
- hoox infra queues create trade-execution`).action(async function(name) {
27914
- const opts = getOptions(this);
28182
+ hoox infra queues create trade-execution`).action(async (name, _, cmd) => {
28183
+ const opts = getOptions(cmd);
27915
28184
  await doQueueCreate(name, opts);
27916
28185
  });
27917
28186
  queuesCmd.command("delete <name>").summary("Delete a Queue").description(`Delete a queue (WARNING: destructive operation).
@@ -27920,8 +28189,8 @@ ARGUMENTS:
27920
28189
  name Queue name
27921
28190
 
27922
28191
  EXAMPLES:
27923
- hoox infra queues delete trade-execution`).action(async function(name) {
27924
- const opts = getOptions(this);
28192
+ hoox infra queues delete trade-execution`).action(async (name, _, cmd) => {
28193
+ const opts = getOptions(cmd);
27925
28194
  await doQueueDelete(name, opts);
27926
28195
  });
27927
28196
  const vectorizeCmd = infraCmd.command("vectorize").summary("Manage Vectorize indexes").description(`Vectorize is Cloudflare's vector database for AI-powered search.
@@ -27930,16 +28199,16 @@ EXAMPLES:
27930
28199
  hoox infra vectorize list
27931
28200
  hoox infra vectorize create my-rag-index
27932
28201
  hoox infra vectorize delete my-rag-index`);
27933
- vectorizeCmd.command("list").summary("List all Vectorize indexes").description("List all Vectorize indexes in your Cloudflare account.").action(async function() {
27934
- const opts = getOptions(this);
28202
+ vectorizeCmd.command("list").summary("List all Vectorize indexes").description("List all Vectorize indexes in your Cloudflare account.").action(async (_, cmd) => {
28203
+ const opts = getOptions(cmd);
27935
28204
  await doVectorizeList(opts);
27936
28205
  });
27937
- vectorizeCmd.command("create <name>").summary("Create a new Vectorize index").description("Create a new Vectorize index with default dimensions (768) and cosine metric.").action(async function(name) {
27938
- const opts = getOptions(this);
28206
+ vectorizeCmd.command("create <name>").summary("Create a new Vectorize index").description("Create a new Vectorize index with default dimensions (768) and cosine metric.").action(async (name, _, cmd) => {
28207
+ const opts = getOptions(cmd);
27939
28208
  await doVectorizeCreate(name, opts);
27940
28209
  });
27941
- vectorizeCmd.command("delete <name>").summary("Delete a Vectorize index").description("Delete a Vectorize index (WARNING: destructive operation).").action(async function(name) {
27942
- const opts = getOptions(this);
28210
+ vectorizeCmd.command("delete <name>").summary("Delete a Vectorize index").description("Delete a Vectorize index (WARNING: destructive operation).").action(async (name, _, cmd) => {
28211
+ const opts = getOptions(cmd);
27943
28212
  await doVectorizeDelete(name, opts);
27944
28213
  });
27945
28214
  const analyticsCmd = infraCmd.command("analytics").summary("Manage Analytics Engine datasets").description(`Analytics Engine for storing and querying time-series data.
@@ -27950,12 +28219,12 @@ The CLI provides creation instructions.
27950
28219
  EXAMPLES:
27951
28220
  hoox infra analytics list
27952
28221
  hoox infra analytics create hoox-analytics`);
27953
- analyticsCmd.command("list").summary("List all Analytics Engine datasets").description("List all Analytics Engine datasets in your account.").action(async function() {
27954
- const opts = getOptions(this);
28222
+ analyticsCmd.command("list").summary("List all Analytics Engine datasets").description("List all Analytics Engine datasets in your account.").action(async (_, cmd) => {
28223
+ const opts = getOptions(cmd);
27955
28224
  await doAnalyticsList(opts);
27956
28225
  });
27957
- analyticsCmd.command("create <name>").summary("Show instructions for creating an Analytics Engine dataset").description("Analytics Engine datasets must be created via Cloudflare Dashboard.").action(async function(name) {
27958
- const opts = getOptions(this);
28226
+ analyticsCmd.command("create <name>").summary("Show instructions for creating an Analytics Engine dataset").description("Analytics Engine datasets must be created via Cloudflare Dashboard.").action(async (name, _, cmd) => {
28227
+ const opts = getOptions(cmd);
27959
28228
  await doAnalyticsCreate(name, opts);
27960
28229
  });
27961
28230
  }
@@ -28134,32 +28403,7 @@ class SecretsService {
28134
28403
  }
28135
28404
  }
28136
28405
  }
28137
- // src/utils/error-handler.ts
28138
- function withErrorHandling(handler, options) {
28139
- return async (...args) => {
28140
- try {
28141
- await handler(...args);
28142
- } catch (error51) {
28143
- const service = options?.service ?? "cli";
28144
- if (error51 instanceof CLIError) {
28145
- formatError2(error51, options?.opts);
28146
- process.exit(error51.code);
28147
- } else if (error51 instanceof Error) {
28148
- formatError2(`[${service}] ${error51.message}`, options?.opts);
28149
- process.exit(1 /* ERROR */);
28150
- } else {
28151
- formatError2(`[${service}] Unknown error: ${String(error51)}`, options?.opts);
28152
- process.exit(-1 /* CommandFailed */);
28153
- }
28154
- }
28155
- };
28156
- }
28157
-
28158
28406
  // src/commands/config/env-command.ts
28159
- function formatOpts(cmd) {
28160
- const opts = cmd.optsWithGlobals();
28161
- return { json: Boolean(opts.json), quiet: Boolean(opts.quiet) };
28162
- }
28163
28407
  async function handleInit(opts) {
28164
28408
  ye("Environment Setup");
28165
28409
  const existingEnv = Bun.file(".env.local");
@@ -28313,27 +28557,23 @@ EXAMPLES:
28313
28557
  hoox config env validate
28314
28558
  hoox config env generate-dev-vars`);
28315
28559
  envCmd.command("init").description("Interactive wizard to generate .env.local and .dev.vars").action(withErrorHandling(async (_, cmd) => {
28316
- const opts = formatOpts(cmd);
28560
+ const opts = getFormatOptions(cmd);
28317
28561
  await handleInit(opts);
28318
28562
  }, { service: "env" }));
28319
28563
  envCmd.command("show").description("Display current .env.local (secrets redacted)").action(withErrorHandling(async (_, cmd) => {
28320
- const opts = formatOpts(cmd);
28564
+ const opts = getFormatOptions(cmd);
28321
28565
  await handleShow(opts);
28322
28566
  }, { service: "env" }));
28323
28567
  envCmd.command("validate").description("Check required environment variables").action(withErrorHandling(async (_, cmd) => {
28324
- const opts = formatOpts(cmd);
28568
+ const opts = getFormatOptions(cmd);
28325
28569
  await handleValidate(opts);
28326
28570
  }, { service: "env" }));
28327
28571
  envCmd.command("generate-dev-vars").description("Create per-worker .dev.vars from .env.local").action(withErrorHandling(async (_, cmd) => {
28328
- const opts = formatOpts(cmd);
28572
+ const opts = getFormatOptions(cmd);
28329
28573
  await handleGenerateDevVars(opts);
28330
28574
  }, { service: "env" }));
28331
28575
  }
28332
28576
  // src/commands/config/kv-command.ts
28333
- function formatOpts2(cmd) {
28334
- const opts = cmd.optsWithGlobals();
28335
- return { json: Boolean(opts.json), quiet: Boolean(opts.quiet) };
28336
- }
28337
28577
  async function resolveNs(cmd) {
28338
28578
  const svc = new KvSyncService;
28339
28579
  const opts = cmd.optsWithGlobals();
@@ -28434,32 +28674,32 @@ EXAMPLES:
28434
28674
  hoox config kv apply-manifest
28435
28675
  hoox config kv manifest`).option("--namespace-id <id>", "KV namespace ID (auto-detected if omitted)");
28436
28676
  kvCmd.command("list").description("List all keys in the KV namespace").action(withErrorHandling(async (_, cmd) => {
28437
- const opts = formatOpts2(cmd);
28677
+ const opts = getFormatOptions(cmd);
28438
28678
  const nsId = await resolveNs(cmd);
28439
28679
  await handleList(opts, nsId);
28440
28680
  }, { service: "kv" }));
28441
28681
  kvCmd.command("get <key>").description("Get a key's value from the KV namespace").action(withErrorHandling(async (key, _, cmd) => {
28442
- const opts = formatOpts2(cmd);
28682
+ const opts = getFormatOptions(cmd);
28443
28683
  const nsId = await resolveNs(cmd);
28444
28684
  await handleGet(opts, nsId, key);
28445
28685
  }, { service: "kv" }));
28446
28686
  kvCmd.command("set <key> <value>").description("Set a key's value in the KV namespace").action(withErrorHandling(async (key, value, _, cmd) => {
28447
- const opts = formatOpts2(cmd);
28687
+ const opts = getFormatOptions(cmd);
28448
28688
  const nsId = await resolveNs(cmd);
28449
28689
  await handleSet(opts, nsId, key, value);
28450
28690
  }, { service: "kv" }));
28451
28691
  kvCmd.command("delete <key>").description("Delete a key from the KV namespace").action(withErrorHandling(async (key, _, cmd) => {
28452
- const opts = formatOpts2(cmd);
28692
+ const opts = getFormatOptions(cmd);
28453
28693
  const nsId = await resolveNs(cmd);
28454
28694
  await handleDelete2(opts, nsId, key);
28455
28695
  }, { service: "kv" }));
28456
28696
  kvCmd.command("apply-manifest").description("Apply manifest key defaults to the KV namespace").action(withErrorHandling(async (_, cmd) => {
28457
- const opts = formatOpts2(cmd);
28697
+ const opts = getFormatOptions(cmd);
28458
28698
  const nsId = await resolveNs(cmd);
28459
28699
  await handleApplyManifest(opts, nsId);
28460
28700
  }, { service: "kv" }));
28461
28701
  kvCmd.command("manifest").description("Show expected KV keys from manifest").action(withErrorHandling(async (_, cmd) => {
28462
- const opts = formatOpts2(cmd);
28702
+ const opts = getFormatOptions(cmd);
28463
28703
  await handleManifest(opts);
28464
28704
  }, { service: "kv" }));
28465
28705
  }
@@ -28486,10 +28726,6 @@ async function writeConfigRaw(content, configPath) {
28486
28726
  const path3 = configPath ?? "wrangler.jsonc";
28487
28727
  await Bun.write(path3, content);
28488
28728
  }
28489
- function formatOpts3(program2) {
28490
- const opts = program2.optsWithGlobals();
28491
- return { json: Boolean(opts.json), quiet: Boolean(opts.quiet) };
28492
- }
28493
28729
  async function promptSecret(promptText) {
28494
28730
  process.stdout.write(`${theme.info(icons.info)} ${promptText}: `);
28495
28731
  if (process.stdin.isTTY) {
@@ -28589,48 +28825,43 @@ OPTIONS:
28589
28825
 
28590
28826
  EXAMPLES:
28591
28827
  hoox config show
28592
- hoox config show --json`).action(async () => {
28593
- const opts = formatOpts3(program2);
28594
- try {
28595
- if (opts.json) {
28596
- const raw = await readConfigRaw();
28597
- const parsed = JSON.parse(raw);
28598
- formatJson(parsed, opts);
28599
- return;
28600
- }
28601
- const svc = new ConfigService;
28602
- await svc.load();
28603
- const global = svc.getGlobal();
28604
- const globalPairs = {};
28605
- for (const [k2, v2] of Object.entries(global)) {
28606
- globalPairs[k2] = v2 ?? "(not set)";
28607
- }
28608
- process.stdout.write(`${theme.heading(`
28828
+ hoox config show --json`).action(withErrorHandling(async (_, cmd) => {
28829
+ const opts = getFormatOptions(cmd);
28830
+ if (opts.json) {
28831
+ const raw = await readConfigRaw();
28832
+ const parsed = parse6(raw);
28833
+ formatJson(parsed, opts);
28834
+ return;
28835
+ }
28836
+ const svc = new ConfigService;
28837
+ await svc.load();
28838
+ const global = svc.getGlobal();
28839
+ const globalPairs = {};
28840
+ for (const [k2, v2] of Object.entries(global)) {
28841
+ globalPairs[k2] = v2 ?? "(not set)";
28842
+ }
28843
+ process.stdout.write(`${theme.heading(`
28609
28844
  Global Configuration`)}
28610
28845
  `);
28611
- formatKeyValue(globalPairs, opts);
28612
- const workers = svc.listWorkers();
28613
- if (workers.length > 0) {
28614
- process.stdout.write(`
28846
+ formatKeyValue(globalPairs, opts);
28847
+ const workers = svc.listWorkers();
28848
+ if (workers.length > 0) {
28849
+ process.stdout.write(`
28615
28850
  ${theme.heading("Workers")}
28616
28851
  `);
28617
- const rows = workers.map((name) => {
28618
- const w3 = svc.getWorker(name);
28619
- return {
28620
- Worker: name,
28621
- Enabled: w3.enabled ? `${theme.success("\u2022")} yes` : "-",
28622
- Path: w3.path,
28623
- Secrets: w3.secrets?.length ? String(w3.secrets.length) : "-",
28624
- Vars: w3.vars && Object.keys(w3.vars).length ? String(Object.keys(w3.vars).length) : "-"
28625
- };
28626
- });
28627
- formatTable(rows, opts);
28628
- }
28629
- } catch (err) {
28630
- const message = err instanceof Error ? err.message : String(err);
28631
- formatError2(new CLIError(`Failed to show config: ${message}`, 1 /* ERROR */), opts);
28852
+ const rows = workers.map((name) => {
28853
+ const w3 = svc.getWorker(name);
28854
+ return {
28855
+ Worker: name,
28856
+ Enabled: w3.enabled ? `${theme.success("\u2022")} yes` : "-",
28857
+ Path: w3.path,
28858
+ Secrets: w3.secrets?.length ? String(w3.secrets.length) : "-",
28859
+ Vars: w3.vars && Object.keys(w3.vars).length ? String(Object.keys(w3.vars).length) : "-"
28860
+ };
28861
+ });
28862
+ formatTable(rows, opts);
28632
28863
  }
28633
- });
28864
+ }, { service: "config" }));
28634
28865
  configCmd.command("set <key> <value>").summary("Update a config value in wrangler.jsonc").description(`Update a configuration value in wrangler.jsonc.
28635
28866
 
28636
28867
  ARGUMENTS:
@@ -28645,32 +28876,23 @@ PATH EXAMPLES:
28645
28876
  EXAMPLES:
28646
28877
  hoox config set global.subdomain_prefix myapp
28647
28878
  hoox config set workers.trade-worker.enabled false
28648
- hoox config set workers.agent-worker.vars.interval 5`).action(async (key, value) => {
28649
- const opts = formatOpts3(program2);
28879
+ hoox config set workers.agent-worker.vars.interval 5`).action(withErrorHandling(async (key, value, _, cmd) => {
28880
+ const opts = getFormatOptions(cmd);
28881
+ const raw = await readConfigRaw();
28882
+ const jsonPath = keyToPath(key);
28883
+ let edits;
28650
28884
  try {
28651
- const raw = await readConfigRaw();
28652
- const jsonPath = keyToPath(key);
28653
- let edits;
28654
- try {
28655
- edits = modify(raw, jsonPath, parseValue(value), {
28656
- formattingOptions: FORMATTING_OPTIONS
28657
- });
28658
- } catch (err) {
28659
- const msg = err instanceof Error ? err.message : String(err);
28660
- throw new CLIError(`Invalid key path "${key}": ${msg}`, 2 /* INVALID_USAGE */);
28661
- }
28662
- const updated = applyEdits(raw, edits);
28663
- await writeConfigRaw(updated);
28664
- formatSuccess(`Updated "${key}" = "${value}" in wrangler.jsonc`, opts);
28885
+ edits = modify(raw, jsonPath, parseValue(value), {
28886
+ formattingOptions: FORMATTING_OPTIONS
28887
+ });
28665
28888
  } catch (err) {
28666
- if (err instanceof CLIError) {
28667
- formatError2(err, opts);
28668
- } else {
28669
- const message = err instanceof Error ? err.message : String(err);
28670
- formatError2(new CLIError(`Failed to set config: ${message}`, 1 /* ERROR */), opts);
28671
- }
28889
+ const msg = err instanceof Error ? err.message : String(err);
28890
+ throw new CLIError(`Invalid key path "${key}": ${msg}`, 2 /* INVALID_USAGE */);
28672
28891
  }
28673
- });
28892
+ const updated = applyEdits(raw, edits);
28893
+ await writeConfigRaw(updated);
28894
+ formatSuccess(`Updated "${key}" = "${value}" in wrangler.jsonc`, opts);
28895
+ }, { service: "config" }));
28674
28896
  const secretsCmd = configCmd.command("secrets").summary("Manage Cloudflare Worker secrets").description(`Manage secrets for your Cloudflare Workers.
28675
28897
 
28676
28898
  Secrets are defined in wrangler.jsonc under each worker's 'secrets' array.
@@ -28693,58 +28915,53 @@ ARGUMENTS:
28693
28915
 
28694
28916
  EXAMPLES:
28695
28917
  hoox config secrets list
28696
- hoox config secrets list trade-worker`).action(async (worker) => {
28697
- const opts = formatOpts3(program2);
28698
- try {
28699
- const svc = await SecretsService.create();
28700
- if (worker) {
28701
- const secrets = svc.listSecrets(worker);
28702
- if (secrets.length === 0) {
28703
- process.stdout.write(`${theme.dim(`No secrets declared for worker "${worker}".`)}
28918
+ hoox config secrets list trade-worker`).action(withErrorHandling(async (worker, _, cmd) => {
28919
+ const opts = getFormatOptions(cmd);
28920
+ const svc = await SecretsService.create();
28921
+ if (worker) {
28922
+ const secrets = svc.listSecrets(worker);
28923
+ if (secrets.length === 0) {
28924
+ process.stdout.write(`${theme.dim(`No secrets declared for worker "${worker}".`)}
28704
28925
  `);
28705
- return;
28706
- }
28707
- if (opts.json) {
28708
- formatJson({ worker, secrets }, opts);
28709
- } else {
28710
- process.stdout.write(`${theme.heading(`
28926
+ return;
28927
+ }
28928
+ if (opts.json) {
28929
+ formatJson({ worker, secrets }, opts);
28930
+ } else {
28931
+ process.stdout.write(`${theme.heading(`
28711
28932
  Secrets for ${worker}`)}
28712
28933
  `);
28713
- for (const s of secrets) {
28714
- process.stdout.write(` ${theme.label("\u2022")} ${s}
28934
+ for (const s of secrets) {
28935
+ process.stdout.write(` ${theme.label("\u2022")} ${s}
28715
28936
  `);
28716
- }
28717
28937
  }
28718
- } else {
28719
- const all = svc.listAllSecrets();
28720
- const workers = Object.keys(all);
28721
- if (workers.length === 0) {
28722
- process.stdout.write(`${theme.dim("No secrets declared for any worker.")}
28938
+ }
28939
+ } else {
28940
+ const all = svc.listAllSecrets();
28941
+ const workers = Object.keys(all);
28942
+ if (workers.length === 0) {
28943
+ process.stdout.write(`${theme.dim("No secrets declared for any worker.")}
28723
28944
  `);
28724
- return;
28725
- }
28726
- if (opts.json) {
28727
- formatJson(all, opts);
28728
- } else {
28729
- process.stdout.write(`${theme.heading(`
28945
+ return;
28946
+ }
28947
+ if (opts.json) {
28948
+ formatJson(all, opts);
28949
+ } else {
28950
+ process.stdout.write(`${theme.heading(`
28730
28951
  Secrets by Worker`)}
28731
28952
  `);
28732
- for (const [name, secrets] of Object.entries(all)) {
28733
- process.stdout.write(`
28953
+ for (const [name, secrets] of Object.entries(all)) {
28954
+ process.stdout.write(`
28734
28955
  ${theme.bold(name)} (${secrets.length})
28735
28956
  `);
28736
- for (const s of secrets) {
28737
- process.stdout.write(` ${theme.dim("\u2022")} ${s}
28957
+ for (const s of secrets) {
28958
+ process.stdout.write(` ${theme.dim("\u2022")} ${s}
28738
28959
  `);
28739
- }
28740
28960
  }
28741
28961
  }
28742
28962
  }
28743
- } catch (err) {
28744
- const message = err instanceof Error ? err.message : String(err);
28745
- formatError2(new CLIError(`Failed to list secrets: ${message}`, 1 /* ERROR */), opts);
28746
28963
  }
28747
- });
28964
+ }, { service: "config" }));
28748
28965
  secretsCmd.command("set <worker> <name>").summary("Set a secret value for a worker").description(`Set a secret value for a worker and sync to Cloudflare.
28749
28966
 
28750
28967
  ARGUMENTS:
@@ -28756,39 +28973,30 @@ It writes to the worker's .dev.vars file and syncs to Cloudflare.
28756
28973
 
28757
28974
  EXAMPLES:
28758
28975
  hoox config secrets set trade-worker BINANCE_KEY_BINDING
28759
- hoox config secrets set agent-worker OPENAI_KEY`).action(async (workerName, secretName) => {
28760
- const opts = formatOpts3(program2);
28761
- try {
28762
- const svc = await SecretsService.create();
28763
- const declared = svc.listSecrets(workerName);
28764
- if (!declared.includes(secretName) && declared.length > 0) {
28765
- throw new CLIError(`Secret "${secretName}" is not declared for worker "${workerName}". ` + `Declared secrets: ${declared.join(", ")}`, 2 /* INVALID_USAGE */);
28766
- }
28767
- const value = await promptSecret(`Enter value for "${secretName}"`);
28768
- if (!value) {
28769
- throw new CLIError("Secret value cannot be empty", 2 /* INVALID_USAGE */);
28770
- }
28771
- const devVarsPath = `workers/${workerName}/.dev.vars`;
28772
- await updateDevVars(devVarsPath, secretName, value);
28773
- formatSuccess(`Secret "${secretName}" updated in ${devVarsPath}`, opts);
28774
- const syncSpin = vt();
28775
- syncSpin.start("Syncing to Cloudflare...");
28776
- const result = await svc.syncToCloudflare(workerName);
28777
- if (result.ok) {
28778
- syncSpin.stop(`Secret "${secretName}" synced to Cloudflare`);
28779
- } else {
28780
- syncSpin.stop(`Sync partial: ${result.error ?? "unknown error"}`);
28781
- formatError2(new CLIError(`Sync partial: ${result.error ?? "unknown error"}`, 1 /* ERROR */), opts);
28782
- }
28783
- } catch (err) {
28784
- if (err instanceof CLIError) {
28785
- formatError2(err, opts);
28786
- } else {
28787
- const message = err instanceof Error ? err.message : String(err);
28788
- formatError2(new CLIError(`Failed to set secret: ${message}`, 1 /* ERROR */), opts);
28789
- }
28976
+ hoox config secrets set agent-worker OPENAI_KEY`).action(withErrorHandling(async (workerName, secretName, _, cmd) => {
28977
+ const opts = getFormatOptions(cmd);
28978
+ const svc = await SecretsService.create();
28979
+ const declared = svc.listSecrets(workerName);
28980
+ if (!declared.includes(secretName) && declared.length > 0) {
28981
+ throw new CLIError(`Secret "${secretName}" is not declared for worker "${workerName}". ` + `Declared secrets: ${declared.join(", ")}`, 2 /* INVALID_USAGE */);
28982
+ }
28983
+ const value = await promptSecret(`Enter value for "${secretName}"`);
28984
+ if (!value) {
28985
+ throw new CLIError("Secret value cannot be empty", 2 /* INVALID_USAGE */);
28986
+ }
28987
+ const devVarsPath = `workers/${workerName}/.dev.vars`;
28988
+ await updateDevVars(devVarsPath, secretName, value);
28989
+ formatSuccess(`Secret "${secretName}" updated in ${devVarsPath}`, opts);
28990
+ const syncSpin = vt();
28991
+ syncSpin.start("Syncing to Cloudflare...");
28992
+ const result = await svc.syncToCloudflare(workerName);
28993
+ if (result.ok) {
28994
+ syncSpin.stop(`Secret "${secretName}" synced to Cloudflare`);
28995
+ } else {
28996
+ syncSpin.stop(`Sync partial: ${result.error ?? "unknown error"}`);
28997
+ formatError2(new CLIError(`Sync partial: ${result.error ?? "unknown error"}`, 1 /* ERROR */), opts);
28790
28998
  }
28791
- });
28999
+ }, { service: "config" }));
28792
29000
  secretsCmd.command("delete <worker> <name>").summary("Delete a secret from Cloudflare").description(`Delete a secret from Cloudflare Workers.
28793
29001
 
28794
29002
  ARGUMENTS:
@@ -28798,45 +29006,36 @@ ARGUMENTS:
28798
29006
  This removes the secret from Cloudflare and from the worker's .dev.vars file.
28799
29007
 
28800
29008
  EXAMPLES:
28801
- hoox config secrets delete trade-worker BINANCE_KEY_BINDING`).action(async (workerName, secretName) => {
28802
- const opts = formatOpts3(program2);
28803
- try {
28804
- const svc = await SecretsService.create();
28805
- const declared = svc.listSecrets(workerName);
28806
- if (!declared.includes(secretName)) {
28807
- throw new CLIError(`Secret "${secretName}" is not declared for worker "${workerName}".`, 2 /* INVALID_USAGE */);
28808
- }
28809
- const proc = Bun.spawn(["wrangler", "secret", "delete", secretName], {
28810
- cwd: `workers/${workerName}`,
28811
- stdout: "pipe",
28812
- stderr: "pipe"
28813
- });
28814
- const exitCode = await proc.exited;
28815
- if (exitCode !== 0) {
28816
- const stderrText = await new Response(proc.stderr).text();
28817
- throw new CLIError(`wrangler exited with code ${exitCode}: ${stderrText.trim()}`, 1 /* ERROR */);
28818
- }
28819
- const devVarsPath = `workers/${workerName}/.dev.vars`;
28820
- const devFile = Bun.file(devVarsPath);
28821
- if (await devFile.exists()) {
28822
- let content = await devFile.text();
28823
- const lines = content.split(`
29009
+ hoox config secrets delete trade-worker BINANCE_KEY_BINDING`).action(withErrorHandling(async (workerName, secretName, _, cmd) => {
29010
+ const opts = getFormatOptions(cmd);
29011
+ const svc = await SecretsService.create();
29012
+ const declared = svc.listSecrets(workerName);
29013
+ if (!declared.includes(secretName)) {
29014
+ throw new CLIError(`Secret "${secretName}" is not declared for worker "${workerName}".`, 2 /* INVALID_USAGE */);
29015
+ }
29016
+ const proc = Bun.spawn(["wrangler", "secret", "delete", secretName], {
29017
+ cwd: `workers/${workerName}`,
29018
+ stdout: "pipe",
29019
+ stderr: "pipe"
29020
+ });
29021
+ const exitCode = await proc.exited;
29022
+ if (exitCode !== 0) {
29023
+ const stderrText = await new Response(proc.stderr).text();
29024
+ throw new CLIError(`wrangler exited with code ${exitCode}: ${stderrText.trim()}`, 1 /* ERROR */);
29025
+ }
29026
+ const devVarsPath = `workers/${workerName}/.dev.vars`;
29027
+ const devFile = Bun.file(devVarsPath);
29028
+ if (await devFile.exists()) {
29029
+ let content = await devFile.text();
29030
+ const lines = content.split(`
28824
29031
  `);
28825
- const filtered = lines.filter((line) => !line.startsWith(`${secretName}=`) && line.trim() !== "");
28826
- await Bun.write(devVarsPath, filtered.join(`
29032
+ const filtered = lines.filter((line) => !line.startsWith(`${secretName}=`) && line.trim() !== "");
29033
+ await Bun.write(devVarsPath, filtered.join(`
28827
29034
  `) + (filtered.length > 0 ? `
28828
29035
  ` : ""));
28829
- }
28830
- formatSuccess(`Secret "${secretName}" deleted from Cloudflare`, opts);
28831
- } catch (err) {
28832
- if (err instanceof CLIError) {
28833
- formatError2(err, opts);
28834
- } else {
28835
- const message = err instanceof Error ? err.message : String(err);
28836
- formatError2(new CLIError(`Failed to delete secret: ${message}`, 1 /* ERROR */), opts);
28837
- }
28838
29036
  }
28839
- });
29037
+ formatSuccess(`Secret "${secretName}" deleted from Cloudflare`, opts);
29038
+ }, { service: "config" }));
28840
29039
  secretsCmd.command("sync [worker]").summary("Sync secrets to Cloudflare").description(`Sync secrets from .dev.vars files to Cloudflare Workers.
28841
29040
 
28842
29041
  ARGUMENTS:
@@ -28846,56 +29045,47 @@ This reads .dev.vars files and uploads secrets to Cloudflare via wrangler.
28846
29045
 
28847
29046
  EXAMPLES:
28848
29047
  hoox config secrets sync Sync all workers
28849
- hoox config secrets sync trade-worker Sync specific worker`).action(async (workerName) => {
28850
- const opts = formatOpts3(program2);
28851
- try {
28852
- const svc = await SecretsService.create();
28853
- if (workerName) {
28854
- const syncSpin = vt();
28855
- syncSpin.start(`Syncing secrets for "${workerName}"...`);
28856
- const result = await svc.syncToCloudflare(workerName);
28857
- if (result.ok) {
28858
- syncSpin.stop(`Synced ${result.value?.length ?? 0} secrets for "${workerName}"`);
28859
- } else {
28860
- syncSpin.stop(`Sync failed: ${result.error ?? "unknown error"}`);
28861
- formatError2(new CLIError(`Sync failed: ${result.error ?? "unknown error"}`, 1 /* ERROR */), opts);
28862
- }
29048
+ hoox config secrets sync trade-worker Sync specific worker`).action(withErrorHandling(async (workerName, _, cmd) => {
29049
+ const opts = getFormatOptions(cmd);
29050
+ const svc = await SecretsService.create();
29051
+ if (workerName) {
29052
+ const syncSpin = vt();
29053
+ syncSpin.start(`Syncing secrets for "${workerName}"...`);
29054
+ const result = await svc.syncToCloudflare(workerName);
29055
+ if (result.ok) {
29056
+ syncSpin.stop(`Synced ${result.value?.length ?? 0} secrets for "${workerName}"`);
28863
29057
  } else {
28864
- const all = svc.listAllSecrets();
28865
- const workers = Object.keys(all);
28866
- if (workers.length === 0) {
28867
- formatSuccess("No secrets to sync.", opts);
28868
- return;
28869
- }
28870
- let synced = 0;
28871
- let failed = 0;
28872
- const syncSpin = vt();
28873
- for (const name of workers) {
28874
- syncSpin.start(`Syncing ${name}...`);
28875
- const result = await svc.syncToCloudflare(name);
28876
- if (result.ok) {
28877
- syncSpin.stop(`${theme.success("synced")} ${result.value?.length ?? 0} for ${name}`);
28878
- synced++;
28879
- } else {
28880
- syncSpin.stop(`${theme.error("failed")} ${name}`);
28881
- failed++;
28882
- }
28883
- }
28884
- if (failed === 0) {
28885
- formatSuccess(`All ${synced} workers synced successfully`, opts);
29058
+ syncSpin.stop(`Sync failed: ${result.error ?? "unknown error"}`);
29059
+ formatError2(new CLIError(`Sync failed: ${result.error ?? "unknown error"}`, 1 /* ERROR */), opts);
29060
+ }
29061
+ } else {
29062
+ const all = svc.listAllSecrets();
29063
+ const workers = Object.keys(all);
29064
+ if (workers.length === 0) {
29065
+ formatSuccess("No secrets to sync.", opts);
29066
+ return;
29067
+ }
29068
+ let synced = 0;
29069
+ let failed = 0;
29070
+ const syncSpin = vt();
29071
+ for (const name of workers) {
29072
+ syncSpin.start(`Syncing ${name}...`);
29073
+ const result = await svc.syncToCloudflare(name);
29074
+ if (result.ok) {
29075
+ syncSpin.stop(`${theme.success("synced")} ${result.value?.length ?? 0} for ${name}`);
29076
+ synced++;
28886
29077
  } else {
28887
- formatError2(new CLIError(`${synced} synced, ${failed} failed`, 1 /* ERROR */), opts);
29078
+ syncSpin.stop(`${theme.error("failed")} ${name}`);
29079
+ failed++;
28888
29080
  }
28889
29081
  }
28890
- } catch (err) {
28891
- if (err instanceof CLIError) {
28892
- formatError2(err, opts);
29082
+ if (failed === 0) {
29083
+ formatSuccess(`All ${synced} workers synced successfully`, opts);
28893
29084
  } else {
28894
- const message = err instanceof Error ? err.message : String(err);
28895
- formatError2(new CLIError(`Failed to sync secrets: ${message}`, 1 /* ERROR */), opts);
29085
+ formatError2(new CLIError(`${synced} synced, ${failed} failed`, 1 /* ERROR */), opts);
28896
29086
  }
28897
29087
  }
28898
- });
29088
+ }, { service: "config" }));
28899
29089
  registerEnvCommand(configCmd);
28900
29090
  registerKvCommand(configCmd);
28901
29091
  const keysCmd = configCmd.command("keys").summary("Manage internal auth keys").description(`Generate and manage internal auth keys for inter-worker communication.
@@ -28913,86 +29103,75 @@ Creates the following keys:
28913
29103
  - WEBHOOK_API_KEY_BINDING (32 char)
28914
29104
  - AGENT_INTERNAL_KEY (32 char)
28915
29105
  - TG_BOT_TOKEN_BINDING (16 char)
28916
- - INTERNAL_KEY_BINDING (32 char)
28917
29106
 
28918
29107
  WARNING: Add .keys/ to your .gitignore to avoid committing secrets!
28919
29108
 
28920
29109
  EXAMPLES:
28921
- hoox config keys generate`).action(async () => {
28922
- const opts = formatOpts3(program2);
28923
- try {
28924
- const keysDir = ".keys";
28925
- if (!existsSync3(keysDir)) {
28926
- mkdirSync2(keysDir, { recursive: true });
28927
- }
28928
- const keys = {
28929
- INTERNAL_KEY_BINDING: generateKey(),
28930
- WEBHOOK_API_KEY_BINDING: generateKey(),
28931
- AGENT_INTERNAL_KEY: generateKey(),
28932
- TG_BOT_TOKEN_BINDING: generateKey(16)
28933
- };
28934
- for (const [name, value] of Object.entries(keys)) {
28935
- const filePath = `${keysDir}/${name.toLowerCase()}.env`;
28936
- await Bun.write(filePath, `${name}=${value}
29110
+ hoox config keys generate`).action(withErrorHandling(async (_, cmd) => {
29111
+ const opts = getFormatOptions(cmd);
29112
+ const keysDir = ".keys";
29113
+ if (!existsSync3(keysDir)) {
29114
+ mkdirSync2(keysDir, { recursive: true });
29115
+ }
29116
+ const keys = {
29117
+ INTERNAL_KEY_BINDING: generateKey(),
29118
+ WEBHOOK_API_KEY_BINDING: generateKey(),
29119
+ AGENT_INTERNAL_KEY: generateKey(),
29120
+ TG_BOT_TOKEN_BINDING: generateKey(16)
29121
+ };
29122
+ for (const [name, value] of Object.entries(keys)) {
29123
+ const filePath = `${keysDir}/${name.toLowerCase()}.env`;
29124
+ await Bun.write(filePath, `${name}=${value}
28937
29125
  `);
28938
- }
28939
- formatSuccess(`Generated ${Object.keys(keys).length} keys in ${keysDir}/`, opts);
28940
- if (!opts.quiet && !opts.json) {
28941
- process.stdout.write(`${theme.warning("!")} Keep these keys secret. Add ${keysDir}/ to .gitignore.
29126
+ }
29127
+ formatSuccess(`Generated ${Object.keys(keys).length} keys in ${keysDir}/`, opts);
29128
+ if (!opts.quiet && !opts.json) {
29129
+ process.stdout.write(`${theme.warning("!")} Keep these keys secret. Add ${keysDir}/ to .gitignore.
28942
29130
  `);
28943
- formatKeyValue(keys, opts);
28944
- }
28945
- } catch (err) {
28946
- const message = err instanceof Error ? err.message : String(err);
28947
- formatError2(new CLIError(`Failed to generate keys: ${message}`, 1 /* ERROR */), opts);
29131
+ formatKeyValue(keys, opts);
28948
29132
  }
28949
- });
29133
+ }, { service: "config" }));
28950
29134
  keysCmd.command("list").summary("List existing internal auth keys").description(`List existing internal auth keys from the .keys/ directory.
28951
29135
 
28952
29136
  Shows key names (values are hidden for security).
28953
29137
 
28954
29138
  EXAMPLES:
28955
- hoox config keys list`).action(async () => {
28956
- const opts = formatOpts3(program2);
28957
- try {
28958
- const keysDir = ".keys";
28959
- if (!existsSync3(keysDir)) {
28960
- process.stdout.write(`${theme.dim("No .keys/ directory found.")}
29139
+ hoox config keys list`).action(withErrorHandling(async (_, cmd) => {
29140
+ const opts = getFormatOptions(cmd);
29141
+ const keysDir = ".keys";
29142
+ if (!existsSync3(keysDir)) {
29143
+ process.stdout.write(`${theme.dim("No .keys/ directory found.")}
28961
29144
  `);
28962
- return;
28963
- }
28964
- const glob = new Bun.Glob("*.env");
28965
- const entries = [];
28966
- for await (const f2 of glob.scan({ cwd: keysDir, absolute: false })) {
28967
- entries.push(f2);
28968
- }
28969
- if (entries.length === 0) {
28970
- process.stdout.write(`${theme.dim("No key files found in .keys/")}
29145
+ return;
29146
+ }
29147
+ const glob = new Bun.Glob("*.env");
29148
+ const entries = [];
29149
+ for await (const f2 of glob.scan({ cwd: keysDir, absolute: false })) {
29150
+ entries.push(f2);
29151
+ }
29152
+ if (entries.length === 0) {
29153
+ process.stdout.write(`${theme.dim("No key files found in .keys/")}
28971
29154
  `);
28972
- return;
28973
- }
28974
- const keyMap = {};
28975
- for (const entry of entries) {
28976
- const filePath = `${keysDir}/${entry}`;
28977
- const content = await (await Bun.file(filePath).text()).trim();
28978
- const eqIdx = content.indexOf("=");
28979
- if (eqIdx > 0) {
28980
- keyMap[content.substring(0, eqIdx)] = "****";
28981
- }
29155
+ return;
29156
+ }
29157
+ const keyMap = {};
29158
+ for (const entry of entries) {
29159
+ const filePath = `${keysDir}/${entry}`;
29160
+ const content = await (await Bun.file(filePath).text()).trim();
29161
+ const eqIdx = content.indexOf("=");
29162
+ if (eqIdx > 0) {
29163
+ keyMap[content.substring(0, eqIdx)] = "****";
28982
29164
  }
28983
- if (opts.json) {
28984
- formatJson({ keys: entries.length, files: entries }, opts);
28985
- } else {
28986
- process.stdout.write(`${theme.heading(`
29165
+ }
29166
+ if (opts.json) {
29167
+ formatJson({ keys: entries.length, files: entries }, opts);
29168
+ } else {
29169
+ process.stdout.write(`${theme.heading(`
28987
29170
  Key files in ${keysDir}/`)}
28988
29171
  `);
28989
- formatKeyValue(keyMap, opts);
28990
- }
28991
- } catch (err) {
28992
- const message = err instanceof Error ? err.message : String(err);
28993
- formatError2(new CLIError(`Failed to list keys: ${message}`, 1 /* ERROR */), opts);
29172
+ formatKeyValue(keyMap, opts);
28994
29173
  }
28995
- });
29174
+ }, { service: "config" }));
28996
29175
  }
28997
29176
  function parseValue(raw) {
28998
29177
  if (raw === "true")
@@ -29037,6 +29216,7 @@ async function updateDevVars(filePath, key, value) {
29037
29216
  // src/commands/check/check-command.ts
29038
29217
  import { readdir } from "fs/promises";
29039
29218
  import path3 from "path";
29219
+ init_main();
29040
29220
  init_config2();
29041
29221
 
29042
29222
  // src/commands/check/prerequisites-command.ts
@@ -29644,18 +29824,33 @@ async function handleFix(opts, dryRun) {
29644
29824
  };
29645
29825
  if (!dryRun) {
29646
29826
  try {
29647
- const { parse: parse7 } = await Promise.resolve().then(() => (init_main(), exports_main));
29648
- const config2 = parse7(content);
29649
- if (!config2.compatibility_flags) {
29650
- config2.compatibility_flags = ["nodejs_compat"];
29651
- } else if (Array.isArray(config2.compatibility_flags)) {
29652
- const flags = config2.compatibility_flags;
29827
+ const parsedConfig = parse6(content);
29828
+ let updated = content;
29829
+ if (!parsedConfig.compatibility_flags) {
29830
+ const edits = modify(content, ["compatibility_flags"], ["nodejs_compat"], {
29831
+ formattingOptions: {
29832
+ tabSize: 2,
29833
+ insertSpaces: true,
29834
+ eol: `
29835
+ `
29836
+ }
29837
+ });
29838
+ updated = applyEdits(content, edits);
29839
+ } else if (Array.isArray(parsedConfig.compatibility_flags)) {
29840
+ const flags = parsedConfig.compatibility_flags;
29653
29841
  if (!flags.includes("nodejs_compat")) {
29654
- flags.push("nodejs_compat");
29842
+ const newFlags = [...flags, "nodejs_compat"];
29843
+ const edits = modify(content, ["compatibility_flags"], newFlags, {
29844
+ formattingOptions: {
29845
+ tabSize: 2,
29846
+ insertSpaces: true,
29847
+ eol: `
29848
+ `
29849
+ }
29850
+ });
29851
+ updated = applyEdits(content, edits);
29655
29852
  }
29656
29853
  }
29657
- const updated = JSON.stringify(config2, null, 2) + `
29658
- `;
29659
29854
  await Bun.write(wranglerPath, updated);
29660
29855
  action.applied = true;
29661
29856
  } catch (err) {
@@ -29674,10 +29869,9 @@ async function handleFix(opts, dryRun) {
29674
29869
  const wranglerFile = Bun.file(wranglerPath);
29675
29870
  if (await wranglerFile.exists()) {
29676
29871
  const content = await wranglerFile.text();
29677
- const strippedContent = content.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
29678
29872
  try {
29679
- const config2 = JSON.parse(strippedContent);
29680
- if (!config2.name) {
29873
+ const parsedConfig = parse6(content);
29874
+ if (!parsedConfig.name) {
29681
29875
  const action = {
29682
29876
  description: `Add missing "name" field to wrangler.jsonc for worker "${workerName}"`,
29683
29877
  type: "config",
@@ -29686,9 +29880,15 @@ async function handleFix(opts, dryRun) {
29686
29880
  applied: false
29687
29881
  };
29688
29882
  if (!dryRun) {
29689
- config2.name = workerName;
29690
- const updated = JSON.stringify(config2, null, 2) + `
29691
- `;
29883
+ const edits = modify(content, ["name"], workerName, {
29884
+ formattingOptions: {
29885
+ tabSize: 2,
29886
+ insertSpaces: true,
29887
+ eol: `
29888
+ `
29889
+ }
29890
+ });
29891
+ const updated = applyEdits(content, edits);
29692
29892
  await Bun.write(wranglerPath, updated);
29693
29893
  action.applied = true;
29694
29894
  }
@@ -29868,10 +30068,10 @@ OUTPUT:
29868
30068
 
29869
30069
  EXAMPLES:
29870
30070
  hoox check setup
29871
- hoox check setup --json`).action(async (_, cmd) => {
30071
+ hoox check setup --json`).action(withErrorHandling(async (_, cmd) => {
29872
30072
  const opts = getFormatOptions(cmd);
29873
30073
  await handleSetup(opts);
29874
- });
30074
+ }, { service: "check" }));
29875
30075
  checkCmd.command("health").summary("Check worker connectivity and responsiveness").description(`Run health checks on all enabled workers to verify they are running and responsive.
29876
30076
 
29877
30077
  Each worker is probed to verify:
@@ -29888,10 +30088,10 @@ OUTPUT:
29888
30088
  EXAMPLES:
29889
30089
  hoox check health
29890
30090
  hoox check health --json
29891
- hoox check health --fix`).option("--fix", "Attempt automatic repair for detected issues").action(async (options, cmd) => {
30091
+ hoox check health --fix`).option("--fix", "Attempt automatic repair for detected issues").action(withErrorHandling(async (options, cmd) => {
29892
30092
  const opts = getFormatOptions(cmd);
29893
30093
  await handleHealth(opts, Boolean(options.fix));
29894
- });
30094
+ }, { service: "check" }));
29895
30095
  checkCmd.command("fix").summary("Auto-repair common issues").description(`Automatically repair known common issues in your Hoox setup.
29896
30096
 
29897
30097
  Repairs performed:
@@ -29904,10 +30104,10 @@ OPTIONS:
29904
30104
 
29905
30105
  EXAMPLES:
29906
30106
  hoox check fix Apply fixes automatically
29907
- hoox check fix --dry-run Preview what would be fixed`).option("--dry-run", "Preview changes without applying them").action(async (options, cmd) => {
30107
+ hoox check fix --dry-run Preview what would be fixed`).option("--dry-run", "Preview changes without applying them").action(withErrorHandling(async (options, cmd) => {
29908
30108
  const opts = getFormatOptions(cmd);
29909
30109
  await handleFix(opts, Boolean(options.dryRun));
29910
- });
30110
+ }, { service: "check" }));
29911
30111
  registerPrerequisitesCommand(checkCmd);
29912
30112
  checkCmd.command("submodule-gitignore").alias("sg").summary("Validate and fix worker submodule .gitignore files").description(`Validate and fix .gitignore files in worker submodules.
29913
30113
 
@@ -29922,10 +30122,10 @@ Also removes wrangler.jsonc from git tracking if mistakenly tracked.
29922
30122
 
29923
30123
  EXAMPLES:
29924
30124
  hoox check submodule-gitignore
29925
- hoox check sg # alias`).action(async (_, cmd) => {
30125
+ hoox check sg # alias`).action(withErrorHandling(async (_, cmd) => {
29926
30126
  const opts = getFormatOptions(cmd);
29927
30127
  await handleSubmoduleGitignore(opts);
29928
- });
30128
+ }, { service: "check" }));
29929
30129
  }
29930
30130
  // src/commands/logs/logs-command.ts
29931
30131
  init_config2();
@@ -30071,9 +30271,8 @@ async function tailAllWorkers(opts, fmt) {
30071
30271
  });
30072
30272
  }
30073
30273
  const decoder = new TextDecoder;
30074
- const buffers = new Map;
30075
30274
  const readFromWorker = async (p2) => {
30076
- let buffer = buffers.get(p2.name) ?? "";
30275
+ let buffer = "";
30077
30276
  while (true) {
30078
30277
  const { done, value } = await p2.reader.read();
30079
30278
  if (done)
@@ -30112,7 +30311,6 @@ async function tailAllWorkers(opts, fmt) {
30112
30311
  process.stdout.write(`${theme.bold(`[${p2.name}]`)} ${buffer}
30113
30312
  `);
30114
30313
  }
30115
- buffers.set(p2.name, buffer);
30116
30314
  };
30117
30315
  if (opts.follow) {
30118
30316
  await Promise.all(processes.map((p2) => readFromWorker(p2)));
@@ -30263,34 +30461,26 @@ var PIPELINE_STEPS = [
30263
30461
  ];
30264
30462
  function registerTestCommand(program2) {
30265
30463
  const testCmd = program2.command("test").description("Run tests and CI pipeline");
30266
- testCmd.command("all").description("Run full CI pipeline: lint \u2192 typecheck \u2192 unit tests \u2192 integration tests").option("--json", "Output results as JSON").action(async (options) => {
30464
+ testCmd.command("all").description("Run full CI pipeline: lint \u2192 typecheck \u2192 unit tests \u2192 integration tests").option("--json", "Output results as JSON").action(withErrorHandling(async (options) => {
30267
30465
  const fmt = getFormatOptions(program2);
30268
30466
  const useJson = options.json || fmt.json;
30269
30467
  const opts = { json: useJson, quiet: fmt.quiet };
30270
30468
  const results = [];
30271
30469
  const s = vt();
30272
30470
  s.start("Running CI pipeline...");
30273
- try {
30274
- for (const step of PIPELINE_STEPS) {
30275
- s.message(`${theme.info(icons.info)} ${step.label}...`);
30276
- const result = await runStep(step.args, process.cwd());
30277
- results.push({ ...result, step: step.label });
30278
- if (result.success) {
30279
- s.message(` ${theme.success(icons.success)} ${step.label} passed (${result.duration}ms)`);
30280
- } else {
30281
- s.message(` ${theme.error(icons.error)} ${step.label} failed (${result.duration}ms)`);
30282
- if (result.error) {
30283
- s.message(theme.dim(result.error.slice(0, 500)));
30284
- }
30285
- break;
30471
+ for (const step of PIPELINE_STEPS) {
30472
+ s.message(`${theme.info(icons.info)} ${step.label}...`);
30473
+ const result = await runStep(step.args, process.cwd());
30474
+ results.push({ ...result, step: step.label });
30475
+ if (result.success) {
30476
+ s.message(` ${theme.success(icons.success)} ${step.label} passed (${result.duration}ms)`);
30477
+ } else {
30478
+ s.message(` ${theme.error(icons.error)} ${step.label} failed (${result.duration}ms)`);
30479
+ if (result.error) {
30480
+ s.message(theme.dim(result.error.slice(0, 500)));
30286
30481
  }
30482
+ break;
30287
30483
  }
30288
- } catch (err) {
30289
- const message = err instanceof Error ? err.message : String(err);
30290
- formatError2(message, opts);
30291
- process.exitCode = 1 /* ERROR */;
30292
- s.stop("Pipeline aborted with unexpected error");
30293
- return;
30294
30484
  }
30295
30485
  const summary = {
30296
30486
  total: results.length,
@@ -30303,7 +30493,7 @@ function registerTestCommand(program2) {
30303
30493
  if (summary.failed > 0) {
30304
30494
  process.exitCode = 1 /* ERROR */;
30305
30495
  }
30306
- });
30496
+ }, { service: "test" }));
30307
30497
  testCmd.command("unit").description("Run unit tests with bun test").option("--coverage", "Run with coverage reporting").action(async (options) => {
30308
30498
  const fmt = getFormatOptions(program2);
30309
30499
  const args = ["bun", "test"];
@@ -30330,55 +30520,41 @@ function registerTestCommand(program2) {
30330
30520
  process.exitCode = 1 /* ERROR */;
30331
30521
  }
30332
30522
  });
30333
- testCmd.command("worker <name>").description("Run tests for a specific worker by name").option("--coverage", "Run with coverage reporting").action(async (name, options) => {
30523
+ testCmd.command("worker <name>").description("Run tests for a specific worker by name").option("--coverage", "Run with coverage reporting").action(withErrorHandling(async (name, options) => {
30334
30524
  const fmt = getFormatOptions(program2);
30335
- try {
30336
- const configService = new ConfigService;
30337
- await configService.load();
30338
- const workerConfig = configService.getWorker(name);
30339
- if (!workerConfig) {
30340
- formatError2(new CLIError(`Worker "${name}" not found in wrangler.jsonc`, 2 /* INVALID_USAGE */), fmt);
30341
- process.exitCode = 2 /* INVALID_USAGE */;
30342
- return;
30343
- }
30344
- const workerDir = `${process.cwd()}/${workerConfig.path}`;
30345
- const args = ["bun", "test"];
30346
- if (options.coverage)
30347
- args.push("--coverage");
30348
- const result = await runWithInherit(args, workerDir);
30349
- if (result.success) {
30350
- formatSuccess(`Worker "${name}" tests passed`, fmt);
30351
- } else {
30352
- formatError2(new CLIError(`Worker "${name}" tests failed (exit code ${result.exitCode})`, 1 /* ERROR */), fmt);
30353
- process.exitCode = 1 /* ERROR */;
30354
- }
30355
- } catch (err) {
30356
- const message = err instanceof Error ? err.message : String(err);
30357
- formatError2(message, fmt);
30525
+ const configService = new ConfigService;
30526
+ await configService.load();
30527
+ const workerConfig = configService.getWorker(name);
30528
+ if (!workerConfig) {
30529
+ formatError2(new CLIError(`Worker "${name}" not found in wrangler.jsonc`, 2 /* INVALID_USAGE */), fmt);
30530
+ process.exitCode = 2 /* INVALID_USAGE */;
30531
+ return;
30532
+ }
30533
+ const workerDir = `${process.cwd()}/${workerConfig.path}`;
30534
+ const args = ["bun", "test"];
30535
+ if (options.coverage)
30536
+ args.push("--coverage");
30537
+ const result = await runWithInherit(args, workerDir);
30538
+ if (result.success) {
30539
+ formatSuccess(`Worker "${name}" tests passed`, fmt);
30540
+ } else {
30541
+ formatError2(new CLIError(`Worker "${name}" tests failed (exit code ${result.exitCode})`, 1 /* ERROR */), fmt);
30358
30542
  process.exitCode = 1 /* ERROR */;
30359
30543
  }
30360
- });
30361
- testCmd.command("live").description("Run live Cloudflare service integration tests (no mocks)").option("--service <name>", "Test a specific service: d1, kv, r2, queues, ai, api, secrets, durable-objects").action(async (options) => {
30362
- const fmt = getFormatOptions(program2);
30544
+ }, { service: "test" }));
30545
+ testCmd.command("live").description("Run live Cloudflare service integration tests (no mocks)").option("--service <name>", "Test a specific service: d1, kv, r2, queues, ai, api, secrets, durable-objects").action(withErrorHandling(async (options) => {
30363
30546
  const s = vt();
30364
- try {
30365
- let filePattern = options.service ? `tests/live/${options.service}.test.ts` : "tests/live/";
30366
- s.start(`Running live tests${options.service ? ` for ${options.service}` : ""}...`);
30367
- const args = ["bun", "test", filePattern, "--jobs", "1"];
30368
- const result = await runWithInherit(args, process.cwd());
30369
- if (result.success) {
30370
- s.stop("Live tests complete");
30371
- } else {
30372
- s.stop(`Live tests: some failures (exit code ${result.exitCode})`);
30373
- process.exitCode = 1 /* ERROR */;
30374
- }
30375
- } catch (err) {
30376
- s.stop("Live tests aborted");
30377
- const message = err instanceof Error ? err.message : String(err);
30378
- formatError2(message, fmt);
30547
+ let filePattern = options.service ? `tests/live/${options.service}.test.ts` : "tests/live/";
30548
+ s.start(`Running live tests${options.service ? ` for ${options.service}` : ""}...`);
30549
+ const args = ["bun", "test", filePattern, "--jobs", "1"];
30550
+ const result = await runWithInherit(args, process.cwd());
30551
+ if (result.success) {
30552
+ s.stop("Live tests complete");
30553
+ } else {
30554
+ s.stop(`Live tests: some failures (exit code ${result.exitCode})`);
30379
30555
  process.exitCode = 1 /* ERROR */;
30380
30556
  }
30381
- });
30557
+ }, { service: "test" }));
30382
30558
  }
30383
30559
  // src/commands/waf/waf-command.ts
30384
30560
  var CF_API_BASE = "https://api.cloudflare.com/client/v4";
@@ -30600,29 +30776,29 @@ async function handleMode(mode, opts) {
30600
30776
  function registerWafCommand(program2) {
30601
30777
  const waf = program2.command("waf").description("Manage Cloudflare WAF (Web Application Firewall)");
30602
30778
  waf.command("status").description("Show WAF status (enabled/disabled, active rules, recent blocks)").action(withErrorHandling(async (_, cmd) => {
30603
- const opts = cmd.parent.parent.opts();
30779
+ const opts = getFormatOptions(cmd);
30604
30780
  await handleStatus(opts);
30605
30781
  }, { service: "waf" }));
30606
30782
  const rules = waf.command("rules").description("Manage WAF firewall rules");
30607
30783
  rules.command("list").description("List all WAF firewall rules").action(withErrorHandling(async (_, cmd) => {
30608
- const opts = cmd.parent.parent.parent.opts();
30784
+ const opts = getFormatOptions(cmd);
30609
30785
  await handleRulesList(opts);
30610
30786
  }, { service: "waf" }));
30611
30787
  rules.command("add <type> <value>").description("Add a WAF rule. Types: ip-allowlist, ip-blocklist, rate-limit, custom").action(withErrorHandling(async (type, value, _, cmd) => {
30612
- const opts = cmd.parent.parent.parent.opts();
30788
+ const opts = getFormatOptions(cmd);
30613
30789
  await handleRulesAdd(type, value, opts);
30614
30790
  }, { service: "waf" }));
30615
30791
  rules.command("remove <ruleId>").description("Remove a WAF rule by ID").action(withErrorHandling(async (ruleId, _, cmd) => {
30616
- const opts = cmd.parent.parent.parent.opts();
30792
+ const opts = getFormatOptions(cmd);
30617
30793
  await handleRulesRemove(ruleId, opts);
30618
30794
  }, { service: "waf" }));
30619
30795
  const mode = waf.command("mode").description("Enable or disable WAF protection");
30620
30796
  mode.command("enable").description("Enable WAF protection on the zone").action(withErrorHandling(async (_, cmd) => {
30621
- const opts = cmd.parent.parent.parent.opts();
30797
+ const opts = getFormatOptions(cmd);
30622
30798
  await handleMode("on", opts);
30623
30799
  }, { service: "waf" }));
30624
30800
  mode.command("disable").description("Disable WAF protection on the zone").action(withErrorHandling(async (_, cmd) => {
30625
- const opts = cmd.parent.parent.parent.opts();
30801
+ const opts = getFormatOptions(cmd);
30626
30802
  await handleMode("off", opts);
30627
30803
  }, { service: "waf" }));
30628
30804
  }
@@ -30726,129 +30902,123 @@ EXAMPLES:
30726
30902
  hoox clone List all workers' clone status
30727
30903
  hoox clone --all Clone all workers
30728
30904
  hoox clone trade-worker Clone specific worker
30729
- hoox clone --org my-org Clone from specific org`).option("--all", "Clone all worker repositories").option("--org <org>", "GitHub organization (derived from git remote by default)").argument("[name]", "Worker name to clone (omit to list status)").action(async (name, options) => {
30905
+ hoox clone --org my-org Clone from specific org`).option("--all", "Clone all worker repositories").option("--org <org>", "GitHub organization (derived from git remote by default)").argument("[name]", "Worker name to clone (omit to list status)").action(withErrorHandling(async (name, options) => {
30730
30906
  const fmt = getFormatOptions(program2);
30731
- try {
30732
- const configService = new ConfigService;
30733
- await configService.load();
30734
- const workers = configService.listWorkers();
30735
- if (!options.all && !name) {
30736
- const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
30737
- const statuses = await buildStatusList(configService, repoBase);
30738
- if (fmt.json) {
30739
- formatJson(statuses, fmt);
30740
- return;
30741
- }
30742
- if (fmt.quiet) {
30743
- for (const s of statuses) {
30744
- process.stdout.write(`${s.worker}: ${s.cloned ? "cloned" : "not cloned"}
30907
+ const configService = new ConfigService;
30908
+ await configService.load();
30909
+ const workers = configService.listWorkers();
30910
+ if (!options.all && !name) {
30911
+ const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
30912
+ const statuses = await buildStatusList(configService, repoBase);
30913
+ if (fmt.json) {
30914
+ formatJson(statuses, fmt);
30915
+ return;
30916
+ }
30917
+ if (fmt.quiet) {
30918
+ for (const s of statuses) {
30919
+ process.stdout.write(`${s.worker}: ${s.cloned ? "cloned" : "not cloned"}
30745
30920
  `);
30746
- }
30747
- return;
30748
30921
  }
30749
- const rows = statuses.map((s) => ({
30750
- Worker: s.worker,
30751
- Status: s.cloned ? "cloned" : "not cloned",
30752
- Repo: s.repo ?? "-"
30753
- }));
30754
- formatTable(rows, fmt);
30755
30922
  return;
30756
30923
  }
30757
- if (options.all) {
30758
- if (workers.length === 0) {
30759
- formatSuccess("No workers defined in wrangler.jsonc", fmt);
30760
- return;
30761
- }
30762
- const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
30763
- const s = vt();
30764
- s.start(`Cloning ${workers.length} worker(s)...`);
30765
- const results = [];
30766
- for (let i = 0;i < workers.length; i++) {
30767
- const name2 = workers[i];
30768
- const workerConfig = configService.getWorker(name2);
30769
- if (!workerConfig)
30770
- continue;
30771
- const alreadyCloned = await isWorkerCloned(workerConfig.path);
30772
- if (alreadyCloned) {
30773
- s.message(`[${i + 1}/${workers.length}] ${icons.info} ${name2} already cloned \u2014 skipping`);
30774
- results.push({
30775
- worker: name2,
30776
- path: workerConfig.path,
30777
- cloned: true,
30778
- repo: getRepoUrl(repoBase, name2)
30779
- });
30780
- continue;
30781
- }
30782
- s.message(`[${i + 1}/${workers.length}] Cloning ${name2}...`);
30783
- const result = await cloneWorker(getRepoUrl(repoBase, name2), workerConfig.path, name2, process.cwd());
30784
- results.push(result);
30785
- if (result.cloned) {
30786
- s.message(`[${i + 1}/${workers.length}] ${icons.success} ${name2} cloned`);
30787
- } else {
30788
- s.message(`[${i + 1}/${workers.length}] ${icons.error} ${name2} failed: ${result.error}`);
30789
- }
30790
- }
30791
- try {
30792
- s.message("Updating submodules...");
30793
- await updateSubmodules(process.cwd());
30794
- } catch (updateErr) {
30795
- const msg = updateErr instanceof Error ? updateErr.message : String(updateErr);
30796
- s.message(`Warning: submodule update failed: ${msg}`);
30797
- }
30798
- const succeeded = results.filter((r) => r.cloned).length;
30799
- const failed = results.filter((r) => !r.cloned).length;
30800
- if (failed > 0) {
30801
- s.stop(`Clone complete: ${succeeded} succeeded, ${failed} failed`);
30802
- process.exitCode = 1 /* ERROR */;
30803
- } else {
30804
- s.stop(`All ${succeeded} worker(s) cloned successfully`);
30805
- }
30806
- if (!fmt.quiet) {
30807
- const rows = results.map((r) => ({
30808
- Worker: r.worker,
30809
- Status: r.cloned ? "cloned" : "failed",
30810
- Repo: r.repo ?? "-"
30811
- }));
30812
- formatTable(rows, { json: fmt.json, quiet: false });
30813
- }
30924
+ const rows = statuses.map((s) => ({
30925
+ Worker: s.worker,
30926
+ Status: s.cloned ? "cloned" : "not cloned",
30927
+ Repo: s.repo ?? "-"
30928
+ }));
30929
+ formatTable(rows, fmt);
30930
+ return;
30931
+ }
30932
+ if (options.all) {
30933
+ if (workers.length === 0) {
30934
+ formatSuccess("No workers defined in wrangler.jsonc", fmt);
30814
30935
  return;
30815
30936
  }
30816
- if (name) {
30817
- const workerConfig = configService.getWorker(name);
30818
- if (!workerConfig) {
30819
- formatError2(new CLIError(`Worker "${name}" not found in wrangler.jsonc`, 1 /* ERROR */), fmt);
30820
- process.exitCode = 1 /* ERROR */;
30821
- return;
30822
- }
30937
+ const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
30938
+ const s = vt();
30939
+ s.start(`Cloning ${workers.length} worker(s)...`);
30940
+ const results = [];
30941
+ for (let i = 0;i < workers.length; i++) {
30942
+ const name2 = workers[i];
30943
+ const workerConfig = configService.getWorker(name2);
30944
+ if (!workerConfig)
30945
+ continue;
30823
30946
  const alreadyCloned = await isWorkerCloned(workerConfig.path);
30824
30947
  if (alreadyCloned) {
30825
- formatSuccess(`Worker "${name}" is already cloned at ${workerConfig.path}`, fmt);
30826
- return;
30948
+ s.message(`[${i + 1}/${workers.length}] ${icons.info} ${name2} already cloned \u2014 skipping`);
30949
+ results.push({
30950
+ worker: name2,
30951
+ path: workerConfig.path,
30952
+ cloned: true,
30953
+ repo: getRepoUrl(repoBase, name2)
30954
+ });
30955
+ continue;
30827
30956
  }
30828
- const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
30829
- const s = vt();
30830
- s.start(`Cloning ${name}...`);
30831
- const result = await cloneWorker(getRepoUrl(repoBase, name), workerConfig.path, name, process.cwd());
30957
+ s.message(`[${i + 1}/${workers.length}] Cloning ${name2}...`);
30958
+ const result = await cloneWorker(getRepoUrl(repoBase, name2), workerConfig.path, name2, process.cwd());
30959
+ results.push(result);
30832
30960
  if (result.cloned) {
30833
- try {
30834
- s.message("Updating submodules...");
30835
- await updateSubmodules(process.cwd());
30836
- } catch {}
30837
- s.stop(`Successfully cloned ${name}`);
30838
- formatSuccess(`Cloned ${name} from ${result.repo}`, fmt);
30961
+ s.message(`[${i + 1}/${workers.length}] ${icons.success} ${name2} cloned`);
30839
30962
  } else {
30840
- s.stop(`Failed to clone ${name}`);
30841
- formatError2(new CLIError(`Failed to clone "${name}": ${result.error}`, 1 /* ERROR */), fmt);
30842
- process.exitCode = 1 /* ERROR */;
30963
+ s.message(`[${i + 1}/${workers.length}] ${icons.error} ${name2} failed: ${result.error}`);
30843
30964
  }
30965
+ }
30966
+ try {
30967
+ s.message("Updating submodules...");
30968
+ await updateSubmodules(process.cwd());
30969
+ } catch (updateErr) {
30970
+ const msg = updateErr instanceof Error ? updateErr.message : String(updateErr);
30971
+ s.message(`Warning: submodule update failed: ${msg}`);
30972
+ }
30973
+ const succeeded = results.filter((r) => r.cloned).length;
30974
+ const failed = results.filter((r) => !r.cloned).length;
30975
+ if (failed > 0) {
30976
+ s.stop(`Clone complete: ${succeeded} succeeded, ${failed} failed`);
30977
+ process.exitCode = 1 /* ERROR */;
30978
+ } else {
30979
+ s.stop(`All ${succeeded} worker(s) cloned successfully`);
30980
+ }
30981
+ if (!fmt.quiet) {
30982
+ const rows = results.map((r) => ({
30983
+ Worker: r.worker,
30984
+ Status: r.cloned ? "cloned" : "failed",
30985
+ Repo: r.repo ?? "-"
30986
+ }));
30987
+ formatTable(rows, { json: fmt.json, quiet: false });
30988
+ }
30989
+ return;
30990
+ }
30991
+ if (name) {
30992
+ const workerConfig = configService.getWorker(name);
30993
+ if (!workerConfig) {
30994
+ formatError2(new CLIError(`Worker "${name}" not found in wrangler.jsonc`, 1 /* ERROR */), fmt);
30995
+ process.exitCode = 1 /* ERROR */;
30844
30996
  return;
30845
30997
  }
30846
- } catch (err) {
30847
- const message = err instanceof Error ? err.message : String(err);
30848
- formatError2(message, fmt);
30849
- process.exitCode = 1 /* ERROR */;
30998
+ const alreadyCloned = await isWorkerCloned(workerConfig.path);
30999
+ if (alreadyCloned) {
31000
+ formatSuccess(`Worker "${name}" is already cloned at ${workerConfig.path}`, fmt);
31001
+ return;
31002
+ }
31003
+ const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
31004
+ const s = vt();
31005
+ s.start(`Cloning ${name}...`);
31006
+ const result = await cloneWorker(getRepoUrl(repoBase, name), workerConfig.path, name, process.cwd());
31007
+ if (result.cloned) {
31008
+ try {
31009
+ s.message("Updating submodules...");
31010
+ await updateSubmodules(process.cwd());
31011
+ } catch {}
31012
+ s.stop(`Successfully cloned ${name}`);
31013
+ formatSuccess(`Cloned ${name} from ${result.repo}`, fmt);
31014
+ } else {
31015
+ s.stop(`Failed to clone ${name}`);
31016
+ formatError2(new CLIError(`Failed to clone "${name}": ${result.error}`, 1 /* ERROR */), fmt);
31017
+ process.exitCode = 1 /* ERROR */;
31018
+ }
31019
+ return;
30850
31020
  }
30851
- });
31021
+ }, { service: "clone" }));
30852
31022
  }
30853
31023
  // src/commands/dashboard/dashboard-command.ts
30854
31024
  init_config2();
@@ -30892,8 +31062,9 @@ function updateWranglerVars(filePath, urls, dryRun, opts) {
30892
31062
  Old: c.oldValue,
30893
31063
  New: theme.success(c.newValue)
30894
31064
  }));
30895
- console.log(theme.heading(`
30896
- Service URL changes:`));
31065
+ process.stdout.write(theme.heading(`
31066
+ Service URL changes:`) + `
31067
+ `);
30897
31068
  formatTable(rows, opts);
30898
31069
  }
30899
31070
  if (dryRun) {
@@ -30911,6 +31082,7 @@ Service URL changes:`));
30911
31082
  function registerDashboardCommand(program2) {
30912
31083
  const dashboardCmd = program2.command("dashboard").description("Dashboard-specific operations");
30913
31084
  dashboardCmd.command("update-urls").description("Update dashboard wrangler.jsonc with current service URLs").option("--dry-run", "Show changes without applying").action(withErrorHandling(async (options) => {
31085
+ process.stdout.write(theme.warning("! This command is deprecated. Use `hoox deploy update-internal-urls` instead.\n"));
30914
31086
  const opts = {
30915
31087
  json: program2.opts().json,
30916
31088
  quiet: program2.opts().quiet
@@ -31032,20 +31204,36 @@ async function resolveDb(cmd, svc) {
31032
31204
  }
31033
31205
  async function handleApply(opts, dbName, remote, file2) {
31034
31206
  const svc = new DbService;
31035
- const output = await svc.apply(dbName, remote, file2);
31036
- formatSuccess(`Schema applied to ${dbName}${remote ? " (remote)" : " (local)"}`, opts);
31037
- if (!opts.quiet && output) {
31038
- process.stdout.write(`${output}
31207
+ const s = vt();
31208
+ s.start("Applying schema...");
31209
+ try {
31210
+ const output = await svc.apply(dbName, remote, file2);
31211
+ s.stop("Schema applied");
31212
+ formatSuccess(`Schema applied to ${dbName}${remote ? " (remote)" : " (local)"}`, opts);
31213
+ if (!opts.quiet && output) {
31214
+ process.stdout.write(`${output}
31039
31215
  `);
31216
+ }
31217
+ } catch (err) {
31218
+ s.stop("Schema apply failed");
31219
+ throw err;
31040
31220
  }
31041
31221
  }
31042
31222
  async function handleMigrate(opts, dbName, remote) {
31043
31223
  const svc = new DbService;
31044
- const output = await svc.migrate(dbName, remote);
31045
- formatSuccess(`Migrations applied to ${dbName}${remote ? " (remote)" : " (local)"}`, opts);
31046
- if (!opts.quiet && output) {
31047
- process.stdout.write(`${output}
31224
+ const s = vt();
31225
+ s.start("Running migrations...");
31226
+ try {
31227
+ const output = await svc.migrate(dbName, remote);
31228
+ s.stop("Migrations complete");
31229
+ formatSuccess(`Migrations applied to ${dbName}${remote ? " (remote)" : " (local)"}`, opts);
31230
+ if (!opts.quiet && output) {
31231
+ process.stdout.write(`${output}
31048
31232
  `);
31233
+ }
31234
+ } catch (err) {
31235
+ s.stop("Migrations failed");
31236
+ throw err;
31049
31237
  }
31050
31238
  }
31051
31239
  async function handleList2(opts, dbName, remote) {
@@ -31081,8 +31269,16 @@ async function handleQuery(opts, dbName, sql, remote) {
31081
31269
  }
31082
31270
  async function handleExport(opts, dbName, outputPath) {
31083
31271
  const svc = new DbService;
31084
- const path4 = await svc.export(dbName, outputPath);
31085
- formatSuccess(`Database exported to ${path4}`, opts);
31272
+ const s = vt();
31273
+ s.start("Exporting database...");
31274
+ try {
31275
+ const path4 = await svc.export(dbName, outputPath);
31276
+ s.stop("Database exported");
31277
+ formatSuccess(`Database exported to ${path4}`, opts);
31278
+ } catch (err) {
31279
+ s.stop("Export failed");
31280
+ throw err;
31281
+ }
31086
31282
  }
31087
31283
  async function handleReset(opts, dbName, confirmed) {
31088
31284
  if (!confirmed) {
@@ -31096,11 +31292,19 @@ async function handleReset(opts, dbName, confirmed) {
31096
31292
  }
31097
31293
  }
31098
31294
  const svc = new DbService;
31099
- const output = await svc.reset(dbName);
31100
- formatSuccess(`Database "${dbName}" has been recreated`, opts);
31101
- if (!opts.quiet && output) {
31102
- process.stdout.write(`${output}
31295
+ const s = vt();
31296
+ s.start("Resetting database...");
31297
+ try {
31298
+ const output = await svc.reset(dbName);
31299
+ s.stop("Database reset");
31300
+ formatSuccess(`Database "${dbName}" has been recreated`, opts);
31301
+ if (!opts.quiet && output) {
31302
+ process.stdout.write(`${output}
31103
31303
  `);
31304
+ }
31305
+ } catch (err) {
31306
+ s.stop("Reset failed");
31307
+ throw err;
31104
31308
  }
31105
31309
  }
31106
31310
  function registerDbCommand(program2) {
@@ -31266,7 +31470,10 @@ async function doMonitorLogs(workerName, fmt) {
31266
31470
  const dbName = await db.resolveDbName();
31267
31471
  let sql;
31268
31472
  if (workerName) {
31269
- sql = `SELECT * FROM system_logs WHERE worker = '${workerName.replace(/'/g, "''")}' ORDER BY timestamp DESC LIMIT 20`;
31473
+ if (!/^[a-zA-Z0-9_-]+$/.test(workerName)) {
31474
+ throw new Error(`Invalid worker name: "${workerName}"`);
31475
+ }
31476
+ sql = `SELECT * FROM system_logs WHERE worker = '${workerName}' ORDER BY timestamp DESC LIMIT 20`;
31270
31477
  } else {
31271
31478
  sql = "SELECT * FROM system_logs ORDER BY timestamp DESC LIMIT 20";
31272
31479
  }
@@ -31317,6 +31524,66 @@ async function doMonitorQueueDepth(fmt) {
31317
31524
  process.exitCode = 1 /* ERROR */;
31318
31525
  }
31319
31526
  }
31527
+ async function doMonitorAnalyticsSummary(fmt) {
31528
+ try {
31529
+ const db = new DbService;
31530
+ const dbName = await db.resolveDbName();
31531
+ const sql = "SELECT COUNT(*) as total_events, MIN(timestamp) as earliest, MAX(timestamp) as latest FROM system_logs";
31532
+ const output = await db.query(dbName, sql, true);
31533
+ if (fmt.json) {
31534
+ process.stdout.write(output + `
31535
+ `);
31536
+ return;
31537
+ }
31538
+ const parsed = JSON.parse(output);
31539
+ const results = parsed[0]?.results;
31540
+ if (results && results.length > 0) {
31541
+ const row = results[0];
31542
+ const rows = [
31543
+ {
31544
+ "Total Events": String(row.total_events ?? "-"),
31545
+ Earliest: String(row.earliest ?? "-"),
31546
+ Latest: String(row.latest ?? "-")
31547
+ }
31548
+ ];
31549
+ formatTable(rows, fmt);
31550
+ } else {
31551
+ process.stdout.write(`No analytics data found.
31552
+ `);
31553
+ }
31554
+ } catch (err) {
31555
+ formatError2(err instanceof Error ? err.message : String(err), fmt);
31556
+ process.exitCode = 1 /* ERROR */;
31557
+ }
31558
+ }
31559
+ async function doMonitorAnalyticsErrors(hours, fmt) {
31560
+ try {
31561
+ const db = new DbService;
31562
+ const dbName = await db.resolveDbName();
31563
+ const sql = `SELECT level, COUNT(*) as count FROM system_logs WHERE level IN ('error', 'warn') AND timestamp > unixepoch('now', '-${hours} hours') GROUP BY level`;
31564
+ const output = await db.query(dbName, sql, true);
31565
+ if (fmt.json) {
31566
+ process.stdout.write(output + `
31567
+ `);
31568
+ return;
31569
+ }
31570
+ const parsed = JSON.parse(output);
31571
+ const results = parsed[0]?.results;
31572
+ if (results && results.length > 0) {
31573
+ const rows = results.map((r) => ({
31574
+ Level: String(r.level ?? "-"),
31575
+ Count: String(r.count ?? "0")
31576
+ }));
31577
+ formatTable(rows, fmt);
31578
+ } else {
31579
+ process.stdout.write(`No errors or warnings in the last ${hours} hours.
31580
+ `);
31581
+ }
31582
+ } catch (err) {
31583
+ formatError2(err instanceof Error ? err.message : String(err), fmt);
31584
+ process.exitCode = 1 /* ERROR */;
31585
+ }
31586
+ }
31320
31587
  async function doMonitorBackup(fmt) {
31321
31588
  try {
31322
31589
  const db = new DbService;
@@ -31338,54 +31605,177 @@ SUBCOMMANDS:
31338
31605
  kill-switch Emergency stop/resume trading
31339
31606
  queue-depth List queues and pending messages
31340
31607
  backup Export D1 database to timestamped .sql file
31608
+ analytics Query analytics data from D1
31341
31609
 
31342
31610
  EXAMPLES:
31343
- hoox monitor status Check all workers health
31344
- hoox monitor trades 20 Show 20 recent trades
31345
- hoox monitor logs hoox Show logs for hoox worker
31346
- hoox monitor kill-switch show Check kill switch status
31347
- hoox monitor kill-switch on Halt all trading
31348
- hoox monitor kill-switch off Resume trading
31349
- hoox monitor queue-depth Show queue info
31350
- hoox monitor backup Export D1 database`);
31351
- monitorCmd.command("status").summary("Check health of all workers").description("Probe each worker's /health endpoint and report status.").action(async function() {
31352
- const fmt = getFormatOptions(this);
31611
+ hoox monitor status Check all workers health
31612
+ hoox monitor trades 20 Show 20 recent trades
31613
+ hoox monitor logs hoox Show logs for hoox worker
31614
+ hoox monitor kill-switch show Check kill switch status
31615
+ hoox monitor kill-switch on Halt all trading
31616
+ hoox monitor kill-switch off Resume trading
31617
+ hoox monitor queue-depth Show queue info
31618
+ hoox monitor backup Export D1 database
31619
+ hoox monitor analytics summary Show event rollup statistics
31620
+ hoox monitor analytics errors Show error/warning counts`);
31621
+ monitorCmd.command("status").summary("Check health of all workers").description("Probe each worker's /health endpoint and report status.").action(withErrorHandling(async (_, cmd) => {
31622
+ const fmt = getFormatOptions(cmd);
31353
31623
  await doMonitorStatus(fmt);
31354
- });
31355
- monitorCmd.command("trades").summary("Show recent trades from D1").description("Query the trades table for the most recent entries.").argument("[limit]", "Number of trades to show (default: 10, max: 100)", "10").action(async function(limit) {
31356
- const fmt = getFormatOptions(this);
31624
+ }, { service: "monitor" }));
31625
+ monitorCmd.command("trades").summary("Show recent trades from D1").description("Query the trades table for the most recent entries.").argument("[limit]", "Number of trades to show (default: 10, max: 100)", "10").action(withErrorHandling(async (limit, _, cmd) => {
31626
+ const fmt = getFormatOptions(cmd);
31357
31627
  await doMonitorTrades(parseInt(limit, 10) || 10, fmt);
31358
- });
31359
- monitorCmd.command("logs").summary("Show recent system logs from D1").description("Query the system_logs table. Optionally filter by worker name.").argument("[worker]", "Worker name to filter (optional)").action(async function(worker) {
31360
- const fmt = getFormatOptions(this);
31628
+ }, { service: "monitor" }));
31629
+ monitorCmd.command("logs").summary("Show recent system logs from D1").description("Query the system_logs table. Optionally filter by worker name.").argument("[worker]", "Worker name to filter (optional)").action(withErrorHandling(async (worker, _, cmd) => {
31630
+ const fmt = getFormatOptions(cmd);
31361
31631
  await doMonitorLogs(worker, fmt);
31362
- });
31632
+ }, { service: "monitor" }));
31363
31633
  const ksCmd = monitorCmd.command("kill-switch").summary("Emergency stop or resume trading").description(`Control the trade:kill_switch KV key.
31364
31634
 
31365
31635
  Commands:
31366
31636
  show Display current kill switch status
31367
31637
  on Halt all trading (set kill_switch=true)
31368
31638
  off Resume trading (set kill_switch=false)`);
31369
- ksCmd.command("show").summary("Show kill switch status").description("Display whether trading is halted or active.").action(async function() {
31370
- const fmt = getFormatOptions(this);
31639
+ ksCmd.command("show").summary("Show kill switch status").description("Display whether trading is halted or active.").action(withErrorHandling(async (_, cmd) => {
31640
+ const fmt = getFormatOptions(cmd);
31371
31641
  await doMonitorKillSwitch("show", fmt);
31372
- });
31373
- ksCmd.command("on").summary("Halt all trading").description("Set trade:kill_switch=true to stop all trading operations. WARNING: This halts ALL trading activity immediately.").action(async function() {
31374
- const fmt = getFormatOptions(this);
31642
+ }, { service: "monitor" }));
31643
+ ksCmd.command("on").summary("Halt all trading").description("Set trade:kill_switch=true to stop all trading operations. WARNING: This halts ALL trading activity immediately.").action(withErrorHandling(async (_, cmd) => {
31644
+ const fmt = getFormatOptions(cmd);
31375
31645
  await doMonitorKillSwitch("on", fmt);
31376
- });
31377
- ksCmd.command("off").summary("Resume trading").description("Set trade:kill_switch=false to resume normal trading operations.").action(async function() {
31378
- const fmt = getFormatOptions(this);
31646
+ }, { service: "monitor" }));
31647
+ ksCmd.command("off").summary("Resume trading").description("Set trade:kill_switch=false to resume normal trading operations.").action(withErrorHandling(async (_, cmd) => {
31648
+ const fmt = getFormatOptions(cmd);
31379
31649
  await doMonitorKillSwitch("off", fmt);
31380
- });
31381
- monitorCmd.command("queue-depth").summary("Show queue details").description("List queues via wrangler queues list to show configured queues.").action(async function() {
31382
- const fmt = getFormatOptions(this);
31650
+ }, { service: "monitor" }));
31651
+ monitorCmd.command("queue-depth").summary("Show queue details").description("List queues via wrangler queues list to show configured queues.").action(withErrorHandling(async (_, cmd) => {
31652
+ const fmt = getFormatOptions(cmd);
31383
31653
  await doMonitorQueueDepth(fmt);
31384
- });
31385
- monitorCmd.command("backup").summary("Export D1 database to .sql file").description("Export the D1 database to a timestamped .sql backup file via wrangler d1 export.").action(async function() {
31386
- const fmt = getFormatOptions(this);
31654
+ }, { service: "monitor" }));
31655
+ monitorCmd.command("backup").summary("Export D1 database to .sql file").description("Export the D1 database to a timestamped .sql backup file via wrangler d1 export.").action(withErrorHandling(async (_, cmd) => {
31656
+ const fmt = getFormatOptions(cmd);
31387
31657
  await doMonitorBackup(fmt);
31658
+ }, { service: "monitor" }));
31659
+ const analyticsCmd = monitorCmd.command("analytics").summary("Query analytics data from D1").description(`Run analytical queries against the system_logs table.
31660
+
31661
+ SUBCOMMANDS:
31662
+ summary Show rollup statistics of system events
31663
+ errors Show error/warning counts by level`);
31664
+ analyticsCmd.command("summary").summary("Show event rollup statistics").description("Query system_logs for total events, earliest and latest timestamps.").action(withErrorHandling(async (_, cmd) => {
31665
+ const fmt = getFormatOptions(cmd);
31666
+ await doMonitorAnalyticsSummary(fmt);
31667
+ }, { service: "monitor" }));
31668
+ analyticsCmd.command("errors").summary("Show error and warning counts by level").description("Query system_logs for error/warning counts grouped by level, filtered by hours.").option("--hours <n>", "Hours to look back (default: 24)", "24").action(withErrorHandling(async (options, cmd) => {
31669
+ const fmt = getFormatOptions(cmd);
31670
+ const hours = parseInt(options.hours ?? "24", 10) || 24;
31671
+ await doMonitorAnalyticsErrors(hours, fmt);
31672
+ }, { service: "monitor" }));
31673
+ }
31674
+ // src/commands/workers/workers-command.ts
31675
+ init_config2();
31676
+ async function doListWorkers(fmt) {
31677
+ const configService = new ConfigService;
31678
+ await configService.load();
31679
+ const workers = configService.listWorkers();
31680
+ const rows = workers.map((name) => {
31681
+ const worker = configService.getWorker(name);
31682
+ return {
31683
+ Worker: name,
31684
+ Status: worker?.enabled ? "enabled" : "disabled",
31685
+ Path: worker?.path ?? "-",
31686
+ Secrets: String(worker?.secrets?.length ?? 0)
31687
+ };
31388
31688
  });
31689
+ if (fmt.json) {
31690
+ formatJson(rows, fmt);
31691
+ return;
31692
+ }
31693
+ formatTable(rows, fmt);
31694
+ }
31695
+ function registerWorkersCommand(program2) {
31696
+ const workersCmd = program2.command("workers").summary("Manage and monitor Cloudflare Workers").description(`Manage and monitor your Hoox Cloudflare Workers.
31697
+
31698
+ SUBCOMMANDS:
31699
+ list List all workers with status, path, and secrets count
31700
+ status Check health status of all workers (delegates to monitor status)
31701
+ dev <name> Start a worker for local development (delegates to dev worker)
31702
+ logs <name> Tail logs for a specific worker (delegates to logs worker)
31703
+
31704
+ EXAMPLES:
31705
+ hoox workers list List all workers
31706
+ hoox workers list --json List workers as JSON (respects global --json)
31707
+ hoox workers status Check health of all workers
31708
+ hoox workers dev trade-worker Start dev server for trade-worker
31709
+ hoox workers logs hoox Tail logs for the hoox gateway`);
31710
+ workersCmd.command("list").summary("List all workers with their enabled/disabled status").description(`List all workers defined in wrangler.jsonc.
31711
+
31712
+ Displays each worker's:
31713
+ - Name
31714
+ - Status (enabled / disabled)
31715
+ - Path (relative from project root)
31716
+ - Secrets (count of Cloudflare secrets configured)
31717
+
31718
+ EXAMPLES:
31719
+ hoox workers list Default table output
31720
+ hoox workers list --json JSON-formatted output
31721
+ hoox workers list --quiet Minimal output`).action(withErrorHandling(async (_opts, cmd) => {
31722
+ const fmt = getFormatOptions(cmd);
31723
+ await doListWorkers(fmt);
31724
+ }, { service: "workers" }));
31725
+ workersCmd.command("status").summary("Check health status of all workers").description(`Check the health of all workers by delegating to \`hoox monitor status\`.
31726
+
31727
+ This command spawns \`hoox monitor status\` with inherited stdio, passing
31728
+ all output directly to the terminal.
31729
+
31730
+ EXAMPLES:
31731
+ hoox workers status Check all worker health
31732
+ hoox workers status --json JSON output (via delegated command)`).action(withErrorHandling(async () => {
31733
+ const proc = Bun.spawn(["hoox", "monitor", "status"], {
31734
+ stdio: ["inherit", "inherit", "inherit"]
31735
+ });
31736
+ const exitCode = await proc.exited;
31737
+ if (exitCode !== 0) {
31738
+ process.exitCode = exitCode;
31739
+ }
31740
+ }, { service: "workers" }));
31741
+ workersCmd.command("dev <name>").summary("Start a single worker for local development").description(`Start a specific worker for local development by delegating to \`hoox dev worker <name>\`.
31742
+
31743
+ This command spawns \`hoox dev worker <name>\` with inherited stdio,
31744
+ so all wrangler dev output and prompts appear directly in the terminal.
31745
+
31746
+ ARGUMENTS:
31747
+ name Worker name (e.g., trade-worker, agent-worker, hoox)
31748
+
31749
+ EXAMPLES:
31750
+ hoox workers dev trade-worker Start dev server for trade-worker
31751
+ hoox workers dev hoox Start dev server for the hoox gateway`).action(withErrorHandling(async (name) => {
31752
+ const proc = Bun.spawn(["hoox", "dev", "worker", name], {
31753
+ stdio: ["inherit", "inherit", "inherit"]
31754
+ });
31755
+ const exitCode = await proc.exited;
31756
+ if (exitCode !== 0) {
31757
+ process.exitCode = exitCode;
31758
+ }
31759
+ }, { service: "workers" }));
31760
+ workersCmd.command("logs <name>").summary("Tail logs for a specific worker").description(`Tail logs for a specific worker by delegating to \`hoox logs worker <name>\`.
31761
+
31762
+ This command spawns \`hoox logs worker <name>\` with inherited stdio,
31763
+ preserving all formatting and log-level filtering from the logs command.
31764
+
31765
+ ARGUMENTS:
31766
+ name Worker name (e.g., trade-worker, agent-worker, hoox)
31767
+
31768
+ EXAMPLES:
31769
+ hoox workers logs hoox Tail logs for the hoox gateway
31770
+ hoox workers logs trade-worker Tail logs for trade-worker`).action(withErrorHandling(async (name) => {
31771
+ const proc = Bun.spawn(["hoox", "logs", "worker", name], {
31772
+ stdio: ["inherit", "inherit", "inherit"]
31773
+ });
31774
+ const exitCode = await proc.exited;
31775
+ if (exitCode !== 0) {
31776
+ process.exitCode = exitCode;
31777
+ }
31778
+ }, { service: "workers" }));
31389
31779
  }
31390
31780
  // src/commands/repair/repair-service.ts
31391
31781
  class RepairService {
@@ -31623,7 +32013,12 @@ async function handleDb(fmt) {
31623
32013
  async function handleRebuild(fmt) {
31624
32014
  try {
31625
32015
  const repair = new RepairService;
31626
- await repair.runSystemCheck();
32016
+ const result = await repair.runSystemCheck();
32017
+ if (!result.allPassed) {
32018
+ formatError2(new CLIError(`${result.failedCount} check(s) failed \u2014 aborting rebuild`, 1 /* ERROR */), fmt);
32019
+ process.exitCode = 1 /* ERROR */;
32020
+ return;
32021
+ }
31627
32022
  const cf = new CloudflareService;
31628
32023
  const { ConfigService: ConfigService2 } = await Promise.resolve().then(() => (init_config2(), exports_config));
31629
32024
  const config2 = new ConfigService2;
@@ -31642,34 +32037,34 @@ async function handleRebuild(fmt) {
31642
32037
  }
31643
32038
  function registerRepairCommand(program2) {
31644
32039
  const repairCmd = program2.command("repair").summary("Diagnose and repair the Hoox system").description("Run checks, deploy workers, or fix infrastructure, secrets, KV, and DB issues.");
31645
- repairCmd.command("check").description("Run system diagnostics (workers, deps, types, infra, secrets)").action(async () => {
32040
+ repairCmd.command("check").description("Run system diagnostics (workers, deps, types, infra, secrets)").action(withErrorHandling(async () => {
31646
32041
  const fmt = getFormatOptions(repairCmd);
31647
32042
  await handleCheck(fmt);
31648
- });
31649
- repairCmd.command("worker <name>").description("Deploy a specific worker to fix it").action(async (name) => {
32043
+ }, { service: "repair" }));
32044
+ repairCmd.command("worker <name>").description("Deploy a specific worker to fix it").action(withErrorHandling(async (name) => {
31650
32045
  const fmt = getFormatOptions(repairCmd);
31651
32046
  await handleWorker(name, fmt);
31652
- });
31653
- repairCmd.command("infra").description("Provision missing infrastructure (D1, KV, R2, Queues)").action(async () => {
32047
+ }, { service: "repair" }));
32048
+ repairCmd.command("infra").description("Provision missing infrastructure (D1, KV, R2, Queues)").action(withErrorHandling(async () => {
31654
32049
  const fmt = getFormatOptions(repairCmd);
31655
32050
  await handleInfra(fmt);
31656
- });
31657
- repairCmd.command("secrets").description("Upload missing secrets to Cloudflare").action(async () => {
32051
+ }, { service: "repair" }));
32052
+ repairCmd.command("secrets").description("Upload missing secrets to Cloudflare").action(withErrorHandling(async () => {
31658
32053
  const fmt = getFormatOptions(repairCmd);
31659
32054
  await handleSecrets(fmt);
31660
- });
31661
- repairCmd.command("kv").description("Re-sync KV namespace entries").action(async () => {
32055
+ }, { service: "repair" }));
32056
+ repairCmd.command("kv").description("Re-sync KV namespace entries").action(withErrorHandling(async () => {
31662
32057
  const fmt = getFormatOptions(repairCmd);
31663
32058
  await handleKv(fmt);
31664
- });
31665
- repairCmd.command("db").description("Re-apply database schema").action(async () => {
32059
+ }, { service: "repair" }));
32060
+ repairCmd.command("db").description("Re-apply database schema").action(withErrorHandling(async () => {
31666
32061
  const fmt = getFormatOptions(repairCmd);
31667
32062
  await handleDb(fmt);
31668
- });
31669
- repairCmd.command("rebuild").description("Full rebuild: check, deploy all workers, fix infra/secrets/db").action(async () => {
32063
+ }, { service: "repair" }));
32064
+ repairCmd.command("rebuild").description("Full rebuild: check, deploy all workers, fix infra/secrets/db").action(withErrorHandling(async () => {
31670
32065
  const fmt = getFormatOptions(repairCmd);
31671
32066
  await handleRebuild(fmt);
31672
- });
32067
+ }, { service: "repair" }));
31673
32068
  }
31674
32069
  // src/commands/update/update-command.ts
31675
32070
  init_config2();
@@ -31799,49 +32194,41 @@ With "wrangler", updates wrangler to the latest version.
31799
32194
  EXAMPLES:
31800
32195
  hoox update Pull latest for main repo
31801
32196
  hoox update d1-worker Update the d1-worker submodule
31802
- hoox update wrangler Update wrangler to latest version`).argument("[target]", 'What to update: worker name (e.g. d1-worker) or "wrangler"').action(async (target) => {
31803
- const fmt = getFormatOptions(program2);
32197
+ hoox update wrangler Update wrangler to latest version`).argument("[target]", 'What to update: worker name (e.g. d1-worker) or "wrangler"').action(withErrorHandling(async (target) => {
31804
32198
  const cwd = process.cwd();
31805
- try {
31806
- if (target === "wrangler") {
31807
- const service = new UpdateService;
31808
- const result = await service.updateWrangler();
31809
- if (result.error) {
31810
- formatError2(new CLIError(`Update failed: ${result.error}`, 1 /* ERROR */), fmt);
31811
- process.exitCode = 1 /* ERROR */;
31812
- }
31813
- return;
32199
+ if (target === "wrangler") {
32200
+ const service = new UpdateService;
32201
+ const result = await service.updateWrangler();
32202
+ if (result.error) {
32203
+ throw new CLIError(`Update failed: ${result.error}`, 1 /* ERROR */);
31814
32204
  }
31815
- if (!await isGitRepo(cwd)) {
31816
- throw new CLIError("Not a git repository \u2014 run this command from the project root", 2 /* INVALID_USAGE */);
32205
+ return;
32206
+ }
32207
+ if (!await isGitRepo(cwd)) {
32208
+ throw new CLIError("Not a git repository \u2014 run this command from the project root", 2 /* INVALID_USAGE */);
32209
+ }
32210
+ if (target) {
32211
+ const configService = new ConfigService;
32212
+ await configService.load();
32213
+ const worker = configService.getWorker(target);
32214
+ if (!worker) {
32215
+ throw new CLIError(`Unknown worker "${target}" \u2014 not found in wrangler.jsonc`, 2 /* INVALID_USAGE */);
31817
32216
  }
31818
- if (target) {
31819
- const configService = new ConfigService;
31820
- await configService.load();
31821
- const worker = configService.getWorker(target);
31822
- if (!worker) {
31823
- throw new CLIError(`Unknown worker "${target}" \u2014 not found in wrangler.jsonc`, 2 /* INVALID_USAGE */);
31824
- }
31825
- const submodulePath = worker.path;
31826
- if (!await isSubmodule(cwd, submodulePath)) {
31827
- throw new CLIError(`"${submodulePath}" is not a git submodule \u2014 nothing to update`, 2 /* INVALID_USAGE */);
31828
- }
31829
- const s = vt();
31830
- s.start(`Updating ${target}...`);
31831
- const output = await gitSubmoduleUpdate(cwd, submodulePath);
31832
- s.stop(output ? theme.success(`Updated ${target}`) : theme.muted(`${target} already up to date`));
31833
- } else {
31834
- const s = vt();
31835
- s.start("Pulling latest from remote...");
31836
- const output = await gitPull(cwd);
31837
- s.stop(output ? theme.success("Repository up to date") : theme.muted("Already up to date"));
32217
+ const submodulePath = worker.path;
32218
+ if (!await isSubmodule(cwd, submodulePath)) {
32219
+ throw new CLIError(`"${submodulePath}" is not a git submodule \u2014 nothing to update`, 2 /* INVALID_USAGE */);
31838
32220
  }
31839
- } catch (err) {
31840
- const message = err instanceof Error ? err.message : String(err);
31841
- formatError2(message, fmt);
31842
- process.exitCode = 1 /* ERROR */;
32221
+ const s = vt();
32222
+ s.start(`Updating ${target}...`);
32223
+ const output = await gitSubmoduleUpdate(cwd, submodulePath);
32224
+ s.stop(output ? theme.success(`Updated ${target}`) : theme.muted(`${target} already up to date`));
32225
+ } else {
32226
+ const s = vt();
32227
+ s.start("Pulling latest from remote...");
32228
+ const output = await gitPull(cwd);
32229
+ s.stop(output ? theme.success("Repository up to date") : theme.muted("Already up to date"));
31843
32230
  }
31844
- });
32231
+ }, { service: "update" }));
31845
32232
  }
31846
32233
  // src/commands/schema/schema-command.ts
31847
32234
  init_schema_service();
@@ -32420,8 +32807,9 @@ async function runCommand(program2, commandStr) {
32420
32807
  }
32421
32808
  }
32422
32809
  // src/index.ts
32810
+ var pkgVersion = JSON.parse(readFileSync5(new URL("../package.json", import.meta.url), "utf-8")).version;
32423
32811
  var program2 = new Command;
32424
- program2.name("hoox").description("Hoox CLI \u2014 manage Cloudflare Workers, infrastructure, secrets, and deployments").version(`0.2.0
32812
+ program2.name("hoox").description("Hoox CLI \u2014 manage Cloudflare Workers, infrastructure, secrets, and deployments").version(`${pkgVersion}
32425
32813
 
32426
32814
  ${COPYRIGHT}`).addHelpText("beforeAll", theme.heading(`
32427
32815
  Hoox CLI \u2014 Cloudflare Workers Platform
@@ -32478,11 +32866,67 @@ registerCloneCommand(program2);
32478
32866
  registerDashboardCommand(program2);
32479
32867
  registerDbCommand(program2);
32480
32868
  registerMonitorCommand(program2);
32869
+ registerWorkersCommand(program2);
32481
32870
  registerRepairCommand(program2);
32482
32871
  registerSchemaCommand(program2);
32483
32872
  registerUpdateCommand(program2);
32484
32873
  registerTUICommand(program2);
32485
32874
  registerDisclaimerCommand(program2);
32875
+ program2.command("completion").description("Generate shell completion script").argument("[shell]", "Shell type (bash, zsh, fish)").action(async (shell) => {
32876
+ if (!shell) {
32877
+ process.stdout.write(`Usage: hoox completion <bash|zsh|fish>
32878
+ `);
32879
+ return;
32880
+ }
32881
+ const validShells = ["bash", "zsh", "fish"];
32882
+ if (!validShells.includes(shell)) {
32883
+ process.stderr.write(`Unsupported shell "${shell}". Use: ${validShells.join(", ")}
32884
+ `);
32885
+ process.exitCode = 1;
32886
+ return;
32887
+ }
32888
+ if (shell === "bash") {
32889
+ process.stdout.write(`_hoox_completion() {
32890
+ local cur prev opts
32891
+ COMPREPLY=()
32892
+ cur="\${COMP_WORDS[COMP_CWORD]}"
32893
+ opts="--help --version --json --quiet --yes init clone dev deploy infra config check db monitor repair logs test waf dashboard schema update tui disclaimer workers"
32894
+ COMPREPLY=( $(compgen -W "\${opts}" -- \${cur}) )
32895
+ return 0
32896
+ }
32897
+ complete -F _hoox_completion hoox
32898
+ `);
32899
+ } else if (shell === "zsh") {
32900
+ process.stdout.write(`#compdef hoox
32901
+ _hoox() {
32902
+ local -a opts
32903
+ opts=(
32904
+ '--help:Show help'
32905
+ '--version:Show version'
32906
+ '--json:JSON output'
32907
+ '--quiet:Minimal output'
32908
+ 'init:Interactive setup wizard'
32909
+ 'clone:Clone worker repositories'
32910
+ 'dev:Local development'
32911
+ 'deploy:Deploy to Cloudflare'
32912
+ 'infra:Manage infrastructure'
32913
+ 'config:Manage configuration'
32914
+ 'check:Validate and health-check'
32915
+ 'db:Database operations'
32916
+ 'monitor:Monitor system'
32917
+ 'repair:Repair system'
32918
+ 'logs:View worker logs'
32919
+ 'test:Run tests'
32920
+ 'waf:Manage Web Application Firewall'
32921
+ 'dashboard:Dashboard operations'
32922
+ 'workers:Worker operations'
32923
+ )
32924
+ _describe 'hoox' opts
32925
+ }
32926
+ compdef _hoox hoox
32927
+ `);
32928
+ }
32929
+ });
32486
32930
  var devCmd = program2.commands.find((c) => c.name() === "dev");
32487
32931
  if (devCmd) {
32488
32932
  const devStartCmd = devCmd.commands.find((c) => c.name() === "start");