@codragraph/cli 2.1.1 → 2.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/README.md +36 -9
  2. package/dist/cli/ai-context.js +298 -1
  3. package/dist/cli/analyze.js +19 -2
  4. package/dist/cli/index.js +33 -12
  5. package/dist/cli/serve.d.ts +1 -0
  6. package/dist/cli/serve.js +3 -1
  7. package/dist/cli/setup.js +36 -19
  8. package/dist/cli/status.d.ts +13 -0
  9. package/dist/cli/status.js +99 -0
  10. package/dist/cli/tool.js +73 -33
  11. package/dist/config/ignore-service.js +3 -0
  12. package/dist/core/cgdb/pool-adapter.js +130 -20
  13. package/dist/core/graphstore/cgdb-row-source.js +3 -2
  14. package/dist/core/group/bridge-db.js +42 -10
  15. package/dist/core/ingestion/parsing-processor.js +7 -1
  16. package/dist/core/ingestion/pipeline-phases/parse-impl.js +4 -0
  17. package/dist/core/ingestion/workers/parse-worker.js +1 -1
  18. package/dist/core/ingestion/workers/worker-pool.d.ts +14 -1
  19. package/dist/core/ingestion/workers/worker-pool.js +33 -17
  20. package/dist/core/run-analyze.d.ts +20 -0
  21. package/dist/core/run-analyze.js +225 -1
  22. package/dist/core/search/bm25-index.d.ts +0 -11
  23. package/dist/core/search/bm25-index.js +7 -84
  24. package/dist/core/search/hybrid-search.js +11 -3
  25. package/dist/mcp/local/local-backend.d.ts +2 -0
  26. package/dist/mcp/local/local-backend.js +235 -18
  27. package/dist/mcp/resources.js +2 -2
  28. package/dist/server/api.d.ts +14 -2
  29. package/dist/server/api.js +90 -7
  30. package/dist/server/mcp-http.d.ts +22 -0
  31. package/dist/server/mcp-http.js +21 -2
  32. package/dist/server/web-dashboard.d.ts +28 -0
  33. package/dist/server/web-dashboard.js +61 -0
  34. package/dist/web/assets/agent-D5lb0zXz.js +1089 -0
  35. package/dist/web/assets/architectureDiagram-EMZXCZ2Q-CZtc99v_.js +36 -0
  36. package/dist/web/assets/blockDiagram-IGV67L2C-BtoUp-6Y.js +132 -0
  37. package/dist/web/assets/c4Diagram-DFAF54RM-C4Hl3J2U.js +10 -0
  38. package/dist/web/assets/chunk-3GS5O3IE-DkUjU0WD.js +231 -0
  39. package/dist/web/assets/chunk-3YCYZ6SJ-CQkVgT_z.js +1 -0
  40. package/dist/web/assets/chunk-7RZVMHOQ-BitYcNVR.js +338 -0
  41. package/dist/web/assets/chunk-AEOMTBSW-BgTIXPsY.js +1 -0
  42. package/dist/web/assets/chunk-H3VCZNTA-Cx5XV_aC.js +13 -0
  43. package/dist/web/assets/chunk-HN6EAY2L-BBnyTNdB.js +1 -0
  44. package/dist/web/assets/chunk-KSICW3F5-BYzvDLNI.js +15 -0
  45. package/dist/web/assets/chunk-O5ABG6QK-dHwHzA6n.js +1 -0
  46. package/dist/web/assets/chunk-PK6DOVAG-CvsEnugt.js +206 -0
  47. package/dist/web/assets/chunk-RWUO3TPN-BgRTY0_k.js +1 -0
  48. package/dist/web/assets/chunk-TBF5ZNIQ-DL5stGM1.js +1 -0
  49. package/dist/web/assets/chunk-TU3PZOEN-RLyvLcv-.js +1 -0
  50. package/dist/web/assets/classDiagram-PPOCWD7C-DTr8QIOf.js +1 -0
  51. package/dist/web/assets/classDiagram-v2-23LJLIIU-DTr8QIOf.js +1 -0
  52. package/dist/web/assets/context-builder-22jU3V56.js +16 -0
  53. package/dist/web/assets/cose-bilkent-PNC4W37J-DVhePRYg.js +1 -0
  54. package/dist/web/assets/dagre-E77IOHMT-Dzx0A6ZU.js +4 -0
  55. package/dist/web/assets/diagram-H7BISOXX-CC9pRew1.js +43 -0
  56. package/dist/web/assets/diagram-JC5VWROH-Bau_i9tf.js +24 -0
  57. package/dist/web/assets/diagram-LXUTUG65-D9_FM2Gt.js +10 -0
  58. package/dist/web/assets/diagram-WEHSV5V5-BMlayouL.js +24 -0
  59. package/dist/web/assets/erDiagram-GCSMX5X6-C3dhDFA8.js +85 -0
  60. package/dist/web/assets/flowDiagram-OTCZ4VVT-CWSFWmhr.js +162 -0
  61. package/dist/web/assets/ganttDiagram-MUNLMDZQ-D3a67Yol.js +292 -0
  62. package/dist/web/assets/gitGraphDiagram-3HKGZ4G3-7jmry-vM.js +106 -0
  63. package/dist/web/assets/index-BgeqpYgd.js +1415 -0
  64. package/dist/web/assets/index-CT0GtFLZ.css +1 -0
  65. package/dist/web/assets/infoDiagram-MN7RKWGX-G7lhP0Ib.js +2 -0
  66. package/dist/web/assets/ishikawaDiagram-YMYX4NHK-DUoJvNP2.js +70 -0
  67. package/dist/web/assets/journeyDiagram-SO5T7YLQ-RMFPNNqz.js +139 -0
  68. package/dist/web/assets/kanban-definition-LJHFXRCJ-BzpDs1K9.js +89 -0
  69. package/dist/web/assets/katex-GD7MH7QM-DBQvrix-.js +261 -0
  70. package/dist/web/assets/mindmap-definition-2EUWGEK5-Bk0O4roa.js +96 -0
  71. package/dist/web/assets/pieDiagram-3IATQBI2-DKU7kpgS.js +30 -0
  72. package/dist/web/assets/quadrantDiagram-E256RVCF-BY0TGWCS.js +7 -0
  73. package/dist/web/assets/requirementDiagram-M5DCFWZL-DLHOVTSv.js +84 -0
  74. package/dist/web/assets/sankeyDiagram-L3NBLAOT-DVMj5rX2.js +10 -0
  75. package/dist/web/assets/sequenceDiagram-ZOUHS735-CJC73bV-.js +157 -0
  76. package/dist/web/assets/stateDiagram-MLPALWAM-BCFyESls.js +1 -0
  77. package/dist/web/assets/stateDiagram-v2-B5LQ5ZB2-DahzzIca.js +1 -0
  78. package/dist/web/assets/timeline-definition-5SPVSISX-TRSDRgPw.js +120 -0
  79. package/dist/web/assets/vennDiagram-IE5QUKF5-DNy7HRBM.js +34 -0
  80. package/dist/web/assets/wardley-RL74JXVD-BCRCBASE-B-eZEzf9.js +161 -0
  81. package/dist/web/assets/wardleyDiagram-XU3VSMPF-BP-r1xzR.js +20 -0
  82. package/dist/web/assets/xychartDiagram-ZHJ5623Y-Dr9r7a35.js +7 -0
  83. package/dist/web/codragraph-logo-512.png +0 -0
  84. package/dist/web/codragraph-logo.png +0 -0
  85. package/dist/web/favicon.png +0 -0
  86. package/dist/web/index.html +36 -0
  87. package/hooks/claude/codragraph-hook.cjs +18 -110
  88. package/hooks/claude/pre-tool-use.sh +6 -1
  89. package/package.json +3 -1
  90. package/scripts/build.js +62 -4
  91. package/scripts/patch-tree-sitter-swift.cjs +0 -1
  92. package/skills/codragraph-cli.md +1 -1
  93. package/vendor/leiden/index.cjs +272 -285
  94. package/vendor/leiden/utils.cjs +264 -274
  95. package/dist/_shared/lbug/schema-constants.d.ts +0 -16
  96. package/dist/_shared/lbug/schema-constants.d.ts.map +0 -1
  97. package/dist/_shared/lbug/schema-constants.js +0 -67
  98. package/dist/_shared/lbug/schema-constants.js.map +0 -1
  99. package/dist/core/graphstore/lbug-row-source.d.ts +0 -19
  100. package/dist/core/graphstore/lbug-row-source.js +0 -141
  101. package/dist/core/lbug/content-read.d.ts +0 -46
  102. package/dist/core/lbug/content-read.js +0 -64
  103. package/dist/core/lbug/csv-generator.d.ts +0 -29
  104. package/dist/core/lbug/csv-generator.js +0 -492
  105. package/dist/core/lbug/lbug-adapter.d.ts +0 -176
  106. package/dist/core/lbug/lbug-adapter.js +0 -1320
  107. package/dist/core/lbug/pool-adapter.d.ts +0 -93
  108. package/dist/core/lbug/pool-adapter.js +0 -550
  109. package/dist/core/lbug/schema.d.ts +0 -62
  110. package/dist/core/lbug/schema.js +0 -502
  111. package/dist/mcp/core/lbug-adapter.d.ts +0 -5
  112. package/dist/mcp/core/lbug-adapter.js +0 -5
@@ -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 — the deployed CodraGraph web UI
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 === 'https://codragraph.vercel.app') {
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
- res.json({ version: pkg.version, launchContext, nodeVersion: process.version });
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({ type: 'error', error: message }) + '\n');
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
- console.log(`CodraGraph server running on http://${displayHost}:${port}`);
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>;
@@ -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('/api/mcp', (req, res) => {
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('MCP HTTP endpoints mounted at /api/mcp');
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;
@@ -0,0 +1,61 @@
1
+ import express from 'express';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ export const HOSTED_WEB_APP_URL = process.env.CODRAGRAPH_WEB_URL || 'https://codragraph.vercel.app';
6
+ const moduleDir = path.dirname(fileURLToPath(import.meta.url));
7
+ export const normalizeWebDashboardMode = (raw) => {
8
+ const value = (raw ?? 'local').trim().toLowerCase();
9
+ if (value === 'local' || value === 'hosted' || value === 'off')
10
+ return value;
11
+ throw new Error(`Invalid web dashboard mode "${raw}". Use one of: local, hosted, off.`);
12
+ };
13
+ export const getBundledWebAppCandidates = () => [
14
+ // Published package: packages/core/dist/server/*.js -> packages/core/dist/web
15
+ path.resolve(moduleDir, '..', 'web'),
16
+ // Monorepo/dev fallback after running `npm --prefix apps/web run build`.
17
+ path.resolve(moduleDir, '..', '..', '..', '..', 'apps', 'web', 'dist'),
18
+ ];
19
+ export const hasWebDashboardIndex = (candidate) => {
20
+ try {
21
+ return fs.statSync(path.join(candidate, 'index.html')).isFile();
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ };
27
+ export const resolveBundledWebAppPath = (candidates = getBundledWebAppCandidates()) => {
28
+ for (const candidate of candidates) {
29
+ if (hasWebDashboardIndex(candidate))
30
+ return candidate;
31
+ }
32
+ return null;
33
+ };
34
+ export const mountWebDashboard = (app, options = {}) => {
35
+ const mode = options.mode ?? 'local';
36
+ if (mode !== 'local') {
37
+ return { mode, served: false, hostedUrl: HOSTED_WEB_APP_URL };
38
+ }
39
+ const webAppPath = options.webAppPath ?? resolveBundledWebAppPath();
40
+ if (!webAppPath || !hasWebDashboardIndex(webAppPath)) {
41
+ return {
42
+ mode,
43
+ served: false,
44
+ hostedUrl: HOSTED_WEB_APP_URL,
45
+ reason: 'Bundled web dashboard not found. Rebuild the package or use --web hosted.',
46
+ };
47
+ }
48
+ app.use(express.static(webAppPath, { index: false }));
49
+ app.get(/^\/(?!api(?:\/|$)).*/, (_req, res) => {
50
+ res.sendFile(path.join(webAppPath, 'index.html'));
51
+ });
52
+ return { mode, served: true, hostedUrl: HOSTED_WEB_APP_URL, localPath: webAppPath };
53
+ };
54
+ export const getWebDashboardInfo = (mount, apiBaseUrl) => ({
55
+ mode: mount.mode,
56
+ served: mount.served,
57
+ localUrl: mount.served ? apiBaseUrl : null,
58
+ hostedUrl: mount.hostedUrl,
59
+ apiBaseUrl,
60
+ reason: mount.reason,
61
+ });