@hoox-sh/hoox-cli 0.11.8 → 0.12.0

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 +2 -2
  2. package/dist/index.js +487 -148
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -201,10 +201,10 @@ Validates bun ≥1.2, git ≥2.40, wrangler, Docker, Cloudflare auth, and reposi
201
201
  hoox secrets list trade-worker
202
202
 
203
203
  # Set a secret
204
- hoox secrets set trade-worker BINANCE_KEY_BINDING
204
+ hoox secrets set trade-worker EXCHANGE_KEY_BINDING
205
205
 
206
206
  # Delete a secret
207
- hoox secrets delete trade-worker BINANCE_KEY_BINDING
207
+ hoox secrets delete trade-worker EXCHANGE_KEY_BINDING
208
208
 
209
209
  # Sync system/mesh secrets only (recommended after key rotation)
210
210
  hoox secrets sync --system
package/dist/index.js CHANGED
@@ -14843,8 +14843,10 @@ function toError(err, fallback = "Unknown error") {
14843
14843
  return err.message;
14844
14844
  if (typeof err === "string")
14845
14845
  return err;
14846
- if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
14847
- return err.message;
14846
+ if (err && typeof err === "object" && "message" in err) {
14847
+ const message = err.message;
14848
+ if (typeof message === "string")
14849
+ return message;
14848
14850
  }
14849
14851
  if (err === null || err === undefined)
14850
14852
  return fallback;
@@ -15105,6 +15107,24 @@ async function logKvTimestamp(env, prefix = "timestamp") {
15105
15107
  console.error(`Failed to log timestamp to KV: ${toError(error51, "Unknown error")}`);
15106
15108
  }
15107
15109
  }
15110
+ async function kvGetMany(kv, keys) {
15111
+ if (keys.length === 0)
15112
+ return [];
15113
+ return Promise.all(keys.map((key) => kv.get(key)));
15114
+ }
15115
+ async function kvGetManyAsRecord(kv, keys) {
15116
+ const values = await kvGetMany(kv, keys);
15117
+ const out = {};
15118
+ for (let i = 0;i < keys.length; i++) {
15119
+ out[keys[i]] = values[i] ?? null;
15120
+ }
15121
+ return out;
15122
+ }
15123
+ async function kvPutMany(kv, entries) {
15124
+ if (entries.length === 0)
15125
+ return;
15126
+ await Promise.all(entries.map((entry) => kv.put(entry.key, entry.value, entry.options)));
15127
+ }
15108
15128
  function headersToObject(headers) {
15109
15129
  const result = {};
15110
15130
  if (!headers)
@@ -15180,7 +15200,7 @@ function createQueueHandler(options) {
15180
15200
  } catch (error51) {
15181
15201
  const errorMsg = error51 instanceof Error ? error51.message : String(error51);
15182
15202
  if (attemptNumber < maxRetries) {
15183
- const delaySeconds = backoffDelays[attemptNumber] ?? backoffDelays[backoffDelays.length - 1];
15203
+ const delaySeconds = backoffDelays[attemptNumber] ?? backoffDelays[backoffDelays.length - 1] ?? 0;
15184
15204
  logger?.info(`${logId} Retrying in ${delaySeconds}s (attempt ${attemptNumber + 2}/${maxRetries + 1})`);
15185
15205
  await onRetry?.(msg.body, attemptNumber, errorMsg, delaySeconds);
15186
15206
  msg.retry({ delaySeconds });
@@ -17078,7 +17098,7 @@ var init_presets = __esm(() => {
17078
17098
  "analytics-worker",
17079
17099
  "telegram-worker"
17080
17100
  ],
17081
- integrations: ["binance", "telegram"]
17101
+ integrations: ["exchange", "telegram"]
17082
17102
  },
17083
17103
  {
17084
17104
  name: "full",
@@ -17095,15 +17115,7 @@ var init_presets = __esm(() => {
17095
17115
  "web3-wallet-worker",
17096
17116
  "pyne-worker"
17097
17117
  ],
17098
- integrations: [
17099
- "binance",
17100
- "bybit",
17101
- "mexc",
17102
- "telegram",
17103
- "openai",
17104
- "wallet",
17105
- "pyne"
17106
- ]
17118
+ integrations: ["exchange", "telegram", "openai", "wallet", "pyne"]
17107
17119
  }
17108
17120
  ];
17109
17121
  WORKER_DEPENDENCIES = {
@@ -17116,30 +17128,12 @@ var init_presets = __esm(() => {
17116
17128
  };
17117
17129
  INTEGRATIONS = [
17118
17130
  {
17119
- key: "binance",
17120
- label: "Binance Exchange",
17131
+ key: "exchange",
17132
+ label: "Exchange API (Binance / Bybit / MEXC)",
17121
17133
  workerName: "trade-worker",
17122
17134
  secrets: {
17123
- BINANCE_KEY_BINDING: "Binance API Key",
17124
- BINANCE_SECRET_BINDING: "Binance API Secret"
17125
- }
17126
- },
17127
- {
17128
- key: "mexc",
17129
- label: "MEXC Exchange",
17130
- workerName: "trade-worker",
17131
- secrets: {
17132
- MEXC_KEY_BINDING: "MEXC API Key",
17133
- MEXC_SECRET_BINDING: "MEXC API Secret"
17134
- }
17135
- },
17136
- {
17137
- key: "bybit",
17138
- label: "Bybit Exchange",
17139
- workerName: "trade-worker",
17140
- secrets: {
17141
- BYBIT_KEY_BINDING: "Bybit API Key",
17142
- BYBIT_SECRET_BINDING: "Bybit API Secret"
17135
+ EXCHANGE_KEY_BINDING: "Exchange API Key",
17136
+ EXCHANGE_SECRET_BINDING: "Exchange API Secret"
17143
17137
  }
17144
17138
  },
17145
17139
  {
@@ -17311,7 +17305,11 @@ class WizardEngine {
17311
17305
  return { ...this.state };
17312
17306
  }
17313
17307
  getCurrentStep() {
17314
- return STEPS.find((s) => s.id === this.state.step) ?? STEPS[0];
17308
+ const step = STEPS.find((s) => s.id === this.state.step) ?? STEPS[0];
17309
+ if (!step) {
17310
+ throw new Error("Wizard has no steps defined");
17311
+ }
17312
+ return step;
17315
17313
  }
17316
17314
  getCompletedSteps() {
17317
17315
  return [...this.state.completedSteps];
@@ -17334,8 +17332,11 @@ class WizardEngine {
17334
17332
  this.state.completedSteps.push(this.state.step);
17335
17333
  }
17336
17334
  const currentIdx = STEP_ORDER.indexOf(this.state.step);
17337
- if (currentIdx < STEP_ORDER.length - 1) {
17338
- this.state.step = STEP_ORDER[currentIdx + 1];
17335
+ if (currentIdx >= 0 && currentIdx < STEP_ORDER.length - 1) {
17336
+ const nextStep = STEP_ORDER[currentIdx + 1];
17337
+ if (nextStep !== undefined) {
17338
+ this.state.step = nextStep;
17339
+ }
17339
17340
  }
17340
17341
  this.state.updatedAt = Date.now();
17341
17342
  return [];
@@ -17346,6 +17347,8 @@ class WizardEngine {
17346
17347
  const currentIdx = STEP_ORDER.indexOf(this.state.step);
17347
17348
  if (currentIdx > 0) {
17348
17349
  const previousStep = STEP_ORDER[currentIdx - 1];
17350
+ if (previousStep === undefined)
17351
+ return;
17349
17352
  this.state.step = previousStep;
17350
17353
  this.state.completedSteps = this.state.completedSteps.filter((s) => s !== this.state.step || STEP_ORDER.indexOf(s) <= STEP_ORDER.indexOf(previousStep));
17351
17354
  }
@@ -17573,9 +17576,12 @@ function deriveCalledBy(manifests) {
17573
17576
  if (!calledBy[workerName])
17574
17577
  calledBy[workerName] = new Set;
17575
17578
  for (const svc of manifest.services) {
17576
- if (!calledBy[svc.service])
17577
- calledBy[svc.service] = new Set;
17578
- calledBy[svc.service].add(workerName);
17579
+ let callers = calledBy[svc.service];
17580
+ if (!callers) {
17581
+ callers = new Set;
17582
+ calledBy[svc.service] = callers;
17583
+ }
17584
+ callers.add(workerName);
17579
17585
  }
17580
17586
  }
17581
17587
  const result = {};
@@ -17584,12 +17590,16 @@ function deriveCalledBy(manifests) {
17584
17590
  }
17585
17591
  return result;
17586
17592
  }
17587
- var manifests, CALLED_BY, WORKER_MANIFESTS, WORKER_NAMES;
17593
+ function getWorkerDefaultEnabled(name) {
17594
+ return manifests[name]?.defaultEnabled;
17595
+ }
17596
+ var manifests, CALLED_BY, WORKER_MANIFESTS, WORKER_NAMES, WORKER_CATALOG;
17588
17597
  var init_registry = __esm(() => {
17589
17598
  manifests = {
17590
17599
  hoox: {
17591
17600
  name: "hoox",
17592
17601
  path: "workers/hoox-worker",
17602
+ defaultEnabled: true,
17593
17603
  vars: {
17594
17604
  WEBHOOK_API_KEY_BINDING: {
17595
17605
  type: "secret",
@@ -17640,6 +17650,7 @@ var init_registry = __esm(() => {
17640
17650
  "trade-worker": {
17641
17651
  name: "trade-worker",
17642
17652
  path: "workers/trade-worker",
17653
+ defaultEnabled: true,
17643
17654
  vars: {
17644
17655
  INTERNAL_KEY_BINDING: {
17645
17656
  type: "secret",
@@ -17649,15 +17660,22 @@ var init_registry = __esm(() => {
17649
17660
  type: "secret",
17650
17661
  description: "Telegram outbound auth"
17651
17662
  },
17652
- BINANCE_KEY_BINDING: { type: "secret", description: "Binance API key" },
17653
- BINANCE_SECRET_BINDING: {
17663
+ EXCHANGE_KEY_BINDING: {
17664
+ type: "secret",
17665
+ description: "Unified exchange API key (Binance / Bybit / MEXC)"
17666
+ },
17667
+ EXCHANGE_SECRET_BINDING: {
17654
17668
  type: "secret",
17655
- description: "Binance API secret"
17669
+ description: "Unified exchange API secret"
17656
17670
  },
17657
- MEXC_KEY_BINDING: { type: "secret", description: "MEXC API key" },
17658
- MEXC_SECRET_BINDING: { type: "secret", description: "MEXC API secret" },
17659
- BYBIT_KEY_BINDING: { type: "secret", description: "Bybit API key" },
17660
- BYBIT_SECRET_BINDING: { type: "secret", description: "Bybit API secret" }
17671
+ EXCHANGE_TESTNET_KEY_BINDING: {
17672
+ type: "secret",
17673
+ description: "Optional unified testnet API key"
17674
+ },
17675
+ EXCHANGE_TESTNET_SECRET_BINDING: {
17676
+ type: "secret",
17677
+ description: "Optional unified testnet API secret"
17678
+ }
17661
17679
  },
17662
17680
  services: [
17663
17681
  {
@@ -17699,6 +17717,7 @@ var init_registry = __esm(() => {
17699
17717
  "telegram-worker": {
17700
17718
  name: "telegram-worker",
17701
17719
  path: "workers/telegram-worker",
17720
+ defaultEnabled: true,
17702
17721
  vars: {
17703
17722
  INTERNAL_KEY_BINDING: {
17704
17723
  type: "secret",
@@ -17732,6 +17751,7 @@ var init_registry = __esm(() => {
17732
17751
  "d1-worker": {
17733
17752
  name: "d1-worker",
17734
17753
  path: "workers/d1-worker",
17754
+ defaultEnabled: true,
17735
17755
  vars: {
17736
17756
  INTERNAL_KEY_BINDING: {
17737
17757
  type: "secret",
@@ -17754,6 +17774,7 @@ var init_registry = __esm(() => {
17754
17774
  "web3-wallet-worker": {
17755
17775
  name: "web3-wallet-worker",
17756
17776
  path: "workers/web3-wallet-worker",
17777
+ defaultEnabled: false,
17757
17778
  vars: {
17758
17779
  WALLET_PK_SECRET: { type: "secret", description: "Wallet private key" },
17759
17780
  WALLET_MNEMONIC_SECRET: {
@@ -17774,6 +17795,7 @@ var init_registry = __esm(() => {
17774
17795
  "agent-worker": {
17775
17796
  name: "agent-worker",
17776
17797
  path: "workers/agent-worker",
17798
+ defaultEnabled: true,
17777
17799
  vars: {
17778
17800
  AGENT_INTERNAL_KEY: { type: "secret", description: "Agent worker auth" },
17779
17801
  INTERNAL_KEY_BINDING: {
@@ -17810,6 +17832,7 @@ var init_registry = __esm(() => {
17810
17832
  "email-worker": {
17811
17833
  name: "email-worker",
17812
17834
  path: "workers/email-worker",
17835
+ defaultEnabled: false,
17813
17836
  vars: {
17814
17837
  INTERNAL_KEY_BINDING: {
17815
17838
  type: "secret",
@@ -17847,6 +17870,7 @@ var init_registry = __esm(() => {
17847
17870
  "analytics-worker": {
17848
17871
  name: "analytics-worker",
17849
17872
  path: "workers/analytics-worker",
17873
+ defaultEnabled: true,
17850
17874
  vars: {
17851
17875
  CLOUDFLARE_API_TOKEN: {
17852
17876
  type: "secret",
@@ -17866,6 +17890,7 @@ var init_registry = __esm(() => {
17866
17890
  "report-worker": {
17867
17891
  name: "report-worker",
17868
17892
  path: "workers/report-worker",
17893
+ defaultEnabled: false,
17869
17894
  vars: {
17870
17895
  CF_API_TOKEN_BINDING: {
17871
17896
  type: "secret",
@@ -17900,6 +17925,7 @@ var init_registry = __esm(() => {
17900
17925
  "pyne-worker": {
17901
17926
  name: "pyne-worker",
17902
17927
  path: "workers/pyne-worker",
17928
+ defaultEnabled: true,
17903
17929
  vars: {
17904
17930
  API_KEY: {
17905
17931
  type: "secret",
@@ -17937,6 +17963,7 @@ var init_registry = __esm(() => {
17937
17963
  dashboard: {
17938
17964
  name: "dashboard",
17939
17965
  path: "workers/dashboard",
17966
+ defaultEnabled: true,
17940
17967
  vars: {
17941
17968
  DASHBOARD_USER: {
17942
17969
  type: "secret",
@@ -17979,6 +18006,14 @@ var init_registry = __esm(() => {
17979
18006
  CALLED_BY = deriveCalledBy(manifests);
17980
18007
  WORKER_MANIFESTS = manifests;
17981
18008
  WORKER_NAMES = Object.keys(manifests).sort();
18009
+ WORKER_CATALOG = WORKER_NAMES.map((name) => {
18010
+ const m = manifests[name];
18011
+ return {
18012
+ name: m.name,
18013
+ path: m.path,
18014
+ defaultEnabled: m.defaultEnabled
18015
+ };
18016
+ });
17982
18017
  });
17983
18018
 
17984
18019
  // ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
@@ -19735,6 +19770,8 @@ function generateWranglerJsonc(manifest) {
19735
19770
  parts.push(` "vars": {`);
19736
19771
  for (const [i, name] of varKeys.entries()) {
19737
19772
  const def = manifest.vars[name];
19773
+ if (!def)
19774
+ continue;
19738
19775
  const comma = i < varKeys.length - 1 ? "," : "";
19739
19776
  const val = def.type === "secret" ? "__SECRET__" : JSON.stringify(def.default ?? "");
19740
19777
  parts.push(` "${name}": ${val}${comma}`);
@@ -21561,7 +21598,7 @@ function getBackoffDelay(retryCount) {
21561
21598
  if (retryCount <= 0)
21562
21599
  return 0;
21563
21600
  const index = Math.min(retryCount - 1, BACKOFF_SEQUENCE.length - 1);
21564
- return BACKOFF_SEQUENCE[index];
21601
+ return BACKOFF_SEQUENCE[index] ?? 0;
21565
21602
  }
21566
21603
  var BACKOFF_SEQUENCE, MAX_RETRIES2 = 5, tradeStreamAbort = null, logStreamAbort = null, fetchWorkersGeneration = 0, reconnectTimer = null, scheduledReconnectTick = async () => {}, MAX_TRADES = 500, MAX_ALERTS = 100, MAX_LOGS = 1000, initialState2, useServiceStore;
21567
21604
  var init_service_store = __esm(() => {
@@ -22218,7 +22255,10 @@ __export(exports_src, {
22218
22255
  logKvTimestamp: () => logKvTimestamp,
22219
22256
  loadDashboardManifestsFromRoot: () => loadDashboardManifestsFromRoot,
22220
22257
  loadDashboardKvManifestFromRoot: () => loadDashboardKvManifestFromRoot,
22258
+ kvPutMany: () => kvPutMany,
22221
22259
  kvManifestFromDashboardManifests: () => kvManifestFromDashboardManifests,
22260
+ kvGetManyAsRecord: () => kvGetManyAsRecord,
22261
+ kvGetMany: () => kvGetMany,
22222
22262
  isWithinHooxHome: () => isWithinHooxHome,
22223
22263
  isUnixModeGroupOrWorldAccessible: () => isUnixModeGroupOrWorldAccessible,
22224
22264
  isTruthyKillSwitchFlag: () => isTruthyKillSwitchFlag,
@@ -22233,6 +22273,7 @@ __export(exports_src, {
22233
22273
  healthCheck: () => healthCheck,
22234
22274
  headersToObject: () => headersToObject,
22235
22275
  hasOperatorClientCredentials: () => hasOperatorClientCredentials,
22276
+ getWorkerDefaultEnabled: () => getWorkerDefaultEnabled,
22236
22277
  getTuiEntryCandidates: () => getTuiEntryCandidates,
22237
22278
  getRememberedMonorepoRoot: () => getRememberedMonorepoRoot,
22238
22279
  getRememberedMonorepoPath: () => getRememberedMonorepoPath,
@@ -22295,6 +22336,7 @@ __export(exports_src, {
22295
22336
  WORKER_NAMES: () => WORKER_NAMES,
22296
22337
  WORKER_MANIFESTS: () => WORKER_MANIFESTS,
22297
22338
  WORKER_DEPENDENCIES: () => WORKER_DEPENDENCIES,
22339
+ WORKER_CATALOG: () => WORKER_CATALOG,
22298
22340
  WIZARD_STATE_PATH: () => WIZARD_STATE_PATH,
22299
22341
  WALLET_EXECUTE_AUTH_KEY_FIELDS: () => WALLET_EXECUTE_AUTH_KEY_FIELDS,
22300
22342
  TradeSignalSchema: () => TradeSignalSchema,
@@ -25257,6 +25299,76 @@ ${e.errors.map((m3) => ` - ${m3}`).join(`
25257
25299
  const config2 = this.ensureLoaded();
25258
25300
  return Object.entries(config2.workers).filter(([, w]) => w.enabled).map(([name]) => name);
25259
25301
  }
25302
+ listWorkerStatuses() {
25303
+ const config2 = this.ensureLoaded();
25304
+ const names = new Set([
25305
+ ...WORKER_CATALOG.map((w) => w.name),
25306
+ ...Object.keys(config2.workers ?? {})
25307
+ ]);
25308
+ return [...names].sort().map((name) => {
25309
+ const catalog = WORKER_MANIFESTS[name];
25310
+ const cfg = config2.workers?.[name];
25311
+ return {
25312
+ name,
25313
+ path: cfg?.path ?? catalog?.path ?? `workers/${name}`,
25314
+ enabled: cfg?.enabled ?? catalog?.defaultEnabled ?? false,
25315
+ defaultEnabled: catalog?.defaultEnabled,
25316
+ inConfig: Boolean(cfg),
25317
+ inCatalog: Boolean(catalog)
25318
+ };
25319
+ });
25320
+ }
25321
+ async setWorkersEnabled(names, enabled) {
25322
+ this.ensureLoaded();
25323
+ const filePath = this.configPath;
25324
+ const file2 = Bun.file(filePath);
25325
+ if (!await file2.exists()) {
25326
+ throw new Error(`Config file not found: ${filePath}`);
25327
+ }
25328
+ let raw = await file2.text();
25329
+ const results = [];
25330
+ const unique = [...new Set(names.map((n3) => n3.trim()).filter(Boolean))];
25331
+ for (const name of unique) {
25332
+ const existing = this.config.workers?.[name];
25333
+ const catalog = WORKER_MANIFESTS[name];
25334
+ if (!existing && !catalog) {
25335
+ throw new Error(`Unknown worker "${name}". Known workers: ${WORKER_CATALOG.map((w) => w.name).join(", ")}`);
25336
+ }
25337
+ let seeded = false;
25338
+ if (!existing) {
25339
+ const entry = {
25340
+ enabled,
25341
+ path: catalog.path,
25342
+ vars: {},
25343
+ secrets: []
25344
+ };
25345
+ const seedEdits = modify(raw, ["workers", name], entry, {
25346
+ formattingOptions: JSONC_FORMAT,
25347
+ isArrayInsertion: false
25348
+ });
25349
+ raw = applyEdits(raw, seedEdits);
25350
+ seeded = true;
25351
+ } else {
25352
+ const edits = modify(raw, ["workers", name, "enabled"], enabled, {
25353
+ formattingOptions: JSONC_FORMAT
25354
+ });
25355
+ raw = applyEdits(raw, edits);
25356
+ }
25357
+ results.push({ name, enabled, seeded });
25358
+ }
25359
+ await Bun.write(filePath, raw);
25360
+ await this.load(filePath);
25361
+ return results;
25362
+ }
25363
+ async enableWorker(name) {
25364
+ await this.setWorkersEnabled([name], true);
25365
+ }
25366
+ async disableWorker(name) {
25367
+ await this.setWorkersEnabled([name], false);
25368
+ }
25369
+ getWorkerDefaultEnabled(name) {
25370
+ return getWorkerDefaultEnabled(name);
25371
+ }
25260
25372
  validate() {
25261
25373
  const errors4 = [];
25262
25374
  if (!this.config) {
@@ -25309,9 +25421,16 @@ ${e.errors.map((m3) => ` - ${m3}`).join(`
25309
25421
  return this.config;
25310
25422
  }
25311
25423
  }
25424
+ var JSONC_FORMAT;
25312
25425
  var init_config_service = __esm(() => {
25313
25426
  init_main();
25314
25427
  init_src();
25428
+ JSONC_FORMAT = {
25429
+ tabSize: 2,
25430
+ insertSpaces: true,
25431
+ eol: `
25432
+ `
25433
+ };
25315
25434
  });
25316
25435
 
25317
25436
  // src/services/config/index.ts
@@ -25758,6 +25877,9 @@ class PrerequisitesService {
25758
25877
  return { outdated: false };
25759
25878
  }
25760
25879
  const current2 = match[1];
25880
+ if (!current2) {
25881
+ return { outdated: false };
25882
+ }
25761
25883
  const meetsMin = this.satisfies(current2, this.MINIMUM_WRANGLER);
25762
25884
  return {
25763
25885
  outdated: !meetsMin,
@@ -25814,7 +25936,7 @@ class PrerequisitesService {
25814
25936
  });
25815
25937
  const stdout2 = await new Response(proc.stdout).text();
25816
25938
  const match = stdout2.match(/(\d+\.\d+)/);
25817
- const version2 = match ? match[1] : "unknown";
25939
+ const version2 = match?.[1] ?? "unknown";
25818
25940
  const [major, minor] = version2.split(".").map(Number);
25819
25941
  const passed = (major ?? 0) >= 2 && (minor ?? 0) >= 40;
25820
25942
  return {
@@ -26334,7 +26456,7 @@ var exports_schema_service = {};
26334
26456
  __export(exports_schema_service, {
26335
26457
  SchemaService: () => SchemaService
26336
26458
  });
26337
- import { readFileSync as readFileSync7, existsSync as existsSync11 } from "fs";
26459
+ import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
26338
26460
  import { resolve as resolve4 } from "path";
26339
26461
 
26340
26462
  class SchemaService {
@@ -26370,7 +26492,7 @@ class SchemaService {
26370
26492
  const errors4 = [];
26371
26493
  const isHooxProject = existsSync11(resolve4(this.projectRoot, "wrangler.jsonc")) || existsSync11(resolve4(this.projectRoot, "workers"));
26372
26494
  try {
26373
- const wranglerContent = readFileSync7(workerWranglerPath, "utf-8");
26495
+ const wranglerContent = readFileSync8(workerWranglerPath, "utf-8");
26374
26496
  errors4.push(...validateWranglerJsonc(name, manifest, wranglerContent));
26375
26497
  } catch (e) {
26376
26498
  const isErrnoException = e instanceof Error && "code" in e;
@@ -26384,7 +26506,7 @@ class SchemaService {
26384
26506
  });
26385
26507
  }
26386
26508
  try {
26387
- const rootContent = readFileSync7(rootWranglerPath, "utf-8");
26509
+ const rootContent = readFileSync8(rootWranglerPath, "utf-8");
26388
26510
  errors4.push(...validateRootSecrets(name, manifest, rootContent));
26389
26511
  } catch (e) {
26390
26512
  const errMessage = e instanceof Error ? e.message : String(e);
@@ -26395,7 +26517,7 @@ class SchemaService {
26395
26517
  });
26396
26518
  }
26397
26519
  try {
26398
- const devVarsContent = readFileSync7(devVarsPath, "utf-8");
26520
+ const devVarsContent = readFileSync8(devVarsPath, "utf-8");
26399
26521
  errors4.push(...validateDevVars(name, manifest, devVarsContent));
26400
26522
  } catch (e) {
26401
26523
  const errMessage = e instanceof Error ? e.message : String(e);
@@ -26654,7 +26776,7 @@ var init_trace_service = __esm(() => {
26654
26776
  });
26655
26777
 
26656
26778
  // src/index.ts
26657
- import { readFileSync as readFileSync12 } from "fs";
26779
+ import { readFileSync as readFileSync13 } from "fs";
26658
26780
 
26659
26781
  // ../../node_modules/.bun/commander@15.0.0/node_modules/commander/lib/error.js
26660
26782
  class CommanderError extends Error {
@@ -28857,7 +28979,13 @@ function formatTable(rows, opts = {}) {
28857
28979
  const alignNumbers = opts.alignNumbers !== false;
28858
28980
  const colorizeStatus = opts.colorizeStatus !== false;
28859
28981
  const compact = opts.compact === true;
28860
- const keys = Object.keys(rows[0]);
28982
+ const firstRow = rows[0];
28983
+ if (!firstRow) {
28984
+ process.stdout.write(`${theme.dim("(empty)")}
28985
+ `);
28986
+ return;
28987
+ }
28988
+ const keys = Object.keys(firstRow);
28861
28989
  const processed = rows.map((row) => {
28862
28990
  const out = {};
28863
28991
  for (const k of keys) {
@@ -28894,17 +29022,18 @@ function formatTable(rows, opts = {}) {
28894
29022
  widths[key] = Math.max(widths[key], stripped);
28895
29023
  }
28896
29024
  }
28897
- const topParts = keys.map((k) => "\u2500".repeat(widths[k] + 2));
29025
+ const topParts = keys.map((k) => "\u2500".repeat((widths[k] ?? 0) + 2));
28898
29026
  const topBorder = `\u250C${topParts.join("\u252C")}\u2510`;
28899
29027
  const sepBorder = `\u251C${topParts.join("\u253C")}\u2524`;
28900
29028
  const botBorder = `\u2514${topParts.join("\u2534")}\u2518`;
28901
- const headerCells = keys.map((k) => theme.bold(k.padEnd(widths[k])).toString());
29029
+ const headerCells = keys.map((k) => theme.bold(k.padEnd(widths[k] ?? 0)).toString());
28902
29030
  const headerRow = `\u2502 ${headerCells.join(" \u2502 ")} \u2502`;
28903
29031
  const dataRows = processed.map((row, rowIdx) => {
28904
29032
  const isAlt = zebra && rowIdx % 2 === 0;
28905
29033
  const cells = keys.map((k) => {
28906
29034
  const value = row[k] ?? "";
28907
- const padded = numericColumns.has(k) ? value.padStart(widths[k]) : value.padEnd(widths[k]);
29035
+ const width = widths[k] ?? 0;
29036
+ const padded = numericColumns.has(k) ? value.padStart(width) : value.padEnd(width);
28908
29037
  if (isAlt && !value.includes("\x1B")) {
28909
29038
  return theme.textMuted(padded);
28910
29039
  }
@@ -29027,11 +29156,11 @@ function levenshtein(a2, b) {
29027
29156
  curr[0] = j;
29028
29157
  for (let i = 1;i <= m; i++) {
29029
29158
  const cost = s.charCodeAt(i - 1) === t.charCodeAt(j - 1) ? 0 : 1;
29030
- curr[i] = Math.min(curr[i - 1] + 1, prev[i] + 1, prev[i - 1] + cost);
29159
+ curr[i] = Math.min((curr[i - 1] ?? 0) + 1, (prev[i] ?? 0) + 1, (prev[i - 1] ?? 0) + cost);
29031
29160
  }
29032
29161
  [prev, curr] = [curr, prev];
29033
29162
  }
29034
- return prev[m];
29163
+ return prev[m] ?? 0;
29035
29164
  }
29036
29165
 
29037
29166
  // src/utils/error-handler.ts
@@ -30480,6 +30609,8 @@ class SetupService {
30480
30609
  if (!varsMatch)
30481
30610
  continue;
30482
30611
  const varsBlock = varsMatch[1];
30612
+ if (varsBlock === undefined)
30613
+ continue;
30483
30614
  const nullEntries = varsBlock.match(/"\w+"\s*:\s*null/g);
30484
30615
  if (!nullEntries)
30485
30616
  continue;
@@ -31614,46 +31745,32 @@ class EnvService {
31614
31745
  hint: "Telegram webhook secret token"
31615
31746
  },
31616
31747
  {
31617
- name: "BINANCE_KEY_BINDING",
31748
+ name: "EXCHANGE_KEY_BINDING",
31618
31749
  required: false,
31619
31750
  secret: true,
31620
31751
  section: "Exchanges",
31621
- hint: "Binance API key"
31752
+ hint: "Unified exchange API key (Binance / Bybit / MEXC \u2014 venue via routing)"
31622
31753
  },
31623
31754
  {
31624
- name: "BINANCE_SECRET_BINDING",
31755
+ name: "EXCHANGE_SECRET_BINDING",
31625
31756
  required: false,
31626
31757
  secret: true,
31627
31758
  section: "Exchanges",
31628
- hint: "Binance API secret"
31759
+ hint: "Unified exchange API secret"
31629
31760
  },
31630
31761
  {
31631
- name: "MEXC_KEY_BINDING",
31762
+ name: "EXCHANGE_TESTNET_KEY_BINDING",
31632
31763
  required: false,
31633
31764
  secret: true,
31634
31765
  section: "Exchanges",
31635
- hint: "MEXC API key"
31766
+ hint: "Optional testnet API key (when signals use test: true)"
31636
31767
  },
31637
31768
  {
31638
- name: "MEXC_SECRET_BINDING",
31769
+ name: "EXCHANGE_TESTNET_SECRET_BINDING",
31639
31770
  required: false,
31640
31771
  secret: true,
31641
31772
  section: "Exchanges",
31642
- hint: "MEXC API secret"
31643
- },
31644
- {
31645
- name: "BYBIT_KEY_BINDING",
31646
- required: false,
31647
- secret: true,
31648
- section: "Exchanges",
31649
- hint: "Bybit API key"
31650
- },
31651
- {
31652
- name: "BYBIT_SECRET_BINDING",
31653
- required: false,
31654
- secret: true,
31655
- section: "Exchanges",
31656
- hint: "Bybit API secret"
31773
+ hint: "Optional testnet API secret"
31657
31774
  },
31658
31775
  {
31659
31776
  name: "EMAIL_HOST_BINDING",
@@ -31824,12 +31941,8 @@ class EnvService {
31824
31941
  "workers/trade-worker": [
31825
31942
  "API_SERVICE_KEY_BINDING",
31826
31943
  "INTERNAL_KEY_BINDING",
31827
- "BINANCE_KEY_BINDING",
31828
- "BINANCE_SECRET_BINDING",
31829
- "MEXC_KEY_BINDING",
31830
- "MEXC_SECRET_BINDING",
31831
- "BYBIT_KEY_BINDING",
31832
- "BYBIT_SECRET_BINDING"
31944
+ "EXCHANGE_KEY_BINDING",
31945
+ "EXCHANGE_SECRET_BINDING"
31833
31946
  ],
31834
31947
  "workers/agent-worker": ["AGENT_INTERNAL_KEY"],
31835
31948
  "workers/d1-worker": ["INTERNAL_KEY_BINDING"],
@@ -33724,6 +33837,27 @@ var SYSTEM_SECRET_NAMES = [
33724
33837
  "API_SERVICE_KEY_BINDING"
33725
33838
  ];
33726
33839
  var SYSTEM_SECRET_SET = new Set(SYSTEM_SECRET_NAMES);
33840
+ var WORKER_SYSTEM_SECRETS = {
33841
+ "d1-worker": ["INTERNAL_KEY_BINDING"],
33842
+ "analytics-worker": ["INTERNAL_KEY_BINDING"],
33843
+ "trade-worker": [
33844
+ "INTERNAL_KEY_BINDING",
33845
+ "TELEGRAM_INTERNAL_KEY_BINDING",
33846
+ "API_SERVICE_KEY_BINDING"
33847
+ ],
33848
+ "report-worker": ["INTERNAL_KEY_BINDING"],
33849
+ "email-worker": ["INTERNAL_KEY_BINDING"],
33850
+ "agent-worker": ["INTERNAL_KEY_BINDING", "AGENT_INTERNAL_KEY"],
33851
+ hoox: ["INTERNAL_KEY_BINDING", "WEBHOOK_API_KEY_BINDING"],
33852
+ "web3-wallet-worker": ["INTERNAL_KEY_BINDING"],
33853
+ "telegram-worker": ["INTERNAL_KEY_BINDING"],
33854
+ dashboard: [
33855
+ "AGENT_INTERNAL_KEY",
33856
+ "SESSION_SECRET",
33857
+ "TRADE_INTERNAL_KEY",
33858
+ "API_SERVICE_KEY_BINDING"
33859
+ ]
33860
+ };
33727
33861
  function isSystemSecret(name) {
33728
33862
  return SYSTEM_SECRET_SET.has(name);
33729
33863
  }
@@ -33844,11 +33978,19 @@ class SecretsService {
33844
33978
  const content = await devVarsFile.text();
33845
33979
  existingValues = this.parseDotEnv(content);
33846
33980
  }
33981
+ let keysFallback = new Map;
33982
+ if (options.systemOnly) {
33983
+ const keysFile = Bun.file(".keys/setup.env");
33984
+ if (await keysFile.exists()) {
33985
+ keysFallback = this.parseDotEnv(await keysFile.text());
33986
+ }
33987
+ }
33847
33988
  let secrets;
33848
33989
  if (options.systemOnly) {
33849
33990
  const fromDeclared = declared.filter((s) => isSystemSecret(s));
33850
33991
  const fromDevVars = [...SYSTEM_SECRET_NAMES].filter((s) => existingValues.has(s));
33851
- secrets = [...new Set([...fromDeclared, ...fromDevVars])];
33992
+ const fromMap = [...WORKER_SYSTEM_SECRETS[workerName] ?? []];
33993
+ secrets = [...new Set([...fromDeclared, ...fromDevVars, ...fromMap])];
33852
33994
  } else {
33853
33995
  secrets = declared;
33854
33996
  }
@@ -33858,7 +34000,7 @@ class SecretsService {
33858
34000
  const items = [];
33859
34001
  for (const secret of secrets) {
33860
34002
  try {
33861
- const value = existingValues.get(secret);
34003
+ const value = existingValues.get(secret) ?? keysFallback.get(secret);
33862
34004
  if (value !== undefined && !this.isPlaceholder(value)) {
33863
34005
  await this.execWranglerSecretPut(worker.path, secret, value, workerName);
33864
34006
  synced.push(secret);
@@ -34138,8 +34280,8 @@ The command will prompt for the secret value (hidden input).
34138
34280
  It writes to the worker's .dev.vars file and syncs to Cloudflare.
34139
34281
 
34140
34282
  EXAMPLES:
34141
- hoox secrets set trade-worker BINANCE_KEY_BINDING
34142
- hoox config secrets set trade-worker BINANCE_KEY_BINDING`).action(withErrorHandling(async (workerName, secretName, _2, cmd) => {
34283
+ hoox secrets set trade-worker EXCHANGE_KEY_BINDING
34284
+ hoox config secrets set trade-worker EXCHANGE_KEY_BINDING`).action(withErrorHandling(async (workerName, secretName, _2, cmd) => {
34143
34285
  const opts = getFormatOptions(cmd);
34144
34286
  const svc = await SecretsService.create();
34145
34287
  const declared = svc.listSecrets(workerName);
@@ -34184,8 +34326,8 @@ ARGUMENTS:
34184
34326
  This removes the secret from Cloudflare and from the worker's .dev.vars file.
34185
34327
 
34186
34328
  EXAMPLES:
34187
- hoox secrets delete trade-worker BINANCE_KEY_BINDING
34188
- hoox config secrets delete trade-worker BINANCE_KEY_BINDING`).action(withErrorHandling(async (workerName, secretName, _2, cmd) => {
34329
+ hoox secrets delete trade-worker EXCHANGE_KEY_BINDING
34330
+ hoox config secrets delete trade-worker EXCHANGE_KEY_BINDING`).action(withErrorHandling(async (workerName, secretName, _2, cmd) => {
34189
34331
  const opts = getFormatOptions(cmd);
34190
34332
  const svc = await SecretsService.create();
34191
34333
  const declared = svc.listSecrets(workerName);
@@ -34327,7 +34469,7 @@ EXAMPLES:
34327
34469
  }
34328
34470
 
34329
34471
  // src/commands/config/keys-subcommands.ts
34330
- import { existsSync as existsSync9 } from "fs";
34472
+ import { chmodSync as chmodSync6, existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
34331
34473
  init_fs_secure();
34332
34474
  init_theme();
34333
34475
  function generateKey(bytes = 32) {
@@ -34335,6 +34477,82 @@ function generateKey(bytes = 32) {
34335
34477
  crypto.getRandomValues(buf);
34336
34478
  return Array.from(buf, (b3) => b3.toString(16).padStart(2, "0")).join("");
34337
34479
  }
34480
+ var DEV_VARS_WORKER_KEYS2 = {
34481
+ "d1-worker": ["INTERNAL_KEY_BINDING"],
34482
+ "analytics-worker": ["INTERNAL_KEY_BINDING"],
34483
+ "trade-worker": [
34484
+ "INTERNAL_KEY_BINDING",
34485
+ "TELEGRAM_INTERNAL_KEY_BINDING",
34486
+ "API_SERVICE_KEY_BINDING"
34487
+ ],
34488
+ "report-worker": ["INTERNAL_KEY_BINDING"],
34489
+ "email-worker": ["INTERNAL_KEY_BINDING"],
34490
+ "agent-worker": ["INTERNAL_KEY_BINDING", "AGENT_INTERNAL_KEY"],
34491
+ hoox: ["INTERNAL_KEY_BINDING", "WEBHOOK_API_KEY_BINDING"],
34492
+ "web3-wallet-worker": ["INTERNAL_KEY_BINDING"],
34493
+ "telegram-worker": ["INTERNAL_KEY_BINDING"],
34494
+ dashboard: [
34495
+ "AGENT_INTERNAL_KEY",
34496
+ "SESSION_SECRET",
34497
+ "TRADE_INTERNAL_KEY",
34498
+ "API_SERVICE_KEY_BINDING"
34499
+ ]
34500
+ };
34501
+ var WORKER_FS_DIR2 = {
34502
+ hoox: "hoox-worker"
34503
+ };
34504
+ function workerFsDir2(worker) {
34505
+ return WORKER_FS_DIR2[worker] ?? worker;
34506
+ }
34507
+ function parseEnvFile2(text2) {
34508
+ const map2 = new Map;
34509
+ for (const line of text2.split(`
34510
+ `)) {
34511
+ const trimmed = line.trim();
34512
+ if (!trimmed || trimmed.startsWith("#"))
34513
+ continue;
34514
+ const eq = trimmed.indexOf("=");
34515
+ if (eq <= 0)
34516
+ continue;
34517
+ map2.set(trimmed.slice(0, eq), trimmed.slice(eq + 1));
34518
+ }
34519
+ return map2;
34520
+ }
34521
+ async function writeDevVarsFromKeys(keys) {
34522
+ let count = 0;
34523
+ for (const [worker, secretNames] of Object.entries(DEV_VARS_WORKER_KEYS2)) {
34524
+ const path5 = `workers/${workerFsDir2(worker)}/.dev.vars`;
34525
+ const meshSet = new Set(secretNames);
34526
+ const existing = existsSync9(path5) ? parseEnvFile2(readFileSync7(path5, "utf-8")) : new Map;
34527
+ for (const secretName of secretNames) {
34528
+ const value = keys[secretName];
34529
+ if (value)
34530
+ existing.set(secretName, value);
34531
+ }
34532
+ let content = `# .dev.vars \u2014 local secrets for ${worker}
34533
+ `;
34534
+ content += `# Generated by \`hoox keys generate\`. NEVER commit this file.
34535
+
34536
+ `;
34537
+ for (const secretName of secretNames) {
34538
+ const value = existing.get(secretName);
34539
+ if (value !== undefined)
34540
+ content += `${secretName}=${value}
34541
+ `;
34542
+ }
34543
+ for (const [k, v] of existing) {
34544
+ if (!meshSet.has(k))
34545
+ content += `${k}=${v}
34546
+ `;
34547
+ }
34548
+ await Bun.write(path5, content);
34549
+ try {
34550
+ chmodSync6(path5, 384);
34551
+ } catch {}
34552
+ count++;
34553
+ }
34554
+ return count;
34555
+ }
34338
34556
  function registerKeysSubcommands(keysCmd, service = "config") {
34339
34557
  keysCmd.command("generate").summary("Generate new internal auth keys").description(`Generate new internal auth keys and save to .keys/ directory.
34340
34558
 
@@ -34376,9 +34594,12 @@ EXAMPLES:
34376
34594
  `) + `
34377
34595
  `);
34378
34596
  secureSecretFile(`${keysDir}/setup.env`);
34379
- formatSuccess(`Generated ${Object.keys(keys).length} keys in ${keysDir}/`, opts);
34597
+ const devVarsCount = await writeDevVarsFromKeys(keys);
34598
+ formatSuccess(`Generated ${Object.keys(keys).length} keys in ${keysDir}/ + ${devVarsCount} .dev.vars`, opts);
34380
34599
  if (!opts.quiet && !opts.json) {
34381
34600
  process.stdout.write(`${theme.warning("!")} Keep these keys secret. Add ${keysDir}/ to .gitignore.
34601
+ `);
34602
+ process.stdout.write(`${theme.dim("\u2192")} Next: hoox secrets sync --system
34382
34603
  `);
34383
34604
  formatKeyValue(keys, opts);
34384
34605
  }
@@ -34476,7 +34697,7 @@ EXAMPLES:
34476
34697
  hoox config show Display current configuration
34477
34698
  hoox config set workers.agent-worker.enabled false
34478
34699
  hoox config secrets list List secrets for a worker
34479
- hoox config secrets set trade-worker BINANCE_KEY_BINDING
34700
+ hoox config secrets set trade-worker EXCHANGE_KEY_BINDING
34480
34701
  hoox config keys generate Generate new internal keys`);
34481
34702
  configCmd.command("show").summary("Display current wrangler.jsonc configuration").description(`Display the current wrangler.jsonc configuration in a formatted table.
34482
34703
 
@@ -34671,7 +34892,7 @@ COMMANDS:
34671
34892
 
34672
34893
  EXAMPLES:
34673
34894
  hoox secrets list trade-worker
34674
- hoox secrets set trade-worker BINANCE_KEY_BINDING
34895
+ hoox secrets set trade-worker EXCHANGE_KEY_BINDING
34675
34896
  hoox secrets sync --system
34676
34897
  hoox secrets sync`);
34677
34898
  registerSecretsSubcommands(cmd, "secrets");
@@ -36223,11 +36444,19 @@ async function resolveZone(cf) {
36223
36444
  if (lines.length === 0) {
36224
36445
  throw new CLIError("No zones found. Set CLOUDFLARE_ZONE_ID environment variable.", 1 /* ERROR */);
36225
36446
  }
36226
- const match = lines[0].match(/^(.+?)\s+\((.+?)\)\s*$/);
36447
+ const firstLine = lines[0];
36448
+ if (!firstLine) {
36449
+ throw new CLIError("No zones found. Set CLOUDFLARE_ZONE_ID environment variable.", 1 /* ERROR */);
36450
+ }
36451
+ const match = firstLine.match(/^(.+?)\s+\((.+?)\)\s*$/);
36227
36452
  if (match) {
36228
- return { name: match[1], id: match[2] };
36453
+ const name = match[1];
36454
+ const id = match[2];
36455
+ if (name && id) {
36456
+ return { name, id };
36457
+ }
36229
36458
  }
36230
- return { name: lines[0], id: lines[0] };
36459
+ return { name: firstLine, id: firstLine };
36231
36460
  }
36232
36461
  async function handleStatus(opts) {
36233
36462
  const cf = new CloudflareService;
@@ -36545,6 +36774,8 @@ EXAMPLES:
36545
36774
  const results = [];
36546
36775
  for (let i2 = 0;i2 < workers.length; i2++) {
36547
36776
  const name2 = workers[i2];
36777
+ if (!name2)
36778
+ continue;
36548
36779
  const workerConfig = configService.getWorker(name2);
36549
36780
  if (!workerConfig)
36550
36781
  continue;
@@ -36822,7 +37053,11 @@ class DbService {
36822
37053
  if (!match) {
36823
37054
  throw new Error(`Migration script "${path6}" 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.`);
36824
37055
  }
36825
- return match[1];
37056
+ const command = match[1];
37057
+ if (!command) {
37058
+ throw new Error(`Migration script "${path6}" 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.`);
37059
+ }
37060
+ return command;
36826
37061
  }
36827
37062
  }
36828
37063
  // src/commands/db/db-command.ts
@@ -37129,15 +37364,19 @@ function parseWranglerQueuesTable(raw) {
37129
37364
  const cells = line.split("\u2502").map((c3) => c3.trim()).filter((c3) => c3.length > 0);
37130
37365
  if (cells.length < 2)
37131
37366
  continue;
37132
- if (cells[0] === "id" || cells[0] === "name")
37367
+ const queueId = cells[0];
37368
+ const queueName = cells[1];
37369
+ if (!queueId || !queueName)
37133
37370
  continue;
37134
- if (!/^[0-9a-f-]{16,}$/i.test(cells[0]))
37371
+ if (queueId === "id" || queueId === "name")
37372
+ continue;
37373
+ if (!/^[0-9a-f-]{16,}$/i.test(queueId))
37135
37374
  continue;
37136
37375
  const producers = cells[4] !== undefined && cells[4] !== "" ? Number(cells[4]) : undefined;
37137
37376
  const consumers = cells[5] !== undefined && cells[5] !== "" ? Number(cells[5]) : undefined;
37138
37377
  queues.push({
37139
- queue_id: cells[0],
37140
- queue_name: cells[1],
37378
+ queue_id: queueId,
37379
+ queue_name: queueName,
37141
37380
  created_on: cells[2],
37142
37381
  modified_on: cells[3],
37143
37382
  producers_total_count: Number.isFinite(producers) ? producers : undefined,
@@ -37322,57 +37561,151 @@ SUBCOMMANDS:
37322
37561
  }, { service: "monitor" }));
37323
37562
  }
37324
37563
  // src/commands/workers/workers-command.ts
37564
+ init_src();
37325
37565
  init_config2();
37566
+ init_errors4();
37326
37567
  async function doListWorkers(fmt) {
37327
37568
  const configService = new ConfigService;
37328
37569
  await configService.load();
37329
- const workers = configService.listWorkers();
37330
- const rows = workers.map((name) => {
37331
- const worker = configService.getWorker(name);
37332
- return {
37333
- Worker: name,
37334
- Status: worker?.enabled ? "enabled" : "disabled",
37335
- Path: worker?.path ?? "-",
37336
- Secrets: String(worker?.secrets?.length ?? 0)
37337
- };
37338
- });
37570
+ const statuses = configService.listWorkerStatuses();
37571
+ const rows = statuses.map((w) => ({
37572
+ Worker: w.name,
37573
+ Status: w.enabled ? "enabled" : "disabled",
37574
+ Default: w.defaultEnabled === undefined ? "-" : w.defaultEnabled ? "enabled" : "disabled",
37575
+ Path: w.path,
37576
+ Config: w.inConfig ? "yes" : "no",
37577
+ Catalog: w.inCatalog ? "yes" : "no"
37578
+ }));
37339
37579
  if (fmt.json) {
37340
- formatJson(rows, fmt);
37580
+ formatJson(statuses.map((w) => ({
37581
+ name: w.name,
37582
+ enabled: w.enabled,
37583
+ defaultEnabled: w.defaultEnabled ?? null,
37584
+ path: w.path,
37585
+ inConfig: w.inConfig,
37586
+ inCatalog: w.inCatalog
37587
+ })), fmt);
37341
37588
  return;
37342
37589
  }
37343
37590
  formatTable(rows, fmt);
37344
37591
  }
37592
+ function doCatalog(fmt) {
37593
+ const rows = WORKER_CATALOG.map((w) => ({
37594
+ Worker: w.name,
37595
+ Default: w.defaultEnabled ? "enabled" : "disabled",
37596
+ Path: w.path
37597
+ }));
37598
+ if (fmt.json) {
37599
+ formatJson(WORKER_CATALOG.map((w) => ({
37600
+ name: w.name,
37601
+ defaultEnabled: w.defaultEnabled,
37602
+ path: w.path
37603
+ })), fmt);
37604
+ return;
37605
+ }
37606
+ formatTable(rows, fmt);
37607
+ }
37608
+ async function doSetEnabled(names, enabled, fmt) {
37609
+ if (names.length === 0) {
37610
+ throw new CLIError(`Specify at least one worker name (e.g. hoox workers ${enabled ? "enable" : "disable"} trade-worker)`, 2 /* INVALID_USAGE */, undefined, true, `Run \`hoox workers catalog\` to see known workers.`);
37611
+ }
37612
+ const configService = new ConfigService;
37613
+ await configService.load();
37614
+ let results;
37615
+ try {
37616
+ results = await configService.setWorkersEnabled(names, enabled);
37617
+ } catch (e) {
37618
+ const msg = e instanceof Error ? e.message : String(e);
37619
+ throw new CLIError(msg, 2 /* INVALID_USAGE */, undefined, true);
37620
+ }
37621
+ if (fmt.json) {
37622
+ formatJson({ action: enabled ? "enable" : "disable", results }, fmt);
37623
+ return;
37624
+ }
37625
+ for (const r2 of results) {
37626
+ const seedNote = r2.seeded ? " (seeded from catalog)" : "";
37627
+ formatSuccess(`${r2.name} \u2192 ${r2.enabled ? "enabled" : "disabled"}${seedNote}`, fmt);
37628
+ }
37629
+ }
37345
37630
  function registerWorkersCommand(program2) {
37346
- const workersCmd = program2.command("workers").summary("Manage Cloudflare Workers (list, dev, logs)").description(`Manage your Hoox Cloudflare Workers.
37631
+ const workersCmd = program2.command("workers").summary("Manage Cloudflare Workers (list, enable, disable, dev, logs)").description(`Manage your Hoox Cloudflare Workers.
37347
37632
 
37348
37633
  SUBCOMMANDS:
37349
- list List all workers with status, path, and secrets count
37634
+ list List all workers (catalog + config) with status
37635
+ catalog Show the canonical worker catalog and defaults
37636
+ enable <name...> Enable workers in wrangler.jsonc
37637
+ disable <name...> Disable workers in wrangler.jsonc
37350
37638
  dev <name> Start a worker for local development (delegates to dev worker)
37351
37639
  logs <name> Tail logs for a specific worker (delegates to logs worker)
37352
37640
 
37641
+ The worker catalog (shared package) is the source of all known workers and
37642
+ their default enabled flags. Enable/disable updates workers.<name>.enabled
37643
+ in the root wrangler.jsonc. Missing catalog workers are seeded on enable.
37644
+
37353
37645
  For health checks, use 'hoox check health'.
37354
37646
  For deployments, use 'hoox deploy workers' or 'hoox deploy worker <name>'.
37355
37647
 
37356
37648
  EXAMPLES:
37357
- hoox workers list List all workers
37358
- hoox workers list --json List workers as JSON (respects global --json)
37359
- hoox workers dev trade-worker Start dev server for trade-worker
37360
- hoox workers logs hoox Tail logs for the hoox gateway`);
37361
- workersCmd.command("list").summary("List all workers with their enabled/disabled status").description(`List all workers defined in wrangler.jsonc.
37362
-
37363
- Displays each worker's:
37364
- - Name
37365
- - Status (enabled / disabled)
37366
- - Path (relative from project root)
37367
- - Secrets (count of Cloudflare secrets configured)
37649
+ hoox workers list
37650
+ hoox workers catalog
37651
+ hoox workers enable trade-worker agent-worker
37652
+ hoox workers disable web3-wallet-worker email-worker
37653
+ hoox workers dev trade-worker
37654
+ hoox workers logs hoox`);
37655
+ workersCmd.command("list").summary("List workers with enabled status (catalog + config)").description(`List all workers from the shared catalog merged with wrangler.jsonc.
37656
+
37657
+ Columns:
37658
+ - Worker name
37659
+ - Status (effective enabled/disabled)
37660
+ - Default (catalog default)
37661
+ - Path
37662
+ - Config / Catalog membership
37368
37663
 
37369
37664
  EXAMPLES:
37370
- hoox workers list Default table output
37371
- hoox workers list --json JSON-formatted output
37372
- hoox workers list --quiet Minimal output`).action(withErrorHandling(async (_opts, cmd) => {
37665
+ hoox workers list
37666
+ hoox workers list --json`).action(withErrorHandling(async (_opts, cmd) => {
37373
37667
  const fmt = getFormatOptions(cmd);
37374
37668
  await doListWorkers(fmt);
37375
37669
  }, { service: "workers" }));
37670
+ workersCmd.command("catalog").summary("Show the canonical worker catalog with defaults").description(`Print the shared worker catalog \u2014 every known worker and its default
37671
+ enabled flag. This is the source of truth used when seeding missing workers
37672
+ on enable.
37673
+
37674
+ EXAMPLES:
37675
+ hoox workers catalog
37676
+ hoox workers catalog --json`).action(withErrorHandling(async (_opts, cmd) => {
37677
+ const fmt = getFormatOptions(cmd);
37678
+ doCatalog(fmt);
37679
+ }, { service: "workers" }));
37680
+ workersCmd.command("enable").argument("<names...>", "Worker name(s) to enable").summary("Enable one or more workers in wrangler.jsonc").description(`Set workers.<name>.enabled = true in the root wrangler.jsonc.
37681
+
37682
+ If a worker exists in the catalog but not in config, it is seeded with
37683
+ path/secrets from the catalog and enabled.
37684
+
37685
+ ARGUMENTS:
37686
+ names One or more worker names (e.g. trade-worker email-worker)
37687
+
37688
+ EXAMPLES:
37689
+ hoox workers enable trade-worker
37690
+ hoox workers enable report-worker web3-wallet-worker
37691
+ hoox workers enable dashboard --json`).action(withErrorHandling(async (names, _opts, cmd) => {
37692
+ const fmt = getFormatOptions(cmd);
37693
+ await doSetEnabled(names, true, fmt);
37694
+ }, { service: "workers" }));
37695
+ workersCmd.command("disable").argument("<names...>", "Worker name(s) to disable").summary("Disable one or more workers in wrangler.jsonc").description(`Set workers.<name>.enabled = false in the root wrangler.jsonc.
37696
+
37697
+ Disabled workers are skipped by deploy/logs/all flows that use
37698
+ ConfigService.listEnabledWorkers().
37699
+
37700
+ ARGUMENTS:
37701
+ names One or more worker names
37702
+
37703
+ EXAMPLES:
37704
+ hoox workers disable email-worker
37705
+ hoox workers disable web3-wallet-worker report-worker`).action(withErrorHandling(async (names, _opts, cmd) => {
37706
+ const fmt = getFormatOptions(cmd);
37707
+ await doSetEnabled(names, false, fmt);
37708
+ }, { service: "workers" }));
37376
37709
  workersCmd.command("dev <name>").summary("Start a single worker for local development").description(`Start a specific worker for local development by running \`hoox dev worker <name>\` in-process.
37377
37710
 
37378
37711
  ARGUMENTS:
@@ -37918,10 +38251,10 @@ import { existsSync as existsSync12, realpathSync } from "fs";
37918
38251
  import { homedir as homedir4 } from "os";
37919
38252
 
37920
38253
  // src/services/perf/endpoint-resolver.ts
37921
- import { readFileSync as readFileSync8 } from "fs";
38254
+ import { readFileSync as readFileSync9 } from "fs";
37922
38255
  function readSubdomainPrefix() {
37923
38256
  try {
37924
- const raw = readFileSync8("wrangler.jsonc", "utf8");
38257
+ const raw = readFileSync9("wrangler.jsonc", "utf8");
37925
38258
  const stripped = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
37926
38259
  const parsed = JSON.parse(stripped);
37927
38260
  const prefix = parsed.global?.subdomain_prefix;
@@ -38965,7 +39298,7 @@ function registerDisclaimerCommand(program2) {
38965
39298
  }));
38966
39299
  }
38967
39300
  // src/commands/agent/agent-command.ts
38968
- import { readFileSync as readFileSync9 } from "fs";
39301
+ import { readFileSync as readFileSync10 } from "fs";
38969
39302
  init_errors4();
38970
39303
  var PROVIDERS = [
38971
39304
  {
@@ -38999,7 +39332,7 @@ function checkDevVars() {
38999
39332
  const candidates = ["workers/agent-worker/.dev.vars", ".dev.vars"];
39000
39333
  for (const filePath of candidates) {
39001
39334
  try {
39002
- const content = readFileSync9(filePath, "utf-8");
39335
+ const content = readFileSync10(filePath, "utf-8");
39003
39336
  for (const line of content.split(`
39004
39337
  `)) {
39005
39338
  const trimmed = line.trim();
@@ -39530,7 +39863,7 @@ EXAMPLES:
39530
39863
  }
39531
39864
  // src/commands/pyne/pyne-command.ts
39532
39865
  init_config2();
39533
- import { existsSync as existsSync14, readFileSync as readFileSync10 } from "fs";
39866
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
39534
39867
  import { resolve as resolve8 } from "path";
39535
39868
  init_errors4();
39536
39869
  var DEFAULT_TIMEOUT_MS = 30000;
@@ -39556,7 +39889,7 @@ function loadDevVars(workerDir) {
39556
39889
  try {
39557
39890
  if (!existsSync14(filePath))
39558
39891
  continue;
39559
- const content = readFileSync10(filePath, "utf-8");
39892
+ const content = readFileSync11(filePath, "utf-8");
39560
39893
  for (const line of content.split(`
39561
39894
  `)) {
39562
39895
  const trimmed = line.trim();
@@ -39775,7 +40108,7 @@ EXAMPLES:
39775
40108
  if (!existsSync14(abs)) {
39776
40109
  throw new CLIError(`Script not found: ${abs}`, 1 /* ERROR */);
39777
40110
  }
39778
- body.script = readFileSync10(abs, "utf-8");
40111
+ body.script = readFileSync11(abs, "utf-8");
39779
40112
  }
39780
40113
  const res = await pyneFetch(baseUrl, "/run", {
39781
40114
  method: "POST",
@@ -39826,7 +40159,7 @@ EXAMPLES:
39826
40159
  if (!existsSync14(abs)) {
39827
40160
  throw new CLIError(`Script not found: ${abs}`, 1 /* ERROR */);
39828
40161
  }
39829
- const script = readFileSync10(abs, "utf-8");
40162
+ const script = readFileSync11(abs, "utf-8");
39830
40163
  const baseName = abs.split(/[/\\]/).pop() ?? "script.pine";
39831
40164
  const scriptId = opts.id ?? baseName.replace(/\.pine$/i, "").replace(/[^a-zA-Z0-9_-]/g, "_");
39832
40165
  const res = await pyneFetch(baseUrl, "/scripts", {
@@ -40189,15 +40522,21 @@ function parseTime(input) {
40189
40522
  return;
40190
40523
  const relativeMatch = input.match(/^(\d+)([smhd])$/);
40191
40524
  if (relativeMatch) {
40192
- const value = parseInt(relativeMatch[1], 10);
40525
+ const rawValue = relativeMatch[1];
40193
40526
  const unit = relativeMatch[2];
40527
+ if (!rawValue || !unit)
40528
+ return;
40529
+ const value = parseInt(rawValue, 10);
40194
40530
  const multipliers = {
40195
40531
  s: 1000,
40196
40532
  m: 60 * 1000,
40197
40533
  h: 60 * 60 * 1000,
40198
40534
  d: 24 * 60 * 60 * 1000
40199
40535
  };
40200
- return Date.now() - value * multipliers[unit];
40536
+ const mult = multipliers[unit];
40537
+ if (mult === undefined)
40538
+ return;
40539
+ return Date.now() - value * mult;
40201
40540
  }
40202
40541
  const ms = new Date(input).getTime();
40203
40542
  return Number.isFinite(ms) ? ms : undefined;
@@ -41489,7 +41828,7 @@ function registerCompletionCommand(program2) {
41489
41828
  // src/ui/banner.ts
41490
41829
  init_ansis();
41491
41830
  init_theme();
41492
- import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
41831
+ import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
41493
41832
  import { fileURLToPath as fileURLToPath2 } from "url";
41494
41833
  import { dirname as dirname4, join as join13 } from "path";
41495
41834
  var ORANGE = ansis_default.hex("#ff7f2a");
@@ -41507,7 +41846,7 @@ function findCliVersion() {
41507
41846
  const pkgPath = join13(dir, "package.json");
41508
41847
  if (existsSync15(pkgPath)) {
41509
41848
  try {
41510
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
41849
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
41511
41850
  if (pkg.name === PKG_NAME)
41512
41851
  return pkg.version;
41513
41852
  } catch {}
@@ -42142,7 +42481,7 @@ if (typeof Bun === "undefined") {
42142
42481
  `);
42143
42482
  process.exit(1);
42144
42483
  }
42145
- var pkgVersion = JSON.parse(readFileSync12(new URL("../package.json", import.meta.url), "utf-8")).version;
42484
+ var pkgVersion = JSON.parse(readFileSync13(new URL("../package.json", import.meta.url), "utf-8")).version;
42146
42485
  var program2 = new Command;
42147
42486
  program2.name("hoox").description("Hoox CLI \u2014 manage Cloudflare Workers, infrastructure, secrets, and deployments").version(`${pkgVersion}
42148
42487
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoox-sh/hoox-cli",
3
- "version": "0.11.8",
3
+ "version": "0.12.0",
4
4
  "description": "Hoox CLI — manage Cloudflare Workers, infrastructure, secrets, and deployments (@hoox-sh/hoox-cli)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,7 +26,7 @@
26
26
  "@hoox-sh/hoox-tui": "workspace:*"
27
27
  },
28
28
  "devDependencies": {
29
- "@hoox-sh/hoox-shared": "^1.3.0",
29
+ "@hoox-sh/hoox-shared": "^1.4.0",
30
30
  "@types/node": "^26.1.1",
31
31
  "bun-types": "^1.3.14",
32
32
  "typescript": "^6.0.3"