@codragraph/cli 2.1.1 → 2.1.4
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 +12 -9
- package/dist/cli/ai-context.js +1 -1
- package/dist/cli/analyze.js +19 -2
- package/dist/cli/index.js +2 -1
- package/dist/cli/serve.d.ts +1 -0
- package/dist/cli/serve.js +3 -1
- package/dist/cli/setup.js +36 -19
- package/dist/cli/status.d.ts +13 -0
- package/dist/cli/status.js +99 -0
- package/dist/config/ignore-service.js +2 -0
- package/dist/core/graphstore/cgdb-row-source.js +3 -2
- package/dist/core/group/bridge-db.js +42 -10
- package/dist/core/run-analyze.d.ts +20 -0
- package/dist/core/run-analyze.js +201 -0
- package/dist/core/search/hybrid-search.js +11 -3
- package/dist/mcp/resources.js +2 -2
- package/dist/server/api.d.ts +14 -2
- package/dist/server/api.js +90 -7
- package/dist/server/mcp-http.d.ts +22 -0
- package/dist/server/mcp-http.js +21 -2
- package/dist/server/web-dashboard.d.ts +28 -0
- package/dist/server/web-dashboard.js +61 -0
- package/dist/web/assets/agent-D5lb0zXz.js +1089 -0
- package/dist/web/assets/architectureDiagram-EMZXCZ2Q-CZtc99v_.js +36 -0
- package/dist/web/assets/blockDiagram-IGV67L2C-BtoUp-6Y.js +132 -0
- package/dist/web/assets/c4Diagram-DFAF54RM-C4Hl3J2U.js +10 -0
- package/dist/web/assets/chunk-3GS5O3IE-DkUjU0WD.js +231 -0
- package/dist/web/assets/chunk-3YCYZ6SJ-CQkVgT_z.js +1 -0
- package/dist/web/assets/chunk-7RZVMHOQ-BitYcNVR.js +338 -0
- package/dist/web/assets/chunk-AEOMTBSW-BgTIXPsY.js +1 -0
- package/dist/web/assets/chunk-H3VCZNTA-Cx5XV_aC.js +13 -0
- package/dist/web/assets/chunk-HN6EAY2L-BBnyTNdB.js +1 -0
- package/dist/web/assets/chunk-KSICW3F5-BYzvDLNI.js +15 -0
- package/dist/web/assets/chunk-O5ABG6QK-dHwHzA6n.js +1 -0
- package/dist/web/assets/chunk-PK6DOVAG-CvsEnugt.js +206 -0
- package/dist/web/assets/chunk-RWUO3TPN-BgRTY0_k.js +1 -0
- package/dist/web/assets/chunk-TBF5ZNIQ-DL5stGM1.js +1 -0
- package/dist/web/assets/chunk-TU3PZOEN-RLyvLcv-.js +1 -0
- package/dist/web/assets/classDiagram-PPOCWD7C-DTr8QIOf.js +1 -0
- package/dist/web/assets/classDiagram-v2-23LJLIIU-DTr8QIOf.js +1 -0
- package/dist/web/assets/context-builder-22jU3V56.js +16 -0
- package/dist/web/assets/cose-bilkent-PNC4W37J-DVhePRYg.js +1 -0
- package/dist/web/assets/dagre-E77IOHMT-Dzx0A6ZU.js +4 -0
- package/dist/web/assets/diagram-H7BISOXX-CC9pRew1.js +43 -0
- package/dist/web/assets/diagram-JC5VWROH-Bau_i9tf.js +24 -0
- package/dist/web/assets/diagram-LXUTUG65-D9_FM2Gt.js +10 -0
- package/dist/web/assets/diagram-WEHSV5V5-BMlayouL.js +24 -0
- package/dist/web/assets/erDiagram-GCSMX5X6-C3dhDFA8.js +85 -0
- package/dist/web/assets/flowDiagram-OTCZ4VVT-CWSFWmhr.js +162 -0
- package/dist/web/assets/ganttDiagram-MUNLMDZQ-D3a67Yol.js +292 -0
- package/dist/web/assets/gitGraphDiagram-3HKGZ4G3-7jmry-vM.js +106 -0
- package/dist/web/assets/index-BgeqpYgd.js +1415 -0
- package/dist/web/assets/index-CT0GtFLZ.css +1 -0
- package/dist/web/assets/infoDiagram-MN7RKWGX-G7lhP0Ib.js +2 -0
- package/dist/web/assets/ishikawaDiagram-YMYX4NHK-DUoJvNP2.js +70 -0
- package/dist/web/assets/journeyDiagram-SO5T7YLQ-RMFPNNqz.js +139 -0
- package/dist/web/assets/kanban-definition-LJHFXRCJ-BzpDs1K9.js +89 -0
- package/dist/web/assets/katex-GD7MH7QM-DBQvrix-.js +261 -0
- package/dist/web/assets/mindmap-definition-2EUWGEK5-Bk0O4roa.js +96 -0
- package/dist/web/assets/pieDiagram-3IATQBI2-DKU7kpgS.js +30 -0
- package/dist/web/assets/quadrantDiagram-E256RVCF-BY0TGWCS.js +7 -0
- package/dist/web/assets/requirementDiagram-M5DCFWZL-DLHOVTSv.js +84 -0
- package/dist/web/assets/sankeyDiagram-L3NBLAOT-DVMj5rX2.js +10 -0
- package/dist/web/assets/sequenceDiagram-ZOUHS735-CJC73bV-.js +157 -0
- package/dist/web/assets/stateDiagram-MLPALWAM-BCFyESls.js +1 -0
- package/dist/web/assets/stateDiagram-v2-B5LQ5ZB2-DahzzIca.js +1 -0
- package/dist/web/assets/timeline-definition-5SPVSISX-TRSDRgPw.js +120 -0
- package/dist/web/assets/vennDiagram-IE5QUKF5-DNy7HRBM.js +34 -0
- package/dist/web/assets/wardley-RL74JXVD-BCRCBASE-B-eZEzf9.js +161 -0
- package/dist/web/assets/wardleyDiagram-XU3VSMPF-BP-r1xzR.js +20 -0
- package/dist/web/assets/xychartDiagram-ZHJ5623Y-Dr9r7a35.js +7 -0
- package/dist/web/codragraph-logo-512.png +0 -0
- package/dist/web/codragraph-logo.png +0 -0
- package/dist/web/favicon.png +0 -0
- package/dist/web/index.html +36 -0
- package/hooks/claude/codragraph-hook.cjs +24 -9
- package/hooks/claude/pre-tool-use.sh +6 -1
- package/package.json +3 -1
- package/scripts/build.js +62 -4
- package/scripts/patch-tree-sitter-swift.cjs +0 -1
- package/skills/codragraph-cli.md +1 -1
- package/vendor/leiden/index.cjs +272 -285
- package/vendor/leiden/utils.cjs +264 -274
- package/dist/_shared/lbug/schema-constants.d.ts +0 -16
- package/dist/_shared/lbug/schema-constants.d.ts.map +0 -1
- package/dist/_shared/lbug/schema-constants.js +0 -67
- package/dist/_shared/lbug/schema-constants.js.map +0 -1
- package/dist/core/graphstore/lbug-row-source.d.ts +0 -19
- package/dist/core/graphstore/lbug-row-source.js +0 -141
- package/dist/core/lbug/content-read.d.ts +0 -46
- package/dist/core/lbug/content-read.js +0 -64
- package/dist/core/lbug/csv-generator.d.ts +0 -29
- package/dist/core/lbug/csv-generator.js +0 -492
- package/dist/core/lbug/lbug-adapter.d.ts +0 -176
- package/dist/core/lbug/lbug-adapter.js +0 -1320
- package/dist/core/lbug/pool-adapter.d.ts +0 -93
- package/dist/core/lbug/pool-adapter.js +0 -550
- package/dist/core/lbug/schema.d.ts +0 -62
- package/dist/core/lbug/schema.js +0 -502
- package/dist/mcp/core/lbug-adapter.d.ts +0 -5
- package/dist/mcp/core/lbug-adapter.js +0 -5
package/dist/core/run-analyze.js
CHANGED
|
@@ -10,18 +10,52 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import fs from 'fs/promises';
|
|
13
|
+
import { execFileSync } from 'node:child_process';
|
|
13
14
|
import * as fsSync from 'node:fs';
|
|
14
15
|
import * as v8 from 'node:v8';
|
|
16
|
+
import { getLanguageFromFilename } from '../_shared/index.js';
|
|
15
17
|
import { runPipelineFromRepo } from './ingestion/pipeline.js';
|
|
16
18
|
import { initCgdb, loadGraphToCgdb, getCgdbStats, executeQuery, executeWithReusedStatement, closeCgdb, loadCachedEmbeddings, } from './cgdb/cgdb-adapter.js';
|
|
17
19
|
import { getStoragePaths, saveMeta, loadMeta, addToGitignore, registerRepo, cleanupOldKuzuFiles, INDEX_SCHEMA_VERSION, } from '../storage/repo-manager.js';
|
|
18
20
|
import { getCurrentCommit, getRemoteUrl, hasGitDir, getInferredRepoName } from '../storage/git.js';
|
|
21
|
+
import { shouldIgnorePath } from '../config/ignore-service.js';
|
|
19
22
|
import { recordAnalysisSnapshot } from './graphstore/index.js';
|
|
20
23
|
import { generateAIContextFiles } from '../cli/ai-context.js';
|
|
21
24
|
import { EMBEDDING_TABLE_NAME } from './cgdb/schema.js';
|
|
22
25
|
import { STALE_HASH_SENTINEL } from './cgdb/schema.js';
|
|
23
26
|
/** Threshold: auto-skip embeddings for repos with more nodes than this */
|
|
24
27
|
const EMBEDDING_NODE_LIMIT = 50_000;
|
|
28
|
+
const GENERATED_AGENT_CONTEXT_PATHS = new Set(['agents.md', 'claude.md']);
|
|
29
|
+
const GENERATED_AGENT_CONTEXT_PREFIXES = [
|
|
30
|
+
'.claude/skills/generated/',
|
|
31
|
+
'.cursor/rules/codragraph-generated/',
|
|
32
|
+
];
|
|
33
|
+
const IGNORE_CONTROL_FILES = new Set(['.gitignore', '.codragraphignore']);
|
|
34
|
+
const GRAPH_CONFIG_BASENAMES = new Set([
|
|
35
|
+
'package.json',
|
|
36
|
+
'tsconfig.json',
|
|
37
|
+
'jsconfig.json',
|
|
38
|
+
'go.mod',
|
|
39
|
+
'cargo.toml',
|
|
40
|
+
'pyproject.toml',
|
|
41
|
+
'requirements.txt',
|
|
42
|
+
'composer.json',
|
|
43
|
+
'gemfile',
|
|
44
|
+
'pom.xml',
|
|
45
|
+
'build.gradle',
|
|
46
|
+
'build.gradle.kts',
|
|
47
|
+
'settings.gradle',
|
|
48
|
+
'settings.gradle.kts',
|
|
49
|
+
'pubspec.yaml',
|
|
50
|
+
'pubspec.yml',
|
|
51
|
+
'mix.exs',
|
|
52
|
+
'rebar.config',
|
|
53
|
+
'cmakelists.txt',
|
|
54
|
+
'makefile',
|
|
55
|
+
'dockerfile',
|
|
56
|
+
]);
|
|
57
|
+
const GRAPH_CONFIG_PATTERNS = [/^tsconfig\..+\.json$/i, /^jsconfig\..+\.json$/i];
|
|
58
|
+
const MARKDOWN_EXTENSIONS = new Set(['.md', '.mdx']);
|
|
25
59
|
export const PHASE_LABELS = {
|
|
26
60
|
extracting: 'Scanning files',
|
|
27
61
|
structure: 'Building structure',
|
|
@@ -38,6 +72,119 @@ export const PHASE_LABELS = {
|
|
|
38
72
|
embeddings: 'Generating embeddings',
|
|
39
73
|
done: 'Done',
|
|
40
74
|
};
|
|
75
|
+
const normalizeGitPath = (filePath) => filePath.replace(/\\/g, '/');
|
|
76
|
+
export const parseGitNameStatus = (raw) => {
|
|
77
|
+
const tokens = raw.split('\0').filter(Boolean);
|
|
78
|
+
const changes = [];
|
|
79
|
+
for (let i = 0; i < tokens.length;) {
|
|
80
|
+
const status = tokens[i++] ?? '';
|
|
81
|
+
const code = status[0]?.toUpperCase();
|
|
82
|
+
if (code === 'R' || code === 'C') {
|
|
83
|
+
const previousPath = tokens[i++];
|
|
84
|
+
const nextPath = tokens[i++];
|
|
85
|
+
if (previousPath && nextPath) {
|
|
86
|
+
changes.push({
|
|
87
|
+
status,
|
|
88
|
+
path: normalizeGitPath(nextPath),
|
|
89
|
+
previousPath: normalizeGitPath(previousPath),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const changedPath = tokens[i++];
|
|
95
|
+
if (status && changedPath) {
|
|
96
|
+
changes.push({ status, path: normalizeGitPath(changedPath) });
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return changes;
|
|
100
|
+
};
|
|
101
|
+
export const listChangedPathsBetweenCommits = (repoPath, fromRef, toRef) => {
|
|
102
|
+
if (!fromRef || !toRef || fromRef === toRef)
|
|
103
|
+
return [];
|
|
104
|
+
try {
|
|
105
|
+
const stdout = execFileSync('git', ['diff', '--name-status', '-z', `${fromRef}..${toRef}`], {
|
|
106
|
+
cwd: repoPath,
|
|
107
|
+
encoding: 'utf8',
|
|
108
|
+
maxBuffer: 20 * 1024 * 1024,
|
|
109
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
110
|
+
});
|
|
111
|
+
return parseGitNameStatus(stdout);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
export const isGeneratedAgentContextPath = (filePath) => {
|
|
118
|
+
const normalized = normalizeGitPath(filePath).toLowerCase();
|
|
119
|
+
const basename = path.posix.basename(normalized);
|
|
120
|
+
return (GENERATED_AGENT_CONTEXT_PATHS.has(basename) ||
|
|
121
|
+
GENERATED_AGENT_CONTEXT_PREFIXES.some((prefix) => normalized.startsWith(prefix)));
|
|
122
|
+
};
|
|
123
|
+
export const isGraphContentPath = (filePath) => {
|
|
124
|
+
const normalized = normalizeGitPath(filePath);
|
|
125
|
+
const basename = path.posix.basename(normalized);
|
|
126
|
+
const lowerBasename = basename.toLowerCase();
|
|
127
|
+
if (isGeneratedAgentContextPath(normalized))
|
|
128
|
+
return false;
|
|
129
|
+
if (IGNORE_CONTROL_FILES.has(lowerBasename))
|
|
130
|
+
return true;
|
|
131
|
+
if (shouldIgnorePath(normalized))
|
|
132
|
+
return false;
|
|
133
|
+
if (getLanguageFromFilename(normalized) !== null)
|
|
134
|
+
return true;
|
|
135
|
+
const ext = path.posix.extname(lowerBasename);
|
|
136
|
+
if (MARKDOWN_EXTENSIONS.has(ext))
|
|
137
|
+
return true;
|
|
138
|
+
if (GRAPH_CONFIG_BASENAMES.has(lowerBasename))
|
|
139
|
+
return true;
|
|
140
|
+
return GRAPH_CONFIG_PATTERNS.some((pattern) => pattern.test(basename));
|
|
141
|
+
};
|
|
142
|
+
export const changedPathAffectsGraph = (change) => {
|
|
143
|
+
const statusCode = change.status[0]?.toUpperCase();
|
|
144
|
+
const paths = [change.path, change.previousPath].filter((p) => Boolean(p));
|
|
145
|
+
if (paths.some(isGraphContentPath))
|
|
146
|
+
return true;
|
|
147
|
+
// Add/delete/rename/copy can change File/Folder structure even when content
|
|
148
|
+
// is not parsed. Ignored or generated-agent paths are outside the index.
|
|
149
|
+
if (statusCode === 'A' || statusCode === 'D' || statusCode === 'R' || statusCode === 'C') {
|
|
150
|
+
return paths.some((p) => !isGeneratedAgentContextPath(p) && !shouldIgnorePath(p));
|
|
151
|
+
}
|
|
152
|
+
// Modified non-code/non-doc files keep the same path and are not read by the
|
|
153
|
+
// graph pipeline, so the existing graph can be reused.
|
|
154
|
+
if (statusCode === 'M' || statusCode === 'T')
|
|
155
|
+
return false;
|
|
156
|
+
// Unknown git status: rebuild rather than risk stale graph state.
|
|
157
|
+
return true;
|
|
158
|
+
};
|
|
159
|
+
export const getGraphRelevantChangedPaths = (changes) => changes.filter(changedPathAffectsGraph);
|
|
160
|
+
export const getAnalyzeConfigRebuildReason = (existingMeta, options) => {
|
|
161
|
+
const existingCompress = existingMeta.compress ?? 'none';
|
|
162
|
+
if (options.compress && options.compress !== existingCompress) {
|
|
163
|
+
return `requested compression changed from ${existingCompress} to ${options.compress}`;
|
|
164
|
+
}
|
|
165
|
+
if (options.embeddings && (existingMeta.stats?.embeddings ?? 0) === 0) {
|
|
166
|
+
return 'embeddings were requested but the existing index has no vectors';
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
};
|
|
170
|
+
const formatChangeForLog = (change) => change.previousPath ? `${change.previousPath} -> ${change.path}` : change.path;
|
|
171
|
+
const buildReusedMeta = (existingMeta, repoPath, currentCommit) => ({
|
|
172
|
+
...existingMeta,
|
|
173
|
+
repoPath,
|
|
174
|
+
lastCommit: currentCommit,
|
|
175
|
+
indexedAt: new Date().toISOString(),
|
|
176
|
+
schemaVersion: INDEX_SCHEMA_VERSION,
|
|
177
|
+
remoteUrl: hasGitDir(repoPath) ? getRemoteUrl(repoPath) : existingMeta.remoteUrl,
|
|
178
|
+
});
|
|
179
|
+
const pathExists = async (targetPath) => {
|
|
180
|
+
try {
|
|
181
|
+
await fs.stat(targetPath);
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
41
188
|
// ---------------------------------------------------------------------------
|
|
42
189
|
// Main orchestrator
|
|
43
190
|
// ---------------------------------------------------------------------------
|
|
@@ -135,9 +282,18 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
135
282
|
// end-to-end yet, so the supported migration path is re-analyze via a fresh
|
|
136
283
|
// CREATE NODE TABLE.
|
|
137
284
|
const schemaUpToDate = !!existingMeta && (existingMeta.schemaVersion ?? 0) >= INDEX_SCHEMA_VERSION;
|
|
285
|
+
const existingCgdbPresent = existingMeta ? await pathExists(cgdbPath) : false;
|
|
286
|
+
const storageRebuildReason = existingMeta && schemaUpToDate && !existingCgdbPresent
|
|
287
|
+
? 'graph database files are missing'
|
|
288
|
+
: null;
|
|
289
|
+
const configRebuildReason = storageRebuildReason ??
|
|
290
|
+
(existingMeta && schemaUpToDate && !options.force
|
|
291
|
+
? getAnalyzeConfigRebuildReason(existingMeta, options)
|
|
292
|
+
: null);
|
|
138
293
|
if (existingMeta &&
|
|
139
294
|
schemaUpToDate &&
|
|
140
295
|
!options.force &&
|
|
296
|
+
!configRebuildReason &&
|
|
141
297
|
existingMeta.lastCommit === currentCommit) {
|
|
142
298
|
// Non-git folders have currentCommit = '' — always rebuild since we can't detect changes
|
|
143
299
|
if (currentCommit !== '') {
|
|
@@ -149,6 +305,51 @@ export async function runFullAnalysis(repoPath, options, callbacks) {
|
|
|
149
305
|
};
|
|
150
306
|
}
|
|
151
307
|
}
|
|
308
|
+
if (existingMeta && schemaUpToDate && !options.force && configRebuildReason) {
|
|
309
|
+
log(`Re-analyzing: ${configRebuildReason}.`);
|
|
310
|
+
}
|
|
311
|
+
if (existingMeta &&
|
|
312
|
+
schemaUpToDate &&
|
|
313
|
+
!options.force &&
|
|
314
|
+
!configRebuildReason &&
|
|
315
|
+
currentCommit !== '' &&
|
|
316
|
+
existingMeta.lastCommit !== currentCommit) {
|
|
317
|
+
const changedPaths = listChangedPathsBetweenCommits(repoPath, existingMeta.lastCommit, currentCommit);
|
|
318
|
+
if (changedPaths) {
|
|
319
|
+
const graphRelevantChanges = getGraphRelevantChangedPaths(changedPaths);
|
|
320
|
+
if (graphRelevantChanges.length === 0) {
|
|
321
|
+
const reusedMeta = buildReusedMeta(existingMeta, repoPath, currentCommit);
|
|
322
|
+
await saveMeta(storagePath, reusedMeta);
|
|
323
|
+
const projectName = await registerRepo(repoPath, reusedMeta, {
|
|
324
|
+
name: options.registryName,
|
|
325
|
+
allowDuplicateName: options.allowDuplicateName,
|
|
326
|
+
});
|
|
327
|
+
if (hasGitDir(repoPath)) {
|
|
328
|
+
await addToGitignore(repoPath);
|
|
329
|
+
}
|
|
330
|
+
const reuseReason = `Smart analyze reused the existing graph; ${changedPaths.length} changed ` +
|
|
331
|
+
`file(s) did not affect indexed code, docs, config, or file structure.`;
|
|
332
|
+
log(reuseReason);
|
|
333
|
+
progress('done', 100, 'Existing graph reused');
|
|
334
|
+
return {
|
|
335
|
+
repoName: projectName,
|
|
336
|
+
repoPath,
|
|
337
|
+
stats: reusedMeta.stats ?? {},
|
|
338
|
+
alreadyUpToDate: true,
|
|
339
|
+
reusedExistingIndex: true,
|
|
340
|
+
reuseReason,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
const preview = graphRelevantChanges.slice(0, 5).map(formatChangeForLog).join(', ');
|
|
344
|
+
const suffix = graphRelevantChanges.length > 5 ? ', ...' : '';
|
|
345
|
+
log(`Smart analyze: ${graphRelevantChanges.length} indexed change(s) require rebuild` +
|
|
346
|
+
(preview ? ` (${preview}${suffix})` : '') +
|
|
347
|
+
'.');
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
log('Smart analyze: could not inspect git diff; rebuilding.');
|
|
351
|
+
}
|
|
352
|
+
}
|
|
152
353
|
if (existingMeta && !schemaUpToDate) {
|
|
153
354
|
log(`Index schema version ${existingMeta.schemaVersion ?? '<missing>'} is older than ` +
|
|
154
355
|
`${INDEX_SCHEMA_VERSION} (FeatureCluster context-pack schema). ` +
|
|
@@ -111,8 +111,16 @@ export const formatHybridResults = (results) => {
|
|
|
111
111
|
* The semanticSearch function is injected to keep this module environment-agnostic.
|
|
112
112
|
*/
|
|
113
113
|
export const hybridSearch = async (query, limit, executeQuery, semanticSearch) => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
114
|
+
const bm25Promise = searchFTSFromCgdb(query, limit);
|
|
115
|
+
// Start semantic work immediately, but gate its DB calls behind BM25.
|
|
116
|
+
// semanticSearch performs embedding before it calls executeQuery, so this
|
|
117
|
+
// overlaps CPU/model work with BM25 while avoiding concurrent queries on the
|
|
118
|
+
// singleton LadybugDB connection used by CLI/HTTP paths.
|
|
119
|
+
const executeAfterBm25 = async (cypher) => {
|
|
120
|
+
await bm25Promise;
|
|
121
|
+
return executeQuery(cypher);
|
|
122
|
+
};
|
|
123
|
+
const semanticPromise = semanticSearch(executeAfterBm25, query, limit);
|
|
124
|
+
const [bm25Results, semanticResults] = await Promise.all([bm25Promise, semanticPromise]);
|
|
117
125
|
return mergeWithRRF(bm25Results, semanticResults, limit);
|
|
118
126
|
};
|
package/dist/mcp/resources.js
CHANGED
|
@@ -685,8 +685,8 @@ async function getSetupResource(backend) {
|
|
|
685
685
|
'',
|
|
686
686
|
'## Cross-platform commands',
|
|
687
687
|
'',
|
|
688
|
-
'- Use `npx @codragraph/cli
|
|
689
|
-
'- Prefer `npm --prefix <package> <script>` from repo root for package checks instead of shell-specific `cd dir && ...` chains.',
|
|
688
|
+
'- Use `npx @codragraph/cli ...`, `bunx @codragraph/cli ...`, or `codragraph ...` in Windows PowerShell, macOS bash/zsh, and Linux shells.',
|
|
689
|
+
'- Prefer `npm --prefix <package> <script>` or `bun run --filter <workspace> <script>` from repo root for package checks instead of shell-specific `cd dir && ...` chains.',
|
|
690
690
|
];
|
|
691
691
|
sections.push(lines.join('\n'));
|
|
692
692
|
}
|
package/dist/server/api.d.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import express from 'express';
|
|
11
11
|
import { type GraphNode, type GraphRelationship } from '../_shared/index.js';
|
|
12
|
+
import { type WebDashboardMode } from './web-dashboard.js';
|
|
12
13
|
/**
|
|
13
14
|
* Determine whether an HTTP Origin header value is allowed by CORS policy.
|
|
14
15
|
*
|
|
@@ -20,13 +21,16 @@ import { type GraphNode, type GraphRelationship } from '../_shared/index.js';
|
|
|
20
21
|
* 10.0.0.0/8 → 10.x.x.x
|
|
21
22
|
* 172.16.0.0/12 → 172.16.x.x – 172.31.x.x
|
|
22
23
|
* 192.168.0.0/16 → 192.168.x.x
|
|
23
|
-
* - https://codragraph.vercel.app
|
|
24
|
+
* - Hosted CodraGraph web UI — defaults to https://codragraph.vercel.app
|
|
24
25
|
*
|
|
25
26
|
* @param origin - The value of the HTTP `Origin` request header, or `undefined`
|
|
26
27
|
* when the header is absent (non-browser request).
|
|
27
28
|
* @returns `true` if the origin is allowed, `false` otherwise.
|
|
28
29
|
*/
|
|
29
30
|
export declare const isAllowedOrigin: (origin: string | undefined) => boolean;
|
|
31
|
+
export interface CreateServerOptions {
|
|
32
|
+
web?: WebDashboardMode;
|
|
33
|
+
}
|
|
30
34
|
type GraphStreamRecord = {
|
|
31
35
|
type: 'node';
|
|
32
36
|
data: GraphNode;
|
|
@@ -41,7 +45,15 @@ export declare class ClientDisconnectedError extends Error {
|
|
|
41
45
|
constructor();
|
|
42
46
|
}
|
|
43
47
|
export declare const isIgnorableGraphQueryError: (err: unknown) => boolean;
|
|
48
|
+
export interface GraphStoreErrorResponse {
|
|
49
|
+
error: string;
|
|
50
|
+
code: 'GRAPHSTORE_CORRUPT';
|
|
51
|
+
operation: string;
|
|
52
|
+
recovery: string[];
|
|
53
|
+
}
|
|
54
|
+
export declare const isGraphStoreCorruptionError: (err: unknown) => boolean;
|
|
55
|
+
export declare const getGraphStoreErrorResponse: (err: unknown, operation: string) => GraphStoreErrorResponse | null;
|
|
44
56
|
export declare const writeNdjsonRecord: (res: express.Response, record: GraphStreamRecord, signal?: AbortSignal) => Promise<void>;
|
|
45
57
|
export declare const streamGraphNdjson: (res: express.Response, includeContent?: boolean, signal?: AbortSignal) => Promise<void>;
|
|
46
|
-
export declare const createServer: (port: number, host?: string) => Promise<void>;
|
|
58
|
+
export declare const createServer: (port: number, host?: string, options?: CreateServerOptions) => Promise<void>;
|
|
47
59
|
export {};
|
package/dist/server/api.js
CHANGED
|
@@ -22,7 +22,8 @@ import { hybridSearch } from '../core/search/hybrid-search.js';
|
|
|
22
22
|
// Embedding imports are lazy (dynamic import) to avoid loading onnxruntime-node
|
|
23
23
|
// at server startup — crashes on unsupported Node ABI versions (#89)
|
|
24
24
|
import { LocalBackend } from '../mcp/local/local-backend.js';
|
|
25
|
-
import { mountMCPEndpoints } from './mcp-http.js';
|
|
25
|
+
import { getMcpHttpRouteGuidance, mountMCPEndpoints } from './mcp-http.js';
|
|
26
|
+
import { HOSTED_WEB_APP_URL, getWebDashboardInfo, mountWebDashboard, } from './web-dashboard.js';
|
|
26
27
|
import { fork } from 'child_process';
|
|
27
28
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
28
29
|
import { JobManager } from './analyze-job.js';
|
|
@@ -40,7 +41,7 @@ const pkg = _require('../../package.json');
|
|
|
40
41
|
* 10.0.0.0/8 → 10.x.x.x
|
|
41
42
|
* 172.16.0.0/12 → 172.16.x.x – 172.31.x.x
|
|
42
43
|
* 192.168.0.0/16 → 192.168.x.x
|
|
43
|
-
* - https://codragraph.vercel.app
|
|
44
|
+
* - Hosted CodraGraph web UI — defaults to https://codragraph.vercel.app
|
|
44
45
|
*
|
|
45
46
|
* @param origin - The value of the HTTP `Origin` request header, or `undefined`
|
|
46
47
|
* when the header is absent (non-browser request).
|
|
@@ -57,7 +58,7 @@ export const isAllowedOrigin = (origin) => {
|
|
|
57
58
|
origin === 'http://127.0.0.1' ||
|
|
58
59
|
origin.startsWith('http://[::1]:') ||
|
|
59
60
|
origin === 'http://[::1]' ||
|
|
60
|
-
origin ===
|
|
61
|
+
origin === HOSTED_WEB_APP_URL) {
|
|
61
62
|
return true;
|
|
62
63
|
}
|
|
63
64
|
// RFC 1918 private network ranges — allow any port on these hosts.
|
|
@@ -104,6 +105,30 @@ export const isIgnorableGraphQueryError = (err) => {
|
|
|
104
105
|
message.includes('not found') ||
|
|
105
106
|
message.includes('No table named'));
|
|
106
107
|
};
|
|
108
|
+
export const isGraphStoreCorruptionError = (err) => {
|
|
109
|
+
const message = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
110
|
+
return (message.includes('wal checksum') ||
|
|
111
|
+
(message.includes('wal') && message.includes('corrupt')) ||
|
|
112
|
+
(message.includes('checksum') && message.includes('corrupt')) ||
|
|
113
|
+
message.includes('database disk image is malformed') ||
|
|
114
|
+
message.includes('database image is malformed'));
|
|
115
|
+
};
|
|
116
|
+
export const getGraphStoreErrorResponse = (err, operation) => {
|
|
117
|
+
if (!isGraphStoreCorruptionError(err))
|
|
118
|
+
return null;
|
|
119
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
120
|
+
return {
|
|
121
|
+
error: message || 'Graph store is corrupted',
|
|
122
|
+
code: 'GRAPHSTORE_CORRUPT',
|
|
123
|
+
operation,
|
|
124
|
+
recovery: [
|
|
125
|
+
'Stop overlapping codragraph serve, mcp, analyze, and embedding jobs for this repo.',
|
|
126
|
+
'Retry: npx @codragraph/cli analyze --force',
|
|
127
|
+
'If this repo had embeddings, preserve them with: npx @codragraph/cli analyze --force --embeddings',
|
|
128
|
+
'Only after approval, use: npx @codragraph/cli clean --force',
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
};
|
|
107
132
|
const ensureStreamIsWritable = (res, signal) => {
|
|
108
133
|
if (signal?.aborted || res.destroyed || res.writableEnded) {
|
|
109
134
|
throw new ClientDisconnectedError();
|
|
@@ -355,6 +380,8 @@ const mountSSEProgress = (app, routePath, jm) => {
|
|
|
355
380
|
});
|
|
356
381
|
};
|
|
357
382
|
const statusFromError = (err) => {
|
|
383
|
+
if (isGraphStoreCorruptionError(err))
|
|
384
|
+
return 503;
|
|
358
385
|
const msg = String(err?.message ?? '');
|
|
359
386
|
if (msg.includes('No indexed repositories') || msg.includes('not found'))
|
|
360
387
|
return 404;
|
|
@@ -371,9 +398,14 @@ const requestedRepo = (req) => {
|
|
|
371
398
|
}
|
|
372
399
|
return undefined;
|
|
373
400
|
};
|
|
374
|
-
export const createServer = async (port, host = '127.0.0.1') => {
|
|
401
|
+
export const createServer = async (port, host = '127.0.0.1', options = {}) => {
|
|
375
402
|
const app = express();
|
|
376
403
|
app.disable('x-powered-by');
|
|
404
|
+
let webDashboard = {
|
|
405
|
+
mode: options.web ?? 'local',
|
|
406
|
+
served: false,
|
|
407
|
+
hostedUrl: HOSTED_WEB_APP_URL,
|
|
408
|
+
};
|
|
377
409
|
// CORS: allow localhost, private/LAN networks, and the deployed site.
|
|
378
410
|
// Non-browser requests (curl, server-to-server) have no origin and are allowed.
|
|
379
411
|
// Disallowed origins get the response without Access-Control-Allow-Origin,
|
|
@@ -520,7 +552,15 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
520
552
|
else {
|
|
521
553
|
launchContext = 'global';
|
|
522
554
|
}
|
|
523
|
-
|
|
555
|
+
const displayHost = host === '::' || host === '0.0.0.0' ? 'localhost' : host;
|
|
556
|
+
const apiBaseUrl = `http://${displayHost}:${port}`;
|
|
557
|
+
res.json({
|
|
558
|
+
version: pkg.version,
|
|
559
|
+
launchContext,
|
|
560
|
+
nodeVersion: process.version,
|
|
561
|
+
mcp: getMcpHttpRouteGuidance(),
|
|
562
|
+
web: getWebDashboardInfo(webDashboard, apiBaseUrl),
|
|
563
|
+
});
|
|
524
564
|
});
|
|
525
565
|
// List all registered repos
|
|
526
566
|
app.get('/api/repos', async (_req, res) => {
|
|
@@ -995,10 +1035,13 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
995
1035
|
if (err instanceof ClientDisconnectedError) {
|
|
996
1036
|
return;
|
|
997
1037
|
}
|
|
1038
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.graph');
|
|
998
1039
|
const message = err.message || 'Failed to build graph';
|
|
999
1040
|
if (res.headersSent) {
|
|
1000
1041
|
try {
|
|
1001
|
-
res.write(JSON.stringify(
|
|
1042
|
+
res.write(JSON.stringify(graphStoreError
|
|
1043
|
+
? { type: 'error', ...graphStoreError }
|
|
1044
|
+
: { type: 'error', error: message }) + '\n');
|
|
1002
1045
|
}
|
|
1003
1046
|
catch {
|
|
1004
1047
|
// Best-effort only after streaming has started.
|
|
@@ -1006,6 +1049,10 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1006
1049
|
res.end();
|
|
1007
1050
|
return;
|
|
1008
1051
|
}
|
|
1052
|
+
if (graphStoreError) {
|
|
1053
|
+
res.status(503).json(graphStoreError);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1009
1056
|
res.status(500).json({ error: message });
|
|
1010
1057
|
}
|
|
1011
1058
|
});
|
|
@@ -1031,6 +1078,11 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1031
1078
|
res.json({ result });
|
|
1032
1079
|
}
|
|
1033
1080
|
catch (err) {
|
|
1081
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.query');
|
|
1082
|
+
if (graphStoreError) {
|
|
1083
|
+
res.status(503).json(graphStoreError);
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1034
1086
|
res.status(500).json({ error: err.message || 'Query failed' });
|
|
1035
1087
|
}
|
|
1036
1088
|
});
|
|
@@ -1050,6 +1102,11 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1050
1102
|
res.json(result);
|
|
1051
1103
|
}
|
|
1052
1104
|
catch (err) {
|
|
1105
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.context');
|
|
1106
|
+
if (graphStoreError) {
|
|
1107
|
+
res.status(503).json(graphStoreError);
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1053
1110
|
res.status(statusFromError(err)).json({ error: err.message || 'Context query failed' });
|
|
1054
1111
|
}
|
|
1055
1112
|
});
|
|
@@ -1070,6 +1127,11 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1070
1127
|
res.json(result);
|
|
1071
1128
|
}
|
|
1072
1129
|
catch (err) {
|
|
1130
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.impact');
|
|
1131
|
+
if (graphStoreError) {
|
|
1132
|
+
res.status(503).json(graphStoreError);
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1073
1135
|
res.status(statusFromError(err)).json({ error: err.message || 'Impact query failed' });
|
|
1074
1136
|
}
|
|
1075
1137
|
});
|
|
@@ -1197,6 +1259,11 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1197
1259
|
res.json({ results });
|
|
1198
1260
|
}
|
|
1199
1261
|
catch (err) {
|
|
1262
|
+
const graphStoreError = getGraphStoreErrorResponse(err, 'api.search');
|
|
1263
|
+
if (graphStoreError) {
|
|
1264
|
+
res.status(503).json(graphStoreError);
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1200
1267
|
res.status(500).json({ error: err.message || 'Search failed' });
|
|
1201
1268
|
}
|
|
1202
1269
|
});
|
|
@@ -1813,6 +1880,7 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1813
1880
|
embedJobManager.cancelJob(req.params.jobId, 'Cancelled by user');
|
|
1814
1881
|
res.json({ id: job.id, status: 'failed', error: 'Cancelled by user' });
|
|
1815
1882
|
});
|
|
1883
|
+
webDashboard = mountWebDashboard(app, { mode: options.web ?? 'local' });
|
|
1816
1884
|
// Global error handler — catch anything the route handlers miss
|
|
1817
1885
|
app.use((err, _req, res, _next) => {
|
|
1818
1886
|
console.error('Unhandled error:', err);
|
|
@@ -1823,7 +1891,22 @@ export const createServer = async (port, host = '127.0.0.1') => {
|
|
|
1823
1891
|
await new Promise((resolve, reject) => {
|
|
1824
1892
|
const server = app.listen(port, host, () => {
|
|
1825
1893
|
const displayHost = host === '::' || host === '0.0.0.0' ? 'localhost' : host;
|
|
1826
|
-
|
|
1894
|
+
const localUrl = `http://${displayHost}:${port}`;
|
|
1895
|
+
console.log(`CodraGraph server running on ${localUrl}`);
|
|
1896
|
+
if (webDashboard.served) {
|
|
1897
|
+
console.log(`Web dashboard: ${localUrl}`);
|
|
1898
|
+
}
|
|
1899
|
+
else if (webDashboard.mode === 'hosted') {
|
|
1900
|
+
console.log(`Hosted dashboard: ${webDashboard.hostedUrl}`);
|
|
1901
|
+
console.log(`Connect it to local API: ${localUrl}`);
|
|
1902
|
+
}
|
|
1903
|
+
else if (webDashboard.mode === 'off') {
|
|
1904
|
+
console.log('Web dashboard disabled (--web off).');
|
|
1905
|
+
}
|
|
1906
|
+
else {
|
|
1907
|
+
console.warn(`Web dashboard not bundled: ${webDashboard.reason}`);
|
|
1908
|
+
console.warn(`Hosted dashboard: ${webDashboard.hostedUrl}`);
|
|
1909
|
+
}
|
|
1827
1910
|
resolve();
|
|
1828
1911
|
});
|
|
1829
1912
|
server.on('error', (err) => reject(err));
|
|
@@ -10,4 +10,26 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import type { Express } from 'express';
|
|
12
12
|
import type { LocalBackend } from '../mcp/local/local-backend.js';
|
|
13
|
+
export declare const MCP_HTTP_ENDPOINT = "/api/mcp";
|
|
14
|
+
export declare const UNSUPPORTED_MCP_REST_EXAMPLE = "/api/mcp/tools/list";
|
|
15
|
+
export interface McpHttpRouteGuidance {
|
|
16
|
+
endpoint: string;
|
|
17
|
+
transport: 'streamable-http';
|
|
18
|
+
note: string;
|
|
19
|
+
unsupportedRestExample: string;
|
|
20
|
+
powershellHealthCheck: string;
|
|
21
|
+
clientInstruction: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const getMcpHttpRouteGuidance: () => McpHttpRouteGuidance;
|
|
24
|
+
export declare const getUnsupportedMcpRestRouteResponse: (path: string) => {
|
|
25
|
+
endpoint: string;
|
|
26
|
+
transport: "streamable-http";
|
|
27
|
+
note: string;
|
|
28
|
+
unsupportedRestExample: string;
|
|
29
|
+
powershellHealthCheck: string;
|
|
30
|
+
clientInstruction: string;
|
|
31
|
+
error: string;
|
|
32
|
+
code: string;
|
|
33
|
+
unsupportedRoute: string;
|
|
34
|
+
};
|
|
13
35
|
export declare function mountMCPEndpoints(app: Express, backend: LocalBackend): () => Promise<void>;
|
package/dist/server/mcp-http.js
CHANGED
|
@@ -11,6 +11,22 @@
|
|
|
11
11
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
12
12
|
import { createMCPServer } from '../mcp/server.js';
|
|
13
13
|
import { randomUUID } from 'crypto';
|
|
14
|
+
export const MCP_HTTP_ENDPOINT = '/api/mcp';
|
|
15
|
+
export const UNSUPPORTED_MCP_REST_EXAMPLE = '/api/mcp/tools/list';
|
|
16
|
+
export const getMcpHttpRouteGuidance = () => ({
|
|
17
|
+
endpoint: MCP_HTTP_ENDPOINT,
|
|
18
|
+
transport: 'streamable-http',
|
|
19
|
+
note: 'HTTP MCP is a protocol endpoint, not a REST tools namespace.',
|
|
20
|
+
unsupportedRestExample: UNSUPPORTED_MCP_REST_EXAMPLE,
|
|
21
|
+
powershellHealthCheck: "Invoke-RestMethod -Uri 'http://127.0.0.1:4747/api/info' -TimeoutSec 10",
|
|
22
|
+
clientInstruction: 'Point an MCP client at http://127.0.0.1:4747/api/mcp using StreamableHTTP.',
|
|
23
|
+
});
|
|
24
|
+
export const getUnsupportedMcpRestRouteResponse = (path) => ({
|
|
25
|
+
error: 'Unsupported MCP HTTP REST route',
|
|
26
|
+
code: 'MCP_HTTP_REST_ROUTE_UNSUPPORTED',
|
|
27
|
+
unsupportedRoute: path,
|
|
28
|
+
...getMcpHttpRouteGuidance(),
|
|
29
|
+
});
|
|
14
30
|
/** Idle sessions are evicted after 30 minutes */
|
|
15
31
|
const SESSION_TTL_MS = 30 * 60 * 1000;
|
|
16
32
|
/** Cleanup sweep runs every 5 minutes */
|
|
@@ -72,7 +88,7 @@ export function mountMCPEndpoints(app, backend) {
|
|
|
72
88
|
});
|
|
73
89
|
}
|
|
74
90
|
};
|
|
75
|
-
app.all(
|
|
91
|
+
app.all(MCP_HTTP_ENDPOINT, (req, res) => {
|
|
76
92
|
void handleMcpRequest(req, res).catch((err) => {
|
|
77
93
|
console.error('MCP HTTP request failed:', err);
|
|
78
94
|
if (res.headersSent)
|
|
@@ -84,6 +100,9 @@ export function mountMCPEndpoints(app, backend) {
|
|
|
84
100
|
});
|
|
85
101
|
});
|
|
86
102
|
});
|
|
103
|
+
app.all(/^\/api\/mcp\/.+/, (req, res) => {
|
|
104
|
+
res.status(200).json(getUnsupportedMcpRestRouteResponse(req.path));
|
|
105
|
+
});
|
|
87
106
|
const cleanup = async () => {
|
|
88
107
|
clearInterval(cleanupTimer);
|
|
89
108
|
const closers = [...sessions.values()].map(async (session) => {
|
|
@@ -95,6 +114,6 @@ export function mountMCPEndpoints(app, backend) {
|
|
|
95
114
|
sessions.clear();
|
|
96
115
|
await Promise.allSettled(closers);
|
|
97
116
|
};
|
|
98
|
-
console.log(
|
|
117
|
+
console.log(`MCP HTTP endpoint mounted at ${MCP_HTTP_ENDPOINT}`);
|
|
99
118
|
return cleanup;
|
|
100
119
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type Express } from 'express';
|
|
2
|
+
export declare const HOSTED_WEB_APP_URL: string;
|
|
3
|
+
export type WebDashboardMode = 'local' | 'hosted' | 'off';
|
|
4
|
+
export interface WebDashboardMountOptions {
|
|
5
|
+
mode?: WebDashboardMode;
|
|
6
|
+
webAppPath?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface WebDashboardMount {
|
|
9
|
+
mode: WebDashboardMode;
|
|
10
|
+
served: boolean;
|
|
11
|
+
hostedUrl: string;
|
|
12
|
+
localPath?: string;
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface WebDashboardInfo {
|
|
16
|
+
mode: WebDashboardMode;
|
|
17
|
+
served: boolean;
|
|
18
|
+
localUrl: string | null;
|
|
19
|
+
hostedUrl: string;
|
|
20
|
+
apiBaseUrl: string;
|
|
21
|
+
reason?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const normalizeWebDashboardMode: (raw: string | undefined) => WebDashboardMode;
|
|
24
|
+
export declare const getBundledWebAppCandidates: () => string[];
|
|
25
|
+
export declare const hasWebDashboardIndex: (candidate: string) => boolean;
|
|
26
|
+
export declare const resolveBundledWebAppPath: (candidates?: readonly string[]) => string | null;
|
|
27
|
+
export declare const mountWebDashboard: (app: Express, options?: WebDashboardMountOptions) => WebDashboardMount;
|
|
28
|
+
export declare const getWebDashboardInfo: (mount: WebDashboardMount, apiBaseUrl: string) => WebDashboardInfo;
|