@jango-blockchained/hoox-shared 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/README.md +17 -0
  2. package/dist/analytics.d.ts +23 -2
  3. package/dist/analytics.d.ts.map +1 -1
  4. package/dist/analytics.js +14 -3
  5. package/dist/api-client.d.ts +12 -2
  6. package/dist/api-client.d.ts.map +1 -1
  7. package/dist/api-client.js +74 -7
  8. package/dist/colors.d.ts +63 -25
  9. package/dist/colors.d.ts.map +1 -1
  10. package/dist/colors.js +60 -20
  11. package/dist/config.d.ts +34 -1
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +61 -6
  14. package/dist/cron-handler.d.ts +34 -0
  15. package/dist/cron-handler.d.ts.map +1 -0
  16. package/dist/cron-handler.js +48 -0
  17. package/dist/d1/index.js +50 -6
  18. package/dist/d1/repository.d.ts +5 -0
  19. package/dist/d1/repository.d.ts.map +1 -1
  20. package/dist/d1/repository.js +297 -0
  21. package/dist/d1/schemas.js +81 -0
  22. package/dist/errors.d.ts +1 -0
  23. package/dist/errors.d.ts.map +1 -1
  24. package/dist/errors.js +48 -5
  25. package/dist/exchanges/base-exchange-client.d.ts +68 -0
  26. package/dist/exchanges/base-exchange-client.d.ts.map +1 -0
  27. package/dist/exchanges/base-exchange-client.js +120 -0
  28. package/dist/exchanges/index.d.ts +3 -0
  29. package/dist/exchanges/index.d.ts.map +1 -0
  30. package/dist/exchanges/index.js +120 -0
  31. package/dist/exchanges/types.d.ts +19 -0
  32. package/dist/exchanges/types.d.ts.map +1 -0
  33. package/dist/exchanges/types.js +1 -0
  34. package/dist/health.js +48 -5
  35. package/dist/index.d.ts +30 -11
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +1058 -73
  38. package/dist/kvUtils.js +48 -5
  39. package/dist/middleware/auth.d.ts +46 -7
  40. package/dist/middleware/auth.d.ts.map +1 -1
  41. package/dist/middleware/auth.js +151 -0
  42. package/dist/middleware/cors.d.ts +28 -2
  43. package/dist/middleware/cors.d.ts.map +1 -1
  44. package/dist/middleware/cors.js +77 -0
  45. package/dist/middleware/index.d.ts +2 -2
  46. package/dist/middleware/index.d.ts.map +1 -1
  47. package/dist/middleware/index.js +361 -101
  48. package/dist/middleware/logger.d.ts +2 -1
  49. package/dist/middleware/logger.d.ts.map +1 -1
  50. package/dist/middleware/logger.js +202 -0
  51. package/dist/middleware/rate-limit.d.ts +3 -1
  52. package/dist/middleware/rate-limit.d.ts.map +1 -1
  53. package/dist/middleware/rate-limit.js +181 -0
  54. package/dist/middleware/security-headers.js +58 -0
  55. package/dist/middleware/validate.js +56 -0
  56. package/dist/operator-transport.d.ts +61 -0
  57. package/dist/operator-transport.d.ts.map +1 -0
  58. package/dist/operator-transport.js +87 -0
  59. package/dist/path-utils.d.ts +205 -0
  60. package/dist/path-utils.d.ts.map +1 -0
  61. package/dist/path-utils.js +188 -0
  62. package/dist/queue-handler.d.ts +43 -0
  63. package/dist/queue-handler.d.ts.map +1 -0
  64. package/dist/queue-handler.js +46 -0
  65. package/dist/router.d.ts +2 -2
  66. package/dist/router.d.ts.map +1 -1
  67. package/dist/router.js +15 -8
  68. package/dist/schemas/index.js +7 -4
  69. package/dist/schemas/registry.js +387 -0
  70. package/dist/schemas/types.js +1 -0
  71. package/dist/schemas/validators.d.ts.map +1 -1
  72. package/dist/schemas/validators.js +290 -0
  73. package/dist/service-bindings.d.ts +68 -26
  74. package/dist/service-bindings.d.ts.map +1 -1
  75. package/dist/service-bindings.js +219 -2
  76. package/dist/session.d.ts +9 -5
  77. package/dist/session.d.ts.map +1 -1
  78. package/dist/session.js +179 -4
  79. package/dist/sse.d.ts +12 -3
  80. package/dist/sse.d.ts.map +1 -1
  81. package/dist/sse.js +70 -7
  82. package/dist/stores/config-store.d.ts.map +1 -1
  83. package/dist/stores/config-store.js +129 -6
  84. package/dist/stores/service-store.d.ts +50 -1
  85. package/dist/stores/service-store.d.ts.map +1 -1
  86. package/dist/stores/service-store.js +162 -27
  87. package/dist/test-utils.d.ts +132 -0
  88. package/dist/test-utils.d.ts.map +1 -0
  89. package/dist/test-utils.js +128 -0
  90. package/dist/types/router.d.ts +18 -8
  91. package/dist/types/router.d.ts.map +1 -1
  92. package/dist/types.d.ts +124 -5
  93. package/dist/types.d.ts.map +1 -1
  94. package/dist/types.js +11 -0
  95. package/dist/wizard/engine.d.ts.map +1 -1
  96. package/dist/wizard/engine.js +501 -0
  97. package/dist/wizard/index.js +2 -1
  98. package/dist/wizard/persistence.js +31 -0
  99. package/dist/wizard/presets.js +199 -0
  100. package/dist/wizard/provisioner.js +1 -0
  101. package/dist/wizard/types.js +1 -0
  102. package/package.json +46 -12
@@ -1,32 +1,74 @@
1
+ import type { InternalAuthKeyName } from "./middleware/auth";
2
+ export interface ServiceBinding {
3
+ fetch(request: Request | string, init?: RequestInit): Promise<Response>;
4
+ }
5
+ /** Env binding names for scoped internal auth keys. */
6
+ export declare const InternalAuthKeyFields: {
7
+ readonly DEFAULT: "INTERNAL_KEY_BINDING";
8
+ readonly D1_READ: "D1_READ_KEY_BINDING";
9
+ readonly D1_WRITE: "D1_WRITE_KEY_BINDING";
10
+ readonly TRADE_EXECUTE: "TRADE_EXECUTE_KEY_BINDING";
11
+ readonly TRADE_READ: "TRADE_READ_KEY_BINDING";
12
+ readonly TELEGRAM_ALERT: "TELEGRAM_INTERNAL_KEY_BINDING";
13
+ readonly WALLET_EXECUTE: "WALLET_EXECUTE_KEY_BINDING";
14
+ readonly AGENT: "AGENT_INTERNAL_KEY";
15
+ /** Dashboard Pages secret aliases (legacy naming). */
16
+ readonly D1_INTERNAL_ALIAS: "D1_INTERNAL_KEY";
17
+ readonly TRADE_INTERNAL_ALIAS: "TRADE_INTERNAL_KEY";
18
+ readonly TELEGRAM_INTERNAL_ALIAS: "TELEGRAM_INTERNAL_KEY";
19
+ };
20
+ /** d1-worker read routes: prefer read-only key, fall back to legacy full key. */
21
+ export declare const D1_READ_AUTH_KEY_FIELDS: readonly ["D1_READ_KEY_BINDING", "INTERNAL_KEY_BINDING"];
22
+ /** d1-worker write/RPC routes: prefer write key, fall back to legacy full key. */
23
+ export declare const D1_WRITE_AUTH_KEY_FIELDS: readonly ["D1_WRITE_KEY_BINDING", "INTERNAL_KEY_BINDING"];
24
+ /** trade-worker execute routes (/webhook, /process, POST /api/signals). */
25
+ export declare const TRADE_EXECUTE_AUTH_KEY_FIELDS: readonly ["TRADE_EXECUTE_KEY_BINDING", "INTERNAL_KEY_BINDING", "AGENT_INTERNAL_KEY"];
26
+ /** trade-worker read routes (GET /api/signals, GET /report). */
27
+ export declare const TRADE_READ_AUTH_KEY_FIELDS: readonly ["TRADE_READ_KEY_BINDING", "INTERNAL_KEY_BINDING"];
28
+ /** telegram-worker /alert notification endpoint. */
29
+ export declare const TELEGRAM_ALERT_AUTH_KEY_FIELDS: readonly ["TELEGRAM_INTERNAL_KEY_BINDING", "INTERNAL_KEY_BINDING"];
30
+ /** web3-wallet-worker privileged routes. */
31
+ export declare const WALLET_EXECUTE_AUTH_KEY_FIELDS: readonly ["WALLET_EXECUTE_KEY_BINDING", "INTERNAL_KEY_BINDING"];
32
+ /** Dashboard → d1-worker read calls (includes Pages secret aliases). */
33
+ export declare const DASHBOARD_D1_READ_AUTH_KEY_FIELDS: readonly ["D1_READ_KEY_BINDING", "D1_INTERNAL_KEY", "INTERNAL_KEY_BINDING"];
34
+ /** Dashboard → trade-worker execute calls. */
35
+ export declare const DASHBOARD_TRADE_EXECUTE_AUTH_KEY_FIELDS: readonly ["TRADE_EXECUTE_KEY_BINDING", "TRADE_INTERNAL_KEY", "INTERNAL_KEY_BINDING"];
36
+ /** Dashboard → telegram-worker /alert calls. */
37
+ export declare const DASHBOARD_TELEGRAM_ALERT_AUTH_KEY_FIELDS: readonly ["TELEGRAM_INTERNAL_KEY_BINDING", "TELEGRAM_INTERNAL_KEY", "INTERNAL_KEY_BINDING"];
38
+ /** First configured key from an ordered fallback list. */
39
+ export declare function resolveInternalAuthKey(env: object, keyFields: InternalAuthKeyName): string | undefined;
40
+ export interface AuthenticatedServiceEnv {
41
+ INTERNAL_KEY_BINDING?: string;
42
+ D1_READ_KEY_BINDING?: string;
43
+ D1_WRITE_KEY_BINDING?: string;
44
+ TRADE_EXECUTE_KEY_BINDING?: string;
45
+ TRADE_READ_KEY_BINDING?: string;
46
+ TELEGRAM_INTERNAL_KEY_BINDING?: string;
47
+ WALLET_EXECUTE_KEY_BINDING?: string;
48
+ AGENT_INTERNAL_KEY?: string;
49
+ D1_INTERNAL_KEY?: string;
50
+ TRADE_INTERNAL_KEY?: string;
51
+ TELEGRAM_INTERNAL_KEY?: string;
52
+ }
53
+ export declare class ServiceAuthError extends Error {
54
+ constructor(message: string);
55
+ }
1
56
  /**
2
- * Standardized helper for inter-worker service binding calls.
3
- *
4
- * Uses `Fetcher.fetch(url, init)` directly — avoids the `as unknown as Request`
5
- * anti-pattern and inconsistent URL schemes used across workers
6
- * (http://trade-service/webhook, http://localhost/query, https://internal/webhook, etc.).
7
- *
8
- * Convention: URL path matches the route the target worker serves.
9
- * Defaults to POST with JSON Content-Type body.
10
- *
11
- * Note: Uses `any` for the binding parameter to avoid `@cloudflare/workers-types`
12
- * `Fetcher` type incompatibilities that can arise when different workspace
13
- * packages resolve distinct instances of the types package. At runtime, service
14
- * bindings from the Workers runtime always have a compatible `fetch` method.
15
- *
16
- * @example
17
- * // POST with JSON body (default)
18
- * const response = await serviceFetch(env.TRADE_SERVICE, "/webhook", payload);
19
- *
20
- * // POST with extra headers
21
- * const response = await serviceFetch(env.D1_SERVICE, "/query", { query, params }, {
22
- * headers: { "X-Request-ID": requestId },
23
- * });
24
- *
25
- * // GET request (no body)
26
- * const response = await serviceFetch(env.D1_SERVICE, "/health", undefined, { method: "GET" });
57
+ * serviceFetch with fail-closed internal auth.
58
+ * Injects X-Internal-Auth-Key from env.INTERNAL_KEY_BINDING.
27
59
  */
28
- export declare function serviceFetch(binding: any, path: string, body?: unknown, options?: {
60
+ export declare function authenticatedServiceFetch<E extends AuthenticatedServiceEnv>(binding: ServiceBinding, env: E, path: string, body?: unknown, options?: {
29
61
  method?: string;
30
62
  headers?: Record<string, string>;
63
+ timeout?: number;
64
+ /** Override env key (e.g. TELEGRAM_INTERNAL_KEY_BINDING, AGENT_INTERNAL_KEY) */
65
+ internalKey?: string;
66
+ /** Ordered env fields to try when internalKey is not set */
67
+ internalKeyFields?: InternalAuthKeyName;
68
+ }): Promise<Response>;
69
+ export declare function serviceFetch(binding: ServiceBinding, path: string, body?: unknown, options?: {
70
+ method?: string;
71
+ headers?: Record<string, string>;
72
+ timeout?: number;
31
73
  }): Promise<Response>;
32
74
  //# sourceMappingURL=service-bindings.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"service-bindings.d.ts","sourceRoot":"","sources":["../src/service-bindings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,GAAG,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAC9D,OAAO,CAAC,QAAQ,CAAC,CASnB"}
1
+ {"version":3,"file":"service-bindings.d.ts","sourceRoot":"","sources":["../src/service-bindings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAmB,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAE9E,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACzE;AAED,uDAAuD;AACvD,eAAO,MAAM,qBAAqB;;;;;;;;;IAShC,sDAAsD;;;;CAI9C,CAAC;AAEX,iFAAiF;AACjF,eAAO,MAAM,uBAAuB,0DAG1B,CAAC;AAEX,kFAAkF;AAClF,eAAO,MAAM,wBAAwB,2DAG3B,CAAC;AAEX,2EAA2E;AAC3E,eAAO,MAAM,6BAA6B,sFAIhC,CAAC;AAEX,gEAAgE;AAChE,eAAO,MAAM,0BAA0B,6DAG7B,CAAC;AAEX,oDAAoD;AACpD,eAAO,MAAM,8BAA8B,oEAGjC,CAAC;AAEX,4CAA4C;AAC5C,eAAO,MAAM,8BAA8B,iEAGjC,CAAC;AAEX,wEAAwE;AACxE,eAAO,MAAM,iCAAiC,6EAIpC,CAAC;AAEX,8CAA8C;AAC9C,eAAO,MAAM,uCAAuC,sFAI1C,CAAC;AAEX,gDAAgD;AAChD,eAAO,MAAM,wCAAwC,6FAI3C,CAAC;AAEX,0DAA0D;AAC1D,wBAAgB,sBAAsB,CACpC,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,mBAAmB,GAC7B,MAAM,GAAG,SAAS,CAEpB;AAED,MAAM,WAAW,uBAAuB;IACtC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,0BAA0B,CAAC,EAAE,MAAM,CAAC;IACpC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC7B,OAAO,EAAE,MAAM;CAI5B;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,CAAC,SAAS,uBAAuB,EACzE,OAAO,EAAE,cAAc,EACvB,GAAG,EAAE,CAAC,EACN,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gFAAgF;IAChF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4DAA4D;IAC5D,iBAAiB,CAAC,EAAE,mBAAmB,CAAC;CACzC,GACA,OAAO,CAAC,QAAQ,CAAC,CAsBnB;AAED,wBAAgB,YAAY,CAC1B,OAAO,EAAE,cAAc,EACvB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,EACd,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,GACA,OAAO,CAAC,QAAQ,CAAC,CAWnB"}
@@ -15,17 +15,234 @@ var __export = (target, all) => {
15
15
  };
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
+ // src/middleware/auth.ts
19
+ function timingSafeEqual(a, b) {
20
+ const encoder = new TextEncoder;
21
+ const aBuf = encoder.encode(a);
22
+ const bBuf = encoder.encode(b);
23
+ const len = Math.max(aBuf.length, bBuf.length);
24
+ let result = aBuf.length === bBuf.length ? 0 : 1;
25
+ for (let i = 0;i < len; i++) {
26
+ const av = i < aBuf.length ? aBuf[i] : 0;
27
+ const bv = i < bBuf.length ? bBuf[i] : 0;
28
+ result |= av ^ bv;
29
+ }
30
+ return result === 0;
31
+ }
32
+ function resolveOperatorApiKey(env) {
33
+ const preferred = env.OPERATOR_API_KEY;
34
+ if (typeof preferred === "string" && preferred.length > 0) {
35
+ return preferred;
36
+ }
37
+ const legacy = env.INTERNAL_API_KEY;
38
+ if (typeof legacy === "string" && legacy.length > 0) {
39
+ return legacy;
40
+ }
41
+ return;
42
+ }
43
+ async function requireAuth(request, env) {
44
+ return requireOperatorAuth(request, env);
45
+ }
46
+ async function requireOperatorAuth(request, env) {
47
+ const apiKey = resolveOperatorApiKey(env);
48
+ if (!apiKey) {
49
+ return new Response(JSON.stringify({
50
+ error: "Operator API key not configured",
51
+ hint: "Set OPERATOR_API_KEY (preferred) or INTERNAL_API_KEY on the Worker"
52
+ }), { status: 401, headers: { "Content-Type": "application/json" } });
53
+ }
54
+ const authHeader = request.headers.get("Authorization");
55
+ const expectedHeader = `Bearer ${apiKey}`;
56
+ if (!authHeader || !timingSafeEqual(authHeader, expectedHeader)) {
57
+ return new Response(JSON.stringify({ error: "Unauthorized" }), {
58
+ status: 401,
59
+ headers: { "Content-Type": "application/json" }
60
+ });
61
+ }
62
+ return null;
63
+ }
64
+ function createOperatorAuthMiddleware() {
65
+ return async (request, env, _ctx) => {
66
+ const denied = await requireOperatorAuth(request, env);
67
+ if (denied)
68
+ return denied;
69
+ return;
70
+ };
71
+ }
72
+ function normalizeKeyNames(keyName) {
73
+ if (typeof keyName === "string") {
74
+ return [keyName];
75
+ }
76
+ return [...keyName];
77
+ }
78
+ function collectInternalAuthKeys(env, keyNames) {
79
+ const keys = [];
80
+ for (const name of normalizeKeyNames(keyNames)) {
81
+ const value = env[name];
82
+ if (typeof value === "string" && value.length > 0) {
83
+ keys.push(value);
84
+ }
85
+ }
86
+ return keys;
87
+ }
88
+ function matchesAnyInternalAuthKey(providedKey, expectedKeys) {
89
+ for (const expected of expectedKeys) {
90
+ if (timingSafeEqual(providedKey, expected)) {
91
+ return true;
92
+ }
93
+ }
94
+ return false;
95
+ }
96
+ function internalAuthNotConfiguredResponse(keyNames) {
97
+ const label = normalizeKeyNames(keyNames).join(" | ");
98
+ return new Response(JSON.stringify({
99
+ success: false,
100
+ error: `Internal auth key(s) not configured: ${label}`
101
+ }), { status: 401, headers: { "Content-Type": "application/json" } });
102
+ }
103
+ function requireInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
104
+ const expectedKeys = collectInternalAuthKeys(env, keyName);
105
+ if (expectedKeys.length === 0) {
106
+ return internalAuthNotConfiguredResponse(keyName);
107
+ }
108
+ const providedKey = request.headers.get("X-Internal-Auth-Key");
109
+ if (!providedKey || !matchesAnyInternalAuthKey(providedKey, expectedKeys)) {
110
+ return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } });
111
+ }
112
+ return null;
113
+ }
114
+ function createInternalAuthMiddleware(keyName = "INTERNAL_KEY_BINDING") {
115
+ return async (request, env, _ctx) => {
116
+ const expectedKeys = collectInternalAuthKeys(env, keyName);
117
+ if (expectedKeys.length === 0) {
118
+ return internalAuthNotConfiguredResponse(keyName);
119
+ }
120
+ const providedKey = request.headers.get("X-Internal-Auth-Key");
121
+ if (!providedKey || !matchesAnyInternalAuthKey(providedKey, expectedKeys)) {
122
+ return new Response(JSON.stringify({ success: false, error: "Unauthorized" }), { status: 401, headers: { "Content-Type": "application/json" } });
123
+ }
124
+ return;
125
+ };
126
+ }
127
+ function checkInternalAuth(request, env, keyName = "INTERNAL_KEY_BINDING") {
128
+ const expectedKeys = collectInternalAuthKeys(env, keyName);
129
+ if (expectedKeys.length === 0) {
130
+ return {
131
+ authorized: false,
132
+ error: `${normalizeKeyNames(keyName).join(" | ")} not configured`
133
+ };
134
+ }
135
+ const providedKey = request.headers.get("X-Internal-Auth-Key");
136
+ if (!providedKey || !matchesAnyInternalAuthKey(providedKey, expectedKeys)) {
137
+ return { authorized: false, error: "Unauthorized" };
138
+ }
139
+ return { authorized: true };
140
+ }
141
+
18
142
  // src/service-bindings.ts
143
+ var InternalAuthKeyFields = {
144
+ DEFAULT: "INTERNAL_KEY_BINDING",
145
+ D1_READ: "D1_READ_KEY_BINDING",
146
+ D1_WRITE: "D1_WRITE_KEY_BINDING",
147
+ TRADE_EXECUTE: "TRADE_EXECUTE_KEY_BINDING",
148
+ TRADE_READ: "TRADE_READ_KEY_BINDING",
149
+ TELEGRAM_ALERT: "TELEGRAM_INTERNAL_KEY_BINDING",
150
+ WALLET_EXECUTE: "WALLET_EXECUTE_KEY_BINDING",
151
+ AGENT: "AGENT_INTERNAL_KEY",
152
+ D1_INTERNAL_ALIAS: "D1_INTERNAL_KEY",
153
+ TRADE_INTERNAL_ALIAS: "TRADE_INTERNAL_KEY",
154
+ TELEGRAM_INTERNAL_ALIAS: "TELEGRAM_INTERNAL_KEY"
155
+ };
156
+ var D1_READ_AUTH_KEY_FIELDS = [
157
+ InternalAuthKeyFields.D1_READ,
158
+ InternalAuthKeyFields.DEFAULT
159
+ ];
160
+ var D1_WRITE_AUTH_KEY_FIELDS = [
161
+ InternalAuthKeyFields.D1_WRITE,
162
+ InternalAuthKeyFields.DEFAULT
163
+ ];
164
+ var TRADE_EXECUTE_AUTH_KEY_FIELDS = [
165
+ InternalAuthKeyFields.TRADE_EXECUTE,
166
+ InternalAuthKeyFields.DEFAULT,
167
+ InternalAuthKeyFields.AGENT
168
+ ];
169
+ var TRADE_READ_AUTH_KEY_FIELDS = [
170
+ InternalAuthKeyFields.TRADE_READ,
171
+ InternalAuthKeyFields.DEFAULT
172
+ ];
173
+ var TELEGRAM_ALERT_AUTH_KEY_FIELDS = [
174
+ InternalAuthKeyFields.TELEGRAM_ALERT,
175
+ InternalAuthKeyFields.DEFAULT
176
+ ];
177
+ var WALLET_EXECUTE_AUTH_KEY_FIELDS = [
178
+ InternalAuthKeyFields.WALLET_EXECUTE,
179
+ InternalAuthKeyFields.DEFAULT
180
+ ];
181
+ var DASHBOARD_D1_READ_AUTH_KEY_FIELDS = [
182
+ InternalAuthKeyFields.D1_READ,
183
+ InternalAuthKeyFields.D1_INTERNAL_ALIAS,
184
+ InternalAuthKeyFields.DEFAULT
185
+ ];
186
+ var DASHBOARD_TRADE_EXECUTE_AUTH_KEY_FIELDS = [
187
+ InternalAuthKeyFields.TRADE_EXECUTE,
188
+ InternalAuthKeyFields.TRADE_INTERNAL_ALIAS,
189
+ InternalAuthKeyFields.DEFAULT
190
+ ];
191
+ var DASHBOARD_TELEGRAM_ALERT_AUTH_KEY_FIELDS = [
192
+ InternalAuthKeyFields.TELEGRAM_ALERT,
193
+ InternalAuthKeyFields.TELEGRAM_INTERNAL_ALIAS,
194
+ InternalAuthKeyFields.DEFAULT
195
+ ];
196
+ function resolveInternalAuthKey(env, keyFields) {
197
+ return collectInternalAuthKeys(env, keyFields)[0];
198
+ }
199
+
200
+ class ServiceAuthError extends Error {
201
+ constructor(message) {
202
+ super(message);
203
+ this.name = "ServiceAuthError";
204
+ }
205
+ }
206
+ function authenticatedServiceFetch(binding, env, path, body, options) {
207
+ const key = options?.internalKey ?? (options?.internalKeyFields ? resolveInternalAuthKey(env, options.internalKeyFields) : env.INTERNAL_KEY_BINDING);
208
+ if (!key) {
209
+ const label = options?.internalKeyFields ? Array.isArray(options.internalKeyFields) ? options.internalKeyFields.join(" | ") : options.internalKeyFields : "INTERNAL_KEY_BINDING";
210
+ return Promise.reject(new ServiceAuthError(`${label} not configured`));
211
+ }
212
+ return serviceFetch(binding, path, body, {
213
+ method: options?.method,
214
+ timeout: options?.timeout,
215
+ headers: {
216
+ ...options?.headers,
217
+ "X-Internal-Auth-Key": key
218
+ }
219
+ });
220
+ }
19
221
  function serviceFetch(binding, path, body, options) {
222
+ const timeout = options?.timeout ?? 30000;
20
223
  return binding.fetch(`http://internal${path}`, {
21
224
  method: options?.method ?? "POST",
22
225
  headers: {
23
226
  "Content-Type": "application/json",
24
227
  ...options?.headers
25
228
  },
26
- body: body != null ? JSON.stringify(body) : undefined
229
+ body: body != null ? JSON.stringify(body) : undefined,
230
+ signal: AbortSignal.timeout(timeout)
27
231
  });
28
232
  }
29
233
  export {
30
- serviceFetch
234
+ serviceFetch,
235
+ resolveInternalAuthKey,
236
+ authenticatedServiceFetch,
237
+ WALLET_EXECUTE_AUTH_KEY_FIELDS,
238
+ TRADE_READ_AUTH_KEY_FIELDS,
239
+ TRADE_EXECUTE_AUTH_KEY_FIELDS,
240
+ TELEGRAM_ALERT_AUTH_KEY_FIELDS,
241
+ ServiceAuthError,
242
+ InternalAuthKeyFields,
243
+ DASHBOARD_TRADE_EXECUTE_AUTH_KEY_FIELDS,
244
+ DASHBOARD_TELEGRAM_ALERT_AUTH_KEY_FIELDS,
245
+ DASHBOARD_D1_READ_AUTH_KEY_FIELDS,
246
+ D1_WRITE_AUTH_KEY_FIELDS,
247
+ D1_READ_AUTH_KEY_FIELDS
31
248
  };
package/dist/session.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Session persistence — saves/restores TUI state to ~/.hoox/session.json.
2
+ * Session persistence — saves/restores TUI state to $HOME/.hoox/.tui-state/session.json.
3
3
  *
4
4
  * On destroy (app exit): serializes { activeView, sidebarExpanded, windowSize,
5
5
  * lastData } so the user returns to the same state on next launch.
@@ -7,6 +7,10 @@
7
7
  * On startup: restores previous activeView and sidebarExpanded state.
8
8
  * Window size and lastData are saved for reference but re-detected/fetched fresh.
9
9
  *
10
+ * Uses the shared getHooxHome() utility for cross-OS home directory resolution,
11
+ * and stores session data in the .tui-state subdirectory. Falls back to
12
+ * current working directory if $HOME is not available.
13
+ *
10
14
  * Uses Bun's native file API for async read/write.
11
15
  */
12
16
  import type { ViewId } from "./types";
@@ -26,18 +30,18 @@ export interface SessionState {
26
30
  savedAt: string;
27
31
  }
28
32
  /**
29
- * Persist the current UI state to ~/.hoox/session.json.
33
+ * Persist the current UI state to $HOME/.hoox/.tui-state/session.json.
30
34
  * Call this on app destroy / clean shutdown.
31
35
  *
32
- * The ~/.hoox directory is created automatically by Bun.write if needed,
33
- * but we attempt to verify existence first for clarity.
36
+ * The $HOME/.hoox/.tui-state directory is created automatically by
37
+ * Bun.write if needed (Bun creates parent directories).
34
38
  */
35
39
  export declare function saveSession(activeView: ViewId, sidebarExpanded: boolean, windowSize: {
36
40
  cols: number;
37
41
  rows: number;
38
42
  }, lastData: number): Promise<void>;
39
43
  /**
40
- * Restore the previous session state from ~/.hoox/session.json.
44
+ * Restore the previous session state from $HOME/.hoox/.tui-state/session.json.
41
45
  * Returns defaults if the file doesn't exist or can't be parsed.
42
46
  *
43
47
  * @returns The saved session state, or defaults
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAItC,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,eAAe,EAAE,OAAO,CAAC;IACzB,+CAA+C;IAC/C,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;CACjB;AAkBD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,OAAO,EACxB,UAAU,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC1C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAkBf;AAID;;;;;GAKG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC,CA6B5D"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAKtC,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,UAAU,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,eAAe,EAAE,OAAO,CAAC;IACzB,+CAA+C;IAC/C,UAAU,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,uDAAuD;IACvD,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;CACjB;AA+CD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,eAAe,EAAE,OAAO,EACxB,UAAU,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAC1C,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC,CAkBf;AAID;;;;;GAKG;AACH,wBAAsB,cAAc,IAAI,OAAO,CAAC,YAAY,CAAC,CA8B5D"}
package/dist/session.js CHANGED
@@ -15,10 +15,184 @@ var __export = (target, all) => {
15
15
  };
16
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
- // src/session.ts
18
+ // src/path-utils.ts
19
+ import { existsSync } from "fs";
19
20
  import { homedir } from "os";
20
- import { join } from "path";
21
- var SESSION_FILE = join(homedir(), ".hoox", "session.json");
21
+ import { dirname, join, resolve } from "path";
22
+ function createHooxPath(path) {
23
+ return path;
24
+ }
25
+ function getHooxHome() {
26
+ try {
27
+ const override = process.env.HOOX_HOME?.trim();
28
+ if (override) {
29
+ return createHooxPath(resolve(override));
30
+ }
31
+ const home = homedir();
32
+ if (!home || home.length === 0) {
33
+ return createHooxPath(resolve(process.cwd(), ".hoox"));
34
+ }
35
+ return createHooxPath(join(home, ".hoox"));
36
+ } catch {
37
+ return createHooxPath(resolve(process.cwd(), ".hoox"));
38
+ }
39
+ }
40
+ function isHooxSetupRoot(dir) {
41
+ if (!dir)
42
+ return false;
43
+ try {
44
+ const root = resolve(dir);
45
+ return existsSync(join(root, "wrangler.jsonc")) && existsSync(join(root, "packages", "cli", "package.json"));
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+ function findHooxSetupRoot(startDir = process.cwd()) {
51
+ let dir = resolve(startDir);
52
+ for (;; ) {
53
+ if (isHooxSetupRoot(dir))
54
+ return dir;
55
+ const parent = dirname(dir);
56
+ if (parent === dir)
57
+ return null;
58
+ dir = parent;
59
+ }
60
+ }
61
+ function resolveHooxRuntimeRoot(options) {
62
+ const env = options?.env ?? process.env;
63
+ const cwd = resolve(options?.cwd ?? process.cwd());
64
+ const globalRepo = getHooxRepoPath();
65
+ const envRepo = env.HOOX_REPO?.trim();
66
+ if (envRepo) {
67
+ const resolved = resolve(envRepo);
68
+ if (isHooxSetupRoot(resolved)) {
69
+ return {
70
+ root: resolved,
71
+ source: "env",
72
+ checked: {
73
+ env: resolved,
74
+ cwd: findHooxSetupRoot(cwd),
75
+ global: globalRepo
76
+ }
77
+ };
78
+ }
79
+ return {
80
+ root: null,
81
+ source: "env",
82
+ checked: {
83
+ env: resolved,
84
+ cwd: findHooxSetupRoot(cwd),
85
+ global: globalRepo
86
+ }
87
+ };
88
+ }
89
+ const local = findHooxSetupRoot(cwd);
90
+ if (local) {
91
+ return {
92
+ root: local,
93
+ source: "cwd",
94
+ checked: { cwd: local, global: globalRepo }
95
+ };
96
+ }
97
+ if (isHooxSetupRoot(globalRepo)) {
98
+ return {
99
+ root: globalRepo,
100
+ source: "global",
101
+ checked: { cwd: null, global: globalRepo }
102
+ };
103
+ }
104
+ return {
105
+ root: null,
106
+ source: "none",
107
+ checked: { cwd: null, global: globalRepo }
108
+ };
109
+ }
110
+ function getTuiEntryCandidates(runtimeRoot) {
111
+ const root = resolve(runtimeRoot);
112
+ return [
113
+ join(root, "packages", "tui", "src", "main.tsx"),
114
+ join(root, "packages", "tui", "dist", "main.js"),
115
+ join(root, "packages", "tui", "src", "main.ts")
116
+ ];
117
+ }
118
+ function resolveHooxPath(relativePath) {
119
+ if (!relativePath || typeof relativePath !== "string") {
120
+ throw new Error("relativePath must be a non-empty string");
121
+ }
122
+ if (relativePath.includes("..")) {
123
+ throw new Error(`Path traversal detected in relativePath: "${relativePath}"`);
124
+ }
125
+ const normalizedPath = relativePath.replace(/\\/g, "/");
126
+ const hooxHome = getHooxHome();
127
+ const fullPath = join(hooxHome, normalizedPath);
128
+ const resolvedPath = resolve(fullPath);
129
+ const resolvedHome = resolve(hooxHome);
130
+ if (!resolvedPath.startsWith(resolvedHome)) {
131
+ throw new Error(`Resolved path "${resolvedPath}" is outside Hoox home directory`);
132
+ }
133
+ return createHooxPath(resolvedPath);
134
+ }
135
+ function isWithinHooxHome(path) {
136
+ try {
137
+ const hooxHome = getHooxHome();
138
+ const resolvedPath = resolve(path);
139
+ const resolvedHome = resolve(hooxHome);
140
+ return resolvedPath.startsWith(resolvedHome);
141
+ } catch {
142
+ return false;
143
+ }
144
+ }
145
+ function getRelativeHooxPath(absolutePath) {
146
+ try {
147
+ if (!isWithinHooxHome(absolutePath)) {
148
+ return null;
149
+ }
150
+ const hooxHome = getHooxHome();
151
+ const resolvedPath = resolve(absolutePath);
152
+ const resolvedHome = resolve(hooxHome);
153
+ const relativePath = resolvedPath.slice(resolvedHome.length);
154
+ return relativePath.startsWith("/") || relativePath.startsWith("\\") ? relativePath.slice(1) : relativePath;
155
+ } catch {
156
+ return null;
157
+ }
158
+ }
159
+ function getHooxRepoPath() {
160
+ return resolveHooxPath("repo");
161
+ }
162
+ function getHooxConfigDir() {
163
+ return resolveHooxPath("config");
164
+ }
165
+ function getHooxDataDir() {
166
+ return resolveHooxPath("data");
167
+ }
168
+ function getHooxWranglerPath() {
169
+ return resolveHooxPath("config/wrangler.jsonc");
170
+ }
171
+ function getHooxStatePath() {
172
+ return resolveHooxPath("data/state.json");
173
+ }
174
+
175
+ // src/session.ts
176
+ import { z } from "zod";
177
+ var SessionStateFileSchema = z.object({
178
+ activeView: z.string().optional(),
179
+ sidebarExpanded: z.boolean().optional(),
180
+ windowSize: z.object({
181
+ cols: z.number().int().positive(),
182
+ rows: z.number().int().positive()
183
+ }).optional(),
184
+ lastData: z.number().nonnegative().optional(),
185
+ savedAt: z.string().optional()
186
+ }).strict();
187
+ var TUI_STATE_DIR = ".tui-state";
188
+ var SESSION_FILE = (() => {
189
+ try {
190
+ const hooxHome = getHooxHome();
191
+ return `${hooxHome}/${TUI_STATE_DIR}/session.json`;
192
+ } catch {
193
+ return `${process.cwd()}/${TUI_STATE_DIR}/session.json`;
194
+ }
195
+ })();
22
196
  var DEFAULT_SESSION = {
23
197
  activeView: "dashboard",
24
198
  sidebarExpanded: true,
@@ -47,7 +221,8 @@ async function restoreSession() {
47
221
  if (!exists)
48
222
  return { ...DEFAULT_SESSION };
49
223
  const raw = await file.text();
50
- const parsed = JSON.parse(raw);
224
+ const result = SessionStateFileSchema.safeParse(JSON.parse(raw));
225
+ const parsed = result.success ? result.data : {};
51
226
  return {
52
227
  activeView: validateViewId(parsed.activeView) ? parsed.activeView : DEFAULT_SESSION.activeView,
53
228
  sidebarExpanded: typeof parsed.sidebarExpanded === "boolean" ? parsed.sidebarExpanded : DEFAULT_SESSION.sidebarExpanded,
package/dist/sse.d.ts CHANGED
@@ -1,26 +1,35 @@
1
1
  /**
2
- * SSE Streaming Client — subscribes to Server-Sent Events from hoox-setup API.
2
+ * SSE Streaming Client — subscribes to Server-Sent Events from the Hoox
3
+ * operator management API.
3
4
  *
4
5
  * Uses Bun's native fetch with ReadableStream reader for SSE parsing.
5
6
  * Supports reconnection with exponential backoff.
6
7
  * Events must be JSON-encoded in the "data:" field.
8
+ *
9
+ * Auth: operator transport profile (Bearer + optional Access service token).
7
10
  */
11
+ import { type OperatorTransportEnv, type OperatorTransportProfile } from "./operator-transport";
8
12
  export type SSECallback<T> = (event: T) => void;
9
13
  export type SSEStatusCallback = (status: "connected" | "reconnecting" | "disconnected") => void;
10
14
  interface SSESubscription {
11
15
  abort: () => void;
12
16
  }
17
+ export interface SubscribeSSEOptions {
18
+ transport?: OperatorTransportProfile;
19
+ transportEnv?: OperatorTransportEnv;
20
+ }
13
21
  /**
14
22
  * Subscribe to an SSE endpoint and invoke the callback for each parsed event.
15
23
  *
16
24
  * Automatically reconnects on connection loss with exponential backoff.
17
25
  * Returns an object with an `abort()` method to cancel the subscription.
18
26
  *
19
- * @param path API path (e.g. "/trades/stream")
27
+ * @param path API path (e.g. "/v1/trades/stream")
20
28
  * @param callback Called with each parsed event of type T
21
29
  * @param onStatus Optional callback for connection status updates
30
+ * @param options Optional transport profile override
22
31
  * @returns Subscription controller with abort method
23
32
  */
24
- export declare function subscribeSSE<T>(path: string, callback: SSECallback<T>, onStatus?: SSEStatusCallback): SSESubscription;
33
+ export declare function subscribeSSE<T>(path: string, callback: SSECallback<T>, onStatus?: SSEStatusCallback, options?: SubscribeSSEOptions): SSESubscription;
25
34
  export {};
26
35
  //# sourceMappingURL=sse.d.ts.map
package/dist/sse.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;AAChD,MAAM,MAAM,iBAAiB,GAAG,CAC9B,MAAM,EAAE,WAAW,GAAG,cAAc,GAAG,cAAc,KAClD,IAAI,CAAC;AAEV,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAID;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EACxB,QAAQ,CAAC,EAAE,iBAAiB,GAC3B,eAAe,CAgFjB"}
1
+ {"version":3,"file":"sse.d.ts","sourceRoot":"","sources":["../src/sse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,wBAAwB,EAC9B,MAAM,sBAAsB,CAAC;AAS9B,MAAM,MAAM,WAAW,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;AAChD,MAAM,MAAM,iBAAiB,GAAG,CAC9B,MAAM,EAAE,WAAW,GAAG,cAAc,GAAG,cAAc,KAClD,IAAI,CAAC;AAEV,UAAU,eAAe;IACvB,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,SAAS,CAAC,EAAE,wBAAwB,CAAC;IACrC,YAAY,CAAC,EAAE,oBAAoB,CAAC;CACrC;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAC5B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EACxB,QAAQ,CAAC,EAAE,iBAAiB,EAC5B,OAAO,CAAC,EAAE,mBAAmB,GAC5B,eAAe,CAsFjB"}