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