@code-rag/mcp-server 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.
@@ -0,0 +1,17 @@
1
+ import { z } from 'zod';
2
+ import type { HybridSearch, ContextExpander } from '@code-rag/core';
3
+ export declare const explainInputSchema: z.ZodObject<{
4
+ file_path: z.ZodOptional<z.ZodString>;
5
+ name: z.ZodOptional<z.ZodString>;
6
+ detail_level: z.ZodDefault<z.ZodOptional<z.ZodEnum<{
7
+ brief: "brief";
8
+ detailed: "detailed";
9
+ }>>>;
10
+ }, z.core.$strip>;
11
+ export type ExplainInput = z.infer<typeof explainInputSchema>;
12
+ export declare function handleExplain(args: Record<string, unknown>, hybridSearch: HybridSearch | null, contextExpander: ContextExpander | null): Promise<{
13
+ content: Array<{
14
+ type: 'text';
15
+ text: string;
16
+ }>;
17
+ }>;
@@ -0,0 +1,138 @@
1
+ import { z } from 'zod';
2
+ export const explainInputSchema = z.object({
3
+ file_path: z.string().min(1, 'file_path must not be empty').refine((s) => !s.includes('..'), 'file_path must not contain path traversal').optional(),
4
+ name: z.string().min(1, 'name must not be empty').optional(),
5
+ detail_level: z.enum(['brief', 'detailed']).optional().default('detailed'),
6
+ }).refine((data) => data.file_path !== undefined || data.name !== undefined, 'At least one of file_path or name must be provided');
7
+ function formatChunk(result, detailLevel) {
8
+ const explanation = {
9
+ file_path: result.chunk?.filePath ?? '',
10
+ chunk_type: result.metadata?.chunkType ?? 'unknown',
11
+ name: result.metadata?.name ?? '',
12
+ nl_summary: result.nlSummary,
13
+ };
14
+ if (detailLevel === 'detailed') {
15
+ explanation.code = result.content;
16
+ }
17
+ return explanation;
18
+ }
19
+ export async function handleExplain(args, hybridSearch, contextExpander) {
20
+ const parsed = explainInputSchema.safeParse(args);
21
+ if (!parsed.success) {
22
+ return {
23
+ content: [
24
+ {
25
+ type: 'text',
26
+ text: JSON.stringify({
27
+ error: 'Invalid input',
28
+ details: parsed.error.issues,
29
+ }),
30
+ },
31
+ ],
32
+ };
33
+ }
34
+ if (!hybridSearch) {
35
+ return {
36
+ content: [
37
+ {
38
+ type: 'text',
39
+ text: JSON.stringify({
40
+ explanation: { chunks: [], detail_level: parsed.data.detail_level },
41
+ chunks_found: 0,
42
+ message: 'Services not initialized. Run indexing first.',
43
+ }),
44
+ },
45
+ ],
46
+ };
47
+ }
48
+ const { file_path, name, detail_level } = parsed.data;
49
+ try {
50
+ let results = [];
51
+ if (name) {
52
+ // Search by function/class/method name
53
+ const searchResult = await hybridSearch.search(name, { topK: 5 });
54
+ if (searchResult.isErr()) {
55
+ return {
56
+ content: [
57
+ {
58
+ type: 'text',
59
+ text: JSON.stringify({
60
+ error: 'Search failed',
61
+ message: searchResult.error.message,
62
+ }),
63
+ },
64
+ ],
65
+ };
66
+ }
67
+ // Filter to chunks whose name matches
68
+ results = searchResult.value.filter((r) => r.metadata?.name?.toLowerCase().includes(name.toLowerCase()));
69
+ }
70
+ else if (file_path) {
71
+ // Search by file path
72
+ const searchResult = await hybridSearch.search(file_path, { topK: 20 });
73
+ if (searchResult.isErr()) {
74
+ return {
75
+ content: [
76
+ {
77
+ type: 'text',
78
+ text: JSON.stringify({
79
+ error: 'Search failed',
80
+ message: searchResult.error.message,
81
+ }),
82
+ },
83
+ ],
84
+ };
85
+ }
86
+ // Filter to chunks in the specified file
87
+ results = searchResult.value.filter((r) => r.chunk?.filePath?.includes(file_path));
88
+ }
89
+ if (results.length === 0) {
90
+ return {
91
+ content: [
92
+ {
93
+ type: 'text',
94
+ text: JSON.stringify({
95
+ explanation: { chunks: [], detail_level },
96
+ chunks_found: 0,
97
+ message: name
98
+ ? `No chunks found matching name: ${name}`
99
+ : `No chunks found for file: ${file_path}`,
100
+ }),
101
+ },
102
+ ],
103
+ };
104
+ }
105
+ const chunks = results.map((r) => formatChunk(r, detail_level));
106
+ const result = {
107
+ explanation: {
108
+ chunks,
109
+ detail_level,
110
+ },
111
+ chunks_found: results.length,
112
+ };
113
+ // Add related symbols from context expander when in detailed mode
114
+ if (detail_level === 'detailed' && contextExpander) {
115
+ const expanded = contextExpander.expand(results);
116
+ const relatedSymbols = expanded.relatedChunks.map((related) => {
117
+ return related.chunk.metadata.name ?? related.chunk.chunk?.filePath ?? 'unknown';
118
+ });
119
+ if (relatedSymbols.length > 0) {
120
+ result.explanation.related_symbols = relatedSymbols;
121
+ }
122
+ }
123
+ return {
124
+ content: [{ type: 'text', text: JSON.stringify(result) }],
125
+ };
126
+ }
127
+ catch (error) {
128
+ const message = error instanceof Error ? error.message : 'Unknown error';
129
+ return {
130
+ content: [
131
+ {
132
+ type: 'text',
133
+ text: JSON.stringify({ error: 'Explain failed', message }),
134
+ },
135
+ ],
136
+ };
137
+ }
138
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.js","sourceRoot":"","sources":["../../src/tools/explain.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,244 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { ok, err } from 'neverthrow';
3
+ import { handleExplain, explainInputSchema } from './explain.js';
4
+ import { EmbedError } from '@coderag/core';
5
+ // --- Helpers ---
6
+ function makeSearchResult(overrides = {}) {
7
+ return {
8
+ chunkId: 'chunk-1',
9
+ content: 'function hello() {}',
10
+ nlSummary: 'A greeting function',
11
+ score: 0.95,
12
+ method: 'hybrid',
13
+ metadata: {
14
+ chunkType: 'function',
15
+ name: 'hello',
16
+ declarations: [],
17
+ imports: [],
18
+ exports: [],
19
+ },
20
+ chunk: {
21
+ id: 'chunk-1',
22
+ content: 'function hello() {}',
23
+ nlSummary: 'A greeting function',
24
+ filePath: 'src/utils/hello.ts',
25
+ startLine: 1,
26
+ endLine: 3,
27
+ language: 'typescript',
28
+ metadata: {
29
+ chunkType: 'function',
30
+ name: 'hello',
31
+ declarations: [],
32
+ imports: [],
33
+ exports: [],
34
+ },
35
+ },
36
+ ...overrides,
37
+ };
38
+ }
39
+ function parseResponse(response) {
40
+ return JSON.parse(response.content[0].text);
41
+ }
42
+ // --- Input Validation Tests ---
43
+ describe('explainInputSchema', () => {
44
+ it('should accept valid input with name', () => {
45
+ const result = explainInputSchema.safeParse({ name: 'hello' });
46
+ expect(result.success).toBe(true);
47
+ });
48
+ it('should accept valid input with file_path', () => {
49
+ const result = explainInputSchema.safeParse({ file_path: 'src/utils/hello.ts' });
50
+ expect(result.success).toBe(true);
51
+ });
52
+ it('should reject input with neither file_path nor name', () => {
53
+ const result = explainInputSchema.safeParse({});
54
+ expect(result.success).toBe(false);
55
+ });
56
+ it('should reject file_path with path traversal', () => {
57
+ const result = explainInputSchema.safeParse({ file_path: '../../etc/passwd' });
58
+ expect(result.success).toBe(false);
59
+ });
60
+ it('should apply default detail_level of detailed', () => {
61
+ const result = explainInputSchema.parse({ name: 'hello' });
62
+ expect(result.detail_level).toBe('detailed');
63
+ });
64
+ it('should accept brief detail_level', () => {
65
+ const result = explainInputSchema.parse({ name: 'hello', detail_level: 'brief' });
66
+ expect(result.detail_level).toBe('brief');
67
+ });
68
+ it('should reject invalid detail_level', () => {
69
+ const result = explainInputSchema.safeParse({ name: 'hello', detail_level: 'verbose' });
70
+ expect(result.success).toBe(false);
71
+ });
72
+ it('should accept both file_path and name together', () => {
73
+ const result = explainInputSchema.safeParse({ file_path: 'src/hello.ts', name: 'hello' });
74
+ expect(result.success).toBe(true);
75
+ });
76
+ });
77
+ // --- Handler Tests ---
78
+ describe('handleExplain', () => {
79
+ let mockHybridSearch;
80
+ let mockContextExpander;
81
+ beforeEach(() => {
82
+ mockHybridSearch = {
83
+ search: vi.fn(),
84
+ };
85
+ mockContextExpander = {
86
+ expand: vi.fn(),
87
+ };
88
+ });
89
+ it('should return validation error when both file_path and name are missing', async () => {
90
+ const response = await handleExplain({}, mockHybridSearch, mockContextExpander);
91
+ const parsed = parseResponse(response);
92
+ expect(parsed.error).toBe('Invalid input');
93
+ });
94
+ it('should return validation error for file_path with path traversal', async () => {
95
+ const response = await handleExplain({ file_path: '../../../etc/passwd' }, mockHybridSearch, mockContextExpander);
96
+ const parsed = parseResponse(response);
97
+ expect(parsed.error).toBe('Invalid input');
98
+ });
99
+ it('should return graceful message when services are null', async () => {
100
+ const response = await handleExplain({ name: 'hello' }, null, null);
101
+ const parsed = parseResponse(response);
102
+ expect(parsed.chunks_found).toBe(0);
103
+ expect(parsed.explanation.chunks).toEqual([]);
104
+ expect(parsed.message).toContain('not initialized');
105
+ });
106
+ it('should search by name and return matching chunks', async () => {
107
+ const results = [makeSearchResult()];
108
+ vi.mocked(mockHybridSearch.search).mockResolvedValue(ok(results));
109
+ const expandedContext = {
110
+ primaryResults: results,
111
+ relatedChunks: [],
112
+ graphExcerpt: { nodes: [], edges: [] },
113
+ };
114
+ vi.mocked(mockContextExpander.expand).mockReturnValue(expandedContext);
115
+ const response = await handleExplain({ name: 'hello' }, mockHybridSearch, mockContextExpander);
116
+ const parsed = parseResponse(response);
117
+ expect(mockHybridSearch.search).toHaveBeenCalledWith('hello', { topK: 5 });
118
+ expect(parsed.chunks_found).toBe(1);
119
+ expect(parsed.explanation.chunks[0].name).toBe('hello');
120
+ expect(parsed.explanation.chunks[0].nl_summary).toBe('A greeting function');
121
+ expect(parsed.explanation.chunks[0].code).toBe('function hello() {}');
122
+ });
123
+ it('should search by file_path and return matching chunks', async () => {
124
+ const results = [makeSearchResult()];
125
+ vi.mocked(mockHybridSearch.search).mockResolvedValue(ok(results));
126
+ const expandedContext = {
127
+ primaryResults: results,
128
+ relatedChunks: [],
129
+ graphExcerpt: { nodes: [], edges: [] },
130
+ };
131
+ vi.mocked(mockContextExpander.expand).mockReturnValue(expandedContext);
132
+ const response = await handleExplain({ file_path: 'src/utils/hello.ts' }, mockHybridSearch, mockContextExpander);
133
+ const parsed = parseResponse(response);
134
+ expect(mockHybridSearch.search).toHaveBeenCalledWith('src/utils/hello.ts', { topK: 20 });
135
+ expect(parsed.chunks_found).toBe(1);
136
+ expect(parsed.explanation.chunks[0].file_path).toBe('src/utils/hello.ts');
137
+ });
138
+ it('should return brief output without code or related_symbols', async () => {
139
+ const results = [makeSearchResult()];
140
+ vi.mocked(mockHybridSearch.search).mockResolvedValue(ok(results));
141
+ const response = await handleExplain({ name: 'hello', detail_level: 'brief' }, mockHybridSearch, mockContextExpander);
142
+ const parsed = parseResponse(response);
143
+ expect(parsed.explanation.detail_level).toBe('brief');
144
+ expect(parsed.explanation.chunks[0].nl_summary).toBe('A greeting function');
145
+ expect(parsed.explanation.chunks[0].code).toBeUndefined();
146
+ expect(parsed.explanation.related_symbols).toBeUndefined();
147
+ });
148
+ it('should return detailed output with code and related_symbols', async () => {
149
+ const results = [makeSearchResult()];
150
+ vi.mocked(mockHybridSearch.search).mockResolvedValue(ok(results));
151
+ const depResult = makeSearchResult({
152
+ chunkId: 'dep-1',
153
+ metadata: {
154
+ chunkType: 'function',
155
+ name: 'greet',
156
+ declarations: [],
157
+ imports: [],
158
+ exports: [],
159
+ },
160
+ chunk: {
161
+ id: 'dep-1',
162
+ content: 'function greet() {}',
163
+ nlSummary: 'A greet helper',
164
+ filePath: 'src/utils/greet.ts',
165
+ startLine: 1,
166
+ endLine: 2,
167
+ language: 'typescript',
168
+ metadata: {
169
+ chunkType: 'function',
170
+ name: 'greet',
171
+ declarations: [],
172
+ imports: [],
173
+ exports: [],
174
+ },
175
+ },
176
+ });
177
+ const expandedContext = {
178
+ primaryResults: results,
179
+ relatedChunks: [
180
+ {
181
+ chunk: depResult,
182
+ relationship: 'imports',
183
+ distance: 1,
184
+ },
185
+ ],
186
+ graphExcerpt: { nodes: [], edges: [] },
187
+ };
188
+ vi.mocked(mockContextExpander.expand).mockReturnValue(expandedContext);
189
+ const response = await handleExplain({ name: 'hello', detail_level: 'detailed' }, mockHybridSearch, mockContextExpander);
190
+ const parsed = parseResponse(response);
191
+ expect(parsed.explanation.detail_level).toBe('detailed');
192
+ expect(parsed.explanation.chunks[0].code).toBe('function hello() {}');
193
+ expect(parsed.explanation.related_symbols).toContain('greet');
194
+ });
195
+ it('should handle search API errors gracefully', async () => {
196
+ vi.mocked(mockHybridSearch.search).mockResolvedValue(err(new EmbedError('Connection refused')));
197
+ const response = await handleExplain({ name: 'hello' }, mockHybridSearch, mockContextExpander);
198
+ const parsed = parseResponse(response);
199
+ expect(parsed.error).toBe('Search failed');
200
+ expect(parsed.message).toContain('Connection refused');
201
+ });
202
+ it('should handle thrown exceptions', async () => {
203
+ vi.mocked(mockHybridSearch.search).mockRejectedValue(new Error('Unexpected'));
204
+ const response = await handleExplain({ name: 'hello' }, mockHybridSearch, mockContextExpander);
205
+ const parsed = parseResponse(response);
206
+ expect(parsed.error).toBe('Explain failed');
207
+ expect(parsed.message).toBe('Unexpected');
208
+ });
209
+ it('should return empty explanation when no chunks match name', async () => {
210
+ const results = [makeSearchResult({
211
+ metadata: {
212
+ chunkType: 'function',
213
+ name: 'other',
214
+ declarations: [],
215
+ imports: [],
216
+ exports: [],
217
+ },
218
+ })];
219
+ vi.mocked(mockHybridSearch.search).mockResolvedValue(ok(results));
220
+ const response = await handleExplain({ name: 'hello' }, mockHybridSearch, mockContextExpander);
221
+ const parsed = parseResponse(response);
222
+ expect(parsed.chunks_found).toBe(0);
223
+ expect(parsed.message).toContain('No chunks found matching name: hello');
224
+ });
225
+ it('should return empty explanation when no chunks match file_path', async () => {
226
+ const results = [makeSearchResult()]; // filePath is 'src/utils/hello.ts'
227
+ vi.mocked(mockHybridSearch.search).mockResolvedValue(ok(results));
228
+ const response = await handleExplain({ file_path: 'src/other.ts' }, mockHybridSearch, mockContextExpander);
229
+ const parsed = parseResponse(response);
230
+ expect(parsed.chunks_found).toBe(0);
231
+ expect(parsed.message).toContain('No chunks found for file: src/other.ts');
232
+ });
233
+ it('should work without context expander in detailed mode', async () => {
234
+ const results = [makeSearchResult()];
235
+ vi.mocked(mockHybridSearch.search).mockResolvedValue(ok(results));
236
+ const response = await handleExplain({ name: 'hello', detail_level: 'detailed' }, mockHybridSearch, null);
237
+ const parsed = parseResponse(response);
238
+ expect(parsed.chunks_found).toBe(1);
239
+ expect(parsed.explanation.chunks[0].code).toBe('function hello() {}');
240
+ // No related_symbols since context expander is null
241
+ expect(parsed.explanation.related_symbols).toBeUndefined();
242
+ });
243
+ });
244
+ //# sourceMappingURL=explain.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain.test.js","sourceRoot":"","sources":["../../src/tools/explain.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ import { z } from 'zod';
2
+ import type { HybridSearch } from '@code-rag/core';
3
+ import type { ReRanker } from '@code-rag/core';
4
+ export declare const searchInputSchema: z.ZodObject<{
5
+ query: z.ZodString;
6
+ language: z.ZodOptional<z.ZodString>;
7
+ file_path: z.ZodOptional<z.ZodString>;
8
+ chunk_type: z.ZodOptional<z.ZodString>;
9
+ top_k: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
10
+ }, z.core.$strip>;
11
+ export type SearchInput = z.infer<typeof searchInputSchema>;
12
+ export interface SearchToolResult {
13
+ file_path: string;
14
+ chunk_type: string;
15
+ name: string;
16
+ content: string;
17
+ nl_summary: string;
18
+ score: number;
19
+ }
20
+ export declare function handleSearch(args: Record<string, unknown>, hybridSearch: HybridSearch | null, reranker?: ReRanker | null): Promise<{
21
+ content: Array<{
22
+ type: 'text';
23
+ text: string;
24
+ }>;
25
+ }>;
@@ -0,0 +1,101 @@
1
+ import { z } from 'zod';
2
+ export const searchInputSchema = z.object({
3
+ query: z.string().min(1, 'query must not be empty'),
4
+ language: z.string().optional(),
5
+ file_path: z.string().refine((s) => !s.includes('..'), 'file_path must not contain path traversal').optional(),
6
+ chunk_type: z.string().optional(),
7
+ top_k: z.number().int().positive().max(100).optional().default(10),
8
+ });
9
+ function formatResult(result) {
10
+ return {
11
+ file_path: result.chunk?.filePath ?? '',
12
+ chunk_type: result.metadata?.chunkType ?? 'unknown',
13
+ name: result.metadata?.name ?? '',
14
+ content: result.content,
15
+ nl_summary: result.nlSummary,
16
+ score: result.score,
17
+ };
18
+ }
19
+ export async function handleSearch(args, hybridSearch, reranker = null) {
20
+ const parsed = searchInputSchema.safeParse(args);
21
+ if (!parsed.success) {
22
+ return {
23
+ content: [
24
+ {
25
+ type: 'text',
26
+ text: JSON.stringify({
27
+ error: 'Invalid input',
28
+ details: parsed.error.issues,
29
+ }),
30
+ },
31
+ ],
32
+ };
33
+ }
34
+ if (!hybridSearch) {
35
+ return {
36
+ content: [
37
+ {
38
+ type: 'text',
39
+ text: JSON.stringify({
40
+ results: [],
41
+ message: 'Search index not initialized. Run indexing first.',
42
+ }),
43
+ },
44
+ ],
45
+ };
46
+ }
47
+ const { query, top_k } = parsed.data;
48
+ try {
49
+ const searchResult = await hybridSearch.search(query, { topK: top_k });
50
+ if (searchResult.isErr()) {
51
+ return {
52
+ content: [
53
+ {
54
+ type: 'text',
55
+ text: JSON.stringify({
56
+ error: 'Search failed',
57
+ message: searchResult.error.message,
58
+ }),
59
+ },
60
+ ],
61
+ };
62
+ }
63
+ let results = searchResult.value;
64
+ // Apply optional filters
65
+ if (parsed.data.language) {
66
+ const lang = parsed.data.language.toLowerCase();
67
+ results = results.filter((r) => r.chunk?.language?.toLowerCase() === lang);
68
+ }
69
+ if (parsed.data.file_path) {
70
+ const fp = parsed.data.file_path;
71
+ results = results.filter((r) => r.chunk?.filePath?.includes(fp));
72
+ }
73
+ if (parsed.data.chunk_type) {
74
+ const ct = parsed.data.chunk_type;
75
+ results = results.filter((r) => r.metadata?.chunkType === ct);
76
+ }
77
+ // Re-rank results if reranker is available
78
+ if (reranker) {
79
+ const rerankResult = await reranker.rerank(query, results);
80
+ if (rerankResult.isOk()) {
81
+ results = rerankResult.value;
82
+ }
83
+ // If reranking fails, fall back to original results (don't fail the search)
84
+ }
85
+ const formatted = results.map(formatResult);
86
+ return {
87
+ content: [{ type: 'text', text: JSON.stringify({ results: formatted }) }],
88
+ };
89
+ }
90
+ catch (error) {
91
+ const message = error instanceof Error ? error.message : 'Unknown error';
92
+ return {
93
+ content: [
94
+ {
95
+ type: 'text',
96
+ text: JSON.stringify({ error: 'Search failed', message }),
97
+ },
98
+ ],
99
+ };
100
+ }
101
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/tools/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { LanceDBStore, CodeRAGConfig } from '@code-rag/core';
2
+ export interface StatusResult {
3
+ total_chunks: number;
4
+ last_indexed: string | null;
5
+ model: string;
6
+ languages: string[] | 'auto';
7
+ health: 'ok' | 'degraded' | 'not_initialized';
8
+ }
9
+ export declare function handleStatus(store: LanceDBStore | null, config: CodeRAGConfig | null): Promise<{
10
+ content: Array<{
11
+ type: 'text';
12
+ text: string;
13
+ }>;
14
+ }>;
@@ -0,0 +1,41 @@
1
+ export async function handleStatus(store, config) {
2
+ try {
3
+ let totalChunks = 0;
4
+ let health = 'not_initialized';
5
+ if (store) {
6
+ const countResult = await store.count();
7
+ if (countResult.isOk()) {
8
+ totalChunks = countResult.value;
9
+ health = totalChunks > 0 ? 'ok' : 'degraded';
10
+ }
11
+ else {
12
+ health = 'degraded';
13
+ }
14
+ }
15
+ const status = {
16
+ total_chunks: totalChunks,
17
+ last_indexed: null,
18
+ model: config?.embedding.model ?? 'unknown',
19
+ languages: config?.project.languages ?? 'auto',
20
+ health,
21
+ };
22
+ return {
23
+ content: [{ type: 'text', text: JSON.stringify(status) }],
24
+ };
25
+ }
26
+ catch (error) {
27
+ const message = error instanceof Error ? error.message : 'Unknown error';
28
+ return {
29
+ content: [
30
+ {
31
+ type: 'text',
32
+ text: JSON.stringify({
33
+ error: 'Status check failed',
34
+ message,
35
+ health: 'degraded',
36
+ }),
37
+ },
38
+ ],
39
+ };
40
+ }
41
+ }
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/tools/status.ts"],"names":[],"mappings":"AAUA,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAA0B,EAC1B,MAA4B;IAE5B,IAAI,CAAC;QACH,IAAI,WAAW,GAAG,CAAC,CAAC;QACpB,IAAI,MAAM,GAA2B,iBAAiB,CAAC;QAEvD,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,WAAW,GAAG,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;YACxC,IAAI,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;gBACvB,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC;gBAChC,MAAM,GAAG,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC;YAC/C,CAAC;iBAAM,CAAC;gBACN,MAAM,GAAG,UAAU,CAAC;YACtB,CAAC;QACH,CAAC;QAED,MAAM,MAAM,GAAiB;YAC3B,YAAY,EAAE,WAAW;YACzB,YAAY,EAAE,IAAI;YAClB,KAAK,EAAE,MAAM,EAAE,SAAS,CAAC,KAAK,IAAI,SAAS;YAC3C,SAAS,EAAE,MAAM,EAAE,OAAO,CAAC,SAAS,IAAI,MAAM;YAC9C,MAAM;SACP,CAAC;QAEF,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;SAC1D,CAAC;IACJ,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC;QACzE,OAAO;YACL,OAAO,EAAE;gBACP;oBACE,IAAI,EAAE,MAAM;oBACZ,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;wBACnB,KAAK,EAAE,qBAAqB;wBAC5B,OAAO;wBACP,MAAM,EAAE,UAAU;qBACnB,CAAC;iBACH;aACF;SACF,CAAC;IACJ,CAAC;AACH,CAAC"}