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.
- package/README.md +2 -2
- package/dist/cli/app.d.ts +3 -0
- package/dist/cli/app.js +100 -102
- package/dist/cli/commands/handlers.d.ts +8 -6
- package/dist/cli/commands/handlers.js +90 -32
- package/dist/cli/commands/useCommands.d.ts +20 -0
- package/dist/cli/commands/useCommands.js +189 -0
- package/dist/cli/commands/useRagCommands.d.ts +2 -5
- package/dist/cli/commands/useRagCommands.js +11 -18
- package/dist/cli/components/InitWizard.js +66 -27
- package/dist/cli/components/McpSetupWizard.js +23 -4
- package/dist/cli/components/SlotRow.d.ts +22 -0
- package/dist/cli/components/SlotRow.js +55 -0
- package/dist/cli/components/StatusBar.d.ts +14 -0
- package/dist/cli/components/StatusBar.js +156 -0
- package/dist/cli/contexts/DaemonStatusContext.d.ts +38 -0
- package/dist/cli/contexts/DaemonStatusContext.js +106 -0
- package/dist/cli/hooks/useStatusPolling.d.ts +34 -0
- package/dist/cli/hooks/useStatusPolling.js +121 -0
- package/dist/cli/store/app/selectors.d.ts +87 -0
- package/dist/cli/store/app/selectors.js +28 -0
- package/dist/cli/store/app/slice.d.ts +1013 -0
- package/dist/cli/store/app/slice.js +112 -0
- package/dist/cli/store/hooks.d.ts +22 -0
- package/dist/cli/store/hooks.js +17 -0
- package/dist/cli/store/store.d.ts +17 -0
- package/dist/cli/store/store.js +18 -0
- package/dist/cli/store/wizard/selectors.d.ts +115 -0
- package/dist/cli/store/wizard/selectors.js +36 -0
- package/dist/cli/store/wizard/slice.d.ts +523 -0
- package/dist/cli/store/wizard/slice.js +119 -0
- package/dist/cli/utils/error-handler.d.ts +55 -0
- package/dist/cli/utils/error-handler.js +92 -0
- package/dist/client/auto-start.d.ts +42 -0
- package/dist/client/auto-start.js +250 -0
- package/dist/client/connection.d.ts +48 -0
- package/dist/client/connection.js +200 -0
- package/dist/client/index.d.ts +93 -0
- package/dist/client/index.js +209 -0
- package/dist/client/types.d.ts +105 -0
- package/dist/client/types.js +7 -0
- package/dist/common/components/SlotRow.d.ts +22 -0
- package/dist/common/components/SlotRow.js +53 -0
- package/dist/common/components/StatusBar.js +82 -31
- package/dist/common/types.d.ts +12 -13
- package/dist/daemon/handlers.d.ts +15 -0
- package/dist/daemon/handlers.js +157 -0
- package/dist/daemon/index.d.ts +21 -0
- package/dist/daemon/index.js +123 -0
- package/dist/daemon/lib/chunker/bounded-channel.d.ts +51 -0
- package/dist/daemon/lib/chunker/bounded-channel.js +138 -0
- package/dist/daemon/lib/chunker/index.d.ts +135 -0
- package/dist/daemon/lib/chunker/index.js +1370 -0
- package/dist/daemon/lib/chunker/types.d.ts +77 -0
- package/dist/daemon/lib/chunker/types.js +50 -0
- package/dist/daemon/lib/config.d.ts +73 -0
- package/dist/daemon/lib/config.js +149 -0
- package/dist/daemon/lib/constants.d.ts +75 -0
- package/dist/daemon/lib/constants.js +114 -0
- package/dist/daemon/lib/gitignore.d.ts +57 -0
- package/dist/daemon/lib/gitignore.js +246 -0
- package/dist/daemon/lib/logger.d.ts +51 -0
- package/dist/daemon/lib/logger.js +167 -0
- package/dist/daemon/lib/manifest.d.ts +58 -0
- package/dist/daemon/lib/manifest.js +116 -0
- package/dist/daemon/lib/merkle/diff.d.ts +32 -0
- package/dist/daemon/lib/merkle/diff.js +107 -0
- package/dist/daemon/lib/merkle/hash.d.ts +40 -0
- package/dist/daemon/lib/merkle/hash.js +180 -0
- package/dist/daemon/lib/merkle/index.d.ts +71 -0
- package/dist/daemon/lib/merkle/index.js +309 -0
- package/dist/daemon/lib/merkle/node.d.ts +55 -0
- package/dist/daemon/lib/merkle/node.js +82 -0
- package/dist/daemon/lifecycle.d.ts +50 -0
- package/dist/daemon/lifecycle.js +142 -0
- package/dist/daemon/owner.d.ts +175 -0
- package/dist/daemon/owner.js +609 -0
- package/dist/daemon/protocol.d.ts +100 -0
- package/dist/daemon/protocol.js +163 -0
- package/dist/daemon/providers/api-utils.d.ts +130 -0
- package/dist/daemon/providers/api-utils.js +248 -0
- package/dist/daemon/providers/gemini.d.ts +39 -0
- package/dist/daemon/providers/gemini.js +205 -0
- package/dist/daemon/providers/index.d.ts +14 -0
- package/dist/daemon/providers/index.js +14 -0
- package/dist/daemon/providers/local-4b.d.ts +28 -0
- package/dist/daemon/providers/local-4b.js +51 -0
- package/dist/daemon/providers/local.d.ts +36 -0
- package/dist/daemon/providers/local.js +166 -0
- package/dist/daemon/providers/mistral.d.ts +35 -0
- package/dist/daemon/providers/mistral.js +160 -0
- package/dist/daemon/providers/mock.d.ts +35 -0
- package/dist/daemon/providers/mock.js +69 -0
- package/dist/daemon/providers/openai.d.ts +41 -0
- package/dist/daemon/providers/openai.js +190 -0
- package/dist/daemon/providers/types.d.ts +68 -0
- package/dist/daemon/providers/types.js +6 -0
- package/dist/daemon/providers/validate.d.ts +30 -0
- package/dist/daemon/providers/validate.js +162 -0
- package/dist/daemon/server.d.ts +79 -0
- package/dist/daemon/server.js +293 -0
- package/dist/daemon/services/index.d.ts +11 -0
- package/dist/daemon/services/index.js +16 -0
- package/dist/daemon/services/indexing.d.ts +117 -0
- package/dist/daemon/services/indexing.js +573 -0
- package/dist/daemon/services/search/filters.d.ts +21 -0
- package/dist/daemon/services/search/filters.js +106 -0
- package/dist/daemon/services/search/fts.d.ts +32 -0
- package/dist/daemon/services/search/fts.js +61 -0
- package/dist/daemon/services/search/hybrid.d.ts +17 -0
- package/dist/daemon/services/search/hybrid.js +58 -0
- package/dist/daemon/services/search/index.d.ts +108 -0
- package/dist/daemon/services/search/index.js +417 -0
- package/dist/daemon/services/search/types.d.ts +126 -0
- package/dist/daemon/services/search/types.js +4 -0
- package/dist/daemon/services/search/vector.d.ts +25 -0
- package/dist/daemon/services/search/vector.js +44 -0
- package/dist/daemon/services/storage/index.d.ts +110 -0
- package/dist/daemon/services/storage/index.js +378 -0
- package/dist/daemon/services/storage/schema.d.ts +24 -0
- package/dist/daemon/services/storage/schema.js +51 -0
- package/dist/daemon/services/storage/types.d.ts +105 -0
- package/dist/daemon/services/storage/types.js +71 -0
- package/dist/daemon/services/types.d.ts +192 -0
- package/dist/daemon/services/types.js +53 -0
- package/dist/daemon/services/watcher.d.ts +98 -0
- package/dist/daemon/services/watcher.js +386 -0
- package/dist/daemon/state.d.ts +119 -0
- package/dist/daemon/state.js +161 -0
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +44 -60
- package/dist/mcp/server.d.ts +10 -14
- package/dist/mcp/server.js +75 -74
- package/dist/mcp/services/lazy-loader.d.ts +23 -0
- package/dist/mcp/services/lazy-loader.js +34 -0
- package/dist/mcp/warmup.d.ts +3 -3
- package/dist/mcp/warmup.js +39 -40
- package/dist/mcp/watcher.d.ts +5 -7
- package/dist/mcp/watcher.js +73 -64
- package/dist/rag/config/index.d.ts +2 -0
- package/dist/rag/constants.d.ts +30 -0
- package/dist/rag/constants.js +38 -0
- package/dist/rag/embeddings/api-utils.d.ts +121 -0
- package/dist/rag/embeddings/api-utils.js +259 -0
- package/dist/rag/embeddings/gemini.d.ts +4 -12
- package/dist/rag/embeddings/gemini.js +22 -72
- package/dist/rag/embeddings/index.d.ts +5 -3
- package/dist/rag/embeddings/index.js +5 -2
- package/dist/rag/embeddings/local-4b.d.ts +2 -2
- package/dist/rag/embeddings/local-4b.js +1 -1
- package/dist/rag/embeddings/local.d.ts +10 -3
- package/dist/rag/embeddings/local.js +58 -12
- package/dist/rag/embeddings/mistral.d.ts +4 -12
- package/dist/rag/embeddings/mistral.js +22 -72
- package/dist/rag/embeddings/mock.d.ts +35 -0
- package/dist/rag/embeddings/mock.js +69 -0
- package/dist/rag/embeddings/openai.d.ts +11 -13
- package/dist/rag/embeddings/openai.js +47 -75
- package/dist/rag/embeddings/types.d.ts +27 -1
- package/dist/rag/embeddings/validate.d.ts +9 -1
- package/dist/rag/embeddings/validate.js +17 -4
- package/dist/rag/index.d.ts +2 -2
- package/dist/rag/index.js +1 -1
- package/dist/rag/indexer/bounded-channel.d.ts +51 -0
- package/dist/rag/indexer/bounded-channel.js +138 -0
- package/dist/rag/indexer/indexer.d.ts +4 -14
- package/dist/rag/indexer/indexer.js +246 -169
- package/dist/rag/indexer/types.d.ts +1 -0
- package/dist/rag/logger/index.d.ts +22 -0
- package/dist/rag/logger/index.js +78 -1
- package/dist/rag/manifest/index.js +1 -2
- package/dist/rag/search/index.js +1 -1
- package/dist/rag/storage/schema.d.ts +2 -4
- package/dist/rag/storage/schema.js +3 -5
- package/dist/store/app/selectors.d.ts +87 -0
- package/dist/store/app/selectors.js +28 -0
- package/dist/store/app/slice.d.ts +1013 -0
- package/dist/store/app/slice.js +112 -0
- package/dist/store/hooks.d.ts +22 -0
- package/dist/store/hooks.js +17 -0
- package/dist/store/index.d.ts +12 -0
- package/dist/store/index.js +18 -0
- package/dist/store/indexing/listeners.d.ts +25 -0
- package/dist/store/indexing/listeners.js +46 -0
- package/dist/store/indexing/selectors.d.ts +195 -0
- package/dist/store/indexing/selectors.js +69 -0
- package/dist/store/indexing/slice.d.ts +309 -0
- package/dist/store/indexing/slice.js +113 -0
- package/dist/store/slot-progress/listeners.d.ts +23 -0
- package/dist/store/slot-progress/listeners.js +33 -0
- package/dist/store/slot-progress/selectors.d.ts +67 -0
- package/dist/store/slot-progress/selectors.js +36 -0
- package/dist/store/slot-progress/slice.d.ts +246 -0
- package/dist/store/slot-progress/slice.js +70 -0
- package/dist/store/store.d.ts +17 -0
- package/dist/store/store.js +18 -0
- package/dist/store/warmup/selectors.d.ts +109 -0
- package/dist/store/warmup/selectors.js +44 -0
- package/dist/store/warmup/slice.d.ts +137 -0
- package/dist/store/warmup/slice.js +72 -0
- package/dist/store/watcher/selectors.d.ts +115 -0
- package/dist/store/watcher/selectors.js +52 -0
- package/dist/store/watcher/slice.d.ts +269 -0
- package/dist/store/watcher/slice.js +100 -0
- package/dist/store/wizard/selectors.d.ts +115 -0
- package/dist/store/wizard/selectors.js +36 -0
- package/dist/store/wizard/slice.d.ts +523 -0
- package/dist/store/wizard/slice.js +119 -0
- 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);
|