@jango-blockchained/hoox-shared 1.0.8 → 1.0.9

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 (61) hide show
  1. package/dist/analytics.d.ts +0 -1
  2. package/dist/analytics.d.ts.map +1 -1
  3. package/dist/colors.d.ts +0 -1
  4. package/dist/colors.d.ts.map +1 -1
  5. package/dist/colors.js +0 -1
  6. package/dist/cron-handler.d.ts +34 -0
  7. package/dist/cron-handler.d.ts.map +1 -0
  8. package/dist/cron-handler.js +48 -0
  9. package/dist/d1/index.js +49 -6
  10. package/dist/d1/repository.d.ts +5 -0
  11. package/dist/d1/repository.d.ts.map +1 -1
  12. package/dist/errors.d.ts +1 -0
  13. package/dist/errors.d.ts.map +1 -1
  14. package/dist/errors.js +48 -5
  15. package/dist/exchanges/base-exchange-client.d.ts +68 -0
  16. package/dist/exchanges/base-exchange-client.d.ts.map +1 -0
  17. package/dist/exchanges/index.d.ts +3 -0
  18. package/dist/exchanges/index.d.ts.map +1 -0
  19. package/dist/exchanges/index.js +120 -0
  20. package/dist/exchanges/types.d.ts +19 -0
  21. package/dist/exchanges/types.d.ts.map +1 -0
  22. package/dist/health.js +48 -5
  23. package/dist/index.d.ts +11 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +480 -15
  26. package/dist/kvUtils.js +48 -5
  27. package/dist/middleware/auth.d.ts +6 -3
  28. package/dist/middleware/auth.d.ts.map +1 -1
  29. package/dist/middleware/index.js +193 -18
  30. package/dist/middleware/logger.d.ts +2 -1
  31. package/dist/middleware/logger.d.ts.map +1 -1
  32. package/dist/middleware/rate-limit.d.ts +3 -1
  33. package/dist/middleware/rate-limit.d.ts.map +1 -1
  34. package/dist/path-utils.d.ts +147 -0
  35. package/dist/path-utils.d.ts.map +1 -0
  36. package/dist/path-utils.js +101 -0
  37. package/dist/queue-handler.d.ts +43 -0
  38. package/dist/queue-handler.d.ts.map +1 -0
  39. package/dist/queue-handler.js +46 -0
  40. package/dist/router.d.ts +1 -1
  41. package/dist/router.d.ts.map +1 -1
  42. package/dist/router.js +15 -8
  43. package/dist/service-bindings.d.ts +5 -28
  44. package/dist/service-bindings.d.ts.map +1 -1
  45. package/dist/service-bindings.js +3 -1
  46. package/dist/session.d.ts +9 -5
  47. package/dist/session.d.ts.map +1 -1
  48. package/dist/session.js +83 -3
  49. package/dist/sse.js +1 -1
  50. package/dist/stores/service-store.d.ts +50 -1
  51. package/dist/stores/service-store.d.ts.map +1 -1
  52. package/dist/stores/service-store.js +67 -3
  53. package/dist/test-utils.d.ts +132 -0
  54. package/dist/test-utils.d.ts.map +1 -0
  55. package/dist/test-utils.js +128 -0
  56. package/dist/types/router.d.ts +18 -8
  57. package/dist/types/router.d.ts.map +1 -1
  58. package/dist/types.d.ts +59 -5
  59. package/dist/types.d.ts.map +1 -1
  60. package/dist/wizard/engine.d.ts.map +1 -1
  61. package/package.json +11 -3
@@ -0,0 +1,43 @@
1
+ import type { MessageBatch } from "@cloudflare/workers-types";
2
+ export interface QueueHandlerOptions<T> {
3
+ /** Maximum number of retry attempts */
4
+ maxRetries: number;
5
+ /** Array of delay times in seconds for exponential backoff */
6
+ backoffDelays: number[];
7
+ /** Handler function called for each message */
8
+ onMessage: (message: T, attemptNumber: number) => Promise<any> | any;
9
+ /** Called when a message fails and will be retried */
10
+ onRetry?: (message: T, attemptNumber: number, error: string, delaySeconds: number) => void | Promise<void>;
11
+ /** Called when message is moved to DLQ (max retries exceeded) */
12
+ onDLQ?: (message: T, attemptNumber: number, error: string) => void | Promise<void>;
13
+ /** Logger function for debugging */
14
+ logger?: {
15
+ info(msg: string, data?: any): void;
16
+ error(msg: string, data?: any): void;
17
+ };
18
+ }
19
+ /**
20
+ * Creates a reusable queue handler with retry + exponential backoff logic.
21
+ *
22
+ * Usage:
23
+ * ```typescript
24
+ * const handler = createQueueHandler({
25
+ * maxRetries: 5,
26
+ * backoffDelays: [0, 30, 60, 300, 900],
27
+ * onMessage: async (msg) => {
28
+ * await executeTask(msg);
29
+ * },
30
+ * onDLQ: async (msg, attempts, error) => {
31
+ * await logFailedTask(msg, error);
32
+ * }
33
+ * });
34
+ *
35
+ * export default {
36
+ * async queue(batch, env, ctx) {
37
+ * return await handler(batch);
38
+ * }
39
+ * };
40
+ * ```
41
+ */
42
+ export declare function createQueueHandler<T>(options: QueueHandlerOptions<T>): (batch: MessageBatch<T>) => Promise<void>;
43
+ //# sourceMappingURL=queue-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue-handler.d.ts","sourceRoot":"","sources":["../src/queue-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAE9D,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,+CAA+C;IAC/C,SAAS,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC;IACrE,sDAAsD;IACtD,OAAO,CAAC,EAAE,CACR,OAAO,EAAE,CAAC,EACV,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,KACjB,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,iEAAiE;IACjE,KAAK,CAAC,EAAE,CACN,OAAO,EAAE,CAAC,EACV,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,KACV,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,oCAAoC;IACpC,MAAM,CAAC,EAAE;QACP,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;QACpC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,GAAG,GAAG,IAAI,CAAC;KACtC,CAAC;CACH;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,IAIrD,OAAO,YAAY,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,IAAI,CAAC,CAmCrD"}
@@ -0,0 +1,46 @@
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/queue-handler.ts
19
+ function createQueueHandler(options) {
20
+ const { maxRetries, backoffDelays, onMessage, onRetry, onDLQ, logger } = options;
21
+ return async (batch) => {
22
+ for (const msg of batch.messages) {
23
+ const attemptNumber = msg.attempts || 0;
24
+ const logId = `[${msg.id}]`;
25
+ try {
26
+ logger?.info(`${logId} Processing message (attempt ${attemptNumber + 1})`);
27
+ await onMessage(msg.body, attemptNumber);
28
+ logger?.info(`${logId} Message processed successfully`);
29
+ } catch (error) {
30
+ const errorMsg = error instanceof Error ? error.message : String(error);
31
+ if (attemptNumber < maxRetries) {
32
+ const delaySeconds = backoffDelays[attemptNumber] ?? backoffDelays[backoffDelays.length - 1];
33
+ logger?.info(`${logId} Retrying in ${delaySeconds}s (attempt ${attemptNumber + 2}/${maxRetries + 1})`);
34
+ await onRetry?.(msg.body, attemptNumber, errorMsg, delaySeconds);
35
+ msg.retry({ delaySeconds });
36
+ } else {
37
+ logger?.error(`${logId} Max retries exceeded (${maxRetries + 1} attempts), moving to DLQ`);
38
+ await onDLQ?.(msg.body, attemptNumber, errorMsg);
39
+ }
40
+ }
41
+ }
42
+ };
43
+ }
44
+ export {
45
+ createQueueHandler
46
+ };
package/dist/router.d.ts CHANGED
@@ -7,5 +7,5 @@
7
7
  */
8
8
  import type { Router } from "./types/router";
9
9
  export type { Handler, RouteParams } from "./types/router";
10
- export declare function createRouter<TEnv = Record<string, unknown>>(): Router<TEnv>;
10
+ export declare function createRouter<TEnv = any>(): Router<TEnv>;
11
11
  //# sourceMappingURL=router.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAIV,MAAM,EACP,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE3D,wBAAgB,YAAY,CAAC,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,IAAI,CAAC,CAsL3E"}
1
+ {"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../src/router.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAKV,MAAM,EACP,MAAM,gBAAgB,CAAC;AAGxB,YAAY,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE3D,wBAAgB,YAAY,CAAC,IAAI,GAAG,GAAG,KAAK,MAAM,CAAC,IAAI,CAAC,CAsMvD"}
package/dist/router.js CHANGED
@@ -52,7 +52,7 @@ function createRouter() {
52
52
  routes.push(route);
53
53
  }
54
54
  function matchRoute(path, method) {
55
- const exact = routes.findLast ? routes.findLast((r) => r.path === path && r.method === method) : [...routes].reverse().find((r) => r.path === path && r.method === method);
55
+ const exact = routes.reduceRight((found, r) => found ?? (r.path === path && r.method === method ? r : null), null);
56
56
  if (exact)
57
57
  return { route: exact };
58
58
  for (let i = routes.length - 1;i >= 0; i--) {
@@ -85,14 +85,20 @@ function createRouter() {
85
85
  const method = request.method;
86
86
  const match = matchRoute(path, method);
87
87
  if (!match) {
88
- const pathExists = routes.some((r) => {
89
- if (r.path === path)
90
- return true;
88
+ let pathExists = false;
89
+ for (let i = routes.length - 1;i >= 0; i--) {
90
+ const r = routes[i];
91
+ if (r.path === path) {
92
+ pathExists = true;
93
+ break;
94
+ }
91
95
  if (r.params && r.params.length > 0 && r.regex) {
92
- return r.regex.test(path);
96
+ if (r.regex.test(path)) {
97
+ pathExists = true;
98
+ break;
99
+ }
93
100
  }
94
- return false;
95
- });
101
+ }
96
102
  if (pathExists) {
97
103
  return new Response(JSON.stringify({
98
104
  success: false,
@@ -114,7 +120,8 @@ function createRouter() {
114
120
  return result;
115
121
  }
116
122
  }
117
- return match.route.handler(request, env, ctx, match.params);
123
+ const handlerResult = await match.route.handler(request, env, ctx, match.params);
124
+ return handlerResult ?? new Response(null, { status: 204 });
118
125
  }
119
126
  };
120
127
  }
@@ -1,32 +1,9 @@
1
- /**
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" });
27
- */
28
- export declare function serviceFetch(binding: any, path: string, body?: unknown, options?: {
1
+ export interface ServiceBinding {
2
+ fetch(request: Request | string, init?: RequestInit): Promise<Response>;
3
+ }
4
+ export declare function serviceFetch(binding: ServiceBinding, path: string, body?: unknown, options?: {
29
5
  method?: string;
30
6
  headers?: Record<string, string>;
7
+ timeout?: number;
31
8
  }): Promise<Response>;
32
9
  //# 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":"AAAA,MAAM,WAAW,cAAc;IAC7B,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACzE;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"}
@@ -17,13 +17,15 @@ var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
17
17
 
18
18
  // src/service-bindings.ts
19
19
  function serviceFetch(binding, path, body, options) {
20
+ const timeout = options?.timeout ?? 30000;
20
21
  return binding.fetch(`http://internal${path}`, {
21
22
  method: options?.method ?? "POST",
22
23
  headers: {
23
24
  "Content-Type": "application/json",
24
25
  ...options?.headers
25
26
  },
26
- body: body != null ? JSON.stringify(body) : undefined
27
+ body: body != null ? JSON.stringify(body) : undefined,
28
+ signal: AbortSignal.timeout(timeout)
27
29
  });
28
30
  }
29
31
  export {
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;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;AA2BD;;;;;;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"}
package/dist/session.js CHANGED
@@ -15,10 +15,90 @@ 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
19
  import { homedir } from "os";
20
- import { join } from "path";
21
- var SESSION_FILE = join(homedir(), ".hoox", "session.json");
20
+ import { join, resolve } from "path";
21
+ function createHooxPath(path) {
22
+ return path;
23
+ }
24
+ function getHooxHome() {
25
+ try {
26
+ const home = homedir();
27
+ if (!home || home.length === 0) {
28
+ return createHooxPath(resolve(process.cwd(), ".hoox"));
29
+ }
30
+ return createHooxPath(join(home, ".hoox"));
31
+ } catch {
32
+ return createHooxPath(resolve(process.cwd(), ".hoox"));
33
+ }
34
+ }
35
+ function resolveHooxPath(relativePath) {
36
+ if (!relativePath || typeof relativePath !== "string") {
37
+ throw new Error("relativePath must be a non-empty string");
38
+ }
39
+ if (relativePath.includes("..")) {
40
+ throw new Error(`Path traversal detected in relativePath: "${relativePath}"`);
41
+ }
42
+ const normalizedPath = relativePath.replace(/\\/g, "/");
43
+ const hooxHome = getHooxHome();
44
+ const fullPath = join(hooxHome, normalizedPath);
45
+ const resolvedPath = resolve(fullPath);
46
+ const resolvedHome = resolve(hooxHome);
47
+ if (!resolvedPath.startsWith(resolvedHome)) {
48
+ throw new Error(`Resolved path "${resolvedPath}" is outside Hoox home directory`);
49
+ }
50
+ return createHooxPath(resolvedPath);
51
+ }
52
+ function isWithinHooxHome(path) {
53
+ try {
54
+ const hooxHome = getHooxHome();
55
+ const resolvedPath = resolve(path);
56
+ const resolvedHome = resolve(hooxHome);
57
+ return resolvedPath.startsWith(resolvedHome);
58
+ } catch {
59
+ return false;
60
+ }
61
+ }
62
+ function getRelativeHooxPath(absolutePath) {
63
+ try {
64
+ if (!isWithinHooxHome(absolutePath)) {
65
+ return null;
66
+ }
67
+ const hooxHome = getHooxHome();
68
+ const resolvedPath = resolve(absolutePath);
69
+ const resolvedHome = resolve(hooxHome);
70
+ const relativePath = resolvedPath.slice(resolvedHome.length);
71
+ return relativePath.startsWith("/") || relativePath.startsWith("\\") ? relativePath.slice(1) : relativePath;
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+ function getHooxRepoPath() {
77
+ return resolveHooxPath("repo");
78
+ }
79
+ function getHooxConfigDir() {
80
+ return resolveHooxPath("config");
81
+ }
82
+ function getHooxDataDir() {
83
+ return resolveHooxPath("data");
84
+ }
85
+ function getHooxWranglerPath() {
86
+ return resolveHooxPath("config/wrangler.jsonc");
87
+ }
88
+ function getHooxStatePath() {
89
+ return resolveHooxPath("data/state.json");
90
+ }
91
+
92
+ // src/session.ts
93
+ var TUI_STATE_DIR = ".tui-state";
94
+ var SESSION_FILE = (() => {
95
+ try {
96
+ const hooxHome = getHooxHome();
97
+ return `${hooxHome}/${TUI_STATE_DIR}/session.json`;
98
+ } catch {
99
+ return `${process.cwd()}/${TUI_STATE_DIR}/session.json`;
100
+ }
101
+ })();
22
102
  var DEFAULT_SESSION = {
23
103
  activeView: "dashboard",
24
104
  sidebarExpanded: true,
package/dist/sse.js CHANGED
@@ -59,7 +59,7 @@ function subscribeSSE(path, callback, onStatus) {
59
59
  }
60
60
  }
61
61
  }
62
- } catch (error) {
62
+ } catch (_error) {
63
63
  if (aborted)
64
64
  break;
65
65
  onStatus?.("reconnecting");
@@ -1,4 +1,4 @@
1
- import type { WorkerInfo, Trade, Alert, LogEntry, SystemMetrics, ConnectionStatus } from "../types";
1
+ import type { WorkerInfo, Trade, Alert, LogEntry, SystemMetrics, ConnectionStatus, CliErrorDetails } from "../types";
2
2
  export interface ServiceState {
3
3
  workers: WorkerInfo[];
4
4
  tradeStream: Trade[];
@@ -12,6 +12,13 @@ export interface ServiceState {
12
12
  retryCount: number;
13
13
  /** Last connection error message (for display) */
14
14
  lastError: string | null;
15
+ /**
16
+ * Structured CLI bridge error details. Populated when a `hoox` command
17
+ * fails so the status bar can render the full diagnostic context
18
+ * (command, exit code, stderr, error type) — not just a one-line summary.
19
+ * Cleared together with `lastError` on success or retry reset.
20
+ */
21
+ lastErrorDetails: CliErrorDetails | null;
15
22
  /** Timestamp (ms) of the last successful API fetch */
16
23
  lastSuccessfulFetch: number;
17
24
  /** Current backoff delay in ms (0 when not reconnecting) */
@@ -39,6 +46,24 @@ interface ServiceActions {
39
46
  resetRetries: () => void;
40
47
  /** Force a connection retry from offline state */
41
48
  forceRetry: () => void;
49
+ /**
50
+ * Record a structured CLI bridge failure. Stores both the short
51
+ * `lastError` string (for the one-line summary) and the full
52
+ * `lastErrorDetails` (for click-to-expand). Pass `null` to clear.
53
+ */
54
+ setLastErrorDetails: (details: CliErrorDetails | null) => void;
55
+ /**
56
+ * Clear the last error and last error details. Convenience alias for
57
+ * `setLastErrorDetails(null)` — clearer at call sites where the
58
+ * intent is to acknowledge an error rather than record a new one.
59
+ */
60
+ clearError: () => void;
61
+ /**
62
+ * Record a CLI bridge failure **and** add a matching high-severity
63
+ * alert to the alerts ring buffer. Use this when a failure should
64
+ * also be visible in the alerts panel (not just the status bar).
65
+ */
66
+ addCliErrorAlert: (details: CliErrorDetails) => void;
42
67
  }
43
68
  export declare const useServiceStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<ServiceState & ServiceActions>, "setState"> & {
44
69
  setState(nextStateOrUpdater: (ServiceState & ServiceActions) | Partial<ServiceState & ServiceActions> | ((state: {
@@ -102,6 +127,15 @@ export declare const useServiceStore: import("zustand").UseBoundStore<Omit<impor
102
127
  selectedWorkerId: string | null;
103
128
  retryCount: number;
104
129
  lastError: string | null;
130
+ lastErrorDetails: {
131
+ command: string;
132
+ exitCode: number;
133
+ stderr: string;
134
+ stdout: string;
135
+ errorType: import("..").CliErrorType;
136
+ timestamp: number;
137
+ duration: number;
138
+ } | null;
105
139
  lastSuccessfulFetch: number;
106
140
  reconnectDelay: number;
107
141
  disconnectedAt: number | null;
@@ -120,6 +154,9 @@ export declare const useServiceStore: import("zustand").UseBoundStore<Omit<impor
120
154
  handleConnectionFailure: (errorMessage: string) => void;
121
155
  resetRetries: () => void;
122
156
  forceRetry: () => void;
157
+ setLastErrorDetails: (details: CliErrorDetails | null) => void;
158
+ clearError: () => void;
159
+ addCliErrorAlert: (details: CliErrorDetails) => void;
123
160
  }) => void), shouldReplace?: false): void;
124
161
  setState(nextStateOrUpdater: (ServiceState & ServiceActions) | ((state: {
125
162
  workers: {
@@ -182,6 +219,15 @@ export declare const useServiceStore: import("zustand").UseBoundStore<Omit<impor
182
219
  selectedWorkerId: string | null;
183
220
  retryCount: number;
184
221
  lastError: string | null;
222
+ lastErrorDetails: {
223
+ command: string;
224
+ exitCode: number;
225
+ stderr: string;
226
+ stdout: string;
227
+ errorType: import("..").CliErrorType;
228
+ timestamp: number;
229
+ duration: number;
230
+ } | null;
185
231
  lastSuccessfulFetch: number;
186
232
  reconnectDelay: number;
187
233
  disconnectedAt: number | null;
@@ -200,6 +246,9 @@ export declare const useServiceStore: import("zustand").UseBoundStore<Omit<impor
200
246
  handleConnectionFailure: (errorMessage: string) => void;
201
247
  resetRetries: () => void;
202
248
  forceRetry: () => void;
249
+ setLastErrorDetails: (details: CliErrorDetails | null) => void;
250
+ clearError: () => void;
251
+ addCliErrorAlert: (details: CliErrorDetails) => void;
203
252
  }) => void), shouldReplace: true): void;
204
253
  }>;
205
254
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"service-store.d.ts","sourceRoot":"","sources":["../../src/stores/service-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"service-store.d.ts","sourceRoot":"","sources":["../../src/stores/service-store.ts"],"names":[],"mappings":"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"}
@@ -161,7 +161,7 @@ function subscribeSSE(path, callback, onStatus) {
161
161
  }
162
162
  }
163
163
  }
164
- } catch (error) {
164
+ } catch (_error) {
165
165
  if (aborted)
166
166
  break;
167
167
  onStatus?.("reconnecting");
@@ -190,6 +190,13 @@ var init_sse = __esm(() => {
190
190
  // src/stores/service-store.ts
191
191
  import { create } from "zustand";
192
192
  import { immer } from "zustand/middleware/immer";
193
+ function summarizeCliError(details) {
194
+ const raw = details.stderr.trim() || details.stdout.trim() || details.command || "Unknown CLI error";
195
+ const firstLine = raw.split(`
196
+ `, 1)[0] ?? "";
197
+ const trimmed = firstLine.slice(0, 120).trimEnd();
198
+ return trimmed.length > 0 ? trimmed : details.command;
199
+ }
193
200
  var BACKOFF_SEQUENCE = [1000, 2000, 4000, 8000, 16000];
194
201
  var MAX_RETRIES2 = 5;
195
202
  function ringPush(arr, item, max) {
@@ -210,6 +217,7 @@ var initialState = {
210
217
  selectedWorkerId: null,
211
218
  retryCount: 0,
212
219
  lastError: null,
220
+ lastErrorDetails: null,
213
221
  lastSuccessfulFetch: 0,
214
222
  reconnectDelay: 0,
215
223
  disconnectedAt: null
@@ -234,12 +242,21 @@ var useServiceStore = create()(immer((set, get) => ({
234
242
  state.connectionStatus = "connected";
235
243
  state.retryCount = 0;
236
244
  state.lastError = null;
245
+ state.lastErrorDetails = null;
237
246
  state.reconnectDelay = 0;
238
247
  state.disconnectedAt = null;
239
248
  }
240
249
  });
241
250
  } catch (error) {
242
251
  const msg = error instanceof Error ? error.message : "Unknown fetch error";
252
+ get().addAlert({
253
+ id: crypto.randomUUID(),
254
+ type: "connection",
255
+ severity: "error",
256
+ message: msg,
257
+ timestamp: Date.now(),
258
+ acknowledged: false
259
+ });
243
260
  set((state) => {
244
261
  if (state.connectionStatus === "connected" || state.connectionStatus === "polling") {
245
262
  state.connectionStatus = "reconnecting";
@@ -265,7 +282,18 @@ var useServiceStore = create()(immer((set, get) => ({
265
282
  await subscribeSSE2("/trades/stream", (trade) => {
266
283
  get().pushTrade(trade);
267
284
  });
268
- } catch {}
285
+ } catch (error) {
286
+ const msg = error instanceof Error ? error.message : "SSE trade stream failed";
287
+ get().addAlert({
288
+ id: crypto.randomUUID(),
289
+ type: "connection",
290
+ severity: "error",
291
+ message: msg,
292
+ timestamp: Date.now(),
293
+ acknowledged: false
294
+ });
295
+ get().handleConnectionFailure(msg);
296
+ }
269
297
  },
270
298
  streamLogs: async () => {
271
299
  const { subscribeSSE: subscribeSSE2 } = await Promise.resolve().then(() => (init_sse(), exports_sse));
@@ -273,7 +301,18 @@ var useServiceStore = create()(immer((set, get) => ({
273
301
  await subscribeSSE2("/logs/stream", (log) => {
274
302
  get().pushLog(log);
275
303
  });
276
- } catch {}
304
+ } catch (error) {
305
+ const msg = error instanceof Error ? error.message : "SSE log stream failed";
306
+ get().addAlert({
307
+ id: crypto.randomUUID(),
308
+ type: "connection",
309
+ severity: "error",
310
+ message: msg,
311
+ timestamp: Date.now(),
312
+ acknowledged: false
313
+ });
314
+ get().handleConnectionFailure(msg);
315
+ }
277
316
  },
278
317
  addAlert: (alert) => set((state) => {
279
318
  state.alerts = ringPush(state.alerts, alert, MAX_ALERTS);
@@ -286,6 +325,7 @@ var useServiceStore = create()(immer((set, get) => ({
286
325
  if (status === "connected") {
287
326
  state.retryCount = 0;
288
327
  state.lastError = null;
328
+ state.lastErrorDetails = null;
289
329
  state.reconnectDelay = 0;
290
330
  state.disconnectedAt = null;
291
331
  }
@@ -315,6 +355,7 @@ var useServiceStore = create()(immer((set, get) => ({
315
355
  state.lastSuccessfulFetch = Date.now();
316
356
  state.retryCount = 0;
317
357
  state.lastError = null;
358
+ state.lastErrorDetails = null;
318
359
  state.reconnectDelay = 0;
319
360
  state.disconnectedAt = null;
320
361
  if (state.connectionStatus === "reconnecting" || state.connectionStatus === "polling") {
@@ -342,6 +383,7 @@ var useServiceStore = create()(immer((set, get) => ({
342
383
  resetRetries: () => set((state) => {
343
384
  state.retryCount = 0;
344
385
  state.lastError = null;
386
+ state.lastErrorDetails = null;
345
387
  state.reconnectDelay = 0;
346
388
  state.disconnectedAt = null;
347
389
  }),
@@ -350,8 +392,30 @@ var useServiceStore = create()(immer((set, get) => ({
350
392
  state.connectionStatus = "polling";
351
393
  state.retryCount = 0;
352
394
  state.lastError = null;
395
+ state.lastErrorDetails = null;
353
396
  state.reconnectDelay = 0;
354
397
  }
398
+ }),
399
+ setLastErrorDetails: (details) => set((state) => {
400
+ state.lastErrorDetails = details;
401
+ state.lastError = details === null ? null : summarizeCliError(details);
402
+ }),
403
+ clearError: () => set((state) => {
404
+ state.lastError = null;
405
+ state.lastErrorDetails = null;
406
+ }),
407
+ addCliErrorAlert: (details) => set((state) => {
408
+ state.lastErrorDetails = details;
409
+ state.lastError = summarizeCliError(details);
410
+ const summary = summarizeCliError(details);
411
+ state.alerts = ringPush(state.alerts, {
412
+ id: crypto.randomUUID(),
413
+ type: "connection",
414
+ severity: "error",
415
+ message: `CLI failure: ${summary}`,
416
+ timestamp: Date.now(),
417
+ acknowledged: false
418
+ }, MAX_ALERTS);
355
419
  })
356
420
  })));
357
421
  export {