@hoox-sh/hoox-cli 0.12.0 → 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 (2) hide show
  1. package/dist/index.js +101 -30
  2. package/package.json +1 -1
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,
@@ -15107,10 +15108,38 @@ async function logKvTimestamp(env, prefix = "timestamp") {
15107
15108
  console.error(`Failed to log timestamp to KV: ${toError(error51, "Unknown error")}`);
15108
15109
  }
15109
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
+ }
15110
15122
  async function kvGetMany(kv, keys) {
15111
15123
  if (keys.length === 0)
15112
15124
  return [];
15113
- return Promise.all(keys.map((key) => kv.get(key)));
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;
15114
15143
  }
15115
15144
  async function kvGetManyAsRecord(kv, keys) {
15116
15145
  const values = await kvGetMany(kv, keys);
@@ -15138,6 +15167,7 @@ function headersToObject(headers) {
15138
15167
  }
15139
15168
  return result;
15140
15169
  }
15170
+ var KV_BULK_GET_MAX_KEYS = 100;
15141
15171
  var init_kvUtils = __esm(() => {
15142
15172
  init_errors3();
15143
15173
  });
@@ -15187,29 +15217,60 @@ var init_health = __esm(() => {
15187
15217
  });
15188
15218
 
15189
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
+ }
15190
15241
  function createQueueHandler(options) {
15191
- const { maxRetries, backoffDelays, onMessage, onRetry, onDLQ, logger } = options;
15192
- return async (batch) => {
15193
- for (const msg of batch.messages) {
15194
- const attemptNumber = msg.attempts || 0;
15195
- const logId = `[${msg.id}]`;
15196
- try {
15197
- logger?.info(`${logId} Processing message (attempt ${attemptNumber + 1})`);
15198
- await onMessage(msg.body, attemptNumber);
15199
- logger?.info(`${logId} Message processed successfully`);
15200
- } catch (error51) {
15201
- const errorMsg = error51 instanceof Error ? error51.message : String(error51);
15202
- if (attemptNumber < maxRetries) {
15203
- const delaySeconds = backoffDelays[attemptNumber] ?? backoffDelays[backoffDelays.length - 1] ?? 0;
15204
- logger?.info(`${logId} Retrying in ${delaySeconds}s (attempt ${attemptNumber + 2}/${maxRetries + 1})`);
15205
- await onRetry?.(msg.body, attemptNumber, errorMsg, delaySeconds);
15206
- msg.retry({ delaySeconds });
15207
- } else {
15208
- logger?.error(`${logId} Max retries exceeded (${maxRetries + 1} attempts), moving to DLQ`);
15209
- await onDLQ?.(msg.body, attemptNumber, errorMsg);
15210
- }
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);
15211
15269
  }
15212
15270
  }
15271
+ }
15272
+ return async (batch) => {
15273
+ await mapPool(batch.messages, concurrency, processMessage);
15213
15274
  };
15214
15275
  }
15215
15276
 
@@ -17609,7 +17670,11 @@ var init_registry = __esm(() => {
17609
17670
  type: "secret",
17610
17671
  description: "Inter-worker auth key"
17611
17672
  },
17612
- 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
+ }
17613
17678
  },
17614
17679
  services: [
17615
17680
  {
@@ -17635,7 +17700,8 @@ var init_registry = __esm(() => {
17635
17700
  ai: true,
17636
17701
  queues: { producer: ["trade-execution"] },
17637
17702
  durableObjects: [
17638
- { name: "IDEMPOTENCY_STORE", className: "IdempotencyStore" }
17703
+ { name: "IDEMPOTENCY_STORE", className: "IdempotencyStore" },
17704
+ { name: "RATE_LIMITER", className: "RateLimiterStore" }
17639
17705
  ]
17640
17706
  },
17641
17707
  middleware: [
@@ -17731,6 +17797,10 @@ var init_registry = __esm(() => {
17731
17797
  TELEGRAM_SECRET_TOKEN: {
17732
17798
  type: "secret",
17733
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"
17734
17804
  }
17735
17805
  },
17736
17806
  services: [],
@@ -19653,7 +19723,7 @@ function validateWranglerJsonc(workerName, manifest, jsoncContent) {
19653
19723
  }
19654
19724
  }
19655
19725
  for (const [name, def] of Object.entries(manifest.vars)) {
19656
- 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") {
19657
19727
  errors3.push({
19658
19728
  worker: workerName,
19659
19729
  severity: "warning",
@@ -22351,6 +22421,7 @@ __export(exports_src, {
22351
22421
  PositionSchema: () => PositionSchema,
22352
22422
  PRESETS: () => PRESETS,
22353
22423
  LogLevelColor: () => LogLevelColor,
22424
+ KV_BULK_GET_MAX_KEYS: () => KV_BULK_GET_MAX_KEYS,
22354
22425
  KVKeys: () => KVKeys,
22355
22426
  KILL_SWITCH_KEYS: () => KILL_SWITCH_KEYS,
22356
22427
  KILL_SWITCH_ACTIVE_PREFIX: () => KILL_SWITCH_ACTIVE_PREFIX,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hoox-sh/hoox-cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Hoox CLI — manage Cloudflare Workers, infrastructure, secrets, and deployments (@hoox-sh/hoox-cli)",
5
5
  "type": "module",
6
6
  "bin": {