@hoox-sh/hoox-cli 0.11.9 → 0.13.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 +586 -176
  3. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -14691,7 +14691,7 @@ var init_zod = __esm(() => {
14691
14691
  });
14692
14692
 
14693
14693
  // ../shared/src/types.ts
14694
- var TradeActionSchema, WebhookPayloadSchema, TradeQueueMessageSchema, TradeSignalSchema, PositionSchema, BalanceSchema;
14694
+ var TradeActionSchema, MAX_WEBHOOK_LEVERAGE = 125, MAX_WEBHOOK_QUANTITY = 1e6, WebhookPayloadSchema, TradeQueueMessageSchema, TradeSignalSchema, PositionSchema, BalanceSchema;
14695
14695
  var init_types = __esm(() => {
14696
14696
  init_zod();
14697
14697
  TradeActionSchema = exports_external.enum([
@@ -14704,10 +14704,10 @@ var init_types = __esm(() => {
14704
14704
  exchange: exports_external.string().min(1),
14705
14705
  action: TradeActionSchema,
14706
14706
  symbol: exports_external.string().min(1).max(20),
14707
- quantity: exports_external.number().positive().finite(),
14707
+ quantity: exports_external.number().positive().finite().max(MAX_WEBHOOK_QUANTITY),
14708
14708
  price: exports_external.number().positive().finite().optional(),
14709
14709
  orderType: exports_external.string().optional(),
14710
- leverage: exports_external.number().int().positive().optional(),
14710
+ leverage: exports_external.number().int().positive().max(MAX_WEBHOOK_LEVERAGE).optional(),
14711
14711
  test: exports_external.boolean().optional()
14712
14712
  }).strict();
14713
14713
  TradeQueueMessageSchema = exports_external.object({
@@ -14715,9 +14715,9 @@ var init_types = __esm(() => {
14715
14715
  exchange: exports_external.string().min(1).max(64),
14716
14716
  action: TradeActionSchema,
14717
14717
  symbol: exports_external.string().min(1).max(32),
14718
- quantity: exports_external.number().positive().finite(),
14718
+ quantity: exports_external.number().positive().finite().max(MAX_WEBHOOK_QUANTITY),
14719
14719
  price: exports_external.number().positive().finite().optional(),
14720
- leverage: exports_external.number().int().positive().max(125).optional(),
14720
+ leverage: exports_external.number().int().positive().max(MAX_WEBHOOK_LEVERAGE).optional(),
14721
14721
  test: exports_external.boolean().optional(),
14722
14722
  idempotencyKey: exports_external.string().min(1).max(512).optional(),
14723
14723
  queuedAt: exports_external.string().min(1).max(64)
@@ -14754,7 +14754,7 @@ var init_types = __esm(() => {
14754
14754
  });
14755
14755
 
14756
14756
  // ../shared/src/kvKeys.ts
14757
- var KV_TRADE_DEFAULT_LEVERAGE = "trade:default_leverage", KV_TRADE_MAX_POSITION_SIZE = "trade:max_position_size", KV_TRADE_MAX_DAILY_DRAWDOWN_PERCENT = "trade:max_daily_drawdown_percent", KV_TRADE_TRAILING_STOP_PERCENT = "trade:trailing_stop_percent", KV_TRADE_KILL_SWITCH = "trade:kill_switch", KV_TRADE_ROUTING = "trade:routing", KV_BOT_ENABLED = "bot:enabled", KV_BOT_DEFAULT_CHAT_ID = "bot:default_chat_id", KV_BOT_NOTIFY_ON_EXECUTION = "bot:notify_on_execution", KV_BOT_NOTIFY_ON_ERROR = "bot:notify_on_error", KV_WEBHOOK_IP_CHECK_ENABLED = "webhook:tradingview:ip_check_enabled", KV_WEBHOOK_ALLOWED_IPS = "webhook:tradingview:allowed_ips", KV_WEBHOOK_QUEUE_MODE = "webhook:queue_mode", KV_EMAIL_SCAN_SUBJECT = "email:scan_subject", KV_EMAIL_USE_IMAP = "email:use_imap", KV_EMAIL_COIN_PATTERN = "email:coin_pattern", KV_EMAIL_ACTION_PATTERN = "email:action_pattern", KV_EMAIL_QUANTITY_MULTIPLIER = "email:quantity_multiplier", KV_AGENT_CONFIG = "agent:config", KV_AGENT_OPENAI_KEY = "agent:openai_key", KV_AGENT_ANTHROPIC_KEY = "agent:anthropic_key", KV_AGENT_GOOGLE_KEY = "agent:google_key", KV_DASHBOARD_AI_HEALTH_SUMMARY = "dashboard:ai_health_summary", KV_GLOBAL_KILL_SWITCH = "global:kill_switch", KV_HEALTH_CHECK = "health_check", KV_HOUSEKEEPING_LAST_CHECK = "housekeeping:last_check", QUEUE_MODE_FAILOVER = "queue_failover", QUEUE_MODE_EVERYWHERE = "queue_everywhere", QUEUE_MODE_DISABLED = "queue_disabled", KVKeys;
14757
+ var KV_TRADE_DEFAULT_LEVERAGE = "trade:default_leverage", KV_TRADE_MAX_POSITION_SIZE = "trade:max_position_size", KV_TRADE_MAX_DAILY_DRAWDOWN_PERCENT = "trade:max_daily_drawdown_percent", KV_TRADE_TRAILING_STOP_PERCENT = "trade:trailing_stop_percent", KV_TRADE_KILL_SWITCH = "trade:kill_switch", KV_TRADE_ROUTING = "trade:routing", KV_BOT_ENABLED = "bot:enabled", KV_BOT_DEFAULT_CHAT_ID = "bot:default_chat_id", KV_BOT_NOTIFY_ON_EXECUTION = "bot:notify_on_execution", KV_BOT_NOTIFY_ON_ERROR = "bot:notify_on_error", KV_TELEGRAM_ALLOWED_CHAT_IDS = "telegram:allowed_chat_ids", KV_WEBHOOK_IP_CHECK_ENABLED = "webhook:tradingview:ip_check_enabled", KV_WEBHOOK_ALLOWED_IPS = "webhook:tradingview:allowed_ips", KV_WEBHOOK_QUEUE_MODE = "webhook:queue_mode", KV_EMAIL_SCAN_SUBJECT = "email:scan_subject", KV_EMAIL_USE_IMAP = "email:use_imap", KV_EMAIL_COIN_PATTERN = "email:coin_pattern", KV_EMAIL_ACTION_PATTERN = "email:action_pattern", KV_EMAIL_QUANTITY_MULTIPLIER = "email:quantity_multiplier", KV_AGENT_CONFIG = "agent:config", KV_AGENT_OPENAI_KEY = "agent:openai_key", KV_AGENT_ANTHROPIC_KEY = "agent:anthropic_key", KV_AGENT_GOOGLE_KEY = "agent:google_key", KV_DASHBOARD_AI_HEALTH_SUMMARY = "dashboard:ai_health_summary", KV_GLOBAL_KILL_SWITCH = "global:kill_switch", KV_HEALTH_CHECK = "health_check", KV_HOUSEKEEPING_LAST_CHECK = "housekeeping:last_check", QUEUE_MODE_FAILOVER = "queue_failover", QUEUE_MODE_EVERYWHERE = "queue_everywhere", QUEUE_MODE_DISABLED = "queue_disabled", KVKeys;
14758
14758
  var init_kvKeys = __esm(() => {
14759
14759
  KVKeys = {
14760
14760
  KV_TRADE_DEFAULT_LEVERAGE,
@@ -14767,6 +14767,7 @@ var init_kvKeys = __esm(() => {
14767
14767
  KV_BOT_DEFAULT_CHAT_ID,
14768
14768
  KV_BOT_NOTIFY_ON_EXECUTION,
14769
14769
  KV_BOT_NOTIFY_ON_ERROR,
14770
+ KV_TELEGRAM_ALLOWED_CHAT_IDS,
14770
14771
  KV_WEBHOOK_IP_CHECK_ENABLED,
14771
14772
  KV_WEBHOOK_ALLOWED_IPS,
14772
14773
  KV_WEBHOOK_QUEUE_MODE,
@@ -14843,8 +14844,10 @@ function toError(err, fallback = "Unknown error") {
14843
14844
  return err.message;
14844
14845
  if (typeof err === "string")
14845
14846
  return err;
14846
- if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
14847
- return err.message;
14847
+ if (err && typeof err === "object" && "message" in err) {
14848
+ const message = err.message;
14849
+ if (typeof message === "string")
14850
+ return message;
14848
14851
  }
14849
14852
  if (err === null || err === undefined)
14850
14853
  return fallback;
@@ -15105,6 +15108,52 @@ async function logKvTimestamp(env, prefix = "timestamp") {
15105
15108
  console.error(`Failed to log timestamp to KV: ${toError(error51, "Unknown error")}`);
15106
15109
  }
15107
15110
  }
15111
+ async function tryBulkGetChunk(kv, chunk) {
15112
+ try {
15113
+ const result = await kv.get([...chunk]);
15114
+ if (!(result instanceof Map)) {
15115
+ return null;
15116
+ }
15117
+ return chunk.map((key) => result.get(key) ?? null);
15118
+ } catch {
15119
+ return null;
15120
+ }
15121
+ }
15122
+ async function kvGetMany(kv, keys) {
15123
+ if (keys.length === 0)
15124
+ return [];
15125
+ const out = new Array(keys.length);
15126
+ let offset = 0;
15127
+ while (offset < keys.length) {
15128
+ const chunk = keys.slice(offset, offset + KV_BULK_GET_MAX_KEYS);
15129
+ const bulk = await tryBulkGetChunk(kv, chunk);
15130
+ if (bulk === null) {
15131
+ const rest = await Promise.all(keys.slice(offset).map((key) => kv.get(key)));
15132
+ for (let i = 0;i < rest.length; i++) {
15133
+ out[offset + i] = rest[i] ?? null;
15134
+ }
15135
+ return out;
15136
+ }
15137
+ for (let i = 0;i < bulk.length; i++) {
15138
+ out[offset + i] = bulk[i] ?? null;
15139
+ }
15140
+ offset += chunk.length;
15141
+ }
15142
+ return out;
15143
+ }
15144
+ async function kvGetManyAsRecord(kv, keys) {
15145
+ const values = await kvGetMany(kv, keys);
15146
+ const out = {};
15147
+ for (let i = 0;i < keys.length; i++) {
15148
+ out[keys[i]] = values[i] ?? null;
15149
+ }
15150
+ return out;
15151
+ }
15152
+ async function kvPutMany(kv, entries) {
15153
+ if (entries.length === 0)
15154
+ return;
15155
+ await Promise.all(entries.map((entry) => kv.put(entry.key, entry.value, entry.options)));
15156
+ }
15108
15157
  function headersToObject(headers) {
15109
15158
  const result = {};
15110
15159
  if (!headers)
@@ -15118,6 +15167,7 @@ function headersToObject(headers) {
15118
15167
  }
15119
15168
  return result;
15120
15169
  }
15170
+ var KV_BULK_GET_MAX_KEYS = 100;
15121
15171
  var init_kvUtils = __esm(() => {
15122
15172
  init_errors3();
15123
15173
  });
@@ -15167,29 +15217,60 @@ var init_health = __esm(() => {
15167
15217
  });
15168
15218
 
15169
15219
  // ../shared/src/queue-handler.ts
15220
+ async function mapPool(items, concurrency, fn) {
15221
+ if (items.length === 0)
15222
+ return;
15223
+ const limit = Math.min(Math.max(1, concurrency), items.length);
15224
+ if (limit === 1) {
15225
+ for (const item of items) {
15226
+ await fn(item);
15227
+ }
15228
+ return;
15229
+ }
15230
+ let nextIndex = 0;
15231
+ async function worker() {
15232
+ while (true) {
15233
+ const i = nextIndex++;
15234
+ if (i >= items.length)
15235
+ return;
15236
+ await fn(items[i]);
15237
+ }
15238
+ }
15239
+ await Promise.all(Array.from({ length: limit }, () => worker()));
15240
+ }
15170
15241
  function createQueueHandler(options) {
15171
- const { maxRetries, backoffDelays, onMessage, onRetry, onDLQ, logger } = options;
15172
- return async (batch) => {
15173
- for (const msg of batch.messages) {
15174
- const attemptNumber = msg.attempts || 0;
15175
- const logId = `[${msg.id}]`;
15176
- try {
15177
- logger?.info(`${logId} Processing message (attempt ${attemptNumber + 1})`);
15178
- await onMessage(msg.body, attemptNumber);
15179
- logger?.info(`${logId} Message processed successfully`);
15180
- } catch (error51) {
15181
- const errorMsg = error51 instanceof Error ? error51.message : String(error51);
15182
- if (attemptNumber < maxRetries) {
15183
- const delaySeconds = backoffDelays[attemptNumber] ?? backoffDelays[backoffDelays.length - 1];
15184
- logger?.info(`${logId} Retrying in ${delaySeconds}s (attempt ${attemptNumber + 2}/${maxRetries + 1})`);
15185
- await onRetry?.(msg.body, attemptNumber, errorMsg, delaySeconds);
15186
- msg.retry({ delaySeconds });
15187
- } else {
15188
- logger?.error(`${logId} Max retries exceeded (${maxRetries + 1} attempts), moving to DLQ`);
15189
- await onDLQ?.(msg.body, attemptNumber, errorMsg);
15190
- }
15242
+ const {
15243
+ maxRetries,
15244
+ backoffDelays,
15245
+ onMessage,
15246
+ onRetry,
15247
+ onDLQ,
15248
+ logger,
15249
+ concurrency: concurrencyOpt
15250
+ } = options;
15251
+ const concurrency = typeof concurrencyOpt === "number" && Number.isFinite(concurrencyOpt) && concurrencyOpt >= 1 ? Math.floor(concurrencyOpt) : 1;
15252
+ async function processMessage(msg) {
15253
+ const attemptNumber = msg.attempts || 0;
15254
+ const logId = `[${msg.id}]`;
15255
+ try {
15256
+ logger?.info(`${logId} Processing message (attempt ${attemptNumber + 1})`);
15257
+ await onMessage(msg.body, attemptNumber);
15258
+ logger?.info(`${logId} Message processed successfully`);
15259
+ } catch (error51) {
15260
+ const errorMsg = error51 instanceof Error ? error51.message : String(error51);
15261
+ if (attemptNumber < maxRetries) {
15262
+ const delaySeconds = backoffDelays[attemptNumber] ?? backoffDelays[backoffDelays.length - 1] ?? 0;
15263
+ logger?.info(`${logId} Retrying in ${delaySeconds}s (attempt ${attemptNumber + 2}/${maxRetries + 1})`);
15264
+ await onRetry?.(msg.body, attemptNumber, errorMsg, delaySeconds);
15265
+ msg.retry({ delaySeconds });
15266
+ } else {
15267
+ logger?.error(`${logId} Max retries exceeded (${maxRetries + 1} attempts), moving to DLQ`);
15268
+ await onDLQ?.(msg.body, attemptNumber, errorMsg);
15191
15269
  }
15192
15270
  }
15271
+ }
15272
+ return async (batch) => {
15273
+ await mapPool(batch.messages, concurrency, processMessage);
15193
15274
  };
15194
15275
  }
15195
15276
 
@@ -17078,7 +17159,7 @@ var init_presets = __esm(() => {
17078
17159
  "analytics-worker",
17079
17160
  "telegram-worker"
17080
17161
  ],
17081
- integrations: ["binance", "telegram"]
17162
+ integrations: ["exchange", "telegram"]
17082
17163
  },
17083
17164
  {
17084
17165
  name: "full",
@@ -17095,15 +17176,7 @@ var init_presets = __esm(() => {
17095
17176
  "web3-wallet-worker",
17096
17177
  "pyne-worker"
17097
17178
  ],
17098
- integrations: [
17099
- "binance",
17100
- "bybit",
17101
- "mexc",
17102
- "telegram",
17103
- "openai",
17104
- "wallet",
17105
- "pyne"
17106
- ]
17179
+ integrations: ["exchange", "telegram", "openai", "wallet", "pyne"]
17107
17180
  }
17108
17181
  ];
17109
17182
  WORKER_DEPENDENCIES = {
@@ -17116,30 +17189,12 @@ var init_presets = __esm(() => {
17116
17189
  };
17117
17190
  INTEGRATIONS = [
17118
17191
  {
17119
- key: "binance",
17120
- label: "Binance Exchange",
17121
- workerName: "trade-worker",
17122
- 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",
17192
+ key: "exchange",
17193
+ label: "Exchange API (Binance / Bybit / MEXC)",
17139
17194
  workerName: "trade-worker",
17140
17195
  secrets: {
17141
- BYBIT_KEY_BINDING: "Bybit API Key",
17142
- BYBIT_SECRET_BINDING: "Bybit API Secret"
17196
+ EXCHANGE_KEY_BINDING: "Exchange API Key",
17197
+ EXCHANGE_SECRET_BINDING: "Exchange API Secret"
17143
17198
  }
17144
17199
  },
17145
17200
  {
@@ -17311,7 +17366,11 @@ class WizardEngine {
17311
17366
  return { ...this.state };
17312
17367
  }
17313
17368
  getCurrentStep() {
17314
- return STEPS.find((s) => s.id === this.state.step) ?? STEPS[0];
17369
+ const step = STEPS.find((s) => s.id === this.state.step) ?? STEPS[0];
17370
+ if (!step) {
17371
+ throw new Error("Wizard has no steps defined");
17372
+ }
17373
+ return step;
17315
17374
  }
17316
17375
  getCompletedSteps() {
17317
17376
  return [...this.state.completedSteps];
@@ -17334,8 +17393,11 @@ class WizardEngine {
17334
17393
  this.state.completedSteps.push(this.state.step);
17335
17394
  }
17336
17395
  const currentIdx = STEP_ORDER.indexOf(this.state.step);
17337
- if (currentIdx < STEP_ORDER.length - 1) {
17338
- this.state.step = STEP_ORDER[currentIdx + 1];
17396
+ if (currentIdx >= 0 && currentIdx < STEP_ORDER.length - 1) {
17397
+ const nextStep = STEP_ORDER[currentIdx + 1];
17398
+ if (nextStep !== undefined) {
17399
+ this.state.step = nextStep;
17400
+ }
17339
17401
  }
17340
17402
  this.state.updatedAt = Date.now();
17341
17403
  return [];
@@ -17346,6 +17408,8 @@ class WizardEngine {
17346
17408
  const currentIdx = STEP_ORDER.indexOf(this.state.step);
17347
17409
  if (currentIdx > 0) {
17348
17410
  const previousStep = STEP_ORDER[currentIdx - 1];
17411
+ if (previousStep === undefined)
17412
+ return;
17349
17413
  this.state.step = previousStep;
17350
17414
  this.state.completedSteps = this.state.completedSteps.filter((s) => s !== this.state.step || STEP_ORDER.indexOf(s) <= STEP_ORDER.indexOf(previousStep));
17351
17415
  }
@@ -17573,9 +17637,12 @@ function deriveCalledBy(manifests) {
17573
17637
  if (!calledBy[workerName])
17574
17638
  calledBy[workerName] = new Set;
17575
17639
  for (const svc of manifest.services) {
17576
- if (!calledBy[svc.service])
17577
- calledBy[svc.service] = new Set;
17578
- calledBy[svc.service].add(workerName);
17640
+ let callers = calledBy[svc.service];
17641
+ if (!callers) {
17642
+ callers = new Set;
17643
+ calledBy[svc.service] = callers;
17644
+ }
17645
+ callers.add(workerName);
17579
17646
  }
17580
17647
  }
17581
17648
  const result = {};
@@ -17584,12 +17651,16 @@ function deriveCalledBy(manifests) {
17584
17651
  }
17585
17652
  return result;
17586
17653
  }
17587
- var manifests, CALLED_BY, WORKER_MANIFESTS, WORKER_NAMES;
17654
+ function getWorkerDefaultEnabled(name) {
17655
+ return manifests[name]?.defaultEnabled;
17656
+ }
17657
+ var manifests, CALLED_BY, WORKER_MANIFESTS, WORKER_NAMES, WORKER_CATALOG;
17588
17658
  var init_registry = __esm(() => {
17589
17659
  manifests = {
17590
17660
  hoox: {
17591
17661
  name: "hoox",
17592
17662
  path: "workers/hoox-worker",
17663
+ defaultEnabled: true,
17593
17664
  vars: {
17594
17665
  WEBHOOK_API_KEY_BINDING: {
17595
17666
  type: "secret",
@@ -17599,7 +17670,11 @@ var init_registry = __esm(() => {
17599
17670
  type: "secret",
17600
17671
  description: "Inter-worker auth key"
17601
17672
  },
17602
- HA_TOKEN_BINDING: { type: "secret", description: "Home Assistant token" }
17673
+ HA_TOKEN_BINDING: { type: "secret", description: "Home Assistant token" },
17674
+ TELEGRAM_ALLOWED_CHAT_IDS: {
17675
+ type: "secret",
17676
+ description: "Comma-separated chat IDs for webhook notify (fail-closed when unset); alias AUTHORIZED_CHAT_IDS"
17677
+ }
17603
17678
  },
17604
17679
  services: [
17605
17680
  {
@@ -17625,7 +17700,8 @@ var init_registry = __esm(() => {
17625
17700
  ai: true,
17626
17701
  queues: { producer: ["trade-execution"] },
17627
17702
  durableObjects: [
17628
- { name: "IDEMPOTENCY_STORE", className: "IdempotencyStore" }
17703
+ { name: "IDEMPOTENCY_STORE", className: "IdempotencyStore" },
17704
+ { name: "RATE_LIMITER", className: "RateLimiterStore" }
17629
17705
  ]
17630
17706
  },
17631
17707
  middleware: [
@@ -17640,6 +17716,7 @@ var init_registry = __esm(() => {
17640
17716
  "trade-worker": {
17641
17717
  name: "trade-worker",
17642
17718
  path: "workers/trade-worker",
17719
+ defaultEnabled: true,
17643
17720
  vars: {
17644
17721
  INTERNAL_KEY_BINDING: {
17645
17722
  type: "secret",
@@ -17649,15 +17726,22 @@ var init_registry = __esm(() => {
17649
17726
  type: "secret",
17650
17727
  description: "Telegram outbound auth"
17651
17728
  },
17652
- BINANCE_KEY_BINDING: { type: "secret", description: "Binance API key" },
17653
- BINANCE_SECRET_BINDING: {
17729
+ EXCHANGE_KEY_BINDING: {
17654
17730
  type: "secret",
17655
- description: "Binance API secret"
17731
+ description: "Unified exchange API key (Binance / Bybit / MEXC)"
17656
17732
  },
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" }
17733
+ EXCHANGE_SECRET_BINDING: {
17734
+ type: "secret",
17735
+ description: "Unified exchange API secret"
17736
+ },
17737
+ EXCHANGE_TESTNET_KEY_BINDING: {
17738
+ type: "secret",
17739
+ description: "Optional unified testnet API key"
17740
+ },
17741
+ EXCHANGE_TESTNET_SECRET_BINDING: {
17742
+ type: "secret",
17743
+ description: "Optional unified testnet API secret"
17744
+ }
17661
17745
  },
17662
17746
  services: [
17663
17747
  {
@@ -17699,6 +17783,7 @@ var init_registry = __esm(() => {
17699
17783
  "telegram-worker": {
17700
17784
  name: "telegram-worker",
17701
17785
  path: "workers/telegram-worker",
17786
+ defaultEnabled: true,
17702
17787
  vars: {
17703
17788
  INTERNAL_KEY_BINDING: {
17704
17789
  type: "secret",
@@ -17712,6 +17797,10 @@ var init_registry = __esm(() => {
17712
17797
  TELEGRAM_SECRET_TOKEN: {
17713
17798
  type: "secret",
17714
17799
  description: "Webhook verification token"
17800
+ },
17801
+ AUTHORIZED_CHAT_IDS: {
17802
+ type: "secret",
17803
+ description: "Comma-separated chat IDs; inbound webhook fail-closed when unset; outbound /alert allowlist"
17715
17804
  }
17716
17805
  },
17717
17806
  services: [],
@@ -17732,6 +17821,7 @@ var init_registry = __esm(() => {
17732
17821
  "d1-worker": {
17733
17822
  name: "d1-worker",
17734
17823
  path: "workers/d1-worker",
17824
+ defaultEnabled: true,
17735
17825
  vars: {
17736
17826
  INTERNAL_KEY_BINDING: {
17737
17827
  type: "secret",
@@ -17754,6 +17844,7 @@ var init_registry = __esm(() => {
17754
17844
  "web3-wallet-worker": {
17755
17845
  name: "web3-wallet-worker",
17756
17846
  path: "workers/web3-wallet-worker",
17847
+ defaultEnabled: false,
17757
17848
  vars: {
17758
17849
  WALLET_PK_SECRET: { type: "secret", description: "Wallet private key" },
17759
17850
  WALLET_MNEMONIC_SECRET: {
@@ -17774,6 +17865,7 @@ var init_registry = __esm(() => {
17774
17865
  "agent-worker": {
17775
17866
  name: "agent-worker",
17776
17867
  path: "workers/agent-worker",
17868
+ defaultEnabled: true,
17777
17869
  vars: {
17778
17870
  AGENT_INTERNAL_KEY: { type: "secret", description: "Agent worker auth" },
17779
17871
  INTERNAL_KEY_BINDING: {
@@ -17810,6 +17902,7 @@ var init_registry = __esm(() => {
17810
17902
  "email-worker": {
17811
17903
  name: "email-worker",
17812
17904
  path: "workers/email-worker",
17905
+ defaultEnabled: false,
17813
17906
  vars: {
17814
17907
  INTERNAL_KEY_BINDING: {
17815
17908
  type: "secret",
@@ -17847,6 +17940,7 @@ var init_registry = __esm(() => {
17847
17940
  "analytics-worker": {
17848
17941
  name: "analytics-worker",
17849
17942
  path: "workers/analytics-worker",
17943
+ defaultEnabled: true,
17850
17944
  vars: {
17851
17945
  CLOUDFLARE_API_TOKEN: {
17852
17946
  type: "secret",
@@ -17866,6 +17960,7 @@ var init_registry = __esm(() => {
17866
17960
  "report-worker": {
17867
17961
  name: "report-worker",
17868
17962
  path: "workers/report-worker",
17963
+ defaultEnabled: false,
17869
17964
  vars: {
17870
17965
  CF_API_TOKEN_BINDING: {
17871
17966
  type: "secret",
@@ -17900,6 +17995,7 @@ var init_registry = __esm(() => {
17900
17995
  "pyne-worker": {
17901
17996
  name: "pyne-worker",
17902
17997
  path: "workers/pyne-worker",
17998
+ defaultEnabled: true,
17903
17999
  vars: {
17904
18000
  API_KEY: {
17905
18001
  type: "secret",
@@ -17937,6 +18033,7 @@ var init_registry = __esm(() => {
17937
18033
  dashboard: {
17938
18034
  name: "dashboard",
17939
18035
  path: "workers/dashboard",
18036
+ defaultEnabled: true,
17940
18037
  vars: {
17941
18038
  DASHBOARD_USER: {
17942
18039
  type: "secret",
@@ -17979,6 +18076,14 @@ var init_registry = __esm(() => {
17979
18076
  CALLED_BY = deriveCalledBy(manifests);
17980
18077
  WORKER_MANIFESTS = manifests;
17981
18078
  WORKER_NAMES = Object.keys(manifests).sort();
18079
+ WORKER_CATALOG = WORKER_NAMES.map((name) => {
18080
+ const m = manifests[name];
18081
+ return {
18082
+ name: m.name,
18083
+ path: m.path,
18084
+ defaultEnabled: m.defaultEnabled
18085
+ };
18086
+ });
17982
18087
  });
17983
18088
 
17984
18089
  // ../../node_modules/.bun/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
@@ -19618,7 +19723,7 @@ function validateWranglerJsonc(workerName, manifest, jsoncContent) {
19618
19723
  }
19619
19724
  }
19620
19725
  for (const [name, def] of Object.entries(manifest.vars)) {
19621
- if (def.type === "secret" && !name.endsWith("_BINDING") && !name.endsWith("_SECRET") && !name.startsWith("CLOUDFLARE_") && name !== "TELEGRAM_SECRET_TOKEN" && name !== "MAILGUN_API_KEY" && name !== "EMAIL_SCAN_SUBJECT" && name !== "DASHBOARD_USER" && name !== "DASHBOARD_PASS" && name !== "SESSION_SECRET" && name !== "AGENT_INTERNAL_KEY") {
19726
+ if (def.type === "secret" && !name.endsWith("_BINDING") && !name.endsWith("_SECRET") && !name.startsWith("CLOUDFLARE_") && name !== "TELEGRAM_SECRET_TOKEN" && name !== "TELEGRAM_ALLOWED_CHAT_IDS" && name !== "AUTHORIZED_CHAT_IDS" && name !== "MAILGUN_API_KEY" && name !== "EMAIL_SCAN_SUBJECT" && name !== "DASHBOARD_USER" && name !== "DASHBOARD_PASS" && name !== "SESSION_SECRET" && name !== "AGENT_INTERNAL_KEY") {
19622
19727
  errors3.push({
19623
19728
  worker: workerName,
19624
19729
  severity: "warning",
@@ -19735,6 +19840,8 @@ function generateWranglerJsonc(manifest) {
19735
19840
  parts.push(` "vars": {`);
19736
19841
  for (const [i, name] of varKeys.entries()) {
19737
19842
  const def = manifest.vars[name];
19843
+ if (!def)
19844
+ continue;
19738
19845
  const comma = i < varKeys.length - 1 ? "," : "";
19739
19846
  const val = def.type === "secret" ? "__SECRET__" : JSON.stringify(def.default ?? "");
19740
19847
  parts.push(` "${name}": ${val}${comma}`);
@@ -21561,7 +21668,7 @@ function getBackoffDelay(retryCount) {
21561
21668
  if (retryCount <= 0)
21562
21669
  return 0;
21563
21670
  const index = Math.min(retryCount - 1, BACKOFF_SEQUENCE.length - 1);
21564
- return BACKOFF_SEQUENCE[index];
21671
+ return BACKOFF_SEQUENCE[index] ?? 0;
21565
21672
  }
21566
21673
  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
21674
  var init_service_store = __esm(() => {
@@ -22218,7 +22325,10 @@ __export(exports_src, {
22218
22325
  logKvTimestamp: () => logKvTimestamp,
22219
22326
  loadDashboardManifestsFromRoot: () => loadDashboardManifestsFromRoot,
22220
22327
  loadDashboardKvManifestFromRoot: () => loadDashboardKvManifestFromRoot,
22328
+ kvPutMany: () => kvPutMany,
22221
22329
  kvManifestFromDashboardManifests: () => kvManifestFromDashboardManifests,
22330
+ kvGetManyAsRecord: () => kvGetManyAsRecord,
22331
+ kvGetMany: () => kvGetMany,
22222
22332
  isWithinHooxHome: () => isWithinHooxHome,
22223
22333
  isUnixModeGroupOrWorldAccessible: () => isUnixModeGroupOrWorldAccessible,
22224
22334
  isTruthyKillSwitchFlag: () => isTruthyKillSwitchFlag,
@@ -22233,6 +22343,7 @@ __export(exports_src, {
22233
22343
  healthCheck: () => healthCheck,
22234
22344
  headersToObject: () => headersToObject,
22235
22345
  hasOperatorClientCredentials: () => hasOperatorClientCredentials,
22346
+ getWorkerDefaultEnabled: () => getWorkerDefaultEnabled,
22236
22347
  getTuiEntryCandidates: () => getTuiEntryCandidates,
22237
22348
  getRememberedMonorepoRoot: () => getRememberedMonorepoRoot,
22238
22349
  getRememberedMonorepoPath: () => getRememberedMonorepoPath,
@@ -22295,6 +22406,7 @@ __export(exports_src, {
22295
22406
  WORKER_NAMES: () => WORKER_NAMES,
22296
22407
  WORKER_MANIFESTS: () => WORKER_MANIFESTS,
22297
22408
  WORKER_DEPENDENCIES: () => WORKER_DEPENDENCIES,
22409
+ WORKER_CATALOG: () => WORKER_CATALOG,
22298
22410
  WIZARD_STATE_PATH: () => WIZARD_STATE_PATH,
22299
22411
  WALLET_EXECUTE_AUTH_KEY_FIELDS: () => WALLET_EXECUTE_AUTH_KEY_FIELDS,
22300
22412
  TradeSignalSchema: () => TradeSignalSchema,
@@ -22309,6 +22421,7 @@ __export(exports_src, {
22309
22421
  PositionSchema: () => PositionSchema,
22310
22422
  PRESETS: () => PRESETS,
22311
22423
  LogLevelColor: () => LogLevelColor,
22424
+ KV_BULK_GET_MAX_KEYS: () => KV_BULK_GET_MAX_KEYS,
22312
22425
  KVKeys: () => KVKeys,
22313
22426
  KILL_SWITCH_KEYS: () => KILL_SWITCH_KEYS,
22314
22427
  KILL_SWITCH_ACTIVE_PREFIX: () => KILL_SWITCH_ACTIVE_PREFIX,
@@ -25257,6 +25370,76 @@ ${e.errors.map((m3) => ` - ${m3}`).join(`
25257
25370
  const config2 = this.ensureLoaded();
25258
25371
  return Object.entries(config2.workers).filter(([, w]) => w.enabled).map(([name]) => name);
25259
25372
  }
25373
+ listWorkerStatuses() {
25374
+ const config2 = this.ensureLoaded();
25375
+ const names = new Set([
25376
+ ...WORKER_CATALOG.map((w) => w.name),
25377
+ ...Object.keys(config2.workers ?? {})
25378
+ ]);
25379
+ return [...names].sort().map((name) => {
25380
+ const catalog = WORKER_MANIFESTS[name];
25381
+ const cfg = config2.workers?.[name];
25382
+ return {
25383
+ name,
25384
+ path: cfg?.path ?? catalog?.path ?? `workers/${name}`,
25385
+ enabled: cfg?.enabled ?? catalog?.defaultEnabled ?? false,
25386
+ defaultEnabled: catalog?.defaultEnabled,
25387
+ inConfig: Boolean(cfg),
25388
+ inCatalog: Boolean(catalog)
25389
+ };
25390
+ });
25391
+ }
25392
+ async setWorkersEnabled(names, enabled) {
25393
+ this.ensureLoaded();
25394
+ const filePath = this.configPath;
25395
+ const file2 = Bun.file(filePath);
25396
+ if (!await file2.exists()) {
25397
+ throw new Error(`Config file not found: ${filePath}`);
25398
+ }
25399
+ let raw = await file2.text();
25400
+ const results = [];
25401
+ const unique = [...new Set(names.map((n3) => n3.trim()).filter(Boolean))];
25402
+ for (const name of unique) {
25403
+ const existing = this.config.workers?.[name];
25404
+ const catalog = WORKER_MANIFESTS[name];
25405
+ if (!existing && !catalog) {
25406
+ throw new Error(`Unknown worker "${name}". Known workers: ${WORKER_CATALOG.map((w) => w.name).join(", ")}`);
25407
+ }
25408
+ let seeded = false;
25409
+ if (!existing) {
25410
+ const entry = {
25411
+ enabled,
25412
+ path: catalog.path,
25413
+ vars: {},
25414
+ secrets: []
25415
+ };
25416
+ const seedEdits = modify(raw, ["workers", name], entry, {
25417
+ formattingOptions: JSONC_FORMAT,
25418
+ isArrayInsertion: false
25419
+ });
25420
+ raw = applyEdits(raw, seedEdits);
25421
+ seeded = true;
25422
+ } else {
25423
+ const edits = modify(raw, ["workers", name, "enabled"], enabled, {
25424
+ formattingOptions: JSONC_FORMAT
25425
+ });
25426
+ raw = applyEdits(raw, edits);
25427
+ }
25428
+ results.push({ name, enabled, seeded });
25429
+ }
25430
+ await Bun.write(filePath, raw);
25431
+ await this.load(filePath);
25432
+ return results;
25433
+ }
25434
+ async enableWorker(name) {
25435
+ await this.setWorkersEnabled([name], true);
25436
+ }
25437
+ async disableWorker(name) {
25438
+ await this.setWorkersEnabled([name], false);
25439
+ }
25440
+ getWorkerDefaultEnabled(name) {
25441
+ return getWorkerDefaultEnabled(name);
25442
+ }
25260
25443
  validate() {
25261
25444
  const errors4 = [];
25262
25445
  if (!this.config) {
@@ -25309,9 +25492,16 @@ ${e.errors.map((m3) => ` - ${m3}`).join(`
25309
25492
  return this.config;
25310
25493
  }
25311
25494
  }
25495
+ var JSONC_FORMAT;
25312
25496
  var init_config_service = __esm(() => {
25313
25497
  init_main();
25314
25498
  init_src();
25499
+ JSONC_FORMAT = {
25500
+ tabSize: 2,
25501
+ insertSpaces: true,
25502
+ eol: `
25503
+ `
25504
+ };
25315
25505
  });
25316
25506
 
25317
25507
  // src/services/config/index.ts
@@ -25758,6 +25948,9 @@ class PrerequisitesService {
25758
25948
  return { outdated: false };
25759
25949
  }
25760
25950
  const current2 = match[1];
25951
+ if (!current2) {
25952
+ return { outdated: false };
25953
+ }
25761
25954
  const meetsMin = this.satisfies(current2, this.MINIMUM_WRANGLER);
25762
25955
  return {
25763
25956
  outdated: !meetsMin,
@@ -25814,7 +26007,7 @@ class PrerequisitesService {
25814
26007
  });
25815
26008
  const stdout2 = await new Response(proc.stdout).text();
25816
26009
  const match = stdout2.match(/(\d+\.\d+)/);
25817
- const version2 = match ? match[1] : "unknown";
26010
+ const version2 = match?.[1] ?? "unknown";
25818
26011
  const [major, minor] = version2.split(".").map(Number);
25819
26012
  const passed = (major ?? 0) >= 2 && (minor ?? 0) >= 40;
25820
26013
  return {
@@ -26334,7 +26527,7 @@ var exports_schema_service = {};
26334
26527
  __export(exports_schema_service, {
26335
26528
  SchemaService: () => SchemaService
26336
26529
  });
26337
- import { readFileSync as readFileSync7, existsSync as existsSync11 } from "fs";
26530
+ import { readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
26338
26531
  import { resolve as resolve4 } from "path";
26339
26532
 
26340
26533
  class SchemaService {
@@ -26370,7 +26563,7 @@ class SchemaService {
26370
26563
  const errors4 = [];
26371
26564
  const isHooxProject = existsSync11(resolve4(this.projectRoot, "wrangler.jsonc")) || existsSync11(resolve4(this.projectRoot, "workers"));
26372
26565
  try {
26373
- const wranglerContent = readFileSync7(workerWranglerPath, "utf-8");
26566
+ const wranglerContent = readFileSync8(workerWranglerPath, "utf-8");
26374
26567
  errors4.push(...validateWranglerJsonc(name, manifest, wranglerContent));
26375
26568
  } catch (e) {
26376
26569
  const isErrnoException = e instanceof Error && "code" in e;
@@ -26384,7 +26577,7 @@ class SchemaService {
26384
26577
  });
26385
26578
  }
26386
26579
  try {
26387
- const rootContent = readFileSync7(rootWranglerPath, "utf-8");
26580
+ const rootContent = readFileSync8(rootWranglerPath, "utf-8");
26388
26581
  errors4.push(...validateRootSecrets(name, manifest, rootContent));
26389
26582
  } catch (e) {
26390
26583
  const errMessage = e instanceof Error ? e.message : String(e);
@@ -26395,7 +26588,7 @@ class SchemaService {
26395
26588
  });
26396
26589
  }
26397
26590
  try {
26398
- const devVarsContent = readFileSync7(devVarsPath, "utf-8");
26591
+ const devVarsContent = readFileSync8(devVarsPath, "utf-8");
26399
26592
  errors4.push(...validateDevVars(name, manifest, devVarsContent));
26400
26593
  } catch (e) {
26401
26594
  const errMessage = e instanceof Error ? e.message : String(e);
@@ -26654,7 +26847,7 @@ var init_trace_service = __esm(() => {
26654
26847
  });
26655
26848
 
26656
26849
  // src/index.ts
26657
- import { readFileSync as readFileSync12 } from "fs";
26850
+ import { readFileSync as readFileSync13 } from "fs";
26658
26851
 
26659
26852
  // ../../node_modules/.bun/commander@15.0.0/node_modules/commander/lib/error.js
26660
26853
  class CommanderError extends Error {
@@ -28857,7 +29050,13 @@ function formatTable(rows, opts = {}) {
28857
29050
  const alignNumbers = opts.alignNumbers !== false;
28858
29051
  const colorizeStatus = opts.colorizeStatus !== false;
28859
29052
  const compact = opts.compact === true;
28860
- const keys = Object.keys(rows[0]);
29053
+ const firstRow = rows[0];
29054
+ if (!firstRow) {
29055
+ process.stdout.write(`${theme.dim("(empty)")}
29056
+ `);
29057
+ return;
29058
+ }
29059
+ const keys = Object.keys(firstRow);
28861
29060
  const processed = rows.map((row) => {
28862
29061
  const out = {};
28863
29062
  for (const k of keys) {
@@ -28894,17 +29093,18 @@ function formatTable(rows, opts = {}) {
28894
29093
  widths[key] = Math.max(widths[key], stripped);
28895
29094
  }
28896
29095
  }
28897
- const topParts = keys.map((k) => "\u2500".repeat(widths[k] + 2));
29096
+ const topParts = keys.map((k) => "\u2500".repeat((widths[k] ?? 0) + 2));
28898
29097
  const topBorder = `\u250C${topParts.join("\u252C")}\u2510`;
28899
29098
  const sepBorder = `\u251C${topParts.join("\u253C")}\u2524`;
28900
29099
  const botBorder = `\u2514${topParts.join("\u2534")}\u2518`;
28901
- const headerCells = keys.map((k) => theme.bold(k.padEnd(widths[k])).toString());
29100
+ const headerCells = keys.map((k) => theme.bold(k.padEnd(widths[k] ?? 0)).toString());
28902
29101
  const headerRow = `\u2502 ${headerCells.join(" \u2502 ")} \u2502`;
28903
29102
  const dataRows = processed.map((row, rowIdx) => {
28904
29103
  const isAlt = zebra && rowIdx % 2 === 0;
28905
29104
  const cells = keys.map((k) => {
28906
29105
  const value = row[k] ?? "";
28907
- const padded = numericColumns.has(k) ? value.padStart(widths[k]) : value.padEnd(widths[k]);
29106
+ const width = widths[k] ?? 0;
29107
+ const padded = numericColumns.has(k) ? value.padStart(width) : value.padEnd(width);
28908
29108
  if (isAlt && !value.includes("\x1B")) {
28909
29109
  return theme.textMuted(padded);
28910
29110
  }
@@ -29027,11 +29227,11 @@ function levenshtein(a2, b) {
29027
29227
  curr[0] = j;
29028
29228
  for (let i = 1;i <= m; i++) {
29029
29229
  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);
29230
+ curr[i] = Math.min((curr[i - 1] ?? 0) + 1, (prev[i] ?? 0) + 1, (prev[i - 1] ?? 0) + cost);
29031
29231
  }
29032
29232
  [prev, curr] = [curr, prev];
29033
29233
  }
29034
- return prev[m];
29234
+ return prev[m] ?? 0;
29035
29235
  }
29036
29236
 
29037
29237
  // src/utils/error-handler.ts
@@ -30480,6 +30680,8 @@ class SetupService {
30480
30680
  if (!varsMatch)
30481
30681
  continue;
30482
30682
  const varsBlock = varsMatch[1];
30683
+ if (varsBlock === undefined)
30684
+ continue;
30483
30685
  const nullEntries = varsBlock.match(/"\w+"\s*:\s*null/g);
30484
30686
  if (!nullEntries)
30485
30687
  continue;
@@ -31614,46 +31816,32 @@ class EnvService {
31614
31816
  hint: "Telegram webhook secret token"
31615
31817
  },
31616
31818
  {
31617
- name: "BINANCE_KEY_BINDING",
31618
- required: false,
31619
- secret: true,
31620
- section: "Exchanges",
31621
- hint: "Binance API key"
31622
- },
31623
- {
31624
- name: "BINANCE_SECRET_BINDING",
31625
- required: false,
31626
- secret: true,
31627
- section: "Exchanges",
31628
- hint: "Binance API secret"
31629
- },
31630
- {
31631
- name: "MEXC_KEY_BINDING",
31819
+ name: "EXCHANGE_KEY_BINDING",
31632
31820
  required: false,
31633
31821
  secret: true,
31634
31822
  section: "Exchanges",
31635
- hint: "MEXC API key"
31823
+ hint: "Unified exchange API key (Binance / Bybit / MEXC \u2014 venue via routing)"
31636
31824
  },
31637
31825
  {
31638
- name: "MEXC_SECRET_BINDING",
31826
+ name: "EXCHANGE_SECRET_BINDING",
31639
31827
  required: false,
31640
31828
  secret: true,
31641
31829
  section: "Exchanges",
31642
- hint: "MEXC API secret"
31830
+ hint: "Unified exchange API secret"
31643
31831
  },
31644
31832
  {
31645
- name: "BYBIT_KEY_BINDING",
31833
+ name: "EXCHANGE_TESTNET_KEY_BINDING",
31646
31834
  required: false,
31647
31835
  secret: true,
31648
31836
  section: "Exchanges",
31649
- hint: "Bybit API key"
31837
+ hint: "Optional testnet API key (when signals use test: true)"
31650
31838
  },
31651
31839
  {
31652
- name: "BYBIT_SECRET_BINDING",
31840
+ name: "EXCHANGE_TESTNET_SECRET_BINDING",
31653
31841
  required: false,
31654
31842
  secret: true,
31655
31843
  section: "Exchanges",
31656
- hint: "Bybit API secret"
31844
+ hint: "Optional testnet API secret"
31657
31845
  },
31658
31846
  {
31659
31847
  name: "EMAIL_HOST_BINDING",
@@ -31824,12 +32012,8 @@ class EnvService {
31824
32012
  "workers/trade-worker": [
31825
32013
  "API_SERVICE_KEY_BINDING",
31826
32014
  "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"
32015
+ "EXCHANGE_KEY_BINDING",
32016
+ "EXCHANGE_SECRET_BINDING"
31833
32017
  ],
31834
32018
  "workers/agent-worker": ["AGENT_INTERNAL_KEY"],
31835
32019
  "workers/d1-worker": ["INTERNAL_KEY_BINDING"],
@@ -33724,6 +33908,27 @@ var SYSTEM_SECRET_NAMES = [
33724
33908
  "API_SERVICE_KEY_BINDING"
33725
33909
  ];
33726
33910
  var SYSTEM_SECRET_SET = new Set(SYSTEM_SECRET_NAMES);
33911
+ var WORKER_SYSTEM_SECRETS = {
33912
+ "d1-worker": ["INTERNAL_KEY_BINDING"],
33913
+ "analytics-worker": ["INTERNAL_KEY_BINDING"],
33914
+ "trade-worker": [
33915
+ "INTERNAL_KEY_BINDING",
33916
+ "TELEGRAM_INTERNAL_KEY_BINDING",
33917
+ "API_SERVICE_KEY_BINDING"
33918
+ ],
33919
+ "report-worker": ["INTERNAL_KEY_BINDING"],
33920
+ "email-worker": ["INTERNAL_KEY_BINDING"],
33921
+ "agent-worker": ["INTERNAL_KEY_BINDING", "AGENT_INTERNAL_KEY"],
33922
+ hoox: ["INTERNAL_KEY_BINDING", "WEBHOOK_API_KEY_BINDING"],
33923
+ "web3-wallet-worker": ["INTERNAL_KEY_BINDING"],
33924
+ "telegram-worker": ["INTERNAL_KEY_BINDING"],
33925
+ dashboard: [
33926
+ "AGENT_INTERNAL_KEY",
33927
+ "SESSION_SECRET",
33928
+ "TRADE_INTERNAL_KEY",
33929
+ "API_SERVICE_KEY_BINDING"
33930
+ ]
33931
+ };
33727
33932
  function isSystemSecret(name) {
33728
33933
  return SYSTEM_SECRET_SET.has(name);
33729
33934
  }
@@ -33844,11 +34049,19 @@ class SecretsService {
33844
34049
  const content = await devVarsFile.text();
33845
34050
  existingValues = this.parseDotEnv(content);
33846
34051
  }
34052
+ let keysFallback = new Map;
34053
+ if (options.systemOnly) {
34054
+ const keysFile = Bun.file(".keys/setup.env");
34055
+ if (await keysFile.exists()) {
34056
+ keysFallback = this.parseDotEnv(await keysFile.text());
34057
+ }
34058
+ }
33847
34059
  let secrets;
33848
34060
  if (options.systemOnly) {
33849
34061
  const fromDeclared = declared.filter((s) => isSystemSecret(s));
33850
34062
  const fromDevVars = [...SYSTEM_SECRET_NAMES].filter((s) => existingValues.has(s));
33851
- secrets = [...new Set([...fromDeclared, ...fromDevVars])];
34063
+ const fromMap = [...WORKER_SYSTEM_SECRETS[workerName] ?? []];
34064
+ secrets = [...new Set([...fromDeclared, ...fromDevVars, ...fromMap])];
33852
34065
  } else {
33853
34066
  secrets = declared;
33854
34067
  }
@@ -33858,7 +34071,7 @@ class SecretsService {
33858
34071
  const items = [];
33859
34072
  for (const secret of secrets) {
33860
34073
  try {
33861
- const value = existingValues.get(secret);
34074
+ const value = existingValues.get(secret) ?? keysFallback.get(secret);
33862
34075
  if (value !== undefined && !this.isPlaceholder(value)) {
33863
34076
  await this.execWranglerSecretPut(worker.path, secret, value, workerName);
33864
34077
  synced.push(secret);
@@ -34138,8 +34351,8 @@ The command will prompt for the secret value (hidden input).
34138
34351
  It writes to the worker's .dev.vars file and syncs to Cloudflare.
34139
34352
 
34140
34353
  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) => {
34354
+ hoox secrets set trade-worker EXCHANGE_KEY_BINDING
34355
+ hoox config secrets set trade-worker EXCHANGE_KEY_BINDING`).action(withErrorHandling(async (workerName, secretName, _2, cmd) => {
34143
34356
  const opts = getFormatOptions(cmd);
34144
34357
  const svc = await SecretsService.create();
34145
34358
  const declared = svc.listSecrets(workerName);
@@ -34184,8 +34397,8 @@ ARGUMENTS:
34184
34397
  This removes the secret from Cloudflare and from the worker's .dev.vars file.
34185
34398
 
34186
34399
  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) => {
34400
+ hoox secrets delete trade-worker EXCHANGE_KEY_BINDING
34401
+ hoox config secrets delete trade-worker EXCHANGE_KEY_BINDING`).action(withErrorHandling(async (workerName, secretName, _2, cmd) => {
34189
34402
  const opts = getFormatOptions(cmd);
34190
34403
  const svc = await SecretsService.create();
34191
34404
  const declared = svc.listSecrets(workerName);
@@ -34327,7 +34540,7 @@ EXAMPLES:
34327
34540
  }
34328
34541
 
34329
34542
  // src/commands/config/keys-subcommands.ts
34330
- import { existsSync as existsSync9 } from "fs";
34543
+ import { chmodSync as chmodSync6, existsSync as existsSync9, readFileSync as readFileSync7 } from "fs";
34331
34544
  init_fs_secure();
34332
34545
  init_theme();
34333
34546
  function generateKey(bytes = 32) {
@@ -34335,6 +34548,82 @@ function generateKey(bytes = 32) {
34335
34548
  crypto.getRandomValues(buf);
34336
34549
  return Array.from(buf, (b3) => b3.toString(16).padStart(2, "0")).join("");
34337
34550
  }
34551
+ var DEV_VARS_WORKER_KEYS2 = {
34552
+ "d1-worker": ["INTERNAL_KEY_BINDING"],
34553
+ "analytics-worker": ["INTERNAL_KEY_BINDING"],
34554
+ "trade-worker": [
34555
+ "INTERNAL_KEY_BINDING",
34556
+ "TELEGRAM_INTERNAL_KEY_BINDING",
34557
+ "API_SERVICE_KEY_BINDING"
34558
+ ],
34559
+ "report-worker": ["INTERNAL_KEY_BINDING"],
34560
+ "email-worker": ["INTERNAL_KEY_BINDING"],
34561
+ "agent-worker": ["INTERNAL_KEY_BINDING", "AGENT_INTERNAL_KEY"],
34562
+ hoox: ["INTERNAL_KEY_BINDING", "WEBHOOK_API_KEY_BINDING"],
34563
+ "web3-wallet-worker": ["INTERNAL_KEY_BINDING"],
34564
+ "telegram-worker": ["INTERNAL_KEY_BINDING"],
34565
+ dashboard: [
34566
+ "AGENT_INTERNAL_KEY",
34567
+ "SESSION_SECRET",
34568
+ "TRADE_INTERNAL_KEY",
34569
+ "API_SERVICE_KEY_BINDING"
34570
+ ]
34571
+ };
34572
+ var WORKER_FS_DIR2 = {
34573
+ hoox: "hoox-worker"
34574
+ };
34575
+ function workerFsDir2(worker) {
34576
+ return WORKER_FS_DIR2[worker] ?? worker;
34577
+ }
34578
+ function parseEnvFile2(text2) {
34579
+ const map2 = new Map;
34580
+ for (const line of text2.split(`
34581
+ `)) {
34582
+ const trimmed = line.trim();
34583
+ if (!trimmed || trimmed.startsWith("#"))
34584
+ continue;
34585
+ const eq = trimmed.indexOf("=");
34586
+ if (eq <= 0)
34587
+ continue;
34588
+ map2.set(trimmed.slice(0, eq), trimmed.slice(eq + 1));
34589
+ }
34590
+ return map2;
34591
+ }
34592
+ async function writeDevVarsFromKeys(keys) {
34593
+ let count = 0;
34594
+ for (const [worker, secretNames] of Object.entries(DEV_VARS_WORKER_KEYS2)) {
34595
+ const path5 = `workers/${workerFsDir2(worker)}/.dev.vars`;
34596
+ const meshSet = new Set(secretNames);
34597
+ const existing = existsSync9(path5) ? parseEnvFile2(readFileSync7(path5, "utf-8")) : new Map;
34598
+ for (const secretName of secretNames) {
34599
+ const value = keys[secretName];
34600
+ if (value)
34601
+ existing.set(secretName, value);
34602
+ }
34603
+ let content = `# .dev.vars \u2014 local secrets for ${worker}
34604
+ `;
34605
+ content += `# Generated by \`hoox keys generate\`. NEVER commit this file.
34606
+
34607
+ `;
34608
+ for (const secretName of secretNames) {
34609
+ const value = existing.get(secretName);
34610
+ if (value !== undefined)
34611
+ content += `${secretName}=${value}
34612
+ `;
34613
+ }
34614
+ for (const [k, v] of existing) {
34615
+ if (!meshSet.has(k))
34616
+ content += `${k}=${v}
34617
+ `;
34618
+ }
34619
+ await Bun.write(path5, content);
34620
+ try {
34621
+ chmodSync6(path5, 384);
34622
+ } catch {}
34623
+ count++;
34624
+ }
34625
+ return count;
34626
+ }
34338
34627
  function registerKeysSubcommands(keysCmd, service = "config") {
34339
34628
  keysCmd.command("generate").summary("Generate new internal auth keys").description(`Generate new internal auth keys and save to .keys/ directory.
34340
34629
 
@@ -34376,9 +34665,12 @@ EXAMPLES:
34376
34665
  `) + `
34377
34666
  `);
34378
34667
  secureSecretFile(`${keysDir}/setup.env`);
34379
- formatSuccess(`Generated ${Object.keys(keys).length} keys in ${keysDir}/`, opts);
34668
+ const devVarsCount = await writeDevVarsFromKeys(keys);
34669
+ formatSuccess(`Generated ${Object.keys(keys).length} keys in ${keysDir}/ + ${devVarsCount} .dev.vars`, opts);
34380
34670
  if (!opts.quiet && !opts.json) {
34381
34671
  process.stdout.write(`${theme.warning("!")} Keep these keys secret. Add ${keysDir}/ to .gitignore.
34672
+ `);
34673
+ process.stdout.write(`${theme.dim("\u2192")} Next: hoox secrets sync --system
34382
34674
  `);
34383
34675
  formatKeyValue(keys, opts);
34384
34676
  }
@@ -34476,7 +34768,7 @@ EXAMPLES:
34476
34768
  hoox config show Display current configuration
34477
34769
  hoox config set workers.agent-worker.enabled false
34478
34770
  hoox config secrets list List secrets for a worker
34479
- hoox config secrets set trade-worker BINANCE_KEY_BINDING
34771
+ hoox config secrets set trade-worker EXCHANGE_KEY_BINDING
34480
34772
  hoox config keys generate Generate new internal keys`);
34481
34773
  configCmd.command("show").summary("Display current wrangler.jsonc configuration").description(`Display the current wrangler.jsonc configuration in a formatted table.
34482
34774
 
@@ -34671,7 +34963,7 @@ COMMANDS:
34671
34963
 
34672
34964
  EXAMPLES:
34673
34965
  hoox secrets list trade-worker
34674
- hoox secrets set trade-worker BINANCE_KEY_BINDING
34966
+ hoox secrets set trade-worker EXCHANGE_KEY_BINDING
34675
34967
  hoox secrets sync --system
34676
34968
  hoox secrets sync`);
34677
34969
  registerSecretsSubcommands(cmd, "secrets");
@@ -36223,11 +36515,19 @@ async function resolveZone(cf) {
36223
36515
  if (lines.length === 0) {
36224
36516
  throw new CLIError("No zones found. Set CLOUDFLARE_ZONE_ID environment variable.", 1 /* ERROR */);
36225
36517
  }
36226
- const match = lines[0].match(/^(.+?)\s+\((.+?)\)\s*$/);
36518
+ const firstLine = lines[0];
36519
+ if (!firstLine) {
36520
+ throw new CLIError("No zones found. Set CLOUDFLARE_ZONE_ID environment variable.", 1 /* ERROR */);
36521
+ }
36522
+ const match = firstLine.match(/^(.+?)\s+\((.+?)\)\s*$/);
36227
36523
  if (match) {
36228
- return { name: match[1], id: match[2] };
36524
+ const name = match[1];
36525
+ const id = match[2];
36526
+ if (name && id) {
36527
+ return { name, id };
36528
+ }
36229
36529
  }
36230
- return { name: lines[0], id: lines[0] };
36530
+ return { name: firstLine, id: firstLine };
36231
36531
  }
36232
36532
  async function handleStatus(opts) {
36233
36533
  const cf = new CloudflareService;
@@ -36545,6 +36845,8 @@ EXAMPLES:
36545
36845
  const results = [];
36546
36846
  for (let i2 = 0;i2 < workers.length; i2++) {
36547
36847
  const name2 = workers[i2];
36848
+ if (!name2)
36849
+ continue;
36548
36850
  const workerConfig = configService.getWorker(name2);
36549
36851
  if (!workerConfig)
36550
36852
  continue;
@@ -36822,7 +37124,11 @@ class DbService {
36822
37124
  if (!match) {
36823
37125
  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
37126
  }
36825
- return match[1];
37127
+ const command = match[1];
37128
+ if (!command) {
37129
+ 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.`);
37130
+ }
37131
+ return command;
36826
37132
  }
36827
37133
  }
36828
37134
  // src/commands/db/db-command.ts
@@ -37129,15 +37435,19 @@ function parseWranglerQueuesTable(raw) {
37129
37435
  const cells = line.split("\u2502").map((c3) => c3.trim()).filter((c3) => c3.length > 0);
37130
37436
  if (cells.length < 2)
37131
37437
  continue;
37132
- if (cells[0] === "id" || cells[0] === "name")
37438
+ const queueId = cells[0];
37439
+ const queueName = cells[1];
37440
+ if (!queueId || !queueName)
37133
37441
  continue;
37134
- if (!/^[0-9a-f-]{16,}$/i.test(cells[0]))
37442
+ if (queueId === "id" || queueId === "name")
37443
+ continue;
37444
+ if (!/^[0-9a-f-]{16,}$/i.test(queueId))
37135
37445
  continue;
37136
37446
  const producers = cells[4] !== undefined && cells[4] !== "" ? Number(cells[4]) : undefined;
37137
37447
  const consumers = cells[5] !== undefined && cells[5] !== "" ? Number(cells[5]) : undefined;
37138
37448
  queues.push({
37139
- queue_id: cells[0],
37140
- queue_name: cells[1],
37449
+ queue_id: queueId,
37450
+ queue_name: queueName,
37141
37451
  created_on: cells[2],
37142
37452
  modified_on: cells[3],
37143
37453
  producers_total_count: Number.isFinite(producers) ? producers : undefined,
@@ -37322,57 +37632,151 @@ SUBCOMMANDS:
37322
37632
  }, { service: "monitor" }));
37323
37633
  }
37324
37634
  // src/commands/workers/workers-command.ts
37635
+ init_src();
37325
37636
  init_config2();
37637
+ init_errors4();
37326
37638
  async function doListWorkers(fmt) {
37327
37639
  const configService = new ConfigService;
37328
37640
  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
- });
37641
+ const statuses = configService.listWorkerStatuses();
37642
+ const rows = statuses.map((w) => ({
37643
+ Worker: w.name,
37644
+ Status: w.enabled ? "enabled" : "disabled",
37645
+ Default: w.defaultEnabled === undefined ? "-" : w.defaultEnabled ? "enabled" : "disabled",
37646
+ Path: w.path,
37647
+ Config: w.inConfig ? "yes" : "no",
37648
+ Catalog: w.inCatalog ? "yes" : "no"
37649
+ }));
37650
+ if (fmt.json) {
37651
+ formatJson(statuses.map((w) => ({
37652
+ name: w.name,
37653
+ enabled: w.enabled,
37654
+ defaultEnabled: w.defaultEnabled ?? null,
37655
+ path: w.path,
37656
+ inConfig: w.inConfig,
37657
+ inCatalog: w.inCatalog
37658
+ })), fmt);
37659
+ return;
37660
+ }
37661
+ formatTable(rows, fmt);
37662
+ }
37663
+ function doCatalog(fmt) {
37664
+ const rows = WORKER_CATALOG.map((w) => ({
37665
+ Worker: w.name,
37666
+ Default: w.defaultEnabled ? "enabled" : "disabled",
37667
+ Path: w.path
37668
+ }));
37339
37669
  if (fmt.json) {
37340
- formatJson(rows, fmt);
37670
+ formatJson(WORKER_CATALOG.map((w) => ({
37671
+ name: w.name,
37672
+ defaultEnabled: w.defaultEnabled,
37673
+ path: w.path
37674
+ })), fmt);
37341
37675
  return;
37342
37676
  }
37343
37677
  formatTable(rows, fmt);
37344
37678
  }
37679
+ async function doSetEnabled(names, enabled, fmt) {
37680
+ if (names.length === 0) {
37681
+ 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.`);
37682
+ }
37683
+ const configService = new ConfigService;
37684
+ await configService.load();
37685
+ let results;
37686
+ try {
37687
+ results = await configService.setWorkersEnabled(names, enabled);
37688
+ } catch (e) {
37689
+ const msg = e instanceof Error ? e.message : String(e);
37690
+ throw new CLIError(msg, 2 /* INVALID_USAGE */, undefined, true);
37691
+ }
37692
+ if (fmt.json) {
37693
+ formatJson({ action: enabled ? "enable" : "disable", results }, fmt);
37694
+ return;
37695
+ }
37696
+ for (const r2 of results) {
37697
+ const seedNote = r2.seeded ? " (seeded from catalog)" : "";
37698
+ formatSuccess(`${r2.name} \u2192 ${r2.enabled ? "enabled" : "disabled"}${seedNote}`, fmt);
37699
+ }
37700
+ }
37345
37701
  function registerWorkersCommand(program2) {
37346
- const workersCmd = program2.command("workers").summary("Manage Cloudflare Workers (list, dev, logs)").description(`Manage your Hoox Cloudflare Workers.
37702
+ const workersCmd = program2.command("workers").summary("Manage Cloudflare Workers (list, enable, disable, dev, logs)").description(`Manage your Hoox Cloudflare Workers.
37347
37703
 
37348
37704
  SUBCOMMANDS:
37349
- list List all workers with status, path, and secrets count
37705
+ list List all workers (catalog + config) with status
37706
+ catalog Show the canonical worker catalog and defaults
37707
+ enable <name...> Enable workers in wrangler.jsonc
37708
+ disable <name...> Disable workers in wrangler.jsonc
37350
37709
  dev <name> Start a worker for local development (delegates to dev worker)
37351
37710
  logs <name> Tail logs for a specific worker (delegates to logs worker)
37352
37711
 
37712
+ The worker catalog (shared package) is the source of all known workers and
37713
+ their default enabled flags. Enable/disable updates workers.<name>.enabled
37714
+ in the root wrangler.jsonc. Missing catalog workers are seeded on enable.
37715
+
37353
37716
  For health checks, use 'hoox check health'.
37354
37717
  For deployments, use 'hoox deploy workers' or 'hoox deploy worker <name>'.
37355
37718
 
37356
37719
  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)
37720
+ hoox workers list
37721
+ hoox workers catalog
37722
+ hoox workers enable trade-worker agent-worker
37723
+ hoox workers disable web3-wallet-worker email-worker
37724
+ hoox workers dev trade-worker
37725
+ hoox workers logs hoox`);
37726
+ workersCmd.command("list").summary("List workers with enabled status (catalog + config)").description(`List all workers from the shared catalog merged with wrangler.jsonc.
37727
+
37728
+ Columns:
37729
+ - Worker name
37730
+ - Status (effective enabled/disabled)
37731
+ - Default (catalog default)
37732
+ - Path
37733
+ - Config / Catalog membership
37368
37734
 
37369
37735
  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) => {
37736
+ hoox workers list
37737
+ hoox workers list --json`).action(withErrorHandling(async (_opts, cmd) => {
37373
37738
  const fmt = getFormatOptions(cmd);
37374
37739
  await doListWorkers(fmt);
37375
37740
  }, { service: "workers" }));
37741
+ workersCmd.command("catalog").summary("Show the canonical worker catalog with defaults").description(`Print the shared worker catalog \u2014 every known worker and its default
37742
+ enabled flag. This is the source of truth used when seeding missing workers
37743
+ on enable.
37744
+
37745
+ EXAMPLES:
37746
+ hoox workers catalog
37747
+ hoox workers catalog --json`).action(withErrorHandling(async (_opts, cmd) => {
37748
+ const fmt = getFormatOptions(cmd);
37749
+ doCatalog(fmt);
37750
+ }, { service: "workers" }));
37751
+ 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.
37752
+
37753
+ If a worker exists in the catalog but not in config, it is seeded with
37754
+ path/secrets from the catalog and enabled.
37755
+
37756
+ ARGUMENTS:
37757
+ names One or more worker names (e.g. trade-worker email-worker)
37758
+
37759
+ EXAMPLES:
37760
+ hoox workers enable trade-worker
37761
+ hoox workers enable report-worker web3-wallet-worker
37762
+ hoox workers enable dashboard --json`).action(withErrorHandling(async (names, _opts, cmd) => {
37763
+ const fmt = getFormatOptions(cmd);
37764
+ await doSetEnabled(names, true, fmt);
37765
+ }, { service: "workers" }));
37766
+ 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.
37767
+
37768
+ Disabled workers are skipped by deploy/logs/all flows that use
37769
+ ConfigService.listEnabledWorkers().
37770
+
37771
+ ARGUMENTS:
37772
+ names One or more worker names
37773
+
37774
+ EXAMPLES:
37775
+ hoox workers disable email-worker
37776
+ hoox workers disable web3-wallet-worker report-worker`).action(withErrorHandling(async (names, _opts, cmd) => {
37777
+ const fmt = getFormatOptions(cmd);
37778
+ await doSetEnabled(names, false, fmt);
37779
+ }, { service: "workers" }));
37376
37780
  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
37781
 
37378
37782
  ARGUMENTS:
@@ -37918,10 +38322,10 @@ import { existsSync as existsSync12, realpathSync } from "fs";
37918
38322
  import { homedir as homedir4 } from "os";
37919
38323
 
37920
38324
  // src/services/perf/endpoint-resolver.ts
37921
- import { readFileSync as readFileSync8 } from "fs";
38325
+ import { readFileSync as readFileSync9 } from "fs";
37922
38326
  function readSubdomainPrefix() {
37923
38327
  try {
37924
- const raw = readFileSync8("wrangler.jsonc", "utf8");
38328
+ const raw = readFileSync9("wrangler.jsonc", "utf8");
37925
38329
  const stripped = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/^\s*\/\/.*$/gm, "");
37926
38330
  const parsed = JSON.parse(stripped);
37927
38331
  const prefix = parsed.global?.subdomain_prefix;
@@ -38965,7 +39369,7 @@ function registerDisclaimerCommand(program2) {
38965
39369
  }));
38966
39370
  }
38967
39371
  // src/commands/agent/agent-command.ts
38968
- import { readFileSync as readFileSync9 } from "fs";
39372
+ import { readFileSync as readFileSync10 } from "fs";
38969
39373
  init_errors4();
38970
39374
  var PROVIDERS = [
38971
39375
  {
@@ -38999,7 +39403,7 @@ function checkDevVars() {
38999
39403
  const candidates = ["workers/agent-worker/.dev.vars", ".dev.vars"];
39000
39404
  for (const filePath of candidates) {
39001
39405
  try {
39002
- const content = readFileSync9(filePath, "utf-8");
39406
+ const content = readFileSync10(filePath, "utf-8");
39003
39407
  for (const line of content.split(`
39004
39408
  `)) {
39005
39409
  const trimmed = line.trim();
@@ -39530,7 +39934,7 @@ EXAMPLES:
39530
39934
  }
39531
39935
  // src/commands/pyne/pyne-command.ts
39532
39936
  init_config2();
39533
- import { existsSync as existsSync14, readFileSync as readFileSync10 } from "fs";
39937
+ import { existsSync as existsSync14, readFileSync as readFileSync11 } from "fs";
39534
39938
  import { resolve as resolve8 } from "path";
39535
39939
  init_errors4();
39536
39940
  var DEFAULT_TIMEOUT_MS = 30000;
@@ -39556,7 +39960,7 @@ function loadDevVars(workerDir) {
39556
39960
  try {
39557
39961
  if (!existsSync14(filePath))
39558
39962
  continue;
39559
- const content = readFileSync10(filePath, "utf-8");
39963
+ const content = readFileSync11(filePath, "utf-8");
39560
39964
  for (const line of content.split(`
39561
39965
  `)) {
39562
39966
  const trimmed = line.trim();
@@ -39775,7 +40179,7 @@ EXAMPLES:
39775
40179
  if (!existsSync14(abs)) {
39776
40180
  throw new CLIError(`Script not found: ${abs}`, 1 /* ERROR */);
39777
40181
  }
39778
- body.script = readFileSync10(abs, "utf-8");
40182
+ body.script = readFileSync11(abs, "utf-8");
39779
40183
  }
39780
40184
  const res = await pyneFetch(baseUrl, "/run", {
39781
40185
  method: "POST",
@@ -39826,7 +40230,7 @@ EXAMPLES:
39826
40230
  if (!existsSync14(abs)) {
39827
40231
  throw new CLIError(`Script not found: ${abs}`, 1 /* ERROR */);
39828
40232
  }
39829
- const script = readFileSync10(abs, "utf-8");
40233
+ const script = readFileSync11(abs, "utf-8");
39830
40234
  const baseName = abs.split(/[/\\]/).pop() ?? "script.pine";
39831
40235
  const scriptId = opts.id ?? baseName.replace(/\.pine$/i, "").replace(/[^a-zA-Z0-9_-]/g, "_");
39832
40236
  const res = await pyneFetch(baseUrl, "/scripts", {
@@ -40189,15 +40593,21 @@ function parseTime(input) {
40189
40593
  return;
40190
40594
  const relativeMatch = input.match(/^(\d+)([smhd])$/);
40191
40595
  if (relativeMatch) {
40192
- const value = parseInt(relativeMatch[1], 10);
40596
+ const rawValue = relativeMatch[1];
40193
40597
  const unit = relativeMatch[2];
40598
+ if (!rawValue || !unit)
40599
+ return;
40600
+ const value = parseInt(rawValue, 10);
40194
40601
  const multipliers = {
40195
40602
  s: 1000,
40196
40603
  m: 60 * 1000,
40197
40604
  h: 60 * 60 * 1000,
40198
40605
  d: 24 * 60 * 60 * 1000
40199
40606
  };
40200
- return Date.now() - value * multipliers[unit];
40607
+ const mult = multipliers[unit];
40608
+ if (mult === undefined)
40609
+ return;
40610
+ return Date.now() - value * mult;
40201
40611
  }
40202
40612
  const ms = new Date(input).getTime();
40203
40613
  return Number.isFinite(ms) ? ms : undefined;
@@ -41489,7 +41899,7 @@ function registerCompletionCommand(program2) {
41489
41899
  // src/ui/banner.ts
41490
41900
  init_ansis();
41491
41901
  init_theme();
41492
- import { existsSync as existsSync15, readFileSync as readFileSync11 } from "fs";
41902
+ import { existsSync as existsSync15, readFileSync as readFileSync12 } from "fs";
41493
41903
  import { fileURLToPath as fileURLToPath2 } from "url";
41494
41904
  import { dirname as dirname4, join as join13 } from "path";
41495
41905
  var ORANGE = ansis_default.hex("#ff7f2a");
@@ -41507,7 +41917,7 @@ function findCliVersion() {
41507
41917
  const pkgPath = join13(dir, "package.json");
41508
41918
  if (existsSync15(pkgPath)) {
41509
41919
  try {
41510
- const pkg = JSON.parse(readFileSync11(pkgPath, "utf-8"));
41920
+ const pkg = JSON.parse(readFileSync12(pkgPath, "utf-8"));
41511
41921
  if (pkg.name === PKG_NAME)
41512
41922
  return pkg.version;
41513
41923
  } catch {}
@@ -42142,7 +42552,7 @@ if (typeof Bun === "undefined") {
42142
42552
  `);
42143
42553
  process.exit(1);
42144
42554
  }
42145
- var pkgVersion = JSON.parse(readFileSync12(new URL("../package.json", import.meta.url), "utf-8")).version;
42555
+ var pkgVersion = JSON.parse(readFileSync13(new URL("../package.json", import.meta.url), "utf-8")).version;
42146
42556
  var program2 = new Command;
42147
42557
  program2.name("hoox").description("Hoox CLI \u2014 manage Cloudflare Workers, infrastructure, secrets, and deployments").version(`${pkgVersion}
42148
42558