modular-studio 1.0.6 → 1.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.
Files changed (198) hide show
  1. package/dist/assets/Badge-Bsy2H_p2.js +1 -0
  2. package/dist/assets/GraphPanel-D4X3faxA.js +47 -0
  3. package/dist/assets/{Input-ndEGQSgx.js → Input-Dyb88Erk.js} +1 -1
  4. package/dist/assets/KnowledgeTab-BccWz7Np.js +5 -0
  5. package/dist/assets/MemoryTab-Y_66cE01.js +16 -0
  6. package/dist/assets/QualificationTab-Dm9dEIpM.js +1 -0
  7. package/dist/assets/ReviewTab-BrfXSyyf.js +104 -0
  8. package/dist/assets/{Section-CgmwAj_2.js → Section-68XDCFTl.js} +1 -1
  9. package/dist/assets/TestTab-CLKRT63X.js +42 -0
  10. package/dist/assets/{ToolsTab-C10Ulm8b.js → ToolsTab-xumi9Uds.js} +1 -1
  11. package/dist/assets/icons-CS8RUPBi.js +1 -0
  12. package/dist/assets/index-B2bm0161.css +1 -0
  13. package/dist/assets/index-C626nWuA.js +422 -0
  14. package/dist/assets/services-BDk6yY4o.js +369 -0
  15. package/dist/index.html +4 -4
  16. package/dist-server/bin/modular-mcp.js +1 -0
  17. package/dist-server/server/index.d.ts.map +1 -1
  18. package/dist-server/server/index.js +30 -0
  19. package/dist-server/server/mcp/manager.d.ts +3 -0
  20. package/dist-server/server/mcp/manager.d.ts.map +1 -1
  21. package/dist-server/server/mcp/manager.js +64 -3
  22. package/dist-server/server/migrations/index.d.ts +11 -0
  23. package/dist-server/server/migrations/index.d.ts.map +1 -0
  24. package/dist-server/server/migrations/index.js +57 -0
  25. package/dist-server/server/routes/analytics.d.ts +3 -0
  26. package/dist-server/server/routes/analytics.d.ts.map +1 -0
  27. package/dist-server/server/routes/analytics.js +24 -0
  28. package/dist-server/server/routes/connectors/airtable.d.ts +7 -0
  29. package/dist-server/server/routes/connectors/airtable.d.ts.map +1 -0
  30. package/dist-server/server/routes/connectors/airtable.js +119 -0
  31. package/dist-server/server/routes/connectors/confluence.d.ts +7 -0
  32. package/dist-server/server/routes/connectors/confluence.d.ts.map +1 -0
  33. package/dist-server/server/routes/connectors/confluence.js +176 -0
  34. package/dist-server/server/routes/connectors/github.d.ts +7 -0
  35. package/dist-server/server/routes/connectors/github.d.ts.map +1 -0
  36. package/dist-server/server/routes/connectors/github.js +195 -0
  37. package/dist-server/server/routes/connectors/gmail.d.ts +7 -0
  38. package/dist-server/server/routes/connectors/gmail.d.ts.map +1 -0
  39. package/dist-server/server/routes/connectors/gmail.js +115 -0
  40. package/dist-server/server/routes/connectors/google-docs.d.ts +10 -0
  41. package/dist-server/server/routes/connectors/google-docs.d.ts.map +1 -0
  42. package/dist-server/server/routes/connectors/google-docs.js +165 -0
  43. package/dist-server/server/routes/connectors/google-drive.d.ts +7 -0
  44. package/dist-server/server/routes/connectors/google-drive.d.ts.map +1 -0
  45. package/dist-server/server/routes/connectors/google-drive.js +163 -0
  46. package/dist-server/server/routes/connectors/google-sheets.d.ts +7 -0
  47. package/dist-server/server/routes/connectors/google-sheets.d.ts.map +1 -0
  48. package/dist-server/server/routes/connectors/google-sheets.js +90 -0
  49. package/dist-server/server/routes/connectors/hubspot.d.ts +7 -0
  50. package/dist-server/server/routes/connectors/hubspot.d.ts.map +1 -0
  51. package/dist-server/server/routes/connectors/hubspot.js +134 -0
  52. package/dist-server/server/routes/connectors/index.d.ts +6 -0
  53. package/dist-server/server/routes/connectors/index.d.ts.map +1 -0
  54. package/dist-server/server/routes/connectors/index.js +38 -0
  55. package/dist-server/server/routes/connectors/jira.d.ts +7 -0
  56. package/dist-server/server/routes/connectors/jira.d.ts.map +1 -0
  57. package/dist-server/server/routes/connectors/jira.js +151 -0
  58. package/dist-server/server/routes/connectors/linear.d.ts +7 -0
  59. package/dist-server/server/routes/connectors/linear.d.ts.map +1 -0
  60. package/dist-server/server/routes/connectors/linear.js +154 -0
  61. package/dist-server/server/routes/connectors/notion.d.ts +10 -0
  62. package/dist-server/server/routes/connectors/notion.d.ts.map +1 -0
  63. package/dist-server/server/routes/connectors/notion.js +201 -0
  64. package/dist-server/server/routes/connectors/plane.d.ts +10 -0
  65. package/dist-server/server/routes/connectors/plane.d.ts.map +1 -0
  66. package/dist-server/server/routes/connectors/plane.js +189 -0
  67. package/dist-server/server/routes/connectors/shared.d.ts +25 -0
  68. package/dist-server/server/routes/connectors/shared.d.ts.map +1 -0
  69. package/dist-server/server/routes/connectors/shared.js +202 -0
  70. package/dist-server/server/routes/connectors/slack.d.ts +7 -0
  71. package/dist-server/server/routes/connectors/slack.d.ts.map +1 -0
  72. package/dist-server/server/routes/connectors/slack.js +153 -0
  73. package/dist-server/server/routes/cost.d.ts +3 -0
  74. package/dist-server/server/routes/cost.d.ts.map +1 -0
  75. package/dist-server/server/routes/cost.js +113 -0
  76. package/dist-server/server/routes/graph.d.ts +11 -0
  77. package/dist-server/server/routes/graph.d.ts.map +1 -0
  78. package/dist-server/server/routes/graph.js +213 -0
  79. package/dist-server/server/routes/lessons.d.ts.map +1 -1
  80. package/dist-server/server/routes/lessons.js +119 -5
  81. package/dist-server/server/routes/llm.d.ts.map +1 -1
  82. package/dist-server/server/routes/llm.js +85 -18
  83. package/dist-server/server/routes/metaprompt-v2.d.ts +3 -0
  84. package/dist-server/server/routes/metaprompt-v2.d.ts.map +1 -0
  85. package/dist-server/server/routes/metaprompt-v2.js +104 -0
  86. package/dist-server/server/routes/qualification.d.ts.map +1 -1
  87. package/dist-server/server/routes/qualification.js +61 -11
  88. package/dist-server/server/routes/skills-search.d.ts.map +1 -1
  89. package/dist-server/server/routes/skills-search.js +10 -0
  90. package/dist-server/server/routes/tool-analytics.d.ts +3 -0
  91. package/dist-server/server/routes/tool-analytics.d.ts.map +1 -0
  92. package/dist-server/server/routes/tool-analytics.js +47 -0
  93. package/dist-server/server/services/adapters/sqliteAdapter.d.ts +1 -0
  94. package/dist-server/server/services/adapters/sqliteAdapter.d.ts.map +1 -1
  95. package/dist-server/server/services/adapters/sqliteAdapter.js +78 -48
  96. package/dist-server/server/services/credentialStore.d.ts +10 -0
  97. package/dist-server/server/services/credentialStore.d.ts.map +1 -0
  98. package/dist-server/server/services/credentialStore.js +123 -0
  99. package/dist-server/server/services/hindsightClient.d.ts.map +1 -1
  100. package/dist-server/server/services/hindsightClient.js +1 -0
  101. package/dist-server/server/services/lessonExtractor.d.ts +2 -0
  102. package/dist-server/server/services/lessonExtractor.d.ts.map +1 -1
  103. package/dist-server/server/services/lessonExtractor.js +7 -2
  104. package/dist-server/server/services/repoIndexer.d.ts +7 -1
  105. package/dist-server/server/services/repoIndexer.d.ts.map +1 -1
  106. package/dist-server/server/services/repoIndexer.js +295 -94
  107. package/dist-server/server/services/sqliteStore.d.ts +64 -0
  108. package/dist-server/server/services/sqliteStore.d.ts.map +1 -1
  109. package/dist-server/server/services/sqliteStore.js +238 -0
  110. package/dist-server/src/config.d.ts +2 -0
  111. package/dist-server/src/config.d.ts.map +1 -0
  112. package/dist-server/src/config.js +3 -0
  113. package/dist-server/src/graph/db.d.ts +46 -0
  114. package/dist-server/src/graph/db.d.ts.map +1 -0
  115. package/dist-server/src/graph/db.js +241 -0
  116. package/dist-server/src/graph/extractors/code.d.ts +12 -0
  117. package/dist-server/src/graph/extractors/code.d.ts.map +1 -0
  118. package/dist-server/src/graph/extractors/code.js +239 -0
  119. package/dist-server/src/graph/extractors/cross-type.d.ts +16 -0
  120. package/dist-server/src/graph/extractors/cross-type.d.ts.map +1 -0
  121. package/dist-server/src/graph/extractors/cross-type.js +67 -0
  122. package/dist-server/src/graph/extractors/markdown.d.ts +29 -0
  123. package/dist-server/src/graph/extractors/markdown.d.ts.map +1 -0
  124. package/dist-server/src/graph/extractors/markdown.js +224 -0
  125. package/dist-server/src/graph/extractors/yaml.d.ts +15 -0
  126. package/dist-server/src/graph/extractors/yaml.d.ts.map +1 -0
  127. package/dist-server/src/graph/extractors/yaml.js +104 -0
  128. package/dist-server/src/graph/index.d.ts +62 -0
  129. package/dist-server/src/graph/index.d.ts.map +1 -0
  130. package/dist-server/src/graph/index.js +67 -0
  131. package/dist-server/src/graph/packer.d.ts +19 -0
  132. package/dist-server/src/graph/packer.d.ts.map +1 -0
  133. package/dist-server/src/graph/packer.js +134 -0
  134. package/dist-server/src/graph/resolver.d.ts +12 -0
  135. package/dist-server/src/graph/resolver.d.ts.map +1 -0
  136. package/dist-server/src/graph/resolver.js +81 -0
  137. package/dist-server/src/graph/scanner.d.ts +34 -0
  138. package/dist-server/src/graph/scanner.d.ts.map +1 -0
  139. package/dist-server/src/graph/scanner.js +252 -0
  140. package/dist-server/src/graph/traverser.d.ts +17 -0
  141. package/dist-server/src/graph/traverser.d.ts.map +1 -0
  142. package/dist-server/src/graph/traverser.js +185 -0
  143. package/dist-server/src/graph/types.d.ts +117 -0
  144. package/dist-server/src/graph/types.d.ts.map +1 -0
  145. package/dist-server/src/graph/types.js +63 -0
  146. package/dist-server/src/metaprompt/v2/assembler.d.ts +3 -0
  147. package/dist-server/src/metaprompt/v2/assembler.d.ts.map +1 -0
  148. package/dist-server/src/metaprompt/v2/assembler.js +261 -0
  149. package/dist-server/src/metaprompt/v2/context-strategist.d.ts +3 -0
  150. package/dist-server/src/metaprompt/v2/context-strategist.d.ts.map +1 -0
  151. package/dist-server/src/metaprompt/v2/context-strategist.js +173 -0
  152. package/dist-server/src/metaprompt/v2/evaluator.d.ts +3 -0
  153. package/dist-server/src/metaprompt/v2/evaluator.d.ts.map +1 -0
  154. package/dist-server/src/metaprompt/v2/evaluator.js +281 -0
  155. package/dist-server/src/metaprompt/v2/index.d.ts +41 -0
  156. package/dist-server/src/metaprompt/v2/index.d.ts.map +1 -0
  157. package/dist-server/src/metaprompt/v2/index.js +90 -0
  158. package/dist-server/src/metaprompt/v2/parser.d.ts +3 -0
  159. package/dist-server/src/metaprompt/v2/parser.d.ts.map +1 -0
  160. package/dist-server/src/metaprompt/v2/parser.js +138 -0
  161. package/dist-server/src/metaprompt/v2/pattern-selector.d.ts +3 -0
  162. package/dist-server/src/metaprompt/v2/pattern-selector.d.ts.map +1 -0
  163. package/dist-server/src/metaprompt/v2/pattern-selector.js +154 -0
  164. package/dist-server/src/metaprompt/v2/researcher.d.ts +3 -0
  165. package/dist-server/src/metaprompt/v2/researcher.d.ts.map +1 -0
  166. package/dist-server/src/metaprompt/v2/researcher.js +194 -0
  167. package/dist-server/src/metaprompt/v2/tool-discovery.d.ts +74 -0
  168. package/dist-server/src/metaprompt/v2/tool-discovery.d.ts.map +1 -0
  169. package/dist-server/src/metaprompt/v2/tool-discovery.js +290 -0
  170. package/dist-server/src/metaprompt/v2/types.d.ts +154 -0
  171. package/dist-server/src/metaprompt/v2/types.d.ts.map +1 -0
  172. package/dist-server/src/metaprompt/v2/types.js +2 -0
  173. package/dist-server/src/services/contradictionDetector.js +1 -1
  174. package/dist-server/src/services/llmService.d.ts +61 -0
  175. package/dist-server/src/services/llmService.d.ts.map +1 -0
  176. package/dist-server/src/services/llmService.js +222 -0
  177. package/dist-server/src/store/knowledgeBase.d.ts +5 -1
  178. package/dist-server/src/store/knowledgeBase.d.ts.map +1 -1
  179. package/dist-server/src/store/knowledgeBase.js +0 -1
  180. package/dist-server/src/store/mcp-registry.d.ts +29 -0
  181. package/dist-server/src/store/mcp-registry.d.ts.map +1 -0
  182. package/dist-server/src/store/mcp-registry.js +1303 -0
  183. package/dist-server/src/types/registry.types.d.ts +13 -0
  184. package/dist-server/src/types/registry.types.d.ts.map +1 -0
  185. package/dist-server/src/types/registry.types.js +2 -0
  186. package/dist-server/tsconfig.server.tsbuildinfo +1 -1
  187. package/package.json +118 -105
  188. package/scripts/cleanup-worktrees.ps1 +29 -0
  189. package/dist/assets/Badge-DrUmDAXz.js +0 -1
  190. package/dist/assets/KnowledgeTab-CxlC76Rf.js +0 -4
  191. package/dist/assets/MemoryTab-CUScYWs9.js +0 -16
  192. package/dist/assets/QualificationTab-BqnWSQHm.js +0 -1
  193. package/dist/assets/ReviewTab-DKYl6cR9.js +0 -103
  194. package/dist/assets/TestTab-iJ2vCf9l.js +0 -33
  195. package/dist/assets/icons-MKpPNvV8.js +0 -1
  196. package/dist/assets/index-B_ip7Amg.css +0 -1
  197. package/dist/assets/index-gBy3427k.js +0 -143
  198. package/dist/assets/services-CTWXQK6j.js +0 -356
package/dist/index.html CHANGED
@@ -7,12 +7,12 @@
7
7
  <link href="https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-sans/style.min.css" rel="stylesheet" />
8
8
  <link href="https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-mono/style.min.css" rel="stylesheet" />
9
9
  <title>MODULAR — Agent Patchbay</title>
10
- <script type="module" crossorigin src="/assets/index-gBy3427k.js"></script>
10
+ <script type="module" crossorigin src="/assets/index-C626nWuA.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="/assets/vendor-D1h_O76p.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/stores-CeKWz7ou.js">
13
- <link rel="modulepreload" crossorigin href="/assets/services-CTWXQK6j.js">
14
- <link rel="modulepreload" crossorigin href="/assets/icons-MKpPNvV8.js">
15
- <link rel="stylesheet" crossorigin href="/assets/index-B_ip7Amg.css">
13
+ <link rel="modulepreload" crossorigin href="/assets/services-BDk6yY4o.js">
14
+ <link rel="modulepreload" crossorigin href="/assets/icons-CS8RUPBi.js">
15
+ <link rel="stylesheet" crossorigin href="/assets/index-B2bm0161.css">
16
16
  </head>
17
17
  <body style="margin:0;padding:0;height:100vh;overflow:hidden">
18
18
  <div id="root" style="height:100%;overflow:hidden"></div>
@@ -49,6 +49,7 @@ async function main() {
49
49
  case '-h':
50
50
  printUsage();
51
51
  process.exit(0);
52
+ break; // eslint: no-fallthrough
52
53
  case '--transport':
53
54
  if (i + 1 >= args.length) {
54
55
  console.error('Error: --transport requires a value (stdio|sse)');
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../server/index.ts"],"names":[],"mappings":"AAkCA,wBAAgB,SAAS,gDA+FxB;AA+BD,wBAAgB,WAAW,CAAC,IAAI,GAAE,MAAa,sGAwB9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../server/index.ts"],"names":[],"mappings":"AAwCA,wBAAgB,SAAS,gDAwHxB;AA+BD,wBAAgB,WAAW,CAAC,IAAI,GAAE,MAAa,sGAwB9C"}
@@ -29,6 +29,12 @@ import conversationRoutes from './routes/conversations.js';
29
29
  import memoryRoutes from './routes/memory.js';
30
30
  import cacheRoutes from './routes/cache.js';
31
31
  import lessonRoutes from './routes/lessons.js';
32
+ import metapromptV2Routes from './routes/metaprompt-v2.js';
33
+ import graphRoutes from './routes/graph.js';
34
+ import connectorSubRoutes from './routes/connectors/index.js';
35
+ import costRoutes from './routes/cost.js';
36
+ import toolAnalyticsRoutes from './routes/tool-analytics.js';
37
+ import analyticsRoutes from './routes/analytics.js';
32
38
  const __dirname = dirname(fileURLToPath(import.meta.url));
33
39
  export function createApp() {
34
40
  const app = express();
@@ -42,6 +48,24 @@ export function createApp() {
42
48
  ],
43
49
  }));
44
50
  app.use(express.json({ limit: '10mb' }));
51
+ // Request logging middleware — correlation IDs + structured logs
52
+ app.use((req, res, next) => {
53
+ const requestId = crypto.randomUUID();
54
+ res.locals['requestId'] = requestId;
55
+ res.setHeader('X-Request-Id', requestId);
56
+ const start = Date.now();
57
+ res.on('finish', () => {
58
+ const duration = Date.now() - start;
59
+ console.log(JSON.stringify({
60
+ requestId,
61
+ method: req.method,
62
+ path: req.path,
63
+ status: res.statusCode,
64
+ durationMs: duration,
65
+ }));
66
+ });
67
+ next();
68
+ });
45
69
  // Basic security headers (lightweight helmet-like)
46
70
  app.use((_req, res, next) => {
47
71
  res.setHeader('X-Content-Type-Options', 'nosniff');
@@ -93,6 +117,12 @@ export function createApp() {
93
117
  app.use('/api/memory', memoryRoutes);
94
118
  app.use('/api/cache', cacheRoutes);
95
119
  app.use('/api/lessons', lessonRoutes);
120
+ app.use('/api/metaprompt/v2', metapromptV2Routes);
121
+ app.use('/api/graph', graphRoutes);
122
+ app.use('/api/connectors/v2', connectorSubRoutes);
123
+ app.use('/api/cost', costRoutes);
124
+ app.use('/api/tool-analytics', toolAnalyticsRoutes);
125
+ app.use('/api/analytics', analyticsRoutes);
96
126
  // API 404 catch-all — log unmatched API routes for debugging
97
127
  app.use('/api', (_req, res) => {
98
128
  console.warn(`[API 404] ${_req.method} ${_req.originalUrl}`);
@@ -13,6 +13,8 @@ interface McpConnection {
13
13
  }>;
14
14
  connectedAt: number | null;
15
15
  lastError: string | null;
16
+ retryCount: number;
17
+ retryTimer: ReturnType<typeof setTimeout> | null;
16
18
  }
17
19
  export declare class McpManager {
18
20
  private connections;
@@ -23,6 +25,7 @@ export declare class McpManager {
23
25
  private normalizeConfig;
24
26
  addServer(config: McpServerConfig): void;
25
27
  removeServer(id: string): void;
28
+ private scheduleReconnect;
26
29
  getServer(id: string): McpConnection | undefined;
27
30
  listServers(): Array<McpServerConfig & {
28
31
  status: string;
@@ -1 +1 @@
1
- {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../server/mcp/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,UAAU,aAAa;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACvC,MAAM,EAAE,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAC;IAC9D,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC5E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1B;AAED,qBAAa,UAAU;IACrB,OAAO,CAAC,WAAW,CAAoC;IAGvD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAElC;IAGH,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAEhC;IAEH,OAAO,CAAC,kBAAkB;IAyC1B,OAAO,CAAC,sBAAsB;IAc9B,OAAO,CAAC,eAAe;IASvB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;IAkBxC,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAI9B,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIhD,WAAW,IAAI,KAAK,CAAC,eAAe,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAS7G,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IA8F/E,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IAWvF,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc3C,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAW1H;AAED,eAAO,MAAM,UAAU,YAAmB,CAAC"}
1
+ {"version":3,"file":"manager.d.ts","sourceRoot":"","sources":["../../../server/mcp/manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AAEjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,UAAU,aAAa;IACrB,MAAM,EAAE,eAAe,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACvC,MAAM,EAAE,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,OAAO,CAAC;IAC9D,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC5E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,IAAI,CAAC;CAClD;AAMD,qBAAa,UAAU;IACrB,OAAO,CAAC,WAAW,CAAoC;IAGvD,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAElC;IAGH,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAEhC;IAEH,OAAO,CAAC,kBAAkB;IAyC1B,OAAO,CAAC,sBAAsB;IAc9B,OAAO,CAAC,eAAe;IASvB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI;IAoBxC,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAM9B,OAAO,CAAC,iBAAiB;IAyBzB,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIhD,WAAW,IAAI,KAAK,CAAC,eAAe,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;IAS7G,OAAO,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IAkG/E,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;IA8BvF,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB3C,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;QAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE;CAW1H;AAED,eAAO,MAAM,UAAU,YAAmB,CAAC"}
@@ -2,6 +2,9 @@ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
2
2
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
3
3
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
4
4
  import { getToken } from '../services/mcpOAuth.js';
5
+ const MAX_RETRIES = 5;
6
+ const BACKOFF_BASE_MS = 1_000;
7
+ const BACKOFF_MAX_MS = 30_000;
5
8
  export class McpManager {
6
9
  connections = new Map();
7
10
  // Allowlist of safe MCP executables - SECURITY FIX
@@ -80,11 +83,43 @@ export class McpManager {
80
83
  tools: [],
81
84
  connectedAt: null,
82
85
  lastError: null,
86
+ retryCount: 0,
87
+ retryTimer: null,
83
88
  });
84
89
  }
85
90
  removeServer(id) {
91
+ const conn = this.connections.get(id);
92
+ if (conn?.retryTimer)
93
+ clearTimeout(conn.retryTimer);
86
94
  this.connections.delete(id);
87
95
  }
96
+ scheduleReconnect(id) {
97
+ const conn = this.connections.get(id);
98
+ if (!conn)
99
+ return;
100
+ if (conn.retryCount >= MAX_RETRIES) {
101
+ console.error(`[McpManager] "${id}" exceeded max retries (${MAX_RETRIES}), giving up`);
102
+ return;
103
+ }
104
+ const delayMs = Math.min(BACKOFF_BASE_MS * Math.pow(2, conn.retryCount), BACKOFF_MAX_MS);
105
+ conn.retryCount += 1;
106
+ console.log(`[McpManager] "${id}" will reconnect in ${delayMs}ms (attempt ${conn.retryCount}/${MAX_RETRIES})`);
107
+ conn.retryTimer = setTimeout(async () => {
108
+ const c = this.connections.get(id);
109
+ if (!c || c.status === 'connected')
110
+ return;
111
+ try {
112
+ await this.connect(id);
113
+ c.retryCount = 0; // reset on success
114
+ console.log(`[McpManager] "${id}" reconnected successfully`);
115
+ }
116
+ catch (err) {
117
+ const msg = err instanceof Error ? err.message : String(err);
118
+ console.warn(`[McpManager] "${id}" reconnect attempt failed: ${msg}`);
119
+ this.scheduleReconnect(id);
120
+ }
121
+ }, delayMs);
122
+ }
88
123
  getServer(id) {
89
124
  return this.connections.get(id);
90
125
  }
@@ -147,13 +182,15 @@ export class McpManager {
147
182
  env: { ...process.env, ...conn.config.env },
148
183
  });
149
184
  client = new Client({ name: 'modular-studio', version: '1.0.0' });
150
- // Handle process exit
185
+ // Handle process exit — attempt auto-reconnect with exponential backoff
151
186
  transport.onclose = () => {
152
187
  if (conn.status === 'connected') {
153
188
  conn.status = 'error';
154
189
  conn.lastError = 'Process exited unexpectedly';
155
190
  conn.client = null;
156
191
  conn.transport = null;
192
+ console.warn(`[McpManager] "${id}" process exited unexpectedly — scheduling reconnect`);
193
+ this.scheduleReconnect(id);
157
194
  }
158
195
  };
159
196
  await client.connect(transport);
@@ -161,6 +198,11 @@ export class McpManager {
161
198
  conn.client = client;
162
199
  conn.transport = transport;
163
200
  conn.status = 'connected';
201
+ conn.retryCount = 0;
202
+ if (conn.retryTimer) {
203
+ clearTimeout(conn.retryTimer);
204
+ conn.retryTimer = null;
205
+ }
164
206
  conn.tools = tools.map((t) => ({
165
207
  name: t.name,
166
208
  description: t.description,
@@ -182,13 +224,32 @@ export class McpManager {
182
224
  if (!conn.client || conn.status !== 'connected') {
183
225
  throw new Error(`MCP server "${id}" is not connected`);
184
226
  }
185
- const result = await conn.client.callTool({ name: toolName, arguments: args });
186
- return result;
227
+ const TIMEOUT_MS = 30_000;
228
+ const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error(`MCP tool "${toolName}" on server "${id}" timed out after 30s`)), TIMEOUT_MS));
229
+ try {
230
+ const result = await Promise.race([
231
+ conn.client.callTool({ name: toolName, arguments: args }),
232
+ timeoutPromise,
233
+ ]);
234
+ return result;
235
+ }
236
+ catch (err) {
237
+ const msg = err instanceof Error ? err.message : String(err);
238
+ if (msg.includes('timed out')) {
239
+ console.warn(`[McpManager] ${msg}`);
240
+ }
241
+ throw err;
242
+ }
187
243
  }
188
244
  async disconnect(id) {
189
245
  const conn = this.connections.get(id);
190
246
  if (!conn)
191
247
  throw new Error(`MCP server "${id}" not found`);
248
+ if (conn.retryTimer) {
249
+ clearTimeout(conn.retryTimer);
250
+ conn.retryTimer = null;
251
+ }
252
+ conn.retryCount = 0;
192
253
  if (conn.client) {
193
254
  try {
194
255
  await conn.client.close();
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Database migration system for Modular Studio.
3
+ * Tracks schema version and runs pending migrations on startup.
4
+ */
5
+ import type { Database } from 'sql.js';
6
+ /**
7
+ * Run all pending migrations on the given database.
8
+ * Called once during server startup after initDb().
9
+ */
10
+ export declare function runMigrations(db: Database): void;
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../server/migrations/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAC;AAyCvC;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,EAAE,QAAQ,GAAG,IAAI,CAuBhD"}
@@ -0,0 +1,57 @@
1
+ /** All migrations ordered by version number */
2
+ const MIGRATIONS = [
3
+ {
4
+ version: 1,
5
+ description: 'Initial schema (baseline)',
6
+ // Version 1 is the baseline — tables are created by sqliteStore.ts initDb().
7
+ // This migration just marks the schema as versioned.
8
+ up: (_db) => { },
9
+ },
10
+ ];
11
+ const CURRENT_SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
12
+ /** Ensure the schema_version tracking table exists */
13
+ function ensureVersionTable(db) {
14
+ db.run(`CREATE TABLE IF NOT EXISTS schema_version (
15
+ version INTEGER NOT NULL,
16
+ applied_at TEXT NOT NULL DEFAULT (datetime('now'))
17
+ )`);
18
+ }
19
+ /** Get the currently applied schema version (0 if none) */
20
+ function getCurrentVersion(db) {
21
+ try {
22
+ const result = db.exec(`SELECT MAX(version) as v FROM schema_version`);
23
+ if (result.length === 0 || result[0].values.length === 0)
24
+ return 0;
25
+ const v = result[0].values[0][0];
26
+ return typeof v === 'number' ? v : 0;
27
+ }
28
+ catch {
29
+ return 0;
30
+ }
31
+ }
32
+ /**
33
+ * Run all pending migrations on the given database.
34
+ * Called once during server startup after initDb().
35
+ */
36
+ export function runMigrations(db) {
37
+ ensureVersionTable(db);
38
+ const currentVersion = getCurrentVersion(db);
39
+ if (currentVersion >= CURRENT_SCHEMA_VERSION) {
40
+ console.log(`[DB] Schema up-to-date (v${currentVersion})`);
41
+ return;
42
+ }
43
+ const pending = MIGRATIONS.filter((m) => m.version > currentVersion);
44
+ console.log(`[DB] Running ${pending.length} migration(s) (v${currentVersion} → v${CURRENT_SCHEMA_VERSION})`);
45
+ for (const migration of pending) {
46
+ try {
47
+ migration.up(db);
48
+ db.run(`INSERT INTO schema_version (version) VALUES (?)`, [migration.version]);
49
+ console.log(`[DB] Applied migration v${migration.version}: ${migration.description}`);
50
+ }
51
+ catch (err) {
52
+ const msg = err instanceof Error ? err.message : String(err);
53
+ console.error(`[DB] Migration v${migration.version} failed: ${msg}`);
54
+ throw err;
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,3 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
3
+ //# sourceMappingURL=analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../server/routes/analytics.ts"],"names":[],"mappings":"AAIA,QAAA,MAAM,MAAM,4CAAW,CAAC;AA2BxB,eAAe,MAAM,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { Router } from 'express';
2
+ import { trackUsageEvent, getUsageStats } from '../services/sqliteStore.js';
3
+ const router = Router();
4
+ /** POST /api/analytics/track — record a usage event */
5
+ router.post('/track', async (req, res) => {
6
+ const { event, agentId, metadata } = req.body;
7
+ if (!event) {
8
+ res.status(400).json({ status: 'error', error: 'event is required' });
9
+ return;
10
+ }
11
+ await trackUsageEvent(event, agentId, metadata);
12
+ res.json({ status: 'ok' });
13
+ });
14
+ /** GET /api/analytics/stats — get usage summary */
15
+ router.get('/stats', async (_req, res) => {
16
+ try {
17
+ const stats = await getUsageStats();
18
+ res.json({ status: 'ok', data: stats });
19
+ }
20
+ catch (err) {
21
+ res.status(500).json({ status: 'error', error: err instanceof Error ? err.message : String(err) });
22
+ }
23
+ });
24
+ export default router;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Airtable Connector — Bases + Tables
3
+ * Issue #95
4
+ */
5
+ declare const router: import("express-serve-static-core").Router;
6
+ export default router;
7
+ //# sourceMappingURL=airtable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"airtable.d.ts","sourceRoot":"","sources":["../../../../server/routes/connectors/airtable.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,QAAA,MAAM,MAAM,4CAAW,CAAC;AA2GxB,eAAe,MAAM,CAAC"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Airtable Connector — Bases + Tables
3
+ * Issue #95
4
+ */
5
+ import { Router } from 'express';
6
+ import { rateLimitedFetch, fetchPaginated, toMarkdownTable, connectorError, getApiKey } from './shared.js';
7
+ const router = Router();
8
+ const sessionKeys = new Map();
9
+ const AT_API = 'https://api.airtable.com/v0';
10
+ function atHeaders(token) {
11
+ return { Authorization: `Bearer ${token}` };
12
+ }
13
+ // ── Test ──
14
+ router.post('/test', async (req, res) => {
15
+ const { apiKey } = req.body;
16
+ if (!apiKey) {
17
+ res.status(400).json({ status: 'error', error: 'Missing personal access token' });
18
+ return;
19
+ }
20
+ try {
21
+ const resp = await rateLimitedFetch('https://api.airtable.com/v0/meta/whoami', { headers: atHeaders(apiKey) });
22
+ if (!resp.ok) {
23
+ res.status(401).json({ status: 'error', error: 'Invalid Airtable token' });
24
+ return;
25
+ }
26
+ const user = await resp.json();
27
+ sessionKeys.set('airtable', apiKey);
28
+ res.json({ status: 'ok', data: { userId: user.id } });
29
+ }
30
+ catch (err) {
31
+ connectorError(res, 'Airtable', err);
32
+ }
33
+ });
34
+ // ── List Bases ──
35
+ router.post('/bases', async (req, res) => {
36
+ const body = req.body;
37
+ const token = getApiKey('airtable', body, sessionKeys);
38
+ if (!token) {
39
+ res.status(401).json({ status: 'error', error: 'No Airtable token.' });
40
+ return;
41
+ }
42
+ try {
43
+ const resp = await rateLimitedFetch('https://api.airtable.com/v0/meta/bases', { headers: atHeaders(token) });
44
+ if (!resp.ok)
45
+ throw new Error(`Airtable API ${resp.status}`);
46
+ const data = await resp.json();
47
+ res.json({ status: 'ok', data: data.bases });
48
+ }
49
+ catch (err) {
50
+ connectorError(res, 'Airtable', err);
51
+ }
52
+ });
53
+ // ── Fetch Table Records ──
54
+ router.post('/fetch', async (req, res) => {
55
+ const body = req.body;
56
+ const token = getApiKey('airtable', body, sessionKeys);
57
+ if (!token) {
58
+ res.status(401).json({ status: 'error', error: 'No Airtable token.' });
59
+ return;
60
+ }
61
+ const baseId = body.baseId;
62
+ const tableId = body.tableId;
63
+ const viewName = body.viewName;
64
+ if (!baseId || !tableId) {
65
+ res.status(400).json({ status: 'error', error: 'baseId and tableId required' });
66
+ return;
67
+ }
68
+ try {
69
+ const items = await fetchPaginated({
70
+ maxPages: 10,
71
+ maxItems: 500,
72
+ fetchPage: async (cursor) => {
73
+ const params = new URLSearchParams({ pageSize: '100' });
74
+ if (viewName)
75
+ params.set('view', viewName);
76
+ if (cursor)
77
+ params.set('offset', cursor);
78
+ const resp = await rateLimitedFetch(`${AT_API}/${baseId}/${encodeURIComponent(tableId)}?${params}`, { headers: atHeaders(token) });
79
+ if (!resp.ok)
80
+ throw new Error(`Airtable API ${resp.status}`);
81
+ const data = await resp.json();
82
+ return {
83
+ items: data.records.map((r) => ({
84
+ id: r.id,
85
+ fields: r.fields ?? {},
86
+ createdTime: r.createdTime,
87
+ })),
88
+ nextCursor: data.offset,
89
+ };
90
+ },
91
+ });
92
+ // Build markdown table from fields
93
+ const allKeys = new Set();
94
+ for (const item of items) {
95
+ for (const key of Object.keys(item.fields))
96
+ allKeys.add(key);
97
+ }
98
+ const headers = Array.from(allKeys);
99
+ const rows = items.map((item) => headers.map(h => {
100
+ const val = item.fields[h];
101
+ if (val === null || val === undefined)
102
+ return '';
103
+ if (Array.isArray(val))
104
+ return val.map(v => typeof v === 'object' ? JSON.stringify(v) : String(v)).join(', ');
105
+ if (typeof val === 'object')
106
+ return JSON.stringify(val);
107
+ return String(val);
108
+ }));
109
+ const markdown = `# ${tableId} (${items.length} records)\n\n` + toMarkdownTable(headers, rows);
110
+ res.json({
111
+ status: 'ok',
112
+ data: { items, markdown, count: items.length, tokens: Math.ceil(markdown.length / 4) },
113
+ });
114
+ }
115
+ catch (err) {
116
+ connectorError(res, 'Airtable', err);
117
+ }
118
+ });
119
+ export default router;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Confluence Connector — Pages + Spaces
3
+ * Issue #97
4
+ */
5
+ declare const router: import("express-serve-static-core").Router;
6
+ export default router;
7
+ //# sourceMappingURL=confluence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confluence.d.ts","sourceRoot":"","sources":["../../../../server/routes/connectors/confluence.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,QAAA,MAAM,MAAM,4CAAW,CAAC;AAqLxB,eAAe,MAAM,CAAC"}
@@ -0,0 +1,176 @@
1
+ /**
2
+ * Confluence Connector — Pages + Spaces
3
+ * Issue #97
4
+ */
5
+ import { Router } from 'express';
6
+ import { rateLimitedFetch, fetchPaginated, htmlToMarkdown, connectorError } from './shared.js';
7
+ const router = Router();
8
+ const sessionKeys = new Map();
9
+ function confHeaders(email, token) {
10
+ return {
11
+ Authorization: `Basic ${Buffer.from(`${email}:${token}`).toString('base64')}`,
12
+ Accept: 'application/json',
13
+ };
14
+ }
15
+ // ── Test ──
16
+ router.post('/test', async (req, res) => {
17
+ const { apiKey, email, domain } = req.body;
18
+ if (!apiKey || !email || !domain) {
19
+ res.status(400).json({ status: 'error', error: 'Missing apiKey, email, or domain (e.g., yoursite.atlassian.net)' });
20
+ return;
21
+ }
22
+ try {
23
+ const resp = await rateLimitedFetch(`https://${domain}/wiki/api/v2/spaces?limit=1`, { headers: confHeaders(email, apiKey) });
24
+ if (!resp.ok) {
25
+ res.status(401).json({ status: 'error', error: 'Invalid Confluence credentials' });
26
+ return;
27
+ }
28
+ sessionKeys.set('confluence', JSON.stringify({ apiKey, email, domain }));
29
+ res.json({ status: 'ok', data: { connected: true } });
30
+ }
31
+ catch (err) {
32
+ connectorError(res, 'Confluence', err);
33
+ }
34
+ });
35
+ // ── List Spaces ──
36
+ router.post('/spaces', async (req, res) => {
37
+ const body = req.body;
38
+ const creds = getConfCreds(body);
39
+ if (!creds) {
40
+ res.status(401).json({ status: 'error', error: 'No Confluence credentials.' });
41
+ return;
42
+ }
43
+ try {
44
+ const resp = await rateLimitedFetch(`https://${creds.domain}/wiki/api/v2/spaces?limit=50`, { headers: confHeaders(creds.email, creds.apiKey) });
45
+ if (!resp.ok)
46
+ throw new Error(`Confluence API ${resp.status}`);
47
+ const data = await resp.json();
48
+ res.json({ status: 'ok', data: data.results });
49
+ }
50
+ catch (err) {
51
+ connectorError(res, 'Confluence', err);
52
+ }
53
+ });
54
+ // ── Fetch Pages ──
55
+ router.post('/fetch', async (req, res) => {
56
+ const body = req.body;
57
+ const creds = getConfCreds(body);
58
+ if (!creds) {
59
+ res.status(401).json({ status: 'error', error: 'No Confluence credentials.' });
60
+ return;
61
+ }
62
+ const spaceKey = body.spaceKey;
63
+ const pageIds = body.pageIds;
64
+ try {
65
+ const items = [];
66
+ if (pageIds?.length) {
67
+ for (const id of pageIds.slice(0, 20)) {
68
+ const page = await fetchConfPage(id, creds);
69
+ if (page)
70
+ items.push(page);
71
+ }
72
+ }
73
+ else if (spaceKey) {
74
+ const pages = await fetchPaginated({
75
+ maxPages: 5,
76
+ maxItems: 50,
77
+ fetchPage: async (cursor) => {
78
+ const params = new URLSearchParams({ limit: '25', 'body-format': 'storage' });
79
+ if (cursor)
80
+ params.set('cursor', cursor);
81
+ const resp = await rateLimitedFetch(`https://${creds.domain}/wiki/api/v2/spaces/${spaceKey}/pages?${params}`, { headers: confHeaders(creds.email, creds.apiKey) });
82
+ if (!resp.ok)
83
+ throw new Error(`Confluence API ${resp.status}`);
84
+ const data = await resp.json();
85
+ const nextCursor = data._links?.next
86
+ ? new URL(data._links.next, `https://${creds.domain}`).searchParams.get('cursor') ?? undefined
87
+ : undefined;
88
+ return {
89
+ items: data.results.map((p) => ({
90
+ id: p.id,
91
+ title: p.title,
92
+ body: p.body?.storage?.value ?? '',
93
+ })),
94
+ nextCursor,
95
+ };
96
+ },
97
+ });
98
+ for (const p of pages) {
99
+ const md = htmlToMarkdown(p.body);
100
+ items.push({ id: p.id, title: p.title, markdown: md, tokens: Math.ceil(md.length / 4) });
101
+ }
102
+ }
103
+ const fullMarkdown = items.map(i => `# ${i.title}\n\n${i.markdown}`).join('\n\n---\n\n');
104
+ res.json({
105
+ status: 'ok',
106
+ data: { items, markdown: fullMarkdown, count: items.length, tokens: Math.ceil(fullMarkdown.length / 4) },
107
+ });
108
+ }
109
+ catch (err) {
110
+ connectorError(res, 'Confluence', err);
111
+ }
112
+ });
113
+ // ── Search ──
114
+ router.post('/search', async (req, res) => {
115
+ const body = req.body;
116
+ const creds = getConfCreds(body);
117
+ if (!creds) {
118
+ res.status(401).json({ status: 'error', error: 'No Confluence credentials.' });
119
+ return;
120
+ }
121
+ const query = body.query;
122
+ const spaceKey = body.spaceKey;
123
+ if (!query) {
124
+ res.status(400).json({ status: 'error', error: 'query required' });
125
+ return;
126
+ }
127
+ try {
128
+ const cql = spaceKey
129
+ ? `type=page AND space="${spaceKey}" AND text~"${query}"`
130
+ : `type=page AND text~"${query}"`;
131
+ const resp = await rateLimitedFetch(`https://${creds.domain}/wiki/rest/api/search?cql=${encodeURIComponent(cql)}&limit=20`, { headers: confHeaders(creds.email, creds.apiKey) });
132
+ if (!resp.ok)
133
+ throw new Error(`Confluence search ${resp.status}`);
134
+ const data = await resp.json();
135
+ res.json({
136
+ status: 'ok',
137
+ data: data.results.map(r => ({
138
+ id: r.content.id,
139
+ title: r.content.title,
140
+ type: r.content.type,
141
+ })),
142
+ });
143
+ }
144
+ catch (err) {
145
+ connectorError(res, 'Confluence', err);
146
+ }
147
+ });
148
+ function getConfCreds(body) {
149
+ const stored = sessionKeys.get('confluence');
150
+ if (stored) {
151
+ const parsed = JSON.parse(stored);
152
+ return {
153
+ apiKey: body.apiKey ?? parsed.apiKey,
154
+ email: body.email ?? parsed.email,
155
+ domain: body.domain ?? parsed.domain,
156
+ };
157
+ }
158
+ if (body.apiKey && body.email && body.domain) {
159
+ return { apiKey: body.apiKey, email: body.email, domain: body.domain };
160
+ }
161
+ return null;
162
+ }
163
+ async function fetchConfPage(id, creds) {
164
+ try {
165
+ const resp = await rateLimitedFetch(`https://${creds.domain}/wiki/api/v2/pages/${id}?body-format=storage`, { headers: confHeaders(creds.email, creds.apiKey) });
166
+ if (!resp.ok)
167
+ return null;
168
+ const page = await resp.json();
169
+ const md = htmlToMarkdown(page.body?.storage?.value ?? '');
170
+ return { id: page.id, title: page.title, markdown: md, tokens: Math.ceil(md.length / 4) };
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ }
176
+ export default router;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * GitHub Connector — Issues + PRs
3
+ * Issues #90, #99
4
+ */
5
+ declare const router: import("express-serve-static-core").Router;
6
+ export default router;
7
+ //# sourceMappingURL=github.d.ts.map