modular-studio 1.0.5 → 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 (232) hide show
  1. package/README.md +122 -122
  2. package/dist/assets/Badge-Bsy2H_p2.js +1 -0
  3. package/dist/assets/GraphPanel-D4X3faxA.js +47 -0
  4. package/dist/assets/{Input-Bgp734xs.js → Input-Dyb88Erk.js} +1 -1
  5. package/dist/assets/KnowledgeTab-BccWz7Np.js +5 -0
  6. package/dist/assets/MemoryTab-Y_66cE01.js +16 -0
  7. package/dist/assets/QualificationTab-Dm9dEIpM.js +1 -0
  8. package/dist/assets/ReviewTab-BrfXSyyf.js +104 -0
  9. package/dist/assets/{Section-DoJrmytO.js → Section-68XDCFTl.js} +1 -1
  10. package/dist/assets/TestTab-CLKRT63X.js +42 -0
  11. package/dist/assets/ToolsTab-xumi9Uds.js +1 -0
  12. package/dist/assets/icons-CS8RUPBi.js +1 -0
  13. package/dist/assets/index-B2bm0161.css +1 -0
  14. package/dist/assets/index-C626nWuA.js +422 -0
  15. package/dist/assets/services-BDk6yY4o.js +369 -0
  16. package/dist/index.html +18 -18
  17. package/dist-server/bin/modular-mcp.js +1 -0
  18. package/dist-server/server/index.d.ts.map +1 -1
  19. package/dist-server/server/index.js +34 -0
  20. package/dist-server/server/mcp/manager.d.ts +3 -0
  21. package/dist-server/server/mcp/manager.d.ts.map +1 -1
  22. package/dist-server/server/mcp/manager.js +80 -5
  23. package/dist-server/server/migrations/index.d.ts +11 -0
  24. package/dist-server/server/migrations/index.d.ts.map +1 -0
  25. package/dist-server/server/migrations/index.js +57 -0
  26. package/dist-server/server/routes/agents.d.ts.map +1 -1
  27. package/dist-server/server/routes/agents.js +27 -0
  28. package/dist-server/server/routes/analytics.d.ts +3 -0
  29. package/dist-server/server/routes/analytics.d.ts.map +1 -0
  30. package/dist-server/server/routes/analytics.js +24 -0
  31. package/dist-server/server/routes/cache.d.ts +3 -0
  32. package/dist-server/server/routes/cache.d.ts.map +1 -0
  33. package/dist-server/server/routes/cache.js +55 -0
  34. package/dist-server/server/routes/connectors/airtable.d.ts +7 -0
  35. package/dist-server/server/routes/connectors/airtable.d.ts.map +1 -0
  36. package/dist-server/server/routes/connectors/airtable.js +119 -0
  37. package/dist-server/server/routes/connectors/confluence.d.ts +7 -0
  38. package/dist-server/server/routes/connectors/confluence.d.ts.map +1 -0
  39. package/dist-server/server/routes/connectors/confluence.js +176 -0
  40. package/dist-server/server/routes/connectors/github.d.ts +7 -0
  41. package/dist-server/server/routes/connectors/github.d.ts.map +1 -0
  42. package/dist-server/server/routes/connectors/github.js +195 -0
  43. package/dist-server/server/routes/connectors/gmail.d.ts +7 -0
  44. package/dist-server/server/routes/connectors/gmail.d.ts.map +1 -0
  45. package/dist-server/server/routes/connectors/gmail.js +115 -0
  46. package/dist-server/server/routes/connectors/google-docs.d.ts +10 -0
  47. package/dist-server/server/routes/connectors/google-docs.d.ts.map +1 -0
  48. package/dist-server/server/routes/connectors/google-docs.js +165 -0
  49. package/dist-server/server/routes/connectors/google-drive.d.ts +7 -0
  50. package/dist-server/server/routes/connectors/google-drive.d.ts.map +1 -0
  51. package/dist-server/server/routes/connectors/google-drive.js +163 -0
  52. package/dist-server/server/routes/connectors/google-sheets.d.ts +7 -0
  53. package/dist-server/server/routes/connectors/google-sheets.d.ts.map +1 -0
  54. package/dist-server/server/routes/connectors/google-sheets.js +90 -0
  55. package/dist-server/server/routes/connectors/hubspot.d.ts +7 -0
  56. package/dist-server/server/routes/connectors/hubspot.d.ts.map +1 -0
  57. package/dist-server/server/routes/connectors/hubspot.js +134 -0
  58. package/dist-server/server/routes/connectors/index.d.ts +6 -0
  59. package/dist-server/server/routes/connectors/index.d.ts.map +1 -0
  60. package/dist-server/server/routes/connectors/index.js +38 -0
  61. package/dist-server/server/routes/connectors/jira.d.ts +7 -0
  62. package/dist-server/server/routes/connectors/jira.d.ts.map +1 -0
  63. package/dist-server/server/routes/connectors/jira.js +151 -0
  64. package/dist-server/server/routes/connectors/linear.d.ts +7 -0
  65. package/dist-server/server/routes/connectors/linear.d.ts.map +1 -0
  66. package/dist-server/server/routes/connectors/linear.js +154 -0
  67. package/dist-server/server/routes/connectors/notion.d.ts +10 -0
  68. package/dist-server/server/routes/connectors/notion.d.ts.map +1 -0
  69. package/dist-server/server/routes/connectors/notion.js +201 -0
  70. package/dist-server/server/routes/connectors/plane.d.ts +10 -0
  71. package/dist-server/server/routes/connectors/plane.d.ts.map +1 -0
  72. package/dist-server/server/routes/connectors/plane.js +189 -0
  73. package/dist-server/server/routes/connectors/shared.d.ts +25 -0
  74. package/dist-server/server/routes/connectors/shared.d.ts.map +1 -0
  75. package/dist-server/server/routes/connectors/shared.js +202 -0
  76. package/dist-server/server/routes/connectors/slack.d.ts +7 -0
  77. package/dist-server/server/routes/connectors/slack.d.ts.map +1 -0
  78. package/dist-server/server/routes/connectors/slack.js +153 -0
  79. package/dist-server/server/routes/connectors.d.ts.map +1 -1
  80. package/dist-server/server/routes/connectors.js +47 -17
  81. package/dist-server/server/routes/cost.d.ts +3 -0
  82. package/dist-server/server/routes/cost.d.ts.map +1 -0
  83. package/dist-server/server/routes/cost.js +113 -0
  84. package/dist-server/server/routes/graph.d.ts +11 -0
  85. package/dist-server/server/routes/graph.d.ts.map +1 -0
  86. package/dist-server/server/routes/graph.js +213 -0
  87. package/dist-server/server/routes/lessons.d.ts +3 -0
  88. package/dist-server/server/routes/lessons.d.ts.map +1 -0
  89. package/dist-server/server/routes/lessons.js +160 -0
  90. package/dist-server/server/routes/llm.d.ts.map +1 -1
  91. package/dist-server/server/routes/llm.js +85 -18
  92. package/dist-server/server/routes/memory.d.ts.map +1 -1
  93. package/dist-server/server/routes/memory.js +31 -0
  94. package/dist-server/server/routes/metaprompt-v2.d.ts +3 -0
  95. package/dist-server/server/routes/metaprompt-v2.d.ts.map +1 -0
  96. package/dist-server/server/routes/metaprompt-v2.js +104 -0
  97. package/dist-server/server/routes/qualification.d.ts.map +1 -1
  98. package/dist-server/server/routes/qualification.js +342 -334
  99. package/dist-server/server/routes/repo-index.d.ts.map +1 -1
  100. package/dist-server/server/routes/repo-index.js +7 -0
  101. package/dist-server/server/routes/skills-search.d.ts.map +1 -1
  102. package/dist-server/server/routes/skills-search.js +192 -26
  103. package/dist-server/server/routes/tool-analytics.d.ts +3 -0
  104. package/dist-server/server/routes/tool-analytics.d.ts.map +1 -0
  105. package/dist-server/server/routes/tool-analytics.js +47 -0
  106. package/dist-server/server/services/adapters/hindsightAdapter.d.ts +28 -0
  107. package/dist-server/server/services/adapters/hindsightAdapter.d.ts.map +1 -0
  108. package/dist-server/server/services/adapters/hindsightAdapter.js +63 -0
  109. package/dist-server/server/services/adapters/postgresAdapter.js +30 -30
  110. package/dist-server/server/services/adapters/sqliteAdapter.d.ts +1 -0
  111. package/dist-server/server/services/adapters/sqliteAdapter.d.ts.map +1 -1
  112. package/dist-server/server/services/adapters/sqliteAdapter.js +66 -36
  113. package/dist-server/server/services/agentStore.d.ts +2 -1
  114. package/dist-server/server/services/agentStore.d.ts.map +1 -1
  115. package/dist-server/server/services/agentStore.js +2 -1
  116. package/dist-server/server/services/correctionDetector.d.ts +22 -0
  117. package/dist-server/server/services/correctionDetector.d.ts.map +1 -0
  118. package/dist-server/server/services/correctionDetector.js +91 -0
  119. package/dist-server/server/services/credentialStore.d.ts +10 -0
  120. package/dist-server/server/services/credentialStore.d.ts.map +1 -0
  121. package/dist-server/server/services/credentialStore.js +123 -0
  122. package/dist-server/server/services/hindsightClient.d.ts +15 -0
  123. package/dist-server/server/services/hindsightClient.d.ts.map +1 -0
  124. package/dist-server/server/services/hindsightClient.js +48 -0
  125. package/dist-server/server/services/lessonExtractor.d.ts +21 -0
  126. package/dist-server/server/services/lessonExtractor.d.ts.map +1 -0
  127. package/dist-server/server/services/lessonExtractor.js +92 -0
  128. package/dist-server/server/services/repoIndexer.d.ts +7 -1
  129. package/dist-server/server/services/repoIndexer.d.ts.map +1 -1
  130. package/dist-server/server/services/repoIndexer.js +295 -94
  131. package/dist-server/server/services/responseCache.d.ts +24 -0
  132. package/dist-server/server/services/responseCache.d.ts.map +1 -0
  133. package/dist-server/server/services/responseCache.js +163 -0
  134. package/dist-server/server/services/sqliteStore.d.ts +72 -0
  135. package/dist-server/server/services/sqliteStore.d.ts.map +1 -1
  136. package/dist-server/server/services/sqliteStore.js +291 -13
  137. package/dist-server/src/config.d.ts +2 -0
  138. package/dist-server/src/config.d.ts.map +1 -0
  139. package/dist-server/src/config.js +3 -0
  140. package/dist-server/src/graph/db.d.ts +46 -0
  141. package/dist-server/src/graph/db.d.ts.map +1 -0
  142. package/dist-server/src/graph/db.js +241 -0
  143. package/dist-server/src/graph/extractors/code.d.ts +12 -0
  144. package/dist-server/src/graph/extractors/code.d.ts.map +1 -0
  145. package/dist-server/src/graph/extractors/code.js +239 -0
  146. package/dist-server/src/graph/extractors/cross-type.d.ts +16 -0
  147. package/dist-server/src/graph/extractors/cross-type.d.ts.map +1 -0
  148. package/dist-server/src/graph/extractors/cross-type.js +67 -0
  149. package/dist-server/src/graph/extractors/markdown.d.ts +29 -0
  150. package/dist-server/src/graph/extractors/markdown.d.ts.map +1 -0
  151. package/dist-server/src/graph/extractors/markdown.js +224 -0
  152. package/dist-server/src/graph/extractors/yaml.d.ts +15 -0
  153. package/dist-server/src/graph/extractors/yaml.d.ts.map +1 -0
  154. package/dist-server/src/graph/extractors/yaml.js +104 -0
  155. package/dist-server/src/graph/index.d.ts +62 -0
  156. package/dist-server/src/graph/index.d.ts.map +1 -0
  157. package/dist-server/src/graph/index.js +67 -0
  158. package/dist-server/src/graph/packer.d.ts +19 -0
  159. package/dist-server/src/graph/packer.d.ts.map +1 -0
  160. package/dist-server/src/graph/packer.js +134 -0
  161. package/dist-server/src/graph/resolver.d.ts +12 -0
  162. package/dist-server/src/graph/resolver.d.ts.map +1 -0
  163. package/dist-server/src/graph/resolver.js +81 -0
  164. package/dist-server/src/graph/scanner.d.ts +34 -0
  165. package/dist-server/src/graph/scanner.d.ts.map +1 -0
  166. package/dist-server/src/graph/scanner.js +252 -0
  167. package/dist-server/src/graph/traverser.d.ts +17 -0
  168. package/dist-server/src/graph/traverser.d.ts.map +1 -0
  169. package/dist-server/src/graph/traverser.js +185 -0
  170. package/dist-server/src/graph/types.d.ts +117 -0
  171. package/dist-server/src/graph/types.d.ts.map +1 -0
  172. package/dist-server/src/graph/types.js +63 -0
  173. package/dist-server/src/metaprompt/v2/assembler.d.ts +3 -0
  174. package/dist-server/src/metaprompt/v2/assembler.d.ts.map +1 -0
  175. package/dist-server/src/metaprompt/v2/assembler.js +261 -0
  176. package/dist-server/src/metaprompt/v2/context-strategist.d.ts +3 -0
  177. package/dist-server/src/metaprompt/v2/context-strategist.d.ts.map +1 -0
  178. package/dist-server/src/metaprompt/v2/context-strategist.js +173 -0
  179. package/dist-server/src/metaprompt/v2/evaluator.d.ts +3 -0
  180. package/dist-server/src/metaprompt/v2/evaluator.d.ts.map +1 -0
  181. package/dist-server/src/metaprompt/v2/evaluator.js +281 -0
  182. package/dist-server/src/metaprompt/v2/index.d.ts +41 -0
  183. package/dist-server/src/metaprompt/v2/index.d.ts.map +1 -0
  184. package/dist-server/src/metaprompt/v2/index.js +90 -0
  185. package/dist-server/src/metaprompt/v2/parser.d.ts +3 -0
  186. package/dist-server/src/metaprompt/v2/parser.d.ts.map +1 -0
  187. package/dist-server/src/metaprompt/v2/parser.js +138 -0
  188. package/dist-server/src/metaprompt/v2/pattern-selector.d.ts +3 -0
  189. package/dist-server/src/metaprompt/v2/pattern-selector.d.ts.map +1 -0
  190. package/dist-server/src/metaprompt/v2/pattern-selector.js +154 -0
  191. package/dist-server/src/metaprompt/v2/researcher.d.ts +3 -0
  192. package/dist-server/src/metaprompt/v2/researcher.d.ts.map +1 -0
  193. package/dist-server/src/metaprompt/v2/researcher.js +194 -0
  194. package/dist-server/src/metaprompt/v2/tool-discovery.d.ts +74 -0
  195. package/dist-server/src/metaprompt/v2/tool-discovery.d.ts.map +1 -0
  196. package/dist-server/src/metaprompt/v2/tool-discovery.js +290 -0
  197. package/dist-server/src/metaprompt/v2/types.d.ts +154 -0
  198. package/dist-server/src/metaprompt/v2/types.d.ts.map +1 -0
  199. package/dist-server/src/metaprompt/v2/types.js +2 -0
  200. package/dist-server/src/services/contradictionDetector.js +1 -1
  201. package/dist-server/src/services/llmService.d.ts +61 -0
  202. package/dist-server/src/services/llmService.d.ts.map +1 -0
  203. package/dist-server/src/services/llmService.js +222 -0
  204. package/dist-server/src/store/knowledgeBase.d.ts +6 -1
  205. package/dist-server/src/store/knowledgeBase.d.ts.map +1 -1
  206. package/dist-server/src/store/knowledgeBase.js +0 -1
  207. package/dist-server/src/store/lessonStore.d.ts +26 -0
  208. package/dist-server/src/store/lessonStore.d.ts.map +1 -0
  209. package/dist-server/src/store/lessonStore.js +64 -0
  210. package/dist-server/src/store/mcp-registry.d.ts +29 -0
  211. package/dist-server/src/store/mcp-registry.d.ts.map +1 -0
  212. package/dist-server/src/store/mcp-registry.js +1303 -0
  213. package/dist-server/src/store/memoryStore.d.ts +12 -1
  214. package/dist-server/src/store/memoryStore.d.ts.map +1 -1
  215. package/dist-server/src/store/memoryStore.js +9 -0
  216. package/dist-server/src/types/registry.types.d.ts +13 -0
  217. package/dist-server/src/types/registry.types.d.ts.map +1 -0
  218. package/dist-server/src/types/registry.types.js +2 -0
  219. package/dist-server/tsconfig.server.tsbuildinfo +1 -1
  220. package/package.json +15 -1
  221. package/scripts/cleanup-worktrees.ps1 +29 -0
  222. package/dist/assets/Badge-22Ai0eyi.js +0 -1
  223. package/dist/assets/KnowledgeTab-DABxirZh.js +0 -4
  224. package/dist/assets/MemoryTab-DZeYElIT.js +0 -16
  225. package/dist/assets/QualificationTab-Dfpy3J30.js +0 -1
  226. package/dist/assets/ReviewTab-SD8lQuCc.js +0 -103
  227. package/dist/assets/TestTab-PDyMF8Fw.js +0 -33
  228. package/dist/assets/ToolsTab-B83qGCmG.js +0 -1
  229. package/dist/assets/icons-C2EV-le6.js +0 -1
  230. package/dist/assets/index-DkpMAxX7.css +0 -1
  231. package/dist/assets/index-q24ug5Qs.js +0 -143
  232. package/dist/assets/services-BaKotDf0.js +0 -343
package/dist/index.html CHANGED
@@ -1,20 +1,20 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <link href="https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-sans/style.min.css" rel="stylesheet" />
8
- <link href="https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-mono/style.min.css" rel="stylesheet" />
9
- <title>MODULAR — Agent Patchbay</title>
10
- <script type="module" crossorigin src="/assets/index-q24ug5Qs.js"></script>
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <link href="https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-sans/style.min.css" rel="stylesheet" />
8
+ <link href="https://cdn.jsdelivr.net/npm/geist@1.3.1/dist/fonts/geist-mono/style.min.css" rel="stylesheet" />
9
+ <title>MODULAR — Agent Patchbay</title>
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-BaKotDf0.js">
14
- <link rel="modulepreload" crossorigin href="/assets/icons-C2EV-le6.js">
15
- <link rel="stylesheet" crossorigin href="/assets/index-DkpMAxX7.css">
16
- </head>
17
- <body style="margin:0;padding:0;height:100vh;overflow:hidden">
18
- <div id="root" style="height:100%;overflow:hidden"></div>
19
- </body>
20
- </html>
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
+ </head>
17
+ <body style="margin:0;padding:0;height:100vh;overflow:hidden">
18
+ <div id="root" style="height:100%;overflow:hidden"></div>
19
+ </body>
20
+ </html>
@@ -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":"AAgCA,wBAAgB,SAAS,gDA6FxB;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"}
@@ -27,6 +27,14 @@ import embeddingRoutes from './routes/embeddings.js';
27
27
  import embeddingService from './services/embeddingService.js';
28
28
  import conversationRoutes from './routes/conversations.js';
29
29
  import memoryRoutes from './routes/memory.js';
30
+ import cacheRoutes from './routes/cache.js';
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';
30
38
  const __dirname = dirname(fileURLToPath(import.meta.url));
31
39
  export function createApp() {
32
40
  const app = express();
@@ -40,6 +48,24 @@ export function createApp() {
40
48
  ],
41
49
  }));
42
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
+ });
43
69
  // Basic security headers (lightweight helmet-like)
44
70
  app.use((_req, res, next) => {
45
71
  res.setHeader('X-Content-Type-Options', 'nosniff');
@@ -89,6 +115,14 @@ export function createApp() {
89
115
  app.use('/api/embeddings', embeddingRoutes);
90
116
  app.use('/api/conversations', conversationRoutes);
91
117
  app.use('/api/memory', memoryRoutes);
118
+ app.use('/api/cache', cacheRoutes);
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);
92
126
  // API 404 catch-all — log unmatched API routes for debugging
93
127
  app.use('/api', (_req, res) => {
94
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;IA0B1B,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
@@ -16,8 +19,22 @@ export class McpManager {
16
19
  if (!command) {
17
20
  throw new Error('MCP command cannot be empty');
18
21
  }
19
- // Extract base command (remove path prefixes)
20
- const baseCommand = command.split(/[/\\]/).pop()?.split('.')[0] || '';
22
+ // Extract base command (remove path prefixes and .cmd/.exe/.bat extensions)
23
+ const baseCommand = (command.split(/[/\\]/).pop() || '')
24
+ .replace(/\.(cmd|exe|bat)$/i, '')
25
+ .toLowerCase();
26
+ // On Windows, allow `cmd /c <allowed>` pattern — check that the actual program is allowed
27
+ if (baseCommand === 'cmd' && args.length >= 2) {
28
+ const cmdFlag = args[0].toLowerCase();
29
+ if (cmdFlag === '/c' || cmdFlag === '/k') {
30
+ const actualCommand = (args[1].split(/[/\\]/).pop() || '')
31
+ .replace(/\.(cmd|exe|bat)$/i, '')
32
+ .toLowerCase();
33
+ if (this.ALLOWED_MCP_COMMANDS.has(actualCommand)) {
34
+ return; // cmd /c npx ... is fine
35
+ }
36
+ }
37
+ }
21
38
  // Check if command is in allowlist or starts with allowed prefix
22
39
  const isAllowed = this.ALLOWED_MCP_COMMANDS.has(baseCommand) ||
23
40
  Array.from(this.ALLOWED_MCP_COMMANDS).some(allowed => command.startsWith(allowed));
@@ -66,11 +83,43 @@ export class McpManager {
66
83
  tools: [],
67
84
  connectedAt: null,
68
85
  lastError: null,
86
+ retryCount: 0,
87
+ retryTimer: null,
69
88
  });
70
89
  }
71
90
  removeServer(id) {
91
+ const conn = this.connections.get(id);
92
+ if (conn?.retryTimer)
93
+ clearTimeout(conn.retryTimer);
72
94
  this.connections.delete(id);
73
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
+ }
74
123
  getServer(id) {
75
124
  return this.connections.get(id);
76
125
  }
@@ -133,13 +182,15 @@ export class McpManager {
133
182
  env: { ...process.env, ...conn.config.env },
134
183
  });
135
184
  client = new Client({ name: 'modular-studio', version: '1.0.0' });
136
- // Handle process exit
185
+ // Handle process exit — attempt auto-reconnect with exponential backoff
137
186
  transport.onclose = () => {
138
187
  if (conn.status === 'connected') {
139
188
  conn.status = 'error';
140
189
  conn.lastError = 'Process exited unexpectedly';
141
190
  conn.client = null;
142
191
  conn.transport = null;
192
+ console.warn(`[McpManager] "${id}" process exited unexpectedly — scheduling reconnect`);
193
+ this.scheduleReconnect(id);
143
194
  }
144
195
  };
145
196
  await client.connect(transport);
@@ -147,6 +198,11 @@ export class McpManager {
147
198
  conn.client = client;
148
199
  conn.transport = transport;
149
200
  conn.status = 'connected';
201
+ conn.retryCount = 0;
202
+ if (conn.retryTimer) {
203
+ clearTimeout(conn.retryTimer);
204
+ conn.retryTimer = null;
205
+ }
150
206
  conn.tools = tools.map((t) => ({
151
207
  name: t.name,
152
208
  description: t.description,
@@ -168,13 +224,32 @@ export class McpManager {
168
224
  if (!conn.client || conn.status !== 'connected') {
169
225
  throw new Error(`MCP server "${id}" is not connected`);
170
226
  }
171
- const result = await conn.client.callTool({ name: toolName, arguments: args });
172
- 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
+ }
173
243
  }
174
244
  async disconnect(id) {
175
245
  const conn = this.connections.get(id);
176
246
  if (!conn)
177
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;
178
253
  if (conn.client) {
179
254
  try {
180
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
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../../server/routes/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAeH,QAAA,MAAM,MAAM,4CAAW,CAAC;AA6HxB,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"agents.d.ts","sourceRoot":"","sources":["../../../server/routes/agents.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAeH,QAAA,MAAM,MAAM,4CAAW,CAAC;AA0JxB,eAAe,MAAM,CAAC"}
@@ -73,6 +73,32 @@ router.put('/:id', (req, res) => {
73
73
  res.status(500).json({ status: 'error', error: err.message });
74
74
  }
75
75
  });
76
+ // Explicit save — always creates a version snapshot then persists new state
77
+ router.post('/:id/save', (req, res) => {
78
+ try {
79
+ const { state, label, changeSummary } = req.body;
80
+ if (!state || typeof state !== 'object') {
81
+ res.status(400).json({ status: 'error', error: 'Invalid body' });
82
+ return;
83
+ }
84
+ const existing = loadAgent(req.params.id);
85
+ const versionEntry = existing
86
+ ? createAgentVersion(req.params.id, existing.version, label, changeSummary)
87
+ : null;
88
+ saveAgent(req.params.id, state);
89
+ res.json({
90
+ status: 'ok',
91
+ data: {
92
+ id: req.params.id,
93
+ version: state.version,
94
+ versionId: versionEntry?.id ?? null,
95
+ },
96
+ });
97
+ }
98
+ catch (err) {
99
+ res.status(500).json({ status: 'error', error: err.message });
100
+ }
101
+ });
76
102
  // Delete agent
77
103
  router.delete('/:id', (req, res) => {
78
104
  try {
@@ -96,6 +122,7 @@ router.get('/:id/versions', (req, res) => {
96
122
  version: v.version,
97
123
  timestamp: v.timestamp,
98
124
  label: v.label,
125
+ changeSummary: v.changeSummary,
99
126
  }));
100
127
  res.json({ status: 'ok', data: formatted });
101
128
  }
@@ -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,3 @@
1
+ declare const router: import("express-serve-static-core").Router;
2
+ export default router;
3
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../../server/routes/cache.ts"],"names":[],"mappings":"AAGA,QAAA,MAAM,MAAM,4CAAW,CAAC;AAqDxB,eAAe,MAAM,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { Router } from 'express';
2
+ import { checkCache, storeResponse, getCacheStats, purgeCache, evictExpired, evictLRU } from '../services/responseCache.js';
3
+ const router = Router();
4
+ // GET /api/cache/stats
5
+ router.get('/stats', async (_req, res) => {
6
+ try {
7
+ const stats = await getCacheStats();
8
+ res.json({ status: 'success', ...stats });
9
+ }
10
+ catch (err) {
11
+ res.status(500).json({ status: 'error', error: err instanceof Error ? err.message : 'Unknown error' });
12
+ }
13
+ });
14
+ // DELETE /api/cache/purge?agentId=
15
+ router.delete('/purge', async (req, res) => {
16
+ try {
17
+ const agentId = typeof req.query['agentId'] === 'string' ? req.query['agentId'] : undefined;
18
+ await purgeCache(agentId);
19
+ res.json({ status: 'success' });
20
+ }
21
+ catch (err) {
22
+ res.status(500).json({ status: 'error', error: err instanceof Error ? err.message : 'Unknown error' });
23
+ }
24
+ });
25
+ // POST /api/cache/check — { query, agentId, model, systemPromptHash, ttl? }
26
+ router.post('/check', async (req, res) => {
27
+ try {
28
+ const { query, agentId, model, systemPromptHash, ttl } = req.body;
29
+ if (!query || !agentId || !model || !systemPromptHash) {
30
+ return res.status(400).json({ status: 'error', error: 'Missing required fields' });
31
+ }
32
+ const hit = await checkCache(query, agentId, model, systemPromptHash, ttl ?? 3600);
33
+ res.json({ status: 'success', hit: hit !== null, cached: hit });
34
+ }
35
+ catch (err) {
36
+ res.status(500).json({ status: 'error', error: err instanceof Error ? err.message : 'Unknown error' });
37
+ }
38
+ });
39
+ // POST /api/cache/store — { query, response, agentId, model, systemPromptHash, ttl? }
40
+ router.post('/store', async (req, res) => {
41
+ try {
42
+ const { query, response, agentId, model, systemPromptHash, ttl } = req.body;
43
+ if (!query || !response || !agentId || !model || !systemPromptHash) {
44
+ return res.status(400).json({ status: 'error', error: 'Missing required fields' });
45
+ }
46
+ await storeResponse(query, response, agentId, model, systemPromptHash, ttl ?? 3600);
47
+ await evictExpired();
48
+ await evictLRU(1000);
49
+ res.json({ status: 'success' });
50
+ }
51
+ catch (err) {
52
+ res.status(500).json({ status: 'error', error: err instanceof Error ? err.message : 'Unknown error' });
53
+ }
54
+ });
55
+ 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"}