@jango-blockchained/hoox-cli 0.7.0 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +42 -26
  2. package/dist/index.js +602 -203
  3. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -14792,22 +14792,57 @@ function toError(err, fallback = "Unknown error") {
14792
14792
  return fallback;
14793
14793
  }
14794
14794
  }
14795
+ function hasSensitiveFields(obj) {
14796
+ for (const key of Object.keys(obj)) {
14797
+ if (SENSITIVE_FIELDS.has(key))
14798
+ return true;
14799
+ }
14800
+ return false;
14801
+ }
14802
+ function hasErrorValues(obj) {
14803
+ for (const v of Object.values(obj)) {
14804
+ if (v instanceof Error)
14805
+ return true;
14806
+ }
14807
+ return false;
14808
+ }
14795
14809
  function sanitizeOutput(data) {
14810
+ if (data === null || data === undefined)
14811
+ return data;
14812
+ const t = typeof data;
14813
+ if (t === "string" || t === "number" || t === "boolean")
14814
+ return data;
14796
14815
  if (data instanceof Error) {
14797
14816
  return { name: data.name, message: data.message };
14798
14817
  }
14799
- if (data && typeof data === "object" && !Array.isArray(data)) {
14818
+ if (Array.isArray(data)) {
14819
+ return data.map(sanitizeOutput);
14820
+ }
14821
+ if (t === "object") {
14822
+ const obj = data;
14823
+ const keys = Object.keys(obj);
14824
+ if (keys.length < 10) {
14825
+ const sanitized2 = {};
14826
+ for (const k of keys) {
14827
+ if (k === "stack" || k === "cause")
14828
+ continue;
14829
+ const v = obj[k];
14830
+ sanitized2[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
14831
+ }
14832
+ return sanitized2;
14833
+ }
14834
+ if (!hasSensitiveFields(obj) && !hasErrorValues(obj)) {
14835
+ return obj;
14836
+ }
14800
14837
  const sanitized = {};
14801
- for (const [k, v] of Object.entries(data)) {
14838
+ for (const k of keys) {
14802
14839
  if (k === "stack" || k === "cause")
14803
14840
  continue;
14841
+ const v = obj[k];
14804
14842
  sanitized[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
14805
14843
  }
14806
14844
  return sanitized;
14807
14845
  }
14808
- if (Array.isArray(data)) {
14809
- return data.map(sanitizeOutput);
14810
- }
14811
14846
  return data;
14812
14847
  }
14813
14848
  function createJsonResponse(data, status = 200) {
@@ -14832,8 +14867,16 @@ function createErrorResponse(error51, status) {
14832
14867
  headers: { "Content-Type": "application/json" }
14833
14868
  });
14834
14869
  }
14835
- var Errors;
14870
+ var SENSITIVE_FIELDS, Errors;
14836
14871
  var init_errors3 = __esm(() => {
14872
+ SENSITIVE_FIELDS = new Set([
14873
+ "password",
14874
+ "token",
14875
+ "secret",
14876
+ "api_key",
14877
+ "apiKey",
14878
+ "authorization"
14879
+ ]);
14837
14880
  Errors = {
14838
14881
  badRequest: (message) => createErrorResponse({ message, status: 400, code: "BAD_REQUEST" }),
14839
14882
  unauthorized: (message = "Unauthorized") => createErrorResponse({ message, status: 401, code: "UNAUTHORIZED" }),
@@ -15324,13 +15367,15 @@ var init_d1 = __esm(() => {
15324
15367
 
15325
15368
  // ../shared/src/service-bindings.ts
15326
15369
  function serviceFetch(binding, path2, body, options) {
15370
+ const timeout = options?.timeout ?? 30000;
15327
15371
  return binding.fetch(`http://internal${path2}`, {
15328
15372
  method: options?.method ?? "POST",
15329
15373
  headers: {
15330
15374
  "Content-Type": "application/json",
15331
15375
  ...options?.headers
15332
15376
  },
15333
- body: body != null ? JSON.stringify(body) : undefined
15377
+ body: body != null ? JSON.stringify(body) : undefined,
15378
+ signal: AbortSignal.timeout(timeout)
15334
15379
  });
15335
15380
  }
15336
15381
 
@@ -21388,18 +21433,15 @@ class KvSyncService {
21388
21433
  return stdout2.trim() || null;
21389
21434
  }
21390
21435
  async set(namespaceId, key, value) {
21391
- const proc = Bun.spawn([
21392
- "wrangler",
21393
- "kv",
21394
- "key",
21395
- "put",
21396
- "--namespace-id",
21397
- namespaceId,
21398
- key,
21399
- value
21400
- ], { stdout: "pipe", stderr: "pipe" });
21436
+ const proc = Bun.spawn(["wrangler", "kv", "key", "put", "--namespace-id", namespaceId, key], { stdout: "pipe", stderr: "pipe", stdin: "pipe" });
21437
+ proc.stdin.write(value + `
21438
+ `);
21439
+ proc.stdin.end();
21440
+ const [, stderr] = await Promise.all([
21441
+ new Response(proc.stdout).text(),
21442
+ new Response(proc.stderr).text()
21443
+ ]);
21401
21444
  const exitCode = await proc.exited;
21402
- const stderr = await new Response(proc.stderr).text();
21403
21445
  if (exitCode !== 0) {
21404
21446
  throw new Error(`Failed to set key "${key}": ${stderr.trim() || `exit ${exitCode}`}`);
21405
21447
  }
@@ -21531,7 +21573,7 @@ var exports_schema_service = {};
21531
21573
  __export(exports_schema_service, {
21532
21574
  SchemaService: () => SchemaService
21533
21575
  });
21534
- import { readFileSync as readFileSync4 } from "fs";
21576
+ import { readFileSync as readFileSync4, existsSync as existsSync5 } from "fs";
21535
21577
  import { resolve as resolve5 } from "path";
21536
21578
 
21537
21579
  class SchemaService {
@@ -21565,34 +21607,41 @@ class SchemaService {
21565
21607
  const workerWranglerPath = resolve5(workersDir, name, "wrangler.jsonc");
21566
21608
  const devVarsPath = resolve5(workersDir, name, ".dev.vars");
21567
21609
  const errors4 = [];
21610
+ const isHooxProject = existsSync5(resolve5(this.projectRoot, "wrangler.jsonc")) || existsSync5(resolve5(this.projectRoot, "workers"));
21568
21611
  try {
21569
21612
  const wranglerContent = readFileSync4(workerWranglerPath, "utf-8");
21570
21613
  errors4.push(...validateWranglerJsonc(name, manifest, wranglerContent));
21571
21614
  } catch (e) {
21615
+ const isErrnoException = e instanceof Error && "code" in e;
21616
+ const isEnoent = isErrnoException && e.code === "ENOENT";
21617
+ const errMessage = e instanceof Error ? e.message : String(e);
21618
+ const message = isEnoent && !isHooxProject ? `Cannot read ${workerWranglerPath}: File not found. Are you in the Hoox project root directory?` : `Cannot read ${workerWranglerPath}: ${errMessage}`;
21572
21619
  errors4.push({
21573
21620
  worker: name,
21574
21621
  severity: "error",
21575
- message: `Cannot read ${workerWranglerPath}: ${e.message}`
21622
+ message
21576
21623
  });
21577
21624
  }
21578
21625
  try {
21579
21626
  const rootContent = readFileSync4(rootWranglerPath, "utf-8");
21580
21627
  errors4.push(...validateRootSecrets(name, manifest, rootContent));
21581
21628
  } catch (e) {
21629
+ const errMessage = e instanceof Error ? e.message : String(e);
21582
21630
  errors4.push({
21583
21631
  worker: name,
21584
21632
  severity: "warning",
21585
- message: `Cannot read root wrangler.jsonc: ${e.message}`
21633
+ message: `Cannot read root wrangler.jsonc: ${errMessage}`
21586
21634
  });
21587
21635
  }
21588
21636
  try {
21589
21637
  const devVarsContent = readFileSync4(devVarsPath, "utf-8");
21590
21638
  errors4.push(...validateDevVars(name, manifest, devVarsContent));
21591
21639
  } catch (e) {
21640
+ const errMessage = e instanceof Error ? e.message : String(e);
21592
21641
  errors4.push({
21593
21642
  worker: name,
21594
21643
  severity: "warning",
21595
- message: `Cannot read ${devVarsPath}: ${e.message}`
21644
+ message: `Cannot read ${devVarsPath}: ${errMessage}`
21596
21645
  });
21597
21646
  }
21598
21647
  return {
@@ -23674,11 +23723,13 @@ class CLIError extends Error {
23674
23723
  code;
23675
23724
  details;
23676
23725
  recoverable;
23677
- constructor(message, code = 1 /* ERROR */, details, recoverable = false) {
23726
+ hint;
23727
+ constructor(message, code = 1 /* ERROR */, details, recoverable = false, hint) {
23678
23728
  super(message);
23679
23729
  this.code = code;
23680
23730
  this.details = details;
23681
23731
  this.recoverable = recoverable;
23732
+ this.hint = hint;
23682
23733
  this.name = "CLIError";
23683
23734
  }
23684
23735
  }
@@ -23746,7 +23797,63 @@ function stripAnsi(str) {
23746
23797
  return ansis_default.strip(str);
23747
23798
  }
23748
23799
 
23800
+ // src/utils/timer.ts
23801
+ function formatDuration2(ms) {
23802
+ if (!Number.isFinite(ms) || ms < 0)
23803
+ return "0ms";
23804
+ if (ms < 1000)
23805
+ return `${Math.round(ms)}ms`;
23806
+ if (ms < 60000)
23807
+ return `${(ms / 1000).toFixed(1)}s`;
23808
+ if (ms < 3600000) {
23809
+ const m2 = Math.floor(ms / 60000);
23810
+ const s = Math.floor(ms % 60000 / 1000);
23811
+ return `${m2}m ${String(s).padStart(2, "0")}s`;
23812
+ }
23813
+ const h = Math.floor(ms / 3600000);
23814
+ const m = Math.floor(ms % 3600000 / 60000);
23815
+ return `${h}h ${String(m).padStart(2, "0")}m`;
23816
+ }
23817
+ function startTimer() {
23818
+ const startedAt = Date.now();
23819
+ return {
23820
+ ms: () => Date.now() - startedAt,
23821
+ format: () => formatDuration2(Date.now() - startedAt)
23822
+ };
23823
+ }
23824
+
23825
+ // src/utils/format-mode.ts
23826
+ function isRichMode(opts) {
23827
+ if (opts?.json || opts?.quiet)
23828
+ return false;
23829
+ return Boolean(process.stdout.isTTY);
23830
+ }
23831
+
23749
23832
  // src/utils/formatters.ts
23833
+ var formatDuration3 = formatDuration2;
23834
+ var BADGE_STYLE = {
23835
+ ok: { bg: ansis_default.bgGreen, fg: ansis_default.black },
23836
+ warn: { bg: ansis_default.bgYellow, fg: ansis_default.black },
23837
+ err: { bg: ansis_default.bgRed, fg: ansis_default.white },
23838
+ info: { bg: ansis_default.bgBlue, fg: ansis_default.white }
23839
+ };
23840
+ function formatBadge(level, text) {
23841
+ const content = (text ?? defaultBadgeLabel(level)).padEnd(4, " ");
23842
+ const { bg, fg } = BADGE_STYLE[level];
23843
+ return bg(fg(` ${content} `));
23844
+ }
23845
+ function defaultBadgeLabel(level) {
23846
+ switch (level) {
23847
+ case "ok":
23848
+ return "OK";
23849
+ case "warn":
23850
+ return "WARN";
23851
+ case "err":
23852
+ return "FAIL";
23853
+ case "info":
23854
+ return "INFO";
23855
+ }
23856
+ }
23750
23857
  function formatSuccess(message, opts) {
23751
23858
  if (opts?.quiet)
23752
23859
  return;
@@ -23770,6 +23877,9 @@ function formatError2(error51, opts) {
23770
23877
  if (cliError?.details) {
23771
23878
  output.details = cliError.details;
23772
23879
  }
23880
+ if (cliError?.hint) {
23881
+ output.hint = cliError.hint;
23882
+ }
23773
23883
  process.stdout.write(JSON.stringify(output) + `
23774
23884
  `);
23775
23885
  return;
@@ -23783,6 +23893,10 @@ function formatError2(error51, opts) {
23783
23893
  `);
23784
23894
  if (cliError?.details) {
23785
23895
  process.stdout.write(` ${theme.dim(cliError.details)}
23896
+ `);
23897
+ }
23898
+ if (cliError?.hint) {
23899
+ process.stdout.write(`${theme.dim("\u21B3 hint:")} ${theme.value(cliError.hint)}
23786
23900
  `);
23787
23901
  }
23788
23902
  }
@@ -25455,6 +25569,16 @@ ${l2}
25455
25569
  }).prompt();
25456
25570
  };
25457
25571
  var i = `${styleText2("gray", S_BAR)} `;
25572
+ var tasks = async (o2, e) => {
25573
+ for (const t2 of o2) {
25574
+ if (t2.enabled === false)
25575
+ continue;
25576
+ const s = spinner(e);
25577
+ s.start(t2.title);
25578
+ const n2 = await t2.task(s.message);
25579
+ s.stop(n2 || t2.title);
25580
+ }
25581
+ };
25458
25582
  var text = (t2) => new n({
25459
25583
  validate: t2.validate,
25460
25584
  placeholder: t2.placeholder,
@@ -25540,7 +25664,12 @@ class CloudflareService {
25540
25664
  };
25541
25665
  } catch (err) {
25542
25666
  const message = err instanceof Error ? err.message : String(err);
25543
- return { ok: false, error: `Failed to spawn wrangler: ${message}` };
25667
+ const hint = /ENOENT|not found/i.test(message) ? "Install wrangler with `bun add -g wrangler` (or `npm i -g wrangler`), then run `wrangler login`." : undefined;
25668
+ return {
25669
+ ok: false,
25670
+ error: hint ? `Failed to spawn wrangler: ${message}
25671
+ \u21B3 hint: ${hint}` : `Failed to spawn wrangler: ${message}`
25672
+ };
25544
25673
  }
25545
25674
  }
25546
25675
  async whoami() {
@@ -25752,7 +25881,32 @@ class CloudflareService {
25752
25881
  ]);
25753
25882
  }
25754
25883
  async zonesList() {
25755
- return this.runWrangler(["zones", "list"]);
25884
+ const token = process.env.CLOUDFLARE_API_TOKEN;
25885
+ if (!token) {
25886
+ return {
25887
+ ok: false,
25888
+ error: "CLOUDFLARE_API_TOKEN environment variable is not set. Set it or run `wrangler login`."
25889
+ };
25890
+ }
25891
+ try {
25892
+ const response = await fetch("https://api.cloudflare.com/client/v4/zones?per_page=50", {
25893
+ headers: {
25894
+ Authorization: `Bearer ${token}`,
25895
+ "Content-Type": "application/json"
25896
+ }
25897
+ });
25898
+ const json2 = await response.json();
25899
+ if (!response.ok || !json2.success) {
25900
+ const errorMsg = json2.errors?.map((e) => e.message).join("; ") || `HTTP ${response.status}`;
25901
+ return { ok: false, error: `Failed to list zones: ${errorMsg}` };
25902
+ }
25903
+ const lines = json2.result.map((z2) => `${z2.name} (${z2.id})`);
25904
+ return { ok: true, value: lines.join(`
25905
+ `) };
25906
+ } catch (err) {
25907
+ const message = err instanceof Error ? err.message : String(err);
25908
+ return { ok: false, error: `Cloudflare API request failed: ${message}` };
25909
+ }
25756
25910
  }
25757
25911
  extractUrl(stdout2) {
25758
25912
  for (const line of stdout2.split(`
@@ -25976,8 +26130,8 @@ OPTIONS:
25976
26130
 
25977
26131
  EXAMPLES:
25978
26132
  hoox init Interactive wizard
25979
- 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) => {
25980
- const globalOpts = getFormatOptions(program2);
26133
+ 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, cmd) => {
26134
+ const globalOpts = getFormatOptions(cmd);
25981
26135
  const isNonInteractive = Boolean(options.token && options.account);
25982
26136
  await runInitCommand(options, globalOpts, isNonInteractive);
25983
26137
  }, { service: "init" }));
@@ -26642,11 +26796,53 @@ class DockerService {
26642
26796
  }
26643
26797
  isCommandAvailable(cmd) {
26644
26798
  return new Promise((resolve2) => {
26645
- const proc = Bun.spawn(["which", cmd.split(" ")[0]], {
26646
- stdout: "pipe",
26647
- stderr: "pipe"
26799
+ const parts = cmd.split(/\s+/).filter(Boolean);
26800
+ const binary = parts[0];
26801
+ const hasSubcommand = parts.length > 1;
26802
+ let proc;
26803
+ try {
26804
+ proc = Bun.spawn(["which", binary], {
26805
+ stdout: "pipe",
26806
+ stderr: "pipe"
26807
+ });
26808
+ } catch {
26809
+ resolve2(false);
26810
+ return;
26811
+ }
26812
+ const timer = setTimeout(() => {
26813
+ try {
26814
+ proc.kill();
26815
+ } catch {}
26816
+ resolve2(false);
26817
+ }, 5000);
26818
+ proc.exited.then((code) => {
26819
+ clearTimeout(timer);
26820
+ if (code !== 0) {
26821
+ resolve2(false);
26822
+ return;
26823
+ }
26824
+ if (!hasSubcommand) {
26825
+ resolve2(true);
26826
+ return;
26827
+ }
26828
+ try {
26829
+ const subproc = Bun.spawn([binary, ...parts.slice(1), "version"], {
26830
+ stdout: "pipe",
26831
+ stderr: "pipe"
26832
+ });
26833
+ subproc.exited.then((subcode) => {
26834
+ try {
26835
+ subproc.kill();
26836
+ } catch {}
26837
+ resolve2(subcode === 0);
26838
+ }).catch(() => resolve2(true));
26839
+ } catch {
26840
+ resolve2(true);
26841
+ }
26842
+ }).catch(() => {
26843
+ clearTimeout(timer);
26844
+ resolve2(false);
26648
26845
  });
26649
- proc.exited.then((code) => resolve2(code === 0)).catch(() => resolve2(false));
26650
26846
  });
26651
26847
  }
26652
26848
  }
@@ -26684,8 +26880,8 @@ OPTIONS:
26684
26880
  EXAMPLES:
26685
26881
  hoox dev start
26686
26882
  hoox dev start --runtime native
26687
- hoox dev start --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (options) => {
26688
- const fmt = getFormatOptions(program2);
26883
+ hoox dev start --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (options, cmd) => {
26884
+ const fmt = getFormatOptions(cmd);
26689
26885
  const configService = new ConfigService;
26690
26886
  const prereqs = new PrerequisitesService;
26691
26887
  const docker = new DockerService;
@@ -26803,8 +26999,8 @@ OPTIONS:
26803
26999
  EXAMPLES:
26804
27000
  hoox dev worker trade-worker
26805
27001
  hoox dev worker agent-worker --port 8788
26806
- hoox dev worker hoox --runtime docker`).option("--port <port>", "Dev server port (default: 8787)", (v) => parseInt(v, 10)).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (name, options) => {
26807
- const fmt = getFormatOptions(program2);
27002
+ hoox dev worker hoox --runtime docker`).option("--port <port>", "Dev server port (default: 8787)", (v) => parseInt(v, 10)).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (name, options, cmd) => {
27003
+ const fmt = getFormatOptions(cmd);
26808
27004
  const configService = new ConfigService;
26809
27005
  await configService.load();
26810
27006
  const worker = configService.getWorker(name);
@@ -26839,8 +27035,8 @@ OPTIONS:
26839
27035
 
26840
27036
  EXAMPLES:
26841
27037
  hoox dev dashboard
26842
- hoox dev dashboard --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async () => {
26843
- const fmt = getFormatOptions(program2);
27038
+ hoox dev dashboard --runtime docker`).option("--runtime <native|docker>", "Choose dev runtime (overrides saved preference)").action(withErrorHandling(async (_options, cmd) => {
27039
+ const fmt = getFormatOptions(cmd);
26844
27040
  const dashboardPath = path3.resolve(process.cwd(), "workers/dashboard");
26845
27041
  const dashboardDir = Bun.file(dashboardPath);
26846
27042
  if (!await dashboardDir.exists()) {
@@ -26859,6 +27055,131 @@ EXAMPLES:
26859
27055
  }
26860
27056
  // src/commands/deploy/deploy-command.ts
26861
27057
  init_config2();
27058
+
27059
+ // src/utils/rich.ts
27060
+ async function runRichTasks(tasks2, options = {}) {
27061
+ const rich = isRichMode(options.format);
27062
+ const results = [];
27063
+ if (tasks2.length === 0)
27064
+ return results;
27065
+ const silent = options.format?.json === true;
27066
+ if (rich && !silent) {
27067
+ if (options.title)
27068
+ log.step(theme.heading(options.title));
27069
+ await tasks(tasks2.map((t2) => ({
27070
+ title: t2.title,
27071
+ task: async (message) => {
27072
+ const timer = startTimer();
27073
+ try {
27074
+ const value = await t2.run();
27075
+ const result = {
27076
+ title: t2.title,
27077
+ ok: true,
27078
+ ms: timer.ms(),
27079
+ value
27080
+ };
27081
+ if (t2.details) {
27082
+ result.details = await t2.details(value);
27083
+ }
27084
+ results.push(result);
27085
+ const dur = formatDuration2(result.ms);
27086
+ if (result.details) {
27087
+ const summary = Object.entries(result.details).map(([k, v]) => `${theme.dim(k)}=${theme.value(v)}`).join(" ");
27088
+ message(`${theme.success(icons.success)} ${dur} ${summary}`);
27089
+ } else {
27090
+ message(`${theme.success(icons.success)} ${dur}`);
27091
+ }
27092
+ } catch (err) {
27093
+ const message2 = err instanceof CLIError ? err.message : err instanceof Error ? err.message : String(err);
27094
+ const result = {
27095
+ title: t2.title,
27096
+ ok: false,
27097
+ ms: timer.ms(),
27098
+ error: message2
27099
+ };
27100
+ results.push(result);
27101
+ message(`${theme.error(icons.error)} ${formatDuration2(result.ms)} ${theme.dim(message2)}`);
27102
+ }
27103
+ }
27104
+ })));
27105
+ } else if (silent) {
27106
+ for (const t2 of tasks2) {
27107
+ const timer = startTimer();
27108
+ try {
27109
+ const value = await t2.run();
27110
+ const result = {
27111
+ title: t2.title,
27112
+ ok: true,
27113
+ ms: timer.ms(),
27114
+ value
27115
+ };
27116
+ if (t2.details) {
27117
+ result.details = await t2.details(value);
27118
+ }
27119
+ results.push(result);
27120
+ } catch (err) {
27121
+ const message2 = err instanceof CLIError ? err.message : err instanceof Error ? err.message : String(err);
27122
+ const result = {
27123
+ title: t2.title,
27124
+ ok: false,
27125
+ ms: timer.ms(),
27126
+ error: message2
27127
+ };
27128
+ results.push(result);
27129
+ }
27130
+ }
27131
+ } else {
27132
+ for (const t2 of tasks2) {
27133
+ log.step(t2.title);
27134
+ const timer = startTimer();
27135
+ try {
27136
+ const value = await t2.run();
27137
+ const result = {
27138
+ title: t2.title,
27139
+ ok: true,
27140
+ ms: timer.ms(),
27141
+ value
27142
+ };
27143
+ if (t2.details) {
27144
+ result.details = await t2.details(value);
27145
+ }
27146
+ results.push(result);
27147
+ log.success(`${t2.title} ${theme.dim(formatDuration2(result.ms))}`);
27148
+ } catch (err) {
27149
+ const message2 = err instanceof CLIError ? err.message : err instanceof Error ? err.message : String(err);
27150
+ const result = {
27151
+ title: t2.title,
27152
+ ok: false,
27153
+ ms: timer.ms(),
27154
+ error: message2
27155
+ };
27156
+ results.push(result);
27157
+ log.error(`${t2.title} ${theme.dim(message2)}`);
27158
+ }
27159
+ }
27160
+ }
27161
+ if (results.some((r2) => !r2.ok)) {
27162
+ process.exitCode = 1;
27163
+ }
27164
+ if (options.onSummary) {
27165
+ options.onSummary(results);
27166
+ } else if (!options.format?.json && !options.format?.quiet) {
27167
+ renderDefaultSummary(results);
27168
+ }
27169
+ return results;
27170
+ }
27171
+ function renderDefaultSummary(results) {
27172
+ if (results.length === 0)
27173
+ return;
27174
+ const rows = results.map((r2) => ({
27175
+ Task: r2.title,
27176
+ Status: r2.ok ? theme.success(icons.success) : theme.error(icons.error),
27177
+ Duration: formatDuration2(r2.ms)
27178
+ }));
27179
+ formatTable(rows, {});
27180
+ }
27181
+
27182
+ // src/commands/deploy/deploy-command.ts
26862
27183
  import { statSync, existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
26863
27184
  import { resolve as resolve2 } from "path";
26864
27185
 
@@ -26937,13 +27258,6 @@ class EnvService {
26937
27258
  default: "cryptolinx",
26938
27259
  hint: "Prefix for worker URLs"
26939
27260
  },
26940
- {
26941
- name: "INTERNAL_KEY_BINDING",
26942
- required: true,
26943
- secret: true,
26944
- section: "Internal Auth",
26945
- hint: "Internal key for D1 worker auth"
26946
- },
26947
27261
  {
26948
27262
  name: "TRADE_INTERNAL_KEY",
26949
27263
  required: true,
@@ -27340,62 +27654,61 @@ var DEPLOY_ORDER = [
27340
27654
  "hoox",
27341
27655
  "dashboard"
27342
27656
  ];
27343
- async function deployAll(configService, cf, env, forceRebuildDashboard = false, autoMode = false) {
27657
+ async function deployAll(configService, cf, env, forceRebuildDashboard = false, autoMode = false, format3 = {}) {
27344
27658
  const enabled = configService.listEnabledWorkers();
27345
27659
  const workers = DEPLOY_ORDER.filter((w) => w !== "dashboard" && enabled.includes(w));
27346
27660
  const unknown2 = enabled.filter((w) => w !== "dashboard" && !DEPLOY_ORDER.includes(w));
27347
27661
  const allItems = [...workers, ...unknown2, "dashboard"];
27348
- const results = [];
27349
27662
  if (allItems.length === 0) {
27350
- return results;
27663
+ return [];
27351
27664
  }
27352
- const s = spinner();
27353
- s.start(`Deploying ${allItems.length} item(s)...`);
27354
- for (let i2 = 0;i2 < allItems.length; i2++) {
27355
- const name = allItems[i2];
27356
- const isDashboard = name === "dashboard";
27357
- s.message(`[${i2 + 1}/${allItems.length}] ${name}...`);
27358
- let result;
27359
- if (isDashboard) {
27360
- result = await deployDashboard(cf, forceRebuildDashboard, true, autoMode);
27361
- } else {
27362
- result = await deploySingle(configService, cf, name, env);
27363
- }
27364
- results.push(result);
27365
- if (result.success) {
27366
- s.stop(`${theme.success(icons.success)} ${name} deployed`);
27367
- } else {
27368
- s.stop(`${theme.error(icons.error)} ${name} failed`);
27369
- }
27370
- if (result.success) {
27371
- if (result.url) {
27372
- log.step(` ${theme.dim("URL:")} ${result.url}`);
27373
- }
27374
- if (result.size) {
27375
- log.step(` ${theme.dim("Size:")} ${result.size}`);
27665
+ const tasks2 = allItems.map((name) => ({
27666
+ title: name,
27667
+ run: async () => {
27668
+ if (name === "dashboard") {
27669
+ return await deployDashboard(cf, forceRebuildDashboard, true, autoMode);
27376
27670
  }
27377
- if (result.startupTime) {
27378
- log.step(` ${theme.dim("Startup:")} ${result.startupTime}`);
27379
- }
27380
- if (result.versionId) {
27381
- log.step(` ${theme.dim("Version:")} ${result.versionId.slice(0, 8)}...`);
27382
- }
27383
- if (!result.url && !result.size && !result.startupTime && !result.versionId && result.rawOutput) {
27384
- log.step(` ${theme.dim("Output:")} ${result.rawOutput.slice(0, 80)}`);
27385
- }
27386
- } else if (result.error) {
27387
- log.warn(` ${theme.error("Error:")} ${result.error}`);
27388
- }
27389
- if (i2 < allItems.length - 1) {
27390
- s.start(`Deploying ${allItems.length} item(s)...`);
27671
+ return await deploySingle(configService, cf, name, env);
27672
+ },
27673
+ details: (r2) => {
27674
+ if (!r2.success)
27675
+ return { error: r2.error ?? "unknown" };
27676
+ const d = {};
27677
+ if (r2.url)
27678
+ d.URL = r2.url;
27679
+ if (r2.size)
27680
+ d.Size = r2.size;
27681
+ if (r2.startupTime)
27682
+ d.Startup = r2.startupTime;
27683
+ if (r2.versionId)
27684
+ d.Version = r2.versionId.slice(0, 8) + "\u2026";
27685
+ if (Object.keys(d).length === 0 && r2.rawOutput) {
27686
+ d.Output = r2.rawOutput.slice(0, 80);
27687
+ }
27688
+ return d;
27391
27689
  }
27392
- }
27393
- const succeeded = results.filter((r2) => r2.success).length;
27394
- const failed = results.filter((r2) => !r2.success).length;
27395
- if (failed > 0) {
27396
- log.warn(`Summary: ${succeeded}/${allItems.length} deployed (${failed} failed)`);
27397
- }
27398
- return results;
27690
+ }));
27691
+ const richResults = await runRichTasks(tasks2, {
27692
+ title: `Deploying ${allItems.length} item(s)`,
27693
+ format: format3,
27694
+ onSummary: (results) => renderDeploySummary(results, format3)
27695
+ });
27696
+ return richResults.map((r2) => r2.value).filter(Boolean);
27697
+ }
27698
+ function renderDeploySummary(results, format3) {
27699
+ if (format3.json || format3.quiet)
27700
+ return;
27701
+ const rows = results.map((r2) => {
27702
+ const d = r2.value;
27703
+ return {
27704
+ Worker: r2.title,
27705
+ Status: r2.ok ? formatBadge("ok", "DEPLOYED") : formatBadge("err", "FAILED"),
27706
+ URL: d?.url ?? theme.dim("\u2014"),
27707
+ Size: d?.size ?? theme.dim("\u2014"),
27708
+ Duration: formatDuration3(r2.ms)
27709
+ };
27710
+ });
27711
+ formatTable(rows, {});
27399
27712
  }
27400
27713
  async function deployDashboard(_cf, forceRebuild = false, silentMode = false, autoMode = false) {
27401
27714
  const dashboardPath = "workers/dashboard";
@@ -27803,9 +28116,10 @@ EXAMPLES:
27803
28116
  hoox deploy all
27804
28117
  hoox deploy all --env production
27805
28118
  hoox deploy all --rebuild
27806
- 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) => {
28119
+ 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, cmd) => {
27807
28120
  const configService = new ConfigService;
27808
28121
  await configService.load();
28122
+ const format3 = getFormatOptions(cmd);
27809
28123
  if (options.dryRun) {
27810
28124
  const enabled = configService.listEnabledWorkers();
27811
28125
  const workers = DEPLOY_ORDER.filter((w) => w !== "dashboard" && enabled.includes(w));
@@ -27838,7 +28152,7 @@ ${theme.dim("Run without --dry-run to deploy.")}
27838
28152
  return;
27839
28153
  }
27840
28154
  const cf = new CloudflareService;
27841
- await deployAll(configService, cf, options.env, options.rebuild ?? false, options.auto ?? false);
28155
+ await deployAll(configService, cf, options.env, options.rebuild ?? false, options.auto ?? false, format3);
27842
28156
  }, { service: "deploy" }));
27843
28157
  deployCmd.command("workers").summary("Deploy all enabled workers to Cloudflare").description(`Deploy all enabled workers to Cloudflare Workers.
27844
28158
 
@@ -27927,8 +28241,8 @@ OPTIONS:
27927
28241
 
27928
28242
  EXAMPLES:
27929
28243
  hoox deploy worker trade-worker
27930
- hoox deploy worker agent-worker --env production`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(withErrorHandling(async (name, options) => {
27931
- const fmt = getFormatOptions(program2);
28244
+ hoox deploy worker agent-worker --env production`).option("--env <env>", "Cloudflare environment (e.g. production, staging)").action(withErrorHandling(async (name, options, cmd) => {
28245
+ const fmt = getFormatOptions(cmd);
27932
28246
  const configService = new ConfigService;
27933
28247
  await configService.load();
27934
28248
  const cf = new CloudflareService;
@@ -27950,8 +28264,8 @@ OPTIONS:
27950
28264
 
27951
28265
  EXAMPLES:
27952
28266
  hoox deploy dashboard Interactive: choose to rebuild or use existing
27953
- hoox deploy dashboard --rebuild Force rebuild before deploying`).option("--rebuild", "Force rebuild of dashboard before deploying").action(withErrorHandling(async (options) => {
27954
- const fmt = getFormatOptions(program2);
28267
+ hoox deploy dashboard --rebuild Force rebuild before deploying`).option("--rebuild", "Force rebuild of dashboard before deploying").action(withErrorHandling(async (options, cmd) => {
28268
+ const fmt = getFormatOptions(cmd);
27955
28269
  const cf = new CloudflareService;
27956
28270
  const result = await deployDashboard(cf, options.rebuild);
27957
28271
  if (result.success) {
@@ -27977,8 +28291,8 @@ Use --token and --secret-token to override.
27977
28291
  EXAMPLES:
27978
28292
  hoox deploy telegram-webhook
27979
28293
  hoox deploy telegram-webhook --token 123456:ABC-DEF1234
27980
- 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) => {
27981
- const fmt = getFormatOptions(program2);
28294
+ 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, cmd) => {
28295
+ const fmt = getFormatOptions(cmd);
27982
28296
  await doTelegramWebhook(fmt, options.token, options.secretToken, options.subdomain);
27983
28297
  }, { service: "deploy" }));
27984
28298
  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.
@@ -27987,8 +28301,8 @@ This is a post-deployment step that ensures the dashboard has correct
27987
28301
  service binding URLs for all workers.
27988
28302
 
27989
28303
  EXAMPLES:
27990
- hoox deploy update-internal-urls`).action(withErrorHandling(async () => {
27991
- const fmt = getFormatOptions(program2);
28304
+ hoox deploy update-internal-urls`).action(withErrorHandling(async (_options, cmd) => {
28305
+ const fmt = getFormatOptions(cmd);
27992
28306
  await doUpdateInternalUrls(fmt);
27993
28307
  }, { service: "deploy" }));
27994
28308
  deployCmd.command("kv-config").summary("Apply KV manifest keys post-deployment").description(`Apply the KV manifest key-value pairs after deploying workers.
@@ -27997,8 +28311,8 @@ Sets all KV keys from the manifest to their default values.
27997
28311
  This post-deployment step initializes the CONFIG_KV namespace.
27998
28312
 
27999
28313
  EXAMPLES:
28000
- hoox deploy kv-config`).action(withErrorHandling(async () => {
28001
- const fmt = getFormatOptions(program2);
28314
+ hoox deploy kv-config`).action(withErrorHandling(async (_options, cmd) => {
28315
+ const fmt = getFormatOptions(cmd);
28002
28316
  await doKvConfig(fmt);
28003
28317
  }, { service: "deploy" }));
28004
28318
  deployCmd.command("history <worker>").summary("Show deployment version history for a worker").description(`Display the deployment version history for a Cloudflare Worker.
@@ -28010,8 +28324,8 @@ ARGUMENTS:
28010
28324
 
28011
28325
  EXAMPLES:
28012
28326
  hoox deploy history trade-worker
28013
- hoox deploy history hoox --json`).action(withErrorHandling(async (worker) => {
28014
- const fmt = getFormatOptions(program2);
28327
+ hoox deploy history hoox --json`).action(withErrorHandling(async (worker, _options, cmd) => {
28328
+ const fmt = getFormatOptions(cmd);
28015
28329
  await doVersionHistory(worker, fmt);
28016
28330
  }, { service: "deploy" }));
28017
28331
  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.
@@ -28029,8 +28343,8 @@ OPTIONS:
28029
28343
  EXAMPLES:
28030
28344
  hoox deploy rollback trade-worker
28031
28345
  hoox deploy rollback trade-worker <version-id>
28032
- hoox deploy rollback trade-worker <version-id> --yes`).option("--yes", "Skip confirmation prompt").action(withErrorHandling(async (worker, version2, options) => {
28033
- const fmt = getFormatOptions(program2);
28346
+ hoox deploy rollback trade-worker <version-id> --yes`).option("--yes", "Skip confirmation prompt").action(withErrorHandling(async (worker, version2, options, cmd) => {
28347
+ const fmt = getFormatOptions(cmd);
28034
28348
  await doVersionRollback(worker, version2, fmt, options.yes ?? false);
28035
28349
  }, { service: "deploy" }));
28036
28350
  }
@@ -30101,27 +30415,40 @@ ${summary.failed > 0 ? theme.error(icons.error) : theme.success(icons.success)}
30101
30415
  `);
30102
30416
  }
30103
30417
  async function handleSetup(opts) {
30104
- const s = spinner();
30105
30418
  try {
30106
30419
  const configService = new ConfigService;
30107
- s.start("Loading config...");
30108
30420
  await configService.load();
30109
- s.stop("Config loaded");
30110
30421
  const cf = new CloudflareService;
30111
30422
  const secretsService = await SecretsService.create();
30112
30423
  const categories = [];
30113
- s.start("Validating config...");
30114
- categories.push(await runConfigChecks(configService));
30115
- s.stop("Config validation complete");
30116
- s.start("Checking infrastructure (D1, KV, R2, Queues)...");
30117
- categories.push(await runInfraChecks(cf));
30118
- s.stop("Infrastructure checks complete");
30119
- s.start("Checking secrets...");
30120
- categories.push(await runSecretsChecks(secretsService, configService, cf));
30121
- s.stop("Secrets checks complete");
30122
- s.start("Checking database...");
30123
- categories.push(await runDatabaseChecks(cf, configService));
30124
- s.stop("Database checks complete");
30424
+ const tasks2 = [
30425
+ {
30426
+ title: "Validating config",
30427
+ run: async () => await runConfigChecks(configService)
30428
+ },
30429
+ {
30430
+ title: "Checking infrastructure (D1, KV, R2, Queues)",
30431
+ run: async () => await runInfraChecks(cf)
30432
+ },
30433
+ {
30434
+ title: "Checking secrets",
30435
+ run: async () => await runSecretsChecks(secretsService, configService, cf)
30436
+ },
30437
+ {
30438
+ title: "Checking database",
30439
+ run: async () => await runDatabaseChecks(cf, configService)
30440
+ }
30441
+ ];
30442
+ const results = await runRichTasks(tasks2, {
30443
+ title: "Running diagnostics",
30444
+ format: opts,
30445
+ onSummary: (rows) => {
30446
+ for (const r2 of rows) {
30447
+ if (r2.ok && r2.value)
30448
+ categories.push(r2.value);
30449
+ }
30450
+ }
30451
+ });
30125
30452
  const report = buildReport(categories);
30126
30453
  if (opts.json) {
30127
30454
  process.stdout.write(JSON.stringify(report, null, 2) + `
@@ -30132,6 +30459,9 @@ async function handleSetup(opts) {
30132
30459
  if (!report.success) {
30133
30460
  process.exitCode = 1 /* ERROR */;
30134
30461
  }
30462
+ if (results.some((r2) => !r2.ok)) {
30463
+ process.exitCode = 1 /* ERROR */;
30464
+ }
30135
30465
  } catch (err) {
30136
30466
  const message = err instanceof Error ? err.message : String(err);
30137
30467
  formatError2(message, opts);
@@ -30139,7 +30469,6 @@ async function handleSetup(opts) {
30139
30469
  }
30140
30470
  }
30141
30471
  async function handleHealth(opts, autoFix) {
30142
- const s = spinner();
30143
30472
  const results = [];
30144
30473
  try {
30145
30474
  const configService = new ConfigService;
@@ -30150,29 +30479,37 @@ async function handleHealth(opts, autoFix) {
30150
30479
  formatSuccess("No enabled workers to check", opts);
30151
30480
  return;
30152
30481
  }
30153
- s.start(`Health-checking ${enabledWorkers.length} worker(s)...`);
30154
- for (const workerName of enabledWorkers) {
30155
- s.message(`Probing ${workerName}...`);
30156
- try {
30157
- const tailResult = await cf.tail(workerName);
30158
- const healthy = tailResult.ok;
30159
- results.push({
30160
- worker: workerName,
30161
- status: healthy ? "healthy" : "degraded",
30162
- connectivity: healthy,
30163
- error: tailResult.ok ? undefined : tailResult.error ?? "Unknown error"
30164
- });
30165
- } catch (err) {
30166
- const message = err instanceof Error ? err.message : String(err);
30167
- results.push({
30168
- worker: workerName,
30169
- status: "down",
30170
- connectivity: false,
30171
- error: message
30172
- });
30482
+ const tasks2 = enabledWorkers.map((workerName) => ({
30483
+ title: `Probe ${workerName}`,
30484
+ run: async () => {
30485
+ try {
30486
+ const tailResult = await cf.tail(workerName);
30487
+ const healthy = tailResult.ok;
30488
+ return {
30489
+ worker: workerName,
30490
+ status: healthy ? "healthy" : "degraded",
30491
+ connectivity: healthy,
30492
+ error: tailResult.ok ? undefined : tailResult.error ?? "Unknown error"
30493
+ };
30494
+ } catch (err) {
30495
+ const message = err instanceof Error ? err.message : String(err);
30496
+ return {
30497
+ worker: workerName,
30498
+ status: "down",
30499
+ connectivity: false,
30500
+ error: message
30501
+ };
30502
+ }
30173
30503
  }
30504
+ }));
30505
+ const taskResults = await runRichTasks(tasks2, {
30506
+ title: `Health-checking ${enabledWorkers.length} worker(s)`,
30507
+ format: opts
30508
+ });
30509
+ for (const r2 of taskResults) {
30510
+ if (r2.value)
30511
+ results.push(r2.value);
30174
30512
  }
30175
- s.stop("Health check complete");
30176
30513
  if (opts.json) {
30177
30514
  process.stdout.write(JSON.stringify(results, null, 2) + `
30178
30515
  `);
@@ -30197,7 +30534,6 @@ ${theme.warning(icons.warning)} Auto-fix flag set but health issues require manu
30197
30534
  process.exitCode = 1 /* ERROR */;
30198
30535
  }
30199
30536
  } catch (err) {
30200
- s.stop("Health check failed");
30201
30537
  const message = err instanceof Error ? err.message : String(err);
30202
30538
  formatError2(message, opts);
30203
30539
  process.exitCode = 1 /* ERROR */;
@@ -30771,8 +31107,8 @@ async function tailAllWorkers(opts, fmt) {
30771
31107
  }
30772
31108
  function registerLogsCommand(program2) {
30773
31109
  const logsCmd = program2.command("logs").description("Tail and view Cloudflare Worker logs in real-time");
30774
- logsCmd.command("worker <name>").description("Tail logs for a specific Cloudflare Worker").option("--level <level>", "Filter by log level (log, info, warn, error, debug, all)", "all").option("--follow", "Continuously stream logs (default)", true).option("--no-follow", "Show recent logs and exit after 5 seconds").option("--json", "Output logs in JSON format").action(async (name, options) => {
30775
- const fmt = getFormatOptions(program2);
31110
+ logsCmd.command("worker <name>").description("Tail logs for a specific Cloudflare Worker").option("--level <level>", "Filter by log level (log, info, warn, error, debug, all)", "all").option("--follow", "Continuously stream logs (default)", true).option("--no-follow", "Show recent logs and exit after 5 seconds").option("--json", "Output logs in JSON format").action(async (name, options, cmd) => {
31111
+ const fmt = getFormatOptions(cmd);
30776
31112
  const workerOpts = {
30777
31113
  level: normalizeLevel(options.level ?? "all"),
30778
31114
  follow: options.follow !== false,
@@ -30786,8 +31122,8 @@ function registerLogsCommand(program2) {
30786
31122
  process.exitCode = 1 /* ERROR */;
30787
31123
  }
30788
31124
  });
30789
- logsCmd.command("all").description("Tail logs from all enabled workers concurrently").option("--level <level>", "Filter by log level (log, info, warn, error, debug, all)", "all").option("--follow", "Continuously stream logs (default)", true).option("--no-follow", "Show recent logs and exit after 5 seconds").option("--json", "Output logs in JSON format").action(async (options) => {
30790
- const fmt = getFormatOptions(program2);
31125
+ logsCmd.command("all").description("Tail logs from all enabled workers concurrently").option("--level <level>", "Filter by log level (log, info, warn, error, debug, all)", "all").option("--follow", "Continuously stream logs (default)", true).option("--no-follow", "Show recent logs and exit after 5 seconds").option("--json", "Output logs in JSON format").action(async (options, cmd) => {
31126
+ const fmt = getFormatOptions(cmd);
30791
31127
  const workerOpts = {
30792
31128
  level: normalizeLevel(options.level ?? "all"),
30793
31129
  follow: options.follow !== false,
@@ -30896,21 +31232,22 @@ var PIPELINE_STEPS = [
30896
31232
  ];
30897
31233
  function registerTestCommand(program2) {
30898
31234
  const testCmd = program2.command("test").description("Run tests and CI pipeline");
30899
- 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) => {
30900
- const fmt = getFormatOptions(program2);
31235
+ 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, cmd) => {
31236
+ const fmt = getFormatOptions(cmd);
30901
31237
  const useJson = options.json || fmt.json;
30902
31238
  const opts = { json: useJson, quiet: fmt.quiet };
30903
31239
  const results = [];
30904
31240
  const s = spinner();
31241
+ const pipelineTimer = startTimer();
30905
31242
  s.start("Running CI pipeline...");
30906
31243
  for (const step of PIPELINE_STEPS) {
30907
31244
  s.message(`${theme.info(icons.info)} ${step.label}...`);
30908
31245
  const result = await runStep(step.args, process.cwd());
30909
31246
  results.push({ ...result, step: step.label });
30910
31247
  if (result.success) {
30911
- s.message(` ${theme.success(icons.success)} ${step.label} passed (${result.duration}ms)`);
31248
+ s.message(` ${theme.success(icons.success)} ${step.label} passed (${formatDuration3(result.duration)})`);
30912
31249
  } else {
30913
- s.message(` ${theme.error(icons.error)} ${step.label} failed (${result.duration}ms)`);
31250
+ s.message(` ${theme.error(icons.error)} ${step.label} failed (${formatDuration3(result.duration)})`);
30914
31251
  if (result.error) {
30915
31252
  s.message(theme.dim(result.error.slice(0, 500)));
30916
31253
  }
@@ -30923,14 +31260,14 @@ function registerTestCommand(program2) {
30923
31260
  failed: results.filter((r2) => !r2.success).length,
30924
31261
  results
30925
31262
  };
30926
- s.stop(summary.failed > 0 ? `Pipeline complete: ${summary.passed} passed, ${summary.failed} failed` : "Pipeline complete");
31263
+ s.stop(summary.failed > 0 ? `Pipeline complete: ${summary.passed} passed, ${summary.failed} failed (${formatDuration3(pipelineTimer.ms())})` : `Pipeline complete (${formatDuration3(pipelineTimer.ms())})`);
30927
31264
  printSummary(summary, opts);
30928
31265
  if (summary.failed > 0) {
30929
31266
  process.exitCode = 1 /* ERROR */;
30930
31267
  }
30931
31268
  }, { service: "test" }));
30932
- testCmd.command("unit").description("Run unit tests with bun test").option("--coverage", "Run with coverage reporting").action(async (options) => {
30933
- const fmt = getFormatOptions(program2);
31269
+ testCmd.command("unit").description("Run unit tests with bun test").option("--coverage", "Run with coverage reporting").action(async (options, cmd) => {
31270
+ const fmt = getFormatOptions(cmd);
30934
31271
  const args = ["bun", "test"];
30935
31272
  if (options.coverage)
30936
31273
  args.push("--coverage");
@@ -30942,8 +31279,8 @@ function registerTestCommand(program2) {
30942
31279
  process.exitCode = 1 /* ERROR */;
30943
31280
  }
30944
31281
  });
30945
- testCmd.command("integration").description("Run integration tests with vitest").option("--coverage", "Run with coverage reporting").action(async (options) => {
30946
- const fmt = getFormatOptions(program2);
31282
+ testCmd.command("integration").description("Run integration tests with vitest").option("--coverage", "Run with coverage reporting").action(async (options, cmd) => {
31283
+ const fmt = getFormatOptions(cmd);
30947
31284
  const args = ["vitest", "run", "--config", "vitest.config.ts"];
30948
31285
  if (options.coverage)
30949
31286
  args.push("--coverage");
@@ -30955,8 +31292,8 @@ function registerTestCommand(program2) {
30955
31292
  process.exitCode = 1 /* ERROR */;
30956
31293
  }
30957
31294
  });
30958
- testCmd.command("worker <name>").description("Run tests for a specific worker by name").option("--coverage", "Run with coverage reporting").action(withErrorHandling(async (name, options) => {
30959
- const fmt = getFormatOptions(program2);
31295
+ testCmd.command("worker <name>").description("Run tests for a specific worker by name").option("--coverage", "Run with coverage reporting").action(withErrorHandling(async (name, options, cmd) => {
31296
+ const fmt = getFormatOptions(cmd);
30960
31297
  const configService = new ConfigService;
30961
31298
  await configService.load();
30962
31299
  const workerConfig = configService.getWorker(name);
@@ -30979,14 +31316,16 @@ function registerTestCommand(program2) {
30979
31316
  }, { service: "test" }));
30980
31317
  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) => {
30981
31318
  const s = spinner();
31319
+ const liveTimer = startTimer();
30982
31320
  let filePattern = options.service ? `tests/live/${options.service}.test.ts` : "tests/live/";
30983
31321
  s.start(`Running live tests${options.service ? ` for ${options.service}` : ""}...`);
30984
31322
  const args = ["bun", "test", filePattern, "--jobs", "1"];
30985
31323
  const result = await runWithInherit(args, process.cwd());
31324
+ const dur = formatDuration3(liveTimer.ms());
30986
31325
  if (result.success) {
30987
- s.stop("Live tests complete");
31326
+ s.stop(`Live tests complete (${dur})`);
30988
31327
  } else {
30989
- s.stop(`Live tests: some failures (exit code ${result.exitCode})`);
31328
+ s.stop(`Live tests: some failures (exit code ${result.exitCode}, ${dur})`);
30990
31329
  process.exitCode = 1 /* ERROR */;
30991
31330
  }
30992
31331
  }, { service: "test" }));
@@ -31344,8 +31683,8 @@ EXAMPLES:
31344
31683
  hoox clone --all --home Clone all workers to $HOME/.hoox/workers
31345
31684
  hoox clone trade-worker Clone specific worker
31346
31685
  hoox clone trade-worker --home Clone specific worker to $HOME/.hoox/workers
31347
- hoox clone --org my-org Clone from specific org`).option("--all", "Clone all worker repositories").option("--home", "Clone into $HOME/.hoox/workers instead of default location").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) => {
31348
- const fmt = getFormatOptions(program2);
31686
+ hoox clone --org my-org Clone from specific org`).option("--all", "Clone all worker repositories").option("--home", "Clone into $HOME/.hoox/workers instead of default location").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, cmd) => {
31687
+ const fmt = getFormatOptions(cmd);
31349
31688
  const configService = new ConfigService;
31350
31689
  await configService.load();
31351
31690
  const workers = configService.listWorkers();
@@ -31380,6 +31719,7 @@ EXAMPLES:
31380
31719
  }
31381
31720
  const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
31382
31721
  const s = spinner();
31722
+ const cloneTimer = startTimer();
31383
31723
  const location = options.home ? "$HOME/.hoox/workers" : "workers/";
31384
31724
  s.start(`Cloning ${workers.length} worker(s) to ${location}...`);
31385
31725
  const results = [];
@@ -31419,10 +31759,10 @@ EXAMPLES:
31419
31759
  const succeeded = results.filter((r2) => r2.cloned).length;
31420
31760
  const failed = results.filter((r2) => !r2.cloned).length;
31421
31761
  if (failed > 0) {
31422
- s.stop(`Clone complete: ${succeeded} succeeded, ${failed} failed`);
31762
+ s.stop(`Clone complete: ${succeeded} succeeded, ${failed} failed (${formatDuration3(cloneTimer.ms())})`);
31423
31763
  process.exitCode = 1 /* ERROR */;
31424
31764
  } else {
31425
- s.stop(`All ${succeeded} worker(s) cloned successfully`);
31765
+ s.stop(`All ${succeeded} worker(s) cloned successfully (${formatDuration3(cloneTimer.ms())})`);
31426
31766
  }
31427
31767
  if (!fmt.quiet) {
31428
31768
  const rows = results.map((r2) => ({
@@ -31450,6 +31790,7 @@ EXAMPLES:
31450
31790
  }
31451
31791
  const repoBase = options.org ? `https://github.com/${options.org}` : await resolveRepoBase();
31452
31792
  const s = spinner();
31793
+ const singleTimer = startTimer();
31453
31794
  const location = options.home ? "$HOME/.hoox/workers" : "workers/";
31454
31795
  s.start(`Cloning ${name} to ${location}...`);
31455
31796
  const result = await cloneWorker(getRepoUrl(repoBase, name), workerConfig.path, name, process.cwd(), homeDir);
@@ -31458,11 +31799,11 @@ EXAMPLES:
31458
31799
  s.message("Updating submodules...");
31459
31800
  await updateSubmodules(process.cwd());
31460
31801
  } catch {}
31461
- s.stop(`Successfully cloned ${name}`);
31802
+ s.stop(`Successfully cloned ${name} (${formatDuration3(singleTimer.ms())})`);
31462
31803
  const locationText = options.home ? " at $HOME/.hoox/workers" : "";
31463
31804
  formatSuccess(`Cloned ${name}${locationText} from ${result.repo}`, fmt);
31464
31805
  } else {
31465
- s.stop(`Failed to clone ${name}`);
31806
+ s.stop(`Failed to clone ${name} (${formatDuration3(singleTimer.ms())})`);
31466
31807
  formatError2(new CLIError(`Failed to clone "${name}": ${result.error}`, 1 /* ERROR */), fmt);
31467
31808
  process.exitCode = 1 /* ERROR */;
31468
31809
  }
@@ -31650,16 +31991,23 @@ class DbService {
31650
31991
  return stdout2.trim();
31651
31992
  }
31652
31993
  async readMigrationSql() {
31994
+ const path5 = this.resolveMigrationScriptPath();
31995
+ let file2;
31653
31996
  try {
31654
- const file2 = Bun.file(this.resolveMigrationScriptPath());
31655
- if (await file2.exists()) {
31656
- const content = await file2.text();
31657
- const match = content.match(/d1\s+execute\s+\S+\s+--command=["'](.+?)["']/s);
31658
- if (match)
31659
- return match[1];
31660
- }
31661
- } catch {}
31662
- return "SELECT 1";
31997
+ file2 = Bun.file(path5);
31998
+ } catch (e) {
31999
+ const message = e instanceof Error ? e.message : String(e);
32000
+ throw new Error(`Migration script "${path5}" could not be read: ${message}. ` + `Refusing to run no-op migration.`, { cause: e });
32001
+ }
32002
+ if (!await file2.exists()) {
32003
+ throw new Error(`Migration script "${path5}" not found. ` + `Refusing to run no-op migration. Create the script or run \`hoox setup\` first.`);
32004
+ }
32005
+ const content = await file2.text();
32006
+ const match = content.match(/d1\s+execute\s+\S+\s+--command=["'](.+?)["']/s);
32007
+ if (!match) {
32008
+ throw new Error(`Migration script "${path5}" was found but no \`d1 execute \u2026 --command="\u2026"\` line could be extracted. ` + `Refusing to fall back to a no-op migration \u2014 please check the script format.`);
32009
+ }
32010
+ return match[1];
31663
32011
  }
31664
32012
  }
31665
32013
  // src/commands/db/db-command.ts
@@ -31670,34 +32018,36 @@ async function resolveDb(cmd, svc) {
31670
32018
  async function handleApply(opts, dbName, remote, file2) {
31671
32019
  const svc = new DbService;
31672
32020
  const s = spinner();
32021
+ const t2 = startTimer();
31673
32022
  s.start("Applying schema...");
31674
32023
  try {
31675
32024
  const output = await svc.apply(dbName, remote, file2);
31676
- s.stop("Schema applied");
32025
+ s.stop(`Schema applied (${formatDuration3(t2.ms())})`);
31677
32026
  formatSuccess(`Schema applied to ${dbName}${remote ? " (remote)" : " (local)"}`, opts);
31678
32027
  if (!opts.quiet && output) {
31679
32028
  process.stdout.write(`${output}
31680
32029
  `);
31681
32030
  }
31682
32031
  } catch (err) {
31683
- s.stop("Schema apply failed");
32032
+ s.stop(`Schema apply failed (${formatDuration3(t2.ms())})`);
31684
32033
  throw err;
31685
32034
  }
31686
32035
  }
31687
32036
  async function handleMigrate(opts, dbName, remote) {
31688
32037
  const svc = new DbService;
31689
32038
  const s = spinner();
32039
+ const t2 = startTimer();
31690
32040
  s.start("Running migrations...");
31691
32041
  try {
31692
32042
  const output = await svc.migrate(dbName, remote);
31693
- s.stop("Migrations complete");
32043
+ s.stop(`Migrations complete (${formatDuration3(t2.ms())})`);
31694
32044
  formatSuccess(`Migrations applied to ${dbName}${remote ? " (remote)" : " (local)"}`, opts);
31695
32045
  if (!opts.quiet && output) {
31696
32046
  process.stdout.write(`${output}
31697
32047
  `);
31698
32048
  }
31699
32049
  } catch (err) {
31700
- s.stop("Migrations failed");
32050
+ s.stop(`Migrations failed (${formatDuration3(t2.ms())})`);
31701
32051
  throw err;
31702
32052
  }
31703
32053
  }
@@ -31716,7 +32066,37 @@ async function handleList2(opts, dbName, remote) {
31716
32066
  formatTable(rows, opts);
31717
32067
  }
31718
32068
  }
31719
- async function handleQuery(opts, dbName, sql, remote) {
32069
+ var DESTRUCTIVE_SQL_KEYWORDS = [
32070
+ "DROP",
32071
+ "TRUNCATE",
32072
+ "ALTER",
32073
+ "DELETE",
32074
+ "UPDATE",
32075
+ "REPLACE",
32076
+ "INSERT",
32077
+ "CREATE",
32078
+ "ATTACH",
32079
+ "DETACH",
32080
+ "REINDEX",
32081
+ "VACUUM"
32082
+ ];
32083
+ function findDestructiveKeywords(sql) {
32084
+ const upper = sql.toUpperCase();
32085
+ const found = [];
32086
+ for (const kw of DESTRUCTIVE_SQL_KEYWORDS) {
32087
+ const re = new RegExp(`(^|[^A-Z0-9_])${kw}([^A-Z0-9_]|$)`, "g");
32088
+ if (re.test(upper))
32089
+ found.push(kw);
32090
+ }
32091
+ return found;
32092
+ }
32093
+ async function handleQuery(opts, dbName, sql, remote, allowDestructive) {
32094
+ if (!allowDestructive) {
32095
+ const matches = findDestructiveKeywords(sql);
32096
+ if (matches.length > 0) {
32097
+ throw new Error(`Destructive SQL keywords detected: ${matches.join(", ")}. ` + `Re-run with --allow-destructive to confirm. ` + `(Keywords: ${DESTRUCTIVE_SQL_KEYWORDS.join(", ")})`);
32098
+ }
32099
+ }
31720
32100
  const svc = new DbService;
31721
32101
  const output = await svc.query(dbName, sql, remote);
31722
32102
  if (opts.json) {
@@ -31735,13 +32115,14 @@ async function handleQuery(opts, dbName, sql, remote) {
31735
32115
  async function handleExport(opts, dbName, outputPath) {
31736
32116
  const svc = new DbService;
31737
32117
  const s = spinner();
32118
+ const t2 = startTimer();
31738
32119
  s.start("Exporting database...");
31739
32120
  try {
31740
32121
  const path5 = await svc.export(dbName, outputPath);
31741
- s.stop("Database exported");
32122
+ s.stop(`Database exported (${formatDuration3(t2.ms())})`);
31742
32123
  formatSuccess(`Database exported to ${path5}`, opts);
31743
32124
  } catch (err) {
31744
- s.stop("Export failed");
32125
+ s.stop(`Export failed (${formatDuration3(t2.ms())})`);
31745
32126
  throw err;
31746
32127
  }
31747
32128
  }
@@ -31758,17 +32139,18 @@ async function handleReset(opts, dbName, confirmed) {
31758
32139
  }
31759
32140
  const svc = new DbService;
31760
32141
  const s = spinner();
32142
+ const t2 = startTimer();
31761
32143
  s.start("Resetting database...");
31762
32144
  try {
31763
32145
  const output = await svc.reset(dbName);
31764
- s.stop("Database reset");
32146
+ s.stop(`Database reset (${formatDuration3(t2.ms())})`);
31765
32147
  formatSuccess(`Database "${dbName}" has been recreated`, opts);
31766
32148
  if (!opts.quiet && output) {
31767
32149
  process.stdout.write(`${output}
31768
32150
  `);
31769
32151
  }
31770
32152
  } catch (err) {
31771
- s.stop("Reset failed");
32153
+ s.stop(`Reset failed (${formatDuration3(t2.ms())})`);
31772
32154
  throw err;
31773
32155
  }
31774
32156
  }
@@ -31816,12 +32198,13 @@ EXAMPLES:
31816
32198
  const remote = Boolean(cmd.optsWithGlobals().remote);
31817
32199
  await handleList2(opts, dbName, remote);
31818
32200
  }, { service: "db" }));
31819
- dbCmd.command("query <sql>").description("Execute a SQL query").action(withErrorHandling(async (sql, _2, cmd) => {
32201
+ dbCmd.command("query <sql>").description("Execute a SQL query").option("--allow-destructive", "Allow destructive keywords (DROP, DELETE, TRUNCATE, ALTER, UPDATE)").action(withErrorHandling(async (sql, options, cmd) => {
31820
32202
  const opts = getFormatOptions(cmd);
31821
32203
  const svc = new DbService;
31822
32204
  const dbName = await resolveDb(cmd, svc);
31823
32205
  const remote = Boolean(cmd.optsWithGlobals().remote);
31824
- await handleQuery(opts, dbName, sql, remote);
32206
+ const allowDestructive = Boolean(options.allowDestructive);
32207
+ await handleQuery(opts, dbName, sql, remote, allowDestructive);
31825
32208
  }, { service: "db" }));
31826
32209
  dbCmd.command("export").description("Export database to .sql file").option("--output <path>", "Output file path").action(withErrorHandling(async (options, cmd) => {
31827
32210
  const opts = getFormatOptions(cmd);
@@ -31916,11 +32299,20 @@ ${theme.heading("Summary:")} ${result.healthyCount} healthy, ${result.degradedCo
31916
32299
  process.exitCode = 1 /* ERROR */;
31917
32300
  }
31918
32301
  }
32302
+ function assertSafeInteger(value, name, max) {
32303
+ if (!Number.isInteger(value) || value < 0 || value > max || !Number.isSafeInteger(value)) {
32304
+ throw new Error(`Invalid ${name}: ${value} (must be a non-negative integer \u2264 ${max})`);
32305
+ }
32306
+ }
32307
+ function escapeSqlString(value) {
32308
+ return value.replace(/'/g, "''");
32309
+ }
31919
32310
  async function doMonitorTrades(limit, fmt) {
31920
32311
  try {
32312
+ assertSafeInteger(limit, "limit", 100);
31921
32313
  const db = new DbService;
31922
32314
  const dbName = await db.resolveDbName();
31923
- const sql = `SELECT * FROM trades ORDER BY timestamp DESC LIMIT ${Math.min(limit, 100)}`;
32315
+ const sql = `SELECT * FROM trades ORDER BY timestamp DESC LIMIT ${limit}`;
31924
32316
  const output = await db.query(dbName, sql, true);
31925
32317
  process.stdout.write(output + `
31926
32318
  `);
@@ -31938,7 +32330,7 @@ async function doMonitorLogs(workerName, fmt) {
31938
32330
  if (!/^[a-zA-Z0-9_-]+$/.test(workerName)) {
31939
32331
  throw new Error(`Invalid worker name: "${workerName}"`);
31940
32332
  }
31941
- sql = `SELECT * FROM system_logs WHERE worker = '${workerName}' ORDER BY timestamp DESC LIMIT 20`;
32333
+ sql = `SELECT * FROM system_logs WHERE worker = '${escapeSqlString(workerName)}' ORDER BY timestamp DESC LIMIT 20`;
31942
32334
  } else {
31943
32335
  sql = "SELECT * FROM system_logs ORDER BY timestamp DESC LIMIT 20";
31944
32336
  }
@@ -32052,6 +32444,7 @@ async function doMonitorAnalyticsSummary(fmt) {
32052
32444
  }
32053
32445
  async function doMonitorAnalyticsErrors(hours, fmt) {
32054
32446
  try {
32447
+ assertSafeInteger(hours, "hours", 8760);
32055
32448
  const db = new DbService;
32056
32449
  const dbName = await db.resolveDbName();
32057
32450
  const sql = `SELECT level, COUNT(*) as count FROM system_logs WHERE level IN ('error', 'warn') AND timestamp > unixepoch('now', '-${hours} hours') GROUP BY level`;
@@ -32713,14 +33106,18 @@ EXAMPLES:
32713
33106
  throw new CLIError(`"${submodulePath}" is not a git submodule \u2014 nothing to update`, 2 /* INVALID_USAGE */);
32714
33107
  }
32715
33108
  const s = spinner();
33109
+ const t2 = startTimer();
32716
33110
  s.start(`Updating ${target}...`);
32717
33111
  const output = await gitSubmoduleUpdate(cwd, submodulePath);
32718
- s.stop(output ? theme.success(`Updated ${target}`) : theme.muted(`${target} already up to date`));
33112
+ const dur = formatDuration3(t2.ms());
33113
+ s.stop(output ? theme.success(`Updated ${target} (${dur})`) : theme.muted(`${target} already up to date (${dur})`));
32719
33114
  } else {
32720
33115
  const s = spinner();
33116
+ const t2 = startTimer();
32721
33117
  s.start("Pulling latest from remote...");
32722
33118
  const output = await gitPull(cwd);
32723
- s.stop(output ? theme.success("Repository up to date") : theme.muted("Already up to date"));
33119
+ const dur = formatDuration3(t2.ms());
33120
+ s.stop(output ? theme.success(`Repository up to date (${dur})`) : theme.muted(`Already up to date (${dur})`));
32724
33121
  }
32725
33122
  }, { service: "update" }));
32726
33123
  }
@@ -32807,7 +33204,7 @@ function registerSchemaCommand(program2) {
32807
33204
  import { spawn } from "child_process";
32808
33205
  import { resolve as resolve7, dirname } from "path";
32809
33206
  import { fileURLToPath } from "url";
32810
- import { existsSync as existsSync5 } from "fs";
33207
+ import { existsSync as existsSync6 } from "fs";
32811
33208
  var __dirname2 = dirname(fileURLToPath(import.meta.url));
32812
33209
  function resolveTUIEntry() {
32813
33210
  const candidates = [
@@ -32816,7 +33213,7 @@ function resolveTUIEntry() {
32816
33213
  resolve7(process.cwd(), "../tui/src/main.tsx")
32817
33214
  ];
32818
33215
  for (const path5 of candidates) {
32819
- if (existsSync5(path5))
33216
+ if (existsSync6(path5))
32820
33217
  return path5;
32821
33218
  }
32822
33219
  throw new CLIError("Could not find TUI entry point. Ensure packages/tui/src/main.tsx exists.", 1 /* ERROR */);
@@ -33005,12 +33402,13 @@ EXAMPLES:
33005
33402
  }
33006
33403
  // src/services/setup/setup-service.ts
33007
33404
  import {
33008
- existsSync as existsSync6,
33405
+ existsSync as existsSync7,
33009
33406
  mkdirSync as mkdirSync3,
33010
33407
  readFileSync as readFileSync6,
33011
33408
  statSync as statSync2,
33012
33409
  writeFileSync as writeFileSync4
33013
33410
  } from "fs";
33411
+ import { homedir as homedir4 } from "os";
33014
33412
  import { join as join6 } from "path";
33015
33413
  var ALL_WORKERS = [
33016
33414
  "d1-worker",
@@ -33072,7 +33470,7 @@ class SetupService {
33072
33470
  WEBHOOK_API_KEY_BINDING: this._randomHex(32),
33073
33471
  TELEGRAM_INTERNAL_KEY_BINDING: internalKey
33074
33472
  };
33075
- if (!existsSync6(KEYS_DIR)) {
33473
+ if (!existsSync7(KEYS_DIR)) {
33076
33474
  mkdirSync3(KEYS_DIR, { recursive: true });
33077
33475
  } else if (!statSync2(KEYS_DIR).isDirectory()) {
33078
33476
  mkdirSync3(KEYS_DIR, { recursive: true });
@@ -33377,8 +33775,7 @@ class SetupService {
33377
33775
  }
33378
33776
  const fallbacks = [
33379
33777
  "/usr/local/bin/wrangler",
33380
- "/home/jango/.bun/install/global/bin/wrangler",
33381
- join6(process.env.HOME || "~", ".bun", "bin", "wrangler")
33778
+ join6(homedir4(), ".bun", "bin", "wrangler")
33382
33779
  ];
33383
33780
  for (const fb of fallbacks) {
33384
33781
  try {
@@ -33451,7 +33848,7 @@ class SetupService {
33451
33848
  step: "workers",
33452
33849
  message: `${missing.length} worker(s) not cloned: ${missing.join(", ")}`
33453
33850
  });
33454
- if (!existsSync6(gitmodulesPath)) {
33851
+ if (!existsSync7(gitmodulesPath)) {
33455
33852
  this.onProgress({
33456
33853
  type: "step-error",
33457
33854
  step: "workers",
@@ -34052,7 +34449,7 @@ EXAMPLES:
34052
34449
  const authOk = await tempSvc.checkAuth();
34053
34450
  if (!authOk) {
34054
34451
  authCheck.stop("Authentication failed");
34055
- formatError2(new CLIError("Not authenticated with Cloudflare. Run 'wrangler login' first.", 1 /* ERROR */), globalOpts);
34452
+ formatError2(new CLIError("Not authenticated with Cloudflare. Run 'wrangler login' first.", 1 /* ERROR */, undefined, false, "Run `wrangler login` interactively, or set CLOUDFLARE_API_TOKEN and CLOUDFLARE_ACCOUNT_ID environment variables for CI / non-interactive use."), globalOpts);
34056
34453
  outro(theme.error("Setup aborted"));
34057
34454
  process.exitCode = 1 /* ERROR */;
34058
34455
  return;
@@ -34091,12 +34488,14 @@ EXAMPLES:
34091
34488
  currentSpinner.stop(msg);
34092
34489
  currentSpinner = null;
34093
34490
  }));
34491
+ const totalTimer = startTimer();
34094
34492
  const result = await setupSvc.runAll(opts);
34493
+ const totalMs = totalTimer.ms();
34095
34494
  if (isInteractive) {
34096
- log.step("\u2500\u2500 Summary \u2500\u2500");
34495
+ log.step(`\u2500\u2500 Summary (total ${formatDuration3(totalMs)}) \u2500\u2500`);
34097
34496
  const rows = result.steps.map((s) => ({
34098
34497
  Step: s.step,
34099
- Status: s.success ? theme.success("\u2713") : theme.error("\u2717"),
34498
+ Status: s.success ? formatBadge("ok", "DONE") : formatBadge("err", "FAIL"),
34100
34499
  Message: s.message
34101
34500
  }));
34102
34501
  formatTable(rows, globalOpts);