brainrot-cli 0.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 (162) hide show
  1. package/README.md +372 -0
  2. package/dist/AchievementNotification.d.ts +28 -0
  3. package/dist/AchievementNotification.d.ts.map +1 -0
  4. package/dist/AchievementNotification.js +74 -0
  5. package/dist/AchievementNotification.js.map +1 -0
  6. package/dist/GameSelector.d.ts +25 -0
  7. package/dist/GameSelector.d.ts.map +1 -0
  8. package/dist/GameSelector.js +105 -0
  9. package/dist/GameSelector.js.map +1 -0
  10. package/dist/HelpOverlay.d.ts +15 -0
  11. package/dist/HelpOverlay.d.ts.map +1 -0
  12. package/dist/HelpOverlay.js +134 -0
  13. package/dist/HelpOverlay.js.map +1 -0
  14. package/dist/Layout.d.ts +49 -0
  15. package/dist/Layout.d.ts.map +1 -0
  16. package/dist/Layout.js +83 -0
  17. package/dist/Layout.js.map +1 -0
  18. package/dist/Leaderboard.d.ts +46 -0
  19. package/dist/Leaderboard.d.ts.map +1 -0
  20. package/dist/Leaderboard.js +68 -0
  21. package/dist/Leaderboard.js.map +1 -0
  22. package/dist/LogViewer.d.ts +33 -0
  23. package/dist/LogViewer.d.ts.map +1 -0
  24. package/dist/LogViewer.js +179 -0
  25. package/dist/LogViewer.js.map +1 -0
  26. package/dist/LoopAlertOverlay.d.ts +15 -0
  27. package/dist/LoopAlertOverlay.d.ts.map +1 -0
  28. package/dist/LoopAlertOverlay.js +17 -0
  29. package/dist/LoopAlertOverlay.js.map +1 -0
  30. package/dist/LoopManagementPanel.d.ts +44 -0
  31. package/dist/LoopManagementPanel.d.ts.map +1 -0
  32. package/dist/LoopManagementPanel.js +220 -0
  33. package/dist/LoopManagementPanel.js.map +1 -0
  34. package/dist/SettingsMenu.d.ts +22 -0
  35. package/dist/SettingsMenu.d.ts.map +1 -0
  36. package/dist/SettingsMenu.js +367 -0
  37. package/dist/SettingsMenu.js.map +1 -0
  38. package/dist/SplitPane.d.ts +63 -0
  39. package/dist/SplitPane.d.ts.map +1 -0
  40. package/dist/SplitPane.js +104 -0
  41. package/dist/SplitPane.js.map +1 -0
  42. package/dist/StatsMenu.d.ts +15 -0
  43. package/dist/StatsMenu.d.ts.map +1 -0
  44. package/dist/StatsMenu.js +230 -0
  45. package/dist/StatsMenu.js.map +1 -0
  46. package/dist/StatusBar.d.ts +58 -0
  47. package/dist/StatusBar.d.ts.map +1 -0
  48. package/dist/StatusBar.js +106 -0
  49. package/dist/StatusBar.js.map +1 -0
  50. package/dist/__tests__/ralph-loop-parser.test.d.ts +2 -0
  51. package/dist/__tests__/ralph-loop-parser.test.d.ts.map +1 -0
  52. package/dist/__tests__/ralph-loop-parser.test.js +143 -0
  53. package/dist/__tests__/ralph-loop-parser.test.js.map +1 -0
  54. package/dist/claude-code-process.d.ts +76 -0
  55. package/dist/claude-code-process.d.ts.map +1 -0
  56. package/dist/claude-code-process.js +221 -0
  57. package/dist/claude-code-process.js.map +1 -0
  58. package/dist/cli.d.ts +42 -0
  59. package/dist/cli.d.ts.map +1 -0
  60. package/dist/cli.js +265 -0
  61. package/dist/cli.js.map +1 -0
  62. package/dist/config.d.ts +206 -0
  63. package/dist/config.d.ts.map +1 -0
  64. package/dist/config.js +270 -0
  65. package/dist/config.js.map +1 -0
  66. package/dist/game-types.d.ts +177 -0
  67. package/dist/game-types.d.ts.map +1 -0
  68. package/dist/game-types.js +55 -0
  69. package/dist/game-types.js.map +1 -0
  70. package/dist/games/MinesweeperGame.d.ts +15 -0
  71. package/dist/games/MinesweeperGame.d.ts.map +1 -0
  72. package/dist/games/MinesweeperGame.js +555 -0
  73. package/dist/games/MinesweeperGame.js.map +1 -0
  74. package/dist/games/PongGame.d.ts +15 -0
  75. package/dist/games/PongGame.d.ts.map +1 -0
  76. package/dist/games/PongGame.js +379 -0
  77. package/dist/games/PongGame.js.map +1 -0
  78. package/dist/games/SnakeGame.d.ts +15 -0
  79. package/dist/games/SnakeGame.d.ts.map +1 -0
  80. package/dist/games/SnakeGame.js +333 -0
  81. package/dist/games/SnakeGame.js.map +1 -0
  82. package/dist/games/TetrisGame.d.ts +15 -0
  83. package/dist/games/TetrisGame.d.ts.map +1 -0
  84. package/dist/games/TetrisGame.js +654 -0
  85. package/dist/games/TetrisGame.js.map +1 -0
  86. package/dist/games/index.d.ts +23 -0
  87. package/dist/games/index.d.ts.map +1 -0
  88. package/dist/games/index.js +47 -0
  89. package/dist/games/index.js.map +1 -0
  90. package/dist/high-scores.d.ts +57 -0
  91. package/dist/high-scores.d.ts.map +1 -0
  92. package/dist/high-scores.js +230 -0
  93. package/dist/high-scores.js.map +1 -0
  94. package/dist/index.d.ts +3 -0
  95. package/dist/index.d.ts.map +1 -0
  96. package/dist/index.js +264 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/ralph-loop-parser.d.ts +58 -0
  99. package/dist/ralph-loop-parser.d.ts.map +1 -0
  100. package/dist/ralph-loop-parser.js +315 -0
  101. package/dist/ralph-loop-parser.js.map +1 -0
  102. package/dist/stats.d.ts +142 -0
  103. package/dist/stats.d.ts.map +1 -0
  104. package/dist/stats.js +521 -0
  105. package/dist/stats.js.map +1 -0
  106. package/dist/styled-components.d.ts +231 -0
  107. package/dist/styled-components.d.ts.map +1 -0
  108. package/dist/styled-components.js +192 -0
  109. package/dist/styled-components.js.map +1 -0
  110. package/dist/theme.d.ts +301 -0
  111. package/dist/theme.d.ts.map +1 -0
  112. package/dist/theme.js +372 -0
  113. package/dist/theme.js.map +1 -0
  114. package/dist/themes.d.ts +117 -0
  115. package/dist/themes.d.ts.map +1 -0
  116. package/dist/themes.js +296 -0
  117. package/dist/themes.js.map +1 -0
  118. package/dist/ui/index.d.ts +13 -0
  119. package/dist/ui/index.d.ts.map +1 -0
  120. package/dist/ui/index.js +29 -0
  121. package/dist/ui/index.js.map +1 -0
  122. package/dist/use-claude-code.d.ts +30 -0
  123. package/dist/use-claude-code.d.ts.map +1 -0
  124. package/dist/use-claude-code.js +84 -0
  125. package/dist/use-claude-code.js.map +1 -0
  126. package/dist/use-config.d.ts +58 -0
  127. package/dist/use-config.d.ts.map +1 -0
  128. package/dist/use-config.js +113 -0
  129. package/dist/use-config.js.map +1 -0
  130. package/dist/use-game-loop.d.ts +47 -0
  131. package/dist/use-game-loop.d.ts.map +1 -0
  132. package/dist/use-game-loop.js +136 -0
  133. package/dist/use-game-loop.js.map +1 -0
  134. package/dist/use-high-scores.d.ts +41 -0
  135. package/dist/use-high-scores.d.ts.map +1 -0
  136. package/dist/use-high-scores.js +94 -0
  137. package/dist/use-high-scores.js.map +1 -0
  138. package/dist/use-layout-state.d.ts +77 -0
  139. package/dist/use-layout-state.d.ts.map +1 -0
  140. package/dist/use-layout-state.js +160 -0
  141. package/dist/use-layout-state.js.map +1 -0
  142. package/dist/use-ralph-loop.d.ts +41 -0
  143. package/dist/use-ralph-loop.d.ts.map +1 -0
  144. package/dist/use-ralph-loop.js +106 -0
  145. package/dist/use-ralph-loop.js.map +1 -0
  146. package/dist/use-spinner.d.ts +46 -0
  147. package/dist/use-spinner.d.ts.map +1 -0
  148. package/dist/use-spinner.js +71 -0
  149. package/dist/use-spinner.js.map +1 -0
  150. package/dist/use-stats.d.ts +59 -0
  151. package/dist/use-stats.d.ts.map +1 -0
  152. package/dist/use-stats.js +150 -0
  153. package/dist/use-stats.js.map +1 -0
  154. package/dist/use-terminal-size.d.ts +29 -0
  155. package/dist/use-terminal-size.d.ts.map +1 -0
  156. package/dist/use-terminal-size.js +48 -0
  157. package/dist/use-terminal-size.js.map +1 -0
  158. package/dist/useTheme.d.ts +76 -0
  159. package/dist/useTheme.d.ts.map +1 -0
  160. package/dist/useTheme.js +136 -0
  161. package/dist/useTheme.js.map +1 -0
  162. package/package.json +58 -0
@@ -0,0 +1,160 @@
1
+ /**
2
+ * Hook for managing and persisting layout state during a session.
3
+ * Handles split ratios, pane visibility, and layout preferences.
4
+ */
5
+ import { useState, useCallback, useMemo } from "react";
6
+ const DEFAULT_OPTIONS = {
7
+ initialDirection: "horizontal",
8
+ initialSplitRatio: 0.5,
9
+ minRatio: 0.2,
10
+ maxRatio: 0.8,
11
+ resizeStep: 0.05,
12
+ };
13
+ /**
14
+ * React hook for managing layout state with resizable panes.
15
+ *
16
+ * @param options - Configuration options for the layout
17
+ * @returns Layout state and control functions
18
+ */
19
+ export function useLayoutState(options = {}) {
20
+ const opts = { ...DEFAULT_OPTIONS, ...options };
21
+ const [state, setState] = useState(() => ({
22
+ direction: opts.initialDirection,
23
+ splitRatio: opts.initialSplitRatio,
24
+ isResizing: false,
25
+ showSecondary: true,
26
+ focusedPane: 0,
27
+ }));
28
+ const clampRatio = useCallback((ratio) => {
29
+ return Math.max(opts.minRatio, Math.min(opts.maxRatio, ratio));
30
+ }, [opts.minRatio, opts.maxRatio]);
31
+ const setSplitRatio = useCallback((ratio) => {
32
+ setState((prev) => ({
33
+ ...prev,
34
+ splitRatio: clampRatio(ratio),
35
+ }));
36
+ }, [clampRatio]);
37
+ const adjustSplitRatio = useCallback((delta) => {
38
+ setState((prev) => ({
39
+ ...prev,
40
+ splitRatio: clampRatio(prev.splitRatio + delta),
41
+ }));
42
+ }, [clampRatio]);
43
+ const increaseRatio = useCallback(() => {
44
+ adjustSplitRatio(opts.resizeStep);
45
+ }, [adjustSplitRatio, opts.resizeStep]);
46
+ const decreaseRatio = useCallback(() => {
47
+ adjustSplitRatio(-opts.resizeStep);
48
+ }, [adjustSplitRatio, opts.resizeStep]);
49
+ const toggleDirection = useCallback(() => {
50
+ setState((prev) => ({
51
+ ...prev,
52
+ direction: prev.direction === "horizontal" ? "vertical" : "horizontal",
53
+ }));
54
+ }, []);
55
+ const setDirection = useCallback((direction) => {
56
+ setState((prev) => ({
57
+ ...prev,
58
+ direction,
59
+ }));
60
+ }, []);
61
+ const toggleSecondary = useCallback(() => {
62
+ setState((prev) => ({
63
+ ...prev,
64
+ showSecondary: !prev.showSecondary,
65
+ }));
66
+ }, []);
67
+ const setShowSecondary = useCallback((show) => {
68
+ setState((prev) => ({
69
+ ...prev,
70
+ showSecondary: show,
71
+ }));
72
+ }, []);
73
+ const setResizing = useCallback((isResizing) => {
74
+ setState((prev) => ({
75
+ ...prev,
76
+ isResizing,
77
+ }));
78
+ }, []);
79
+ const focusPane = useCallback((pane) => {
80
+ setState((prev) => ({
81
+ ...prev,
82
+ focusedPane: pane,
83
+ }));
84
+ }, []);
85
+ const toggleFocus = useCallback(() => {
86
+ setState((prev) => ({
87
+ ...prev,
88
+ focusedPane: prev.focusedPane === 0 ? 1 : 0,
89
+ }));
90
+ }, []);
91
+ const resetLayout = useCallback(() => {
92
+ setState({
93
+ direction: opts.initialDirection,
94
+ splitRatio: opts.initialSplitRatio,
95
+ isResizing: false,
96
+ showSecondary: true,
97
+ focusedPane: 0,
98
+ });
99
+ }, [opts.initialDirection, opts.initialSplitRatio]);
100
+ const calculateDimensions = useCallback((availableWidth, availableHeight) => {
101
+ if (!state.showSecondary) {
102
+ return {
103
+ firstPane: { width: availableWidth, height: availableHeight },
104
+ secondPane: { width: 0, height: 0 },
105
+ };
106
+ }
107
+ if (state.direction === "horizontal") {
108
+ // Subtract 1 for the divider
109
+ const usableWidth = availableWidth - 1;
110
+ const firstWidth = Math.floor(usableWidth * state.splitRatio);
111
+ const secondWidth = usableWidth - firstWidth;
112
+ return {
113
+ firstPane: { width: firstWidth, height: availableHeight },
114
+ secondPane: { width: secondWidth, height: availableHeight },
115
+ };
116
+ }
117
+ else {
118
+ // Vertical split - subtract 1 for the divider
119
+ const usableHeight = availableHeight - 1;
120
+ const firstHeight = Math.floor(usableHeight * state.splitRatio);
121
+ const secondHeight = usableHeight - firstHeight;
122
+ return {
123
+ firstPane: { width: availableWidth, height: firstHeight },
124
+ secondPane: { width: availableWidth, height: secondHeight },
125
+ };
126
+ }
127
+ }, [state.direction, state.splitRatio, state.showSecondary]);
128
+ return useMemo(() => ({
129
+ state,
130
+ setSplitRatio,
131
+ adjustSplitRatio,
132
+ increaseRatio,
133
+ decreaseRatio,
134
+ toggleDirection,
135
+ setDirection,
136
+ toggleSecondary,
137
+ setShowSecondary,
138
+ setResizing,
139
+ focusPane,
140
+ toggleFocus,
141
+ resetLayout,
142
+ calculateDimensions,
143
+ }), [
144
+ state,
145
+ setSplitRatio,
146
+ adjustSplitRatio,
147
+ increaseRatio,
148
+ decreaseRatio,
149
+ toggleDirection,
150
+ setDirection,
151
+ toggleSecondary,
152
+ setShowSecondary,
153
+ setResizing,
154
+ focusPane,
155
+ toggleFocus,
156
+ resetLayout,
157
+ calculateDimensions,
158
+ ]);
159
+ }
160
+ //# sourceMappingURL=use-layout-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-layout-state.js","sourceRoot":"","sources":["../src/use-layout-state.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAqEvD,MAAM,eAAe,GAAoC;IACvD,gBAAgB,EAAE,YAAY;IAC9B,iBAAiB,EAAE,GAAG;IACtB,QAAQ,EAAE,GAAG;IACb,QAAQ,EAAE,GAAG;IACb,UAAU,EAAE,IAAI;CACjB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAC5B,UAAiC,EAAE;IAEnC,MAAM,IAAI,GAAG,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,EAAE,CAAC;IAEhD,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAc,GAAG,EAAE,CAAC,CAAC;QACrD,SAAS,EAAE,IAAI,CAAC,gBAAgB;QAChC,UAAU,EAAE,IAAI,CAAC,iBAAiB;QAClC,UAAU,EAAE,KAAK;QACjB,aAAa,EAAE,IAAI;QACnB,WAAW,EAAE,CAAC;KACf,CAAC,CAAC,CAAC;IAEJ,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,KAAa,EAAU,EAAE;QACxB,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC;IACjE,CAAC,EACD,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAC/B,CAAC;IAEF,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,KAAa,EAAE,EAAE;QAChB,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,UAAU,EAAE,UAAU,CAAC,KAAK,CAAC;SAC9B,CAAC,CAAC,CAAC;IACN,CAAC,EACD,CAAC,UAAU,CAAC,CACb,CAAC;IAEF,MAAM,gBAAgB,GAAG,WAAW,CAClC,CAAC,KAAa,EAAE,EAAE;QAChB,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,UAAU,EAAE,UAAU,CAAC,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC;SAChD,CAAC,CAAC,CAAC;IACN,CAAC,EACD,CAAC,UAAU,CAAC,CACb,CAAC;IAEF,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACpC,CAAC,EAAE,CAAC,gBAAgB,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAExC,MAAM,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;QACrC,gBAAgB,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACrC,CAAC,EAAE,CAAC,gBAAgB,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IAExC,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;QACvC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,SAAS,EAAE,IAAI,CAAC,SAAS,KAAK,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,YAAY;SACvE,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,YAAY,GAAG,WAAW,CAAC,CAAC,SAAyB,EAAE,EAAE;QAC7D,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,SAAS;SACV,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,eAAe,GAAG,WAAW,CAAC,GAAG,EAAE;QACvC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,aAAa,EAAE,CAAC,IAAI,CAAC,aAAa;SACnC,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,IAAa,EAAE,EAAE;QACrD,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,aAAa,EAAE,IAAI;SACpB,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,UAAmB,EAAE,EAAE;QACtD,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,UAAU;SACX,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,IAAW,EAAE,EAAE;QAC5C,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,WAAW,EAAE,IAAI;SAClB,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;QACnC,QAAQ,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAClB,GAAG,IAAI;YACP,WAAW,EAAE,IAAI,CAAC,WAAW,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAC5C,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,WAAW,GAAG,WAAW,CAAC,GAAG,EAAE;QACnC,QAAQ,CAAC;YACP,SAAS,EAAE,IAAI,CAAC,gBAAgB;YAChC,UAAU,EAAE,IAAI,CAAC,iBAAiB;YAClC,UAAU,EAAE,KAAK;YACjB,aAAa,EAAE,IAAI;YACnB,WAAW,EAAE,CAAC;SACf,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,IAAI,CAAC,gBAAgB,EAAE,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;IAEpD,MAAM,mBAAmB,GAAG,WAAW,CACrC,CAAC,cAAsB,EAAE,eAAuB,EAAkB,EAAE;QAClE,IAAI,CAAC,KAAK,CAAC,aAAa,EAAE,CAAC;YACzB,OAAO;gBACL,SAAS,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,eAAe,EAAE;gBAC7D,UAAU,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE;aACpC,CAAC;QACJ,CAAC;QAED,IAAI,KAAK,CAAC,SAAS,KAAK,YAAY,EAAE,CAAC;YACrC,6BAA6B;YAC7B,MAAM,WAAW,GAAG,cAAc,GAAG,CAAC,CAAC;YACvC,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;YAC9D,MAAM,WAAW,GAAG,WAAW,GAAG,UAAU,CAAC;YAE7C,OAAO;gBACL,SAAS,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,eAAe,EAAE;gBACzD,UAAU,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE;aAC5D,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,8CAA8C;YAC9C,MAAM,YAAY,GAAG,eAAe,GAAG,CAAC,CAAC;YACzC,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,UAAU,CAAC,CAAC;YAChE,MAAM,YAAY,GAAG,YAAY,GAAG,WAAW,CAAC;YAEhD,OAAO;gBACL,SAAS,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,WAAW,EAAE;gBACzD,UAAU,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,YAAY,EAAE;aAC5D,CAAC;QACJ,CAAC;IACH,CAAC,EACD,CAAC,KAAK,CAAC,SAAS,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,aAAa,CAAC,CACzD,CAAC;IAEF,OAAO,OAAO,CACZ,GAAG,EAAE,CAAC,CAAC;QACL,KAAK;QACL,aAAa;QACb,gBAAgB;QAChB,aAAa;QACb,aAAa;QACb,eAAe;QACf,YAAY;QACZ,eAAe;QACf,gBAAgB;QAChB,WAAW;QACX,SAAS;QACT,WAAW;QACX,WAAW;QACX,mBAAmB;KACpB,CAAC,EACF;QACE,KAAK;QACL,aAAa;QACb,gBAAgB;QAChB,aAAa;QACb,aAAa;QACb,eAAe;QACf,YAAY;QACZ,eAAe;QACf,gBAAgB;QAChB,WAAW;QACX,SAAS;QACT,WAAW;QACX,WAAW;QACX,mBAAmB;KACpB,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * React hook for Ralph Loop status parsing
3
+ *
4
+ * Integrates with useClaudeCode to parse output and provide
5
+ * structured Ralph loop state for UI consumption.
6
+ */
7
+ import type { ClaudeCodeOutput } from "./use-claude-code.js";
8
+ import { type RalphLoopState, type RalphLoopStatus } from "./ralph-loop-parser.js";
9
+ export interface UseRalphLoopOptions {
10
+ /** Maximum number of recent outputs to keep in history */
11
+ maxHistorySize?: number;
12
+ /** Whether to auto-detect loop start from output */
13
+ autoDetectStart?: boolean;
14
+ }
15
+ export interface UseRalphLoopResult {
16
+ /** Current Ralph loop state */
17
+ state: RalphLoopState;
18
+ /** Whether user attention is needed */
19
+ needsAttention: boolean;
20
+ /** Human-readable status message */
21
+ statusMessage: string;
22
+ /** Formatted progress string (e.g., "3/10" or "75%") */
23
+ progressString: string | null;
24
+ /** Process new output from Claude Code */
25
+ processOutput: (output: ClaudeCodeOutput) => void;
26
+ /** Process multiple outputs at once */
27
+ processOutputs: (outputs: ClaudeCodeOutput[]) => void;
28
+ /** Reset the loop state */
29
+ reset: () => void;
30
+ /** Manually set the loop status */
31
+ setStatus: (status: RalphLoopStatus) => void;
32
+ /** Clear user attention state */
33
+ clearAttention: () => void;
34
+ }
35
+ export declare function useRalphLoop(options?: UseRalphLoopOptions): UseRalphLoopResult;
36
+ /**
37
+ * Hook that automatically processes Claude Code output
38
+ * and maintains Ralph loop state
39
+ */
40
+ export declare function useRalphLoopWithClaudeOutput(outputs: ClaudeCodeOutput[], options?: UseRalphLoopOptions): UseRalphLoopResult;
41
+ //# sourceMappingURL=use-ralph-loop.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-ralph-loop.d.ts","sourceRoot":"","sources":["../src/use-ralph-loop.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EACL,KAAK,cAAc,EACnB,KAAK,eAAe,EAMrB,MAAM,wBAAwB,CAAC;AAEhC,MAAM,WAAW,mBAAmB;IAClC,0DAA0D;IAC1D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oDAAoD;IACpD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,+BAA+B;IAC/B,KAAK,EAAE,cAAc,CAAC;IACtB,uCAAuC;IACvC,cAAc,EAAE,OAAO,CAAC;IACxB,oCAAoC;IACpC,aAAa,EAAE,MAAM,CAAC;IACtB,wDAAwD;IACxD,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,0CAA0C;IAC1C,aAAa,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,uCAAuC;IACvC,cAAc,EAAE,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IACtD,2BAA2B;IAC3B,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,mCAAmC;IACnC,SAAS,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAC;IAC7C,iCAAiC;IACjC,cAAc,EAAE,MAAM,IAAI,CAAC;CAC5B;AAED,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAiGpB;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,gBAAgB,EAAE,EAC3B,OAAO,GAAE,mBAAwB,GAChC,kBAAkB,CAepB"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * React hook for Ralph Loop status parsing
3
+ *
4
+ * Integrates with useClaudeCode to parse output and provide
5
+ * structured Ralph loop state for UI consumption.
6
+ */
7
+ import { useState, useEffect, useCallback, useMemo } from "react";
8
+ import { createInitialState, parseOutput, needsUserAttention, getStatusMessage, getProgressString, } from "./ralph-loop-parser.js";
9
+ export function useRalphLoop(options = {}) {
10
+ const { autoDetectStart = true } = options;
11
+ const [state, setState] = useState(createInitialState);
12
+ // Process a single output
13
+ const processOutput = useCallback((output) => {
14
+ setState((currentState) => {
15
+ // Only process stdout for status parsing (stderr is for errors)
16
+ if (output.type === "stderr") {
17
+ // For stderr, just mark as errored if we detect error patterns
18
+ const errorState = parseOutput(output.content, currentState);
19
+ if (errorState.userAttention.type === "error") {
20
+ return {
21
+ ...errorState,
22
+ status: "errored",
23
+ };
24
+ }
25
+ return currentState;
26
+ }
27
+ const newState = parseOutput(output.content, currentState);
28
+ // Auto-detect loop start if enabled
29
+ if (autoDetectStart && currentState.status === "idle") {
30
+ if (newState.agentActivity.isActive ||
31
+ newState.progress.currentStep > 0) {
32
+ return {
33
+ ...newState,
34
+ status: "running",
35
+ };
36
+ }
37
+ }
38
+ return newState;
39
+ });
40
+ }, [autoDetectStart]);
41
+ // Process multiple outputs
42
+ const processOutputs = useCallback((outputs) => {
43
+ for (const output of outputs) {
44
+ processOutput(output);
45
+ }
46
+ }, [processOutput]);
47
+ // Reset to initial state
48
+ const reset = useCallback(() => {
49
+ setState(createInitialState());
50
+ }, []);
51
+ // Manually set status
52
+ const setStatus = useCallback((status) => {
53
+ setState((current) => ({
54
+ ...current,
55
+ status,
56
+ lastUpdated: new Date(),
57
+ }));
58
+ }, []);
59
+ // Clear user attention
60
+ const clearAttention = useCallback(() => {
61
+ setState((current) => ({
62
+ ...current,
63
+ userAttention: {
64
+ needed: false,
65
+ reason: null,
66
+ type: null,
67
+ prompt: null,
68
+ },
69
+ lastUpdated: new Date(),
70
+ }));
71
+ }, []);
72
+ // Computed values
73
+ const needsAttention = useMemo(() => needsUserAttention(state), [state]);
74
+ const statusMessage = useMemo(() => getStatusMessage(state), [state]);
75
+ const progressString = useMemo(() => getProgressString(state), [state]);
76
+ return {
77
+ state,
78
+ needsAttention,
79
+ statusMessage,
80
+ progressString,
81
+ processOutput,
82
+ processOutputs,
83
+ reset,
84
+ setStatus,
85
+ clearAttention,
86
+ };
87
+ }
88
+ /**
89
+ * Hook that automatically processes Claude Code output
90
+ * and maintains Ralph loop state
91
+ */
92
+ export function useRalphLoopWithClaudeOutput(outputs, options = {}) {
93
+ const ralphLoop = useRalphLoop(options);
94
+ // Process new outputs when they arrive
95
+ useEffect(() => {
96
+ if (outputs.length > 0) {
97
+ // Process only the most recent output to avoid reprocessing
98
+ const latestOutput = outputs[outputs.length - 1];
99
+ ralphLoop.processOutput(latestOutput);
100
+ }
101
+ // We only want to run when outputs array length changes
102
+ // eslint-disable-next-line react-hooks/exhaustive-deps
103
+ }, [outputs.length]);
104
+ return ralphLoop;
105
+ }
106
+ //# sourceMappingURL=use-ralph-loop.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-ralph-loop.js","sourceRoot":"","sources":["../src/use-ralph-loop.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAElE,OAAO,EAGL,kBAAkB,EAClB,WAAW,EACX,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,GAClB,MAAM,wBAAwB,CAAC;AA8BhC,MAAM,UAAU,YAAY,CAC1B,UAA+B,EAAE;IAEjC,MAAM,EAAE,eAAe,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IAE3C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAiB,kBAAkB,CAAC,CAAC;IAEvE,0BAA0B;IAC1B,MAAM,aAAa,GAAG,WAAW,CAC/B,CAAC,MAAwB,EAAE,EAAE;QAC3B,QAAQ,CAAC,CAAC,YAAY,EAAE,EAAE;YACxB,gEAAgE;YAChE,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAC7B,+DAA+D;gBAC/D,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;gBAC7D,IAAI,UAAU,CAAC,aAAa,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBAC9C,OAAO;wBACL,GAAG,UAAU;wBACb,MAAM,EAAE,SAA4B;qBACrC,CAAC;gBACJ,CAAC;gBACD,OAAO,YAAY,CAAC;YACtB,CAAC;YAED,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC;YAE3D,oCAAoC;YACpC,IAAI,eAAe,IAAI,YAAY,CAAC,MAAM,KAAK,MAAM,EAAE,CAAC;gBACtD,IACE,QAAQ,CAAC,aAAa,CAAC,QAAQ;oBAC/B,QAAQ,CAAC,QAAQ,CAAC,WAAW,GAAG,CAAC,EACjC,CAAC;oBACD,OAAO;wBACL,GAAG,QAAQ;wBACX,MAAM,EAAE,SAAS;qBAClB,CAAC;gBACJ,CAAC;YACH,CAAC;YAED,OAAO,QAAQ,CAAC;QAClB,CAAC,CAAC,CAAC;IACL,CAAC,EACD,CAAC,eAAe,CAAC,CAClB,CAAC;IAEF,2BAA2B;IAC3B,MAAM,cAAc,GAAG,WAAW,CAChC,CAAC,OAA2B,EAAE,EAAE;QAC9B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,aAAa,CAAC,MAAM,CAAC,CAAC;QACxB,CAAC;IACH,CAAC,EACD,CAAC,aAAa,CAAC,CAChB,CAAC;IAEF,yBAAyB;IACzB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,QAAQ,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACjC,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,sBAAsB;IACtB,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,MAAuB,EAAE,EAAE;QACxD,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACrB,GAAG,OAAO;YACV,MAAM;YACN,WAAW,EAAE,IAAI,IAAI,EAAE;SACxB,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,uBAAuB;IACvB,MAAM,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;QACtC,QAAQ,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACrB,GAAG,OAAO;YACV,aAAa,EAAE;gBACb,MAAM,EAAE,KAAK;gBACb,MAAM,EAAE,IAAI;gBACZ,IAAI,EAAE,IAAI;gBACV,MAAM,EAAE,IAAI;aACb;YACD,WAAW,EAAE,IAAI,IAAI,EAAE;SACxB,CAAC,CAAC,CAAC;IACN,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,kBAAkB;IAClB,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACzE,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IACtE,MAAM,cAAc,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;IAExE,OAAO;QACL,KAAK;QACL,cAAc;QACd,aAAa;QACb,cAAc;QACd,aAAa;QACb,cAAc;QACd,KAAK;QACL,SAAS;QACT,cAAc;KACf,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAC1C,OAA2B,EAC3B,UAA+B,EAAE;IAEjC,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;IAExC,uCAAuC;IACvC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,4DAA4D;YAC5D,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;YACjD,SAAS,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC;QACxC,CAAC;QACD,wDAAwD;QACxD,uDAAuD;IACzD,CAAC,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;IAErB,OAAO,SAAS,CAAC;AACnB,CAAC"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * useSpinner Hook
3
+ *
4
+ * Provides animated spinner functionality for visual feedback
5
+ * during loading states or background operations.
6
+ */
7
+ export type SpinnerStyle = "spinner" | "dots" | "braille";
8
+ export interface UseSpinnerOptions {
9
+ /** Whether the spinner is active */
10
+ isActive?: boolean;
11
+ /** Animation speed in milliseconds */
12
+ interval?: number;
13
+ /** Spinner style */
14
+ style?: SpinnerStyle;
15
+ }
16
+ export interface UseSpinnerResult {
17
+ /** Current spinner frame character */
18
+ frame: string;
19
+ /** Elapsed time in milliseconds since start */
20
+ elapsedMs: number;
21
+ /** Whether the spinner is currently running */
22
+ isRunning: boolean;
23
+ /** Start the spinner */
24
+ start: () => void;
25
+ /** Stop the spinner */
26
+ stop: () => void;
27
+ /** Reset elapsed time to 0 */
28
+ reset: () => void;
29
+ }
30
+ /**
31
+ * Hook for animated spinners
32
+ *
33
+ * @example
34
+ * ```tsx
35
+ * const spinner = useSpinner({ isActive: isLoading });
36
+ * return <Text color="cyan">{spinner.frame} Loading...</Text>;
37
+ * ```
38
+ */
39
+ export declare function useSpinner({ isActive, interval, style, }?: UseSpinnerOptions): UseSpinnerResult;
40
+ /**
41
+ * Simple hook that just returns a spinner frame based on elapsed time
42
+ * Useful when you already track elapsed time elsewhere
43
+ */
44
+ export declare function useSpinnerFrame(elapsedMs: number, style?: SpinnerStyle): string;
45
+ export default useSpinner;
46
+ //# sourceMappingURL=use-spinner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-spinner.d.ts","sourceRoot":"","sources":["../src/use-spinner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,MAAM,MAAM,YAAY,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1D,MAAM,WAAW,iBAAiB;IAChC,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sCAAsC;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB;IACpB,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,+CAA+C;IAC/C,SAAS,EAAE,OAAO,CAAC;IACnB,wBAAwB;IACxB,KAAK,EAAE,MAAM,IAAI,CAAC;IAClB,uBAAuB;IACvB,IAAI,EAAE,MAAM,IAAI,CAAC;IACjB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,EACzB,QAAe,EACf,QAAa,EACb,KAAiB,GAClB,GAAE,iBAAsB,GAAG,gBAAgB,CAsD3C;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,YAAwB,GAC9B,MAAM,CAER;AAED,eAAe,UAAU,CAAC"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * useSpinner Hook
3
+ *
4
+ * Provides animated spinner functionality for visual feedback
5
+ * during loading states or background operations.
6
+ */
7
+ import { useState, useEffect, useCallback } from "react";
8
+ import { getSpinnerFrame } from "./theme.js";
9
+ /**
10
+ * Hook for animated spinners
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * const spinner = useSpinner({ isActive: isLoading });
15
+ * return <Text color="cyan">{spinner.frame} Loading...</Text>;
16
+ * ```
17
+ */
18
+ export function useSpinner({ isActive = true, interval = 80, style = "spinner", } = {}) {
19
+ const [elapsedMs, setElapsedMs] = useState(0);
20
+ const [isRunning, setIsRunning] = useState(isActive);
21
+ // Sync with isActive prop
22
+ useEffect(() => {
23
+ setIsRunning(isActive);
24
+ if (isActive) {
25
+ setElapsedMs(0);
26
+ }
27
+ }, [isActive]);
28
+ // Animation loop
29
+ useEffect(() => {
30
+ if (!isRunning)
31
+ return;
32
+ const startTime = Date.now() - elapsedMs;
33
+ let animationFrame;
34
+ const tick = () => {
35
+ setElapsedMs(Date.now() - startTime);
36
+ animationFrame = setTimeout(tick, interval);
37
+ };
38
+ tick();
39
+ return () => {
40
+ clearTimeout(animationFrame);
41
+ };
42
+ }, [isRunning, interval]);
43
+ const start = useCallback(() => {
44
+ setIsRunning(true);
45
+ setElapsedMs(0);
46
+ }, []);
47
+ const stop = useCallback(() => {
48
+ setIsRunning(false);
49
+ }, []);
50
+ const reset = useCallback(() => {
51
+ setElapsedMs(0);
52
+ }, []);
53
+ const frame = getSpinnerFrame(elapsedMs, style);
54
+ return {
55
+ frame,
56
+ elapsedMs,
57
+ isRunning,
58
+ start,
59
+ stop,
60
+ reset,
61
+ };
62
+ }
63
+ /**
64
+ * Simple hook that just returns a spinner frame based on elapsed time
65
+ * Useful when you already track elapsed time elsewhere
66
+ */
67
+ export function useSpinnerFrame(elapsedMs, style = "spinner") {
68
+ return getSpinnerFrame(elapsedMs, style);
69
+ }
70
+ export default useSpinner;
71
+ //# sourceMappingURL=use-spinner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-spinner.js","sourceRoot":"","sources":["../src/use-spinner.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AA4B7C;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAC,EACzB,QAAQ,GAAG,IAAI,EACf,QAAQ,GAAG,EAAE,EACb,KAAK,GAAG,SAAS,MACI,EAAE;IACvB,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC9C,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAErD,0BAA0B;IAC1B,SAAS,CAAC,GAAG,EAAE;QACb,YAAY,CAAC,QAAQ,CAAC,CAAC;QACvB,IAAI,QAAQ,EAAE,CAAC;YACb,YAAY,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;IAEf,iBAAiB;IACjB,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,SAAS;YAAE,OAAO;QAEvB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC;QACzC,IAAI,cAA6C,CAAC;QAElD,MAAM,IAAI,GAAG,GAAG,EAAE;YAChB,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,CAAC;YACrC,cAAc,GAAG,UAAU,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAC9C,CAAC,CAAC;QAEF,IAAI,EAAE,CAAC;QAEP,OAAO,GAAG,EAAE;YACV,YAAY,CAAC,cAAc,CAAC,CAAC;QAC/B,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC;IAE1B,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,YAAY,CAAC,IAAI,CAAC,CAAC;QACnB,YAAY,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,IAAI,GAAG,WAAW,CAAC,GAAG,EAAE;QAC5B,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;QAC7B,YAAY,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,MAAM,KAAK,GAAG,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAEhD,OAAO;QACL,KAAK;QACL,SAAS;QACT,SAAS;QACT,KAAK;QACL,IAAI;QACJ,KAAK;KACN,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,SAAiB,EACjB,QAAsB,SAAS;IAE/B,OAAO,eAAe,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAC3C,CAAC;AAED,eAAe,UAAU,CAAC"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Stats Hook
3
+ *
4
+ * React hook for tracking game stats and achievements.
5
+ * Provides easy integration with game components.
6
+ */
7
+ import { type GameStats, type GlobalStats, type Achievement } from "./stats.js";
8
+ export interface UseStatsResult {
9
+ /** Stats for the specific game */
10
+ gameStats: GameStats;
11
+ /** Global stats across all games */
12
+ globalStats: GlobalStats;
13
+ /** Whether stats are still loading */
14
+ isLoading: boolean;
15
+ /** Record end of a game session */
16
+ recordSession: (score: number, duration: number, won?: boolean, customStats?: Record<string, number>) => Promise<string[]>;
17
+ /** Reload stats from disk */
18
+ refreshStats: () => Promise<void>;
19
+ }
20
+ /**
21
+ * Hook for tracking stats for a specific game
22
+ */
23
+ export declare function useStats(gameId: string): UseStatsResult;
24
+ export interface UseAchievementsResult {
25
+ /** All achievements with unlock status */
26
+ achievements: Array<Achievement & {
27
+ unlocked: boolean;
28
+ unlockedAt: string | null;
29
+ }>;
30
+ /** Total number of achievements */
31
+ total: number;
32
+ /** Number of unlocked achievements */
33
+ unlocked: number;
34
+ /** Whether achievements are still loading */
35
+ isLoading: boolean;
36
+ /** Get achievement details by ID */
37
+ getAchievement: (id: string) => Achievement | undefined;
38
+ /** Reload achievements from disk */
39
+ refreshAchievements: () => Promise<void>;
40
+ }
41
+ /**
42
+ * Hook for accessing achievements
43
+ */
44
+ export declare function useAchievements(): UseAchievementsResult;
45
+ export interface UseGameSessionResult {
46
+ /** Start tracking a game session */
47
+ startSession: () => void;
48
+ /** End the session and record stats */
49
+ endSession: (score: number, won?: boolean, customStats?: Record<string, number>) => Promise<string[]>;
50
+ /** Current session duration in seconds */
51
+ sessionDuration: number;
52
+ /** Whether a session is active */
53
+ isSessionActive: boolean;
54
+ }
55
+ /**
56
+ * Hook for tracking a single game session's duration
57
+ */
58
+ export declare function useGameSession(gameId: string): UseGameSessionResult;
59
+ //# sourceMappingURL=use-stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-stats.d.ts","sourceRoot":"","sources":["../src/use-stats.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,EACL,KAAK,SAAS,EACd,KAAK,WAAW,EAChB,KAAK,WAAW,EAOjB,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,SAAS,EAAE,SAAS,CAAC;IACrB,oCAAoC;IACpC,WAAW,EAAE,WAAW,CAAC;IACzB,sCAAsC;IACtC,SAAS,EAAE,OAAO,CAAC;IACnB,mCAAmC;IACnC,aAAa,EAAE,CACb,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,GAAG,CAAC,EAAE,OAAO,EACb,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACjC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvB,6BAA6B;IAC7B,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAqEvD;AAED,MAAM,WAAW,qBAAqB;IACpC,0CAA0C;IAC1C,YAAY,EAAE,KAAK,CAAC,WAAW,GAAG;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACpF,mCAAmC;IACnC,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,6CAA6C;IAC7C,SAAS,EAAE,OAAO,CAAC;IACnB,oCAAoC;IACpC,cAAc,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,WAAW,GAAG,SAAS,CAAC;IACxD,oCAAoC;IACpC,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,qBAAqB,CAmCvD;AAED,MAAM,WAAW,oBAAoB;IACnC,oCAAoC;IACpC,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,uCAAuC;IACvC,UAAU,EAAE,CACV,KAAK,EAAE,MAAM,EACb,GAAG,CAAC,EAAE,OAAO,EACb,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KACjC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACvB,0CAA0C;IAC1C,eAAe,EAAE,MAAM,CAAC;IACxB,kCAAkC;IAClC,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,oBAAoB,CA6DnE"}
@@ -0,0 +1,150 @@
1
+ /**
2
+ * Stats Hook
3
+ *
4
+ * React hook for tracking game stats and achievements.
5
+ * Provides easy integration with game components.
6
+ */
7
+ import { useState, useEffect, useCallback, useRef } from "react";
8
+ import { getGameStats, getGlobalStats, recordGameSession, getAchievements, getAchievementById, getAchievementCount, } from "./stats.js";
9
+ /**
10
+ * Hook for tracking stats for a specific game
11
+ */
12
+ export function useStats(gameId) {
13
+ const [gameStats, setGameStats] = useState({
14
+ gamesPlayed: 0,
15
+ timePlayed: 0,
16
+ highestScore: 0,
17
+ totalScore: 0,
18
+ wins: 0,
19
+ losses: 0,
20
+ lastPlayed: null,
21
+ });
22
+ const [globalStats, setGlobalStats] = useState({
23
+ totalGamesPlayed: 0,
24
+ totalTimePlayed: 0,
25
+ achievementsUnlocked: 0,
26
+ firstPlayed: null,
27
+ lastPlayed: null,
28
+ sessionCount: 0,
29
+ currentStreak: 0,
30
+ bestStreak: 0,
31
+ lastStreakDate: null,
32
+ });
33
+ const [isLoading, setIsLoading] = useState(true);
34
+ const loadStats = useCallback(async () => {
35
+ setIsLoading(true);
36
+ try {
37
+ const [game, global] = await Promise.all([
38
+ getGameStats(gameId),
39
+ getGlobalStats(),
40
+ ]);
41
+ setGameStats(game);
42
+ setGlobalStats(global);
43
+ }
44
+ finally {
45
+ setIsLoading(false);
46
+ }
47
+ }, [gameId]);
48
+ useEffect(() => {
49
+ void loadStats();
50
+ }, [loadStats]);
51
+ const recordSession = useCallback(async (score, duration, won, customStats) => {
52
+ const newAchievements = await recordGameSession(gameId, score, duration, won, customStats);
53
+ // Reload stats after recording
54
+ await loadStats();
55
+ return newAchievements;
56
+ }, [gameId, loadStats]);
57
+ return {
58
+ gameStats,
59
+ globalStats,
60
+ isLoading,
61
+ recordSession,
62
+ refreshStats: loadStats,
63
+ };
64
+ }
65
+ /**
66
+ * Hook for accessing achievements
67
+ */
68
+ export function useAchievements() {
69
+ const [achievements, setAchievements] = useState([]);
70
+ const [total, setTotal] = useState(0);
71
+ const [unlocked, setUnlocked] = useState(0);
72
+ const [isLoading, setIsLoading] = useState(true);
73
+ const loadAchievements = useCallback(async () => {
74
+ setIsLoading(true);
75
+ try {
76
+ const [achievementsList, counts] = await Promise.all([
77
+ getAchievements(),
78
+ getAchievementCount(),
79
+ ]);
80
+ setAchievements(achievementsList);
81
+ setTotal(counts.total);
82
+ setUnlocked(counts.unlocked);
83
+ }
84
+ finally {
85
+ setIsLoading(false);
86
+ }
87
+ }, []);
88
+ useEffect(() => {
89
+ void loadAchievements();
90
+ }, [loadAchievements]);
91
+ return {
92
+ achievements,
93
+ total,
94
+ unlocked,
95
+ isLoading,
96
+ getAchievement: getAchievementById,
97
+ refreshAchievements: loadAchievements,
98
+ };
99
+ }
100
+ /**
101
+ * Hook for tracking a single game session's duration
102
+ */
103
+ export function useGameSession(gameId) {
104
+ const [isActive, setIsActive] = useState(false);
105
+ const [duration, setDuration] = useState(0);
106
+ const startTimeRef = useRef(null);
107
+ const intervalRef = useRef(null);
108
+ const startSession = useCallback(() => {
109
+ startTimeRef.current = Date.now();
110
+ setIsActive(true);
111
+ setDuration(0);
112
+ // Update duration every second
113
+ intervalRef.current = setInterval(() => {
114
+ if (startTimeRef.current) {
115
+ setDuration(Math.floor((Date.now() - startTimeRef.current) / 1000));
116
+ }
117
+ }, 1000);
118
+ }, []);
119
+ const endSession = useCallback(async (score, won, customStats) => {
120
+ if (intervalRef.current) {
121
+ clearInterval(intervalRef.current);
122
+ intervalRef.current = null;
123
+ }
124
+ const finalDuration = startTimeRef.current
125
+ ? Math.floor((Date.now() - startTimeRef.current) / 1000)
126
+ : 0;
127
+ setIsActive(false);
128
+ startTimeRef.current = null;
129
+ // Record the session
130
+ if (finalDuration > 0) {
131
+ return recordGameSession(gameId, score, finalDuration, won, customStats);
132
+ }
133
+ return [];
134
+ }, [gameId]);
135
+ // Cleanup on unmount
136
+ useEffect(() => {
137
+ return () => {
138
+ if (intervalRef.current) {
139
+ clearInterval(intervalRef.current);
140
+ }
141
+ };
142
+ }, []);
143
+ return {
144
+ startSession,
145
+ endSession,
146
+ sessionDuration: duration,
147
+ isSessionActive: isActive,
148
+ };
149
+ }
150
+ //# sourceMappingURL=use-stats.js.map