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
@@ -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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github.d.ts","sourceRoot":"","sources":["../../../../server/routes/connectors/github.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,QAAA,MAAM,MAAM,4CAAW,CAAC;AAqMxB,eAAe,MAAM,CAAC"}
@@ -0,0 +1,195 @@
1
+ /**
2
+ * GitHub Connector — Issues + PRs
3
+ * Issues #90, #99
4
+ */
5
+ import { Router } from 'express';
6
+ import { rateLimitedFetch, fetchPaginated, connectorError, getApiKey, formatTimestamp } from './shared.js';
7
+ const router = Router();
8
+ const sessionKeys = new Map();
9
+ const GH_API = 'https://api.github.com';
10
+ function ghHeaders(token) {
11
+ return {
12
+ Authorization: `Bearer ${token}`,
13
+ Accept: 'application/vnd.github+json',
14
+ 'X-GitHub-Api-Version': '2022-11-28',
15
+ };
16
+ }
17
+ // ── Test ──
18
+ router.post('/test', async (req, res) => {
19
+ const { apiKey } = req.body;
20
+ if (!apiKey) {
21
+ res.status(400).json({ status: 'error', error: 'Missing apiKey' });
22
+ return;
23
+ }
24
+ try {
25
+ const resp = await rateLimitedFetch(`${GH_API}/user`, { headers: ghHeaders(apiKey) });
26
+ if (!resp.ok) {
27
+ res.status(401).json({ status: 'error', error: 'Invalid GitHub token' });
28
+ return;
29
+ }
30
+ const user = await resp.json();
31
+ sessionKeys.set('github', apiKey);
32
+ res.json({ status: 'ok', data: { user: user.name ?? user.login } });
33
+ }
34
+ catch (err) {
35
+ connectorError(res, 'GitHub', err);
36
+ }
37
+ });
38
+ // ── Fetch Issues ──
39
+ router.post('/issues', async (req, res) => {
40
+ const body = req.body;
41
+ const token = getApiKey('github', body, sessionKeys);
42
+ if (!token) {
43
+ res.status(401).json({ status: 'error', error: 'No GitHub token. Test connection first.' });
44
+ return;
45
+ }
46
+ const repo = body.repo;
47
+ const state = body.state ?? 'open';
48
+ const labels = body.labels;
49
+ if (!repo || !repo.includes('/')) {
50
+ res.status(400).json({ status: 'error', error: 'repo must be owner/name format' });
51
+ return;
52
+ }
53
+ try {
54
+ const items = await fetchPaginated({
55
+ maxPages: 5,
56
+ maxItems: 200,
57
+ fetchPage: async (cursor) => {
58
+ const params = new URLSearchParams({ state, per_page: '100', sort: 'updated', direction: 'desc' });
59
+ if (labels?.length)
60
+ params.set('labels', labels.join(','));
61
+ if (cursor)
62
+ params.set('page', cursor);
63
+ const resp = await rateLimitedFetch(`${GH_API}/repos/${repo}/issues?${params}`, { headers: ghHeaders(token) });
64
+ if (!resp.ok)
65
+ throw new Error(`GitHub API ${resp.status}`);
66
+ const data = await resp.json();
67
+ // Filter out pull requests (GitHub API returns PRs as issues)
68
+ const issues = data.filter((d) => !d.pull_request);
69
+ // Check for next page via Link header
70
+ const link = resp.headers.get('Link') ?? '';
71
+ const nextMatch = link.match(/page=(\d+)>; rel="next"/);
72
+ const nextCursor = nextMatch ? nextMatch[1] : undefined;
73
+ return {
74
+ items: issues.map((issue) => ({
75
+ number: issue.number,
76
+ title: issue.title,
77
+ body: issue.body ?? '',
78
+ state: issue.state,
79
+ labels: (issue.labels ?? []).map((l) => l.name),
80
+ assignee: issue.assignee?.login ?? 'unassigned',
81
+ author: issue.user?.login ?? '',
82
+ createdAt: issue.created_at,
83
+ updatedAt: issue.updated_at,
84
+ comments: issue.comments ?? 0,
85
+ url: issue.html_url,
86
+ })),
87
+ nextCursor,
88
+ };
89
+ },
90
+ });
91
+ // Convert to markdown
92
+ const markdown = items.map((issue) => `## #${issue.number}: ${issue.title}\n` +
93
+ `**Status:** ${issue.state} · **Assignee:** ${issue.assignee} · **Author:** ${issue.author}\n` +
94
+ `**Labels:** ${issue.labels.join(', ') || 'none'} · **Comments:** ${issue.comments}\n` +
95
+ `**Created:** ${formatTimestamp(issue.createdAt)} · **Updated:** ${formatTimestamp(issue.updatedAt)}\n\n` +
96
+ `${issue.body}\n`).join('\n---\n\n');
97
+ res.json({
98
+ status: 'ok',
99
+ data: {
100
+ items,
101
+ markdown,
102
+ count: items.length,
103
+ tokens: Math.ceil(markdown.length / 4),
104
+ },
105
+ });
106
+ }
107
+ catch (err) {
108
+ connectorError(res, 'GitHub', err);
109
+ }
110
+ });
111
+ // ── Fetch PRs (#99) ──
112
+ router.post('/pulls', async (req, res) => {
113
+ const body = req.body;
114
+ const token = getApiKey('github', body, sessionKeys);
115
+ if (!token) {
116
+ res.status(401).json({ status: 'error', error: 'No GitHub token.' });
117
+ return;
118
+ }
119
+ const repo = body.repo;
120
+ const state = body.state ?? 'open';
121
+ if (!repo?.includes('/')) {
122
+ res.status(400).json({ status: 'error', error: 'repo must be owner/name format' });
123
+ return;
124
+ }
125
+ try {
126
+ const params = new URLSearchParams({ state, per_page: '50', sort: 'updated', direction: 'desc' });
127
+ const resp = await rateLimitedFetch(`${GH_API}/repos/${repo}/pulls?${params}`, { headers: ghHeaders(token) });
128
+ if (!resp.ok)
129
+ throw new Error(`GitHub API ${resp.status}`);
130
+ const prs = await resp.json();
131
+ const items = prs.map((pr) => ({
132
+ number: pr.number,
133
+ title: pr.title,
134
+ body: pr.body ?? '',
135
+ state: pr.state,
136
+ author: pr.user?.login ?? '',
137
+ draft: pr.draft ?? false,
138
+ additions: pr.additions ?? 0,
139
+ deletions: pr.deletions ?? 0,
140
+ changedFiles: pr.changed_files ?? 0,
141
+ createdAt: pr.created_at,
142
+ updatedAt: pr.updated_at,
143
+ mergedAt: pr.merged_at,
144
+ url: pr.html_url,
145
+ }));
146
+ const markdown = items.map((pr) => `## PR #${pr.number}: ${pr.title}${pr.draft ? ' [DRAFT]' : ''}\n` +
147
+ `**Status:** ${pr.mergedAt ? 'merged' : pr.state} · **Author:** ${pr.author}\n` +
148
+ `**Changes:** +${pr.additions} -${pr.deletions} in ${pr.changedFiles} files\n` +
149
+ `**Created:** ${formatTimestamp(pr.createdAt)}${pr.mergedAt ? ` · **Merged:** ${formatTimestamp(pr.mergedAt)}` : ''}\n\n` +
150
+ `${pr.body}\n`).join('\n---\n\n');
151
+ res.json({
152
+ status: 'ok',
153
+ data: { items, markdown, count: items.length, tokens: Math.ceil(markdown.length / 4) },
154
+ });
155
+ }
156
+ catch (err) {
157
+ connectorError(res, 'GitHub', err);
158
+ }
159
+ });
160
+ // ── Search ──
161
+ router.post('/search', async (req, res) => {
162
+ const body = req.body;
163
+ const token = getApiKey('github', body, sessionKeys);
164
+ if (!token) {
165
+ res.status(401).json({ status: 'error', error: 'No GitHub token.' });
166
+ return;
167
+ }
168
+ const query = body.query;
169
+ const repo = body.repo;
170
+ if (!query) {
171
+ res.status(400).json({ status: 'error', error: 'query required' });
172
+ return;
173
+ }
174
+ try {
175
+ const q = repo ? `${query}+repo:${repo}` : query;
176
+ const resp = await rateLimitedFetch(`${GH_API}/search/issues?q=${encodeURIComponent(q)}&per_page=20`, { headers: ghHeaders(token) });
177
+ if (!resp.ok)
178
+ throw new Error(`GitHub search ${resp.status}`);
179
+ const data = await resp.json();
180
+ res.json({
181
+ status: 'ok',
182
+ data: data.items.map((i) => ({
183
+ number: i.number,
184
+ title: i.title,
185
+ type: i.pull_request ? 'pr' : 'issue',
186
+ state: i.state,
187
+ repo: i.repository_url?.split('/').slice(-2).join('/'),
188
+ })),
189
+ });
190
+ }
191
+ catch (err) {
192
+ connectorError(res, 'GitHub', err);
193
+ }
194
+ });
195
+ export default router;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Gmail Connector — Messages via search
3
+ * Issue #100
4
+ */
5
+ declare const router: import("express-serve-static-core").Router;
6
+ export default router;
7
+ //# sourceMappingURL=gmail.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail.d.ts","sourceRoot":"","sources":["../../../../server/routes/connectors/gmail.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,QAAA,MAAM,MAAM,4CAAW,CAAC;AAmIxB,eAAe,MAAM,CAAC"}
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Gmail Connector — Messages via search
3
+ * Issue #100
4
+ */
5
+ import { Router } from 'express';
6
+ import { rateLimitedFetch, connectorError, getApiKey } from './shared.js';
7
+ const router = Router();
8
+ const sessionKeys = new Map();
9
+ const GMAIL_API = 'https://gmail.googleapis.com/gmail/v1/users/me';
10
+ function gHeaders(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 OAuth token' });
18
+ return;
19
+ }
20
+ try {
21
+ const resp = await rateLimitedFetch(`${GMAIL_API}/profile`, { headers: gHeaders(apiKey) });
22
+ if (!resp.ok) {
23
+ res.status(401).json({ status: 'error', error: 'Invalid Gmail token' });
24
+ return;
25
+ }
26
+ const data = await resp.json();
27
+ sessionKeys.set('gmail', apiKey);
28
+ res.json({ status: 'ok', data: { email: data.emailAddress } });
29
+ }
30
+ catch (err) {
31
+ connectorError(res, 'Gmail', err);
32
+ }
33
+ });
34
+ // ── Fetch Messages ──
35
+ router.post('/fetch', async (req, res) => {
36
+ const body = req.body;
37
+ const token = getApiKey('gmail', body, sessionKeys);
38
+ if (!token) {
39
+ res.status(401).json({ status: 'error', error: 'No Gmail token.' });
40
+ return;
41
+ }
42
+ const query = body.query ?? 'in:inbox';
43
+ const maxResults = Math.min(body.maxResults ?? 20, 50);
44
+ try {
45
+ // List message IDs
46
+ const listResp = await rateLimitedFetch(`${GMAIL_API}/messages?q=${encodeURIComponent(query)}&maxResults=${maxResults}`, { headers: gHeaders(token) });
47
+ if (!listResp.ok)
48
+ throw new Error(`Gmail list ${listResp.status}`);
49
+ const listData = await listResp.json();
50
+ const messageIds = (listData.messages ?? []).map(m => m.id);
51
+ const items = [];
52
+ // Fetch each message (batch would be better but simpler this way)
53
+ for (const msgId of messageIds.slice(0, 20)) {
54
+ try {
55
+ const msgResp = await rateLimitedFetch(`${GMAIL_API}/messages/${msgId}?format=full`, { headers: gHeaders(token) });
56
+ if (!msgResp.ok)
57
+ continue;
58
+ const msg = await msgResp.json();
59
+ const headers = msg.payload?.headers ?? [];
60
+ const getHeader = (name) => headers.find(h => h.name.toLowerCase() === name.toLowerCase())?.value ?? '';
61
+ items.push({
62
+ id: msgId,
63
+ subject: getHeader('Subject'),
64
+ from: getHeader('From'),
65
+ date: getHeader('Date'),
66
+ snippet: msg.snippet ?? '',
67
+ body: extractGmailBody(msg.payload),
68
+ });
69
+ }
70
+ catch { /* skip */ }
71
+ }
72
+ const markdown = items.map(m => `## ${m.subject}\n` +
73
+ `**From:** ${m.from}\n` +
74
+ `**Date:** ${m.date}\n\n` +
75
+ `${m.body}\n`).join('\n---\n\n');
76
+ res.json({
77
+ status: 'ok',
78
+ data: { items, markdown, count: items.length, tokens: Math.ceil(markdown.length / 4) },
79
+ });
80
+ }
81
+ catch (err) {
82
+ connectorError(res, 'Gmail', err);
83
+ }
84
+ });
85
+ function extractGmailBody(payload) {
86
+ if (!payload)
87
+ return '';
88
+ // Try to find text/plain first, then text/html
89
+ const plain = findPart(payload, 'text/plain');
90
+ if (plain?.body?.data) {
91
+ return Buffer.from(plain.body.data, 'base64url').toString('utf-8');
92
+ }
93
+ const html = findPart(payload, 'text/html');
94
+ if (html?.body?.data) {
95
+ const raw = Buffer.from(html.body.data, 'base64url').toString('utf-8');
96
+ // Simple HTML strip (shared htmlToMarkdown is overkill for email)
97
+ return raw.replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/\n{3,}/g, '\n\n').trim();
98
+ }
99
+ // Direct body
100
+ if (payload.body?.data) {
101
+ return Buffer.from(payload.body.data, 'base64url').toString('utf-8');
102
+ }
103
+ return '';
104
+ }
105
+ function findPart(part, mimeType) {
106
+ if (part.mimeType === mimeType)
107
+ return part;
108
+ for (const child of part.parts ?? []) {
109
+ const found = findPart(child, mimeType);
110
+ if (found)
111
+ return found;
112
+ }
113
+ return undefined;
114
+ }
115
+ export default router;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Google Docs Connector
3
+ * Issue #91
4
+ *
5
+ * Requires OAuth token (from Google Drive OAuth flow in connectors.ts).
6
+ * Fetches document structural elements and converts to markdown.
7
+ */
8
+ declare const router: import("express-serve-static-core").Router;
9
+ export default router;
10
+ //# sourceMappingURL=google-docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"google-docs.d.ts","sourceRoot":"","sources":["../../../../server/routes/connectors/google-docs.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,QAAA,MAAM,MAAM,4CAAW,CAAC;AA8LxB,eAAe,MAAM,CAAC"}