viberag 0.3.2 → 0.4.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 (209) hide show
  1. package/README.md +2 -2
  2. package/dist/cli/app.d.ts +3 -0
  3. package/dist/cli/app.js +100 -102
  4. package/dist/cli/commands/handlers.d.ts +8 -6
  5. package/dist/cli/commands/handlers.js +90 -32
  6. package/dist/cli/commands/useCommands.d.ts +20 -0
  7. package/dist/cli/commands/useCommands.js +189 -0
  8. package/dist/cli/commands/useRagCommands.d.ts +2 -5
  9. package/dist/cli/commands/useRagCommands.js +11 -18
  10. package/dist/cli/components/InitWizard.js +66 -27
  11. package/dist/cli/components/McpSetupWizard.js +23 -4
  12. package/dist/cli/components/SlotRow.d.ts +22 -0
  13. package/dist/cli/components/SlotRow.js +55 -0
  14. package/dist/cli/components/StatusBar.d.ts +14 -0
  15. package/dist/cli/components/StatusBar.js +156 -0
  16. package/dist/cli/contexts/DaemonStatusContext.d.ts +38 -0
  17. package/dist/cli/contexts/DaemonStatusContext.js +106 -0
  18. package/dist/cli/hooks/useStatusPolling.d.ts +34 -0
  19. package/dist/cli/hooks/useStatusPolling.js +121 -0
  20. package/dist/cli/store/app/selectors.d.ts +87 -0
  21. package/dist/cli/store/app/selectors.js +28 -0
  22. package/dist/cli/store/app/slice.d.ts +1013 -0
  23. package/dist/cli/store/app/slice.js +112 -0
  24. package/dist/cli/store/hooks.d.ts +22 -0
  25. package/dist/cli/store/hooks.js +17 -0
  26. package/dist/cli/store/store.d.ts +17 -0
  27. package/dist/cli/store/store.js +18 -0
  28. package/dist/cli/store/wizard/selectors.d.ts +115 -0
  29. package/dist/cli/store/wizard/selectors.js +36 -0
  30. package/dist/cli/store/wizard/slice.d.ts +523 -0
  31. package/dist/cli/store/wizard/slice.js +119 -0
  32. package/dist/cli/utils/error-handler.d.ts +55 -0
  33. package/dist/cli/utils/error-handler.js +92 -0
  34. package/dist/client/auto-start.d.ts +42 -0
  35. package/dist/client/auto-start.js +250 -0
  36. package/dist/client/connection.d.ts +48 -0
  37. package/dist/client/connection.js +200 -0
  38. package/dist/client/index.d.ts +93 -0
  39. package/dist/client/index.js +209 -0
  40. package/dist/client/types.d.ts +105 -0
  41. package/dist/client/types.js +7 -0
  42. package/dist/common/components/SlotRow.d.ts +22 -0
  43. package/dist/common/components/SlotRow.js +53 -0
  44. package/dist/common/components/StatusBar.js +82 -31
  45. package/dist/common/types.d.ts +12 -13
  46. package/dist/daemon/handlers.d.ts +15 -0
  47. package/dist/daemon/handlers.js +157 -0
  48. package/dist/daemon/index.d.ts +21 -0
  49. package/dist/daemon/index.js +123 -0
  50. package/dist/daemon/lib/chunker/bounded-channel.d.ts +51 -0
  51. package/dist/daemon/lib/chunker/bounded-channel.js +138 -0
  52. package/dist/daemon/lib/chunker/index.d.ts +135 -0
  53. package/dist/daemon/lib/chunker/index.js +1370 -0
  54. package/dist/daemon/lib/chunker/types.d.ts +77 -0
  55. package/dist/daemon/lib/chunker/types.js +50 -0
  56. package/dist/daemon/lib/config.d.ts +73 -0
  57. package/dist/daemon/lib/config.js +149 -0
  58. package/dist/daemon/lib/constants.d.ts +75 -0
  59. package/dist/daemon/lib/constants.js +114 -0
  60. package/dist/daemon/lib/gitignore.d.ts +57 -0
  61. package/dist/daemon/lib/gitignore.js +246 -0
  62. package/dist/daemon/lib/logger.d.ts +51 -0
  63. package/dist/daemon/lib/logger.js +167 -0
  64. package/dist/daemon/lib/manifest.d.ts +58 -0
  65. package/dist/daemon/lib/manifest.js +116 -0
  66. package/dist/daemon/lib/merkle/diff.d.ts +32 -0
  67. package/dist/daemon/lib/merkle/diff.js +107 -0
  68. package/dist/daemon/lib/merkle/hash.d.ts +40 -0
  69. package/dist/daemon/lib/merkle/hash.js +180 -0
  70. package/dist/daemon/lib/merkle/index.d.ts +71 -0
  71. package/dist/daemon/lib/merkle/index.js +309 -0
  72. package/dist/daemon/lib/merkle/node.d.ts +55 -0
  73. package/dist/daemon/lib/merkle/node.js +82 -0
  74. package/dist/daemon/lifecycle.d.ts +50 -0
  75. package/dist/daemon/lifecycle.js +142 -0
  76. package/dist/daemon/owner.d.ts +175 -0
  77. package/dist/daemon/owner.js +609 -0
  78. package/dist/daemon/protocol.d.ts +100 -0
  79. package/dist/daemon/protocol.js +163 -0
  80. package/dist/daemon/providers/api-utils.d.ts +130 -0
  81. package/dist/daemon/providers/api-utils.js +248 -0
  82. package/dist/daemon/providers/gemini.d.ts +39 -0
  83. package/dist/daemon/providers/gemini.js +205 -0
  84. package/dist/daemon/providers/index.d.ts +14 -0
  85. package/dist/daemon/providers/index.js +14 -0
  86. package/dist/daemon/providers/local-4b.d.ts +28 -0
  87. package/dist/daemon/providers/local-4b.js +51 -0
  88. package/dist/daemon/providers/local.d.ts +36 -0
  89. package/dist/daemon/providers/local.js +166 -0
  90. package/dist/daemon/providers/mistral.d.ts +35 -0
  91. package/dist/daemon/providers/mistral.js +160 -0
  92. package/dist/daemon/providers/mock.d.ts +35 -0
  93. package/dist/daemon/providers/mock.js +69 -0
  94. package/dist/daemon/providers/openai.d.ts +41 -0
  95. package/dist/daemon/providers/openai.js +190 -0
  96. package/dist/daemon/providers/types.d.ts +68 -0
  97. package/dist/daemon/providers/types.js +6 -0
  98. package/dist/daemon/providers/validate.d.ts +30 -0
  99. package/dist/daemon/providers/validate.js +162 -0
  100. package/dist/daemon/server.d.ts +79 -0
  101. package/dist/daemon/server.js +293 -0
  102. package/dist/daemon/services/index.d.ts +11 -0
  103. package/dist/daemon/services/index.js +16 -0
  104. package/dist/daemon/services/indexing.d.ts +117 -0
  105. package/dist/daemon/services/indexing.js +573 -0
  106. package/dist/daemon/services/search/filters.d.ts +21 -0
  107. package/dist/daemon/services/search/filters.js +106 -0
  108. package/dist/daemon/services/search/fts.d.ts +32 -0
  109. package/dist/daemon/services/search/fts.js +61 -0
  110. package/dist/daemon/services/search/hybrid.d.ts +17 -0
  111. package/dist/daemon/services/search/hybrid.js +58 -0
  112. package/dist/daemon/services/search/index.d.ts +108 -0
  113. package/dist/daemon/services/search/index.js +417 -0
  114. package/dist/daemon/services/search/types.d.ts +126 -0
  115. package/dist/daemon/services/search/types.js +4 -0
  116. package/dist/daemon/services/search/vector.d.ts +25 -0
  117. package/dist/daemon/services/search/vector.js +44 -0
  118. package/dist/daemon/services/storage/index.d.ts +110 -0
  119. package/dist/daemon/services/storage/index.js +378 -0
  120. package/dist/daemon/services/storage/schema.d.ts +24 -0
  121. package/dist/daemon/services/storage/schema.js +51 -0
  122. package/dist/daemon/services/storage/types.d.ts +105 -0
  123. package/dist/daemon/services/storage/types.js +71 -0
  124. package/dist/daemon/services/types.d.ts +192 -0
  125. package/dist/daemon/services/types.js +53 -0
  126. package/dist/daemon/services/watcher.d.ts +98 -0
  127. package/dist/daemon/services/watcher.js +386 -0
  128. package/dist/daemon/state.d.ts +119 -0
  129. package/dist/daemon/state.js +161 -0
  130. package/dist/mcp/index.d.ts +1 -1
  131. package/dist/mcp/index.js +44 -60
  132. package/dist/mcp/server.d.ts +10 -14
  133. package/dist/mcp/server.js +75 -74
  134. package/dist/mcp/services/lazy-loader.d.ts +23 -0
  135. package/dist/mcp/services/lazy-loader.js +34 -0
  136. package/dist/mcp/warmup.d.ts +3 -3
  137. package/dist/mcp/warmup.js +39 -40
  138. package/dist/mcp/watcher.d.ts +5 -7
  139. package/dist/mcp/watcher.js +73 -64
  140. package/dist/rag/config/index.d.ts +2 -0
  141. package/dist/rag/constants.d.ts +30 -0
  142. package/dist/rag/constants.js +38 -0
  143. package/dist/rag/embeddings/api-utils.d.ts +121 -0
  144. package/dist/rag/embeddings/api-utils.js +259 -0
  145. package/dist/rag/embeddings/gemini.d.ts +4 -12
  146. package/dist/rag/embeddings/gemini.js +22 -72
  147. package/dist/rag/embeddings/index.d.ts +5 -3
  148. package/dist/rag/embeddings/index.js +5 -2
  149. package/dist/rag/embeddings/local-4b.d.ts +2 -2
  150. package/dist/rag/embeddings/local-4b.js +1 -1
  151. package/dist/rag/embeddings/local.d.ts +10 -3
  152. package/dist/rag/embeddings/local.js +58 -12
  153. package/dist/rag/embeddings/mistral.d.ts +4 -12
  154. package/dist/rag/embeddings/mistral.js +22 -72
  155. package/dist/rag/embeddings/mock.d.ts +35 -0
  156. package/dist/rag/embeddings/mock.js +69 -0
  157. package/dist/rag/embeddings/openai.d.ts +11 -13
  158. package/dist/rag/embeddings/openai.js +47 -75
  159. package/dist/rag/embeddings/types.d.ts +27 -1
  160. package/dist/rag/embeddings/validate.d.ts +9 -1
  161. package/dist/rag/embeddings/validate.js +17 -4
  162. package/dist/rag/index.d.ts +2 -2
  163. package/dist/rag/index.js +1 -1
  164. package/dist/rag/indexer/bounded-channel.d.ts +51 -0
  165. package/dist/rag/indexer/bounded-channel.js +138 -0
  166. package/dist/rag/indexer/indexer.d.ts +4 -14
  167. package/dist/rag/indexer/indexer.js +246 -169
  168. package/dist/rag/indexer/types.d.ts +1 -0
  169. package/dist/rag/logger/index.d.ts +22 -0
  170. package/dist/rag/logger/index.js +78 -1
  171. package/dist/rag/manifest/index.js +1 -2
  172. package/dist/rag/search/index.js +1 -1
  173. package/dist/rag/storage/schema.d.ts +2 -4
  174. package/dist/rag/storage/schema.js +3 -5
  175. package/dist/store/app/selectors.d.ts +87 -0
  176. package/dist/store/app/selectors.js +28 -0
  177. package/dist/store/app/slice.d.ts +1013 -0
  178. package/dist/store/app/slice.js +112 -0
  179. package/dist/store/hooks.d.ts +22 -0
  180. package/dist/store/hooks.js +17 -0
  181. package/dist/store/index.d.ts +12 -0
  182. package/dist/store/index.js +18 -0
  183. package/dist/store/indexing/listeners.d.ts +25 -0
  184. package/dist/store/indexing/listeners.js +46 -0
  185. package/dist/store/indexing/selectors.d.ts +195 -0
  186. package/dist/store/indexing/selectors.js +69 -0
  187. package/dist/store/indexing/slice.d.ts +309 -0
  188. package/dist/store/indexing/slice.js +113 -0
  189. package/dist/store/slot-progress/listeners.d.ts +23 -0
  190. package/dist/store/slot-progress/listeners.js +33 -0
  191. package/dist/store/slot-progress/selectors.d.ts +67 -0
  192. package/dist/store/slot-progress/selectors.js +36 -0
  193. package/dist/store/slot-progress/slice.d.ts +246 -0
  194. package/dist/store/slot-progress/slice.js +70 -0
  195. package/dist/store/store.d.ts +17 -0
  196. package/dist/store/store.js +18 -0
  197. package/dist/store/warmup/selectors.d.ts +109 -0
  198. package/dist/store/warmup/selectors.js +44 -0
  199. package/dist/store/warmup/slice.d.ts +137 -0
  200. package/dist/store/warmup/slice.js +72 -0
  201. package/dist/store/watcher/selectors.d.ts +115 -0
  202. package/dist/store/watcher/selectors.js +52 -0
  203. package/dist/store/watcher/slice.d.ts +269 -0
  204. package/dist/store/watcher/slice.js +100 -0
  205. package/dist/store/wizard/selectors.d.ts +115 -0
  206. package/dist/store/wizard/selectors.js +36 -0
  207. package/dist/store/wizard/slice.d.ts +523 -0
  208. package/dist/store/wizard/slice.js +119 -0
  209. package/package.json +10 -2
@@ -0,0 +1,156 @@
1
+ /**
2
+ * StatusBar Component
3
+ *
4
+ * Displays current status and indexing progress.
5
+ * Uses DaemonStatusContext for daemon state instead of Redux.
6
+ */
7
+ import React, { useState, useEffect } from 'react';
8
+ import { Box, Text } from 'ink';
9
+ import { useDaemonStatus } from '../contexts/DaemonStatusContext.js';
10
+ /** Braille dots spinner frames */
11
+ const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
12
+ /**
13
+ * Simple spinner component using interval-based animation.
14
+ */
15
+ function Spinner({ color }) {
16
+ const [frame, setFrame] = useState(0);
17
+ useEffect(() => {
18
+ const timer = setInterval(() => {
19
+ setFrame(f => (f + 1) % SPINNER_FRAMES.length);
20
+ }, 80);
21
+ return () => clearInterval(timer);
22
+ }, []);
23
+ return React.createElement(Text, { color: color },
24
+ SPINNER_FRAMES[frame],
25
+ " ");
26
+ }
27
+ /**
28
+ * Progress bar component for visual progress indication.
29
+ */
30
+ function ProgressBar({ percent, width = 20, }) {
31
+ const filled = Math.round((percent / 100) * width);
32
+ const empty = width - filled;
33
+ return (React.createElement(Text, null,
34
+ React.createElement(Text, { color: "cyan" }, '█'.repeat(filled)),
35
+ React.createElement(Text, { dimColor: true }, '░'.repeat(empty))));
36
+ }
37
+ /**
38
+ * Format status message for display.
39
+ * Note: Indexing state is derived from daemon status.
40
+ * This function only handles non-indexing app states.
41
+ */
42
+ function formatNonIndexingStatus(status) {
43
+ switch (status.state) {
44
+ case 'ready':
45
+ return { text: 'Ready', color: 'green', showSpinner: false };
46
+ case 'searching':
47
+ return { text: 'Searching', color: 'cyan', showSpinner: true };
48
+ case 'warning':
49
+ return { text: status.message, color: 'yellow', showSpinner: false };
50
+ }
51
+ }
52
+ /**
53
+ * Format stats for display.
54
+ */
55
+ function formatStats(stats) {
56
+ if (stats === undefined) {
57
+ return 'Loading...';
58
+ }
59
+ if (stats === null) {
60
+ return 'Not indexed';
61
+ }
62
+ return `${stats.totalFiles} files · ${stats.totalChunks} chunks`;
63
+ }
64
+ /**
65
+ * Derive display values from daemon indexing state.
66
+ */
67
+ function deriveIndexingDisplay(indexing) {
68
+ const isActive = indexing.status === 'initializing' || indexing.status === 'indexing';
69
+ const showProgressBar = isActive && indexing.total > 0;
70
+ const chunkInfo = indexing.chunksProcessed > 0
71
+ ? `${indexing.chunksProcessed} chunks`
72
+ : undefined;
73
+ const color = indexing.throttleMessage !== null ? 'yellow' : 'cyan';
74
+ return {
75
+ isActive,
76
+ showProgressBar,
77
+ percent: indexing.percent,
78
+ stage: indexing.stage,
79
+ chunkInfo,
80
+ throttleInfo: indexing.throttleMessage,
81
+ color,
82
+ };
83
+ }
84
+ export default function StatusBar({ status, stats }) {
85
+ const statsText = formatStats(stats);
86
+ // Get daemon status from context
87
+ const daemonStatus = useDaemonStatus();
88
+ // Derive indexing display from daemon status
89
+ const indexingDisplay = daemonStatus
90
+ ? deriveIndexingDisplay(daemonStatus.indexing)
91
+ : {
92
+ isActive: false,
93
+ showProgressBar: false,
94
+ percent: 0,
95
+ stage: '',
96
+ chunkInfo: undefined,
97
+ throttleInfo: null,
98
+ color: 'cyan',
99
+ };
100
+ const isIndexingActive = indexingDisplay.isActive;
101
+ // Get failures from daemon status
102
+ const failures = daemonStatus?.failures ?? [];
103
+ // Determine display values based on state source
104
+ const nonIndexingStatus = formatNonIndexingStatus(status);
105
+ // Use daemon status for indexing display, props for everything else
106
+ const displayValues = isIndexingActive
107
+ ? {
108
+ text: indexingDisplay.stage,
109
+ color: indexingDisplay.color,
110
+ showSpinner: true,
111
+ showProgressBar: indexingDisplay.showProgressBar,
112
+ percent: indexingDisplay.percent,
113
+ stage: indexingDisplay.stage,
114
+ chunkInfo: indexingDisplay.chunkInfo,
115
+ throttleInfo: indexingDisplay.throttleInfo,
116
+ }
117
+ : {
118
+ text: nonIndexingStatus.text,
119
+ color: nonIndexingStatus.color,
120
+ showSpinner: nonIndexingStatus.showSpinner,
121
+ showProgressBar: false,
122
+ percent: 0,
123
+ stage: '',
124
+ chunkInfo: undefined,
125
+ throttleInfo: null,
126
+ };
127
+ const { text, color, showSpinner, showProgressBar, percent, stage, chunkInfo, throttleInfo, } = displayValues;
128
+ // Show failure summary if any batches failed
129
+ const hasFailures = failures.length > 0;
130
+ return (React.createElement(Box, { flexDirection: "column" },
131
+ React.createElement(Box, { paddingX: 1, justifyContent: "space-between" },
132
+ React.createElement(Box, null,
133
+ showSpinner && React.createElement(Spinner, { color: color }),
134
+ showProgressBar ? (React.createElement(React.Fragment, null,
135
+ React.createElement(Text, { color: color },
136
+ stage,
137
+ " "),
138
+ React.createElement(Text, null, "["),
139
+ React.createElement(ProgressBar, { percent: percent }),
140
+ React.createElement(Text, null, "] "),
141
+ React.createElement(Text, { color: color },
142
+ percent,
143
+ "%"),
144
+ chunkInfo && React.createElement(Text, { dimColor: true },
145
+ " \u00B7 ",
146
+ chunkInfo),
147
+ throttleInfo && React.createElement(Text, { color: "yellow" },
148
+ " \u00B7 ",
149
+ throttleInfo))) : (React.createElement(Text, { color: color }, text))),
150
+ React.createElement(Text, { dimColor: true }, statsText)),
151
+ hasFailures && (React.createElement(Box, { paddingLeft: 2 },
152
+ React.createElement(Text, { color: "red" },
153
+ "\u26A0 ",
154
+ failures.length,
155
+ " batch(es) failed - see .viberag/debug.log")))));
156
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Daemon Status Context
3
+ *
4
+ * Provides daemon status to CLI components via React Context.
5
+ * Replaces Redux sync for daemon state - components read directly
6
+ * from the polled status instead of going through Redux.
7
+ */
8
+ import React, { type ReactNode } from 'react';
9
+ import type { DaemonStatusResponse } from '../../client/types.js';
10
+ interface DaemonStatusProviderProps {
11
+ children: ReactNode;
12
+ projectRoot: string;
13
+ enabled?: boolean;
14
+ intervalMs?: number;
15
+ }
16
+ /**
17
+ * Provides daemon status to child components.
18
+ *
19
+ * Polls daemon.status() and makes the result available via context.
20
+ * Components use useDaemonStatus() to access the current status.
21
+ */
22
+ export declare function DaemonStatusProvider({ children, projectRoot, enabled, intervalMs, }: DaemonStatusProviderProps): React.ReactElement;
23
+ /**
24
+ * Access the current daemon status.
25
+ *
26
+ * Returns null if status hasn't been fetched yet or daemon is not running.
27
+ *
28
+ * Usage:
29
+ * ```tsx
30
+ * function MyComponent() {
31
+ * const status = useDaemonStatus();
32
+ * if (!status) return <Text>Loading...</Text>;
33
+ * return <Text>Indexing: {status.indexing.status}</Text>;
34
+ * }
35
+ * ```
36
+ */
37
+ export declare function useDaemonStatus(): DaemonStatusResponse | null;
38
+ export {};
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Daemon Status Context
3
+ *
4
+ * Provides daemon status to CLI components via React Context.
5
+ * Replaces Redux sync for daemon state - components read directly
6
+ * from the polled status instead of going through Redux.
7
+ */
8
+ import React, { createContext, useContext, useState, useEffect, useRef, } from 'react';
9
+ import { DaemonClient } from '../../client/index.js';
10
+ import { createCliLogger, handleCliErrorIfUnexpected, isExpectedError, } from '../utils/error-handler.js';
11
+ // ============================================================================
12
+ // Context
13
+ // ============================================================================
14
+ const DaemonStatusContext = createContext(null);
15
+ /**
16
+ * Provides daemon status to child components.
17
+ *
18
+ * Polls daemon.status() and makes the result available via context.
19
+ * Components use useDaemonStatus() to access the current status.
20
+ */
21
+ export function DaemonStatusProvider({ children, projectRoot, enabled = true, intervalMs = 500, }) {
22
+ const [status, setStatus] = useState(null);
23
+ const clientRef = useRef(null);
24
+ const pollingRef = useRef(false);
25
+ const loggerRef = useRef(null);
26
+ useEffect(() => {
27
+ let mounted = true;
28
+ let interval = null;
29
+ if (!enabled) {
30
+ return () => {
31
+ mounted = false;
32
+ };
33
+ }
34
+ loggerRef.current = createCliLogger(projectRoot);
35
+ const client = new DaemonClient({
36
+ projectRoot,
37
+ autoStart: false,
38
+ });
39
+ clientRef.current = client;
40
+ const poll = async () => {
41
+ if (!mounted)
42
+ return;
43
+ if (pollingRef.current)
44
+ return;
45
+ pollingRef.current = true;
46
+ try {
47
+ if (!(await client.isRunning())) {
48
+ return;
49
+ }
50
+ await client.connect();
51
+ try {
52
+ const daemonStatus = await client.status();
53
+ if (mounted) {
54
+ setStatus(daemonStatus);
55
+ }
56
+ }
57
+ finally {
58
+ await client.disconnect();
59
+ }
60
+ }
61
+ catch (error) {
62
+ handleCliErrorIfUnexpected('DaemonStatusProvider', error, loggerRef.current);
63
+ }
64
+ finally {
65
+ pollingRef.current = false;
66
+ }
67
+ };
68
+ poll();
69
+ interval = setInterval(poll, intervalMs);
70
+ return () => {
71
+ mounted = false;
72
+ pollingRef.current = false;
73
+ if (interval) {
74
+ clearInterval(interval);
75
+ }
76
+ clientRef.current?.disconnect().catch(err => {
77
+ if (!isExpectedError(err)) {
78
+ handleCliErrorIfUnexpected('DaemonStatusProvider.cleanup', err, loggerRef.current);
79
+ }
80
+ });
81
+ clientRef.current = null;
82
+ loggerRef.current = null;
83
+ };
84
+ }, [projectRoot, enabled, intervalMs]);
85
+ return (React.createElement(DaemonStatusContext.Provider, { value: status }, children));
86
+ }
87
+ // ============================================================================
88
+ // Hook
89
+ // ============================================================================
90
+ /**
91
+ * Access the current daemon status.
92
+ *
93
+ * Returns null if status hasn't been fetched yet or daemon is not running.
94
+ *
95
+ * Usage:
96
+ * ```tsx
97
+ * function MyComponent() {
98
+ * const status = useDaemonStatus();
99
+ * if (!status) return <Text>Loading...</Text>;
100
+ * return <Text>Indexing: {status.indexing.status}</Text>;
101
+ * }
102
+ * ```
103
+ */
104
+ export function useDaemonStatus() {
105
+ return useContext(DaemonStatusContext);
106
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Status Polling Hook
3
+ *
4
+ * Polls daemon status endpoint and syncs state to Redux.
5
+ * This is the primary mechanism for state synchronization between
6
+ * the daemon process and the CLI React application.
7
+ */
8
+ interface UseStatusPollingOptions {
9
+ /** Project root directory */
10
+ projectRoot: string;
11
+ /** Enable/disable polling (default: true) */
12
+ enabled?: boolean;
13
+ /** Polling interval in ms (default: 500) */
14
+ intervalMs?: number;
15
+ }
16
+ /**
17
+ * Polls daemon status and syncs to Redux store.
18
+ *
19
+ * Features:
20
+ * - Deduplicates overlapping polls (skips if previous poll still in-flight)
21
+ * - Logs unexpected errors (silent on expected "daemon not running")
22
+ * - Properly cleans up interval and connections on unmount or disable
23
+ *
24
+ * Usage:
25
+ * ```tsx
26
+ * useStatusPolling({
27
+ * projectRoot: '/path/to/project',
28
+ * enabled: isInitialized,
29
+ * intervalMs: 500,
30
+ * });
31
+ * ```
32
+ */
33
+ export declare function useStatusPolling({ projectRoot, enabled, intervalMs, }: UseStatusPollingOptions): void;
34
+ export {};
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Status Polling Hook
3
+ *
4
+ * Polls daemon status endpoint and syncs state to Redux.
5
+ * This is the primary mechanism for state synchronization between
6
+ * the daemon process and the CLI React application.
7
+ */
8
+ import { useEffect, useRef } from 'react';
9
+ import { useAppDispatch } from '../../store/hooks.js';
10
+ import { IndexingActions, SlotProgressActions } from '../../store/index.js';
11
+ import { DaemonClient } from '../../client/index.js';
12
+ import { createCliLogger, handleCliErrorIfUnexpected, isExpectedError, } from '../utils/error-handler.js';
13
+ /**
14
+ * Polls daemon status and syncs to Redux store.
15
+ *
16
+ * Features:
17
+ * - Deduplicates overlapping polls (skips if previous poll still in-flight)
18
+ * - Logs unexpected errors (silent on expected "daemon not running")
19
+ * - Properly cleans up interval and connections on unmount or disable
20
+ *
21
+ * Usage:
22
+ * ```tsx
23
+ * useStatusPolling({
24
+ * projectRoot: '/path/to/project',
25
+ * enabled: isInitialized,
26
+ * intervalMs: 500,
27
+ * });
28
+ * ```
29
+ */
30
+ export function useStatusPolling({ projectRoot, enabled = true, intervalMs = 500, }) {
31
+ const dispatch = useAppDispatch();
32
+ const clientRef = useRef(null);
33
+ const pollingRef = useRef(false);
34
+ const loggerRef = useRef(null);
35
+ useEffect(() => {
36
+ // Always set up cleanup, even when disabled
37
+ let mounted = true;
38
+ let interval = null;
39
+ // Early exit if disabled, but still return cleanup function
40
+ if (!enabled) {
41
+ return () => {
42
+ mounted = false;
43
+ };
44
+ }
45
+ // Create logger for CLI error tracking
46
+ loggerRef.current = createCliLogger(projectRoot);
47
+ // Create client for polling (don't auto-start for status checks)
48
+ const client = new DaemonClient({
49
+ projectRoot,
50
+ autoStart: false,
51
+ });
52
+ clientRef.current = client;
53
+ const poll = async () => {
54
+ // Skip if unmounted
55
+ if (!mounted)
56
+ return;
57
+ // Deduplicate: skip if previous poll still in-flight
58
+ if (pollingRef.current) {
59
+ return;
60
+ }
61
+ pollingRef.current = true;
62
+ try {
63
+ // Check if daemon is running first
64
+ if (!(await client.isRunning())) {
65
+ return; // Daemon not running, skip poll
66
+ }
67
+ await client.connect();
68
+ try {
69
+ const status = await client.status();
70
+ if (!mounted)
71
+ return;
72
+ // Sync indexing state to Redux
73
+ dispatch(IndexingActions.sync({
74
+ status: status.indexing.status,
75
+ current: status.indexing.current,
76
+ total: status.indexing.total,
77
+ stage: status.indexing.stage,
78
+ chunksProcessed: status.indexing.chunksProcessed,
79
+ throttleMessage: status.indexing.throttleMessage,
80
+ error: status.indexing.error,
81
+ lastCompleted: status.indexing.lastCompleted,
82
+ }));
83
+ // Sync slot progress to Redux
84
+ dispatch(SlotProgressActions.sync({
85
+ slots: status.slots,
86
+ failures: status.failures,
87
+ }));
88
+ }
89
+ finally {
90
+ await client.disconnect();
91
+ }
92
+ }
93
+ catch (error) {
94
+ // Log unexpected errors to console and CLI log file
95
+ handleCliErrorIfUnexpected('StatusPolling', error, loggerRef.current);
96
+ }
97
+ finally {
98
+ pollingRef.current = false;
99
+ }
100
+ };
101
+ // Initial poll
102
+ poll();
103
+ // Set up interval
104
+ interval = setInterval(poll, intervalMs);
105
+ return () => {
106
+ mounted = false;
107
+ pollingRef.current = false;
108
+ if (interval) {
109
+ clearInterval(interval);
110
+ }
111
+ clientRef.current?.disconnect().catch(err => {
112
+ // Log unexpected disconnect errors
113
+ if (!isExpectedError(err)) {
114
+ handleCliErrorIfUnexpected('StatusPolling.cleanup', err, loggerRef.current);
115
+ }
116
+ });
117
+ clientRef.current = null;
118
+ loggerRef.current = null;
119
+ };
120
+ }, [projectRoot, enabled, intervalMs, dispatch]);
121
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Memoized selectors for app state.
3
+ */
4
+ import type { AppState } from './slice.js';
5
+ import type { OutputItem, IndexDisplayStats, AppStatus } from '../../../common/types.js';
6
+ type RootState = {
7
+ app: AppState;
8
+ };
9
+ export declare const selectAppState: (state: RootState) => AppState;
10
+ export declare const selectIsInitialized: (state: RootState) => boolean | undefined;
11
+ export declare const selectIndexStats: (state: RootState) => IndexDisplayStats | null | undefined;
12
+ export declare const selectAppStatus: (state: RootState) => AppStatus;
13
+ export declare const selectOutputItems: (state: RootState) => OutputItem[];
14
+ /**
15
+ * Check if app startup is complete (both init status and stats loaded).
16
+ */
17
+ export declare const selectStartupLoaded: ((state: RootState) => boolean) & {
18
+ clearCache: () => void;
19
+ resultsCount: () => number;
20
+ resetResultsCount: () => void;
21
+ } & {
22
+ resultFunc: (resultFuncArgs_0: boolean | undefined, resultFuncArgs_1: IndexDisplayStats | null | undefined) => boolean;
23
+ memoizedResultFunc: ((resultFuncArgs_0: boolean | undefined, resultFuncArgs_1: IndexDisplayStats | null | undefined) => boolean) & {
24
+ clearCache: () => void;
25
+ resultsCount: () => number;
26
+ resetResultsCount: () => void;
27
+ };
28
+ lastResult: () => boolean;
29
+ dependencies: [(state: RootState) => boolean | undefined, (state: RootState) => IndexDisplayStats | null | undefined];
30
+ recomputations: () => number;
31
+ resetRecomputations: () => void;
32
+ dependencyRecomputations: () => number;
33
+ resetDependencyRecomputations: () => void;
34
+ } & {
35
+ memoize: typeof import("reselect").weakMapMemoize;
36
+ argsMemoize: typeof import("reselect").weakMapMemoize;
37
+ };
38
+ /**
39
+ * Check if the app is in a busy state (searching).
40
+ * Note: For indexing state, use selectIsIndexing from indexing selectors.
41
+ */
42
+ export declare const selectIsBusy: ((state: RootState) => boolean) & {
43
+ clearCache: () => void;
44
+ resultsCount: () => number;
45
+ resetResultsCount: () => void;
46
+ } & {
47
+ resultFunc: (resultFuncArgs_0: AppStatus) => boolean;
48
+ memoizedResultFunc: ((resultFuncArgs_0: AppStatus) => boolean) & {
49
+ clearCache: () => void;
50
+ resultsCount: () => number;
51
+ resetResultsCount: () => void;
52
+ };
53
+ lastResult: () => boolean;
54
+ dependencies: [(state: RootState) => AppStatus];
55
+ recomputations: () => number;
56
+ resetRecomputations: () => void;
57
+ dependencyRecomputations: () => number;
58
+ resetDependencyRecomputations: () => void;
59
+ } & {
60
+ memoize: typeof import("reselect").weakMapMemoize;
61
+ argsMemoize: typeof import("reselect").weakMapMemoize;
62
+ };
63
+ /**
64
+ * Get output item count.
65
+ */
66
+ export declare const selectOutputCount: ((state: RootState) => number) & {
67
+ clearCache: () => void;
68
+ resultsCount: () => number;
69
+ resetResultsCount: () => void;
70
+ } & {
71
+ resultFunc: (resultFuncArgs_0: OutputItem[]) => number;
72
+ memoizedResultFunc: ((resultFuncArgs_0: OutputItem[]) => number) & {
73
+ clearCache: () => void;
74
+ resultsCount: () => number;
75
+ resetResultsCount: () => void;
76
+ };
77
+ lastResult: () => number;
78
+ dependencies: [(state: RootState) => OutputItem[]];
79
+ recomputations: () => number;
80
+ resetRecomputations: () => void;
81
+ dependencyRecomputations: () => number;
82
+ resetDependencyRecomputations: () => void;
83
+ } & {
84
+ memoize: typeof import("reselect").weakMapMemoize;
85
+ argsMemoize: typeof import("reselect").weakMapMemoize;
86
+ };
87
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Memoized selectors for app state.
3
+ */
4
+ import { createSelector } from '@reduxjs/toolkit';
5
+ // ============================================================================
6
+ // Basic Selectors
7
+ // ============================================================================
8
+ export const selectAppState = (state) => state.app;
9
+ export const selectIsInitialized = (state) => state.app.isInitialized;
10
+ export const selectIndexStats = (state) => state.app.indexStats;
11
+ export const selectAppStatus = (state) => state.app.appStatus;
12
+ export const selectOutputItems = (state) => state.app.outputItems;
13
+ // ============================================================================
14
+ // Memoized Selectors
15
+ // ============================================================================
16
+ /**
17
+ * Check if app startup is complete (both init status and stats loaded).
18
+ */
19
+ export const selectStartupLoaded = createSelector([selectIsInitialized, selectIndexStats], (isInitialized, indexStats) => isInitialized !== undefined && indexStats !== undefined);
20
+ /**
21
+ * Check if the app is in a busy state (searching).
22
+ * Note: For indexing state, use selectIsIndexing from indexing selectors.
23
+ */
24
+ export const selectIsBusy = createSelector([selectAppStatus], (status) => status.state === 'searching');
25
+ /**
26
+ * Get output item count.
27
+ */
28
+ export const selectOutputCount = createSelector([selectOutputItems], (items) => items.length);