@jango-blockchained/hoox-shared 1.0.8

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 (115) hide show
  1. package/README.md +27 -0
  2. package/dist/analytics.d.ts +22 -0
  3. package/dist/analytics.d.ts.map +1 -0
  4. package/dist/analytics.js +34 -0
  5. package/dist/api-client.d.ts +34 -0
  6. package/dist/api-client.d.ts.map +1 -0
  7. package/dist/api-client.js +124 -0
  8. package/dist/colors.d.ts +34 -0
  9. package/dist/colors.d.ts.map +1 -0
  10. package/dist/colors.js +42 -0
  11. package/dist/config.d.ts +24 -0
  12. package/dist/config.d.ts.map +1 -0
  13. package/dist/config.js +91 -0
  14. package/dist/d1/index.d.ts +5 -0
  15. package/dist/d1/index.d.ts.map +1 -0
  16. package/dist/d1/index.js +316 -0
  17. package/dist/d1/repository.d.ts +77 -0
  18. package/dist/d1/repository.d.ts.map +1 -0
  19. package/dist/d1/schemas.d.ts +85 -0
  20. package/dist/d1/schemas.d.ts.map +1 -0
  21. package/dist/errors.d.ts +39 -0
  22. package/dist/errors.d.ts.map +1 -0
  23. package/dist/errors.js +106 -0
  24. package/dist/exchange-client.d.ts +84 -0
  25. package/dist/exchange-client.d.ts.map +1 -0
  26. package/dist/exchange-client.js +104 -0
  27. package/dist/format-time.d.ts +24 -0
  28. package/dist/format-time.d.ts.map +1 -0
  29. package/dist/format-time.js +57 -0
  30. package/dist/formatters.d.ts +32 -0
  31. package/dist/formatters.d.ts.map +1 -0
  32. package/dist/formatters.js +155 -0
  33. package/dist/health.d.ts +19 -0
  34. package/dist/health.d.ts.map +1 -0
  35. package/dist/health.js +117 -0
  36. package/dist/index.d.ts +45 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +2581 -0
  39. package/dist/kvKeys.d.ts +72 -0
  40. package/dist/kvKeys.d.ts.map +1 -0
  41. package/dist/kvKeys.js +107 -0
  42. package/dist/kvUtils.d.ts +26 -0
  43. package/dist/kvUtils.d.ts.map +1 -0
  44. package/dist/kvUtils.js +127 -0
  45. package/dist/legal.d.ts +11 -0
  46. package/dist/legal.d.ts.map +1 -0
  47. package/dist/legal.js +40 -0
  48. package/dist/middleware/auth.d.ts +59 -0
  49. package/dist/middleware/auth.d.ts.map +1 -0
  50. package/dist/middleware/cors.d.ts +28 -0
  51. package/dist/middleware/cors.d.ts.map +1 -0
  52. package/dist/middleware/index.d.ts +10 -0
  53. package/dist/middleware/index.d.ts.map +1 -0
  54. package/dist/middleware/index.js +375 -0
  55. package/dist/middleware/logger.d.ts +16 -0
  56. package/dist/middleware/logger.d.ts.map +1 -0
  57. package/dist/middleware/rate-limit.d.ts +24 -0
  58. package/dist/middleware/rate-limit.d.ts.map +1 -0
  59. package/dist/middleware/security-headers.d.ts +39 -0
  60. package/dist/middleware/security-headers.d.ts.map +1 -0
  61. package/dist/middleware/validate.d.ts +21 -0
  62. package/dist/middleware/validate.d.ts.map +1 -0
  63. package/dist/router.d.ts +11 -0
  64. package/dist/router.d.ts.map +1 -0
  65. package/dist/router.js +123 -0
  66. package/dist/schemas/index.d.ts +4 -0
  67. package/dist/schemas/index.d.ts.map +1 -0
  68. package/dist/schemas/index.js +656 -0
  69. package/dist/schemas/registry.d.ts +8 -0
  70. package/dist/schemas/registry.d.ts.map +1 -0
  71. package/dist/schemas/types.d.ts +79 -0
  72. package/dist/schemas/types.d.ts.map +1 -0
  73. package/dist/schemas/validators.d.ts +35 -0
  74. package/dist/schemas/validators.d.ts.map +1 -0
  75. package/dist/service-bindings.d.ts +32 -0
  76. package/dist/service-bindings.d.ts.map +1 -0
  77. package/dist/service-bindings.js +31 -0
  78. package/dist/session.d.ts +46 -0
  79. package/dist/session.d.ts.map +1 -0
  80. package/dist/session.js +79 -0
  81. package/dist/sse.d.ts +26 -0
  82. package/dist/sse.d.ts.map +1 -0
  83. package/dist/sse.js +91 -0
  84. package/dist/stores/config-store.d.ts +87 -0
  85. package/dist/stores/config-store.d.ts.map +1 -0
  86. package/dist/stores/config-store.js +132 -0
  87. package/dist/stores/service-store.d.ts +206 -0
  88. package/dist/stores/service-store.d.ts.map +1 -0
  89. package/dist/stores/service-store.js +359 -0
  90. package/dist/stores/ui-store.d.ts +63 -0
  91. package/dist/stores/ui-store.d.ts.map +1 -0
  92. package/dist/stores/ui-store.js +62 -0
  93. package/dist/types/errors.d.ts +16 -0
  94. package/dist/types/errors.d.ts.map +1 -0
  95. package/dist/types/errors.js +1 -0
  96. package/dist/types/router.d.ts +22 -0
  97. package/dist/types/router.d.ts.map +1 -0
  98. package/dist/types/router.js +1 -0
  99. package/dist/types.d.ts +317 -0
  100. package/dist/types.d.ts.map +1 -0
  101. package/dist/types.js +70 -0
  102. package/dist/wizard/engine.d.ts +42 -0
  103. package/dist/wizard/engine.d.ts.map +1 -0
  104. package/dist/wizard/index.d.ts +7 -0
  105. package/dist/wizard/index.d.ts.map +1 -0
  106. package/dist/wizard/index.js +519 -0
  107. package/dist/wizard/persistence.d.ts +14 -0
  108. package/dist/wizard/persistence.d.ts.map +1 -0
  109. package/dist/wizard/presets.d.ts +34 -0
  110. package/dist/wizard/presets.d.ts.map +1 -0
  111. package/dist/wizard/provisioner.d.ts +11 -0
  112. package/dist/wizard/provisioner.d.ts.map +1 -0
  113. package/dist/wizard/types.d.ts +72 -0
  114. package/dist/wizard/types.d.ts.map +1 -0
  115. package/package.json +83 -0
package/dist/index.js ADDED
@@ -0,0 +1,2581 @@
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/api-client.ts
19
+ var exports_api_client = {};
20
+ __export(exports_api_client, {
21
+ hooxFetch: () => hooxFetch,
22
+ WorkerAPIError: () => WorkerAPIError
23
+ });
24
+ function isRetryableStatus(status) {
25
+ if (status === 401 || status === 403 || status === 429)
26
+ return false;
27
+ if (status >= 500)
28
+ return true;
29
+ if (status === 408)
30
+ return true;
31
+ return false;
32
+ }
33
+ function isNetworkError(error) {
34
+ if (error instanceof Error) {
35
+ const msg = error.message.toLowerCase();
36
+ 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");
37
+ }
38
+ return false;
39
+ }
40
+ function sleep(ms) {
41
+ return new Promise((resolve) => setTimeout(resolve, ms));
42
+ }
43
+ async function hooxFetch(path, options) {
44
+ let lastError;
45
+ for (let attempt = 0;attempt <= MAX_RETRIES; attempt++) {
46
+ const controller = new AbortController;
47
+ const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
48
+ try {
49
+ const url = `${API_BASE}${path}`;
50
+ const headers = {
51
+ "Content-Type": "application/json",
52
+ ...API_TOKEN ? { Authorization: `Bearer ${API_TOKEN}` } : {},
53
+ ...options?.headers
54
+ };
55
+ const response = await fetch(url, {
56
+ ...options,
57
+ headers,
58
+ signal: controller.signal
59
+ });
60
+ if (response.status === 429) {
61
+ throw new WorkerAPIError("API rate limited \u2014 backing off", {
62
+ status: 429,
63
+ retryable: false
64
+ });
65
+ }
66
+ if (response.status === 401 || response.status === 403) {
67
+ throw new WorkerAPIError(`Authentication failed (HTTP ${response.status})`, { status: response.status, retryable: false });
68
+ }
69
+ if (!response.ok) {
70
+ if (attempt < MAX_RETRIES && isRetryableStatus(response.status)) {
71
+ const delay = Math.min(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);
72
+ await sleep(delay);
73
+ continue;
74
+ }
75
+ throw new WorkerAPIError(`HTTP ${response.status}: ${response.statusText}`, {
76
+ status: response.status,
77
+ retryable: isRetryableStatus(response.status)
78
+ });
79
+ }
80
+ return await response.json();
81
+ } catch (error) {
82
+ lastError = error;
83
+ if (error instanceof WorkerAPIError && !error.retryable) {
84
+ throw error;
85
+ }
86
+ if (attempt < MAX_RETRIES && isNetworkError(error)) {
87
+ const delay = Math.min(BASE_RETRY_DELAY_MS * Math.pow(2, attempt), MAX_RETRY_DELAY_MS);
88
+ await sleep(delay);
89
+ continue;
90
+ }
91
+ if (!(error instanceof WorkerAPIError)) {
92
+ throw new WorkerAPIError(`Network request failed: ${error.message}`, { cause: error, retryable: false, status: 0 });
93
+ }
94
+ throw error;
95
+ } finally {
96
+ clearTimeout(timeoutId);
97
+ }
98
+ }
99
+ throw new WorkerAPIError("Max retries exhausted", {
100
+ cause: lastError,
101
+ retryable: false
102
+ });
103
+ }
104
+ var API_BASE, API_TOKEN, FETCH_TIMEOUT_MS = 5000, MAX_RETRIES = 3, BASE_RETRY_DELAY_MS = 1000, MAX_RETRY_DELAY_MS = 16000, WorkerAPIError;
105
+ var init_api_client = __esm(() => {
106
+ API_BASE = process.env.HOOX_API_URL || "http://localhost:8787";
107
+ API_TOKEN = process.env.HOOX_API_TOKEN || "";
108
+ WorkerAPIError = class WorkerAPIError extends Error {
109
+ status;
110
+ retryable;
111
+ constructor(message, options) {
112
+ super(message, { cause: options?.cause });
113
+ this.name = "WorkerAPIError";
114
+ this.status = options?.status ?? 0;
115
+ this.retryable = options?.retryable ?? false;
116
+ }
117
+ };
118
+ });
119
+
120
+ // src/sse.ts
121
+ var exports_sse = {};
122
+ __export(exports_sse, {
123
+ subscribeSSE: () => subscribeSSE
124
+ });
125
+ function subscribeSSE(path, callback, onStatus) {
126
+ let aborted = false;
127
+ const run = async () => {
128
+ let attempt = 0;
129
+ while (!aborted && attempt < MAX_RECONNECT_ATTEMPTS) {
130
+ try {
131
+ const url = `${API_BASE2}${path}`;
132
+ const response = await fetch(url, {
133
+ headers: {
134
+ Accept: "text/event-stream",
135
+ ...API_TOKEN2 ? { Authorization: `Bearer ${API_TOKEN2}` } : {}
136
+ }
137
+ });
138
+ if (!response.ok || !response.body) {
139
+ throw new Error(`SSE connection failed: HTTP ${response.status}`);
140
+ }
141
+ attempt = 0;
142
+ onStatus?.("connected");
143
+ const reader = response.body.getReader();
144
+ const decoder = new TextDecoder;
145
+ let buffer = "";
146
+ while (!aborted) {
147
+ const { done, value } = await reader.read();
148
+ if (done)
149
+ break;
150
+ buffer += decoder.decode(value, { stream: true });
151
+ const lines = buffer.split(`
152
+ `);
153
+ buffer = lines.pop() || "";
154
+ for (const line of lines) {
155
+ const trimmed = line.trim();
156
+ if (trimmed.startsWith("data: ")) {
157
+ try {
158
+ const parsed = JSON.parse(trimmed.slice(6));
159
+ callback(parsed);
160
+ } catch {}
161
+ }
162
+ }
163
+ }
164
+ } catch (error) {
165
+ if (aborted)
166
+ break;
167
+ onStatus?.("reconnecting");
168
+ const delay = Math.min(RECONNECT_BASE_MS * Math.pow(2, attempt), RECONNECT_MAX_MS);
169
+ attempt++;
170
+ await new Promise((resolve) => setTimeout(resolve, delay));
171
+ }
172
+ }
173
+ if (!aborted) {
174
+ onStatus?.("disconnected");
175
+ }
176
+ };
177
+ run();
178
+ return {
179
+ abort: () => {
180
+ aborted = true;
181
+ }
182
+ };
183
+ }
184
+ var API_BASE2, API_TOKEN2, RECONNECT_BASE_MS = 1000, RECONNECT_MAX_MS = 16000, MAX_RECONNECT_ATTEMPTS = 10;
185
+ var init_sse = __esm(() => {
186
+ API_BASE2 = process.env.HOOX_API_URL || "http://localhost:8787";
187
+ API_TOKEN2 = process.env.HOOX_API_TOKEN || "";
188
+ });
189
+
190
+ // src/analytics.ts
191
+ async function trackAnalytics(env, endpoint, body) {
192
+ if (!env.ANALYTICS_SERVICE)
193
+ return;
194
+ try {
195
+ await env.ANALYTICS_SERVICE.fetch(new Request(`http://localhost${endpoint}`, {
196
+ method: "POST",
197
+ headers: { "Content-Type": "application/json" },
198
+ body: JSON.stringify(body)
199
+ }));
200
+ } catch (e) {
201
+ console.error("Analytics tracking failed:", e);
202
+ }
203
+ }
204
+
205
+ // src/colors.ts
206
+ var Colors = {
207
+ background: "#0D1117",
208
+ foreground: "#EEEEEE",
209
+ card: "#1C1C1F",
210
+ border: "#484848",
211
+ muted: "#A0A0A0",
212
+ "muted-foreground": "#6E6E6E",
213
+ dim: "#3B3B3D",
214
+ accent: "#E8780A",
215
+ "accent-dim": "#B85E08",
216
+ success: "#00FF88",
217
+ warning: "#FFAA00",
218
+ error: "#FF4444",
219
+ info: "#4488FF",
220
+ text: "#EEEEEE",
221
+ "text-muted": "#A0A0A0",
222
+ "text-dim": "#6E6E6E",
223
+ panel: "#1C1C1F",
224
+ divider: "#484848",
225
+ highlight: "#E8780A"
226
+ };
227
+
228
+ // src/config.ts
229
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
230
+ import { join } from "path";
231
+ import { homedir } from "os";
232
+ var HOX_DIR = join(homedir(), ".hoox");
233
+ var CONFIG_PATH = join(HOX_DIR, "config.json");
234
+ var DEFAULT_CONFIG = {
235
+ apiUrl: "http://localhost:8787",
236
+ apiToken: "",
237
+ refreshIntervalMs: 500,
238
+ theme: "dark",
239
+ activeExchanges: ["binance", "bybit", "mexc"],
240
+ notifications: {
241
+ alerts: true,
242
+ trades: true,
243
+ debug: false,
244
+ system: true
245
+ },
246
+ soundEnabled: true,
247
+ defaultView: "dashboard"
248
+ };
249
+ function readConfigSync() {
250
+ const envConfig = {
251
+ apiUrl: process.env.HOOX_API_URL,
252
+ apiToken: process.env.HOOX_API_TOKEN
253
+ };
254
+ let fileConfig = {};
255
+ try {
256
+ if (existsSync(CONFIG_PATH)) {
257
+ fileConfig = JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
258
+ }
259
+ } catch {}
260
+ return {
261
+ ...DEFAULT_CONFIG,
262
+ ...fileConfig,
263
+ ...Object.fromEntries(Object.entries(envConfig).filter(([_, v]) => v !== undefined))
264
+ };
265
+ }
266
+ async function readConfig() {
267
+ return readConfigSync();
268
+ }
269
+ function writeConfigSync(config) {
270
+ try {
271
+ if (!existsSync(HOX_DIR)) {
272
+ mkdirSync(HOX_DIR, { recursive: true });
273
+ }
274
+ writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), "utf-8");
275
+ } catch (err) {
276
+ console.error("Failed to write config:", err);
277
+ }
278
+ }
279
+ async function writeConfig(config) {
280
+ writeConfigSync(config);
281
+ }
282
+ function validateConfig(config) {
283
+ const errors = [];
284
+ if (config.apiUrl && !config.apiUrl.startsWith("http")) {
285
+ errors.push("apiUrl must start with http:// or https://");
286
+ }
287
+ if (config.refreshIntervalMs !== undefined && config.refreshIntervalMs < 100) {
288
+ errors.push("refreshIntervalMs must be >= 100ms");
289
+ }
290
+ if (config.theme && !["dark", "light"].includes(config.theme)) {
291
+ errors.push('theme must be "dark" or "light"');
292
+ }
293
+ return errors;
294
+ }
295
+
296
+ // src/errors.ts
297
+ function toError(err, fallback = "Unknown error") {
298
+ if (err instanceof Error)
299
+ return err.message;
300
+ if (typeof err === "string")
301
+ return err;
302
+ if (err && typeof err === "object" && "message" in err && typeof err.message === "string") {
303
+ return err.message;
304
+ }
305
+ if (err === null || err === undefined)
306
+ return fallback;
307
+ try {
308
+ return JSON.stringify(err) || fallback;
309
+ } catch {
310
+ return fallback;
311
+ }
312
+ }
313
+ function sanitizeOutput(data) {
314
+ if (data instanceof Error) {
315
+ return { name: data.name, message: data.message };
316
+ }
317
+ if (data && typeof data === "object" && !Array.isArray(data)) {
318
+ const sanitized = {};
319
+ for (const [k, v] of Object.entries(data)) {
320
+ if (k === "stack" || k === "cause")
321
+ continue;
322
+ sanitized[k] = v instanceof Error ? { name: v.name, message: v.message } : sanitizeOutput(v);
323
+ }
324
+ return sanitized;
325
+ }
326
+ if (Array.isArray(data)) {
327
+ return data.map(sanitizeOutput);
328
+ }
329
+ return data;
330
+ }
331
+ function createJsonResponse(data, status = 200) {
332
+ return new Response(JSON.stringify(sanitizeOutput(data)), {
333
+ status,
334
+ headers: { "Content-Type": "application/json" }
335
+ });
336
+ }
337
+ function createSuccessResponse(result) {
338
+ return createJsonResponse({ success: true, result }, 200);
339
+ }
340
+ function createErrorResponse(error, status) {
341
+ const message = typeof error === "string" ? error : error.message;
342
+ const statusCode = typeof error === "string" ? status ?? 500 : error.status ?? 500;
343
+ const body = { success: false, error: message };
344
+ if (typeof error !== "string" && error.code)
345
+ body.code = error.code;
346
+ if (typeof error !== "string" && error.details !== undefined)
347
+ body.details = error.details;
348
+ return new Response(JSON.stringify(body), {
349
+ status: statusCode,
350
+ headers: { "Content-Type": "application/json" }
351
+ });
352
+ }
353
+ var Errors = {
354
+ badRequest: (message) => createErrorResponse({ message, status: 400, code: "BAD_REQUEST" }),
355
+ unauthorized: (message = "Unauthorized") => createErrorResponse({ message, status: 401, code: "UNAUTHORIZED" }),
356
+ forbidden: (message = "Forbidden") => createErrorResponse({ message, status: 403, code: "FORBIDDEN" }),
357
+ notFound: (message = "Not found") => createErrorResponse({ message, status: 404, code: "NOT_FOUND" }),
358
+ methodNotAllowed: (message = "Method not allowed") => createErrorResponse({ message, status: 405, code: "METHOD_NOT_ALLOWED" }),
359
+ rateLimited: (retryAfter) => {
360
+ const res = createErrorResponse({
361
+ message: "Rate limit exceeded",
362
+ status: 429,
363
+ code: "RATE_LIMITED"
364
+ });
365
+ if (retryAfter)
366
+ res.headers.set("Retry-After", String(retryAfter));
367
+ return res;
368
+ },
369
+ internal: (err) => {
370
+ const message = err ? toError(err, "Internal server error") : "Internal server error";
371
+ return createErrorResponse({
372
+ message,
373
+ status: 500,
374
+ code: "INTERNAL_ERROR"
375
+ });
376
+ }
377
+ };
378
+
379
+ // src/d1/repository.ts
380
+ class D1Repository {
381
+ db;
382
+ constructor(db) {
383
+ this.db = db;
384
+ }
385
+ async query(sql, params = []) {
386
+ try {
387
+ const stmt = this.db.prepare(sql).bind(...params);
388
+ const result = await stmt.all();
389
+ return {
390
+ success: result.success,
391
+ results: result.results,
392
+ error: result.error
393
+ };
394
+ } catch (error) {
395
+ return {
396
+ success: false,
397
+ error: toError(error)
398
+ };
399
+ }
400
+ }
401
+ async execute(sql, params = []) {
402
+ try {
403
+ const stmt = this.db.prepare(sql).bind(...params);
404
+ const result = await stmt.run();
405
+ return {
406
+ success: result.success,
407
+ meta: {
408
+ last_row_id: result.meta?.last_row_id,
409
+ changes: result.meta?.changes,
410
+ duration: result.meta?.duration
411
+ },
412
+ error: result.error
413
+ };
414
+ } catch (error) {
415
+ return {
416
+ success: false,
417
+ error: toError(error)
418
+ };
419
+ }
420
+ }
421
+ async batch(statements) {
422
+ try {
423
+ const prepared = statements.map((s) => s.params && s.params.length > 0 ? this.db.prepare(s.query).bind(...s.params) : this.db.prepare(s.query));
424
+ const results = await this.db.batch(...prepared);
425
+ return results.map((r) => ({
426
+ success: r.success,
427
+ meta: r.meta ? { last_row_id: r.meta.last_row_id, changes: r.meta.changes } : undefined,
428
+ error: r.error
429
+ }));
430
+ } catch (error) {
431
+ return statements.map(() => ({
432
+ success: false,
433
+ error: toError(error)
434
+ }));
435
+ }
436
+ }
437
+ prepare(sql) {
438
+ return this.db.prepare(sql);
439
+ }
440
+ async getTrades(limit = 50, offset = 0) {
441
+ return this.query("SELECT * FROM trades ORDER BY timestamp DESC LIMIT ? OFFSET ?", [limit, offset]);
442
+ }
443
+ async insertTrade(trade) {
444
+ return this.execute(`INSERT INTO trades (id, timestamp, exchange, symbol, action, quantity, price, leverage, status, raw_response)
445
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
446
+ trade.id || crypto.randomUUID(),
447
+ trade.timestamp,
448
+ trade.exchange,
449
+ trade.symbol,
450
+ trade.action,
451
+ trade.quantity ?? null,
452
+ trade.price ?? null,
453
+ trade.leverage ?? null,
454
+ trade.status,
455
+ trade.raw_response ?? null
456
+ ]);
457
+ }
458
+ async getPositions(status) {
459
+ if (status) {
460
+ return this.query("SELECT * FROM positions WHERE status = ? ORDER BY updated_at DESC", [status]);
461
+ }
462
+ return this.query("SELECT * FROM positions ORDER BY updated_at DESC");
463
+ }
464
+ async upsertPosition(position) {
465
+ return this.execute(`REPLACE INTO positions (id, exchange, symbol, side, entry_price, mark_price, liquidation_price, leverage, size, unrealized_pnl, status, updated_at)
466
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [
467
+ position.id,
468
+ position.exchange,
469
+ position.symbol,
470
+ position.side,
471
+ position.entry_price ?? null,
472
+ position.mark_price ?? null,
473
+ position.liquidation_price ?? null,
474
+ position.leverage ?? null,
475
+ position.size,
476
+ position.unrealized_pnl ?? null,
477
+ position.status,
478
+ position.updated_at
479
+ ]);
480
+ }
481
+ async getTradeSignals(limit = 10) {
482
+ return this.query(`SELECT signal_id, timestamp, symbol, signal_type, source, processed_at
483
+ FROM trade_signals
484
+ ORDER BY processed_at DESC
485
+ LIMIT ?`, [limit]);
486
+ }
487
+ async insertTradeSignal(signal) {
488
+ return this.execute(`INSERT INTO trade_signals (signal_id, timestamp, symbol, signal_type, source, raw_data)
489
+ VALUES (?, ?, ?, ?, ?, ?)`, [
490
+ signal.signal_id,
491
+ signal.timestamp,
492
+ signal.symbol,
493
+ signal.signal_type,
494
+ signal.source ?? null,
495
+ signal.raw_data ?? null
496
+ ]);
497
+ }
498
+ async getSystemLogs(limit = 50) {
499
+ return this.query("SELECT * FROM system_logs ORDER BY timestamp DESC LIMIT ?", [limit]);
500
+ }
501
+ async insertSystemLog(log) {
502
+ return this.execute(`INSERT INTO system_logs (id, level, service, message, details) VALUES (?, ?, ?, ?, ?)`, [
503
+ log.id || crypto.randomUUID(),
504
+ log.level,
505
+ log.service,
506
+ log.message,
507
+ log.details ?? null
508
+ ]);
509
+ }
510
+ async getLatestBalances() {
511
+ return this.query(`
512
+ SELECT b.id, b.exchange, b.asset, b.total, b.free, b.used, b.timestamp
513
+ FROM balances b
514
+ INNER JOIN (
515
+ SELECT exchange, asset, MAX(timestamp) as max_time
516
+ FROM balances
517
+ GROUP BY exchange, asset
518
+ ) latest ON b.exchange = latest.exchange AND b.asset = latest.asset AND b.timestamp = latest.max_time
519
+ `);
520
+ }
521
+ async healthCheck() {
522
+ try {
523
+ await this.db.prepare("SELECT 1").first();
524
+ return true;
525
+ } catch {
526
+ return false;
527
+ }
528
+ }
529
+ }
530
+ // src/d1/schemas.ts
531
+ import { z } from "zod";
532
+ var TradeRecordSchema = z.object({
533
+ id: z.string(),
534
+ signal_id: z.string().nullable().optional(),
535
+ timestamp: z.number().int().positive(),
536
+ exchange: z.string().min(1),
537
+ symbol: z.string().min(1),
538
+ action: z.string().min(1),
539
+ quantity: z.number().nullable(),
540
+ price: z.number().nullable(),
541
+ leverage: z.number().nullable(),
542
+ status: z.string().min(1),
543
+ error_message: z.string().nullable().optional(),
544
+ raw_response: z.string().nullable().optional(),
545
+ created_at: z.number().int().optional()
546
+ }).strict();
547
+ var PositionRecordSchema = z.object({
548
+ id: z.string(),
549
+ exchange: z.string().min(1),
550
+ symbol: z.string().min(1),
551
+ side: z.enum(["LONG", "SHORT"]),
552
+ entry_price: z.number().nullable().optional(),
553
+ mark_price: z.number().nullable().optional(),
554
+ liquidation_price: z.number().nullable().optional(),
555
+ leverage: z.number().nullable().optional(),
556
+ size: z.number().nullable(),
557
+ unrealized_pnl: z.number().nullable().optional(),
558
+ status: z.enum(["OPEN", "CLOSED"]),
559
+ updated_at: z.number().int()
560
+ }).strict();
561
+ var BalanceRecordSchema = z.object({
562
+ id: z.string(),
563
+ exchange: z.string().min(1),
564
+ asset: z.string().min(1),
565
+ free: z.number().nullable(),
566
+ used: z.number().nullable(),
567
+ total: z.number().nullable(),
568
+ timestamp: z.number().int().positive()
569
+ }).strict();
570
+ var SystemLogRecordSchema = z.object({
571
+ id: z.string(),
572
+ timestamp: z.number().int().optional(),
573
+ level: z.string().min(1),
574
+ service: z.string().min(1),
575
+ message: z.string().min(1),
576
+ details: z.string().nullable().optional()
577
+ }).strict();
578
+ var TradeSignalRecordSchema = z.object({
579
+ signal_id: z.string(),
580
+ timestamp: z.number().int().positive(),
581
+ symbol: z.string().min(1),
582
+ signal_type: z.string().min(1),
583
+ source: z.string().nullable().optional(),
584
+ raw_data: z.string().nullable().optional(),
585
+ processed_at: z.number().int().optional()
586
+ }).strict();
587
+ // src/exchange-client.ts
588
+ class ExchangeRouter {
589
+ providers = new Map;
590
+ registerProvider(provider) {
591
+ this.providers.set(provider.name.toLowerCase(), provider);
592
+ }
593
+ route(exchange, env) {
594
+ const key = exchange.toLowerCase();
595
+ const provider = this.providers.get(key);
596
+ if (!provider) {
597
+ throw new Error(`Unsupported exchange: ${exchange}`);
598
+ }
599
+ if (!provider.hasCredentials(env)) {
600
+ throw new Error(`API secret bindings not configured or accessible for ${exchange}`);
601
+ }
602
+ return { exchange: key, client: provider.createClient(env) };
603
+ }
604
+ }
605
+
606
+ class BaseExchangeClient {
607
+ apiKey;
608
+ apiSecret;
609
+ baseUrl;
610
+ isTestnet;
611
+ constructor(config) {
612
+ if (!config.apiKey || !config.apiSecret) {
613
+ throw new Error("API key and secret are required.");
614
+ }
615
+ this.apiKey = config.apiKey;
616
+ this.apiSecret = config.apiSecret;
617
+ this.baseUrl = config.baseUrl || this.getDefaultBaseUrl();
618
+ this.isTestnet = config.testnet || false;
619
+ }
620
+ async fetch(method, path, params) {
621
+ const url = `${this.baseUrl}${path}`;
622
+ const headers = this.buildHeaders(method, path, params);
623
+ const body = params ? new URLSearchParams(params).toString() : undefined;
624
+ const response = await fetch(url, {
625
+ method,
626
+ headers,
627
+ body: method !== "GET" ? body : undefined
628
+ });
629
+ if (!response.ok) {
630
+ const error = await response.text();
631
+ throw new Error(`Exchange API error: ${response.status} - ${error}`);
632
+ }
633
+ return response.json();
634
+ }
635
+ async openLong(symbol, quantity, price, orderType = "MARKET") {
636
+ return this.executeTrade({
637
+ symbol,
638
+ side: "long",
639
+ orderType,
640
+ quantity,
641
+ price
642
+ });
643
+ }
644
+ async openShort(symbol, quantity, price, orderType = "MARKET") {
645
+ return this.executeTrade({
646
+ symbol,
647
+ side: "short",
648
+ orderType,
649
+ quantity,
650
+ price
651
+ });
652
+ }
653
+ async closeLong(symbol, quantity) {
654
+ return this.executeTrade({
655
+ symbol,
656
+ side: "long",
657
+ quantity,
658
+ reduceOnly: true
659
+ });
660
+ }
661
+ async closeShort(symbol, quantity) {
662
+ return this.executeTrade({
663
+ symbol,
664
+ side: "short",
665
+ quantity,
666
+ reduceOnly: true
667
+ });
668
+ }
669
+ }
670
+
671
+ // src/format-time.ts
672
+ function formatRelativeTime(timestampMs, nowMs) {
673
+ const now = nowMs ?? Date.now();
674
+ const diffMs = now - timestampMs;
675
+ if (diffMs < 0)
676
+ return "just now";
677
+ if (timestampMs === 0)
678
+ return "\u2014";
679
+ const seconds = Math.floor(diffMs / 1000);
680
+ const minutes = Math.floor(seconds / 60);
681
+ const hours = Math.floor(minutes / 60);
682
+ const days = Math.floor(hours / 24);
683
+ if (seconds < 60)
684
+ return "< 1m ago";
685
+ if (minutes < 60)
686
+ return `${minutes}m ago`;
687
+ if (hours < 24)
688
+ return `${hours}h ago`;
689
+ if (days < 30)
690
+ return `${days}d ago`;
691
+ return `> 30d ago`;
692
+ }
693
+ function formatDuration(durationMs) {
694
+ const seconds = Math.floor(durationMs / 1000);
695
+ const minutes = Math.floor(seconds / 60);
696
+ const hours = Math.floor(minutes / 60);
697
+ if (hours > 0) {
698
+ const remainMin = minutes % 60;
699
+ return remainMin > 0 ? `${hours}h ${remainMin}m` : `${hours}h`;
700
+ }
701
+ if (minutes > 0) {
702
+ const remainSec = seconds % 60;
703
+ return remainSec > 0 ? `${minutes}m ${remainSec}s` : `${minutes}m`;
704
+ }
705
+ return `${seconds}s`;
706
+ }
707
+
708
+ // src/formatters.ts
709
+ function formatNumber(n) {
710
+ if (!isFinite(n))
711
+ return "\u2014";
712
+ if (Math.abs(n) >= 1e9)
713
+ return `${(n / 1e9).toFixed(1)}B`;
714
+ if (Math.abs(n) >= 1e6)
715
+ return `${(n / 1e6).toFixed(1)}M`;
716
+ if (Math.abs(n) >= 1000)
717
+ return `${(n / 1000).toFixed(1)}K`;
718
+ return n.toLocaleString();
719
+ }
720
+ function formatCurrency(n) {
721
+ if (!isFinite(n))
722
+ return "\u2014";
723
+ const sign = n >= 0 ? "+" : "-";
724
+ const abs = Math.abs(n);
725
+ if (abs >= 1e6)
726
+ return `${sign}$${(abs / 1e6).toFixed(2)}M`;
727
+ if (abs >= 1000)
728
+ return `${sign}$${(abs / 1000).toFixed(2)}K`;
729
+ return `${sign}$${abs.toFixed(2)}`;
730
+ }
731
+ function formatCompactCurrency(n) {
732
+ if (!isFinite(n))
733
+ return "\u2014";
734
+ const abs = Math.abs(n);
735
+ const sign = n < 0 ? "-" : "";
736
+ if (abs >= 1e6)
737
+ return `${sign}$${(abs / 1e6).toFixed(2)}M`;
738
+ if (abs >= 1000)
739
+ return `${sign}$${(abs / 1000).toFixed(2)}K`;
740
+ return `${sign}$${abs.toFixed(2)}`;
741
+ }
742
+ function formatDuration2(seconds) {
743
+ if (!isFinite(seconds) || seconds < 0)
744
+ return "\u2014";
745
+ const d = Math.floor(seconds / 86400);
746
+ const h = Math.floor(seconds % 86400 / 3600);
747
+ const m = Math.floor(seconds % 3600 / 60);
748
+ const s = Math.floor(seconds % 60);
749
+ const parts = [];
750
+ if (d > 0)
751
+ parts.push(`${d}d`);
752
+ if (h > 0 || d > 0)
753
+ parts.push(`${h}h`);
754
+ if (m > 0 || h > 0 || d > 0)
755
+ parts.push(`${m}m`);
756
+ parts.push(`${s}s`);
757
+ return parts.join(" ");
758
+ }
759
+ function formatDurationCompact(seconds) {
760
+ if (!isFinite(seconds) || seconds < 0)
761
+ return "\u2014";
762
+ const d = Math.floor(seconds / 86400);
763
+ const h = Math.floor(seconds % 86400 / 3600);
764
+ const m = Math.floor(seconds % 3600 / 60);
765
+ if (d > 0)
766
+ return `${d}d ${h}h`;
767
+ if (h > 0)
768
+ return `${h}h ${m}m`;
769
+ if (m > 0)
770
+ return `${m}m`;
771
+ return `${Math.floor(seconds)}s`;
772
+ }
773
+ function formatTimestamp(iso) {
774
+ try {
775
+ const d = new Date(iso);
776
+ if (isNaN(d.getTime()))
777
+ return iso;
778
+ return d.toLocaleTimeString("en-US", { hour12: false });
779
+ } catch {
780
+ return iso;
781
+ }
782
+ }
783
+ function formatRelativeTime2(timestamp) {
784
+ const now = Date.now();
785
+ const diff = now - timestamp;
786
+ if (diff < 0)
787
+ return "just now";
788
+ const seconds = Math.floor(diff / 1000);
789
+ if (seconds < 60)
790
+ return `${seconds}s ago`;
791
+ const minutes = Math.floor(seconds / 60);
792
+ if (minutes < 60)
793
+ return `${minutes}m ago`;
794
+ const hours = Math.floor(minutes / 60);
795
+ if (hours < 24)
796
+ return `${hours}h ago`;
797
+ const days = Math.floor(hours / 24);
798
+ return `${days}d ago`;
799
+ }
800
+ function formatPercent(n, decimals = 2) {
801
+ if (!isFinite(n))
802
+ return "\u2014";
803
+ const sign = n >= 0 ? "+" : "";
804
+ return `${sign}${n.toFixed(decimals)}%`;
805
+ }
806
+ function formatUptime(seconds) {
807
+ return formatDuration2(seconds);
808
+ }
809
+ function formatLatency(ms) {
810
+ if (!isFinite(ms))
811
+ return "\u2014";
812
+ if (ms < 1)
813
+ return "<1ms";
814
+ if (ms < 1000)
815
+ return `${Math.round(ms)}ms`;
816
+ return `${(ms / 1000).toFixed(1)}s`;
817
+ }
818
+ function formatRequests(n) {
819
+ return formatNumber(n);
820
+ }
821
+ function formatMemory(usedMB, limitMB) {
822
+ const u = usedMB.toFixed(0);
823
+ const l = limitMB.toFixed(0);
824
+ return `${u}/${l} MB`;
825
+ }
826
+ function formatCpu(ms) {
827
+ if (!isFinite(ms))
828
+ return "\u2014";
829
+ return `${ms.toFixed(1)}ms`;
830
+ }
831
+
832
+ // src/health.ts
833
+ function healthCheck(options) {
834
+ const result = {
835
+ status: "ok",
836
+ timestamp: new Date().toISOString()
837
+ };
838
+ if (options?.worker !== undefined && options?.worker !== null)
839
+ result.service = options.worker;
840
+ if (options?.version !== undefined && options?.version !== null)
841
+ result.version = options.version;
842
+ if (options?.details)
843
+ result.details = options.details;
844
+ return createSuccessResponse(result);
845
+ }
846
+
847
+ // src/legal.ts
848
+ var COPYRIGHT = `Copyright (C) ${new Date().getFullYear()} Hoox Team. All rights reserved.`;
849
+ var TRADEMARKS = [
850
+ "TradingView\xAE is a registered trademark of TradingView, Inc.",
851
+ "Cloudflare\xAE is a registered trademark of Cloudflare, Inc."
852
+ ];
853
+ var TRADEMARK_NOTICE = TRADEMARKS.join(`
854
+ `);
855
+ 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.";
856
+ var FULL_LEGAL_NOTICE = `${COPYRIGHT}
857
+
858
+ ${TRADEMARK_NOTICE}
859
+
860
+ ${DISCLAIMER}`;
861
+ var DISCLAIMER_HEADER = "X-Disclaimer";
862
+
863
+ // src/types.ts
864
+ import { z as z2 } from "zod";
865
+ var TradeActionSchema = z2.enum([
866
+ "LONG",
867
+ "SHORT",
868
+ "CLOSE_LONG",
869
+ "CLOSE_SHORT"
870
+ ]);
871
+ var WebhookPayloadSchema = z2.object({
872
+ exchange: z2.string().min(1),
873
+ action: TradeActionSchema,
874
+ symbol: z2.string().min(1).max(20),
875
+ quantity: z2.number().positive().finite(),
876
+ price: z2.number().positive().finite().optional(),
877
+ orderType: z2.string().optional(),
878
+ leverage: z2.number().int().positive().optional()
879
+ }).strict();
880
+ var TradeSignalSchema = z2.object({
881
+ id: z2.number().int().positive().optional(),
882
+ source: z2.string().min(1),
883
+ symbol: z2.string().min(1),
884
+ action: TradeActionSchema,
885
+ price: z2.number().positive().finite().optional(),
886
+ quantity: z2.number().positive().finite(),
887
+ leverage: z2.number().int().positive().optional(),
888
+ status: z2.enum(["pending", "executed", "failed", "skipped"]).optional(),
889
+ createdAt: z2.string().optional(),
890
+ executedAt: z2.string().optional(),
891
+ error: z2.string().optional()
892
+ }).strict();
893
+ var PositionSchema = z2.object({
894
+ id: z2.string(),
895
+ symbol: z2.string().min(1),
896
+ side: TradeActionSchema,
897
+ quantity: z2.number().positive(),
898
+ entry_price: z2.number().positive(),
899
+ current_price: z2.number().positive(),
900
+ unrealized_pnl: z2.number(),
901
+ timestamp: z2.number().int().positive()
902
+ }).strict();
903
+ var BalanceSchema = z2.object({
904
+ asset: z2.string().min(1),
905
+ free: z2.number().min(0),
906
+ locked: z2.number().min(0),
907
+ timestamp: z2.number().int().positive()
908
+ }).strict();
909
+
910
+ // src/kvKeys.ts
911
+ var KV_TRADE_DEFAULT_LEVERAGE = "trade:default_leverage";
912
+ var KV_TRADE_MAX_POSITION_SIZE = "trade:max_position_size";
913
+ var KV_TRADE_MAX_DAILY_DRAWDOWN_PERCENT = "trade:max_daily_drawdown_percent";
914
+ var KV_TRADE_TRAILING_STOP_PERCENT = "trade:trailing_stop_percent";
915
+ var KV_TRADE_KILL_SWITCH = "trade:kill_switch";
916
+ var KV_TRADE_ROUTING = "trade:routing";
917
+ var KV_BOT_ENABLED = "bot:enabled";
918
+ var KV_BOT_DEFAULT_CHAT_ID = "bot:default_chat_id";
919
+ var KV_BOT_NOTIFY_ON_EXECUTION = "bot:notify_on_execution";
920
+ var KV_BOT_NOTIFY_ON_ERROR = "bot:notify_on_error";
921
+ var KV_WEBHOOK_IP_CHECK_ENABLED = "webhook:tradingview:ip_check_enabled";
922
+ var KV_WEBHOOK_ALLOWED_IPS = "webhook:tradingview:allowed_ips";
923
+ var KV_WEBHOOK_QUEUE_MODE = "webhook:queue_mode";
924
+ var KV_EMAIL_SCAN_SUBJECT = "email:scan_subject";
925
+ var KV_EMAIL_USE_IMAP = "email:use_imap";
926
+ var KV_EMAIL_COIN_PATTERN = "email:coin_pattern";
927
+ var KV_EMAIL_ACTION_PATTERN = "email:action_pattern";
928
+ var KV_EMAIL_QUANTITY_MULTIPLIER = "email:quantity_multiplier";
929
+ var KV_AGENT_CONFIG = "agent:config";
930
+ var KV_AGENT_OPENAI_KEY = "agent:openai_key";
931
+ var KV_AGENT_ANTHROPIC_KEY = "agent:anthropic_key";
932
+ var KV_AGENT_GOOGLE_KEY = "agent:google_key";
933
+ var KV_DASHBOARD_AI_HEALTH_SUMMARY = "dashboard:ai_health_summary";
934
+ var KV_HEALTH_CHECK = "health_check";
935
+ var KV_HOUSEKEEPING_LAST_CHECK = "housekeeping:last_check";
936
+ var QUEUE_MODE_FAILOVER = "queue_failover";
937
+ var QUEUE_MODE_EVERYWHERE = "queue_everywhere";
938
+ var QUEUE_MODE_DISABLED = "queue_disabled";
939
+ var KVKeys = {
940
+ KV_TRADE_DEFAULT_LEVERAGE,
941
+ KV_TRADE_MAX_POSITION_SIZE,
942
+ KV_TRADE_MAX_DAILY_DRAWDOWN_PERCENT,
943
+ KV_TRADE_TRAILING_STOP_PERCENT,
944
+ KV_TRADE_KILL_SWITCH,
945
+ KV_TRADE_ROUTING,
946
+ KV_BOT_ENABLED,
947
+ KV_BOT_DEFAULT_CHAT_ID,
948
+ KV_BOT_NOTIFY_ON_EXECUTION,
949
+ KV_BOT_NOTIFY_ON_ERROR,
950
+ KV_WEBHOOK_IP_CHECK_ENABLED,
951
+ KV_WEBHOOK_ALLOWED_IPS,
952
+ KV_WEBHOOK_QUEUE_MODE,
953
+ KV_EMAIL_SCAN_SUBJECT,
954
+ KV_EMAIL_USE_IMAP,
955
+ KV_EMAIL_COIN_PATTERN,
956
+ KV_EMAIL_ACTION_PATTERN,
957
+ KV_EMAIL_QUANTITY_MULTIPLIER,
958
+ KV_AGENT_CONFIG,
959
+ KV_AGENT_OPENAI_KEY,
960
+ KV_AGENT_ANTHROPIC_KEY,
961
+ KV_AGENT_GOOGLE_KEY,
962
+ KV_DASHBOARD_AI_HEALTH_SUMMARY,
963
+ KV_HEALTH_CHECK,
964
+ KV_HOUSEKEEPING_LAST_CHECK,
965
+ QUEUE_MODE_FAILOVER,
966
+ QUEUE_MODE_EVERYWHERE,
967
+ QUEUE_MODE_DISABLED
968
+ };
969
+
970
+ // src/kvUtils.ts
971
+ async function logKvTimestamp(env, prefix = "timestamp") {
972
+ const timestamp = new Date().toISOString();
973
+ const key = `${prefix}_${timestamp}`;
974
+ try {
975
+ await env.REPORT_KV.put(key, timestamp);
976
+ } catch (error) {
977
+ console.error(`Failed to log timestamp to KV: ${toError(error, "Unknown error")}`);
978
+ }
979
+ }
980
+ function headersToObject(headers) {
981
+ const result = {};
982
+ if (!headers)
983
+ return result;
984
+ try {
985
+ headers.forEach((value, key) => {
986
+ result[key] = value;
987
+ });
988
+ } catch (error) {
989
+ console.error(`Error converting headers to object: ${toError(error, "Unknown error")}`);
990
+ }
991
+ return result;
992
+ }
993
+
994
+ // src/service-bindings.ts
995
+ function serviceFetch(binding, path, body, options) {
996
+ return binding.fetch(`http://internal${path}`, {
997
+ method: options?.method ?? "POST",
998
+ headers: {
999
+ "Content-Type": "application/json",
1000
+ ...options?.headers
1001
+ },
1002
+ body: body != null ? JSON.stringify(body) : undefined
1003
+ });
1004
+ }
1005
+
1006
+ // src/session.ts
1007
+ import { homedir as homedir2 } from "os";
1008
+ import { join as join2 } from "path";
1009
+ var SESSION_FILE = join2(homedir2(), ".hoox", "session.json");
1010
+ var DEFAULT_SESSION = {
1011
+ activeView: "dashboard",
1012
+ sidebarExpanded: true,
1013
+ windowSize: { cols: 80, rows: 24 },
1014
+ lastData: 0,
1015
+ savedAt: new Date().toISOString()
1016
+ };
1017
+ async function saveSession(activeView, sidebarExpanded, windowSize, lastData) {
1018
+ try {
1019
+ const session = {
1020
+ activeView,
1021
+ sidebarExpanded,
1022
+ windowSize,
1023
+ lastData,
1024
+ savedAt: new Date().toISOString()
1025
+ };
1026
+ await Bun.write(SESSION_FILE, JSON.stringify(session, null, 2));
1027
+ } catch (error) {
1028
+ console.error("[session] Failed to save session:", error.message);
1029
+ }
1030
+ }
1031
+ async function restoreSession() {
1032
+ try {
1033
+ const file = Bun.file(SESSION_FILE);
1034
+ const exists = await file.exists();
1035
+ if (!exists)
1036
+ return { ...DEFAULT_SESSION };
1037
+ const raw = await file.text();
1038
+ const parsed = JSON.parse(raw);
1039
+ return {
1040
+ activeView: validateViewId(parsed.activeView) ? parsed.activeView : DEFAULT_SESSION.activeView,
1041
+ sidebarExpanded: typeof parsed.sidebarExpanded === "boolean" ? parsed.sidebarExpanded : DEFAULT_SESSION.sidebarExpanded,
1042
+ windowSize: parsed.windowSize ?? DEFAULT_SESSION.windowSize,
1043
+ lastData: typeof parsed.lastData === "number" ? parsed.lastData : DEFAULT_SESSION.lastData,
1044
+ savedAt: parsed.savedAt ?? DEFAULT_SESSION.savedAt
1045
+ };
1046
+ } catch {
1047
+ return { ...DEFAULT_SESSION };
1048
+ }
1049
+ }
1050
+ var VALID_VIEW_IDS = new Set([
1051
+ "dashboard",
1052
+ "workers",
1053
+ "worker-detail",
1054
+ "trade-monitor",
1055
+ "logs-viewer",
1056
+ "service-manager",
1057
+ "config-editor",
1058
+ "setup-wizard",
1059
+ "settings"
1060
+ ]);
1061
+ function validateViewId(id) {
1062
+ return typeof id === "string" && VALID_VIEW_IDS.has(id);
1063
+ }
1064
+
1065
+ // src/wizard/presets.ts
1066
+ var PRESETS = [
1067
+ {
1068
+ name: "minimal",
1069
+ label: "Minimal",
1070
+ description: "Gateway + D1 database \u2014 webhook processing only",
1071
+ workers: ["hoox", "d1-worker", "analytics-worker"],
1072
+ integrations: []
1073
+ },
1074
+ {
1075
+ name: "standard",
1076
+ label: "Standard",
1077
+ description: "Trading + analytics + Telegram notifications",
1078
+ workers: [
1079
+ "hoox",
1080
+ "d1-worker",
1081
+ "trade-worker",
1082
+ "analytics-worker",
1083
+ "telegram-worker"
1084
+ ],
1085
+ integrations: ["binance", "telegram"]
1086
+ },
1087
+ {
1088
+ name: "full",
1089
+ label: "Full",
1090
+ description: "All workers + AI agent + DeFi + email",
1091
+ workers: [
1092
+ "hoox",
1093
+ "d1-worker",
1094
+ "trade-worker",
1095
+ "agent-worker",
1096
+ "telegram-worker",
1097
+ "analytics-worker",
1098
+ "email-worker",
1099
+ "web3-wallet-worker"
1100
+ ],
1101
+ integrations: ["binance", "bybit", "mexc", "telegram", "openai", "wallet"]
1102
+ }
1103
+ ];
1104
+ var WORKER_DEPENDENCIES = {
1105
+ "trade-worker": ["d1-worker"],
1106
+ "agent-worker": ["d1-worker"],
1107
+ "email-worker": ["d1-worker"],
1108
+ "analytics-worker": ["d1-worker"],
1109
+ "web3-wallet-worker": ["d1-worker", "hoox"]
1110
+ };
1111
+ function resolveDependencies(selected) {
1112
+ const result = new Set(selected);
1113
+ let changed = true;
1114
+ while (changed) {
1115
+ changed = false;
1116
+ for (const worker of [...result]) {
1117
+ const deps = WORKER_DEPENDENCIES[worker];
1118
+ if (deps) {
1119
+ for (const dep of deps) {
1120
+ if (!result.has(dep)) {
1121
+ result.add(dep);
1122
+ changed = true;
1123
+ }
1124
+ }
1125
+ }
1126
+ }
1127
+ }
1128
+ return [...result];
1129
+ }
1130
+ var INTEGRATIONS = [
1131
+ {
1132
+ key: "binance",
1133
+ label: "Binance Exchange",
1134
+ workerName: "trade-worker",
1135
+ secrets: {
1136
+ BINANCE_KEY_BINDING: "Binance API Key",
1137
+ BINANCE_SECRET_BINDING: "Binance API Secret"
1138
+ }
1139
+ },
1140
+ {
1141
+ key: "mexc",
1142
+ label: "MEXC Exchange",
1143
+ workerName: "trade-worker",
1144
+ secrets: {
1145
+ MEXC_KEY_BINDING: "MEXC API Key",
1146
+ MEXC_SECRET_BINDING: "MEXC API Secret"
1147
+ }
1148
+ },
1149
+ {
1150
+ key: "bybit",
1151
+ label: "Bybit Exchange",
1152
+ workerName: "trade-worker",
1153
+ secrets: {
1154
+ BYBIT_KEY_BINDING: "Bybit API Key",
1155
+ BYBIT_SECRET_BINDING: "Bybit API Secret"
1156
+ }
1157
+ },
1158
+ {
1159
+ key: "wallet",
1160
+ label: "Web3 Wallet (on-chain execution)",
1161
+ workerName: "web3-wallet-worker",
1162
+ secrets: {
1163
+ WALLET_MNEMONIC_SECRET: "Wallet Mnemonic Phrase",
1164
+ WALLET_PK_SECRET: "Wallet Private Key"
1165
+ }
1166
+ },
1167
+ {
1168
+ key: "email",
1169
+ label: "Email Signal Parsing",
1170
+ workerName: "email-worker",
1171
+ secrets: {
1172
+ INTERNAL_KEY_BINDING: "Internal Auth Key",
1173
+ EMAIL_HOST_BINDING: "Email Host (IMAP server)",
1174
+ EMAIL_USER_BINDING: "Email Username",
1175
+ EMAIL_PASS_BINDING: "Email Password"
1176
+ },
1177
+ vars: { USE_IMAP: "false" }
1178
+ },
1179
+ {
1180
+ key: "telegram",
1181
+ label: "Telegram Notifications",
1182
+ workerName: "telegram-worker",
1183
+ secrets: {
1184
+ TG_BOT_TOKEN_BINDING: "Telegram Bot Token"
1185
+ }
1186
+ },
1187
+ {
1188
+ key: "openai",
1189
+ label: "OpenAI (AI Agent)",
1190
+ workerName: "agent-worker",
1191
+ secrets: {
1192
+ AGENT_INTERNAL_KEY: "OpenAI API Key"
1193
+ }
1194
+ },
1195
+ {
1196
+ key: "anthropic",
1197
+ label: "Anthropic (AI Agent)",
1198
+ workerName: "agent-worker",
1199
+ secrets: {
1200
+ AGENT_INTERNAL_KEY: "Anthropic API Key"
1201
+ }
1202
+ },
1203
+ {
1204
+ key: "google-ai",
1205
+ label: "Google AI (AI Agent)",
1206
+ workerName: "agent-worker",
1207
+ secrets: {
1208
+ AGENT_INTERNAL_KEY: "Google AI API Key"
1209
+ }
1210
+ },
1211
+ {
1212
+ key: "home-assistant",
1213
+ label: "Home Assistant (Smart Home)",
1214
+ workerName: "hoox",
1215
+ secrets: {
1216
+ HA_TOKEN_BINDING: "Home Assistant Token"
1217
+ }
1218
+ }
1219
+ ];
1220
+ var BASE_WORKERS = {
1221
+ "d1-worker": {
1222
+ enabled: true,
1223
+ path: "workers/d1-worker",
1224
+ vars: { database_name: "hoox-db" }
1225
+ },
1226
+ hoox: { enabled: true, path: "workers/hoox", vars: {} },
1227
+ "agent-worker": { enabled: true, path: "workers/agent-worker", vars: {} },
1228
+ "analytics-worker": {
1229
+ enabled: true,
1230
+ path: "workers/analytics-worker",
1231
+ vars: {}
1232
+ }
1233
+ };
1234
+ var BASE_SECRETS = {
1235
+ hoox: ["WEBHOOK_API_KEY_BINDING"],
1236
+ "agent-worker": ["AGENT_INTERNAL_KEY", "INTERNAL_KEY_BINDING"],
1237
+ "analytics-worker": ["CLOUDFLARE_API_TOKEN"]
1238
+ };
1239
+
1240
+ // src/wizard/engine.ts
1241
+ var STEPS = [
1242
+ {
1243
+ id: "PREREQUISITES",
1244
+ label: "System Prerequisites",
1245
+ canGoBack: false,
1246
+ optional: false,
1247
+ validate: validatePrerequisites
1248
+ },
1249
+ {
1250
+ id: "CLOUDFLARE_CONFIG",
1251
+ label: "Cloudflare Configuration",
1252
+ canGoBack: true,
1253
+ optional: false,
1254
+ validate: validateCloudflareConfig
1255
+ },
1256
+ {
1257
+ id: "WORKER_SELECTION",
1258
+ label: "Worker Selection",
1259
+ canGoBack: true,
1260
+ optional: false,
1261
+ validate: validateWorkerSelection
1262
+ },
1263
+ {
1264
+ id: "PROVISIONING",
1265
+ label: "Infrastructure Provisioning",
1266
+ canGoBack: true,
1267
+ optional: true,
1268
+ validate: () => []
1269
+ },
1270
+ {
1271
+ id: "SECRETS",
1272
+ label: "Secrets Configuration",
1273
+ canGoBack: true,
1274
+ optional: false,
1275
+ validate: validateSecrets
1276
+ },
1277
+ {
1278
+ id: "CONFIG_WRITE",
1279
+ label: "Configuration Write",
1280
+ canGoBack: true,
1281
+ optional: false,
1282
+ validate: () => []
1283
+ },
1284
+ {
1285
+ id: "DEPLOY",
1286
+ label: "Deploy",
1287
+ canGoBack: false,
1288
+ optional: true,
1289
+ validate: () => []
1290
+ },
1291
+ {
1292
+ id: "DONE",
1293
+ label: "Complete",
1294
+ canGoBack: false,
1295
+ optional: false,
1296
+ validate: () => []
1297
+ }
1298
+ ];
1299
+ var STEP_ORDER = STEPS.map((s) => s.id);
1300
+ function createInitialState() {
1301
+ return {
1302
+ step: "PREREQUISITES",
1303
+ completedSteps: [],
1304
+ selectedWorkers: [],
1305
+ selectedIntegrations: [],
1306
+ secrets: {},
1307
+ startedAt: Date.now(),
1308
+ updatedAt: Date.now()
1309
+ };
1310
+ }
1311
+ function validatePrerequisites(_state, input) {
1312
+ const errors = [];
1313
+ if (!input.checksPassed) {
1314
+ errors.push("System prerequisite checks must pass before continuing");
1315
+ }
1316
+ return errors;
1317
+ }
1318
+ function validateCloudflareConfig(_state, input) {
1319
+ const errors = [];
1320
+ const token = input.apiToken;
1321
+ const accountId = input.accountId;
1322
+ if (!token || String(token).trim().length === 0) {
1323
+ errors.push("Cloudflare API token is required");
1324
+ }
1325
+ if (!accountId || String(accountId).trim().length === 0) {
1326
+ errors.push("Cloudflare Account ID is required");
1327
+ }
1328
+ return errors;
1329
+ }
1330
+ function validateWorkerSelection(_state, input) {
1331
+ const errors = [];
1332
+ const preset = input.preset;
1333
+ const customWorkers = input.workers;
1334
+ if (preset === "custom" && (!customWorkers || customWorkers.length === 0)) {
1335
+ errors.push("At least one worker must be selected");
1336
+ }
1337
+ return errors;
1338
+ }
1339
+ function validateSecrets(state, input) {
1340
+ const errors = [];
1341
+ const secrets = input.secrets;
1342
+ if (!secrets || Object.keys(secrets).length === 0) {
1343
+ return [];
1344
+ }
1345
+ for (const integrationKey of state.selectedIntegrations) {
1346
+ const integration = INTEGRATIONS.find((i) => i.key === integrationKey);
1347
+ if (!integration)
1348
+ continue;
1349
+ const integrationSecrets = secrets[integrationKey];
1350
+ if (integrationSecrets) {
1351
+ for (const secretName of Object.keys(integration.secrets)) {
1352
+ const val = integrationSecrets[secretName];
1353
+ if (!val || String(val).trim().length === 0) {
1354
+ errors.push(`Secret "${integration.secrets[secretName]}" is required for ${integration.label}`);
1355
+ }
1356
+ }
1357
+ }
1358
+ }
1359
+ return errors;
1360
+ }
1361
+
1362
+ class WizardEngine {
1363
+ state;
1364
+ constructor(initialState) {
1365
+ this.state = { ...createInitialState(), ...initialState };
1366
+ }
1367
+ getState() {
1368
+ return { ...this.state };
1369
+ }
1370
+ getCurrentStep() {
1371
+ return STEPS.find((s) => s.id === this.state.step) ?? STEPS[0];
1372
+ }
1373
+ getCompletedSteps() {
1374
+ return [...this.state.completedSteps];
1375
+ }
1376
+ canProceed() {
1377
+ const step = this.getCurrentStep();
1378
+ return step.optional || this.state.completedSteps.includes(step.id);
1379
+ }
1380
+ canGoBack() {
1381
+ return this.getCurrentStep().canGoBack;
1382
+ }
1383
+ execute(input) {
1384
+ const currentStep = this.getCurrentStep();
1385
+ const errors = currentStep.validate(this.state, input);
1386
+ if (errors.length > 0) {
1387
+ return errors;
1388
+ }
1389
+ this.applyInput(input);
1390
+ if (!this.state.completedSteps.includes(this.state.step)) {
1391
+ this.state.completedSteps.push(this.state.step);
1392
+ }
1393
+ const currentIdx = STEP_ORDER.indexOf(this.state.step);
1394
+ if (currentIdx < STEP_ORDER.length - 1) {
1395
+ this.state.step = STEP_ORDER[currentIdx + 1];
1396
+ }
1397
+ this.state.updatedAt = Date.now();
1398
+ return [];
1399
+ }
1400
+ goBack() {
1401
+ if (!this.canGoBack())
1402
+ return;
1403
+ const currentIdx = STEP_ORDER.indexOf(this.state.step);
1404
+ if (currentIdx > 0) {
1405
+ const previousStep = STEP_ORDER[currentIdx - 1];
1406
+ this.state.step = previousStep;
1407
+ this.state.completedSteps = this.state.completedSteps.filter((s) => s !== this.state.step || STEP_ORDER.indexOf(s) <= STEP_ORDER.indexOf(previousStep));
1408
+ }
1409
+ this.state.updatedAt = Date.now();
1410
+ }
1411
+ reset() {
1412
+ this.state = createInitialState();
1413
+ }
1414
+ buildConfig() {
1415
+ const cf = this.state.cloudflareConfig;
1416
+ const workers = {};
1417
+ for (const [name, baseCfg] of Object.entries(BASE_WORKERS)) {
1418
+ workers[name] = {
1419
+ enabled: true,
1420
+ path: baseCfg.path,
1421
+ vars: { ...baseCfg.vars },
1422
+ secrets: [...BASE_SECRETS[name] ?? []]
1423
+ };
1424
+ }
1425
+ for (const key of this.state.selectedIntegrations) {
1426
+ const integration = INTEGRATIONS.find((i) => i.key === key);
1427
+ if (!integration)
1428
+ continue;
1429
+ const workerName = integration.workerName;
1430
+ if (!workers[workerName]) {
1431
+ workers[workerName] = {
1432
+ enabled: true,
1433
+ path: `workers/${workerName}`,
1434
+ vars: {},
1435
+ secrets: []
1436
+ };
1437
+ }
1438
+ if (integration.vars) {
1439
+ Object.assign(workers[workerName].vars, integration.vars);
1440
+ }
1441
+ for (const secretName of Object.keys(integration.secrets)) {
1442
+ if (!workers[workerName].secrets.includes(secretName)) {
1443
+ workers[workerName].secrets.push(secretName);
1444
+ }
1445
+ }
1446
+ }
1447
+ for (const workerName of this.state.selectedWorkers) {
1448
+ if (!workers[workerName]) {
1449
+ workers[workerName] = {
1450
+ enabled: true,
1451
+ path: `workers/${workerName}`,
1452
+ vars: {},
1453
+ secrets: []
1454
+ };
1455
+ }
1456
+ }
1457
+ return {
1458
+ global: {
1459
+ cloudflare_api_token: cf?.apiToken ?? "",
1460
+ cloudflare_account_id: cf?.accountId ?? "",
1461
+ cloudflare_secret_store_id: cf?.secretStoreId ?? "",
1462
+ subdomain_prefix: cf?.subdomain ?? ""
1463
+ },
1464
+ workers
1465
+ };
1466
+ }
1467
+ getProvisioningPlan() {
1468
+ const plan = {
1469
+ d1Databases: [],
1470
+ kvNamespaces: [],
1471
+ r2Buckets: [],
1472
+ queues: []
1473
+ };
1474
+ const allWorkers = [
1475
+ ...Object.keys(BASE_WORKERS),
1476
+ ...this.state.selectedWorkers,
1477
+ ...this.state.selectedIntegrations.map((k) => {
1478
+ const i = INTEGRATIONS.find((i2) => i2.key === k);
1479
+ return i?.workerName ?? "";
1480
+ }).filter(Boolean)
1481
+ ];
1482
+ const workerSet = new Set(allWorkers);
1483
+ if (workerSet.has("d1-worker")) {
1484
+ plan.d1Databases.push("hoox-db");
1485
+ }
1486
+ if (workerSet.has("hoox") || workerSet.has("analytics-worker")) {
1487
+ plan.kvNamespaces.push("CONFIG_KV");
1488
+ }
1489
+ if (workerSet.has("web3-wallet-worker")) {
1490
+ plan.kvNamespaces.push("WEB3_CACHE_KV");
1491
+ }
1492
+ plan.d1Databases = [...new Set(plan.d1Databases)];
1493
+ plan.kvNamespaces = [...new Set(plan.kvNamespaces)];
1494
+ return plan;
1495
+ }
1496
+ applyInput(input) {
1497
+ const step = this.state.step;
1498
+ switch (step) {
1499
+ case "PREREQUISITES":
1500
+ break;
1501
+ case "CLOUDFLARE_CONFIG":
1502
+ this.state.cloudflareConfig = {
1503
+ apiToken: String(input.apiToken ?? ""),
1504
+ accountId: String(input.accountId ?? ""),
1505
+ secretStoreId: String(input.secretStoreId ?? ""),
1506
+ subdomain: String(input.subdomain ?? "cryptolinx")
1507
+ };
1508
+ break;
1509
+ case "WORKER_SELECTION": {
1510
+ const preset = input.preset;
1511
+ this.state.preset = preset;
1512
+ if (preset && preset !== "custom") {
1513
+ const presetDef = PRESETS.find((p) => p.name === preset);
1514
+ if (presetDef) {
1515
+ const resolved = resolveDependencies(presetDef.workers);
1516
+ this.state.selectedWorkers = resolved;
1517
+ this.state.selectedIntegrations = presetDef.integrations;
1518
+ }
1519
+ } else {
1520
+ const workers = input.workers;
1521
+ const integrations = input.integrations;
1522
+ if (workers) {
1523
+ this.state.selectedWorkers = resolveDependencies(workers);
1524
+ }
1525
+ if (integrations) {
1526
+ this.state.selectedIntegrations = integrations;
1527
+ }
1528
+ }
1529
+ break;
1530
+ }
1531
+ case "SECRETS":
1532
+ this.state.secrets = input.secrets ?? {};
1533
+ break;
1534
+ case "PROVISIONING":
1535
+ if (input.provisioningResults) {
1536
+ this.state.provisioningResults = input.provisioningResults;
1537
+ }
1538
+ break;
1539
+ case "CONFIG_WRITE":
1540
+ case "DEPLOY":
1541
+ case "DONE":
1542
+ break;
1543
+ }
1544
+ }
1545
+ }
1546
+ // src/wizard/persistence.ts
1547
+ function serializeState(state) {
1548
+ return JSON.stringify(state, null, 2);
1549
+ }
1550
+ function deserializeState(json) {
1551
+ const parsed = JSON.parse(json);
1552
+ return parsed;
1553
+ }
1554
+ var WIZARD_STATE_PATH = ".wizard-state.json";
1555
+ // src/schemas/registry.ts
1556
+ function deriveCalledBy(manifests) {
1557
+ const calledBy = {};
1558
+ for (const [workerName, manifest] of Object.entries(manifests)) {
1559
+ if (!calledBy[workerName])
1560
+ calledBy[workerName] = new Set;
1561
+ for (const svc of manifest.services) {
1562
+ if (!calledBy[svc.service])
1563
+ calledBy[svc.service] = new Set;
1564
+ calledBy[svc.service].add(workerName);
1565
+ }
1566
+ }
1567
+ const result = {};
1568
+ for (const [worker, callers] of Object.entries(calledBy)) {
1569
+ result[worker] = [...callers].sort();
1570
+ }
1571
+ return result;
1572
+ }
1573
+ var manifests = {
1574
+ hoox: {
1575
+ name: "hoox",
1576
+ path: "workers/hoox",
1577
+ vars: {
1578
+ WEBHOOK_API_KEY_BINDING: {
1579
+ type: "secret",
1580
+ description: "External webhook auth key"
1581
+ },
1582
+ INTERNAL_KEY_BINDING: {
1583
+ type: "secret",
1584
+ description: "Inter-worker auth key"
1585
+ },
1586
+ HA_TOKEN_BINDING: { type: "secret", description: "Home Assistant token" }
1587
+ },
1588
+ services: [
1589
+ {
1590
+ binding: "TRADE_SERVICE",
1591
+ service: "trade-worker",
1592
+ description: "Trading functionality"
1593
+ },
1594
+ {
1595
+ binding: "TELEGRAM_SERVICE",
1596
+ service: "telegram-worker",
1597
+ description: "Notifications"
1598
+ }
1599
+ ],
1600
+ infrastructure: {
1601
+ kv: [
1602
+ { binding: "SESSIONS_KV", description: "Webhook session storage" },
1603
+ {
1604
+ binding: "CONFIG_KV",
1605
+ description: "Configuration + rate limiter state"
1606
+ }
1607
+ ],
1608
+ vectorize: [{ binding: "VECTORIZE_INDEX", index: "my-rag-index" }],
1609
+ ai: true,
1610
+ queues: { producer: ["trade-execution"] },
1611
+ durableObjects: [
1612
+ { name: "IDEMPOTENCY_STORE", className: "IdempotencyStore" }
1613
+ ]
1614
+ },
1615
+ middleware: [
1616
+ "requireAuth",
1617
+ "requireInternalAuth",
1618
+ "cors",
1619
+ "rateLimit",
1620
+ "logger",
1621
+ "validate"
1622
+ ]
1623
+ },
1624
+ "trade-worker": {
1625
+ name: "trade-worker",
1626
+ path: "workers/trade-worker",
1627
+ vars: {
1628
+ INTERNAL_KEY_BINDING: {
1629
+ type: "secret",
1630
+ description: "Inter-worker auth key"
1631
+ },
1632
+ TELEGRAM_INTERNAL_KEY_BINDING: {
1633
+ type: "secret",
1634
+ description: "Telegram outbound auth"
1635
+ },
1636
+ BINANCE_KEY_BINDING: { type: "secret", description: "Binance API key" },
1637
+ BINANCE_SECRET_BINDING: {
1638
+ type: "secret",
1639
+ description: "Binance API secret"
1640
+ },
1641
+ MEXC_KEY_BINDING: { type: "secret", description: "MEXC API key" },
1642
+ MEXC_SECRET_BINDING: { type: "secret", description: "MEXC API secret" },
1643
+ BYBIT_KEY_BINDING: { type: "secret", description: "Bybit API key" },
1644
+ BYBIT_SECRET_BINDING: { type: "secret", description: "Bybit API secret" }
1645
+ },
1646
+ services: [
1647
+ {
1648
+ binding: "D1_SERVICE",
1649
+ service: "d1-worker",
1650
+ description: "Database operations"
1651
+ },
1652
+ {
1653
+ binding: "TELEGRAM_SERVICE",
1654
+ service: "telegram-worker",
1655
+ description: "Notifications"
1656
+ }
1657
+ ],
1658
+ infrastructure: {
1659
+ kv: [{ binding: "CONFIG_KV", description: "Configuration" }],
1660
+ d1: [
1661
+ {
1662
+ binding: "DB",
1663
+ database: "trade-data-db",
1664
+ description: "Trade operations"
1665
+ }
1666
+ ],
1667
+ r2: [
1668
+ {
1669
+ binding: "REPORTS_BUCKET",
1670
+ bucket: "trade-reports",
1671
+ description: "Trade reports"
1672
+ },
1673
+ {
1674
+ binding: "SYSTEM_LOGS_BUCKET",
1675
+ bucket: "hoox-system-logs",
1676
+ description: "Verbose exchange logs"
1677
+ }
1678
+ ],
1679
+ queues: { consumer: ["trade-execution"] }
1680
+ },
1681
+ middleware: ["requireInternalAuth"]
1682
+ },
1683
+ "telegram-worker": {
1684
+ name: "telegram-worker",
1685
+ path: "workers/telegram-worker",
1686
+ vars: {
1687
+ INTERNAL_KEY_BINDING: {
1688
+ type: "secret",
1689
+ description: "Inter-worker auth key"
1690
+ },
1691
+ TG_BOT_TOKEN_BINDING: {
1692
+ type: "secret",
1693
+ description: "Telegram bot token"
1694
+ },
1695
+ TG_CHAT_ID_BINDING: { type: "secret", description: "Default chat ID" },
1696
+ TELEGRAM_SECRET_TOKEN: {
1697
+ type: "secret",
1698
+ description: "Webhook verification token"
1699
+ }
1700
+ },
1701
+ services: [],
1702
+ infrastructure: {
1703
+ kv: [{ binding: "CONFIG_KV", description: "Configuration" }],
1704
+ r2: [
1705
+ {
1706
+ binding: "UPLOADS_BUCKET",
1707
+ bucket: "user-uploads",
1708
+ description: "User uploaded files"
1709
+ }
1710
+ ],
1711
+ vectorize: [{ binding: "VECTORIZE_INDEX", index: "my-rag-index" }],
1712
+ ai: true
1713
+ },
1714
+ middleware: ["requireInternalAuth"]
1715
+ },
1716
+ "d1-worker": {
1717
+ name: "d1-worker",
1718
+ path: "workers/d1-worker",
1719
+ vars: {
1720
+ INTERNAL_KEY_BINDING: {
1721
+ type: "secret",
1722
+ description: "Inter-worker auth key"
1723
+ }
1724
+ },
1725
+ services: [],
1726
+ infrastructure: {
1727
+ kv: [{ binding: "CONFIG_KV", description: "Configuration" }],
1728
+ d1: [
1729
+ {
1730
+ binding: "DB",
1731
+ database: "trade-data-db",
1732
+ description: "Main database"
1733
+ }
1734
+ ]
1735
+ },
1736
+ middleware: ["requireInternalAuth"]
1737
+ },
1738
+ "web3-wallet-worker": {
1739
+ name: "web3-wallet-worker",
1740
+ path: "workers/web3-wallet-worker",
1741
+ vars: {
1742
+ WALLET_PK_SECRET: { type: "secret", description: "Wallet private key" },
1743
+ WALLET_MNEMONIC_SECRET: {
1744
+ type: "secret",
1745
+ description: "Wallet mnemonic phrase"
1746
+ }
1747
+ },
1748
+ services: [
1749
+ {
1750
+ binding: "TELEGRAM_SERVICE",
1751
+ service: "telegram-worker",
1752
+ description: "Notifications"
1753
+ }
1754
+ ],
1755
+ infrastructure: {},
1756
+ middleware: ["requireInternalAuth"]
1757
+ },
1758
+ "agent-worker": {
1759
+ name: "agent-worker",
1760
+ path: "workers/agent-worker",
1761
+ vars: {
1762
+ AGENT_INTERNAL_KEY: { type: "secret", description: "Agent worker auth" },
1763
+ INTERNAL_KEY_BINDING: {
1764
+ type: "secret",
1765
+ description: "Inter-worker auth key"
1766
+ }
1767
+ },
1768
+ services: [
1769
+ {
1770
+ binding: "TRADE_SERVICE",
1771
+ service: "trade-worker",
1772
+ description: "Trading functionality"
1773
+ },
1774
+ {
1775
+ binding: "TELEGRAM_SERVICE",
1776
+ service: "telegram-worker",
1777
+ description: "Notifications"
1778
+ }
1779
+ ],
1780
+ infrastructure: {
1781
+ kv: [{ binding: "CONFIG_KV", description: "Configuration" }],
1782
+ d1: [
1783
+ {
1784
+ binding: "DB",
1785
+ database: "trade-data-db",
1786
+ description: "Portfolio queries"
1787
+ }
1788
+ ],
1789
+ ai: true
1790
+ },
1791
+ middleware: ["requireInternalAuth"],
1792
+ cron: ["*/5 * * * *"]
1793
+ },
1794
+ "email-worker": {
1795
+ name: "email-worker",
1796
+ path: "workers/email-worker",
1797
+ vars: {
1798
+ INTERNAL_KEY_BINDING: {
1799
+ type: "secret",
1800
+ description: "Inter-worker auth key"
1801
+ },
1802
+ EMAIL_HOST_BINDING: { type: "secret", description: "Email IMAP host" },
1803
+ EMAIL_USER_BINDING: { type: "secret", description: "Email username" },
1804
+ EMAIL_PASS_BINDING: { type: "secret", description: "Email password" },
1805
+ TRADE_WORKER_NAME: {
1806
+ type: "plaintext",
1807
+ description: "Trade worker service name",
1808
+ default: "trade-worker"
1809
+ },
1810
+ USE_IMAP: {
1811
+ type: "plaintext",
1812
+ description: "Use IMAP for email",
1813
+ default: "false"
1814
+ },
1815
+ MAILGUN_API_KEY: { type: "secret", description: "Mailgun API key" },
1816
+ EMAIL_SCAN_SUBJECT: { type: "secret", description: "Email scan subject" }
1817
+ },
1818
+ services: [
1819
+ {
1820
+ binding: "TRADE_SERVICE",
1821
+ service: "trade-worker",
1822
+ description: "Trading functionality"
1823
+ }
1824
+ ],
1825
+ infrastructure: {
1826
+ kv: [{ binding: "CONFIG_KV", description: "Configuration" }]
1827
+ },
1828
+ middleware: ["requireInternalAuth"],
1829
+ cron: ["*/5 * * * *"]
1830
+ },
1831
+ "analytics-worker": {
1832
+ name: "analytics-worker",
1833
+ path: "workers/analytics-worker",
1834
+ vars: {
1835
+ CLOUDFLARE_API_TOKEN: {
1836
+ type: "secret",
1837
+ description: "CF API token for Analytics SQL"
1838
+ },
1839
+ CLOUDFLARE_ACCOUNT_ID: {
1840
+ type: "plaintext",
1841
+ description: "CF Account ID"
1842
+ }
1843
+ },
1844
+ services: [],
1845
+ infrastructure: {
1846
+ analyticsEngine: true
1847
+ },
1848
+ middleware: []
1849
+ },
1850
+ "report-worker": {
1851
+ name: "report-worker",
1852
+ path: "workers/report-worker",
1853
+ vars: {
1854
+ CF_API_TOKEN_BINDING: {
1855
+ type: "secret",
1856
+ description: "CF API token for Browser Rendering"
1857
+ },
1858
+ ACCOUNT_ID: { type: "plaintext", description: "CF Account ID" }
1859
+ },
1860
+ services: [
1861
+ {
1862
+ binding: "D1_SERVICE",
1863
+ service: "d1-worker",
1864
+ description: "Database queries"
1865
+ },
1866
+ {
1867
+ binding: "TELEGRAM_SERVICE",
1868
+ service: "telegram-worker",
1869
+ description: "Notifications"
1870
+ }
1871
+ ],
1872
+ infrastructure: {
1873
+ r2: [
1874
+ {
1875
+ binding: "REPORTS_BUCKET",
1876
+ bucket: "trade-reports",
1877
+ description: "PDF reports"
1878
+ }
1879
+ ]
1880
+ },
1881
+ middleware: ["requireInternalAuth"],
1882
+ cron: ["0 8 * * *", "0 18 * * *"]
1883
+ },
1884
+ dashboard: {
1885
+ name: "dashboard",
1886
+ path: "workers/dashboard",
1887
+ vars: {
1888
+ DASHBOARD_USER: {
1889
+ type: "secret",
1890
+ description: "Dashboard admin username"
1891
+ },
1892
+ DASHBOARD_PASS: {
1893
+ type: "secret",
1894
+ description: "Dashboard admin password"
1895
+ },
1896
+ SESSION_SECRET: { type: "secret", description: "Session encryption key" }
1897
+ },
1898
+ services: [
1899
+ {
1900
+ binding: "D1_SERVICE",
1901
+ service: "d1-worker",
1902
+ description: "Database queries"
1903
+ },
1904
+ {
1905
+ binding: "AGENT_SERVICE",
1906
+ service: "agent-worker",
1907
+ description: "AI risk data"
1908
+ }
1909
+ ],
1910
+ infrastructure: {
1911
+ kv: [{ binding: "CONFIG_KV", description: "Configuration" }],
1912
+ ai: true
1913
+ },
1914
+ middleware: []
1915
+ }
1916
+ };
1917
+ var CALLED_BY = deriveCalledBy(manifests);
1918
+ var WORKER_MANIFESTS = manifests;
1919
+ var WORKER_NAMES = Object.keys(manifests).sort();
1920
+ // src/schemas/validators.ts
1921
+ import { parse } from "jsonc-parser";
1922
+ function validateWranglerJsonc(workerName, manifest, jsoncContent) {
1923
+ const errors = [];
1924
+ let parsed;
1925
+ try {
1926
+ parsed = parse(jsoncContent);
1927
+ } catch {
1928
+ errors.push({
1929
+ worker: workerName,
1930
+ severity: "error",
1931
+ message: "Failed to parse wrangler.jsonc",
1932
+ file: "wrangler.jsonc"
1933
+ });
1934
+ return errors;
1935
+ }
1936
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
1937
+ errors.push({
1938
+ worker: workerName,
1939
+ severity: "error",
1940
+ message: "Failed to parse wrangler.jsonc \u2014 result is not a valid object",
1941
+ file: "wrangler.jsonc"
1942
+ });
1943
+ return errors;
1944
+ }
1945
+ const declaredVars = parsed?.vars ?? {};
1946
+ for (const [name, def] of Object.entries(manifest.vars)) {
1947
+ if (!(name in declaredVars)) {
1948
+ errors.push({
1949
+ worker: workerName,
1950
+ severity: "error",
1951
+ message: `Missing var: ${name} (${def.description})`,
1952
+ file: "wrangler.jsonc"
1953
+ });
1954
+ }
1955
+ }
1956
+ for (const [name, def] of Object.entries(manifest.vars)) {
1957
+ 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") {
1958
+ errors.push({
1959
+ worker: workerName,
1960
+ severity: "warning",
1961
+ message: `Secret var "${name}" does not end with _BINDING or _SECRET suffix`,
1962
+ file: "wrangler.jsonc"
1963
+ });
1964
+ }
1965
+ }
1966
+ const declaredServices = parsed?.services ?? [];
1967
+ for (const expected of manifest.services) {
1968
+ const match = declaredServices.find((s) => s.binding === expected.binding && s.service === expected.service);
1969
+ if (!match) {
1970
+ const declared = declaredServices.find((s) => s.binding === expected.binding);
1971
+ if (declared) {
1972
+ errors.push({
1973
+ worker: workerName,
1974
+ severity: "error",
1975
+ message: `Service "${expected.binding}" points to "${declared.service}" but manifest expects "${expected.service}"`,
1976
+ file: "wrangler.jsonc"
1977
+ });
1978
+ } else {
1979
+ errors.push({
1980
+ worker: workerName,
1981
+ severity: "error",
1982
+ message: `Missing service binding: ${expected.binding} -> ${expected.service}`,
1983
+ file: "wrangler.jsonc"
1984
+ });
1985
+ }
1986
+ }
1987
+ }
1988
+ for (const declared of declaredServices) {
1989
+ const expected = manifest.services.find((s) => s.binding === declared.binding);
1990
+ if (!expected) {
1991
+ errors.push({
1992
+ worker: workerName,
1993
+ severity: "warning",
1994
+ message: `Unexpected service binding: ${declared.binding}`,
1995
+ file: "wrangler.jsonc"
1996
+ });
1997
+ }
1998
+ }
1999
+ return errors;
2000
+ }
2001
+ function validateRootSecrets(workerName, manifest, rootJsoncContent) {
2002
+ const errors = [];
2003
+ let parsed;
2004
+ try {
2005
+ parsed = parse(rootJsoncContent);
2006
+ } catch {
2007
+ errors.push({
2008
+ worker: workerName,
2009
+ severity: "error",
2010
+ message: "Failed to parse root wrangler.jsonc"
2011
+ });
2012
+ return errors;
2013
+ }
2014
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
2015
+ errors.push({
2016
+ worker: workerName,
2017
+ severity: "error",
2018
+ message: "Failed to parse root wrangler.jsonc \u2014 result is not a valid object"
2019
+ });
2020
+ return errors;
2021
+ }
2022
+ const rootSecrets = parsed?.workers?.[workerName]?.secrets ?? [];
2023
+ const expectedSecrets = Object.entries(manifest.vars).filter(([_, def]) => def.type === "secret").map(([name]) => name);
2024
+ for (const expected of expectedSecrets) {
2025
+ if (!rootSecrets.includes(expected)) {
2026
+ errors.push({
2027
+ worker: workerName,
2028
+ severity: "error",
2029
+ message: `Secret "${expected}" missing from root wrangler.jsonc workers.${workerName}.secrets`,
2030
+ file: "wrangler.jsonc (root)"
2031
+ });
2032
+ }
2033
+ }
2034
+ return errors;
2035
+ }
2036
+ function validateDevVars(workerName, manifest, devVarsContent) {
2037
+ const errors = [];
2038
+ const vars = new Map;
2039
+ for (const line of devVarsContent.split(`
2040
+ `)) {
2041
+ const trimmed = line.trim();
2042
+ if (trimmed === "" || trimmed.startsWith("#"))
2043
+ continue;
2044
+ const eqIdx = trimmed.indexOf("=");
2045
+ if (eqIdx < 1)
2046
+ continue;
2047
+ vars.set(trimmed.substring(0, eqIdx).trim(), trimmed.substring(eqIdx + 1).trim());
2048
+ }
2049
+ for (const [name, def] of Object.entries(manifest.vars)) {
2050
+ if (def.type !== "secret")
2051
+ continue;
2052
+ if (!vars.has(name)) {
2053
+ errors.push({
2054
+ worker: workerName,
2055
+ severity: "error",
2056
+ message: `Missing "${name}" in .dev.vars`,
2057
+ file: ".dev.vars"
2058
+ });
2059
+ }
2060
+ }
2061
+ return errors;
2062
+ }
2063
+ function generateWranglerJsonc(manifest) {
2064
+ const parts = [];
2065
+ parts.push(`{`);
2066
+ parts.push(` "name": "${manifest.name}",`);
2067
+ parts.push(` "main": "src/index.ts",`);
2068
+ const varKeys = Object.keys(manifest.vars);
2069
+ if (varKeys.length > 0) {
2070
+ parts.push(` "vars": {`);
2071
+ for (const [i, name] of varKeys.entries()) {
2072
+ const def = manifest.vars[name];
2073
+ const comma = i < varKeys.length - 1 ? "," : "";
2074
+ const val = def.type === "secret" ? "__SECRET__" : JSON.stringify(def.default ?? "");
2075
+ parts.push(` "${name}": ${val}${comma}`);
2076
+ }
2077
+ parts.push(` },`);
2078
+ }
2079
+ if (manifest.services.length > 0) {
2080
+ parts.push(` "services": [`);
2081
+ for (const [i, svc] of manifest.services.entries()) {
2082
+ const comma = i < manifest.services.length - 1 ? "," : "";
2083
+ parts.push(` { "binding": "${svc.binding}", "service": "${svc.service}" }${comma}`);
2084
+ }
2085
+ parts.push(` ],`);
2086
+ }
2087
+ const infra = manifest.infrastructure;
2088
+ if (infra.kv && infra.kv.length > 0) {
2089
+ parts.push(` "kv_namespaces": [`);
2090
+ for (const [i, binding] of infra.kv.entries()) {
2091
+ const comma = i < infra.kv.length - 1 ? "," : "";
2092
+ parts.push(` { "binding": "${binding.binding}", "id": "" }${comma}`);
2093
+ }
2094
+ parts.push(` ],`);
2095
+ }
2096
+ if (infra.d1 && infra.d1.length > 0) {
2097
+ parts.push(` "d1_databases": [`);
2098
+ for (const [i, binding] of infra.d1.entries()) {
2099
+ const comma = i < infra.d1.length - 1 ? "," : "";
2100
+ parts.push(` { "binding": "${binding.binding}", "database_name": "${binding.database}", "database_id": "" }${comma}`);
2101
+ }
2102
+ parts.push(` ],`);
2103
+ }
2104
+ if (infra.r2 && infra.r2.length > 0) {
2105
+ parts.push(` "r2_buckets": [`);
2106
+ for (const [i, binding] of infra.r2.entries()) {
2107
+ const comma = i < infra.r2.length - 1 ? "," : "";
2108
+ parts.push(` { "binding": "${binding.binding}", "bucket_name": "${binding.bucket}" }${comma}`);
2109
+ }
2110
+ parts.push(` ],`);
2111
+ }
2112
+ if (infra.ai) {
2113
+ parts.push(` "ai": { "binding": "AI" },`);
2114
+ }
2115
+ if (infra.analyticsEngine) {
2116
+ parts.push(` "analytics_engine_datasets": [{ "binding": "ANALYTICS_ENGINE", "dataset": "hoox-analytics" }],`);
2117
+ }
2118
+ if (infra.vectorize && infra.vectorize.length > 0) {
2119
+ parts.push(` "vectorize": [`);
2120
+ for (const [i, binding] of infra.vectorize.entries()) {
2121
+ const comma = i < infra.vectorize.length - 1 ? "," : "";
2122
+ parts.push(` { "binding": "${binding.binding}", "index_name": "${binding.index}" }${comma}`);
2123
+ }
2124
+ parts.push(` ],`);
2125
+ }
2126
+ if (infra.queues) {
2127
+ if (infra.queues.producer && infra.queues.producer.length > 0) {
2128
+ parts.push(` "queues": { "producers": [`);
2129
+ for (const [i, q] of infra.queues.producer.entries()) {
2130
+ const comma = i < infra.queues.producer.length - 1 ? "," : "";
2131
+ parts.push(` { "queue": "${q}", "binding": "" }${comma}`);
2132
+ }
2133
+ parts.push(` ] },`);
2134
+ }
2135
+ if (infra.queues.consumer && infra.queues.consumer.length > 0) {
2136
+ parts.push(` "queues": { "consumers": [`);
2137
+ for (const [i, q] of infra.queues.consumer.entries()) {
2138
+ const comma = i < infra.queues.consumer.length - 1 ? "," : "";
2139
+ parts.push(` { "queue": "${q}" }${comma}`);
2140
+ }
2141
+ parts.push(` ] },`);
2142
+ }
2143
+ }
2144
+ if (infra.durableObjects && infra.durableObjects.length > 0) {
2145
+ parts.push(` "durable_objects": { "bindings": [`);
2146
+ for (const [i, doBinding] of infra.durableObjects.entries()) {
2147
+ const comma = i < infra.durableObjects.length - 1 ? "," : "";
2148
+ parts.push(` { "name": "${doBinding.name}", "class_name": "${doBinding.className}" }${comma}`);
2149
+ }
2150
+ parts.push(` ] },`);
2151
+ }
2152
+ parts.push(`}`);
2153
+ return parts.join(`
2154
+ `) + `
2155
+ `;
2156
+ }
2157
+ function generateDevVars(manifest) {
2158
+ const lines = [];
2159
+ lines.push(`# ${manifest.name} \u2014 Environment Variables`);
2160
+ lines.push(`# Auto-generated from worker manifest schema`);
2161
+ lines.push(``);
2162
+ for (const [name, def] of Object.entries(manifest.vars)) {
2163
+ if (def.type === "secret") {
2164
+ lines.push(`${name}=placeholder_${name.toLowerCase()}`);
2165
+ }
2166
+ }
2167
+ lines.push(``);
2168
+ return lines.join(`
2169
+ `);
2170
+ }
2171
+ function validateAll(workerName, manifest, files) {
2172
+ const errors = [
2173
+ ...validateWranglerJsonc(workerName, manifest, files.wranglerJsonc),
2174
+ ...validateRootSecrets(workerName, manifest, files.rootWranglerJsonc),
2175
+ ...validateDevVars(workerName, manifest, files.devVars)
2176
+ ];
2177
+ return {
2178
+ worker: workerName,
2179
+ passed: errors.filter((e) => e.severity === "error").length === 0,
2180
+ errors
2181
+ };
2182
+ }
2183
+ // src/stores/ui-store.ts
2184
+ import { create } from "zustand";
2185
+ import { immer } from "zustand/middleware/immer";
2186
+ var DEFAULT_VIEW = "dashboard";
2187
+ var initialState = {
2188
+ activeView: DEFAULT_VIEW,
2189
+ sidebarExpanded: true,
2190
+ modal: null,
2191
+ commandPaletteOpen: false,
2192
+ previousView: null
2193
+ };
2194
+ var useUIStore = create()(immer((set) => ({
2195
+ ...initialState,
2196
+ setView: (view) => set((state) => {
2197
+ if (state.activeView !== view) {
2198
+ state.previousView = state.activeView;
2199
+ }
2200
+ state.activeView = view;
2201
+ state.commandPaletteOpen = false;
2202
+ }),
2203
+ toggleSidebar: () => set((state) => {
2204
+ state.sidebarExpanded = !state.sidebarExpanded;
2205
+ }),
2206
+ openPalette: () => set((state) => {
2207
+ state.commandPaletteOpen = true;
2208
+ }),
2209
+ closePalette: () => set((state) => {
2210
+ state.commandPaletteOpen = false;
2211
+ }),
2212
+ showModal: (modal) => set((state) => {
2213
+ state.modal = modal;
2214
+ }),
2215
+ dismissModal: () => set((state) => {
2216
+ state.modal = null;
2217
+ }),
2218
+ goBack: () => set((state) => {
2219
+ if (state.previousView) {
2220
+ state.activeView = state.previousView;
2221
+ state.previousView = null;
2222
+ }
2223
+ })
2224
+ })));
2225
+
2226
+ // src/stores/service-store.ts
2227
+ import { create as create2 } from "zustand";
2228
+ import { immer as immer2 } from "zustand/middleware/immer";
2229
+ var BACKOFF_SEQUENCE = [1000, 2000, 4000, 8000, 16000];
2230
+ var MAX_RETRIES2 = 5;
2231
+ function ringPush(arr, item, max) {
2232
+ const next = [...arr, item];
2233
+ return next.length > max ? next.slice(next.length - max) : next;
2234
+ }
2235
+ var MAX_TRADES = 500;
2236
+ var MAX_ALERTS = 100;
2237
+ var MAX_LOGS = 1000;
2238
+ var initialState2 = {
2239
+ workers: [],
2240
+ tradeStream: [],
2241
+ alerts: [],
2242
+ logs: [],
2243
+ metrics: null,
2244
+ connectionStatus: "offline",
2245
+ lastUpdated: 0,
2246
+ selectedWorkerId: null,
2247
+ retryCount: 0,
2248
+ lastError: null,
2249
+ lastSuccessfulFetch: 0,
2250
+ reconnectDelay: 0,
2251
+ disconnectedAt: null
2252
+ };
2253
+ function getBackoffDelay(retryCount) {
2254
+ if (retryCount <= 0)
2255
+ return 0;
2256
+ const index = Math.min(retryCount - 1, BACKOFF_SEQUENCE.length - 1);
2257
+ return BACKOFF_SEQUENCE[index];
2258
+ }
2259
+ var useServiceStore = create2()(immer2((set, get) => ({
2260
+ ...initialState2,
2261
+ fetchWorkers: async () => {
2262
+ const { hooxFetch: hooxFetch2 } = await Promise.resolve().then(() => (init_api_client(), exports_api_client));
2263
+ try {
2264
+ const data = await hooxFetch2("/workers");
2265
+ set((state) => {
2266
+ state.workers = data;
2267
+ state.lastUpdated = Date.now();
2268
+ state.lastSuccessfulFetch = Date.now();
2269
+ if (state.connectionStatus === "polling" || state.connectionStatus === "reconnecting" || state.connectionStatus === "offline") {
2270
+ state.connectionStatus = "connected";
2271
+ state.retryCount = 0;
2272
+ state.lastError = null;
2273
+ state.reconnectDelay = 0;
2274
+ state.disconnectedAt = null;
2275
+ }
2276
+ });
2277
+ } catch (error) {
2278
+ const msg = error instanceof Error ? error.message : "Unknown fetch error";
2279
+ set((state) => {
2280
+ if (state.connectionStatus === "connected" || state.connectionStatus === "polling") {
2281
+ state.connectionStatus = "reconnecting";
2282
+ state.retryCount = state.retryCount + 1;
2283
+ state.lastError = msg;
2284
+ state.disconnectedAt = state.disconnectedAt ?? Date.now();
2285
+ if (state.retryCount >= MAX_RETRIES2) {
2286
+ state.connectionStatus = "offline";
2287
+ state.lastError = `Connection lost after ${MAX_RETRIES2} retries`;
2288
+ } else {
2289
+ state.reconnectDelay = getBackoffDelay(state.retryCount);
2290
+ }
2291
+ }
2292
+ if (state.connectionStatus === "reconnecting") {
2293
+ state.lastError = msg;
2294
+ }
2295
+ });
2296
+ }
2297
+ },
2298
+ streamTrades: async () => {
2299
+ const { subscribeSSE: subscribeSSE2 } = await Promise.resolve().then(() => (init_sse(), exports_sse));
2300
+ try {
2301
+ await subscribeSSE2("/trades/stream", (trade) => {
2302
+ get().pushTrade(trade);
2303
+ });
2304
+ } catch {}
2305
+ },
2306
+ streamLogs: async () => {
2307
+ const { subscribeSSE: subscribeSSE2 } = await Promise.resolve().then(() => (init_sse(), exports_sse));
2308
+ try {
2309
+ await subscribeSSE2("/logs/stream", (log) => {
2310
+ get().pushLog(log);
2311
+ });
2312
+ } catch {}
2313
+ },
2314
+ addAlert: (alert) => set((state) => {
2315
+ state.alerts = ringPush(state.alerts, alert, MAX_ALERTS);
2316
+ }),
2317
+ addAlerts: (alerts) => set((state) => {
2318
+ const merged = [...state.alerts, ...alerts];
2319
+ state.alerts = merged.length > MAX_ALERTS ? merged.slice(merged.length - MAX_ALERTS) : merged;
2320
+ }),
2321
+ setConnectionStatus: (status) => set((state) => {
2322
+ if (status === "connected") {
2323
+ state.retryCount = 0;
2324
+ state.lastError = null;
2325
+ state.reconnectDelay = 0;
2326
+ state.disconnectedAt = null;
2327
+ }
2328
+ if (status === "offline" || status === "reconnecting") {
2329
+ state.disconnectedAt = state.disconnectedAt ?? Date.now();
2330
+ }
2331
+ state.connectionStatus = status;
2332
+ }),
2333
+ selectWorker: (id) => set((state) => {
2334
+ state.selectedWorkerId = id;
2335
+ }),
2336
+ setWorkers: (workers) => set((state) => {
2337
+ state.workers = workers;
2338
+ state.lastUpdated = Date.now();
2339
+ }),
2340
+ pushTrade: (trade) => set((state) => {
2341
+ state.tradeStream = ringPush(state.tradeStream, trade, MAX_TRADES);
2342
+ }),
2343
+ pushLog: (log) => set((state) => {
2344
+ state.logs = ringPush(state.logs, log, MAX_LOGS);
2345
+ }),
2346
+ setMetrics: (metrics) => set((state) => {
2347
+ state.metrics = metrics;
2348
+ state.lastUpdated = metrics.lastUpdated;
2349
+ }),
2350
+ handleConnectionSuccess: () => set((state) => {
2351
+ state.lastSuccessfulFetch = Date.now();
2352
+ state.retryCount = 0;
2353
+ state.lastError = null;
2354
+ state.reconnectDelay = 0;
2355
+ state.disconnectedAt = null;
2356
+ if (state.connectionStatus === "reconnecting" || state.connectionStatus === "polling") {
2357
+ state.connectionStatus = "connected";
2358
+ }
2359
+ }),
2360
+ handleConnectionFailure: (errorMessage) => set((state) => {
2361
+ state.lastError = errorMessage;
2362
+ state.disconnectedAt = state.disconnectedAt ?? Date.now();
2363
+ if (state.connectionStatus === "connected" || state.connectionStatus === "polling") {
2364
+ state.connectionStatus = "reconnecting";
2365
+ state.retryCount = 1;
2366
+ state.reconnectDelay = getBackoffDelay(1);
2367
+ } else if (state.connectionStatus === "reconnecting") {
2368
+ state.retryCount = state.retryCount + 1;
2369
+ if (state.retryCount >= MAX_RETRIES2) {
2370
+ state.connectionStatus = "offline";
2371
+ state.lastError = `Connection lost after ${MAX_RETRIES2} retries`;
2372
+ state.reconnectDelay = 0;
2373
+ } else {
2374
+ state.reconnectDelay = getBackoffDelay(state.retryCount);
2375
+ }
2376
+ }
2377
+ }),
2378
+ resetRetries: () => set((state) => {
2379
+ state.retryCount = 0;
2380
+ state.lastError = null;
2381
+ state.reconnectDelay = 0;
2382
+ state.disconnectedAt = null;
2383
+ }),
2384
+ forceRetry: () => set((state) => {
2385
+ if (state.connectionStatus === "offline") {
2386
+ state.connectionStatus = "polling";
2387
+ state.retryCount = 0;
2388
+ state.lastError = null;
2389
+ state.reconnectDelay = 0;
2390
+ }
2391
+ })
2392
+ })));
2393
+
2394
+ // src/stores/config-store.ts
2395
+ import { create as create3 } from "zustand";
2396
+ import { immer as immer3 } from "zustand/middleware/immer";
2397
+ import { persist, createJSONStorage } from "zustand/middleware";
2398
+ import { homedir as homedir3 } from "os";
2399
+ import { join as join3 } from "path";
2400
+ var bunConfigStorage = {
2401
+ getItem: async (_name) => {
2402
+ try {
2403
+ const file = Bun.file(join3(homedir3(), ".hoox", "config.json"));
2404
+ const exists = await file.exists();
2405
+ if (!exists)
2406
+ return null;
2407
+ return await file.text();
2408
+ } catch {
2409
+ return null;
2410
+ }
2411
+ },
2412
+ setItem: async (_name, value) => {
2413
+ const dir = join3(homedir3(), ".hoox");
2414
+ try {
2415
+ await Bun.file(dir).exists();
2416
+ } catch {}
2417
+ const filePath = join3(dir, "config.json");
2418
+ await Bun.write(filePath, value);
2419
+ },
2420
+ removeItem: async (_name) => {}
2421
+ };
2422
+ var DEFAULT_SHORTCUTS = {
2423
+ "command-palette": "Ctrl+P",
2424
+ "toggle-sidebar": "Ctrl+B",
2425
+ "force-refresh": "Ctrl+R",
2426
+ quit: "Ctrl+Q",
2427
+ "view-1": "Ctrl+1",
2428
+ "view-2": "Ctrl+2",
2429
+ "view-3": "Ctrl+3",
2430
+ "view-4": "Ctrl+4",
2431
+ "view-5": "Ctrl+5",
2432
+ "view-6": "Ctrl+6",
2433
+ "view-7": "Ctrl+7",
2434
+ "view-8": "Ctrl+8",
2435
+ "view-9": "Ctrl+9",
2436
+ "view-10": "Ctrl+0"
2437
+ };
2438
+ var DEFAULT_LOG_FILTER = {
2439
+ levels: ["info", "warn", "error"],
2440
+ workers: [],
2441
+ searchText: ""
2442
+ };
2443
+ var DEFAULT_NOTIFICATIONS = {
2444
+ alerts: true,
2445
+ trades: false,
2446
+ debug: false,
2447
+ system: true
2448
+ };
2449
+ var defaults = {
2450
+ theme: "dark",
2451
+ refreshIntervalMs: 500,
2452
+ defaultView: "dashboard",
2453
+ activeExchanges: [],
2454
+ keyboardShortcuts: { ...DEFAULT_SHORTCUTS },
2455
+ logFilters: { ...DEFAULT_LOG_FILTER },
2456
+ notifications: { ...DEFAULT_NOTIFICATIONS },
2457
+ soundEnabled: false
2458
+ };
2459
+ var useConfigStore = create3()(persist(immer3((set) => ({
2460
+ ...defaults,
2461
+ updateConfig: (partial) => set((state) => {
2462
+ if (partial.theme !== undefined)
2463
+ state.theme = partial.theme;
2464
+ if (partial.refreshIntervalMs !== undefined)
2465
+ state.refreshIntervalMs = partial.refreshIntervalMs;
2466
+ if (partial.defaultView !== undefined)
2467
+ state.defaultView = partial.defaultView;
2468
+ if (partial.activeExchanges !== undefined)
2469
+ state.activeExchanges = partial.activeExchanges;
2470
+ if (partial.keyboardShortcuts !== undefined)
2471
+ state.keyboardShortcuts = partial.keyboardShortcuts;
2472
+ if (partial.logFilters !== undefined)
2473
+ state.logFilters = partial.logFilters;
2474
+ if (partial.notifications !== undefined)
2475
+ state.notifications = partial.notifications;
2476
+ if (partial.soundEnabled !== undefined)
2477
+ state.soundEnabled = partial.soundEnabled;
2478
+ }),
2479
+ resetDefaults: () => set((state) => {
2480
+ Object.assign(state, defaults);
2481
+ }),
2482
+ setShortcut: (action, key) => set((state) => {
2483
+ state.keyboardShortcuts[action] = key;
2484
+ }),
2485
+ toggleNotification: (channel) => set((state) => {
2486
+ state.notifications[channel] = !state.notifications[channel];
2487
+ })
2488
+ })), {
2489
+ name: "hoox-config",
2490
+ storage: createJSONStorage(() => bunConfigStorage),
2491
+ partialize: (state) => ({
2492
+ theme: state.theme,
2493
+ refreshIntervalMs: state.refreshIntervalMs,
2494
+ defaultView: state.defaultView,
2495
+ activeExchanges: state.activeExchanges,
2496
+ keyboardShortcuts: state.keyboardShortcuts,
2497
+ logFilters: state.logFilters,
2498
+ notifications: state.notifications,
2499
+ soundEnabled: state.soundEnabled
2500
+ }),
2501
+ merge: (persisted, current) => ({
2502
+ ...current,
2503
+ ...persisted
2504
+ })
2505
+ }));
2506
+
2507
+ // src/index.ts
2508
+ init_api_client();
2509
+ init_sse();
2510
+ export {
2511
+ writeConfigSync,
2512
+ writeConfig,
2513
+ validateWranglerJsonc,
2514
+ validateRootSecrets,
2515
+ validateDevVars,
2516
+ validateConfig,
2517
+ validateAll,
2518
+ useUIStore,
2519
+ useServiceStore,
2520
+ useConfigStore,
2521
+ trackAnalytics,
2522
+ toError,
2523
+ subscribeSSE,
2524
+ serviceFetch,
2525
+ serializeState,
2526
+ saveSession,
2527
+ restoreSession,
2528
+ resolveDependencies,
2529
+ readConfigSync,
2530
+ readConfig,
2531
+ logKvTimestamp,
2532
+ hooxFetch,
2533
+ healthCheck,
2534
+ headersToObject,
2535
+ generateWranglerJsonc,
2536
+ generateDevVars,
2537
+ formatUptime,
2538
+ formatTimestamp,
2539
+ formatRequests,
2540
+ formatRelativeTime as formatRelativeTimeFromTime,
2541
+ formatRelativeTime2 as formatRelativeTime,
2542
+ formatPercent,
2543
+ formatNumber,
2544
+ formatMemory,
2545
+ formatLatency,
2546
+ formatDurationCompact,
2547
+ formatDuration2 as formatDuration,
2548
+ formatCurrency,
2549
+ formatCpu,
2550
+ formatCompactCurrency,
2551
+ deserializeState,
2552
+ createSuccessResponse,
2553
+ createJsonResponse,
2554
+ createErrorResponse,
2555
+ WorkerAPIError,
2556
+ WizardEngine,
2557
+ WebhookPayloadSchema,
2558
+ WORKER_NAMES,
2559
+ WORKER_MANIFESTS,
2560
+ WORKER_DEPENDENCIES,
2561
+ WIZARD_STATE_PATH,
2562
+ TradeSignalSchema,
2563
+ TradeActionSchema,
2564
+ TRADEMARK_NOTICE,
2565
+ TRADEMARKS,
2566
+ PositionSchema,
2567
+ PRESETS,
2568
+ KVKeys,
2569
+ INTEGRATIONS,
2570
+ FULL_LEGAL_NOTICE,
2571
+ ExchangeRouter,
2572
+ Errors,
2573
+ DISCLAIMER_HEADER,
2574
+ DISCLAIMER,
2575
+ D1Repository,
2576
+ Colors,
2577
+ COPYRIGHT,
2578
+ CALLED_BY,
2579
+ BaseExchangeClient,
2580
+ BalanceSchema
2581
+ };