@hoox-sh/hoox-shared 1.5.0 → 1.6.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 (64) hide show
  1. package/dist/agent-config-fields.d.ts +5 -0
  2. package/dist/agent-config-fields.d.ts.map +1 -1
  3. package/dist/analytics.d.ts.map +1 -1
  4. package/dist/analytics.js +8 -3
  5. package/dist/config.d.ts +10 -2
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +256 -24
  8. package/dist/errors.d.ts +1 -0
  9. package/dist/errors.d.ts.map +1 -1
  10. package/dist/exchange-client.d.ts +0 -19
  11. package/dist/exchange-client.d.ts.map +1 -1
  12. package/dist/exchange-client.js +0 -66
  13. package/dist/exchanges/base-exchange-client.d.ts.map +1 -1
  14. package/dist/exchanges/base-exchange-client.js +4 -3
  15. package/dist/exchanges/index.js +4 -3
  16. package/dist/index.d.ts +2 -3
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +293 -604
  19. package/dist/middleware/auth.d.ts +9 -0
  20. package/dist/middleware/auth.d.ts.map +1 -1
  21. package/dist/middleware/index.js +3 -2
  22. package/dist/middleware/logger.d.ts +2 -0
  23. package/dist/middleware/logger.d.ts.map +1 -1
  24. package/dist/middleware/rate-limit.d.ts +7 -0
  25. package/dist/middleware/rate-limit.d.ts.map +1 -1
  26. package/dist/middleware/rate-limit.js +3 -2
  27. package/dist/middleware/validate.d.ts +2 -0
  28. package/dist/middleware/validate.d.ts.map +1 -1
  29. package/dist/path-utils.d.ts.map +1 -1
  30. package/dist/path-utils.js +13 -3
  31. package/dist/queue-handler.d.ts.map +1 -1
  32. package/dist/queue-handler.js +19 -8
  33. package/dist/router.d.ts +5 -0
  34. package/dist/router.d.ts.map +1 -1
  35. package/dist/schemas/index.js +40 -23
  36. package/dist/schemas/registry.d.ts.map +1 -1
  37. package/dist/schemas/registry.js +4 -1
  38. package/dist/schemas/validators.d.ts.map +1 -1
  39. package/dist/schemas/validators.js +36 -22
  40. package/dist/service-bindings.d.ts +4 -0
  41. package/dist/service-bindings.d.ts.map +1 -1
  42. package/dist/session.d.ts +10 -3
  43. package/dist/session.d.ts.map +1 -1
  44. package/dist/session.js +109 -33
  45. package/dist/sse.d.ts.map +1 -1
  46. package/dist/sse.js +24 -6
  47. package/dist/stores/config-store.d.ts +2 -50
  48. package/dist/stores/config-store.d.ts.map +1 -1
  49. package/dist/stores/config-store.js +145 -134
  50. package/dist/stores/service-store.d.ts +2 -192
  51. package/dist/stores/service-store.d.ts.map +1 -1
  52. package/dist/stores/service-store.js +24 -6
  53. package/dist/stores/ui-store.d.ts +2 -48
  54. package/dist/stores/ui-store.d.ts.map +1 -1
  55. package/dist/tenant.d.ts +1 -0
  56. package/dist/tenant.d.ts.map +1 -1
  57. package/dist/types.d.ts +6 -1
  58. package/dist/types.d.ts.map +1 -1
  59. package/dist/types.js +19 -0
  60. package/dist/wizard/engine.d.ts.map +1 -1
  61. package/dist/wizard/engine.js +13 -7
  62. package/dist/wizard/index.js +13 -7
  63. package/dist/wizard/presets.js +3 -3
  64. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -182,6 +182,7 @@ var init_api_client = __esm(() => {
182
182
  // src/sse.ts
183
183
  function subscribeSSE(path, callback, onStatus, options) {
184
184
  let aborted = false;
185
+ const ac = new AbortController;
185
186
  const profile = options?.transport ?? (options?.transportEnv ? resolveOperatorTransportProfile(options.transportEnv) : resolveOperatorTransportProfile());
186
187
  const run = async () => {
187
188
  let attempt = 0;
@@ -192,7 +193,8 @@ function subscribeSSE(path, callback, onStatus, options) {
192
193
  headers: {
193
194
  Accept: "text/event-stream",
194
195
  ...buildOperatorAuthHeaders(profile)
195
- }
196
+ },
197
+ signal: ac.signal
196
198
  });
197
199
  if (!response.ok || !response.body) {
198
200
  throw new Error(`SSE connection failed: HTTP ${response.status}`);
@@ -204,17 +206,21 @@ function subscribeSSE(path, callback, onStatus, options) {
204
206
  let buffer = "";
205
207
  while (!aborted) {
206
208
  const { done, value } = await reader.read();
207
- if (done)
208
- break;
209
+ if (done) {
210
+ throw new Error("SSE stream ended");
211
+ }
209
212
  buffer += decoder.decode(value, { stream: true });
210
213
  const lines = buffer.split(`
211
214
  `);
212
215
  buffer = lines.pop() || "";
213
216
  for (const line of lines) {
214
217
  const trimmed = line.trim();
215
- if (trimmed.startsWith("data: ")) {
218
+ if (trimmed.startsWith("data:")) {
219
+ const jsonText = trimmed.slice(5).replace(/^\s/, "");
220
+ if (!jsonText)
221
+ continue;
216
222
  try {
217
- const parsed = JSON.parse(trimmed.slice(6));
223
+ const parsed = JSON.parse(jsonText);
218
224
  callback(parsed);
219
225
  } catch {}
220
226
  }
@@ -226,7 +232,18 @@ function subscribeSSE(path, callback, onStatus, options) {
226
232
  onStatus?.("reconnecting");
227
233
  const delay = Math.min(RECONNECT_BASE_MS * Math.pow(2, attempt), RECONNECT_MAX_MS);
228
234
  attempt++;
229
- await new Promise((resolve) => setTimeout(resolve, delay));
235
+ await new Promise((resolve) => {
236
+ const timer = setTimeout(resolve, delay);
237
+ const onAbort = () => {
238
+ clearTimeout(timer);
239
+ resolve();
240
+ };
241
+ if (ac.signal.aborted) {
242
+ onAbort();
243
+ return;
244
+ }
245
+ ac.signal.addEventListener("abort", onAbort, { once: true });
246
+ });
230
247
  }
231
248
  }
232
249
  if (!aborted) {
@@ -237,6 +254,7 @@ function subscribeSSE(path, callback, onStatus, options) {
237
254
  return {
238
255
  abort: () => {
239
256
  aborted = true;
257
+ ac.abort();
240
258
  }
241
259
  };
242
260
  }
@@ -323,6 +341,24 @@ var BalanceSchema = z.object({
323
341
  locked: z.number().min(0),
324
342
  timestamp: z.number().int().positive()
325
343
  }).strict();
344
+ var VIEW_IDS = [
345
+ "dashboard",
346
+ "workers",
347
+ "worker-detail",
348
+ "trade-monitor",
349
+ "logs-viewer",
350
+ "service-manager",
351
+ "config-editor",
352
+ "setup-wizard",
353
+ "settings",
354
+ "queue-depth",
355
+ "kv-viewer",
356
+ "secrets-viewer",
357
+ "db-query",
358
+ "ai-chat",
359
+ "edge-topology",
360
+ "worker-settings"
361
+ ];
326
362
  // src/kvKeys.ts
327
363
  var KV_TRADE_DEFAULT_LEVERAGE = "trade:default_leverage";
328
364
  var KV_TRADE_MAX_POSITION_SIZE = "trade:max_position_size";
@@ -631,73 +667,8 @@ class ExchangeRouter {
631
667
  return { exchange: key, client: provider.createClient(env, options) };
632
668
  }
633
669
  }
634
-
635
- class BaseExchangeClient {
636
- apiKey;
637
- apiSecret;
638
- baseUrl;
639
- isTestnet;
640
- constructor(config) {
641
- if (!config.apiKey || !config.apiSecret) {
642
- throw new Error("API key and secret are required.");
643
- }
644
- this.apiKey = config.apiKey;
645
- this.apiSecret = config.apiSecret;
646
- this.baseUrl = config.baseUrl || this.getDefaultBaseUrl();
647
- this.isTestnet = config.testnet || false;
648
- }
649
- async fetch(method, path, params) {
650
- const url = `${this.baseUrl}${path}`;
651
- const headers = this.buildHeaders(method, path, params);
652
- const body = params ? new URLSearchParams(params).toString() : undefined;
653
- const response = await fetch(url, {
654
- method,
655
- headers,
656
- body: method !== "GET" ? body : undefined
657
- });
658
- if (!response.ok) {
659
- const error = await response.text();
660
- throw new Error(`Exchange API error: ${response.status} - ${error}`);
661
- }
662
- return response.json();
663
- }
664
- async openLong(symbol, quantity, price, orderType = "MARKET") {
665
- return this.executeTrade({
666
- symbol,
667
- side: "long",
668
- orderType,
669
- quantity,
670
- price
671
- });
672
- }
673
- async openShort(symbol, quantity, price, orderType = "MARKET") {
674
- return this.executeTrade({
675
- symbol,
676
- side: "short",
677
- orderType,
678
- quantity,
679
- price
680
- });
681
- }
682
- async closeLong(symbol, quantity) {
683
- return this.executeTrade({
684
- symbol,
685
- side: "long",
686
- quantity,
687
- reduceOnly: true
688
- });
689
- }
690
- async closeShort(symbol, quantity) {
691
- return this.executeTrade({
692
- symbol,
693
- side: "short",
694
- quantity,
695
- reduceOnly: true
696
- });
697
- }
698
- }
699
670
  // src/exchanges/base-exchange-client.ts
700
- class BaseExchangeClient2 {
671
+ class BaseExchangeClient {
701
672
  apiKey;
702
673
  apiSecret;
703
674
  baseUrl;
@@ -736,13 +707,14 @@ class BaseExchangeClient2 {
736
707
  return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
737
708
  }
738
709
  async fetch(method, path, params) {
739
- const url = `${this.baseUrl}${path}`;
710
+ const encoded = params ? new URLSearchParams(Object.fromEntries(Object.entries(params).map(([k, v]) => [k, String(v)]))).toString() : "";
711
+ const isGet = method === "GET" || method === "HEAD";
712
+ const url = isGet && encoded ? `${this.baseUrl}${path}${path.includes("?") ? "&" : "?"}${encoded}` : `${this.baseUrl}${path}`;
740
713
  const headers = this.buildHeaders(method, path, params);
741
- const body = params ? new URLSearchParams(params).toString() : undefined;
742
714
  const response = await fetch(url, {
743
715
  method,
744
716
  headers,
745
- body: method !== "GET" ? body : undefined
717
+ body: isGet || !encoded ? undefined : encoded
746
718
  });
747
719
  if (!response.ok) {
748
720
  const error = await response.text();
@@ -892,14 +864,19 @@ async function trackAnalytics(env, endpoint, body, options) {
892
864
  "Content-Type": "application/json"
893
865
  };
894
866
  const internalKey = env.INTERNAL_KEY_BINDING;
895
- if (internalKey) {
896
- headers["X-Internal-Auth-Key"] = internalKey;
867
+ if (!internalKey) {
868
+ console.error("Analytics tracking skipped: INTERNAL_KEY_BINDING missing");
869
+ return;
897
870
  }
898
- await env.ANALYTICS_SERVICE.fetch(new Request(`http://localhost${endpoint}`, {
871
+ headers["X-Internal-Auth-Key"] = internalKey;
872
+ const response = await env.ANALYTICS_SERVICE.fetch(new Request(`http://localhost${endpoint}`, {
899
873
  method: "POST",
900
874
  headers,
901
875
  body: JSON.stringify(payload)
902
876
  }));
877
+ if (!response.ok) {
878
+ console.error(`Analytics tracking failed: HTTP ${response.status} ${response.statusText}`);
879
+ }
903
880
  } catch (e) {
904
881
  console.error("Analytics tracking failed:", e);
905
882
  }
@@ -952,22 +929,33 @@ function createQueueHandler(options) {
952
929
  } = options;
953
930
  const concurrency = typeof concurrencyOpt === "number" && Number.isFinite(concurrencyOpt) && concurrencyOpt >= 1 ? Math.floor(concurrencyOpt) : 1;
954
931
  async function processMessage(msg) {
955
- const attemptNumber = msg.attempts || 0;
932
+ const delivery = Math.max(1, typeof msg.attempts === "number" && Number.isFinite(msg.attempts) ? msg.attempts : 1);
933
+ const retryIndex = delivery - 1;
956
934
  const logId = `[${msg.id}]`;
957
935
  try {
958
- logger?.info(`${logId} Processing message (attempt ${attemptNumber + 1})`);
959
- await onMessage(msg.body, attemptNumber);
936
+ logger?.info(`${logId} Processing message (attempt ${delivery})`);
937
+ await onMessage(msg.body, retryIndex);
960
938
  logger?.info(`${logId} Message processed successfully`);
939
+ msg.ack();
961
940
  } catch (error) {
962
941
  const errorMsg = error instanceof Error ? error.message : String(error);
963
- if (attemptNumber < maxRetries) {
964
- const delaySeconds = backoffDelays[attemptNumber] ?? backoffDelays[backoffDelays.length - 1] ?? 0;
965
- logger?.info(`${logId} Retrying in ${delaySeconds}s (attempt ${attemptNumber + 2}/${maxRetries + 1})`);
966
- await onRetry?.(msg.body, attemptNumber, errorMsg, delaySeconds);
942
+ if (retryIndex < maxRetries) {
943
+ const delaySeconds = backoffDelays[retryIndex] ?? backoffDelays[backoffDelays.length - 1] ?? 0;
944
+ logger?.info(`${logId} Retrying in ${delaySeconds}s (attempt ${delivery + 1}/${maxRetries + 1})`);
945
+ try {
946
+ await onRetry?.(msg.body, retryIndex, errorMsg, delaySeconds);
947
+ } catch (hookError) {
948
+ logger?.error(`${logId} onRetry failed: ${hookError instanceof Error ? hookError.message : String(hookError)}`);
949
+ }
967
950
  msg.retry({ delaySeconds });
968
951
  } else {
969
952
  logger?.error(`${logId} Max retries exceeded (${maxRetries + 1} attempts), moving to DLQ`);
970
- await onDLQ?.(msg.body, attemptNumber, errorMsg);
953
+ try {
954
+ await onDLQ?.(msg.body, retryIndex, errorMsg);
955
+ } catch (hookError) {
956
+ logger?.error(`${logId} onDLQ failed: ${hookError instanceof Error ? hookError.message : String(hookError)}`);
957
+ }
958
+ msg.ack();
971
959
  }
972
960
  }
973
961
  }
@@ -1003,214 +991,6 @@ function createCronHandler(options) {
1003
991
  }
1004
992
  };
1005
993
  }
1006
- // src/d1/repository.ts
1007
- class D1Repository {
1008
- db;
1009
- constructor(db) {
1010
- this.db = db;
1011
- }
1012
- async query(sql, params = []) {
1013
- try {
1014
- const stmt = this.db.prepare(sql).bind(...params);
1015
- const result = await stmt.all();
1016
- return {
1017
- success: result.success,
1018
- results: result.results,
1019
- error: result.error
1020
- };
1021
- } catch (error) {
1022
- return {
1023
- success: false,
1024
- error: toError(error)
1025
- };
1026
- }
1027
- }
1028
- async execute(sql, params = []) {
1029
- try {
1030
- const stmt = this.db.prepare(sql).bind(...params);
1031
- const result = await stmt.run();
1032
- return {
1033
- success: result.success,
1034
- meta: {
1035
- last_row_id: result.meta?.last_row_id,
1036
- changes: result.meta?.changes,
1037
- duration: result.meta?.duration
1038
- },
1039
- error: result.error
1040
- };
1041
- } catch (error) {
1042
- return {
1043
- success: false,
1044
- error: toError(error)
1045
- };
1046
- }
1047
- }
1048
- async batch(statements) {
1049
- try {
1050
- const prepared = statements.map((s) => s.params && s.params.length > 0 ? this.db.prepare(s.query).bind(...s.params) : this.db.prepare(s.query));
1051
- const results = await this.db.batch(prepared);
1052
- return results.map((r) => ({
1053
- success: r.success,
1054
- meta: r.meta ? { last_row_id: r.meta.last_row_id, changes: r.meta.changes } : undefined,
1055
- error: r.error
1056
- }));
1057
- } catch (error) {
1058
- return statements.map(() => ({
1059
- success: false,
1060
- error: toError(error)
1061
- }));
1062
- }
1063
- }
1064
- prepare(sql) {
1065
- return this.db.prepare(sql);
1066
- }
1067
- async getTrades(limit = 50, offset = 0) {
1068
- return this.query("SELECT * FROM trades ORDER BY timestamp DESC LIMIT ? OFFSET ?", [limit, offset]);
1069
- }
1070
- async insertTrade(trade) {
1071
- return this.execute(`INSERT INTO trades (id, timestamp, exchange, symbol, action, quantity, price, leverage, status, raw_response)
1072
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
1073
- trade.id || crypto.randomUUID(),
1074
- trade.timestamp,
1075
- trade.exchange,
1076
- trade.symbol,
1077
- trade.action,
1078
- trade.quantity ?? null,
1079
- trade.price ?? null,
1080
- trade.leverage ?? null,
1081
- trade.status,
1082
- trade.raw_response ?? null
1083
- ]);
1084
- }
1085
- async getPositions(status) {
1086
- if (status) {
1087
- return this.query("SELECT * FROM positions WHERE status = ? ORDER BY updated_at DESC", [status]);
1088
- }
1089
- return this.query("SELECT * FROM positions ORDER BY updated_at DESC");
1090
- }
1091
- async upsertPosition(position) {
1092
- return this.execute(`REPLACE INTO positions (id, exchange, symbol, side, entry_price, mark_price, liquidation_price, leverage, size, unrealized_pnl, status, updated_at)
1093
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
1094
- position.id,
1095
- position.exchange,
1096
- position.symbol,
1097
- position.side,
1098
- position.entry_price ?? null,
1099
- position.mark_price ?? null,
1100
- position.liquidation_price ?? null,
1101
- position.leverage ?? null,
1102
- position.size,
1103
- position.unrealized_pnl ?? null,
1104
- position.status,
1105
- position.updated_at
1106
- ]);
1107
- }
1108
- async getTradeSignals(limit = 10) {
1109
- return this.query(`SELECT signal_id, timestamp, symbol, signal_type, source, processed_at
1110
- FROM trade_signals
1111
- ORDER BY processed_at DESC
1112
- LIMIT ?`, [limit]);
1113
- }
1114
- async insertTradeSignal(signal) {
1115
- return this.execute(`INSERT INTO trade_signals (signal_id, timestamp, symbol, signal_type, source, raw_data)
1116
- VALUES (?, ?, ?, ?, ?, ?)`, [
1117
- signal.signal_id,
1118
- signal.timestamp,
1119
- signal.symbol,
1120
- signal.signal_type,
1121
- signal.source ?? null,
1122
- signal.raw_data ?? null
1123
- ]);
1124
- }
1125
- async getSystemLogs(limit = 50) {
1126
- return this.query("SELECT * FROM system_logs ORDER BY timestamp DESC LIMIT ?", [limit]);
1127
- }
1128
- async insertSystemLog(log) {
1129
- return this.execute(`INSERT INTO system_logs (id, level, service, message, details) VALUES (?, ?, ?, ?, ?)`, [
1130
- log.id || crypto.randomUUID(),
1131
- log.level,
1132
- log.service,
1133
- log.message,
1134
- log.details ?? null
1135
- ]);
1136
- }
1137
- async getLatestBalances() {
1138
- return this.query(`
1139
- SELECT b.id, b.exchange, b.asset, b.total, b.free, b.used, b.timestamp
1140
- FROM balances b
1141
- INNER JOIN (
1142
- SELECT exchange, asset, MAX(timestamp) as max_time
1143
- FROM balances
1144
- GROUP BY exchange, asset
1145
- ) latest ON b.exchange = latest.exchange AND b.asset = latest.asset AND b.timestamp = latest.max_time
1146
- `);
1147
- }
1148
- async healthCheck() {
1149
- try {
1150
- await this.db.prepare("SELECT 1").first();
1151
- return true;
1152
- } catch {
1153
- return false;
1154
- }
1155
- }
1156
- }
1157
- // src/d1/schemas.ts
1158
- import { z as z2 } from "zod";
1159
- var TradeRecordSchema = z2.object({
1160
- id: z2.string(),
1161
- signal_id: z2.string().nullable().optional(),
1162
- timestamp: z2.number().int().positive(),
1163
- exchange: z2.string().min(1),
1164
- symbol: z2.string().min(1),
1165
- action: z2.string().min(1),
1166
- quantity: z2.number().nullable(),
1167
- price: z2.number().nullable(),
1168
- leverage: z2.number().nullable(),
1169
- status: z2.string().min(1),
1170
- error_message: z2.string().nullable().optional(),
1171
- raw_response: z2.string().nullable().optional(),
1172
- created_at: z2.number().int().optional()
1173
- }).strict();
1174
- var PositionRecordSchema = z2.object({
1175
- id: z2.string(),
1176
- exchange: z2.string().min(1),
1177
- symbol: z2.string().min(1),
1178
- side: z2.enum(["LONG", "SHORT"]),
1179
- entry_price: z2.number().nullable().optional(),
1180
- mark_price: z2.number().nullable().optional(),
1181
- liquidation_price: z2.number().nullable().optional(),
1182
- leverage: z2.number().nullable().optional(),
1183
- size: z2.number().nullable(),
1184
- unrealized_pnl: z2.number().nullable().optional(),
1185
- status: z2.enum(["OPEN", "CLOSED"]),
1186
- updated_at: z2.number().int()
1187
- }).strict();
1188
- var BalanceRecordSchema = z2.object({
1189
- id: z2.string(),
1190
- exchange: z2.string().min(1),
1191
- asset: z2.string().min(1),
1192
- free: z2.number().nullable(),
1193
- used: z2.number().nullable(),
1194
- total: z2.number().nullable(),
1195
- timestamp: z2.number().int().positive()
1196
- }).strict();
1197
- var SystemLogRecordSchema = z2.object({
1198
- id: z2.string(),
1199
- timestamp: z2.number().int().optional(),
1200
- level: z2.string().min(1),
1201
- service: z2.string().min(1),
1202
- message: z2.string().min(1),
1203
- details: z2.string().nullable().optional()
1204
- }).strict();
1205
- var TradeSignalRecordSchema = z2.object({
1206
- signal_id: z2.string(),
1207
- timestamp: z2.number().int().positive(),
1208
- symbol: z2.string().min(1),
1209
- signal_type: z2.string().min(1),
1210
- source: z2.string().nullable().optional(),
1211
- raw_data: z2.string().nullable().optional(),
1212
- processed_at: z2.number().int().optional()
1213
- }).strict();
1214
994
  // src/middleware/auth.ts
1215
995
  function timingSafeEqual(a, b) {
1216
996
  const encoder = new TextEncoder;
@@ -1439,103 +1219,6 @@ function serviceFetch(binding, path, body, options) {
1439
1219
  signal: AbortSignal.timeout(timeout)
1440
1220
  });
1441
1221
  }
1442
- // src/test-utils.ts
1443
- function createMockKV(overrides) {
1444
- return {
1445
- get: async (_key) => null,
1446
- put: async (_key, _value, _options) => {},
1447
- delete: async (_key) => {},
1448
- list: async (_options) => ({
1449
- keys: []
1450
- }),
1451
- ...overrides
1452
- };
1453
- }
1454
- function createMockR2(overrides) {
1455
- return {
1456
- put: async (key, _value, _options) => ({
1457
- key
1458
- }),
1459
- get: async (_key) => null,
1460
- delete: async (_key) => {},
1461
- list: async (_options) => ({
1462
- objects: []
1463
- }),
1464
- ...overrides
1465
- };
1466
- }
1467
- function createMockPreparedStatement() {
1468
- return {
1469
- bind: (..._params) => createMockPreparedStatement(),
1470
- run: async () => ({ success: true }),
1471
- all: async () => ({ success: true, results: [] }),
1472
- first: async () => null
1473
- };
1474
- }
1475
- function createMockD1(overrides) {
1476
- return {
1477
- prepare: (_sql) => createMockPreparedStatement(),
1478
- dump: async () => new ArrayBuffer(0),
1479
- batch: async (_statements) => [],
1480
- exec: async (_sql) => ({ success: true }),
1481
- ...overrides
1482
- };
1483
- }
1484
- function createMockFetcher(response) {
1485
- const defaultResponse = response ?? new Response("OK", { status: 200 });
1486
- return {
1487
- fetch: async (_request, _init) => defaultResponse
1488
- };
1489
- }
1490
- function createMockAnalyticsEngine() {
1491
- return {
1492
- writeDataPoint: (_data) => {}
1493
- };
1494
- }
1495
- function createMockAnalyticsEngineDataset() {
1496
- return createMockAnalyticsEngine();
1497
- }
1498
- function createMockQueue() {
1499
- return {
1500
- send: async (_body, _options) => {},
1501
- sendBatch: async (_messages) => {}
1502
- };
1503
- }
1504
- function createMockVectorizeIndex(overrides) {
1505
- return {
1506
- query: async (_vector, _options) => ({
1507
- matches: []
1508
- }),
1509
- upsert: async (_vectors) => {},
1510
- deleteByIds: async (_ids) => {},
1511
- describe: async () => ({ dimension: 384, totalVectors: 0 }),
1512
- ...overrides
1513
- };
1514
- }
1515
- function createMockAi(overrides) {
1516
- return {
1517
- run: async (_model, _input) => ({}),
1518
- ...overrides
1519
- };
1520
- }
1521
- function createMockExecutionContext(overrides) {
1522
- return {
1523
- waitUntil: (_promise) => {},
1524
- passThroughOnException: () => {},
1525
- ...overrides
1526
- };
1527
- }
1528
- function createMockSecretsStore(overrides) {
1529
- return {
1530
- get: async (_key) => null,
1531
- put: async (_key, _value) => {},
1532
- delete: async (_key) => {},
1533
- ...overrides
1534
- };
1535
- }
1536
- function createMockEnv(overrides) {
1537
- return overrides;
1538
- }
1539
1222
  // src/colors.ts
1540
1223
  var Colors = {
1541
1224
  background: "#050508",
@@ -1718,149 +1401,49 @@ function formatCpu(ms) {
1718
1401
  }
1719
1402
  // src/config.ts
1720
1403
  import {
1721
- readFileSync,
1722
- writeFileSync,
1723
- existsSync,
1724
- mkdirSync,
1725
- chmodSync,
1404
+ readFileSync as readFileSync2,
1405
+ writeFileSync as writeFileSync2,
1406
+ existsSync as existsSync2,
1407
+ mkdirSync as mkdirSync2,
1408
+ chmodSync as chmodSync2,
1726
1409
  statSync
1727
1410
  } from "fs";
1728
- import { join } from "path";
1729
- import { homedir } from "os";
1730
- var HOX_DIR = join(homedir(), ".hoox");
1731
- var CONFIG_PATH = join(HOX_DIR, "config.json");
1732
- var HOOX_DIR_MODE = 448;
1733
- var HOOX_CONFIG_FILE_MODE = 384;
1734
- var DEFAULT_CONFIG = {
1735
- apiUrl: "http://localhost:8787",
1736
- apiToken: "",
1737
- transport: "public",
1738
- refreshIntervalMs: 500,
1739
- theme: "dark",
1740
- activeExchanges: ["binance", "bybit", "mexc"],
1741
- notifications: {
1742
- alerts: true,
1743
- trades: true,
1744
- debug: false,
1745
- system: true
1746
- },
1747
- soundEnabled: true,
1748
- defaultView: "dashboard"
1749
- };
1750
- function readConfigSync() {
1751
- const envConfig = {
1752
- apiUrl: process.env.HOOX_API_URL,
1753
- apiToken: process.env.HOOX_API_TOKEN
1754
- };
1755
- let fileConfig = {};
1756
- try {
1757
- if (existsSync(CONFIG_PATH)) {
1758
- fileConfig = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
1759
- }
1760
- } catch {}
1761
- return {
1762
- ...DEFAULT_CONFIG,
1763
- ...fileConfig,
1764
- ...Object.fromEntries(Object.entries(envConfig).filter(([_, v]) => v !== undefined))
1765
- };
1766
- }
1767
- async function readConfig() {
1768
- return readConfigSync();
1769
- }
1770
- function ensureHooxDirSecure(dir = HOX_DIR) {
1771
- if (!existsSync(dir)) {
1772
- mkdirSync(dir, { recursive: true, mode: HOOX_DIR_MODE });
1773
- }
1774
- try {
1775
- chmodSync(dir, HOOX_DIR_MODE);
1776
- } catch {}
1777
- }
1778
- function writeSecureFileSync(filePath, contents, mode = HOOX_CONFIG_FILE_MODE) {
1779
- writeFileSync(filePath, contents, { encoding: "utf-8", mode });
1780
- try {
1781
- chmodSync(filePath, mode);
1782
- } catch {}
1783
- }
1784
- function isUnixModeGroupOrWorldAccessible(mode) {
1785
- return (mode & 63) !== 0;
1786
- }
1787
- function isConfigWorldOrGroupReadable(filePath = CONFIG_PATH) {
1788
- try {
1789
- if (!existsSync(filePath))
1790
- return false;
1791
- return isUnixModeGroupOrWorldAccessible(statSync(filePath).mode & 511);
1792
- } catch {
1793
- return false;
1794
- }
1795
- }
1796
- function formatConfigPermissionWarning(filePath = CONFIG_PATH) {
1797
- if (!isConfigWorldOrGroupReadable(filePath))
1798
- return null;
1799
- return `Warning: ${filePath} is group/world-accessible. ` + `Run: chmod 600 ${filePath} (and chmod 700 on the parent dir). ` + `This file may contain apiToken / operator secrets.`;
1800
- }
1801
- function writeConfigSync(config) {
1802
- try {
1803
- ensureHooxDirSecure(HOX_DIR);
1804
- writeSecureFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), HOOX_CONFIG_FILE_MODE);
1805
- } catch (err) {
1806
- console.error("Failed to write config:", err);
1807
- }
1808
- }
1809
- async function writeConfig(config) {
1810
- writeConfigSync(config);
1811
- }
1812
- var VALID_TRANSPORTS = new Set([
1813
- "public",
1814
- "access",
1815
- "mtls",
1816
- "tunnel"
1817
- ]);
1818
- function validateConfig(config) {
1819
- const errors = [];
1820
- if (config.apiUrl && !config.apiUrl.startsWith("http")) {
1821
- errors.push("apiUrl must start with http:// or https://");
1822
- }
1823
- if (config.refreshIntervalMs !== undefined && config.refreshIntervalMs < 100) {
1824
- errors.push("refreshIntervalMs must be >= 100ms");
1825
- }
1826
- if (config.theme && !["dark", "light"].includes(config.theme)) {
1827
- errors.push('theme must be "dark" or "light"');
1828
- }
1829
- if (config.transport !== undefined && !VALID_TRANSPORTS.has(config.transport)) {
1830
- errors.push('transport must be "public" | "access" | "mtls" | "tunnel"');
1831
- }
1832
- return errors;
1833
- }
1834
-
1835
- // src/index.ts
1836
- init_api_client();
1837
- init_sse();
1838
- init_operator_transport();
1411
+ import { join as join2 } from "path";
1839
1412
 
1840
1413
  // src/path-utils.ts
1841
1414
  import {
1842
- existsSync as existsSync2,
1843
- mkdirSync as mkdirSync2,
1844
- readFileSync as readFileSync2,
1845
- writeFileSync as writeFileSync2,
1846
- chmodSync as chmodSync2
1415
+ existsSync,
1416
+ mkdirSync,
1417
+ readFileSync,
1418
+ writeFileSync,
1419
+ chmodSync
1847
1420
  } from "fs";
1848
- import { homedir as homedir2 } from "os";
1849
- import { dirname, join as join2, resolve } from "path";
1421
+ import { homedir } from "os";
1422
+ import { dirname, join, resolve, sep } from "path";
1850
1423
  function createHooxPath(path) {
1851
1424
  return path;
1852
1425
  }
1426
+ function isInsideDir(child, parent) {
1427
+ const resolvedChild = resolve(child);
1428
+ const resolvedParent = resolve(parent);
1429
+ const cmpChild = process.platform === "win32" ? resolvedChild.toLowerCase() : resolvedChild;
1430
+ const cmpParent = process.platform === "win32" ? resolvedParent.toLowerCase() : resolvedParent;
1431
+ if (cmpChild === cmpParent)
1432
+ return true;
1433
+ const prefix = cmpParent.endsWith(sep) ? cmpParent : cmpParent + sep;
1434
+ return cmpChild.startsWith(prefix);
1435
+ }
1853
1436
  function getHooxHome() {
1854
1437
  try {
1855
1438
  const override = process.env.HOOX_HOME?.trim();
1856
1439
  if (override) {
1857
1440
  return createHooxPath(resolve(override));
1858
1441
  }
1859
- const home = homedir2();
1442
+ const home = homedir();
1860
1443
  if (!home || home.length === 0) {
1861
1444
  return createHooxPath(resolve(process.cwd(), ".hoox"));
1862
1445
  }
1863
- return createHooxPath(join2(home, ".hoox"));
1446
+ return createHooxPath(join(home, ".hoox"));
1864
1447
  } catch {
1865
1448
  return createHooxPath(resolve(process.cwd(), ".hoox"));
1866
1449
  }
@@ -1870,10 +1453,10 @@ function isHooxSetupRoot(dir) {
1870
1453
  return false;
1871
1454
  try {
1872
1455
  const root = resolve(dir);
1873
- if (!existsSync2(join2(root, "packages", "cli", "package.json"))) {
1456
+ if (!existsSync(join(root, "packages", "cli", "package.json"))) {
1874
1457
  return false;
1875
1458
  }
1876
- return existsSync2(join2(root, "wrangler.jsonc")) || existsSync2(join2(root, "wrangler.jsonc.example")) || existsSync2(join2(root, "workers")) || existsSync2(join2(root, ".gitmodules"));
1459
+ return existsSync(join(root, "wrangler.jsonc")) || existsSync(join(root, "wrangler.jsonc.example")) || existsSync(join(root, "workers")) || existsSync(join(root, ".gitmodules"));
1877
1460
  } catch {
1878
1461
  return false;
1879
1462
  }
@@ -1884,9 +1467,9 @@ function getRememberedMonorepoPath() {
1884
1467
  function getRememberedMonorepoRoot() {
1885
1468
  try {
1886
1469
  const path = getRememberedMonorepoPath();
1887
- if (!existsSync2(path))
1470
+ if (!existsSync(path))
1888
1471
  return null;
1889
- const raw = readFileSync2(path, "utf-8");
1472
+ const raw = readFileSync(path, "utf-8");
1890
1473
  const data = JSON.parse(raw);
1891
1474
  if (!data.root || typeof data.root !== "string")
1892
1475
  return null;
@@ -1902,21 +1485,21 @@ function rememberMonorepoRoot(root) {
1902
1485
  if (!isHooxSetupRoot(resolved))
1903
1486
  return false;
1904
1487
  const configDir = getHooxConfigDir();
1905
- mkdirSync2(configDir, { recursive: true, mode: 448 });
1488
+ mkdirSync(configDir, { recursive: true, mode: 448 });
1906
1489
  try {
1907
- chmodSync2(configDir, 448);
1490
+ chmodSync(configDir, 448);
1908
1491
  } catch {}
1909
1492
  const payload = {
1910
1493
  root: resolved,
1911
1494
  updatedAt: new Date().toISOString()
1912
1495
  };
1913
1496
  const path = getRememberedMonorepoPath();
1914
- writeFileSync2(path, JSON.stringify(payload, null, 2) + `
1497
+ writeFileSync(path, JSON.stringify(payload, null, 2) + `
1915
1498
  `, {
1916
1499
  mode: 384
1917
1500
  });
1918
1501
  try {
1919
- chmodSync2(path, 384);
1502
+ chmodSync(path, 384);
1920
1503
  } catch {}
1921
1504
  return true;
1922
1505
  } catch {
@@ -1996,9 +1579,9 @@ function resolveHooxRuntimeRoot(options) {
1996
1579
  function getTuiEntryCandidates(runtimeRoot) {
1997
1580
  const root = resolve(runtimeRoot);
1998
1581
  return [
1999
- join2(root, "packages", "tui", "src", "main.tsx"),
2000
- join2(root, "packages", "tui", "dist", "main.js"),
2001
- join2(root, "packages", "tui", "src", "main.ts")
1582
+ join(root, "packages", "tui", "src", "main.tsx"),
1583
+ join(root, "packages", "tui", "dist", "main.js"),
1584
+ join(root, "packages", "tui", "src", "main.ts")
2002
1585
  ];
2003
1586
  }
2004
1587
  function resolveHooxPath(relativePath) {
@@ -2010,10 +1593,10 @@ function resolveHooxPath(relativePath) {
2010
1593
  }
2011
1594
  const normalizedPath = relativePath.replace(/\\/g, "/");
2012
1595
  const hooxHome = getHooxHome();
2013
- const fullPath = join2(hooxHome, normalizedPath);
1596
+ const fullPath = join(hooxHome, normalizedPath);
2014
1597
  const resolvedPath = resolve(fullPath);
2015
1598
  const resolvedHome = resolve(hooxHome);
2016
- if (!resolvedPath.startsWith(resolvedHome)) {
1599
+ if (!isInsideDir(resolvedPath, resolvedHome)) {
2017
1600
  throw new Error(`Resolved path "${resolvedPath}" is outside Hoox home directory`);
2018
1601
  }
2019
1602
  return createHooxPath(resolvedPath);
@@ -2023,7 +1606,7 @@ function isWithinHooxHome(path) {
2023
1606
  const hooxHome = getHooxHome();
2024
1607
  const resolvedPath = resolve(path);
2025
1608
  const resolvedHome = resolve(hooxHome);
2026
- return resolvedPath.startsWith(resolvedHome);
1609
+ return isInsideDir(resolvedPath, resolvedHome);
2027
1610
  } catch {
2028
1611
  return false;
2029
1612
  }
@@ -2058,17 +1641,128 @@ function getHooxStatePath() {
2058
1641
  return resolveHooxPath("data/state.json");
2059
1642
  }
2060
1643
 
1644
+ // src/config.ts
1645
+ function hooxDir() {
1646
+ return getHooxHome();
1647
+ }
1648
+ function configPath() {
1649
+ return join2(hooxDir(), "config.json");
1650
+ }
1651
+ var HOOX_DIR_MODE = 448;
1652
+ var HOOX_CONFIG_FILE_MODE = 384;
1653
+ var DEFAULT_CONFIG = {
1654
+ apiUrl: "http://localhost:8787",
1655
+ apiToken: "",
1656
+ transport: "public",
1657
+ refreshIntervalMs: 500,
1658
+ theme: "dark",
1659
+ activeExchanges: ["binance", "bybit", "mexc"],
1660
+ notifications: {
1661
+ alerts: true,
1662
+ trades: true,
1663
+ debug: false,
1664
+ system: true
1665
+ },
1666
+ soundEnabled: true,
1667
+ defaultView: "dashboard"
1668
+ };
1669
+ function readConfigSync() {
1670
+ const envConfig = {
1671
+ apiUrl: process.env.HOOX_API_URL,
1672
+ apiToken: process.env.HOOX_API_TOKEN
1673
+ };
1674
+ let fileConfig = {};
1675
+ try {
1676
+ if (existsSync2(configPath())) {
1677
+ fileConfig = JSON.parse(readFileSync2(configPath(), "utf-8"));
1678
+ }
1679
+ } catch {}
1680
+ return {
1681
+ ...DEFAULT_CONFIG,
1682
+ ...fileConfig,
1683
+ ...Object.fromEntries(Object.entries(envConfig).filter(([_, v]) => v !== undefined))
1684
+ };
1685
+ }
1686
+ async function readConfig() {
1687
+ return readConfigSync();
1688
+ }
1689
+ function ensureHooxDirSecure(dir = hooxDir()) {
1690
+ if (!existsSync2(dir)) {
1691
+ mkdirSync2(dir, { recursive: true, mode: HOOX_DIR_MODE });
1692
+ }
1693
+ try {
1694
+ chmodSync2(dir, HOOX_DIR_MODE);
1695
+ } catch {}
1696
+ }
1697
+ function writeSecureFileSync(filePath, contents, mode = HOOX_CONFIG_FILE_MODE) {
1698
+ writeFileSync2(filePath, contents, { encoding: "utf-8", mode });
1699
+ try {
1700
+ chmodSync2(filePath, mode);
1701
+ } catch {}
1702
+ }
1703
+ function isUnixModeGroupOrWorldAccessible(mode) {
1704
+ return (mode & 63) !== 0;
1705
+ }
1706
+ function isConfigWorldOrGroupReadable(filePath = configPath()) {
1707
+ try {
1708
+ if (!existsSync2(filePath))
1709
+ return false;
1710
+ return isUnixModeGroupOrWorldAccessible(statSync(filePath).mode & 511);
1711
+ } catch {
1712
+ return false;
1713
+ }
1714
+ }
1715
+ function formatConfigPermissionWarning(filePath = configPath()) {
1716
+ if (!isConfigWorldOrGroupReadable(filePath))
1717
+ return null;
1718
+ return `Warning: ${filePath} is group/world-accessible. ` + `Run: chmod 600 ${filePath} (and chmod 700 on the parent dir). ` + `This file may contain apiToken / operator secrets.`;
1719
+ }
1720
+ function writeConfigSync(config) {
1721
+ ensureHooxDirSecure(hooxDir());
1722
+ writeSecureFileSync(configPath(), JSON.stringify(config, null, 2), HOOX_CONFIG_FILE_MODE);
1723
+ }
1724
+ async function writeConfig(config) {
1725
+ writeConfigSync(config);
1726
+ }
1727
+ var VALID_TRANSPORTS = new Set([
1728
+ "public",
1729
+ "access",
1730
+ "mtls",
1731
+ "tunnel"
1732
+ ]);
1733
+ function validateConfig(config) {
1734
+ const errors = [];
1735
+ if (config.apiUrl && !config.apiUrl.startsWith("http")) {
1736
+ errors.push("apiUrl must start with http:// or https://");
1737
+ }
1738
+ if (config.refreshIntervalMs !== undefined && config.refreshIntervalMs < 100) {
1739
+ errors.push("refreshIntervalMs must be >= 100ms");
1740
+ }
1741
+ if (config.theme && !["dark", "light"].includes(config.theme)) {
1742
+ errors.push('theme must be "dark" or "light"');
1743
+ }
1744
+ if (config.transport !== undefined && !VALID_TRANSPORTS.has(config.transport)) {
1745
+ errors.push('transport must be "public" | "access" | "mtls" | "tunnel"');
1746
+ }
1747
+ return errors;
1748
+ }
1749
+
1750
+ // src/index.ts
1751
+ init_api_client();
1752
+ init_sse();
1753
+ init_operator_transport();
1754
+
2061
1755
  // src/session.ts
2062
- import { z as z3 } from "zod";
2063
- var SessionStateFileSchema = z3.object({
2064
- activeView: z3.string().optional(),
2065
- sidebarExpanded: z3.boolean().optional(),
2066
- windowSize: z3.object({
2067
- cols: z3.number().int().positive(),
2068
- rows: z3.number().int().positive()
1756
+ import { z as z2 } from "zod";
1757
+ var SessionStateFileSchema = z2.object({
1758
+ activeView: z2.string().optional(),
1759
+ sidebarExpanded: z2.boolean().optional(),
1760
+ windowSize: z2.object({
1761
+ cols: z2.number().int().positive(),
1762
+ rows: z2.number().int().positive()
2069
1763
  }).optional(),
2070
- lastData: z3.number().nonnegative().optional(),
2071
- savedAt: z3.string().optional()
1764
+ lastData: z2.number().nonnegative().optional(),
1765
+ savedAt: z2.string().optional()
2072
1766
  }).strict();
2073
1767
  var TUI_STATE_DIR = ".tui-state";
2074
1768
  function getSessionFilePath() {
@@ -2105,7 +1799,7 @@ async function restoreSession() {
2105
1799
  const file = Bun.file(getSessionFilePath());
2106
1800
  const exists = await file.exists();
2107
1801
  if (!exists)
2108
- return { ...DEFAULT_SESSION };
1802
+ return { ...DEFAULT_SESSION, fromDisk: false };
2109
1803
  const raw = await file.text();
2110
1804
  const result = SessionStateFileSchema.safeParse(JSON.parse(raw));
2111
1805
  const parsed = result.success ? result.data : {};
@@ -2114,30 +1808,14 @@ async function restoreSession() {
2114
1808
  sidebarExpanded: typeof parsed.sidebarExpanded === "boolean" ? parsed.sidebarExpanded : DEFAULT_SESSION.sidebarExpanded,
2115
1809
  windowSize: parsed.windowSize ?? DEFAULT_SESSION.windowSize,
2116
1810
  lastData: typeof parsed.lastData === "number" ? parsed.lastData : DEFAULT_SESSION.lastData,
2117
- savedAt: parsed.savedAt ?? DEFAULT_SESSION.savedAt
1811
+ savedAt: parsed.savedAt ?? DEFAULT_SESSION.savedAt,
1812
+ fromDisk: true
2118
1813
  };
2119
1814
  } catch {
2120
- return { ...DEFAULT_SESSION };
1815
+ return { ...DEFAULT_SESSION, fromDisk: false };
2121
1816
  }
2122
1817
  }
2123
- var VALID_VIEW_IDS = new Set([
2124
- "dashboard",
2125
- "workers",
2126
- "worker-detail",
2127
- "trade-monitor",
2128
- "logs-viewer",
2129
- "service-manager",
2130
- "config-editor",
2131
- "setup-wizard",
2132
- "settings",
2133
- "queue-depth",
2134
- "kv-viewer",
2135
- "secrets-viewer",
2136
- "db-query",
2137
- "ai-chat",
2138
- "edge-topology",
2139
- "worker-settings"
2140
- ]);
1818
+ var VALID_VIEW_IDS = new Set(VIEW_IDS);
2141
1819
  function validateViewId(id) {
2142
1820
  return typeof id === "string" && VALID_VIEW_IDS.has(id);
2143
1821
  }
@@ -2821,7 +2499,7 @@ var INTEGRATIONS = [
2821
2499
  label: "OpenAI (AI Agent)",
2822
2500
  workerName: "agent-worker",
2823
2501
  secrets: {
2824
- AGENT_INTERNAL_KEY: "OpenAI API Key"
2502
+ OPENAI_API_KEY: "OpenAI API Key"
2825
2503
  }
2826
2504
  },
2827
2505
  {
@@ -2829,7 +2507,7 @@ var INTEGRATIONS = [
2829
2507
  label: "Anthropic (AI Agent)",
2830
2508
  workerName: "agent-worker",
2831
2509
  secrets: {
2832
- AGENT_INTERNAL_KEY: "Anthropic API Key"
2510
+ ANTHROPIC_API_KEY: "Anthropic API Key"
2833
2511
  }
2834
2512
  },
2835
2513
  {
@@ -2837,7 +2515,7 @@ var INTEGRATIONS = [
2837
2515
  label: "Google AI (AI Agent)",
2838
2516
  workerName: "agent-worker",
2839
2517
  secrets: {
2840
- AGENT_INTERNAL_KEY: "Google AI API Key"
2518
+ GOOGLE_API_KEY: "Google AI API Key"
2841
2519
  }
2842
2520
  },
2843
2521
  {
@@ -3058,8 +2736,9 @@ class WizardEngine {
3058
2736
  const previousStep = STEP_ORDER[currentIdx - 1];
3059
2737
  if (previousStep === undefined)
3060
2738
  return;
2739
+ const leaving = this.state.step;
3061
2740
  this.state.step = previousStep;
3062
- this.state.completedSteps = this.state.completedSteps.filter((s) => s !== this.state.step || STEP_ORDER.indexOf(s) <= STEP_ORDER.indexOf(previousStep));
2741
+ this.state.completedSteps = this.state.completedSteps.filter((s) => s !== leaving && STEP_ORDER.indexOf(s) < STEP_ORDER.indexOf(previousStep));
3063
2742
  }
3064
2743
  this.state.updatedAt = Date.now();
3065
2744
  }
@@ -3069,9 +2748,15 @@ class WizardEngine {
3069
2748
  buildConfig() {
3070
2749
  const cf = this.state.cloudflareConfig;
3071
2750
  const workers = {};
2751
+ const enabled = new Set(this.state.selectedWorkers);
2752
+ for (const key of this.state.selectedIntegrations) {
2753
+ const integration = INTEGRATIONS.find((i) => i.key === key);
2754
+ if (integration?.workerName)
2755
+ enabled.add(integration.workerName);
2756
+ }
3072
2757
  for (const [name, baseCfg] of Object.entries(BASE_WORKERS)) {
3073
2758
  workers[name] = {
3074
- enabled: true,
2759
+ enabled: enabled.has(name),
3075
2760
  path: baseCfg.path,
3076
2761
  vars: { ...baseCfg.vars },
3077
2762
  secrets: [...BASE_SECRETS[name] ?? []]
@@ -3131,7 +2816,6 @@ class WizardEngine {
3131
2816
  vectorizeIndexes: []
3132
2817
  };
3133
2818
  const allWorkers = [
3134
- ...Object.keys(BASE_WORKERS),
3135
2819
  ...this.state.selectedWorkers,
3136
2820
  ...this.state.selectedIntegrations.map((k) => {
3137
2821
  const i = INTEGRATIONS.find((i) => i.key === k);
@@ -3150,7 +2834,7 @@ class WizardEngine {
3150
2834
  }
3151
2835
  if (workerSet.has("hoox") || workerSet.has("telegram-worker")) {
3152
2836
  plan.vectorizeIndexes = plan.vectorizeIndexes ?? [];
3153
- plan.vectorizeIndexes.push("hoox-rag-index");
2837
+ plan.vectorizeIndexes.push("my-rag-index");
3154
2838
  }
3155
2839
  plan.d1Databases = [...new Set(plan.d1Databases)];
3156
2840
  plan.kvNamespaces = [...new Set(plan.kvNamespaces)];
@@ -3481,7 +3165,10 @@ var manifests = {
3481
3165
  INTERNAL_KEY_BINDING: {
3482
3166
  type: "secret",
3483
3167
  description: "Inter-worker auth key"
3484
- }
3168
+ },
3169
+ OPENAI_API_KEY: { type: "secret", description: "OpenAI API key" },
3170
+ ANTHROPIC_API_KEY: { type: "secret", description: "Anthropic API key" },
3171
+ GOOGLE_API_KEY: { type: "secret", description: "Google AI API key" }
3485
3172
  },
3486
3173
  services: [
3487
3174
  {
@@ -3699,12 +3386,15 @@ function getWorkerDefaultEnabled(name) {
3699
3386
  }
3700
3387
  // src/schemas/validators.ts
3701
3388
  import { parse } from "jsonc-parser";
3389
+ function parseJsoncDocument(content) {
3390
+ const errors = [];
3391
+ const value = parse(content, errors, { allowTrailingComma: true });
3392
+ return { value, errors };
3393
+ }
3702
3394
  function validateWranglerJsonc(workerName, manifest, jsoncContent) {
3703
3395
  const errors = [];
3704
- let parsed;
3705
- try {
3706
- parsed = parse(jsoncContent);
3707
- } catch {
3396
+ const { value: parsed, errors: parseErrors } = parseJsoncDocument(jsoncContent);
3397
+ if (parseErrors.length > 0) {
3708
3398
  errors.push({
3709
3399
  worker: workerName,
3710
3400
  severity: "error",
@@ -3735,7 +3425,7 @@ function validateWranglerJsonc(workerName, manifest, jsoncContent) {
3735
3425
  }
3736
3426
  }
3737
3427
  for (const [name, def] of Object.entries(manifest.vars)) {
3738
- 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") {
3428
+ 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" && name !== "OPENAI_API_KEY" && name !== "ANTHROPIC_API_KEY" && name !== "GOOGLE_API_KEY") {
3739
3429
  errors.push({
3740
3430
  worker: workerName,
3741
3431
  severity: "warning",
@@ -3781,10 +3471,8 @@ function validateWranglerJsonc(workerName, manifest, jsoncContent) {
3781
3471
  }
3782
3472
  function validateRootSecrets(workerName, manifest, rootJsoncContent) {
3783
3473
  const errors = [];
3784
- let parsed;
3785
- try {
3786
- parsed = parse(rootJsoncContent);
3787
- } catch {
3474
+ const { value: parsed, errors: parseErrors } = parseJsoncDocument(rootJsoncContent);
3475
+ if (parseErrors.length > 0) {
3788
3476
  errors.push({
3789
3477
  worker: workerName,
3790
3478
  severity: "error",
@@ -3842,6 +3530,11 @@ function validateDevVars(workerName, manifest, devVarsContent) {
3842
3530
  }
3843
3531
  return errors;
3844
3532
  }
3533
+ function queueProducerBinding(queue) {
3534
+ if (queue === "trade-execution")
3535
+ return "TRADE_QUEUE";
3536
+ return queue.replace(/[^A-Za-z0-9]+/g, "_").toUpperCase();
3537
+ }
3845
3538
  function generateWranglerJsonc(manifest) {
3846
3539
  const parts = [];
3847
3540
  parts.push(`{`);
@@ -3908,21 +3601,29 @@ function generateWranglerJsonc(manifest) {
3908
3601
  parts.push(` ],`);
3909
3602
  }
3910
3603
  if (infra.queues) {
3911
- if (infra.queues.producer && infra.queues.producer.length > 0) {
3912
- parts.push(` "queues": { "producers": [`);
3913
- for (const [i, q] of infra.queues.producer.entries()) {
3914
- const comma = i < infra.queues.producer.length - 1 ? "," : "";
3915
- parts.push(` { "queue": "${q}", "binding": "" }${comma}`);
3604
+ const producers = infra.queues.producer ?? [];
3605
+ const consumers = infra.queues.consumer ?? [];
3606
+ if (producers.length > 0 || consumers.length > 0) {
3607
+ const queueParts = [];
3608
+ if (producers.length > 0) {
3609
+ queueParts.push(` "producers": [`);
3610
+ for (const [i, q] of producers.entries()) {
3611
+ const comma = i < producers.length - 1 ? "," : "";
3612
+ queueParts.push(` { "queue": "${q}", "binding": "${queueProducerBinding(q)}" }${comma}`);
3613
+ }
3614
+ queueParts.push(consumers.length > 0 ? ` ],` : ` ]`);
3916
3615
  }
3917
- parts.push(` ] },`);
3918
- }
3919
- if (infra.queues.consumer && infra.queues.consumer.length > 0) {
3920
- parts.push(` "queues": { "consumers": [`);
3921
- for (const [i, q] of infra.queues.consumer.entries()) {
3922
- const comma = i < infra.queues.consumer.length - 1 ? "," : "";
3923
- parts.push(` { "queue": "${q}" }${comma}`);
3616
+ if (consumers.length > 0) {
3617
+ queueParts.push(` "consumers": [`);
3618
+ for (const [i, q] of consumers.entries()) {
3619
+ const comma = i < consumers.length - 1 ? "," : "";
3620
+ queueParts.push(` { "queue": "${q}" }${comma}`);
3621
+ }
3622
+ queueParts.push(` ]`);
3924
3623
  }
3925
- parts.push(` ] },`);
3624
+ parts.push(` "queues": {`);
3625
+ parts.push(...queueParts);
3626
+ parts.push(` },`);
3926
3627
  }
3927
3628
  }
3928
3629
  if (infra.durableObjects && infra.durableObjects.length > 0) {
@@ -4484,14 +4185,13 @@ export {
4484
4185
  AGENT_CONFIG_KV_KEY,
4485
4186
  AlertSeverityColor,
4486
4187
  BalanceSchema,
4487
- BaseExchangeClient2 as BaseExchangeClient,
4188
+ BaseExchangeClient,
4488
4189
  CALLED_BY,
4489
4190
  CLOUDFLARE_API_TOKEN_PLACEHOLDER,
4490
4191
  COPYRIGHT,
4491
4192
  Colors,
4492
4193
  ConnectionStatusColor,
4493
4194
  CoolBracketPalette,
4494
- D1Repository,
4495
4195
  D1_READ_AUTH_KEY_FIELDS,
4496
4196
  D1_WRITE_AUTH_KEY_FIELDS,
4497
4197
  DASHBOARD_D1_READ_AUTH_KEY_FIELDS,
@@ -4529,6 +4229,7 @@ export {
4529
4229
  TradeActionSchema,
4530
4230
  TradeQueueMessageSchema,
4531
4231
  TradeSignalSchema,
4232
+ VIEW_IDS,
4532
4233
  WALLET_EXECUTE_AUTH_KEY_FIELDS,
4533
4234
  WIZARD_STATE_PATH,
4534
4235
  WORKER_CATALOG,
@@ -4547,18 +4248,6 @@ export {
4547
4248
  createCronHandler,
4548
4249
  createErrorResponse,
4549
4250
  createJsonResponse,
4550
- createMockAi,
4551
- createMockAnalyticsEngine,
4552
- createMockAnalyticsEngineDataset,
4553
- createMockD1,
4554
- createMockEnv,
4555
- createMockExecutionContext,
4556
- createMockFetcher,
4557
- createMockKV,
4558
- createMockQueue,
4559
- createMockR2,
4560
- createMockSecretsStore,
4561
- createMockVectorizeIndex,
4562
4251
  createQueueHandler,
4563
4252
  createSuccessResponse,
4564
4253
  dashboardWorkerDir,