@hoox-sh/hoox-shared 1.2.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 (161) hide show
  1. package/README.md +44 -0
  2. package/dist/agent-config-fields.d.ts +32 -0
  3. package/dist/agent-config-fields.d.ts.map +1 -0
  4. package/dist/agent-config-fields.js +220 -0
  5. package/dist/analytics.d.ts +47 -0
  6. package/dist/analytics.d.ts.map +1 -0
  7. package/dist/analytics.js +45 -0
  8. package/dist/api-client.d.ts +48 -0
  9. package/dist/api-client.d.ts.map +1 -0
  10. package/dist/api-client.js +191 -0
  11. package/dist/colors.d.ts +76 -0
  12. package/dist/colors.d.ts.map +1 -0
  13. package/dist/colors.js +82 -0
  14. package/dist/config.d.ts +61 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +146 -0
  17. package/dist/cron-handler.d.ts +38 -0
  18. package/dist/cron-handler.d.ts.map +1 -0
  19. package/dist/cron-handler.js +48 -0
  20. package/dist/d1/index.d.ts +9 -0
  21. package/dist/d1/index.d.ts.map +1 -0
  22. package/dist/d1/index.js +360 -0
  23. package/dist/d1/repository.d.ts +86 -0
  24. package/dist/d1/repository.d.ts.map +1 -0
  25. package/dist/d1/repository.js +297 -0
  26. package/dist/d1/schemas.d.ts +89 -0
  27. package/dist/d1/schemas.d.ts.map +1 -0
  28. package/dist/d1/schemas.js +81 -0
  29. package/dist/dashboard-manifest.d.ts +135 -0
  30. package/dist/dashboard-manifest.d.ts.map +1 -0
  31. package/dist/dashboard-manifest.js +401 -0
  32. package/dist/errors.d.ts +44 -0
  33. package/dist/errors.d.ts.map +1 -0
  34. package/dist/errors.js +149 -0
  35. package/dist/exchange-client.d.ts +98 -0
  36. package/dist/exchange-client.d.ts.map +1 -0
  37. package/dist/exchange-client.js +107 -0
  38. package/dist/exchanges/base-exchange-client.d.ts +81 -0
  39. package/dist/exchanges/base-exchange-client.d.ts.map +1 -0
  40. package/dist/exchanges/base-exchange-client.js +123 -0
  41. package/dist/exchanges/index.d.ts +7 -0
  42. package/dist/exchanges/index.d.ts.map +1 -0
  43. package/dist/exchanges/index.js +123 -0
  44. package/dist/exchanges/types.d.ts +23 -0
  45. package/dist/exchanges/types.d.ts.map +1 -0
  46. package/dist/exchanges/types.js +1 -0
  47. package/dist/format-time.d.ts +28 -0
  48. package/dist/format-time.d.ts.map +1 -0
  49. package/dist/format-time.js +57 -0
  50. package/dist/formatters.d.ts +36 -0
  51. package/dist/formatters.d.ts.map +1 -0
  52. package/dist/formatters.js +155 -0
  53. package/dist/health.d.ts +19 -0
  54. package/dist/health.d.ts.map +1 -0
  55. package/dist/health.js +160 -0
  56. package/dist/index.d.ts +72 -0
  57. package/dist/index.d.ts.map +1 -0
  58. package/dist/index.js +4198 -0
  59. package/dist/kvKeys.d.ts +76 -0
  60. package/dist/kvKeys.d.ts.map +1 -0
  61. package/dist/kvKeys.js +107 -0
  62. package/dist/kvUtils.d.ts +30 -0
  63. package/dist/kvUtils.d.ts.map +1 -0
  64. package/dist/kvUtils.js +170 -0
  65. package/dist/legal.d.ts +15 -0
  66. package/dist/legal.d.ts.map +1 -0
  67. package/dist/legal.js +40 -0
  68. package/dist/middleware/auth.d.ts +102 -0
  69. package/dist/middleware/auth.d.ts.map +1 -0
  70. package/dist/middleware/auth.js +151 -0
  71. package/dist/middleware/cors.d.ts +58 -0
  72. package/dist/middleware/cors.d.ts.map +1 -0
  73. package/dist/middleware/cors.js +77 -0
  74. package/dist/middleware/index.d.ts +14 -0
  75. package/dist/middleware/index.d.ts.map +1 -0
  76. package/dist/middleware/index.js +635 -0
  77. package/dist/middleware/logger.d.ts +17 -0
  78. package/dist/middleware/logger.d.ts.map +1 -0
  79. package/dist/middleware/logger.js +202 -0
  80. package/dist/middleware/rate-limit.d.ts +30 -0
  81. package/dist/middleware/rate-limit.d.ts.map +1 -0
  82. package/dist/middleware/rate-limit.js +181 -0
  83. package/dist/middleware/security-headers.d.ts +43 -0
  84. package/dist/middleware/security-headers.d.ts.map +1 -0
  85. package/dist/middleware/security-headers.js +58 -0
  86. package/dist/middleware/validate.d.ts +25 -0
  87. package/dist/middleware/validate.d.ts.map +1 -0
  88. package/dist/middleware/validate.js +56 -0
  89. package/dist/operator-transport.d.ts +65 -0
  90. package/dist/operator-transport.d.ts.map +1 -0
  91. package/dist/operator-transport.js +87 -0
  92. package/dist/path-utils.d.ts +190 -0
  93. package/dist/path-utils.d.ts.map +1 -0
  94. package/dist/path-utils.js +188 -0
  95. package/dist/queue-handler.d.ts +47 -0
  96. package/dist/queue-handler.d.ts.map +1 -0
  97. package/dist/queue-handler.js +46 -0
  98. package/dist/router.d.ts +15 -0
  99. package/dist/router.d.ts.map +1 -0
  100. package/dist/router.js +130 -0
  101. package/dist/schemas/index.d.ts +8 -0
  102. package/dist/schemas/index.d.ts.map +1 -0
  103. package/dist/schemas/index.js +705 -0
  104. package/dist/schemas/registry.d.ts +12 -0
  105. package/dist/schemas/registry.d.ts.map +1 -0
  106. package/dist/schemas/registry.js +433 -0
  107. package/dist/schemas/types.d.ts +83 -0
  108. package/dist/schemas/types.d.ts.map +1 -0
  109. package/dist/schemas/types.js +1 -0
  110. package/dist/schemas/validators.d.ts +39 -0
  111. package/dist/schemas/validators.d.ts.map +1 -0
  112. package/dist/schemas/validators.js +290 -0
  113. package/dist/service-bindings.d.ts +78 -0
  114. package/dist/service-bindings.d.ts.map +1 -0
  115. package/dist/service-bindings.js +248 -0
  116. package/dist/session.d.ts +39 -0
  117. package/dist/session.d.ts.map +1 -0
  118. package/dist/session.js +261 -0
  119. package/dist/sse.d.ts +39 -0
  120. package/dist/sse.d.ts.map +1 -0
  121. package/dist/sse.js +154 -0
  122. package/dist/stores/config-store.d.ts +91 -0
  123. package/dist/stores/config-store.d.ts.map +1 -0
  124. package/dist/stores/config-store.js +255 -0
  125. package/dist/stores/service-store.d.ts +266 -0
  126. package/dist/stores/service-store.d.ts.map +1 -0
  127. package/dist/stores/service-store.js +516 -0
  128. package/dist/stores/ui-store.d.ts +67 -0
  129. package/dist/stores/ui-store.d.ts.map +1 -0
  130. package/dist/stores/ui-store.js +62 -0
  131. package/dist/test-utils.d.ts +136 -0
  132. package/dist/test-utils.d.ts.map +1 -0
  133. package/dist/test-utils.js +128 -0
  134. package/dist/types/errors.d.ts +20 -0
  135. package/dist/types/errors.d.ts.map +1 -0
  136. package/dist/types/errors.js +1 -0
  137. package/dist/types/router.d.ts +36 -0
  138. package/dist/types/router.d.ts.map +1 -0
  139. package/dist/types/router.js +1 -0
  140. package/dist/types.d.ts +442 -0
  141. package/dist/types.d.ts.map +1 -0
  142. package/dist/types.js +83 -0
  143. package/dist/wizard/engine.d.ts +46 -0
  144. package/dist/wizard/engine.d.ts.map +1 -0
  145. package/dist/wizard/engine.js +525 -0
  146. package/dist/wizard/index.d.ts +11 -0
  147. package/dist/wizard/index.d.ts.map +1 -0
  148. package/dist/wizard/index.js +544 -0
  149. package/dist/wizard/persistence.d.ts +18 -0
  150. package/dist/wizard/persistence.d.ts.map +1 -0
  151. package/dist/wizard/persistence.js +31 -0
  152. package/dist/wizard/presets.d.ts +38 -0
  153. package/dist/wizard/presets.d.ts.map +1 -0
  154. package/dist/wizard/presets.js +223 -0
  155. package/dist/wizard/provisioner.d.ts +15 -0
  156. package/dist/wizard/provisioner.d.ts.map +1 -0
  157. package/dist/wizard/provisioner.js +1 -0
  158. package/dist/wizard/types.d.ts +76 -0
  159. package/dist/wizard/types.d.ts.map +1 -0
  160. package/dist/wizard/types.js +1 -0
  161. package/package.json +117 -0
package/dist/index.js ADDED
@@ -0,0 +1,4198 @@
1
+ // @bun
2
+ var __defProp = Object.defineProperty;
3
+ var __returnValue = (v) => v;
4
+ function __exportSetter(name, newValue) {
5
+ this[name] = __returnValue.bind(null, newValue);
6
+ }
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, {
10
+ get: all[name],
11
+ enumerable: true,
12
+ configurable: true,
13
+ set: __exportSetter.bind(all, name)
14
+ });
15
+ };
16
+ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
+
18
+ // src/operator-transport.ts
19
+ function stripTrailingSlashes(url) {
20
+ return url.replace(/\/+$/, "");
21
+ }
22
+ function parseTransport(raw) {
23
+ if (!raw)
24
+ return null;
25
+ const v = raw.trim().toLowerCase();
26
+ if (VALID_TRANSPORTS.has(v)) {
27
+ return v;
28
+ }
29
+ return null;
30
+ }
31
+ function resolveOperatorTransportProfile(env = process.env, options = {}) {
32
+ const accessClientId = env.CF_ACCESS_CLIENT_ID?.trim() ?? "";
33
+ const accessClientSecret = env.CF_ACCESS_CLIENT_SECRET?.trim() ?? "";
34
+ const hasAccess = Boolean(accessClientId && accessClientSecret);
35
+ const explicit = parseTransport(env.HOOX_TRANSPORT) ?? parseTransport(options.configTransport);
36
+ const transport = explicit ?? (hasAccess ? "access" : "public");
37
+ const apiFromEnv = env.HOOX_API_URL?.trim();
38
+ const apiFromConfig = options.configApiUrl?.trim();
39
+ const tokenFromEnv = env.HOOX_API_TOKEN?.trim();
40
+ const tokenFromConfig = options.configApiToken?.trim();
41
+ return {
42
+ transport,
43
+ apiBase: stripTrailingSlashes(apiFromEnv || apiFromConfig || DEFAULT_API_BASE),
44
+ bearerToken: tokenFromEnv || tokenFromConfig || "",
45
+ accessClientId,
46
+ accessClientSecret
47
+ };
48
+ }
49
+ function buildOperatorAuthHeaders(profile) {
50
+ const headers = {};
51
+ if (profile.bearerToken) {
52
+ headers.Authorization = `Bearer ${profile.bearerToken}`;
53
+ }
54
+ const useAccessHeaders = profile.transport === "access" || Boolean(profile.accessClientId) && Boolean(profile.accessClientSecret);
55
+ if (useAccessHeaders && profile.accessClientId && profile.accessClientSecret) {
56
+ headers["CF-Access-Client-Id"] = profile.accessClientId;
57
+ headers["CF-Access-Client-Secret"] = profile.accessClientSecret;
58
+ }
59
+ return headers;
60
+ }
61
+ function hasOperatorClientCredentials(profile) {
62
+ if (profile.bearerToken)
63
+ return true;
64
+ return Boolean(profile.accessClientId && profile.accessClientSecret);
65
+ }
66
+ function operatorUrl(profile, path) {
67
+ const base = profile.apiBase.replace(/\/+$/, "");
68
+ const p = path.startsWith("/") ? path : `/${path}`;
69
+ return `${base}${p}`;
70
+ }
71
+ var DEFAULT_API_BASE = "http://localhost:8787", VALID_TRANSPORTS;
72
+ var init_operator_transport = __esm(() => {
73
+ VALID_TRANSPORTS = new Set([
74
+ "public",
75
+ "access",
76
+ "mtls",
77
+ "tunnel"
78
+ ]);
79
+ });
80
+
81
+ // src/api-client.ts
82
+ var exports_api_client = {};
83
+ __export(exports_api_client, {
84
+ hooxFetch: () => hooxFetch,
85
+ WorkerAPIError: () => WorkerAPIError
86
+ });
87
+ function isRetryableStatus(status) {
88
+ if (status === 401 || status === 403 || status === 429)
89
+ return false;
90
+ if (status >= 500)
91
+ return true;
92
+ if (status === 408)
93
+ return true;
94
+ return false;
95
+ }
96
+ function isNetworkError(error) {
97
+ if (error instanceof Error) {
98
+ const msg = error.message.toLowerCase();
99
+ return error.name === "AbortError" || error.name === "TimeoutError" || msg.includes("fetch") || msg.includes("network") || msg.includes("econnrefused") || msg.includes("enotfound") || msg.includes("dns") || msg.includes("timeout");
100
+ }
101
+ return false;
102
+ }
103
+ function sleep(ms) {
104
+ return new Promise((resolve) => setTimeout(resolve, ms));
105
+ }
106
+ function activeProfile(override) {
107
+ return override ?? resolveOperatorTransportProfile();
108
+ }
109
+ async function hooxFetch(path, options) {
110
+ const profile = options?.transport ?? (options?.transportEnv ? resolveOperatorTransportProfile(options.transportEnv) : activeProfile());
111
+ const { transport: _t, transportEnv: _e, ...fetchInit } = options ?? {};
112
+ let lastError;
113
+ for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
114
+ const controller = new AbortController;
115
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
116
+ try {
117
+ const url = operatorUrl(profile, path);
118
+ const headers = {
119
+ "Content-Type": "application/json",
120
+ ...buildOperatorAuthHeaders(profile),
121
+ ...fetchInit.headers
122
+ };
123
+ const response = await fetch(url, {
124
+ ...fetchInit,
125
+ headers,
126
+ signal: controller.signal
127
+ });
128
+ if (response.status === 429) {
129
+ throw new WorkerAPIError("API rate limited \u2014 backing off", {
130
+ status: 429,
131
+ retryable: false
132
+ });
133
+ }
134
+ if (response.status === 401 || response.status === 403) {
135
+ throw new WorkerAPIError(`Authentication failed (HTTP ${response.status})`, { status: response.status, retryable: false });
136
+ }
137
+ if (!response.ok) {
138
+ if (attempt < MAX_RETRIES && isRetryableStatus(response.status)) {
139
+ const delay = Math.min(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);
140
+ await sleep(delay);
141
+ continue;
142
+ }
143
+ throw new WorkerAPIError(`HTTP ${response.status}: ${response.statusText}`, {
144
+ status: response.status,
145
+ retryable: isRetryableStatus(response.status)
146
+ });
147
+ }
148
+ return await response.json();
149
+ } catch (error) {
150
+ lastError = error;
151
+ if (error instanceof WorkerAPIError && !error.retryable) {
152
+ throw error;
153
+ }
154
+ if (attempt < MAX_RETRIES && isNetworkError(error)) {
155
+ const delay = Math.min(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);
156
+ await sleep(delay);
157
+ continue;
158
+ }
159
+ if (!(error instanceof WorkerAPIError)) {
160
+ throw new WorkerAPIError(`Network request failed: ${error.message}`, { cause: error, retryable: false, status: 0 });
161
+ }
162
+ throw error;
163
+ } finally {
164
+ clearTimeout(timeoutId);
165
+ }
166
+ }
167
+ throw new WorkerAPIError("Max retries exhausted", {
168
+ cause: lastError,
169
+ retryable: false
170
+ });
171
+ }
172
+ var FETCH_TIMEOUT_MS = 5000, MAX_RETRIES = 3, BASE_RETRY_DELAY_MS = 1000, MAX_RETRY_DELAY_MS = 16000, WorkerAPIError;
173
+ var init_api_client = __esm(() => {
174
+ init_operator_transport();
175
+ WorkerAPIError = class WorkerAPIError extends Error {
176
+ status;
177
+ retryable;
178
+ constructor(message, options) {
179
+ super(message, { cause: options?.cause });
180
+ this.name = "WorkerAPIError";
181
+ this.status = options?.status ?? 0;
182
+ this.retryable = options?.retryable ?? false;
183
+ }
184
+ };
185
+ });
186
+
187
+ // src/sse.ts
188
+ var exports_sse = {};
189
+ __export(exports_sse, {
190
+ subscribeSSE: () => subscribeSSE
191
+ });
192
+ function subscribeSSE(path, callback, onStatus, options) {
193
+ let aborted = false;
194
+ const profile = options?.transport ?? (options?.transportEnv ? resolveOperatorTransportProfile(options.transportEnv) : resolveOperatorTransportProfile());
195
+ const run = async () => {
196
+ let attempt = 0;
197
+ while (!aborted && attempt < MAX_RECONNECT_ATTEMPTS) {
198
+ try {
199
+ const url = operatorUrl(profile, path);
200
+ const response = await fetch(url, {
201
+ headers: {
202
+ Accept: "text/event-stream",
203
+ ...buildOperatorAuthHeaders(profile)
204
+ }
205
+ });
206
+ if (!response.ok || !response.body) {
207
+ throw new Error(`SSE connection failed: HTTP ${response.status}`);
208
+ }
209
+ attempt = 0;
210
+ onStatus?.("connected");
211
+ const reader = response.body.getReader();
212
+ const decoder = new TextDecoder;
213
+ let buffer = "";
214
+ while (!aborted) {
215
+ const { done, value } = await reader.read();
216
+ if (done)
217
+ break;
218
+ buffer += decoder.decode(value, { stream: true });
219
+ const lines = buffer.split(`
220
+ `);
221
+ buffer = lines.pop() || "";
222
+ for (const line of lines) {
223
+ const trimmed = line.trim();
224
+ if (trimmed.startsWith("data: ")) {
225
+ try {
226
+ const parsed = JSON.parse(trimmed.slice(6));
227
+ callback(parsed);
228
+ } catch {}
229
+ }
230
+ }
231
+ }
232
+ } catch {
233
+ if (aborted)
234
+ break;
235
+ onStatus?.("reconnecting");
236
+ const delay = Math.min(RECONNECT_BASE_MS * Math.pow(2, attempt), RECONNECT_MAX_MS);
237
+ attempt++;
238
+ await new Promise((resolve) => setTimeout(resolve, delay));
239
+ }
240
+ }
241
+ if (!aborted) {
242
+ onStatus?.("disconnected");
243
+ }
244
+ };
245
+ run();
246
+ return {
247
+ abort: () => {
248
+ aborted = true;
249
+ }
250
+ };
251
+ }
252
+ var RECONNECT_BASE_MS = 1000, RECONNECT_MAX_MS = 16000, MAX_RECONNECT_ATTEMPTS = 10;
253
+ var init_sse = __esm(() => {
254
+ init_operator_transport();
255
+ });
256
+
257
+ // src/kvKeys.ts
258
+ var KV_TRADE_DEFAULT_LEVERAGE = "trade:default_leverage";
259
+ var KV_TRADE_MAX_POSITION_SIZE = "trade:max_position_size";
260
+ var KV_TRADE_MAX_DAILY_DRAWDOWN_PERCENT = "trade:max_daily_drawdown_percent";
261
+ var KV_TRADE_TRAILING_STOP_PERCENT = "trade:trailing_stop_percent";
262
+ var KV_TRADE_KILL_SWITCH = "trade:kill_switch";
263
+ var KV_TRADE_ROUTING = "trade:routing";
264
+ var KV_BOT_ENABLED = "bot:enabled";
265
+ var KV_BOT_DEFAULT_CHAT_ID = "bot:default_chat_id";
266
+ var KV_BOT_NOTIFY_ON_EXECUTION = "bot:notify_on_execution";
267
+ var KV_BOT_NOTIFY_ON_ERROR = "bot:notify_on_error";
268
+ var KV_WEBHOOK_IP_CHECK_ENABLED = "webhook:tradingview:ip_check_enabled";
269
+ var KV_WEBHOOK_ALLOWED_IPS = "webhook:tradingview:allowed_ips";
270
+ var KV_WEBHOOK_QUEUE_MODE = "webhook:queue_mode";
271
+ var KV_EMAIL_SCAN_SUBJECT = "email:scan_subject";
272
+ var KV_EMAIL_USE_IMAP = "email:use_imap";
273
+ var KV_EMAIL_COIN_PATTERN = "email:coin_pattern";
274
+ var KV_EMAIL_ACTION_PATTERN = "email:action_pattern";
275
+ var KV_EMAIL_QUANTITY_MULTIPLIER = "email:quantity_multiplier";
276
+ var KV_AGENT_CONFIG = "agent:config";
277
+ var KV_AGENT_OPENAI_KEY = "agent:openai_key";
278
+ var KV_AGENT_ANTHROPIC_KEY = "agent:anthropic_key";
279
+ var KV_AGENT_GOOGLE_KEY = "agent:google_key";
280
+ var KV_DASHBOARD_AI_HEALTH_SUMMARY = "dashboard:ai_health_summary";
281
+ var KV_HEALTH_CHECK = "health_check";
282
+ var KV_HOUSEKEEPING_LAST_CHECK = "housekeeping:last_check";
283
+ var QUEUE_MODE_FAILOVER = "queue_failover";
284
+ var QUEUE_MODE_EVERYWHERE = "queue_everywhere";
285
+ var QUEUE_MODE_DISABLED = "queue_disabled";
286
+ var KVKeys = {
287
+ KV_TRADE_DEFAULT_LEVERAGE,
288
+ KV_TRADE_MAX_POSITION_SIZE,
289
+ KV_TRADE_MAX_DAILY_DRAWDOWN_PERCENT,
290
+ KV_TRADE_TRAILING_STOP_PERCENT,
291
+ KV_TRADE_KILL_SWITCH,
292
+ KV_TRADE_ROUTING,
293
+ KV_BOT_ENABLED,
294
+ KV_BOT_DEFAULT_CHAT_ID,
295
+ KV_BOT_NOTIFY_ON_EXECUTION,
296
+ KV_BOT_NOTIFY_ON_ERROR,
297
+ KV_WEBHOOK_IP_CHECK_ENABLED,
298
+ KV_WEBHOOK_ALLOWED_IPS,
299
+ KV_WEBHOOK_QUEUE_MODE,
300
+ KV_EMAIL_SCAN_SUBJECT,
301
+ KV_EMAIL_USE_IMAP,
302
+ KV_EMAIL_COIN_PATTERN,
303
+ KV_EMAIL_ACTION_PATTERN,
304
+ KV_EMAIL_QUANTITY_MULTIPLIER,
305
+ KV_AGENT_CONFIG,
306
+ KV_AGENT_OPENAI_KEY,
307
+ KV_AGENT_ANTHROPIC_KEY,
308
+ KV_AGENT_GOOGLE_KEY,
309
+ KV_DASHBOARD_AI_HEALTH_SUMMARY,
310
+ KV_HEALTH_CHECK,
311
+ KV_HOUSEKEEPING_LAST_CHECK,
312
+ QUEUE_MODE_FAILOVER,
313
+ QUEUE_MODE_EVERYWHERE,
314
+ QUEUE_MODE_DISABLED
315
+ };
316
+
317
+ // src/agent-config-fields.ts
318
+ var AGENT_CONFIG_KV_KEY = KV_AGENT_CONFIG;
319
+ var AGENT_CONFIG_EMBEDDED_FIELDS = {
320
+ "providers:default_provider": { kind: "top", key: "defaultProvider" },
321
+ "providers:fallback_chain": { kind: "top", key: "fallbackChain" },
322
+ "providers:timeout_ms": { kind: "top", key: "timeoutMs" },
323
+ "providers:retry_count": { kind: "top", key: "retryCount" },
324
+ "models:workers_ai_model": { kind: "model", provider: "workers-ai" },
325
+ "models:openai_model": { kind: "model", provider: "openai" },
326
+ "models:anthropic_model": { kind: "model", provider: "anthropic" },
327
+ "models:google_model": { kind: "model", provider: "google" },
328
+ "risk:max_daily_drawdown_percent": {
329
+ kind: "top",
330
+ key: "maxDailyDrawdownPercent"
331
+ },
332
+ "risk:trailing_stop_percent": { kind: "top", key: "trailingStopPercent" },
333
+ "risk:take_profit_percent": { kind: "top", key: "takeProfitPercent" }
334
+ };
335
+ var DASHBOARD_SECTIONS_AGENT_CONFIG = new Set([
336
+ "providers",
337
+ "models"
338
+ ]);
339
+ function isAgentConfigEmbeddedField(fieldKey) {
340
+ return Object.prototype.hasOwnProperty.call(AGENT_CONFIG_EMBEDDED_FIELDS, fieldKey);
341
+ }
342
+ function isAgentConfigSection(sectionId) {
343
+ return DASHBOARD_SECTIONS_AGENT_CONFIG.has(sectionId);
344
+ }
345
+ function parseAgentConfigJson(raw) {
346
+ if (raw === null || raw === undefined || raw === "")
347
+ return {};
348
+ try {
349
+ let v = JSON.parse(raw);
350
+ if (typeof v === "string") {
351
+ try {
352
+ v = JSON.parse(v);
353
+ } catch {
354
+ return {};
355
+ }
356
+ }
357
+ if (v && typeof v === "object" && !Array.isArray(v)) {
358
+ return { ...v };
359
+ }
360
+ } catch {}
361
+ return {};
362
+ }
363
+ function serializeAgentConfigForKv(config) {
364
+ return JSON.stringify(config);
365
+ }
366
+ var NUMBER_TOP_KEYS = new Set([
367
+ "timeoutMs",
368
+ "retryCount",
369
+ "maxDailyDrawdownPercent",
370
+ "trailingStopPercent",
371
+ "takeProfitPercent"
372
+ ]);
373
+ function getAgentConfigEmbeddedValue(config, fieldKey) {
374
+ const spec = AGENT_CONFIG_EMBEDDED_FIELDS[fieldKey];
375
+ if (!spec)
376
+ return;
377
+ if (spec.kind === "model") {
378
+ const map = config.modelMap;
379
+ if (!map || typeof map !== "object" || Array.isArray(map))
380
+ return;
381
+ const v2 = map[spec.provider];
382
+ if (typeof v2 === "string" || typeof v2 === "number" || typeof v2 === "boolean")
383
+ return v2;
384
+ return;
385
+ }
386
+ const v = config[spec.key];
387
+ if (spec.key === "fallbackChain") {
388
+ if (Array.isArray(v))
389
+ return JSON.stringify(v);
390
+ if (typeof v === "string")
391
+ return v;
392
+ return;
393
+ }
394
+ if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
395
+ return v;
396
+ }
397
+ return;
398
+ }
399
+ function setAgentConfigEmbeddedValue(config, fieldKey, value) {
400
+ const spec = AGENT_CONFIG_EMBEDDED_FIELDS[fieldKey];
401
+ if (!spec)
402
+ return { ...config };
403
+ const next = { ...config };
404
+ if (spec.kind === "model") {
405
+ const prev = next.modelMap && typeof next.modelMap === "object" && !Array.isArray(next.modelMap) ? next.modelMap : {};
406
+ next.modelMap = { ...prev, [spec.provider]: String(value) };
407
+ return next;
408
+ }
409
+ if (spec.key === "fallbackChain") {
410
+ if (typeof value === "string") {
411
+ try {
412
+ next.fallbackChain = JSON.parse(value);
413
+ } catch {
414
+ next.fallbackChain = value;
415
+ }
416
+ } else {
417
+ next.fallbackChain = value;
418
+ }
419
+ return next;
420
+ }
421
+ if (NUMBER_TOP_KEYS.has(spec.key)) {
422
+ const n = typeof value === "number" ? value : Number(value);
423
+ next[spec.key] = Number.isFinite(n) ? n : value;
424
+ return next;
425
+ }
426
+ next[spec.key] = value;
427
+ return next;
428
+ }
429
+ function expandAgentConfigToFieldMap(config) {
430
+ const out = {};
431
+ for (const fieldKey of Object.keys(AGENT_CONFIG_EMBEDDED_FIELDS)) {
432
+ const v = getAgentConfigEmbeddedValue(config, fieldKey);
433
+ if (v !== undefined)
434
+ out[fieldKey] = v;
435
+ }
436
+ return out;
437
+ }
438
+ function applyAgentConfigFieldUpdates(config, updates) {
439
+ let next = { ...config };
440
+ for (const [fieldKey, value] of Object.entries(updates)) {
441
+ if (isAgentConfigEmbeddedField(fieldKey)) {
442
+ next = setAgentConfigEmbeddedValue(next, fieldKey, value);
443
+ }
444
+ }
445
+ return next;
446
+ }
447
+
448
+ // src/analytics.ts
449
+ async function trackAnalytics(env, endpoint, body, options) {
450
+ if (!env.ANALYTICS_SERVICE)
451
+ return;
452
+ try {
453
+ const payload = {
454
+ ...body,
455
+ ...options?.indexes ? { indexes: [...options.indexes] } : {}
456
+ };
457
+ const headers = {
458
+ "Content-Type": "application/json"
459
+ };
460
+ const internalKey = env.INTERNAL_KEY_BINDING;
461
+ if (internalKey) {
462
+ headers["X-Internal-Auth-Key"] = internalKey;
463
+ }
464
+ await env.ANALYTICS_SERVICE.fetch(new Request(`http://localhost${endpoint}`, {
465
+ method: "POST",
466
+ headers,
467
+ body: JSON.stringify(payload)
468
+ }));
469
+ } catch (e) {
470
+ console.error("Analytics tracking failed:", e);
471
+ }
472
+ }
473
+
474
+ // src/colors.ts
475
+ var Colors = {
476
+ background: "#050508",
477
+ foreground: "#E8E8F0",
478
+ card: "#0A0A0F",
479
+ border: "#232330",
480
+ muted: "#8B8B9E",
481
+ "muted-foreground": "#6E6E84",
482
+ dim: "#3C3C50",
483
+ accent: "#818CF8",
484
+ "accent-dim": "#6366F1",
485
+ success: "#34D399",
486
+ warning: "#FBBF24",
487
+ error: "#FB7185",
488
+ info: "#38BDF8",
489
+ text: "#E8E8F0",
490
+ "text-muted": "#8B8B9E",
491
+ panel: "#0A0A0F",
492
+ divider: "#232330",
493
+ highlight: "#22D3EE",
494
+ backdrop: "#000000"
495
+ };
496
+ var CoolBracketPalette = [
497
+ "#22D3EE",
498
+ "#38BDF8",
499
+ "#60A5FA",
500
+ "#818CF8",
501
+ "#A78BFA",
502
+ "#C084FC",
503
+ "#E879F9",
504
+ "#A78BFA",
505
+ "#818CF8",
506
+ "#38BDF8"
507
+ ];
508
+ var ConnectionStatusColor = {
509
+ connected: Colors.success,
510
+ polling: Colors.highlight,
511
+ reconnecting: Colors.warning,
512
+ offline: Colors.error
513
+ };
514
+ var WorkerStatusColor = {
515
+ operational: Colors.success,
516
+ degraded: Colors.warning,
517
+ down: Colors.error
518
+ };
519
+ var LogLevelColor = {
520
+ error: Colors.error,
521
+ warn: Colors.warning,
522
+ info: Colors.foreground,
523
+ debug: Colors.muted
524
+ };
525
+ var AlertSeverityColor = {
526
+ info: Colors.info,
527
+ warning: Colors.warning,
528
+ error: Colors.error,
529
+ critical: Colors.error
530
+ };
531
+
532
+ // src/config.ts
533
+ import {
534
+ readFileSync,
535
+ writeFileSync,
536
+ existsSync,
537
+ mkdirSync,
538
+ chmodSync,
539
+ statSync
540
+ } from "fs";
541
+ import { join } from "path";
542
+ import { homedir } from "os";
543
+ var HOX_DIR = join(homedir(), ".hoox");
544
+ var CONFIG_PATH = join(HOX_DIR, "config.json");
545
+ var HOOX_DIR_MODE = 448;
546
+ var HOOX_CONFIG_FILE_MODE = 384;
547
+ var DEFAULT_CONFIG = {
548
+ apiUrl: "http://localhost:8787",
549
+ apiToken: "",
550
+ transport: "public",
551
+ refreshIntervalMs: 500,
552
+ theme: "dark",
553
+ activeExchanges: ["binance", "bybit", "mexc"],
554
+ notifications: {
555
+ alerts: true,
556
+ trades: true,
557
+ debug: false,
558
+ system: true
559
+ },
560
+ soundEnabled: true,
561
+ defaultView: "dashboard"
562
+ };
563
+ function readConfigSync() {
564
+ const envConfig = {
565
+ apiUrl: process.env.HOOX_API_URL,
566
+ apiToken: process.env.HOOX_API_TOKEN
567
+ };
568
+ let fileConfig = {};
569
+ try {
570
+ if (existsSync(CONFIG_PATH)) {
571
+ fileConfig = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
572
+ }
573
+ } catch {}
574
+ return {
575
+ ...DEFAULT_CONFIG,
576
+ ...fileConfig,
577
+ ...Object.fromEntries(Object.entries(envConfig).filter(([_, v]) => v !== undefined))
578
+ };
579
+ }
580
+ async function readConfig() {
581
+ return readConfigSync();
582
+ }
583
+ function ensureHooxDirSecure(dir = HOX_DIR) {
584
+ if (!existsSync(dir)) {
585
+ mkdirSync(dir, { recursive: true, mode: HOOX_DIR_MODE });
586
+ }
587
+ try {
588
+ chmodSync(dir, HOOX_DIR_MODE);
589
+ } catch {}
590
+ }
591
+ function writeSecureFileSync(filePath, contents, mode = HOOX_CONFIG_FILE_MODE) {
592
+ writeFileSync(filePath, contents, { encoding: "utf-8", mode });
593
+ try {
594
+ chmodSync(filePath, mode);
595
+ } catch {}
596
+ }
597
+ function isUnixModeGroupOrWorldAccessible(mode) {
598
+ return (mode & 63) !== 0;
599
+ }
600
+ function isConfigWorldOrGroupReadable(filePath = CONFIG_PATH) {
601
+ try {
602
+ if (!existsSync(filePath))
603
+ return false;
604
+ return isUnixModeGroupOrWorldAccessible(statSync(filePath).mode & 511);
605
+ } catch {
606
+ return false;
607
+ }
608
+ }
609
+ function formatConfigPermissionWarning(filePath = CONFIG_PATH) {
610
+ if (!isConfigWorldOrGroupReadable(filePath))
611
+ return null;
612
+ 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.`;
613
+ }
614
+ function writeConfigSync(config) {
615
+ try {
616
+ ensureHooxDirSecure(HOX_DIR);
617
+ writeSecureFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), HOOX_CONFIG_FILE_MODE);
618
+ } catch (err) {
619
+ console.error("Failed to write config:", err);
620
+ }
621
+ }
622
+ async function writeConfig(config) {
623
+ writeConfigSync(config);
624
+ }
625
+ var VALID_TRANSPORTS2 = new Set([
626
+ "public",
627
+ "access",
628
+ "mtls",
629
+ "tunnel"
630
+ ]);
631
+ function validateConfig(config) {
632
+ const errors = [];
633
+ if (config.apiUrl && !config.apiUrl.startsWith("http")) {
634
+ errors.push("apiUrl must start with http:// or https://");
635
+ }
636
+ if (config.refreshIntervalMs !== undefined && config.refreshIntervalMs < 100) {
637
+ errors.push("refreshIntervalMs must be >= 100ms");
638
+ }
639
+ if (config.theme && !["dark", "light"].includes(config.theme)) {
640
+ errors.push('theme must be "dark" or "light"');
641
+ }
642
+ if (config.transport !== undefined && !VALID_TRANSPORTS2.has(config.transport)) {
643
+ errors.push('transport must be "public" | "access" | "mtls" | "tunnel"');
644
+ }
645
+ return errors;
646
+ }
647
+
648
+ // src/cron-handler.ts
649
+ function createCronHandler(options) {
650
+ const { name, handler, logger } = options;
651
+ return async (event, env, ctx) => {
652
+ const startTime = Date.now();
653
+ logger?.info(`${name} cron triggered`, {
654
+ cron: event.cron,
655
+ scheduledTime: event.scheduledTime
656
+ });
657
+ try {
658
+ await handler(event, env, ctx);
659
+ const durationMs = Date.now() - startTime;
660
+ logger?.info(`${name} cron handler completed successfully`, {
661
+ cron: event.cron,
662
+ durationMs
663
+ });
664
+ } catch (error) {
665
+ const durationMs = Date.now() - startTime;
666
+ const errorMsg = error instanceof Error ? error.message : String(error);
667
+ logger?.error(`${name} cron handler failed`, {
668
+ cron: event.cron,
669
+ durationMs,
670
+ error: errorMsg
671
+ });
672
+ throw error;
673
+ }
674
+ };
675
+ }
676
+
677
+ // src/errors.ts
678
+ function toError(err, fallback = "Unknown error") {
679
+ if (err instanceof Error)
680
+ return err.message;
681
+ if (typeof err === "string")
682
+ return err;
683
+ if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
684
+ return err.message;
685
+ }
686
+ if (err === null || err === undefined)
687
+ return fallback;
688
+ try {
689
+ return JSON.stringify(err) || fallback;
690
+ } catch {
691
+ return fallback;
692
+ }
693
+ }
694
+ var SENSITIVE_FIELDS = new Set([
695
+ "password",
696
+ "token",
697
+ "secret",
698
+ "api_key",
699
+ "apiKey",
700
+ "authorization"
701
+ ]);
702
+ function hasSensitiveFields(obj) {
703
+ for (const key of Object.keys(obj)) {
704
+ if (SENSITIVE_FIELDS.has(key))
705
+ return true;
706
+ }
707
+ return false;
708
+ }
709
+ function hasErrorValues(obj) {
710
+ for (const v of Object.values(obj)) {
711
+ if (v instanceof Error)
712
+ return true;
713
+ }
714
+ return false;
715
+ }
716
+ function sanitizeOutput(data) {
717
+ if (data === null || data === undefined)
718
+ return data;
719
+ const t = typeof data;
720
+ if (t === "string" || t === "number" || t === "boolean")
721
+ return data;
722
+ if (data instanceof Error) {
723
+ return { name: data.name, message: data.message };
724
+ }
725
+ if (Array.isArray(data)) {
726
+ return data.map(sanitizeOutput);
727
+ }
728
+ if (t === "object") {
729
+ const obj = data;
730
+ const keys = Object.keys(obj);
731
+ if (keys.length < 10) {
732
+ const sanitized2 = {};
733
+ for (const k of keys) {
734
+ if (k === "stack" || k === "cause")
735
+ continue;
736
+ const v = obj[k];
737
+ sanitized2[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
738
+ }
739
+ return sanitized2;
740
+ }
741
+ if (!hasSensitiveFields(obj) && !hasErrorValues(obj)) {
742
+ return obj;
743
+ }
744
+ const sanitized = {};
745
+ for (const k of keys) {
746
+ if (k === "stack" || k === "cause")
747
+ continue;
748
+ const v = obj[k];
749
+ sanitized[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
750
+ }
751
+ return sanitized;
752
+ }
753
+ return data;
754
+ }
755
+ function createJsonResponse(data, status = 200) {
756
+ return new Response(JSON.stringify(sanitizeOutput(data)), {
757
+ status,
758
+ headers: { "Content-Type": "application/json" }
759
+ });
760
+ }
761
+ function createSuccessResponse(result) {
762
+ return createJsonResponse({ success: true, result }, 200);
763
+ }
764
+ function createErrorResponse(error, status) {
765
+ const message = typeof error === "string" ? error : error.message;
766
+ const statusCode = typeof error === "string" ? status ?? 500 : error.status ?? 500;
767
+ const body = { success: false, error: message };
768
+ if (typeof error !== "string" && error.code)
769
+ body.code = error.code;
770
+ if (typeof error !== "string" && error.details !== undefined)
771
+ body.details = error.details;
772
+ return new Response(JSON.stringify(body), {
773
+ status: statusCode,
774
+ headers: { "Content-Type": "application/json" }
775
+ });
776
+ }
777
+ var Errors = {
778
+ badRequest: (message) => createErrorResponse({ message, status: 400, code: "BAD_REQUEST" }),
779
+ unauthorized: (message = "Unauthorized") => createErrorResponse({ message, status: 401, code: "UNAUTHORIZED" }),
780
+ forbidden: (message = "Forbidden") => createErrorResponse({ message, status: 403, code: "FORBIDDEN" }),
781
+ notFound: (message = "Not found") => createErrorResponse({ message, status: 404, code: "NOT_FOUND" }),
782
+ methodNotAllowed: (message = "Method not allowed") => createErrorResponse({ message, status: 405, code: "METHOD_NOT_ALLOWED" }),
783
+ rateLimited: (retryAfter) => {
784
+ const res = createErrorResponse({
785
+ message: "Rate limit exceeded",
786
+ status: 429,
787
+ code: "RATE_LIMITED"
788
+ });
789
+ if (retryAfter)
790
+ res.headers.set("Retry-After", String(retryAfter));
791
+ return res;
792
+ },
793
+ internal: (err) => {
794
+ const message = err ? toError(err, "Internal server error") : "Internal server error";
795
+ return createErrorResponse({
796
+ message,
797
+ status: 500,
798
+ code: "INTERNAL_ERROR"
799
+ });
800
+ }
801
+ };
802
+
803
+ // src/d1/repository.ts
804
+ class D1Repository {
805
+ db;
806
+ constructor(db) {
807
+ this.db = db;
808
+ }
809
+ async query(sql, params = []) {
810
+ try {
811
+ const stmt = this.db.prepare(sql).bind(...params);
812
+ const result = await stmt.all();
813
+ return {
814
+ success: result.success,
815
+ results: result.results,
816
+ error: result.error
817
+ };
818
+ } catch (error) {
819
+ return {
820
+ success: false,
821
+ error: toError(error)
822
+ };
823
+ }
824
+ }
825
+ async execute(sql, params = []) {
826
+ try {
827
+ const stmt = this.db.prepare(sql).bind(...params);
828
+ const result = await stmt.run();
829
+ return {
830
+ success: result.success,
831
+ meta: {
832
+ last_row_id: result.meta?.last_row_id,
833
+ changes: result.meta?.changes,
834
+ duration: result.meta?.duration
835
+ },
836
+ error: result.error
837
+ };
838
+ } catch (error) {
839
+ return {
840
+ success: false,
841
+ error: toError(error)
842
+ };
843
+ }
844
+ }
845
+ async batch(statements) {
846
+ try {
847
+ const prepared = statements.map((s) => s.params && s.params.length > 0 ? this.db.prepare(s.query).bind(...s.params) : this.db.prepare(s.query));
848
+ const results = await this.db.batch(prepared);
849
+ return results.map((r) => ({
850
+ success: r.success,
851
+ meta: r.meta ? { last_row_id: r.meta.last_row_id, changes: r.meta.changes } : undefined,
852
+ error: r.error
853
+ }));
854
+ } catch (error) {
855
+ return statements.map(() => ({
856
+ success: false,
857
+ error: toError(error)
858
+ }));
859
+ }
860
+ }
861
+ prepare(sql) {
862
+ return this.db.prepare(sql);
863
+ }
864
+ async getTrades(limit = 50, offset = 0) {
865
+ return this.query("SELECT * FROM trades ORDER BY timestamp DESC LIMIT ? OFFSET ?", [limit, offset]);
866
+ }
867
+ async insertTrade(trade) {
868
+ return this.execute(`INSERT INTO trades (id, timestamp, exchange, symbol, action, quantity, price, leverage, status, raw_response)
869
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
870
+ trade.id || crypto.randomUUID(),
871
+ trade.timestamp,
872
+ trade.exchange,
873
+ trade.symbol,
874
+ trade.action,
875
+ trade.quantity ?? null,
876
+ trade.price ?? null,
877
+ trade.leverage ?? null,
878
+ trade.status,
879
+ trade.raw_response ?? null
880
+ ]);
881
+ }
882
+ async getPositions(status) {
883
+ if (status) {
884
+ return this.query("SELECT * FROM positions WHERE status = ? ORDER BY updated_at DESC", [status]);
885
+ }
886
+ return this.query("SELECT * FROM positions ORDER BY updated_at DESC");
887
+ }
888
+ async upsertPosition(position) {
889
+ return this.execute(`REPLACE INTO positions (id, exchange, symbol, side, entry_price, mark_price, liquidation_price, leverage, size, unrealized_pnl, status, updated_at)
890
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
891
+ position.id,
892
+ position.exchange,
893
+ position.symbol,
894
+ position.side,
895
+ position.entry_price ?? null,
896
+ position.mark_price ?? null,
897
+ position.liquidation_price ?? null,
898
+ position.leverage ?? null,
899
+ position.size,
900
+ position.unrealized_pnl ?? null,
901
+ position.status,
902
+ position.updated_at
903
+ ]);
904
+ }
905
+ async getTradeSignals(limit = 10) {
906
+ return this.query(`SELECT signal_id, timestamp, symbol, signal_type, source, processed_at
907
+ FROM trade_signals
908
+ ORDER BY processed_at DESC
909
+ LIMIT ?`, [limit]);
910
+ }
911
+ async insertTradeSignal(signal) {
912
+ return this.execute(`INSERT INTO trade_signals (signal_id, timestamp, symbol, signal_type, source, raw_data)
913
+ VALUES (?, ?, ?, ?, ?, ?)`, [
914
+ signal.signal_id,
915
+ signal.timestamp,
916
+ signal.symbol,
917
+ signal.signal_type,
918
+ signal.source ?? null,
919
+ signal.raw_data ?? null
920
+ ]);
921
+ }
922
+ async getSystemLogs(limit = 50) {
923
+ return this.query("SELECT * FROM system_logs ORDER BY timestamp DESC LIMIT ?", [limit]);
924
+ }
925
+ async insertSystemLog(log) {
926
+ return this.execute(`INSERT INTO system_logs (id, level, service, message, details) VALUES (?, ?, ?, ?, ?)`, [
927
+ log.id || crypto.randomUUID(),
928
+ log.level,
929
+ log.service,
930
+ log.message,
931
+ log.details ?? null
932
+ ]);
933
+ }
934
+ async getLatestBalances() {
935
+ return this.query(`
936
+ SELECT b.id, b.exchange, b.asset, b.total, b.free, b.used, b.timestamp
937
+ FROM balances b
938
+ INNER JOIN (
939
+ SELECT exchange, asset, MAX(timestamp) as max_time
940
+ FROM balances
941
+ GROUP BY exchange, asset
942
+ ) latest ON b.exchange = latest.exchange AND b.asset = latest.asset AND b.timestamp = latest.max_time
943
+ `);
944
+ }
945
+ async healthCheck() {
946
+ try {
947
+ await this.db.prepare("SELECT 1").first();
948
+ return true;
949
+ } catch {
950
+ return false;
951
+ }
952
+ }
953
+ }
954
+
955
+ // src/d1/schemas.ts
956
+ import { z } from "zod";
957
+ var TradeRecordSchema = z.object({
958
+ id: z.string(),
959
+ signal_id: z.string().nullable().optional(),
960
+ timestamp: z.number().int().positive(),
961
+ exchange: z.string().min(1),
962
+ symbol: z.string().min(1),
963
+ action: z.string().min(1),
964
+ quantity: z.number().nullable(),
965
+ price: z.number().nullable(),
966
+ leverage: z.number().nullable(),
967
+ status: z.string().min(1),
968
+ error_message: z.string().nullable().optional(),
969
+ raw_response: z.string().nullable().optional(),
970
+ created_at: z.number().int().optional()
971
+ }).strict();
972
+ var PositionRecordSchema = z.object({
973
+ id: z.string(),
974
+ exchange: z.string().min(1),
975
+ symbol: z.string().min(1),
976
+ side: z.enum(["LONG", "SHORT"]),
977
+ entry_price: z.number().nullable().optional(),
978
+ mark_price: z.number().nullable().optional(),
979
+ liquidation_price: z.number().nullable().optional(),
980
+ leverage: z.number().nullable().optional(),
981
+ size: z.number().nullable(),
982
+ unrealized_pnl: z.number().nullable().optional(),
983
+ status: z.enum(["OPEN", "CLOSED"]),
984
+ updated_at: z.number().int()
985
+ }).strict();
986
+ var BalanceRecordSchema = z.object({
987
+ id: z.string(),
988
+ exchange: z.string().min(1),
989
+ asset: z.string().min(1),
990
+ free: z.number().nullable(),
991
+ used: z.number().nullable(),
992
+ total: z.number().nullable(),
993
+ timestamp: z.number().int().positive()
994
+ }).strict();
995
+ var SystemLogRecordSchema = z.object({
996
+ id: z.string(),
997
+ timestamp: z.number().int().optional(),
998
+ level: z.string().min(1),
999
+ service: z.string().min(1),
1000
+ message: z.string().min(1),
1001
+ details: z.string().nullable().optional()
1002
+ }).strict();
1003
+ var TradeSignalRecordSchema = z.object({
1004
+ signal_id: z.string(),
1005
+ timestamp: z.number().int().positive(),
1006
+ symbol: z.string().min(1),
1007
+ signal_type: z.string().min(1),
1008
+ source: z.string().nullable().optional(),
1009
+ raw_data: z.string().nullable().optional(),
1010
+ processed_at: z.number().int().optional()
1011
+ }).strict();
1012
+ // src/dashboard-manifest.ts
1013
+ import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync2 } from "fs";
1014
+ import { join as join2 } from "path";
1015
+ var DASHBOARD_WORKER_PREFIX = {
1016
+ hoox: "global:",
1017
+ "trade-worker": "trade:",
1018
+ "agent-worker": "agent:",
1019
+ "telegram-worker": "bot:",
1020
+ "d1-worker": "database:",
1021
+ "email-worker": "email:",
1022
+ "web3-wallet-worker": "wallet:",
1023
+ "analytics-worker": "ai:",
1024
+ "report-worker": "report:",
1025
+ "pyne-worker": "pyne:"
1026
+ };
1027
+ var DASHBOARD_SECTION_PREFIX = {
1028
+ global: "global:",
1029
+ webhook: "webhook:",
1030
+ routing: "routing:",
1031
+ security: "webhook:",
1032
+ trade: "trade:",
1033
+ agent: "agent:",
1034
+ bot: "bot:",
1035
+ email: "email:",
1036
+ signal: "email:",
1037
+ database: "database:",
1038
+ retention: "retention:",
1039
+ cron: "cron:",
1040
+ behavior: "behavior:",
1041
+ risk: "trade:",
1042
+ exchanges: "trade:",
1043
+ fees: "trade:",
1044
+ providers: "agent:",
1045
+ models: "agent:",
1046
+ ai: "ai:",
1047
+ report: "report:",
1048
+ pyne: "pyne:",
1049
+ pyne_alerts: "pyne:"
1050
+ };
1051
+ var DASHBOARD_FIELD_KV_OVERRIDES = {
1052
+ "webhook:tradingview_ip_check_enabled": "webhook:tradingview:ip_check_enabled",
1053
+ "webhook:tradingview_allowed_ips": "webhook:tradingview:allowed_ips",
1054
+ "exchanges:binance_enabled": "exchange:binance:enabled",
1055
+ "exchanges:mexc_enabled": "exchange:mexc:enabled",
1056
+ "exchanges:bybit_enabled": "exchange:bybit:enabled"
1057
+ };
1058
+ var DASHBOARD_SECTIONS_NOT_FLAT_KV = new Set([
1059
+ "providers",
1060
+ "models",
1061
+ "cron",
1062
+ "behavior"
1063
+ ]);
1064
+ var DASHBOARD_SECTIONS_UI_SKIP = new Set([
1065
+ "cron",
1066
+ "behavior"
1067
+ ]);
1068
+ function isDashboardSectionFlatKv(sectionId) {
1069
+ return !DASHBOARD_SECTIONS_NOT_FLAT_KV.has(sectionId);
1070
+ }
1071
+ function isDashboardSectionEditable(sectionId) {
1072
+ return !DASHBOARD_SECTIONS_UI_SKIP.has(sectionId);
1073
+ }
1074
+ function buildDashboardKvKey(worker, fieldKey) {
1075
+ const override = DASHBOARD_FIELD_KV_OVERRIDES[fieldKey];
1076
+ if (override)
1077
+ return override;
1078
+ if (fieldKey.includes(":")) {
1079
+ const [section, ...rest] = fieldKey.split(":");
1080
+ const fieldName = rest.join(":");
1081
+ if (section === "exchanges" && fieldName.endsWith("_enabled")) {
1082
+ const exchange = fieldName.slice(0, -"_enabled".length);
1083
+ if (exchange)
1084
+ return `exchange:${exchange}:enabled`;
1085
+ }
1086
+ const mapped = DASHBOARD_SECTION_PREFIX[section ?? ""];
1087
+ if (mapped)
1088
+ return `${mapped}${fieldName}`;
1089
+ return `${section}:${fieldName}`;
1090
+ }
1091
+ const workerPrefix = DASHBOARD_WORKER_PREFIX[worker] ?? "";
1092
+ return `${workerPrefix}${fieldKey}`;
1093
+ }
1094
+ function stripJsonc(input) {
1095
+ let out = "";
1096
+ let i = 0;
1097
+ let inString = false;
1098
+ let stringChar = "";
1099
+ while (i < input.length) {
1100
+ const c = input[i];
1101
+ const next = input[i + 1];
1102
+ if (inString) {
1103
+ out += c;
1104
+ if (c === "\\" && i + 1 < input.length) {
1105
+ out += next;
1106
+ i += 2;
1107
+ continue;
1108
+ }
1109
+ if (c === stringChar)
1110
+ inString = false;
1111
+ i++;
1112
+ continue;
1113
+ }
1114
+ if (c === '"' || c === "'") {
1115
+ inString = true;
1116
+ stringChar = c;
1117
+ out += c;
1118
+ i++;
1119
+ continue;
1120
+ }
1121
+ if (c === "/" && next === "/") {
1122
+ while (i < input.length && input[i] !== `
1123
+ `)
1124
+ i++;
1125
+ continue;
1126
+ }
1127
+ if (c === "/" && next === "*") {
1128
+ i += 2;
1129
+ while (i < input.length && !(input[i] === "*" && input[i + 1] === "/"))
1130
+ i++;
1131
+ i += 2;
1132
+ continue;
1133
+ }
1134
+ if (c === ",") {
1135
+ let j = i + 1;
1136
+ while (j < input.length && /\s/.test(input[j]))
1137
+ j++;
1138
+ if (j < input.length && (input[j] === "}" || input[j] === "]")) {
1139
+ i++;
1140
+ continue;
1141
+ }
1142
+ }
1143
+ out += c;
1144
+ i++;
1145
+ }
1146
+ return out;
1147
+ }
1148
+ function parseFieldType(value) {
1149
+ if (typeof value === "boolean")
1150
+ return "boolean";
1151
+ if (typeof value === "number")
1152
+ return "number";
1153
+ if (value === "true" || value === "false")
1154
+ return "boolean";
1155
+ if (value.trim().startsWith("{") || value.trim().startsWith("["))
1156
+ return "json";
1157
+ if (!Number.isNaN(Number(value)) && value.trim() !== "")
1158
+ return "number";
1159
+ return "text";
1160
+ }
1161
+ function labelFromKey(key) {
1162
+ return key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1163
+ }
1164
+ function parseDashboardManifest(content, workerName, onError) {
1165
+ try {
1166
+ const raw = JSON.parse(stripJsonc(content));
1167
+ const displayName = raw.display_name || raw.displayName || workerName;
1168
+ const description = raw.description || "";
1169
+ const sections = [];
1170
+ if (raw.sections) {
1171
+ for (const [sectionId, sectionData] of Object.entries(raw.sections)) {
1172
+ const fields = [];
1173
+ const sectionFields = sectionData.fields || {};
1174
+ const sectionOptions = sectionData.options || {};
1175
+ const sectionDescriptions = sectionData.descriptions || {};
1176
+ const sectionValidation = sectionData.validation || {};
1177
+ const sectionSecrets = sectionData.secrets || {};
1178
+ const sectionSecretCommands = sectionData.secret_commands || {};
1179
+ for (const [key, value] of Object.entries(sectionFields)) {
1180
+ const field = {
1181
+ key: `${sectionId}:${key}`,
1182
+ label: labelFromKey(key),
1183
+ type: parseFieldType(value),
1184
+ default: value
1185
+ };
1186
+ if (sectionOptions[key]) {
1187
+ field.type = "select";
1188
+ field.options = sectionOptions[key].map((opt) => ({
1189
+ value: String(opt),
1190
+ label: String(opt)
1191
+ }));
1192
+ }
1193
+ if (sectionDescriptions[key]) {
1194
+ field.description = String(sectionDescriptions[key]);
1195
+ }
1196
+ if (sectionValidation[key]) {
1197
+ field.validation = sectionValidation[key];
1198
+ }
1199
+ if (sectionSecrets[key]) {
1200
+ field.kind = "secret";
1201
+ field.cliCommand = sectionSecretCommands[key];
1202
+ }
1203
+ if (key.includes("kill_switch") || key.includes("drawdown") || key === "enabled" && sectionId === "global") {
1204
+ if (field.kind !== "secret")
1205
+ field.kind = "dangerous";
1206
+ }
1207
+ fields.push(field);
1208
+ }
1209
+ sections.push({
1210
+ id: sectionId,
1211
+ title: sectionData.title || labelFromKey(sectionId),
1212
+ description: sectionData.description || `Configure ${sectionId} settings`,
1213
+ icon: sectionData.icon,
1214
+ priority: sectionData.priority !== undefined ? sectionData.priority : sections.length * 10,
1215
+ fields
1216
+ });
1217
+ }
1218
+ }
1219
+ return {
1220
+ worker: workerName,
1221
+ displayName,
1222
+ description,
1223
+ sections: sections.sort((a, b) => a.priority - b.priority)
1224
+ };
1225
+ } catch (err) {
1226
+ const message = err instanceof Error ? err.message : `parse failed: ${String(err)}`;
1227
+ onError?.(`dashboard.jsonc for ${workerName}: ${message}`);
1228
+ return {
1229
+ worker: workerName,
1230
+ displayName: workerName,
1231
+ description: "Failed to load configuration",
1232
+ sections: []
1233
+ };
1234
+ }
1235
+ }
1236
+ var DASHBOARD_WORKER_IDS = [
1237
+ "hoox",
1238
+ "trade-worker",
1239
+ "agent-worker",
1240
+ "telegram-worker",
1241
+ "d1-worker",
1242
+ "email-worker",
1243
+ "web3-wallet-worker",
1244
+ "analytics-worker",
1245
+ "report-worker",
1246
+ "pyne-worker"
1247
+ ];
1248
+ function dashboardWorkerDir(workerId) {
1249
+ if (workerId === "hoox")
1250
+ return "hoox-worker";
1251
+ return workerId;
1252
+ }
1253
+ function fieldTypeToKv(t) {
1254
+ if (t === "boolean")
1255
+ return "boolean";
1256
+ if (t === "number")
1257
+ return "number";
1258
+ return "string";
1259
+ }
1260
+ function defaultToString(value) {
1261
+ if (typeof value === "string")
1262
+ return value;
1263
+ return JSON.stringify(value);
1264
+ }
1265
+ function isDocumentationField(fieldKey) {
1266
+ const name = fieldKey.includes(":") ? fieldKey.slice(fieldKey.indexOf(":") + 1) : fieldKey;
1267
+ return /^(GET|POST|PUT|PATCH|DELETE)\s+\//i.test(name);
1268
+ }
1269
+ function kvManifestFromDashboardManifests(manifests) {
1270
+ const byKey = new Map;
1271
+ for (const manifest of manifests) {
1272
+ for (const section of manifest.sections) {
1273
+ if (!isDashboardSectionFlatKv(section.id))
1274
+ continue;
1275
+ for (const field of section.fields) {
1276
+ if (isDocumentationField(field.key))
1277
+ continue;
1278
+ const key = buildDashboardKvKey(manifest.worker, field.key);
1279
+ if (!key || key.endsWith(":"))
1280
+ continue;
1281
+ const next = {
1282
+ key,
1283
+ type: fieldTypeToKv(field.type),
1284
+ default: defaultToString(field.default),
1285
+ description: field.description || `${manifest.displayName}: ${field.label}`,
1286
+ secret: field.kind === "secret" ? true : undefined,
1287
+ worker: manifest.worker
1288
+ };
1289
+ const prev = byKey.get(key);
1290
+ if (!prev) {
1291
+ byKey.set(key, next);
1292
+ continue;
1293
+ }
1294
+ if (!prev.secret && next.secret)
1295
+ prev.secret = true;
1296
+ if ((!prev.description || prev.description.length < 8) && next.description) {
1297
+ prev.description = next.description;
1298
+ }
1299
+ }
1300
+ }
1301
+ }
1302
+ const keys = [...byKey.values()].sort((a, b) => a.key.localeCompare(b.key));
1303
+ return { namespace: "CONFIG_KV", keys };
1304
+ }
1305
+ function loadDashboardKvManifestFromRoot(root, onError) {
1306
+ return kvManifestFromDashboardManifests(loadDashboardManifestsFromRoot(root, onError));
1307
+ }
1308
+ function tryReadFile(path) {
1309
+ try {
1310
+ if (!existsSync2(path))
1311
+ return null;
1312
+ return readFileSync2(path, "utf-8");
1313
+ } catch {
1314
+ return null;
1315
+ }
1316
+ }
1317
+ function loadDashboardManifestsFromRoot(root, onError) {
1318
+ const manifests = [];
1319
+ const seen = new Set;
1320
+ const workersDir = join2(root, "workers");
1321
+ if (!existsSync2(workersDir))
1322
+ return manifests;
1323
+ const warn = onError ?? ((message) => {
1324
+ console.warn(`[dashboard-manifest] ${message}`);
1325
+ });
1326
+ for (const workerId of DASHBOARD_WORKER_IDS) {
1327
+ const dir = dashboardWorkerDir(workerId);
1328
+ const path = join2(workersDir, dir, "dashboard.jsonc");
1329
+ const content = tryReadFile(path);
1330
+ if (!content)
1331
+ continue;
1332
+ const m = parseDashboardManifest(content, workerId, warn);
1333
+ if (m.sections.length > 0) {
1334
+ manifests.push(m);
1335
+ seen.add(workerId);
1336
+ } else if (m.description === "Failed to load configuration") {}
1337
+ }
1338
+ try {
1339
+ for (const entry of readdirSync(workersDir)) {
1340
+ if (entry === "dashboard")
1341
+ continue;
1342
+ const workerId = entry === "hoox-worker" || entry === "hoox" ? "hoox" : entry;
1343
+ if (seen.has(workerId))
1344
+ continue;
1345
+ const path = join2(workersDir, entry, "dashboard.jsonc");
1346
+ const content = tryReadFile(path);
1347
+ if (!content)
1348
+ continue;
1349
+ const m = parseDashboardManifest(content, workerId, warn);
1350
+ if (m.sections.length > 0) {
1351
+ manifests.push(m);
1352
+ seen.add(workerId);
1353
+ }
1354
+ }
1355
+ } catch {}
1356
+ const publicDir = join2(workersDir, "dashboard", "public", "workers");
1357
+ if (existsSync2(publicDir)) {
1358
+ try {
1359
+ for (const file of readdirSync(publicDir)) {
1360
+ if (!file.endsWith(".jsonc") && !file.endsWith(".json"))
1361
+ continue;
1362
+ const base = file.replace(/\.jsonc?$/, "");
1363
+ const workerId = base === "hoox-worker" || base === "hoox" ? "hoox" : base;
1364
+ if (seen.has(workerId))
1365
+ continue;
1366
+ const content = tryReadFile(join2(publicDir, file));
1367
+ if (!content)
1368
+ continue;
1369
+ const m = parseDashboardManifest(content, workerId, warn);
1370
+ if (m.sections.length > 0) {
1371
+ manifests.push(m);
1372
+ seen.add(workerId);
1373
+ }
1374
+ }
1375
+ } catch {}
1376
+ }
1377
+ return manifests;
1378
+ }
1379
+
1380
+ // src/exchange-client.ts
1381
+ class ExchangeRouter {
1382
+ providers = new Map;
1383
+ registerProvider(provider) {
1384
+ this.providers.set(provider.name.toLowerCase(), provider);
1385
+ }
1386
+ route(exchange, env, options) {
1387
+ const key = exchange.toLowerCase();
1388
+ const provider = this.providers.get(key);
1389
+ if (!provider) {
1390
+ throw new Error(`Unsupported exchange: ${exchange}`);
1391
+ }
1392
+ if (options?.testnet && provider.supportsTestTrading === false) {
1393
+ throw new Error(`TEST_TRADING_UNSUPPORTED: ${exchange} does not support test/sandbox trading via API`);
1394
+ }
1395
+ if (!provider.hasCredentials(env)) {
1396
+ throw new Error(`API secret bindings not configured or accessible for ${exchange}`);
1397
+ }
1398
+ return { exchange: key, client: provider.createClient(env, options) };
1399
+ }
1400
+ }
1401
+
1402
+ class BaseExchangeClient {
1403
+ apiKey;
1404
+ apiSecret;
1405
+ baseUrl;
1406
+ isTestnet;
1407
+ constructor(config) {
1408
+ if (!config.apiKey || !config.apiSecret) {
1409
+ throw new Error("API key and secret are required.");
1410
+ }
1411
+ this.apiKey = config.apiKey;
1412
+ this.apiSecret = config.apiSecret;
1413
+ this.baseUrl = config.baseUrl || this.getDefaultBaseUrl();
1414
+ this.isTestnet = config.testnet || false;
1415
+ }
1416
+ async fetch(method, path, params) {
1417
+ const url = `${this.baseUrl}${path}`;
1418
+ const headers = this.buildHeaders(method, path, params);
1419
+ const body = params ? new URLSearchParams(params).toString() : undefined;
1420
+ const response = await fetch(url, {
1421
+ method,
1422
+ headers,
1423
+ body: method !== "GET" ? body : undefined
1424
+ });
1425
+ if (!response.ok) {
1426
+ const error = await response.text();
1427
+ throw new Error(`Exchange API error: ${response.status} - ${error}`);
1428
+ }
1429
+ return response.json();
1430
+ }
1431
+ async openLong(symbol, quantity, price, orderType = "MARKET") {
1432
+ return this.executeTrade({
1433
+ symbol,
1434
+ side: "long",
1435
+ orderType,
1436
+ quantity,
1437
+ price
1438
+ });
1439
+ }
1440
+ async openShort(symbol, quantity, price, orderType = "MARKET") {
1441
+ return this.executeTrade({
1442
+ symbol,
1443
+ side: "short",
1444
+ orderType,
1445
+ quantity,
1446
+ price
1447
+ });
1448
+ }
1449
+ async closeLong(symbol, quantity) {
1450
+ return this.executeTrade({
1451
+ symbol,
1452
+ side: "long",
1453
+ quantity,
1454
+ reduceOnly: true
1455
+ });
1456
+ }
1457
+ async closeShort(symbol, quantity) {
1458
+ return this.executeTrade({
1459
+ symbol,
1460
+ side: "short",
1461
+ quantity,
1462
+ reduceOnly: true
1463
+ });
1464
+ }
1465
+ }
1466
+
1467
+ // src/exchanges/base-exchange-client.ts
1468
+ class BaseExchangeClient2 {
1469
+ apiKey;
1470
+ apiSecret;
1471
+ baseUrl;
1472
+ isTestnet;
1473
+ importedKeyPromise;
1474
+ constructor(apiKey, apiSecret, options) {
1475
+ if (!apiKey || !apiSecret) {
1476
+ throw new Error(this.getErrorMessagePrefix() + "API key and secret are required.");
1477
+ }
1478
+ this.apiKey = apiKey;
1479
+ this.apiSecret = apiSecret;
1480
+ this.isTestnet = options?.testnet ?? false;
1481
+ this.baseUrl = options?.baseUrl ?? this.getDefaultBaseUrl();
1482
+ this.importedKeyPromise = this.importKey();
1483
+ }
1484
+ get testnet() {
1485
+ return this.isTestnet;
1486
+ }
1487
+ getErrorMessagePrefix() {
1488
+ return "";
1489
+ }
1490
+ generateSignature(_params) {
1491
+ throw new Error("generateSignature must be implemented by subclass");
1492
+ }
1493
+ buildHeaders(_method, _path, _params) {
1494
+ return new Headers;
1495
+ }
1496
+ async importKey() {
1497
+ const encoder = new TextEncoder;
1498
+ return crypto.subtle.importKey("raw", encoder.encode(this.apiSecret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
1499
+ }
1500
+ async cryptoSign(data) {
1501
+ const encoder = new TextEncoder;
1502
+ const key = await this.importedKeyPromise;
1503
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(data));
1504
+ return Array.from(new Uint8Array(signature)).map((b) => b.toString(16).padStart(2, "0")).join("");
1505
+ }
1506
+ async fetch(method, path, params) {
1507
+ const url = `${this.baseUrl}${path}`;
1508
+ const headers = this.buildHeaders(method, path, params);
1509
+ const body = params ? new URLSearchParams(params).toString() : undefined;
1510
+ const response = await fetch(url, {
1511
+ method,
1512
+ headers,
1513
+ body: method !== "GET" ? body : undefined
1514
+ });
1515
+ if (!response.ok) {
1516
+ const error = await response.text();
1517
+ throw new Error(`Exchange API error: ${response.status} - ${error}`);
1518
+ }
1519
+ return response.json();
1520
+ }
1521
+ setLeverage(_symbol, _leverage) {
1522
+ throw new Error("setLeverage must be implemented by subclass");
1523
+ }
1524
+ executeTrade(_params) {
1525
+ throw new Error("executeTrade must be implemented by subclass");
1526
+ }
1527
+ getAccountInfo() {
1528
+ throw new Error("getAccountInfo must be implemented by subclass");
1529
+ }
1530
+ getPositions(_symbol) {
1531
+ throw new Error("getPositions must be implemented by subclass");
1532
+ }
1533
+ async openLong(symbol, quantity, price, orderType = "MARKET") {
1534
+ return this.executeTrade({
1535
+ symbol,
1536
+ side: "BUY",
1537
+ orderType,
1538
+ quantity,
1539
+ price
1540
+ });
1541
+ }
1542
+ async openShort(symbol, quantity, price, orderType = "MARKET") {
1543
+ return this.executeTrade({
1544
+ symbol,
1545
+ side: "SELL",
1546
+ orderType,
1547
+ quantity,
1548
+ price
1549
+ });
1550
+ }
1551
+ async closeLong(symbol, quantity) {
1552
+ return this.executeTrade({
1553
+ symbol,
1554
+ side: "SELL",
1555
+ orderType: "MARKET",
1556
+ quantity,
1557
+ reduceOnly: true
1558
+ });
1559
+ }
1560
+ async closeShort(symbol, quantity) {
1561
+ return this.executeTrade({
1562
+ symbol,
1563
+ side: "BUY",
1564
+ orderType: "MARKET",
1565
+ quantity,
1566
+ reduceOnly: true
1567
+ });
1568
+ }
1569
+ }
1570
+ // src/format-time.ts
1571
+ function formatRelativeTime(timestampMs, nowMs) {
1572
+ const now = nowMs ?? Date.now();
1573
+ const diffMs = now - timestampMs;
1574
+ if (diffMs < 0)
1575
+ return "just now";
1576
+ if (timestampMs === 0)
1577
+ return "\u2014";
1578
+ const seconds = Math.floor(diffMs / 1000);
1579
+ const minutes = Math.floor(seconds / 60);
1580
+ const hours = Math.floor(minutes / 60);
1581
+ const days = Math.floor(hours / 24);
1582
+ if (seconds < 60)
1583
+ return "< 1m ago";
1584
+ if (minutes < 60)
1585
+ return `${minutes}m ago`;
1586
+ if (hours < 24)
1587
+ return `${hours}h ago`;
1588
+ if (days < 30)
1589
+ return `${days}d ago`;
1590
+ return `> 30d ago`;
1591
+ }
1592
+ function formatDuration(durationMs) {
1593
+ const seconds = Math.floor(durationMs / 1000);
1594
+ const minutes = Math.floor(seconds / 60);
1595
+ const hours = Math.floor(minutes / 60);
1596
+ if (hours > 0) {
1597
+ const remainMin = minutes % 60;
1598
+ return remainMin > 0 ? `${hours}h ${remainMin}m` : `${hours}h`;
1599
+ }
1600
+ if (minutes > 0) {
1601
+ const remainSec = seconds % 60;
1602
+ return remainSec > 0 ? `${minutes}m ${remainSec}s` : `${minutes}m`;
1603
+ }
1604
+ return `${seconds}s`;
1605
+ }
1606
+
1607
+ // src/formatters.ts
1608
+ function formatNumber(n) {
1609
+ if (!isFinite(n))
1610
+ return "\u2014";
1611
+ if (Math.abs(n) >= 1e9)
1612
+ return `${(n / 1e9).toFixed(1)}B`;
1613
+ if (Math.abs(n) >= 1e6)
1614
+ return `${(n / 1e6).toFixed(1)}M`;
1615
+ if (Math.abs(n) >= 1000)
1616
+ return `${(n / 1000).toFixed(1)}K`;
1617
+ return n.toLocaleString();
1618
+ }
1619
+ function formatCurrency(n) {
1620
+ if (!isFinite(n))
1621
+ return "\u2014";
1622
+ const sign = n >= 0 ? "+" : "-";
1623
+ const abs = Math.abs(n);
1624
+ if (abs >= 1e6)
1625
+ return `${sign}$${(abs / 1e6).toFixed(2)}M`;
1626
+ if (abs >= 1000)
1627
+ return `${sign}$${(abs / 1000).toFixed(2)}K`;
1628
+ return `${sign}$${abs.toFixed(2)}`;
1629
+ }
1630
+ function formatCompactCurrency(n) {
1631
+ if (!isFinite(n))
1632
+ return "\u2014";
1633
+ const abs = Math.abs(n);
1634
+ const sign = n < 0 ? "-" : "";
1635
+ if (abs >= 1e6)
1636
+ return `${sign}$${(abs / 1e6).toFixed(2)}M`;
1637
+ if (abs >= 1000)
1638
+ return `${sign}$${(abs / 1000).toFixed(2)}K`;
1639
+ return `${sign}$${abs.toFixed(2)}`;
1640
+ }
1641
+ function formatDuration2(seconds) {
1642
+ if (!isFinite(seconds) || seconds < 0)
1643
+ return "\u2014";
1644
+ const d = Math.floor(seconds / 86400);
1645
+ const h = Math.floor(seconds % 86400 / 3600);
1646
+ const m = Math.floor(seconds % 3600 / 60);
1647
+ const s = Math.floor(seconds % 60);
1648
+ const parts = [];
1649
+ if (d > 0)
1650
+ parts.push(`${d}d`);
1651
+ if (h > 0 || d > 0)
1652
+ parts.push(`${h}h`);
1653
+ if (m > 0 || h > 0 || d > 0)
1654
+ parts.push(`${m}m`);
1655
+ parts.push(`${s}s`);
1656
+ return parts.join(" ");
1657
+ }
1658
+ function formatDurationCompact(seconds) {
1659
+ if (!isFinite(seconds) || seconds < 0)
1660
+ return "\u2014";
1661
+ const d = Math.floor(seconds / 86400);
1662
+ const h = Math.floor(seconds % 86400 / 3600);
1663
+ const m = Math.floor(seconds % 3600 / 60);
1664
+ if (d > 0)
1665
+ return `${d}d ${h}h`;
1666
+ if (h > 0)
1667
+ return `${h}h ${m}m`;
1668
+ if (m > 0)
1669
+ return `${m}m`;
1670
+ return `${Math.floor(seconds)}s`;
1671
+ }
1672
+ function formatTimestamp(iso) {
1673
+ try {
1674
+ const d = new Date(iso);
1675
+ if (isNaN(d.getTime()))
1676
+ return iso;
1677
+ return d.toLocaleTimeString("en-US", { hour12: false });
1678
+ } catch {
1679
+ return iso;
1680
+ }
1681
+ }
1682
+ function formatRelativeTime2(timestamp) {
1683
+ const now = Date.now();
1684
+ const diff = now - timestamp;
1685
+ if (diff < 0)
1686
+ return "just now";
1687
+ const seconds = Math.floor(diff / 1000);
1688
+ if (seconds < 60)
1689
+ return `${seconds}s ago`;
1690
+ const minutes = Math.floor(seconds / 60);
1691
+ if (minutes < 60)
1692
+ return `${minutes}m ago`;
1693
+ const hours = Math.floor(minutes / 60);
1694
+ if (hours < 24)
1695
+ return `${hours}h ago`;
1696
+ const days = Math.floor(hours / 24);
1697
+ return `${days}d ago`;
1698
+ }
1699
+ function formatPercent(n, decimals = 2) {
1700
+ if (!isFinite(n))
1701
+ return "\u2014";
1702
+ const sign = n >= 0 ? "+" : "";
1703
+ return `${sign}${n.toFixed(decimals)}%`;
1704
+ }
1705
+ function formatUptime(seconds) {
1706
+ return formatDuration2(seconds);
1707
+ }
1708
+ function formatLatency(ms) {
1709
+ if (!isFinite(ms))
1710
+ return "\u2014";
1711
+ if (ms < 1)
1712
+ return "<1ms";
1713
+ if (ms < 1000)
1714
+ return `${Math.round(ms)}ms`;
1715
+ return `${(ms / 1000).toFixed(1)}s`;
1716
+ }
1717
+ function formatRequests(n) {
1718
+ return formatNumber(n);
1719
+ }
1720
+ function formatMemory(usedMB, limitMB) {
1721
+ const u = usedMB.toFixed(0);
1722
+ const l = limitMB.toFixed(0);
1723
+ return `${u}/${l} MB`;
1724
+ }
1725
+ function formatCpu(ms) {
1726
+ if (!isFinite(ms))
1727
+ return "\u2014";
1728
+ return `${ms.toFixed(1)}ms`;
1729
+ }
1730
+
1731
+ // src/health.ts
1732
+ function healthCheck(options) {
1733
+ const result = {
1734
+ status: "ok",
1735
+ timestamp: new Date().toISOString()
1736
+ };
1737
+ if (options?.worker !== undefined && options?.worker !== null)
1738
+ result.service = options.worker;
1739
+ if (options?.version !== undefined && options?.version !== null)
1740
+ result.version = options.version;
1741
+ if (options?.details)
1742
+ result.details = options.details;
1743
+ return createSuccessResponse(result);
1744
+ }
1745
+
1746
+ // src/legal.ts
1747
+ var COPYRIGHT = `Copyright (C) ${new Date().getFullYear()} Hoox Team. All rights reserved.`;
1748
+ var TRADEMARKS = [
1749
+ "TradingView\xAE is a registered trademark of TradingView, Inc.",
1750
+ "Cloudflare\xAE is a registered trademark of Cloudflare, Inc."
1751
+ ];
1752
+ var TRADEMARK_NOTICE = TRADEMARKS.join(`
1753
+ `);
1754
+ var DISCLAIMER = "DISCLAIMER: This software is provided for educational and informational purposes only. " + "Trading cryptocurrencies and other financial instruments involves substantial risk of loss. " + "No guarantee of profits or protection against losses is made or implied. " + "Past performance does not indicate future results. Use at your own risk.";
1755
+ var FULL_LEGAL_NOTICE = `${COPYRIGHT}
1756
+
1757
+ ${TRADEMARK_NOTICE}
1758
+
1759
+ ${DISCLAIMER}`;
1760
+ var DISCLAIMER_HEADER = "X-Disclaimer";
1761
+
1762
+ // src/types.ts
1763
+ import { z as z2 } from "zod";
1764
+ var TradeActionSchema = z2.enum([
1765
+ "LONG",
1766
+ "SHORT",
1767
+ "CLOSE_LONG",
1768
+ "CLOSE_SHORT"
1769
+ ]);
1770
+ var WebhookPayloadSchema = z2.object({
1771
+ exchange: z2.string().min(1),
1772
+ action: TradeActionSchema,
1773
+ symbol: z2.string().min(1).max(20),
1774
+ quantity: z2.number().positive().finite(),
1775
+ price: z2.number().positive().finite().optional(),
1776
+ orderType: z2.string().optional(),
1777
+ leverage: z2.number().int().positive().optional(),
1778
+ test: z2.boolean().optional()
1779
+ }).strict();
1780
+ var TradeQueueMessageSchema = z2.object({
1781
+ requestId: z2.string().min(1).max(128),
1782
+ exchange: z2.string().min(1).max(64),
1783
+ action: TradeActionSchema,
1784
+ symbol: z2.string().min(1).max(32),
1785
+ quantity: z2.number().positive().finite(),
1786
+ price: z2.number().positive().finite().optional(),
1787
+ leverage: z2.number().int().positive().max(125).optional(),
1788
+ test: z2.boolean().optional(),
1789
+ queuedAt: z2.string().min(1).max(64)
1790
+ }).strict();
1791
+ var TradeSignalSchema = z2.object({
1792
+ id: z2.number().int().positive().optional(),
1793
+ source: z2.string().min(1),
1794
+ symbol: z2.string().min(1),
1795
+ action: TradeActionSchema,
1796
+ price: z2.number().positive().finite().optional(),
1797
+ quantity: z2.number().positive().finite(),
1798
+ leverage: z2.number().int().positive().optional(),
1799
+ status: z2.enum(["pending", "executed", "failed", "skipped"]).optional(),
1800
+ createdAt: z2.string().optional(),
1801
+ executedAt: z2.string().optional(),
1802
+ error: z2.string().optional()
1803
+ }).strict();
1804
+ var PositionSchema = z2.object({
1805
+ id: z2.string(),
1806
+ symbol: z2.string().min(1),
1807
+ side: TradeActionSchema,
1808
+ quantity: z2.number().positive(),
1809
+ entry_price: z2.number().positive(),
1810
+ current_price: z2.number().positive(),
1811
+ unrealized_pnl: z2.number(),
1812
+ timestamp: z2.number().int().positive()
1813
+ }).strict();
1814
+ var BalanceSchema = z2.object({
1815
+ asset: z2.string().min(1),
1816
+ free: z2.number().min(0),
1817
+ locked: z2.number().min(0),
1818
+ timestamp: z2.number().int().positive()
1819
+ }).strict();
1820
+
1821
+ // src/kvUtils.ts
1822
+ async function logKvTimestamp(env, prefix = "timestamp") {
1823
+ const timestamp = new Date().toISOString();
1824
+ const key = `${prefix}_${timestamp}`;
1825
+ try {
1826
+ await env.REPORT_KV.put(key, timestamp);
1827
+ } catch (error) {
1828
+ console.error(`Failed to log timestamp to KV: ${toError(error, "Unknown error")}`);
1829
+ }
1830
+ }
1831
+ function headersToObject(headers) {
1832
+ const result = {};
1833
+ if (!headers)
1834
+ return result;
1835
+ try {
1836
+ headers.forEach((value, key) => {
1837
+ result[key] = value;
1838
+ });
1839
+ } catch (error) {
1840
+ console.error(`Error converting headers to object: ${toError(error, "Unknown error")}`);
1841
+ }
1842
+ return result;
1843
+ }
1844
+
1845
+ // src/queue-handler.ts
1846
+ function createQueueHandler(options) {
1847
+ const { maxRetries, backoffDelays, onMessage, onRetry, onDLQ, logger } = options;
1848
+ return async (batch) => {
1849
+ for (const msg of batch.messages) {
1850
+ const attemptNumber = msg.attempts || 0;
1851
+ const logId = `[${msg.id}]`;
1852
+ try {
1853
+ logger?.info(`${logId} Processing message (attempt ${attemptNumber + 1})`);
1854
+ await onMessage(msg.body, attemptNumber);
1855
+ logger?.info(`${logId} Message processed successfully`);
1856
+ } catch (error) {
1857
+ const errorMsg = error instanceof Error ? error.message : String(error);
1858
+ if (attemptNumber < maxRetries) {
1859
+ const delaySeconds = backoffDelays[attemptNumber] ?? backoffDelays[backoffDelays.length - 1];
1860
+ logger?.info(`${logId} Retrying in ${delaySeconds}s (attempt ${attemptNumber + 2}/${maxRetries + 1})`);
1861
+ await onRetry?.(msg.body, attemptNumber, errorMsg, delaySeconds);
1862
+ msg.retry({ delaySeconds });
1863
+ } else {
1864
+ logger?.error(`${logId} Max retries exceeded (${maxRetries + 1} attempts), moving to DLQ`);
1865
+ await onDLQ?.(msg.body, attemptNumber, errorMsg);
1866
+ }
1867
+ }
1868
+ }
1869
+ };
1870
+ }
1871
+
1872
+ // src/middleware/auth.ts
1873
+ function timingSafeEqual(a, b) {
1874
+ const encoder = new TextEncoder;
1875
+ const aBuf = encoder.encode(a);
1876
+ const bBuf = encoder.encode(b);
1877
+ const len = Math.max(aBuf.length, bBuf.length);
1878
+ let result = aBuf.length === bBuf.length ? 0 : 1;
1879
+ for (let i = 0;i < len; i++) {
1880
+ const av = i < aBuf.length ? aBuf[i] : 0;
1881
+ const bv = i < bBuf.length ? bBuf[i] : 0;
1882
+ result |= av ^ bv;
1883
+ }
1884
+ return result === 0;
1885
+ }
1886
+ function resolveOperatorApiKey(env) {
1887
+ const preferred = env.OPERATOR_API_KEY;
1888
+ if (typeof preferred === "string" && preferred.length > 0) {
1889
+ return preferred;
1890
+ }
1891
+ const legacy = env.INTERNAL_API_KEY;
1892
+ if (typeof legacy === "string" && legacy.length > 0) {
1893
+ return legacy;
1894
+ }
1895
+ return;
1896
+ }
1897
+ async function requireAuth(request, env) {
1898
+ return requireOperatorAuth(request, env);
1899
+ }
1900
+ async function requireOperatorAuth(request, env) {
1901
+ const apiKey = resolveOperatorApiKey(env);
1902
+ if (!apiKey) {
1903
+ return new Response(JSON.stringify({
1904
+ error: "Operator API key not configured",
1905
+ hint: "Set OPERATOR_API_KEY (preferred) or INTERNAL_API_KEY on the Worker"
1906
+ }), { status: 401, headers: { "Content-Type": "application/json" } });
1907
+ }
1908
+ const authHeader = request.headers.get("Authorization");
1909
+ const expectedHeader = `Bearer ${apiKey}`;
1910
+ if (!authHeader || !timingSafeEqual(authHeader, expectedHeader)) {
1911
+ return new Response(JSON.stringify({ error: "Unauthorized" }), {
1912
+ status: 401,
1913
+ headers: { "Content-Type": "application/json" }
1914
+ });
1915
+ }
1916
+ return null;
1917
+ }
1918
+ function createOperatorAuthMiddleware() {
1919
+ return async (request, env, _ctx) => {
1920
+ const denied = await requireOperatorAuth(request, env);
1921
+ if (denied)
1922
+ return denied;
1923
+ return;
1924
+ };
1925
+ }
1926
+ function normalizeKeyNames(keyName) {
1927
+ if (typeof keyName === "string") {
1928
+ return [keyName];
1929
+ }
1930
+ return [...keyName];
1931
+ }
1932
+ function collectInternalAuthKeys(env, keyNames) {
1933
+ const keys = [];
1934
+ for (const name of normalizeKeyNames(keyNames)) {
1935
+ const value = env[name];
1936
+ if (typeof value === "string" && value.length > 0) {
1937
+ keys.push(value);
1938
+ }
1939
+ }
1940
+ return keys;
1941
+ }
1942
+ function matchesAnyInternalAuthKey(providedKey, expectedKeys) {
1943
+ for (const expected of expectedKeys) {
1944
+ if (timingSafeEqual(providedKey, expected)) {
1945
+ return true;
1946
+ }
1947
+ }
1948
+ return false;
1949
+ }
1950
+ function internalAuthNotConfiguredResponse(keyNames) {
1951
+ const label = normalizeKeyNames(keyNames).join(" | ");
1952
+ return new Response(JSON.stringify({
1953
+ success: false,
1954
+ error: `Internal auth key(s) not configured: ${label}`
1955
+ }), { status: 401, headers: { "Content-Type": "application/json" } });
1956
+ }
1957
+ function requireInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
1958
+ const expectedKeys = collectInternalAuthKeys(env, keyName);
1959
+ if (expectedKeys.length === 0) {
1960
+ return internalAuthNotConfiguredResponse(keyName);
1961
+ }
1962
+ const providedKey = request.headers.get("X-Internal-Auth-Key");
1963
+ if (!providedKey || !matchesAnyInternalAuthKey(providedKey, expectedKeys)) {
1964
+ return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } });
1965
+ }
1966
+ return null;
1967
+ }
1968
+ function createInternalAuthMiddleware(keyName = "INTERNAL_KEY_BINDING") {
1969
+ return async (request, env, _ctx) => {
1970
+ const expectedKeys = collectInternalAuthKeys(env, keyName);
1971
+ if (expectedKeys.length === 0) {
1972
+ return internalAuthNotConfiguredResponse(keyName);
1973
+ }
1974
+ const providedKey = request.headers.get("X-Internal-Auth-Key");
1975
+ if (!providedKey || !matchesAnyInternalAuthKey(providedKey, expectedKeys)) {
1976
+ return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } });
1977
+ }
1978
+ return;
1979
+ };
1980
+ }
1981
+ function checkInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
1982
+ const expectedKeys = collectInternalAuthKeys(env, keyName);
1983
+ if (expectedKeys.length === 0) {
1984
+ return {
1985
+ authorized: false,
1986
+ error: `${normalizeKeyNames(keyName).join(" | ")} not configured`
1987
+ };
1988
+ }
1989
+ const providedKey = request.headers.get("X-Internal-Auth-Key");
1990
+ if (!providedKey || !matchesAnyInternalAuthKey(providedKey, expectedKeys)) {
1991
+ return { authorized: false, error: "Unauthorized" };
1992
+ }
1993
+ return { authorized: true };
1994
+ }
1995
+
1996
+ // src/service-bindings.ts
1997
+ var InternalAuthKeyFields = {
1998
+ DEFAULT: "INTERNAL_KEY_BINDING",
1999
+ D1_READ: "D1_READ_KEY_BINDING",
2000
+ D1_WRITE: "D1_WRITE_KEY_BINDING",
2001
+ TRADE_EXECUTE: "TRADE_EXECUTE_KEY_BINDING",
2002
+ TRADE_READ: "TRADE_READ_KEY_BINDING",
2003
+ TELEGRAM_ALERT: "TELEGRAM_INTERNAL_KEY_BINDING",
2004
+ WALLET_EXECUTE: "WALLET_EXECUTE_KEY_BINDING",
2005
+ AGENT: "AGENT_INTERNAL_KEY",
2006
+ D1_INTERNAL_ALIAS: "D1_INTERNAL_KEY",
2007
+ TRADE_INTERNAL_ALIAS: "TRADE_INTERNAL_KEY",
2008
+ TELEGRAM_INTERNAL_ALIAS: "TELEGRAM_INTERNAL_KEY"
2009
+ };
2010
+ var D1_READ_AUTH_KEY_FIELDS = [
2011
+ InternalAuthKeyFields.D1_READ,
2012
+ InternalAuthKeyFields.DEFAULT
2013
+ ];
2014
+ var D1_WRITE_AUTH_KEY_FIELDS = [
2015
+ InternalAuthKeyFields.D1_WRITE,
2016
+ InternalAuthKeyFields.DEFAULT
2017
+ ];
2018
+ var TRADE_EXECUTE_AUTH_KEY_FIELDS = [
2019
+ InternalAuthKeyFields.TRADE_EXECUTE,
2020
+ InternalAuthKeyFields.DEFAULT,
2021
+ InternalAuthKeyFields.AGENT
2022
+ ];
2023
+ var TRADE_READ_AUTH_KEY_FIELDS = [
2024
+ InternalAuthKeyFields.TRADE_READ,
2025
+ InternalAuthKeyFields.DEFAULT
2026
+ ];
2027
+ var TELEGRAM_ALERT_AUTH_KEY_FIELDS = [
2028
+ InternalAuthKeyFields.TELEGRAM_ALERT,
2029
+ InternalAuthKeyFields.DEFAULT
2030
+ ];
2031
+ var WALLET_EXECUTE_AUTH_KEY_FIELDS = [
2032
+ InternalAuthKeyFields.WALLET_EXECUTE,
2033
+ InternalAuthKeyFields.DEFAULT
2034
+ ];
2035
+ var DASHBOARD_D1_READ_AUTH_KEY_FIELDS = [
2036
+ InternalAuthKeyFields.D1_READ,
2037
+ InternalAuthKeyFields.D1_INTERNAL_ALIAS,
2038
+ InternalAuthKeyFields.DEFAULT
2039
+ ];
2040
+ var DASHBOARD_TRADE_EXECUTE_AUTH_KEY_FIELDS = [
2041
+ InternalAuthKeyFields.TRADE_EXECUTE,
2042
+ InternalAuthKeyFields.TRADE_INTERNAL_ALIAS,
2043
+ InternalAuthKeyFields.DEFAULT
2044
+ ];
2045
+ var DASHBOARD_TELEGRAM_ALERT_AUTH_KEY_FIELDS = [
2046
+ InternalAuthKeyFields.TELEGRAM_ALERT,
2047
+ InternalAuthKeyFields.TELEGRAM_INTERNAL_ALIAS,
2048
+ InternalAuthKeyFields.DEFAULT
2049
+ ];
2050
+ function resolveInternalAuthKey(env, keyFields) {
2051
+ return collectInternalAuthKeys(env, keyFields)[0];
2052
+ }
2053
+
2054
+ class ServiceAuthError extends Error {
2055
+ constructor(message) {
2056
+ super(message);
2057
+ this.name = "ServiceAuthError";
2058
+ }
2059
+ }
2060
+ function authenticatedServiceFetch(binding, env, path, body, options) {
2061
+ const key = options?.internalKey ?? (options?.internalKeyFields ? resolveInternalAuthKey(env, options.internalKeyFields) : env.INTERNAL_KEY_BINDING);
2062
+ if (!key) {
2063
+ const label = options?.internalKeyFields ? Array.isArray(options.internalKeyFields) ? options.internalKeyFields.join(" | ") : options.internalKeyFields : "INTERNAL_KEY_BINDING";
2064
+ return Promise.reject(new ServiceAuthError(`${label} not configured`));
2065
+ }
2066
+ return serviceFetch(binding, path, body, {
2067
+ method: options?.method,
2068
+ timeout: options?.timeout,
2069
+ headers: {
2070
+ ...options?.headers,
2071
+ "X-Internal-Auth-Key": key
2072
+ }
2073
+ });
2074
+ }
2075
+ function serviceFetch(binding, path, body, options) {
2076
+ const timeout = options?.timeout ?? 30000;
2077
+ return binding.fetch(`http://internal${path}`, {
2078
+ method: options?.method ?? "POST",
2079
+ headers: {
2080
+ "Content-Type": "application/json",
2081
+ ...options?.headers
2082
+ },
2083
+ body: body != null ? JSON.stringify(body) : undefined,
2084
+ signal: AbortSignal.timeout(timeout)
2085
+ });
2086
+ }
2087
+
2088
+ // src/test-utils.ts
2089
+ function createMockKV(overrides) {
2090
+ return {
2091
+ get: async (_key) => null,
2092
+ put: async (_key, _value, _options) => {},
2093
+ delete: async (_key) => {},
2094
+ list: async (_options) => ({
2095
+ keys: []
2096
+ }),
2097
+ ...overrides
2098
+ };
2099
+ }
2100
+ function createMockR2(overrides) {
2101
+ return {
2102
+ put: async (key, _value, _options) => ({
2103
+ key
2104
+ }),
2105
+ get: async (_key) => null,
2106
+ delete: async (_key) => {},
2107
+ list: async (_options) => ({
2108
+ objects: []
2109
+ }),
2110
+ ...overrides
2111
+ };
2112
+ }
2113
+ function createMockPreparedStatement() {
2114
+ return {
2115
+ bind: (..._params) => createMockPreparedStatement(),
2116
+ run: async () => ({ success: true }),
2117
+ all: async () => ({ success: true, results: [] }),
2118
+ first: async () => null
2119
+ };
2120
+ }
2121
+ function createMockD1(overrides) {
2122
+ return {
2123
+ prepare: (_sql) => createMockPreparedStatement(),
2124
+ dump: async () => new ArrayBuffer(0),
2125
+ batch: async (_statements) => [],
2126
+ exec: async (_sql) => ({ success: true }),
2127
+ ...overrides
2128
+ };
2129
+ }
2130
+ function createMockFetcher(response) {
2131
+ const defaultResponse = response ?? new Response("OK", { status: 200 });
2132
+ return {
2133
+ fetch: async (_request, _init) => defaultResponse
2134
+ };
2135
+ }
2136
+ function createMockAnalyticsEngine() {
2137
+ return {
2138
+ writeDataPoint: (_data) => {}
2139
+ };
2140
+ }
2141
+ function createMockAnalyticsEngineDataset() {
2142
+ return createMockAnalyticsEngine();
2143
+ }
2144
+ function createMockQueue() {
2145
+ return {
2146
+ send: async (_body, _options) => {},
2147
+ sendBatch: async (_messages) => {}
2148
+ };
2149
+ }
2150
+ function createMockVectorizeIndex(overrides) {
2151
+ return {
2152
+ query: async (_vector, _options) => ({
2153
+ matches: []
2154
+ }),
2155
+ upsert: async (_vectors) => {},
2156
+ deleteByIds: async (_ids) => {},
2157
+ describe: async () => ({ dimension: 384, totalVectors: 0 }),
2158
+ ...overrides
2159
+ };
2160
+ }
2161
+ function createMockAi(overrides) {
2162
+ return {
2163
+ run: async (_model, _input) => ({}),
2164
+ ...overrides
2165
+ };
2166
+ }
2167
+ function createMockExecutionContext(overrides) {
2168
+ return {
2169
+ waitUntil: (_promise) => {},
2170
+ passThroughOnException: () => {},
2171
+ ...overrides
2172
+ };
2173
+ }
2174
+ function createMockSecretsStore(overrides) {
2175
+ return {
2176
+ get: async (_key) => null,
2177
+ put: async (_key, _value) => {},
2178
+ delete: async (_key) => {},
2179
+ ...overrides
2180
+ };
2181
+ }
2182
+ function createMockEnv(overrides) {
2183
+ return overrides;
2184
+ }
2185
+
2186
+ // src/path-utils.ts
2187
+ import { existsSync as existsSync3 } from "fs";
2188
+ import { homedir as homedir2 } from "os";
2189
+ import { dirname, join as join3, resolve } from "path";
2190
+ function createHooxPath(path) {
2191
+ return path;
2192
+ }
2193
+ function getHooxHome() {
2194
+ try {
2195
+ const override = process.env.HOOX_HOME?.trim();
2196
+ if (override) {
2197
+ return createHooxPath(resolve(override));
2198
+ }
2199
+ const home = homedir2();
2200
+ if (!home || home.length === 0) {
2201
+ return createHooxPath(resolve(process.cwd(), ".hoox"));
2202
+ }
2203
+ return createHooxPath(join3(home, ".hoox"));
2204
+ } catch {
2205
+ return createHooxPath(resolve(process.cwd(), ".hoox"));
2206
+ }
2207
+ }
2208
+ function isHooxSetupRoot(dir) {
2209
+ if (!dir)
2210
+ return false;
2211
+ try {
2212
+ const root = resolve(dir);
2213
+ return existsSync3(join3(root, "wrangler.jsonc")) && existsSync3(join3(root, "packages", "cli", "package.json"));
2214
+ } catch {
2215
+ return false;
2216
+ }
2217
+ }
2218
+ function findHooxSetupRoot(startDir = process.cwd()) {
2219
+ let dir = resolve(startDir);
2220
+ for (;; ) {
2221
+ if (isHooxSetupRoot(dir))
2222
+ return dir;
2223
+ const parent = dirname(dir);
2224
+ if (parent === dir)
2225
+ return null;
2226
+ dir = parent;
2227
+ }
2228
+ }
2229
+ function resolveHooxRuntimeRoot(options) {
2230
+ const env = options?.env ?? process.env;
2231
+ const cwd = resolve(options?.cwd ?? process.cwd());
2232
+ const globalRepo = getHooxRepoPath();
2233
+ const envRepo = env.HOOX_REPO?.trim();
2234
+ if (envRepo) {
2235
+ const resolved = resolve(envRepo);
2236
+ if (isHooxSetupRoot(resolved)) {
2237
+ return {
2238
+ root: resolved,
2239
+ source: "env",
2240
+ checked: {
2241
+ env: resolved,
2242
+ cwd: findHooxSetupRoot(cwd),
2243
+ global: globalRepo
2244
+ }
2245
+ };
2246
+ }
2247
+ return {
2248
+ root: null,
2249
+ source: "env",
2250
+ checked: {
2251
+ env: resolved,
2252
+ cwd: findHooxSetupRoot(cwd),
2253
+ global: globalRepo
2254
+ }
2255
+ };
2256
+ }
2257
+ const local = findHooxSetupRoot(cwd);
2258
+ if (local) {
2259
+ return {
2260
+ root: local,
2261
+ source: "cwd",
2262
+ checked: { cwd: local, global: globalRepo }
2263
+ };
2264
+ }
2265
+ if (isHooxSetupRoot(globalRepo)) {
2266
+ return {
2267
+ root: globalRepo,
2268
+ source: "global",
2269
+ checked: { cwd: null, global: globalRepo }
2270
+ };
2271
+ }
2272
+ return {
2273
+ root: null,
2274
+ source: "none",
2275
+ checked: { cwd: null, global: globalRepo }
2276
+ };
2277
+ }
2278
+ function getTuiEntryCandidates(runtimeRoot) {
2279
+ const root = resolve(runtimeRoot);
2280
+ return [
2281
+ join3(root, "packages", "tui", "src", "main.tsx"),
2282
+ join3(root, "packages", "tui", "dist", "main.js"),
2283
+ join3(root, "packages", "tui", "src", "main.ts")
2284
+ ];
2285
+ }
2286
+ function resolveHooxPath(relativePath) {
2287
+ if (!relativePath || typeof relativePath !== "string") {
2288
+ throw new Error("relativePath must be a non-empty string");
2289
+ }
2290
+ if (relativePath.includes("..")) {
2291
+ throw new Error(`Path traversal detected in relativePath: "${relativePath}"`);
2292
+ }
2293
+ const normalizedPath = relativePath.replace(/\\/g, "/");
2294
+ const hooxHome = getHooxHome();
2295
+ const fullPath = join3(hooxHome, normalizedPath);
2296
+ const resolvedPath = resolve(fullPath);
2297
+ const resolvedHome = resolve(hooxHome);
2298
+ if (!resolvedPath.startsWith(resolvedHome)) {
2299
+ throw new Error(`Resolved path "${resolvedPath}" is outside Hoox home directory`);
2300
+ }
2301
+ return createHooxPath(resolvedPath);
2302
+ }
2303
+ function isWithinHooxHome(path) {
2304
+ try {
2305
+ const hooxHome = getHooxHome();
2306
+ const resolvedPath = resolve(path);
2307
+ const resolvedHome = resolve(hooxHome);
2308
+ return resolvedPath.startsWith(resolvedHome);
2309
+ } catch {
2310
+ return false;
2311
+ }
2312
+ }
2313
+ function getRelativeHooxPath(absolutePath) {
2314
+ try {
2315
+ if (!isWithinHooxHome(absolutePath)) {
2316
+ return null;
2317
+ }
2318
+ const hooxHome = getHooxHome();
2319
+ const resolvedPath = resolve(absolutePath);
2320
+ const resolvedHome = resolve(hooxHome);
2321
+ const relativePath = resolvedPath.slice(resolvedHome.length);
2322
+ return relativePath.startsWith("/") || relativePath.startsWith("\\") ? relativePath.slice(1) : relativePath;
2323
+ } catch {
2324
+ return null;
2325
+ }
2326
+ }
2327
+ function getHooxRepoPath() {
2328
+ return resolveHooxPath("repo");
2329
+ }
2330
+ function getHooxConfigDir() {
2331
+ return resolveHooxPath("config");
2332
+ }
2333
+ function getHooxDataDir() {
2334
+ return resolveHooxPath("data");
2335
+ }
2336
+ function getHooxWranglerPath() {
2337
+ return resolveHooxPath("config/wrangler.jsonc");
2338
+ }
2339
+ function getHooxStatePath() {
2340
+ return resolveHooxPath("data/state.json");
2341
+ }
2342
+
2343
+ // src/session.ts
2344
+ import { z as z3 } from "zod";
2345
+ var SessionStateFileSchema = z3.object({
2346
+ activeView: z3.string().optional(),
2347
+ sidebarExpanded: z3.boolean().optional(),
2348
+ windowSize: z3.object({
2349
+ cols: z3.number().int().positive(),
2350
+ rows: z3.number().int().positive()
2351
+ }).optional(),
2352
+ lastData: z3.number().nonnegative().optional(),
2353
+ savedAt: z3.string().optional()
2354
+ }).strict();
2355
+ var TUI_STATE_DIR = ".tui-state";
2356
+ function getSessionFilePath() {
2357
+ try {
2358
+ const hooxHome = getHooxHome();
2359
+ return `${hooxHome}/${TUI_STATE_DIR}/session.json`;
2360
+ } catch {
2361
+ return `${process.cwd()}/${TUI_STATE_DIR}/session.json`;
2362
+ }
2363
+ }
2364
+ var DEFAULT_SESSION = {
2365
+ activeView: "dashboard",
2366
+ sidebarExpanded: true,
2367
+ windowSize: { cols: 80, rows: 24 },
2368
+ lastData: 0,
2369
+ savedAt: new Date().toISOString()
2370
+ };
2371
+ async function saveSession(activeView, sidebarExpanded, windowSize, lastData) {
2372
+ try {
2373
+ const session = {
2374
+ activeView,
2375
+ sidebarExpanded,
2376
+ windowSize,
2377
+ lastData,
2378
+ savedAt: new Date().toISOString()
2379
+ };
2380
+ await Bun.write(getSessionFilePath(), JSON.stringify(session, null, 2));
2381
+ } catch (error) {
2382
+ console.error("[session] Failed to save session:", error.message);
2383
+ }
2384
+ }
2385
+ async function restoreSession() {
2386
+ try {
2387
+ const file = Bun.file(getSessionFilePath());
2388
+ const exists = await file.exists();
2389
+ if (!exists)
2390
+ return { ...DEFAULT_SESSION };
2391
+ const raw = await file.text();
2392
+ const result = SessionStateFileSchema.safeParse(JSON.parse(raw));
2393
+ const parsed = result.success ? result.data : {};
2394
+ return {
2395
+ activeView: validateViewId(parsed.activeView) ? parsed.activeView : DEFAULT_SESSION.activeView,
2396
+ sidebarExpanded: typeof parsed.sidebarExpanded === "boolean" ? parsed.sidebarExpanded : DEFAULT_SESSION.sidebarExpanded,
2397
+ windowSize: parsed.windowSize ?? DEFAULT_SESSION.windowSize,
2398
+ lastData: typeof parsed.lastData === "number" ? parsed.lastData : DEFAULT_SESSION.lastData,
2399
+ savedAt: parsed.savedAt ?? DEFAULT_SESSION.savedAt
2400
+ };
2401
+ } catch {
2402
+ return { ...DEFAULT_SESSION };
2403
+ }
2404
+ }
2405
+ var VALID_VIEW_IDS = new Set([
2406
+ "dashboard",
2407
+ "workers",
2408
+ "worker-detail",
2409
+ "trade-monitor",
2410
+ "logs-viewer",
2411
+ "service-manager",
2412
+ "config-editor",
2413
+ "setup-wizard",
2414
+ "settings",
2415
+ "queue-depth",
2416
+ "kv-viewer",
2417
+ "secrets-viewer",
2418
+ "db-query",
2419
+ "ai-chat",
2420
+ "edge-topology",
2421
+ "worker-settings"
2422
+ ]);
2423
+ function validateViewId(id) {
2424
+ return typeof id === "string" && VALID_VIEW_IDS.has(id);
2425
+ }
2426
+
2427
+ // src/wizard/presets.ts
2428
+ var PRESETS = [
2429
+ {
2430
+ name: "minimal",
2431
+ label: "Minimal",
2432
+ description: "Gateway + D1 database \u2014 webhook processing only",
2433
+ workers: ["hoox", "d1-worker", "analytics-worker"],
2434
+ integrations: []
2435
+ },
2436
+ {
2437
+ name: "standard",
2438
+ label: "Standard",
2439
+ description: "Trading + analytics + Telegram notifications",
2440
+ workers: [
2441
+ "hoox",
2442
+ "d1-worker",
2443
+ "trade-worker",
2444
+ "analytics-worker",
2445
+ "telegram-worker"
2446
+ ],
2447
+ integrations: ["binance", "telegram"]
2448
+ },
2449
+ {
2450
+ name: "full",
2451
+ label: "Full",
2452
+ description: "All workers + AI agent + DeFi + email + PYNE",
2453
+ workers: [
2454
+ "hoox",
2455
+ "d1-worker",
2456
+ "trade-worker",
2457
+ "agent-worker",
2458
+ "telegram-worker",
2459
+ "analytics-worker",
2460
+ "email-worker",
2461
+ "web3-wallet-worker",
2462
+ "pyne-worker"
2463
+ ],
2464
+ integrations: [
2465
+ "binance",
2466
+ "bybit",
2467
+ "mexc",
2468
+ "telegram",
2469
+ "openai",
2470
+ "wallet",
2471
+ "pyne"
2472
+ ]
2473
+ }
2474
+ ];
2475
+ var WORKER_DEPENDENCIES = {
2476
+ "trade-worker": ["d1-worker"],
2477
+ "agent-worker": ["d1-worker"],
2478
+ "email-worker": ["d1-worker"],
2479
+ "analytics-worker": ["d1-worker"],
2480
+ "web3-wallet-worker": ["d1-worker", "hoox"],
2481
+ "pyne-worker": ["trade-worker"]
2482
+ };
2483
+ function resolveDependencies(selected) {
2484
+ const result = new Set(selected);
2485
+ let changed = true;
2486
+ while (changed) {
2487
+ changed = false;
2488
+ for (const worker of [...result]) {
2489
+ const deps = WORKER_DEPENDENCIES[worker];
2490
+ if (deps) {
2491
+ for (const dep of deps) {
2492
+ if (!result.has(dep)) {
2493
+ result.add(dep);
2494
+ changed = true;
2495
+ }
2496
+ }
2497
+ }
2498
+ }
2499
+ }
2500
+ return [...result];
2501
+ }
2502
+ var INTEGRATIONS = [
2503
+ {
2504
+ key: "binance",
2505
+ label: "Binance Exchange",
2506
+ workerName: "trade-worker",
2507
+ secrets: {
2508
+ BINANCE_KEY_BINDING: "Binance API Key",
2509
+ BINANCE_SECRET_BINDING: "Binance API Secret"
2510
+ }
2511
+ },
2512
+ {
2513
+ key: "mexc",
2514
+ label: "MEXC Exchange",
2515
+ workerName: "trade-worker",
2516
+ secrets: {
2517
+ MEXC_KEY_BINDING: "MEXC API Key",
2518
+ MEXC_SECRET_BINDING: "MEXC API Secret"
2519
+ }
2520
+ },
2521
+ {
2522
+ key: "bybit",
2523
+ label: "Bybit Exchange",
2524
+ workerName: "trade-worker",
2525
+ secrets: {
2526
+ BYBIT_KEY_BINDING: "Bybit API Key",
2527
+ BYBIT_SECRET_BINDING: "Bybit API Secret"
2528
+ }
2529
+ },
2530
+ {
2531
+ key: "wallet",
2532
+ label: "Web3 Wallet (on-chain execution)",
2533
+ workerName: "web3-wallet-worker",
2534
+ secrets: {
2535
+ WALLET_MNEMONIC_SECRET: "Wallet Mnemonic Phrase",
2536
+ WALLET_PK_SECRET: "Wallet Private Key"
2537
+ }
2538
+ },
2539
+ {
2540
+ key: "email",
2541
+ label: "Email Signal Parsing",
2542
+ workerName: "email-worker",
2543
+ secrets: {
2544
+ INTERNAL_KEY_BINDING: "Internal Auth Key",
2545
+ EMAIL_HOST_BINDING: "Email Host (IMAP server)",
2546
+ EMAIL_USER_BINDING: "Email Username",
2547
+ EMAIL_PASS_BINDING: "Email Password"
2548
+ },
2549
+ vars: { USE_IMAP: "false" }
2550
+ },
2551
+ {
2552
+ key: "telegram",
2553
+ label: "Telegram Notifications",
2554
+ workerName: "telegram-worker",
2555
+ secrets: {
2556
+ TG_BOT_TOKEN_BINDING: "Telegram Bot Token"
2557
+ }
2558
+ },
2559
+ {
2560
+ key: "openai",
2561
+ label: "OpenAI (AI Agent)",
2562
+ workerName: "agent-worker",
2563
+ secrets: {
2564
+ AGENT_INTERNAL_KEY: "OpenAI API Key"
2565
+ }
2566
+ },
2567
+ {
2568
+ key: "anthropic",
2569
+ label: "Anthropic (AI Agent)",
2570
+ workerName: "agent-worker",
2571
+ secrets: {
2572
+ AGENT_INTERNAL_KEY: "Anthropic API Key"
2573
+ }
2574
+ },
2575
+ {
2576
+ key: "google-ai",
2577
+ label: "Google AI (AI Agent)",
2578
+ workerName: "agent-worker",
2579
+ secrets: {
2580
+ AGENT_INTERNAL_KEY: "Google AI API Key"
2581
+ }
2582
+ },
2583
+ {
2584
+ key: "home-assistant",
2585
+ label: "Home Assistant (Smart Home)",
2586
+ workerName: "hoox",
2587
+ secrets: {
2588
+ HA_TOKEN_BINDING: "Home Assistant Token"
2589
+ }
2590
+ },
2591
+ {
2592
+ key: "pyne",
2593
+ label: "PYNE (Pine Script edge evaluate)",
2594
+ workerName: "pyne-worker",
2595
+ secrets: {
2596
+ API_KEY: "PYNE evaluate API key (X-API-Key)",
2597
+ ALERT_WEBHOOK_URL: "Default alert() webhook URL (optional)"
2598
+ }
2599
+ }
2600
+ ];
2601
+ var BASE_WORKERS = {
2602
+ "d1-worker": {
2603
+ enabled: true,
2604
+ path: "workers/d1-worker",
2605
+ vars: { database_name: "hoox-db" }
2606
+ },
2607
+ hoox: { enabled: true, path: "workers/hoox-worker", vars: {} },
2608
+ "agent-worker": { enabled: true, path: "workers/agent-worker", vars: {} },
2609
+ "analytics-worker": {
2610
+ enabled: true,
2611
+ path: "workers/analytics-worker",
2612
+ vars: {}
2613
+ },
2614
+ "pyne-worker": {
2615
+ enabled: true,
2616
+ path: "workers/pyne-worker",
2617
+ vars: {}
2618
+ }
2619
+ };
2620
+ var BASE_SECRETS = {
2621
+ hoox: ["WEBHOOK_API_KEY_BINDING"],
2622
+ "agent-worker": ["AGENT_INTERNAL_KEY", "INTERNAL_KEY_BINDING"],
2623
+ "analytics-worker": ["CLOUDFLARE_API_TOKEN"]
2624
+ };
2625
+
2626
+ // src/wizard/engine.ts
2627
+ var STEPS = [
2628
+ {
2629
+ id: "PREREQUISITES",
2630
+ label: "System Prerequisites",
2631
+ canGoBack: false,
2632
+ optional: false,
2633
+ validate: validatePrerequisites
2634
+ },
2635
+ {
2636
+ id: "CLOUDFLARE_CONFIG",
2637
+ label: "Cloudflare Configuration",
2638
+ canGoBack: true,
2639
+ optional: false,
2640
+ validate: validateCloudflareConfig
2641
+ },
2642
+ {
2643
+ id: "WORKER_SELECTION",
2644
+ label: "Worker Selection",
2645
+ canGoBack: true,
2646
+ optional: false,
2647
+ validate: validateWorkerSelection
2648
+ },
2649
+ {
2650
+ id: "PROVISIONING",
2651
+ label: "Infrastructure Provisioning",
2652
+ canGoBack: true,
2653
+ optional: true,
2654
+ validate: () => []
2655
+ },
2656
+ {
2657
+ id: "SECRETS",
2658
+ label: "Secrets Configuration",
2659
+ canGoBack: true,
2660
+ optional: false,
2661
+ validate: validateSecrets
2662
+ },
2663
+ {
2664
+ id: "CONFIG_WRITE",
2665
+ label: "Configuration Write",
2666
+ canGoBack: true,
2667
+ optional: false,
2668
+ validate: () => []
2669
+ },
2670
+ {
2671
+ id: "DEPLOY",
2672
+ label: "Deploy",
2673
+ canGoBack: false,
2674
+ optional: true,
2675
+ validate: () => []
2676
+ },
2677
+ {
2678
+ id: "DONE",
2679
+ label: "Complete",
2680
+ canGoBack: false,
2681
+ optional: false,
2682
+ validate: () => []
2683
+ }
2684
+ ];
2685
+ var STEP_ORDER = STEPS.map((s) => s.id);
2686
+ function createInitialState() {
2687
+ return {
2688
+ step: "PREREQUISITES",
2689
+ completedSteps: [],
2690
+ selectedWorkers: [],
2691
+ selectedIntegrations: [],
2692
+ secrets: {},
2693
+ startedAt: Date.now(),
2694
+ updatedAt: Date.now()
2695
+ };
2696
+ }
2697
+ function validatePrerequisites(_state, input) {
2698
+ const errors = [];
2699
+ if (!input.checksPassed) {
2700
+ errors.push("System prerequisite checks must pass before continuing");
2701
+ }
2702
+ return errors;
2703
+ }
2704
+ function validateCloudflareConfig(_state, input) {
2705
+ const errors = [];
2706
+ const token = input.apiToken;
2707
+ const accountId = input.accountId;
2708
+ if (!token || String(token).trim().length === 0) {
2709
+ errors.push("Cloudflare API token is required");
2710
+ }
2711
+ if (!accountId || String(accountId).trim().length === 0) {
2712
+ errors.push("Cloudflare Account ID is required");
2713
+ }
2714
+ return errors;
2715
+ }
2716
+ function validateWorkerSelection(_state, input) {
2717
+ const errors = [];
2718
+ const preset = input.preset;
2719
+ const customWorkers = input.workers;
2720
+ if (preset === "custom" && (!customWorkers || customWorkers.length === 0)) {
2721
+ errors.push("At least one worker must be selected");
2722
+ }
2723
+ return errors;
2724
+ }
2725
+ function validateSecrets(state, input) {
2726
+ const errors = [];
2727
+ const secrets = input.secrets;
2728
+ if (!secrets || Object.keys(secrets).length === 0) {
2729
+ return [];
2730
+ }
2731
+ for (const integrationKey of state.selectedIntegrations) {
2732
+ const integration = INTEGRATIONS.find((i) => i.key === integrationKey);
2733
+ if (!integration)
2734
+ continue;
2735
+ const integrationSecrets = secrets[integrationKey];
2736
+ if (integrationSecrets) {
2737
+ for (const secretName of Object.keys(integration.secrets)) {
2738
+ const val = integrationSecrets[secretName];
2739
+ if (!val || String(val).trim().length === 0) {
2740
+ errors.push(`Secret "${integration.secrets[secretName]}" is required for ${integration.label}`);
2741
+ }
2742
+ }
2743
+ }
2744
+ }
2745
+ return errors;
2746
+ }
2747
+
2748
+ class WizardEngine {
2749
+ state;
2750
+ constructor(initialState) {
2751
+ this.state = { ...createInitialState(), ...initialState };
2752
+ }
2753
+ getState() {
2754
+ return { ...this.state };
2755
+ }
2756
+ getCurrentStep() {
2757
+ return STEPS.find((s) => s.id === this.state.step) ?? STEPS[0];
2758
+ }
2759
+ getCompletedSteps() {
2760
+ return [...this.state.completedSteps];
2761
+ }
2762
+ canProceed() {
2763
+ const step = this.getCurrentStep();
2764
+ return step.optional || this.state.completedSteps.includes(step.id);
2765
+ }
2766
+ canGoBack() {
2767
+ return this.getCurrentStep().canGoBack;
2768
+ }
2769
+ execute(input) {
2770
+ const currentStep = this.getCurrentStep();
2771
+ const errors = currentStep.validate(this.state, input);
2772
+ if (errors.length > 0) {
2773
+ return errors;
2774
+ }
2775
+ this.applyInput(input);
2776
+ if (!this.state.completedSteps.includes(this.state.step)) {
2777
+ this.state.completedSteps.push(this.state.step);
2778
+ }
2779
+ const currentIdx = STEP_ORDER.indexOf(this.state.step);
2780
+ if (currentIdx < STEP_ORDER.length - 1) {
2781
+ this.state.step = STEP_ORDER[currentIdx + 1];
2782
+ }
2783
+ this.state.updatedAt = Date.now();
2784
+ return [];
2785
+ }
2786
+ goBack() {
2787
+ if (!this.canGoBack())
2788
+ return;
2789
+ const currentIdx = STEP_ORDER.indexOf(this.state.step);
2790
+ if (currentIdx > 0) {
2791
+ const previousStep = STEP_ORDER[currentIdx - 1];
2792
+ this.state.step = previousStep;
2793
+ this.state.completedSteps = this.state.completedSteps.filter((s) => s !== this.state.step || STEP_ORDER.indexOf(s) <= STEP_ORDER.indexOf(previousStep));
2794
+ }
2795
+ this.state.updatedAt = Date.now();
2796
+ }
2797
+ reset() {
2798
+ this.state = createInitialState();
2799
+ }
2800
+ buildConfig() {
2801
+ const cf = this.state.cloudflareConfig;
2802
+ const workers = {};
2803
+ for (const [name, baseCfg] of Object.entries(BASE_WORKERS)) {
2804
+ workers[name] = {
2805
+ enabled: true,
2806
+ path: baseCfg.path,
2807
+ vars: { ...baseCfg.vars },
2808
+ secrets: [...BASE_SECRETS[name] ?? []]
2809
+ };
2810
+ }
2811
+ for (const key of this.state.selectedIntegrations) {
2812
+ const integration = INTEGRATIONS.find((i) => i.key === key);
2813
+ if (!integration)
2814
+ continue;
2815
+ const workerName = integration.workerName;
2816
+ if (!workers[workerName]) {
2817
+ workers[workerName] = {
2818
+ enabled: true,
2819
+ path: `workers/${workerName}`,
2820
+ vars: {},
2821
+ secrets: []
2822
+ };
2823
+ }
2824
+ if (integration.vars) {
2825
+ Object.assign(workers[workerName].vars, integration.vars);
2826
+ }
2827
+ for (const secretName of Object.keys(integration.secrets)) {
2828
+ if (!workers[workerName].secrets.includes(secretName)) {
2829
+ workers[workerName].secrets.push(secretName);
2830
+ }
2831
+ }
2832
+ }
2833
+ for (const workerName of this.state.selectedWorkers) {
2834
+ if (!workers[workerName]) {
2835
+ workers[workerName] = {
2836
+ enabled: true,
2837
+ path: `workers/${workerName}`,
2838
+ vars: {},
2839
+ secrets: []
2840
+ };
2841
+ }
2842
+ }
2843
+ return {
2844
+ global: {
2845
+ cloudflare_api_token: cf?.apiToken ?? "",
2846
+ cloudflare_account_id: cf?.accountId ?? "",
2847
+ cloudflare_secret_store_id: cf?.secretStoreId ?? "",
2848
+ subdomain_prefix: cf?.subdomain ?? ""
2849
+ },
2850
+ workers
2851
+ };
2852
+ }
2853
+ getProvisioningPlan() {
2854
+ const plan = {
2855
+ d1Databases: [],
2856
+ kvNamespaces: [],
2857
+ r2Buckets: [],
2858
+ queues: []
2859
+ };
2860
+ const allWorkers = [
2861
+ ...Object.keys(BASE_WORKERS),
2862
+ ...this.state.selectedWorkers,
2863
+ ...this.state.selectedIntegrations.map((k) => {
2864
+ const i = INTEGRATIONS.find((i2) => i2.key === k);
2865
+ return i?.workerName ?? "";
2866
+ }).filter(Boolean)
2867
+ ];
2868
+ const workerSet = new Set(allWorkers);
2869
+ if (workerSet.has("d1-worker")) {
2870
+ plan.d1Databases.push("hoox-db");
2871
+ }
2872
+ if (workerSet.has("hoox") || workerSet.has("analytics-worker")) {
2873
+ plan.kvNamespaces.push("CONFIG_KV");
2874
+ }
2875
+ if (workerSet.has("web3-wallet-worker")) {
2876
+ plan.kvNamespaces.push("WEB3_CACHE_KV");
2877
+ }
2878
+ plan.d1Databases = [...new Set(plan.d1Databases)];
2879
+ plan.kvNamespaces = [...new Set(plan.kvNamespaces)];
2880
+ return plan;
2881
+ }
2882
+ applyInput(input) {
2883
+ const step = this.state.step;
2884
+ switch (step) {
2885
+ case "PREREQUISITES":
2886
+ break;
2887
+ case "CLOUDFLARE_CONFIG":
2888
+ this.state.cloudflareConfig = {
2889
+ apiToken: String(input.apiToken ?? ""),
2890
+ accountId: String(input.accountId ?? ""),
2891
+ secretStoreId: String(input.secretStoreId ?? ""),
2892
+ subdomain: String(input.subdomain ?? "cryptolinx")
2893
+ };
2894
+ break;
2895
+ case "WORKER_SELECTION": {
2896
+ const preset = input.preset;
2897
+ this.state.preset = preset;
2898
+ if (preset && preset !== "custom") {
2899
+ const presetDef = PRESETS.find((p) => p.name === preset);
2900
+ if (presetDef) {
2901
+ const resolved = resolveDependencies(presetDef.workers);
2902
+ this.state.selectedWorkers = resolved;
2903
+ this.state.selectedIntegrations = presetDef.integrations;
2904
+ }
2905
+ } else {
2906
+ const workers = input.workers;
2907
+ const integrations = input.integrations;
2908
+ if (workers) {
2909
+ this.state.selectedWorkers = resolveDependencies(workers);
2910
+ }
2911
+ if (integrations) {
2912
+ this.state.selectedIntegrations = integrations;
2913
+ }
2914
+ }
2915
+ break;
2916
+ }
2917
+ case "SECRETS":
2918
+ this.state.secrets = input.secrets ?? {};
2919
+ break;
2920
+ case "PROVISIONING":
2921
+ if (input.provisioningResults) {
2922
+ this.state.provisioningResults = input.provisioningResults;
2923
+ }
2924
+ break;
2925
+ case "CONFIG_WRITE":
2926
+ case "DEPLOY":
2927
+ case "DONE":
2928
+ break;
2929
+ }
2930
+ }
2931
+ }
2932
+
2933
+ // src/wizard/persistence.ts
2934
+ function serializeState(state) {
2935
+ return JSON.stringify(state, null, 2);
2936
+ }
2937
+ function deserializeState(json) {
2938
+ const parsed = JSON.parse(json);
2939
+ return parsed;
2940
+ }
2941
+ var WIZARD_STATE_PATH = ".wizard-state.json";
2942
+ // src/schemas/registry.ts
2943
+ function deriveCalledBy(manifests) {
2944
+ const calledBy = {};
2945
+ for (const [workerName, manifest] of Object.entries(manifests)) {
2946
+ if (!calledBy[workerName])
2947
+ calledBy[workerName] = new Set;
2948
+ for (const svc of manifest.services) {
2949
+ if (!calledBy[svc.service])
2950
+ calledBy[svc.service] = new Set;
2951
+ calledBy[svc.service].add(workerName);
2952
+ }
2953
+ }
2954
+ const result = {};
2955
+ for (const [worker, callers] of Object.entries(calledBy)) {
2956
+ result[worker] = [...callers].sort();
2957
+ }
2958
+ return result;
2959
+ }
2960
+ var manifests = {
2961
+ hoox: {
2962
+ name: "hoox",
2963
+ path: "workers/hoox-worker",
2964
+ vars: {
2965
+ WEBHOOK_API_KEY_BINDING: {
2966
+ type: "secret",
2967
+ description: "External webhook auth key"
2968
+ },
2969
+ INTERNAL_KEY_BINDING: {
2970
+ type: "secret",
2971
+ description: "Inter-worker auth key"
2972
+ },
2973
+ HA_TOKEN_BINDING: { type: "secret", description: "Home Assistant token" }
2974
+ },
2975
+ services: [
2976
+ {
2977
+ binding: "TRADE_SERVICE",
2978
+ service: "trade-worker",
2979
+ description: "Trading functionality"
2980
+ },
2981
+ {
2982
+ binding: "TELEGRAM_SERVICE",
2983
+ service: "telegram-worker",
2984
+ description: "Notifications"
2985
+ }
2986
+ ],
2987
+ infrastructure: {
2988
+ kv: [
2989
+ { binding: "SESSIONS_KV", description: "Webhook session storage" },
2990
+ {
2991
+ binding: "CONFIG_KV",
2992
+ description: "Configuration + rate limiter state"
2993
+ }
2994
+ ],
2995
+ vectorize: [{ binding: "VECTORIZE_INDEX", index: "my-rag-index" }],
2996
+ ai: true,
2997
+ queues: { producer: ["trade-execution"] },
2998
+ durableObjects: [
2999
+ { name: "IDEMPOTENCY_STORE", className: "IdempotencyStore" }
3000
+ ]
3001
+ },
3002
+ middleware: [
3003
+ "requireAuth",
3004
+ "requireInternalAuth",
3005
+ "cors",
3006
+ "rateLimit",
3007
+ "logger",
3008
+ "validate"
3009
+ ]
3010
+ },
3011
+ "trade-worker": {
3012
+ name: "trade-worker",
3013
+ path: "workers/trade-worker",
3014
+ vars: {
3015
+ INTERNAL_KEY_BINDING: {
3016
+ type: "secret",
3017
+ description: "Inter-worker auth key"
3018
+ },
3019
+ TELEGRAM_INTERNAL_KEY_BINDING: {
3020
+ type: "secret",
3021
+ description: "Telegram outbound auth"
3022
+ },
3023
+ BINANCE_KEY_BINDING: { type: "secret", description: "Binance API key" },
3024
+ BINANCE_SECRET_BINDING: {
3025
+ type: "secret",
3026
+ description: "Binance API secret"
3027
+ },
3028
+ MEXC_KEY_BINDING: { type: "secret", description: "MEXC API key" },
3029
+ MEXC_SECRET_BINDING: { type: "secret", description: "MEXC API secret" },
3030
+ BYBIT_KEY_BINDING: { type: "secret", description: "Bybit API key" },
3031
+ BYBIT_SECRET_BINDING: { type: "secret", description: "Bybit API secret" }
3032
+ },
3033
+ services: [
3034
+ {
3035
+ binding: "D1_SERVICE",
3036
+ service: "d1-worker",
3037
+ description: "Database operations"
3038
+ },
3039
+ {
3040
+ binding: "TELEGRAM_SERVICE",
3041
+ service: "telegram-worker",
3042
+ description: "Notifications"
3043
+ }
3044
+ ],
3045
+ infrastructure: {
3046
+ kv: [{ binding: "CONFIG_KV", description: "Configuration" }],
3047
+ d1: [
3048
+ {
3049
+ binding: "DB",
3050
+ database: "trade-data-db",
3051
+ description: "Trade operations"
3052
+ }
3053
+ ],
3054
+ r2: [
3055
+ {
3056
+ binding: "REPORTS_BUCKET",
3057
+ bucket: "trade-reports",
3058
+ description: "Trade reports"
3059
+ },
3060
+ {
3061
+ binding: "SYSTEM_LOGS_BUCKET",
3062
+ bucket: "hoox-system-logs",
3063
+ description: "Verbose exchange logs"
3064
+ }
3065
+ ],
3066
+ queues: { consumer: ["trade-execution"] }
3067
+ },
3068
+ middleware: ["requireInternalAuth"]
3069
+ },
3070
+ "telegram-worker": {
3071
+ name: "telegram-worker",
3072
+ path: "workers/telegram-worker",
3073
+ vars: {
3074
+ INTERNAL_KEY_BINDING: {
3075
+ type: "secret",
3076
+ description: "Inter-worker auth key"
3077
+ },
3078
+ TG_BOT_TOKEN_BINDING: {
3079
+ type: "secret",
3080
+ description: "Telegram bot token"
3081
+ },
3082
+ TG_CHAT_ID_BINDING: { type: "secret", description: "Default chat ID" },
3083
+ TELEGRAM_SECRET_TOKEN: {
3084
+ type: "secret",
3085
+ description: "Webhook verification token"
3086
+ }
3087
+ },
3088
+ services: [],
3089
+ infrastructure: {
3090
+ kv: [{ binding: "CONFIG_KV", description: "Configuration" }],
3091
+ r2: [
3092
+ {
3093
+ binding: "UPLOADS_BUCKET",
3094
+ bucket: "user-uploads",
3095
+ description: "User uploaded files"
3096
+ }
3097
+ ],
3098
+ vectorize: [{ binding: "VECTORIZE_INDEX", index: "my-rag-index" }],
3099
+ ai: true
3100
+ },
3101
+ middleware: ["requireInternalAuth"]
3102
+ },
3103
+ "d1-worker": {
3104
+ name: "d1-worker",
3105
+ path: "workers/d1-worker",
3106
+ vars: {
3107
+ INTERNAL_KEY_BINDING: {
3108
+ type: "secret",
3109
+ description: "Inter-worker auth key"
3110
+ }
3111
+ },
3112
+ services: [],
3113
+ infrastructure: {
3114
+ kv: [{ binding: "CONFIG_KV", description: "Configuration" }],
3115
+ d1: [
3116
+ {
3117
+ binding: "DB",
3118
+ database: "trade-data-db",
3119
+ description: "Main database"
3120
+ }
3121
+ ]
3122
+ },
3123
+ middleware: ["requireInternalAuth"]
3124
+ },
3125
+ "web3-wallet-worker": {
3126
+ name: "web3-wallet-worker",
3127
+ path: "workers/web3-wallet-worker",
3128
+ vars: {
3129
+ WALLET_PK_SECRET: { type: "secret", description: "Wallet private key" },
3130
+ WALLET_MNEMONIC_SECRET: {
3131
+ type: "secret",
3132
+ description: "Wallet mnemonic phrase"
3133
+ }
3134
+ },
3135
+ services: [
3136
+ {
3137
+ binding: "TELEGRAM_SERVICE",
3138
+ service: "telegram-worker",
3139
+ description: "Notifications"
3140
+ }
3141
+ ],
3142
+ infrastructure: {},
3143
+ middleware: ["requireInternalAuth"]
3144
+ },
3145
+ "agent-worker": {
3146
+ name: "agent-worker",
3147
+ path: "workers/agent-worker",
3148
+ vars: {
3149
+ AGENT_INTERNAL_KEY: { type: "secret", description: "Agent worker auth" },
3150
+ INTERNAL_KEY_BINDING: {
3151
+ type: "secret",
3152
+ description: "Inter-worker auth key"
3153
+ }
3154
+ },
3155
+ services: [
3156
+ {
3157
+ binding: "TRADE_SERVICE",
3158
+ service: "trade-worker",
3159
+ description: "Trading functionality"
3160
+ },
3161
+ {
3162
+ binding: "TELEGRAM_SERVICE",
3163
+ service: "telegram-worker",
3164
+ description: "Notifications"
3165
+ }
3166
+ ],
3167
+ infrastructure: {
3168
+ kv: [{ binding: "CONFIG_KV", description: "Configuration" }],
3169
+ d1: [
3170
+ {
3171
+ binding: "DB",
3172
+ database: "trade-data-db",
3173
+ description: "Portfolio queries"
3174
+ }
3175
+ ],
3176
+ ai: true
3177
+ },
3178
+ middleware: ["requireInternalAuth"],
3179
+ cron: ["*/15 * * * *"]
3180
+ },
3181
+ "email-worker": {
3182
+ name: "email-worker",
3183
+ path: "workers/email-worker",
3184
+ vars: {
3185
+ INTERNAL_KEY_BINDING: {
3186
+ type: "secret",
3187
+ description: "Inter-worker auth key"
3188
+ },
3189
+ EMAIL_HOST_BINDING: { type: "secret", description: "Email IMAP host" },
3190
+ EMAIL_USER_BINDING: { type: "secret", description: "Email username" },
3191
+ EMAIL_PASS_BINDING: { type: "secret", description: "Email password" },
3192
+ TRADE_WORKER_NAME: {
3193
+ type: "plaintext",
3194
+ description: "Trade worker service name",
3195
+ default: "trade-worker"
3196
+ },
3197
+ USE_IMAP: {
3198
+ type: "plaintext",
3199
+ description: "Use IMAP for email",
3200
+ default: "false"
3201
+ },
3202
+ MAILGUN_API_KEY: { type: "secret", description: "Mailgun API key" },
3203
+ EMAIL_SCAN_SUBJECT: { type: "secret", description: "Email scan subject" }
3204
+ },
3205
+ services: [
3206
+ {
3207
+ binding: "TRADE_SERVICE",
3208
+ service: "trade-worker",
3209
+ description: "Trading functionality"
3210
+ }
3211
+ ],
3212
+ infrastructure: {
3213
+ kv: [{ binding: "CONFIG_KV", description: "Configuration" }]
3214
+ },
3215
+ middleware: ["requireInternalAuth"],
3216
+ cron: ["*/5 * * * *"]
3217
+ },
3218
+ "analytics-worker": {
3219
+ name: "analytics-worker",
3220
+ path: "workers/analytics-worker",
3221
+ vars: {
3222
+ CLOUDFLARE_API_TOKEN: {
3223
+ type: "secret",
3224
+ description: "CF API token for Analytics SQL"
3225
+ },
3226
+ CLOUDFLARE_ACCOUNT_ID: {
3227
+ type: "plaintext",
3228
+ description: "CF Account ID"
3229
+ }
3230
+ },
3231
+ services: [],
3232
+ infrastructure: {
3233
+ analyticsEngine: true
3234
+ },
3235
+ middleware: []
3236
+ },
3237
+ "report-worker": {
3238
+ name: "report-worker",
3239
+ path: "workers/report-worker",
3240
+ vars: {
3241
+ CF_API_TOKEN_BINDING: {
3242
+ type: "secret",
3243
+ description: "CF API token for Browser Rendering"
3244
+ },
3245
+ ACCOUNT_ID: { type: "plaintext", description: "CF Account ID" }
3246
+ },
3247
+ services: [
3248
+ {
3249
+ binding: "D1_SERVICE",
3250
+ service: "d1-worker",
3251
+ description: "Database queries"
3252
+ },
3253
+ {
3254
+ binding: "TELEGRAM_SERVICE",
3255
+ service: "telegram-worker",
3256
+ description: "Notifications"
3257
+ }
3258
+ ],
3259
+ infrastructure: {
3260
+ r2: [
3261
+ {
3262
+ binding: "REPORTS_BUCKET",
3263
+ bucket: "trade-reports",
3264
+ description: "PDF reports"
3265
+ }
3266
+ ]
3267
+ },
3268
+ middleware: ["requireInternalAuth"],
3269
+ cron: ["0 8 * * *", "0 18 * * *"]
3270
+ },
3271
+ "pyne-worker": {
3272
+ name: "pyne-worker",
3273
+ path: "workers/pyne-worker",
3274
+ vars: {
3275
+ API_KEY: {
3276
+ type: "secret",
3277
+ description: "X-API-Key for /run and management endpoints"
3278
+ },
3279
+ ALERT_WEBHOOK_URL: {
3280
+ type: "secret",
3281
+ description: "Default HTTPS webhook for alert()/alertcondition()"
3282
+ },
3283
+ TRADE_WORKER_NAME: {
3284
+ type: "plaintext",
3285
+ description: "Trade worker service name",
3286
+ default: "trade-worker"
3287
+ }
3288
+ },
3289
+ services: [
3290
+ {
3291
+ binding: "TRADE_SERVICE",
3292
+ service: "trade-worker",
3293
+ description: "Forward strategy trade events"
3294
+ }
3295
+ ],
3296
+ infrastructure: {
3297
+ r2: [
3298
+ {
3299
+ binding: "OHLCV_DATA",
3300
+ bucket: "pyne-worker-ohlcv",
3301
+ description: "OHLCV klines + script registry"
3302
+ }
3303
+ ]
3304
+ },
3305
+ middleware: [],
3306
+ cron: ["* * * * *"]
3307
+ },
3308
+ dashboard: {
3309
+ name: "dashboard",
3310
+ path: "workers/dashboard",
3311
+ vars: {
3312
+ DASHBOARD_USER: {
3313
+ type: "secret",
3314
+ description: "Dashboard admin username"
3315
+ },
3316
+ DASHBOARD_PASS: {
3317
+ type: "secret",
3318
+ description: "Dashboard admin password"
3319
+ },
3320
+ SESSION_SECRET: { type: "secret", description: "Session encryption key" },
3321
+ PYNE_API_KEY: {
3322
+ type: "secret",
3323
+ description: "Dashboard \u2192 pyne-worker X-API-Key (match API_KEY)"
3324
+ }
3325
+ },
3326
+ services: [
3327
+ {
3328
+ binding: "D1_SERVICE",
3329
+ service: "d1-worker",
3330
+ description: "Database queries"
3331
+ },
3332
+ {
3333
+ binding: "AGENT_SERVICE",
3334
+ service: "agent-worker",
3335
+ description: "AI risk data"
3336
+ },
3337
+ {
3338
+ binding: "PYNE_SERVICE",
3339
+ service: "pyne-worker",
3340
+ description: "PYNE edge evaluate"
3341
+ }
3342
+ ],
3343
+ infrastructure: {
3344
+ kv: [{ binding: "CONFIG_KV", description: "Configuration" }],
3345
+ ai: true
3346
+ },
3347
+ middleware: []
3348
+ }
3349
+ };
3350
+ var CALLED_BY = deriveCalledBy(manifests);
3351
+ var WORKER_MANIFESTS = manifests;
3352
+ var WORKER_NAMES = Object.keys(manifests).sort();
3353
+
3354
+ // src/schemas/validators.ts
3355
+ import { parse } from "jsonc-parser";
3356
+ function validateWranglerJsonc(workerName, manifest, jsoncContent) {
3357
+ const errors = [];
3358
+ let parsed;
3359
+ try {
3360
+ parsed = parse(jsoncContent);
3361
+ } catch {
3362
+ errors.push({
3363
+ worker: workerName,
3364
+ severity: "error",
3365
+ message: "Failed to parse wrangler.jsonc",
3366
+ file: "wrangler.jsonc"
3367
+ });
3368
+ return errors;
3369
+ }
3370
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3371
+ errors.push({
3372
+ worker: workerName,
3373
+ severity: "error",
3374
+ message: "Failed to parse wrangler.jsonc \u2014 result is not a valid object",
3375
+ file: "wrangler.jsonc"
3376
+ });
3377
+ return errors;
3378
+ }
3379
+ const wrangler = parsed;
3380
+ const declaredVars = wrangler.vars ?? {};
3381
+ for (const [name, def] of Object.entries(manifest.vars)) {
3382
+ if (!(name in declaredVars)) {
3383
+ errors.push({
3384
+ worker: workerName,
3385
+ severity: "error",
3386
+ message: `Missing var: ${name} (${def.description})`,
3387
+ file: "wrangler.jsonc"
3388
+ });
3389
+ }
3390
+ }
3391
+ for (const [name, def] of Object.entries(manifest.vars)) {
3392
+ 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") {
3393
+ errors.push({
3394
+ worker: workerName,
3395
+ severity: "warning",
3396
+ message: `Secret var "${name}" does not end with _BINDING or _SECRET suffix`,
3397
+ file: "wrangler.jsonc"
3398
+ });
3399
+ }
3400
+ }
3401
+ const declaredServices = wrangler.services ?? [];
3402
+ for (const expected of manifest.services) {
3403
+ const match = declaredServices.find((s) => s.binding === expected.binding && s.service === expected.service);
3404
+ if (!match) {
3405
+ const declared = declaredServices.find((s) => s.binding === expected.binding);
3406
+ if (declared) {
3407
+ errors.push({
3408
+ worker: workerName,
3409
+ severity: "error",
3410
+ message: `Service "${expected.binding}" points to "${declared.service}" but manifest expects "${expected.service}"`,
3411
+ file: "wrangler.jsonc"
3412
+ });
3413
+ } else {
3414
+ errors.push({
3415
+ worker: workerName,
3416
+ severity: "error",
3417
+ message: `Missing service binding: ${expected.binding} -> ${expected.service}`,
3418
+ file: "wrangler.jsonc"
3419
+ });
3420
+ }
3421
+ }
3422
+ }
3423
+ for (const declared of declaredServices) {
3424
+ const expected = manifest.services.find((s) => s.binding === declared.binding);
3425
+ if (!expected) {
3426
+ errors.push({
3427
+ worker: workerName,
3428
+ severity: "warning",
3429
+ message: `Unexpected service binding: ${declared.binding}`,
3430
+ file: "wrangler.jsonc"
3431
+ });
3432
+ }
3433
+ }
3434
+ return errors;
3435
+ }
3436
+ function validateRootSecrets(workerName, manifest, rootJsoncContent) {
3437
+ const errors = [];
3438
+ let parsed;
3439
+ try {
3440
+ parsed = parse(rootJsoncContent);
3441
+ } catch {
3442
+ errors.push({
3443
+ worker: workerName,
3444
+ severity: "error",
3445
+ message: "Failed to parse root wrangler.jsonc"
3446
+ });
3447
+ return errors;
3448
+ }
3449
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
3450
+ errors.push({
3451
+ worker: workerName,
3452
+ severity: "error",
3453
+ message: "Failed to parse root wrangler.jsonc \u2014 result is not a valid object"
3454
+ });
3455
+ return errors;
3456
+ }
3457
+ const rootWrangler = parsed;
3458
+ const rootSecrets = rootWrangler.workers?.[workerName]?.secrets ?? [];
3459
+ const expectedSecrets = Object.entries(manifest.vars).filter(([_, def]) => def.type === "secret").map(([name]) => name);
3460
+ for (const expected of expectedSecrets) {
3461
+ if (!rootSecrets.includes(expected)) {
3462
+ errors.push({
3463
+ worker: workerName,
3464
+ severity: "error",
3465
+ message: `Secret "${expected}" missing from root wrangler.jsonc workers.${workerName}.secrets`,
3466
+ file: "wrangler.jsonc (root)"
3467
+ });
3468
+ }
3469
+ }
3470
+ return errors;
3471
+ }
3472
+ function validateDevVars(workerName, manifest, devVarsContent) {
3473
+ const errors = [];
3474
+ const vars = new Map;
3475
+ for (const line of devVarsContent.split(`
3476
+ `)) {
3477
+ const trimmed = line.trim();
3478
+ if (trimmed === "" || trimmed.startsWith("#"))
3479
+ continue;
3480
+ const eqIdx = trimmed.indexOf("=");
3481
+ if (eqIdx < 1)
3482
+ continue;
3483
+ vars.set(trimmed.substring(0, eqIdx).trim(), trimmed.substring(eqIdx + 1).trim());
3484
+ }
3485
+ for (const [name, def] of Object.entries(manifest.vars)) {
3486
+ if (def.type !== "secret")
3487
+ continue;
3488
+ if (!vars.has(name)) {
3489
+ errors.push({
3490
+ worker: workerName,
3491
+ severity: "error",
3492
+ message: `Missing "${name}" in .dev.vars`,
3493
+ file: ".dev.vars"
3494
+ });
3495
+ }
3496
+ }
3497
+ return errors;
3498
+ }
3499
+ function generateWranglerJsonc(manifest) {
3500
+ const parts = [];
3501
+ parts.push(`{`);
3502
+ parts.push(` "name": "${manifest.name}",`);
3503
+ parts.push(` "main": "src/index.ts",`);
3504
+ const varKeys = Object.keys(manifest.vars);
3505
+ if (varKeys.length > 0) {
3506
+ parts.push(` "vars": {`);
3507
+ for (const [i, name] of varKeys.entries()) {
3508
+ const def = manifest.vars[name];
3509
+ const comma = i < varKeys.length - 1 ? "," : "";
3510
+ const val = def.type === "secret" ? "__SECRET__" : JSON.stringify(def.default ?? "");
3511
+ parts.push(` "${name}": ${val}${comma}`);
3512
+ }
3513
+ parts.push(` },`);
3514
+ }
3515
+ if (manifest.services.length > 0) {
3516
+ parts.push(` "services": [`);
3517
+ for (const [i, svc] of manifest.services.entries()) {
3518
+ const comma = i < manifest.services.length - 1 ? "," : "";
3519
+ parts.push(` { "binding": "${svc.binding}", "service": "${svc.service}" }${comma}`);
3520
+ }
3521
+ parts.push(` ],`);
3522
+ }
3523
+ const infra = manifest.infrastructure;
3524
+ if (infra.kv && infra.kv.length > 0) {
3525
+ parts.push(` "kv_namespaces": [`);
3526
+ for (const [i, binding] of infra.kv.entries()) {
3527
+ const comma = i < infra.kv.length - 1 ? "," : "";
3528
+ parts.push(` { "binding": "${binding.binding}", "id": "" }${comma}`);
3529
+ }
3530
+ parts.push(` ],`);
3531
+ }
3532
+ if (infra.d1 && infra.d1.length > 0) {
3533
+ parts.push(` "d1_databases": [`);
3534
+ for (const [i, binding] of infra.d1.entries()) {
3535
+ const comma = i < infra.d1.length - 1 ? "," : "";
3536
+ parts.push(` { "binding": "${binding.binding}", "database_name": "${binding.database}", "database_id": "" }${comma}`);
3537
+ }
3538
+ parts.push(` ],`);
3539
+ }
3540
+ if (infra.r2 && infra.r2.length > 0) {
3541
+ parts.push(` "r2_buckets": [`);
3542
+ for (const [i, binding] of infra.r2.entries()) {
3543
+ const comma = i < infra.r2.length - 1 ? "," : "";
3544
+ parts.push(` { "binding": "${binding.binding}", "bucket_name": "${binding.bucket}" }${comma}`);
3545
+ }
3546
+ parts.push(` ],`);
3547
+ }
3548
+ if (infra.ai) {
3549
+ parts.push(` "ai": { "binding": "AI" },`);
3550
+ }
3551
+ if (infra.analyticsEngine) {
3552
+ parts.push(` "analytics_engine_datasets": [{ "binding": "ANALYTICS_ENGINE", "dataset": "hoox-analytics" }],`);
3553
+ }
3554
+ if (infra.vectorize && infra.vectorize.length > 0) {
3555
+ parts.push(` "vectorize": [`);
3556
+ for (const [i, binding] of infra.vectorize.entries()) {
3557
+ const comma = i < infra.vectorize.length - 1 ? "," : "";
3558
+ parts.push(` { "binding": "${binding.binding}", "index_name": "${binding.index}" }${comma}`);
3559
+ }
3560
+ parts.push(` ],`);
3561
+ }
3562
+ if (infra.queues) {
3563
+ if (infra.queues.producer && infra.queues.producer.length > 0) {
3564
+ parts.push(` "queues": { "producers": [`);
3565
+ for (const [i, q] of infra.queues.producer.entries()) {
3566
+ const comma = i < infra.queues.producer.length - 1 ? "," : "";
3567
+ parts.push(` { "queue": "${q}", "binding": "" }${comma}`);
3568
+ }
3569
+ parts.push(` ] },`);
3570
+ }
3571
+ if (infra.queues.consumer && infra.queues.consumer.length > 0) {
3572
+ parts.push(` "queues": { "consumers": [`);
3573
+ for (const [i, q] of infra.queues.consumer.entries()) {
3574
+ const comma = i < infra.queues.consumer.length - 1 ? "," : "";
3575
+ parts.push(` { "queue": "${q}" }${comma}`);
3576
+ }
3577
+ parts.push(` ] },`);
3578
+ }
3579
+ }
3580
+ if (infra.durableObjects && infra.durableObjects.length > 0) {
3581
+ parts.push(` "durable_objects": { "bindings": [`);
3582
+ for (const [i, doBinding] of infra.durableObjects.entries()) {
3583
+ const comma = i < infra.durableObjects.length - 1 ? "," : "";
3584
+ parts.push(` { "name": "${doBinding.name}", "class_name": "${doBinding.className}" }${comma}`);
3585
+ }
3586
+ parts.push(` ] },`);
3587
+ }
3588
+ parts.push(`}`);
3589
+ return parts.join(`
3590
+ `) + `
3591
+ `;
3592
+ }
3593
+ function generateDevVars(manifest) {
3594
+ const lines = [];
3595
+ lines.push(`# ${manifest.name} \u2014 Environment Variables`);
3596
+ lines.push(`# Auto-generated from worker manifest schema`);
3597
+ lines.push(``);
3598
+ for (const [name, def] of Object.entries(manifest.vars)) {
3599
+ if (def.type === "secret") {
3600
+ lines.push(`${name}=placeholder_${name.toLowerCase()}`);
3601
+ }
3602
+ }
3603
+ lines.push(``);
3604
+ return lines.join(`
3605
+ `);
3606
+ }
3607
+ function validateAll(workerName, manifest, files) {
3608
+ const errors = [
3609
+ ...validateWranglerJsonc(workerName, manifest, files.wranglerJsonc),
3610
+ ...validateRootSecrets(workerName, manifest, files.rootWranglerJsonc),
3611
+ ...validateDevVars(workerName, manifest, files.devVars)
3612
+ ];
3613
+ return {
3614
+ worker: workerName,
3615
+ passed: errors.filter((e) => e.severity === "error").length === 0,
3616
+ errors
3617
+ };
3618
+ }
3619
+ // src/stores/ui-store.ts
3620
+ import { create } from "zustand";
3621
+ import { immer } from "zustand/middleware/immer";
3622
+ var DEFAULT_VIEW = "dashboard";
3623
+ var initialState = {
3624
+ activeView: DEFAULT_VIEW,
3625
+ sidebarExpanded: true,
3626
+ modal: null,
3627
+ commandPaletteOpen: false,
3628
+ previousView: null
3629
+ };
3630
+ var useUIStore = create()(immer((set) => ({
3631
+ ...initialState,
3632
+ setView: (view) => set((state) => {
3633
+ if (state.activeView !== view) {
3634
+ state.previousView = state.activeView;
3635
+ }
3636
+ state.activeView = view;
3637
+ state.commandPaletteOpen = false;
3638
+ }),
3639
+ toggleSidebar: () => set((state) => {
3640
+ state.sidebarExpanded = !state.sidebarExpanded;
3641
+ }),
3642
+ openPalette: () => set((state) => {
3643
+ state.commandPaletteOpen = true;
3644
+ }),
3645
+ closePalette: () => set((state) => {
3646
+ state.commandPaletteOpen = false;
3647
+ }),
3648
+ showModal: (modal) => set((state) => {
3649
+ state.modal = modal;
3650
+ }),
3651
+ dismissModal: () => set((state) => {
3652
+ state.modal = null;
3653
+ }),
3654
+ goBack: () => set((state) => {
3655
+ if (state.previousView) {
3656
+ state.activeView = state.previousView;
3657
+ state.previousView = null;
3658
+ }
3659
+ })
3660
+ })));
3661
+
3662
+ // src/stores/service-store.ts
3663
+ import { create as create2 } from "zustand";
3664
+ import { immer as immer2 } from "zustand/middleware/immer";
3665
+ function summarizeCliError(details) {
3666
+ const raw = details.stderr.trim() || details.stdout.trim() || details.command || "Unknown CLI error";
3667
+ const firstLine = raw.split(`
3668
+ `, 1)[0] ?? "";
3669
+ const trimmed = firstLine.slice(0, 120).trimEnd();
3670
+ return trimmed.length > 0 ? trimmed : details.command;
3671
+ }
3672
+ var BACKOFF_SEQUENCE = [1000, 2000, 4000, 8000, 16000];
3673
+ var MAX_RETRIES2 = 5;
3674
+ function ringPush(arr, item, max) {
3675
+ const next = [...arr, item];
3676
+ return next.length > max ? next.slice(next.length - max) : next;
3677
+ }
3678
+ var tradeStreamAbort = null;
3679
+ var logStreamAbort = null;
3680
+ var fetchWorkersGeneration = 0;
3681
+ var MAX_TRADES = 500;
3682
+ var MAX_ALERTS = 100;
3683
+ var MAX_LOGS = 1000;
3684
+ var initialState2 = {
3685
+ workers: [],
3686
+ tradeStream: [],
3687
+ alerts: [],
3688
+ logs: [],
3689
+ metrics: null,
3690
+ connectionStatus: "offline",
3691
+ lastUpdated: 0,
3692
+ selectedWorkerId: null,
3693
+ retryCount: 0,
3694
+ lastError: null,
3695
+ lastErrorDetails: null,
3696
+ lastSuccessfulFetch: 0,
3697
+ reconnectDelay: 0,
3698
+ disconnectedAt: null
3699
+ };
3700
+ function getBackoffDelay(retryCount) {
3701
+ if (retryCount <= 0)
3702
+ return 0;
3703
+ const index = Math.min(retryCount - 1, BACKOFF_SEQUENCE.length - 1);
3704
+ return BACKOFF_SEQUENCE[index];
3705
+ }
3706
+ var useServiceStore = create2()(immer2((set, get) => ({
3707
+ ...initialState2,
3708
+ fetchWorkers: async () => {
3709
+ const gen = ++fetchWorkersGeneration;
3710
+ const { hooxFetch: hooxFetch2 } = await Promise.resolve().then(() => (init_api_client(), exports_api_client));
3711
+ try {
3712
+ const data = await hooxFetch2("/v1/workers");
3713
+ if (gen !== fetchWorkersGeneration)
3714
+ return;
3715
+ set((state) => {
3716
+ state.workers = data;
3717
+ state.lastUpdated = Date.now();
3718
+ state.lastSuccessfulFetch = Date.now();
3719
+ if (state.connectionStatus === "polling" || state.connectionStatus === "reconnecting" || state.connectionStatus === "offline") {
3720
+ state.connectionStatus = "connected";
3721
+ state.retryCount = 0;
3722
+ state.lastError = null;
3723
+ state.lastErrorDetails = null;
3724
+ state.reconnectDelay = 0;
3725
+ state.disconnectedAt = null;
3726
+ }
3727
+ });
3728
+ } catch (error) {
3729
+ if (gen !== fetchWorkersGeneration)
3730
+ return;
3731
+ const msg = error instanceof Error ? error.message : "Unknown fetch error";
3732
+ get().addAlert({
3733
+ id: crypto.randomUUID(),
3734
+ type: "connection",
3735
+ severity: "error",
3736
+ message: msg,
3737
+ timestamp: Date.now(),
3738
+ acknowledged: false
3739
+ });
3740
+ set((state) => {
3741
+ state.lastError = msg;
3742
+ state.disconnectedAt = state.disconnectedAt ?? Date.now();
3743
+ if (state.connectionStatus === "connected" || state.connectionStatus === "polling") {
3744
+ state.connectionStatus = "reconnecting";
3745
+ state.retryCount = state.retryCount + 1;
3746
+ if (state.retryCount >= MAX_RETRIES2) {
3747
+ state.connectionStatus = "offline";
3748
+ state.lastError = `Connection lost after ${MAX_RETRIES2} retries`;
3749
+ } else {
3750
+ state.reconnectDelay = getBackoffDelay(state.retryCount);
3751
+ }
3752
+ } else if (state.connectionStatus === "reconnecting") {
3753
+ state.retryCount = state.retryCount + 1;
3754
+ if (state.retryCount >= MAX_RETRIES2) {
3755
+ state.connectionStatus = "offline";
3756
+ state.lastError = `Connection lost after ${MAX_RETRIES2} retries`;
3757
+ state.reconnectDelay = 0;
3758
+ } else {
3759
+ state.reconnectDelay = getBackoffDelay(state.retryCount);
3760
+ }
3761
+ }
3762
+ });
3763
+ }
3764
+ },
3765
+ streamTrades: async () => {
3766
+ const { subscribeSSE: subscribeSSE2 } = await Promise.resolve().then(() => (init_sse(), exports_sse));
3767
+ tradeStreamAbort?.();
3768
+ tradeStreamAbort = null;
3769
+ try {
3770
+ const sub = subscribeSSE2("/v1/trades/stream", (trade) => {
3771
+ get().pushTrade(trade);
3772
+ });
3773
+ const resolved = await Promise.resolve(sub);
3774
+ tradeStreamAbort = resolved.abort.bind(resolved);
3775
+ } catch (error) {
3776
+ const msg = error instanceof Error ? error.message : "SSE trade stream failed";
3777
+ get().addAlert({
3778
+ id: crypto.randomUUID(),
3779
+ type: "connection",
3780
+ severity: "error",
3781
+ message: msg,
3782
+ timestamp: Date.now(),
3783
+ acknowledged: false
3784
+ });
3785
+ get().handleConnectionFailure(msg);
3786
+ }
3787
+ },
3788
+ streamLogs: async () => {
3789
+ const { subscribeSSE: subscribeSSE2 } = await Promise.resolve().then(() => (init_sse(), exports_sse));
3790
+ logStreamAbort?.();
3791
+ logStreamAbort = null;
3792
+ try {
3793
+ const sub = subscribeSSE2("/v1/logs/stream", (log) => {
3794
+ get().pushLog(log);
3795
+ });
3796
+ const resolved = await Promise.resolve(sub);
3797
+ logStreamAbort = resolved.abort.bind(resolved);
3798
+ } catch (error) {
3799
+ const msg = error instanceof Error ? error.message : "SSE log stream failed";
3800
+ get().addAlert({
3801
+ id: crypto.randomUUID(),
3802
+ type: "connection",
3803
+ severity: "error",
3804
+ message: msg,
3805
+ timestamp: Date.now(),
3806
+ acknowledged: false
3807
+ });
3808
+ get().handleConnectionFailure(msg);
3809
+ }
3810
+ },
3811
+ stopStreams: () => {
3812
+ tradeStreamAbort?.();
3813
+ logStreamAbort?.();
3814
+ tradeStreamAbort = null;
3815
+ logStreamAbort = null;
3816
+ },
3817
+ addAlert: (alert) => set((state) => {
3818
+ state.alerts = ringPush(state.alerts, alert, MAX_ALERTS);
3819
+ }),
3820
+ addAlerts: (alerts) => set((state) => {
3821
+ const merged = [...state.alerts, ...alerts];
3822
+ state.alerts = merged.length > MAX_ALERTS ? merged.slice(merged.length - MAX_ALERTS) : merged;
3823
+ }),
3824
+ setConnectionStatus: (status) => set((state) => {
3825
+ if (status === "connected") {
3826
+ state.retryCount = 0;
3827
+ state.lastError = null;
3828
+ state.lastErrorDetails = null;
3829
+ state.reconnectDelay = 0;
3830
+ state.disconnectedAt = null;
3831
+ }
3832
+ if (status === "offline" || status === "reconnecting") {
3833
+ state.disconnectedAt = state.disconnectedAt ?? Date.now();
3834
+ }
3835
+ state.connectionStatus = status;
3836
+ }),
3837
+ selectWorker: (id) => set((state) => {
3838
+ state.selectedWorkerId = id;
3839
+ }),
3840
+ setWorkers: (workers) => set((state) => {
3841
+ state.workers = workers;
3842
+ state.lastUpdated = Date.now();
3843
+ }),
3844
+ pushTrade: (trade) => set((state) => {
3845
+ state.tradeStream = ringPush(state.tradeStream, trade, MAX_TRADES);
3846
+ }),
3847
+ pushLog: (log) => set((state) => {
3848
+ state.logs = ringPush(state.logs, log, MAX_LOGS);
3849
+ }),
3850
+ setMetrics: (metrics) => set((state) => {
3851
+ state.metrics = metrics;
3852
+ state.lastUpdated = metrics.lastUpdated;
3853
+ }),
3854
+ handleConnectionSuccess: () => set((state) => {
3855
+ state.lastSuccessfulFetch = Date.now();
3856
+ state.retryCount = 0;
3857
+ state.lastError = null;
3858
+ state.lastErrorDetails = null;
3859
+ state.reconnectDelay = 0;
3860
+ state.disconnectedAt = null;
3861
+ state.connectionStatus = "connected";
3862
+ }),
3863
+ handleConnectionFailure: (errorMessage) => set((state) => {
3864
+ state.lastError = errorMessage;
3865
+ state.disconnectedAt = state.disconnectedAt ?? Date.now();
3866
+ if (state.connectionStatus === "connected" || state.connectionStatus === "polling") {
3867
+ state.connectionStatus = "reconnecting";
3868
+ state.retryCount = 1;
3869
+ state.reconnectDelay = getBackoffDelay(1);
3870
+ } else if (state.connectionStatus === "reconnecting") {
3871
+ state.retryCount = state.retryCount + 1;
3872
+ if (state.retryCount >= MAX_RETRIES2) {
3873
+ state.connectionStatus = "offline";
3874
+ state.lastError = `Connection lost after ${MAX_RETRIES2} retries`;
3875
+ state.reconnectDelay = 0;
3876
+ } else {
3877
+ state.reconnectDelay = getBackoffDelay(state.retryCount);
3878
+ }
3879
+ }
3880
+ }),
3881
+ resetRetries: () => set((state) => {
3882
+ state.retryCount = 0;
3883
+ state.lastError = null;
3884
+ state.lastErrorDetails = null;
3885
+ state.reconnectDelay = 0;
3886
+ state.disconnectedAt = null;
3887
+ }),
3888
+ forceRetry: () => set((state) => {
3889
+ if (state.connectionStatus === "offline") {
3890
+ state.connectionStatus = "polling";
3891
+ state.retryCount = 0;
3892
+ state.lastError = null;
3893
+ state.lastErrorDetails = null;
3894
+ state.reconnectDelay = 0;
3895
+ }
3896
+ }),
3897
+ setLastErrorDetails: (details) => set((state) => {
3898
+ state.lastErrorDetails = details;
3899
+ state.lastError = details === null ? null : summarizeCliError(details);
3900
+ }),
3901
+ clearError: () => set((state) => {
3902
+ state.lastError = null;
3903
+ state.lastErrorDetails = null;
3904
+ }),
3905
+ addCliErrorAlert: (details) => set((state) => {
3906
+ state.lastErrorDetails = details;
3907
+ state.lastError = summarizeCliError(details);
3908
+ const summary = summarizeCliError(details);
3909
+ state.alerts = ringPush(state.alerts, {
3910
+ id: crypto.randomUUID(),
3911
+ type: "connection",
3912
+ severity: "error",
3913
+ message: `CLI failure: ${summary}`,
3914
+ timestamp: Date.now(),
3915
+ acknowledged: false
3916
+ }, MAX_ALERTS);
3917
+ })
3918
+ })));
3919
+
3920
+ // src/stores/config-store.ts
3921
+ import { create as create3 } from "zustand";
3922
+ import { immer as immer3 } from "zustand/middleware/immer";
3923
+ import { persist, createJSONStorage } from "zustand/middleware";
3924
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync2, existsSync as existsSync4 } from "fs";
3925
+ import { homedir as homedir3 } from "os";
3926
+ import { join as join4 } from "path";
3927
+ var bunConfigStorage = {
3928
+ getItem: async (_name) => {
3929
+ try {
3930
+ const file = Bun.file(join4(homedir3(), ".hoox", "config.json"));
3931
+ const exists = await file.exists();
3932
+ if (!exists)
3933
+ return null;
3934
+ return await file.text();
3935
+ } catch {
3936
+ return null;
3937
+ }
3938
+ },
3939
+ setItem: async (_name, value) => {
3940
+ const dir = join4(homedir3(), ".hoox");
3941
+ if (!existsSync4(dir)) {
3942
+ mkdirSync2(dir, { recursive: true, mode: HOOX_DIR_MODE });
3943
+ }
3944
+ try {
3945
+ chmodSync2(dir, HOOX_DIR_MODE);
3946
+ } catch {}
3947
+ const filePath = join4(dir, "config.json");
3948
+ await Bun.write(filePath, value);
3949
+ try {
3950
+ chmodSync2(filePath, HOOX_CONFIG_FILE_MODE);
3951
+ } catch {}
3952
+ },
3953
+ removeItem: async (_name) => {}
3954
+ };
3955
+ var DEFAULT_SHORTCUTS = {
3956
+ "command-palette": "Ctrl+P",
3957
+ "toggle-sidebar": "Ctrl+B",
3958
+ "force-refresh": "Ctrl+R",
3959
+ quit: "Ctrl+Q",
3960
+ "view-1": "Ctrl+1",
3961
+ "view-2": "Ctrl+2",
3962
+ "view-3": "Ctrl+3",
3963
+ "view-4": "Ctrl+4",
3964
+ "view-5": "Ctrl+5",
3965
+ "view-6": "Ctrl+6",
3966
+ "view-7": "Ctrl+7",
3967
+ "view-8": "Ctrl+8",
3968
+ "view-9": "Ctrl+9",
3969
+ "view-10": "Ctrl+0"
3970
+ };
3971
+ var DEFAULT_LOG_FILTER = {
3972
+ levels: ["info", "warn", "error"],
3973
+ workers: [],
3974
+ searchText: ""
3975
+ };
3976
+ var DEFAULT_NOTIFICATIONS = {
3977
+ alerts: true,
3978
+ trades: false,
3979
+ debug: false,
3980
+ system: true
3981
+ };
3982
+ var defaults = {
3983
+ theme: "dark",
3984
+ refreshIntervalMs: 500,
3985
+ defaultView: "dashboard",
3986
+ activeExchanges: [],
3987
+ keyboardShortcuts: { ...DEFAULT_SHORTCUTS },
3988
+ logFilters: { ...DEFAULT_LOG_FILTER },
3989
+ notifications: { ...DEFAULT_NOTIFICATIONS },
3990
+ soundEnabled: false
3991
+ };
3992
+ var useConfigStore = create3()(persist(immer3((set) => ({
3993
+ ...defaults,
3994
+ updateConfig: (partial) => set((state) => {
3995
+ if (partial.theme !== undefined)
3996
+ state.theme = partial.theme;
3997
+ if (partial.refreshIntervalMs !== undefined)
3998
+ state.refreshIntervalMs = partial.refreshIntervalMs;
3999
+ if (partial.defaultView !== undefined)
4000
+ state.defaultView = partial.defaultView;
4001
+ if (partial.activeExchanges !== undefined)
4002
+ state.activeExchanges = partial.activeExchanges;
4003
+ if (partial.keyboardShortcuts !== undefined)
4004
+ state.keyboardShortcuts = partial.keyboardShortcuts;
4005
+ if (partial.logFilters !== undefined)
4006
+ state.logFilters = partial.logFilters;
4007
+ if (partial.notifications !== undefined)
4008
+ state.notifications = partial.notifications;
4009
+ if (partial.soundEnabled !== undefined)
4010
+ state.soundEnabled = partial.soundEnabled;
4011
+ }),
4012
+ resetDefaults: () => set((state) => {
4013
+ Object.assign(state, defaults);
4014
+ }),
4015
+ setShortcut: (action, key) => set((state) => {
4016
+ state.keyboardShortcuts[action] = key;
4017
+ }),
4018
+ toggleNotification: (channel) => set((state) => {
4019
+ state.notifications[channel] = !state.notifications[channel];
4020
+ })
4021
+ })), {
4022
+ name: "hoox-config",
4023
+ storage: createJSONStorage(() => bunConfigStorage),
4024
+ partialize: (state) => ({
4025
+ theme: state.theme,
4026
+ refreshIntervalMs: state.refreshIntervalMs,
4027
+ defaultView: state.defaultView,
4028
+ activeExchanges: state.activeExchanges,
4029
+ keyboardShortcuts: state.keyboardShortcuts,
4030
+ logFilters: state.logFilters,
4031
+ notifications: state.notifications,
4032
+ soundEnabled: state.soundEnabled
4033
+ }),
4034
+ merge: (persisted, current) => ({
4035
+ ...current,
4036
+ ...persisted
4037
+ })
4038
+ }));
4039
+
4040
+ // src/index.ts
4041
+ init_api_client();
4042
+ init_sse();
4043
+ init_operator_transport();
4044
+ export {
4045
+ writeSecureFileSync,
4046
+ writeConfigSync,
4047
+ writeConfig,
4048
+ validateWranglerJsonc,
4049
+ validateRootSecrets,
4050
+ validateDevVars,
4051
+ validateConfig,
4052
+ validateAll,
4053
+ useUIStore,
4054
+ useServiceStore,
4055
+ useConfigStore,
4056
+ trackAnalytics,
4057
+ toError,
4058
+ subscribeSSE,
4059
+ stripJsonc,
4060
+ setAgentConfigEmbeddedValue,
4061
+ serviceFetch,
4062
+ serializeState,
4063
+ serializeAgentConfigForKv,
4064
+ saveSession,
4065
+ restoreSession,
4066
+ resolveOperatorTransportProfile,
4067
+ resolveInternalAuthKey,
4068
+ resolveHooxRuntimeRoot,
4069
+ resolveHooxPath,
4070
+ resolveDependencies,
4071
+ readConfigSync,
4072
+ readConfig,
4073
+ parseDashboardManifest,
4074
+ parseAgentConfigJson,
4075
+ operatorUrl,
4076
+ logKvTimestamp,
4077
+ loadDashboardManifestsFromRoot,
4078
+ loadDashboardKvManifestFromRoot,
4079
+ kvManifestFromDashboardManifests,
4080
+ isWithinHooxHome,
4081
+ isUnixModeGroupOrWorldAccessible,
4082
+ isHooxSetupRoot,
4083
+ isDashboardSectionFlatKv,
4084
+ isDashboardSectionEditable,
4085
+ isConfigWorldOrGroupReadable,
4086
+ isAgentConfigSection,
4087
+ isAgentConfigEmbeddedField,
4088
+ hooxFetch,
4089
+ healthCheck,
4090
+ headersToObject,
4091
+ hasOperatorClientCredentials,
4092
+ getTuiEntryCandidates,
4093
+ getRelativeHooxPath,
4094
+ getHooxWranglerPath,
4095
+ getHooxStatePath,
4096
+ getHooxRepoPath,
4097
+ getHooxHome,
4098
+ getHooxDataDir,
4099
+ getHooxConfigDir,
4100
+ getAgentConfigEmbeddedValue,
4101
+ generateWranglerJsonc,
4102
+ generateDevVars,
4103
+ formatUptime,
4104
+ formatTimestamp,
4105
+ formatRequests,
4106
+ formatRelativeTime as formatRelativeTimeFromTime,
4107
+ formatRelativeTime2 as formatRelativeTime,
4108
+ formatPercent,
4109
+ formatNumber,
4110
+ formatMemory,
4111
+ formatLatency,
4112
+ formatDurationCompact,
4113
+ formatDuration2 as formatDuration,
4114
+ formatCurrency,
4115
+ formatCpu,
4116
+ formatConfigPermissionWarning,
4117
+ formatCompactCurrency,
4118
+ findHooxSetupRoot,
4119
+ expandAgentConfigToFieldMap,
4120
+ ensureHooxDirSecure,
4121
+ deserializeState,
4122
+ dashboardWorkerDir,
4123
+ createSuccessResponse,
4124
+ createQueueHandler,
4125
+ createMockVectorizeIndex,
4126
+ createMockSecretsStore,
4127
+ createMockR2,
4128
+ createMockQueue,
4129
+ createMockKV,
4130
+ createMockFetcher,
4131
+ createMockExecutionContext,
4132
+ createMockEnv,
4133
+ createMockD1,
4134
+ createMockAnalyticsEngineDataset,
4135
+ createMockAnalyticsEngine,
4136
+ createMockAi,
4137
+ createJsonResponse,
4138
+ createErrorResponse,
4139
+ createCronHandler,
4140
+ buildOperatorAuthHeaders,
4141
+ buildDashboardKvKey,
4142
+ authenticatedServiceFetch,
4143
+ applyAgentConfigFieldUpdates,
4144
+ WorkerStatusColor,
4145
+ WorkerAPIError,
4146
+ WizardEngine,
4147
+ WebhookPayloadSchema,
4148
+ WORKER_NAMES,
4149
+ WORKER_MANIFESTS,
4150
+ WORKER_DEPENDENCIES,
4151
+ WIZARD_STATE_PATH,
4152
+ WALLET_EXECUTE_AUTH_KEY_FIELDS,
4153
+ TradeSignalSchema,
4154
+ TradeQueueMessageSchema,
4155
+ TradeActionSchema,
4156
+ TRADE_READ_AUTH_KEY_FIELDS,
4157
+ TRADE_EXECUTE_AUTH_KEY_FIELDS,
4158
+ TRADEMARK_NOTICE,
4159
+ TRADEMARKS,
4160
+ TELEGRAM_ALERT_AUTH_KEY_FIELDS,
4161
+ ServiceAuthError,
4162
+ PositionSchema,
4163
+ PRESETS,
4164
+ LogLevelColor,
4165
+ KVKeys,
4166
+ InternalAuthKeyFields,
4167
+ INTEGRATIONS,
4168
+ HOOX_DIR_MODE,
4169
+ HOOX_CONFIG_FILE_MODE,
4170
+ FULL_LEGAL_NOTICE,
4171
+ ExchangeRouter,
4172
+ Errors,
4173
+ DISCLAIMER_HEADER,
4174
+ DISCLAIMER,
4175
+ DASHBOARD_WORKER_PREFIX,
4176
+ DASHBOARD_WORKER_IDS,
4177
+ DASHBOARD_TRADE_EXECUTE_AUTH_KEY_FIELDS,
4178
+ DASHBOARD_TELEGRAM_ALERT_AUTH_KEY_FIELDS,
4179
+ DASHBOARD_SECTION_PREFIX,
4180
+ DASHBOARD_SECTIONS_UI_SKIP,
4181
+ DASHBOARD_SECTIONS_NOT_FLAT_KV,
4182
+ DASHBOARD_SECTIONS_AGENT_CONFIG,
4183
+ DASHBOARD_FIELD_KV_OVERRIDES,
4184
+ DASHBOARD_D1_READ_AUTH_KEY_FIELDS,
4185
+ D1_WRITE_AUTH_KEY_FIELDS,
4186
+ D1_READ_AUTH_KEY_FIELDS,
4187
+ D1Repository,
4188
+ CoolBracketPalette,
4189
+ ConnectionStatusColor,
4190
+ Colors,
4191
+ COPYRIGHT,
4192
+ CALLED_BY,
4193
+ BaseExchangeClient2 as BaseExchangeClient,
4194
+ BalanceSchema,
4195
+ AlertSeverityColor,
4196
+ AGENT_CONFIG_KV_KEY,
4197
+ AGENT_CONFIG_EMBEDDED_FIELDS
4198
+ };