@rv404/mcp-launchpad 1.0.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 (139) hide show
  1. package/.env.example +29 -0
  2. package/README.md +248 -0
  3. package/dist/backends/clickup.d.ts +3 -0
  4. package/dist/backends/clickup.d.ts.map +1 -0
  5. package/dist/backends/clickup.js +1550 -0
  6. package/dist/backends/clickup.js.map +1 -0
  7. package/dist/backends/email.d.ts +9 -0
  8. package/dist/backends/email.d.ts.map +1 -0
  9. package/dist/backends/email.js +452 -0
  10. package/dist/backends/email.js.map +1 -0
  11. package/dist/backends/firecrawl.d.ts +3 -0
  12. package/dist/backends/firecrawl.d.ts.map +1 -0
  13. package/dist/backends/firecrawl.js +297 -0
  14. package/dist/backends/firecrawl.js.map +1 -0
  15. package/dist/backends/gemini.d.ts +3 -0
  16. package/dist/backends/gemini.d.ts.map +1 -0
  17. package/dist/backends/gemini.js +702 -0
  18. package/dist/backends/gemini.js.map +1 -0
  19. package/dist/backends/github.d.ts +3 -0
  20. package/dist/backends/github.d.ts.map +1 -0
  21. package/dist/backends/github.js +2129 -0
  22. package/dist/backends/github.js.map +1 -0
  23. package/dist/backends/index.d.ts +2 -0
  24. package/dist/backends/index.d.ts.map +1 -0
  25. package/dist/backends/index.js +27 -0
  26. package/dist/backends/index.js.map +1 -0
  27. package/dist/backends/kie.d.ts +3 -0
  28. package/dist/backends/kie.d.ts.map +1 -0
  29. package/dist/backends/kie.js +399 -0
  30. package/dist/backends/kie.js.map +1 -0
  31. package/dist/backends/n8n.d.ts +3 -0
  32. package/dist/backends/n8n.d.ts.map +1 -0
  33. package/dist/backends/n8n.js +414 -0
  34. package/dist/backends/n8n.js.map +1 -0
  35. package/dist/backends/notion.d.ts +3 -0
  36. package/dist/backends/notion.d.ts.map +1 -0
  37. package/dist/backends/notion.js +375 -0
  38. package/dist/backends/notion.js.map +1 -0
  39. package/dist/backends/quickbooks.d.ts +3 -0
  40. package/dist/backends/quickbooks.d.ts.map +1 -0
  41. package/dist/backends/quickbooks.js +2183 -0
  42. package/dist/backends/quickbooks.js.map +1 -0
  43. package/dist/backends/slack.d.ts +3 -0
  44. package/dist/backends/slack.d.ts.map +1 -0
  45. package/dist/backends/slack.js +1725 -0
  46. package/dist/backends/slack.js.map +1 -0
  47. package/dist/backends/youtube.d.ts +3 -0
  48. package/dist/backends/youtube.d.ts.map +1 -0
  49. package/dist/backends/youtube.js +936 -0
  50. package/dist/backends/youtube.js.map +1 -0
  51. package/dist/index.d.ts +3 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +150 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/router.d.ts +36 -0
  56. package/dist/router.d.ts.map +1 -0
  57. package/dist/router.js +283 -0
  58. package/dist/router.js.map +1 -0
  59. package/dist/types.d.ts +103 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +65 -0
  62. package/dist/types.js.map +1 -0
  63. package/lib/mcp-file-output/dist/index.d.ts +99 -0
  64. package/lib/mcp-file-output/dist/index.d.ts.map +1 -0
  65. package/lib/mcp-file-output/dist/index.js +215 -0
  66. package/lib/mcp-file-output/dist/index.js.map +1 -0
  67. package/lib/mcp-file-output/node_modules/@types/node/LICENSE +21 -0
  68. package/lib/mcp-file-output/node_modules/@types/node/README.md +15 -0
  69. package/lib/mcp-file-output/node_modules/@types/node/assert/strict.d.ts +8 -0
  70. package/lib/mcp-file-output/node_modules/@types/node/assert.d.ts +1062 -0
  71. package/lib/mcp-file-output/node_modules/@types/node/async_hooks.d.ts +605 -0
  72. package/lib/mcp-file-output/node_modules/@types/node/buffer.buffer.d.ts +471 -0
  73. package/lib/mcp-file-output/node_modules/@types/node/buffer.d.ts +1936 -0
  74. package/lib/mcp-file-output/node_modules/@types/node/child_process.d.ts +1475 -0
  75. package/lib/mcp-file-output/node_modules/@types/node/cluster.d.ts +577 -0
  76. package/lib/mcp-file-output/node_modules/@types/node/compatibility/disposable.d.ts +16 -0
  77. package/lib/mcp-file-output/node_modules/@types/node/compatibility/index.d.ts +9 -0
  78. package/lib/mcp-file-output/node_modules/@types/node/compatibility/indexable.d.ts +20 -0
  79. package/lib/mcp-file-output/node_modules/@types/node/compatibility/iterators.d.ts +21 -0
  80. package/lib/mcp-file-output/node_modules/@types/node/console.d.ts +452 -0
  81. package/lib/mcp-file-output/node_modules/@types/node/constants.d.ts +21 -0
  82. package/lib/mcp-file-output/node_modules/@types/node/crypto.d.ts +4590 -0
  83. package/lib/mcp-file-output/node_modules/@types/node/dgram.d.ts +597 -0
  84. package/lib/mcp-file-output/node_modules/@types/node/diagnostics_channel.d.ts +578 -0
  85. package/lib/mcp-file-output/node_modules/@types/node/dns/promises.d.ts +479 -0
  86. package/lib/mcp-file-output/node_modules/@types/node/dns.d.ts +871 -0
  87. package/lib/mcp-file-output/node_modules/@types/node/domain.d.ts +170 -0
  88. package/lib/mcp-file-output/node_modules/@types/node/events.d.ts +977 -0
  89. package/lib/mcp-file-output/node_modules/@types/node/fs/promises.d.ts +1270 -0
  90. package/lib/mcp-file-output/node_modules/@types/node/fs.d.ts +4375 -0
  91. package/lib/mcp-file-output/node_modules/@types/node/globals.d.ts +172 -0
  92. package/lib/mcp-file-output/node_modules/@types/node/globals.typedarray.d.ts +38 -0
  93. package/lib/mcp-file-output/node_modules/@types/node/http.d.ts +2049 -0
  94. package/lib/mcp-file-output/node_modules/@types/node/http2.d.ts +2631 -0
  95. package/lib/mcp-file-output/node_modules/@types/node/https.d.ts +578 -0
  96. package/lib/mcp-file-output/node_modules/@types/node/index.d.ts +93 -0
  97. package/lib/mcp-file-output/node_modules/@types/node/inspector.generated.d.ts +3966 -0
  98. package/lib/mcp-file-output/node_modules/@types/node/module.d.ts +539 -0
  99. package/lib/mcp-file-output/node_modules/@types/node/net.d.ts +1012 -0
  100. package/lib/mcp-file-output/node_modules/@types/node/os.d.ts +506 -0
  101. package/lib/mcp-file-output/node_modules/@types/node/package.json +140 -0
  102. package/lib/mcp-file-output/node_modules/@types/node/path.d.ts +200 -0
  103. package/lib/mcp-file-output/node_modules/@types/node/perf_hooks.d.ts +961 -0
  104. package/lib/mcp-file-output/node_modules/@types/node/process.d.ts +1957 -0
  105. package/lib/mcp-file-output/node_modules/@types/node/punycode.d.ts +117 -0
  106. package/lib/mcp-file-output/node_modules/@types/node/querystring.d.ts +152 -0
  107. package/lib/mcp-file-output/node_modules/@types/node/readline/promises.d.ts +162 -0
  108. package/lib/mcp-file-output/node_modules/@types/node/readline.d.ts +589 -0
  109. package/lib/mcp-file-output/node_modules/@types/node/repl.d.ts +430 -0
  110. package/lib/mcp-file-output/node_modules/@types/node/sea.d.ts +153 -0
  111. package/lib/mcp-file-output/node_modules/@types/node/stream/consumers.d.ts +38 -0
  112. package/lib/mcp-file-output/node_modules/@types/node/stream/promises.d.ts +90 -0
  113. package/lib/mcp-file-output/node_modules/@types/node/stream/web.d.ts +533 -0
  114. package/lib/mcp-file-output/node_modules/@types/node/stream.d.ts +1675 -0
  115. package/lib/mcp-file-output/node_modules/@types/node/string_decoder.d.ts +67 -0
  116. package/lib/mcp-file-output/node_modules/@types/node/test.d.ts +1787 -0
  117. package/lib/mcp-file-output/node_modules/@types/node/timers/promises.d.ts +108 -0
  118. package/lib/mcp-file-output/node_modules/@types/node/timers.d.ts +286 -0
  119. package/lib/mcp-file-output/node_modules/@types/node/tls.d.ts +1255 -0
  120. package/lib/mcp-file-output/node_modules/@types/node/trace_events.d.ts +197 -0
  121. package/lib/mcp-file-output/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +468 -0
  122. package/lib/mcp-file-output/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +34 -0
  123. package/lib/mcp-file-output/node_modules/@types/node/ts5.6/index.d.ts +93 -0
  124. package/lib/mcp-file-output/node_modules/@types/node/tty.d.ts +208 -0
  125. package/lib/mcp-file-output/node_modules/@types/node/url.d.ts +964 -0
  126. package/lib/mcp-file-output/node_modules/@types/node/util.d.ts +2331 -0
  127. package/lib/mcp-file-output/node_modules/@types/node/v8.d.ts +809 -0
  128. package/lib/mcp-file-output/node_modules/@types/node/vm.d.ts +1001 -0
  129. package/lib/mcp-file-output/node_modules/@types/node/wasi.d.ts +181 -0
  130. package/lib/mcp-file-output/node_modules/@types/node/web-globals/abortcontroller.d.ts +34 -0
  131. package/lib/mcp-file-output/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  132. package/lib/mcp-file-output/node_modules/@types/node/web-globals/events.d.ts +97 -0
  133. package/lib/mcp-file-output/node_modules/@types/node/web-globals/fetch.d.ts +46 -0
  134. package/lib/mcp-file-output/node_modules/@types/node/worker_threads.d.ts +715 -0
  135. package/lib/mcp-file-output/node_modules/@types/node/zlib.d.ts +540 -0
  136. package/lib/mcp-file-output/package.json +19 -0
  137. package/lib/mcp-file-output/src/index.ts +309 -0
  138. package/lib/mcp-file-output/tsconfig.json +20 -0
  139. package/package.json +64 -0
@@ -0,0 +1,702 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import * as os from 'os';
4
+ // Google AI Gemini API configuration (uses API key)
5
+ const GOOGLE_API_KEY = process.env.GOOGLE_API_KEY;
6
+ const BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';
7
+ // Default output directory for generated images (auto-saves there if no outputPath specified)
8
+ const DEFAULT_IMAGE_OUTPUT_DIR = process.env.GEMINI_IMAGE_OUTPUT_DIR;
9
+ // Local store registry (like the Python CLI)
10
+ const CONFIG_DIR = path.join(os.homedir(), '.gemini-file-search');
11
+ const STORES_FILE = path.join(CONFIG_DIR, 'stores.json');
12
+ function ensureConfigDir() {
13
+ if (!fs.existsSync(CONFIG_DIR)) {
14
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
15
+ }
16
+ }
17
+ function loadStores() {
18
+ if (!fs.existsSync(STORES_FILE)) {
19
+ return {};
20
+ }
21
+ const content = fs.readFileSync(STORES_FILE, 'utf-8');
22
+ return JSON.parse(content);
23
+ }
24
+ function saveStores(stores) {
25
+ ensureConfigDir();
26
+ fs.writeFileSync(STORES_FILE, JSON.stringify(stores, null, 2));
27
+ }
28
+ function getStoreId(name) {
29
+ const stores = loadStores();
30
+ if (!(name in stores)) {
31
+ const available = Object.keys(stores).join(', ') || 'none';
32
+ throw new Error(`Store '${name}' not found. Available stores: ${available}`);
33
+ }
34
+ return stores[name].id;
35
+ }
36
+ // ============================================================================
37
+ // Action Definitions
38
+ // ============================================================================
39
+ const actions = [
40
+ // Image Generation
41
+ {
42
+ name: 'generate_image',
43
+ description: 'Generate images using Google Imagen 4. Creates high-quality images from text descriptions.',
44
+ params: [
45
+ { name: 'prompt', type: 'string', required: true, description: 'Detailed text description of the image to generate' },
46
+ { name: 'aspectRatio', type: 'string', required: false, description: 'Aspect ratio: 1:1, 16:9, 9:16, 4:3, 3:4 (default: 1:1)' },
47
+ { name: 'negativePrompt', type: 'string', required: false, description: 'What to exclude or avoid in the generated image' },
48
+ { name: 'sampleCount', type: 'number', required: false, description: 'Number of image variations (1-4, default: 1)' },
49
+ { name: 'outputPath', type: 'string', required: false, description: 'File path to save the generated image (default: returns base64)' },
50
+ ],
51
+ },
52
+ // File Search - Store Management
53
+ {
54
+ name: 'create_store',
55
+ description: 'Create a new File Search store for indexing documents. Returns the store ID.',
56
+ params: [
57
+ { name: 'name', type: 'string', required: true, description: 'Local name for the store (used to reference it in other commands)' },
58
+ { name: 'displayName', type: 'string', required: false, description: 'Display name in Google Cloud (defaults to name)' },
59
+ ],
60
+ },
61
+ {
62
+ name: 'list_stores',
63
+ description: 'List all registered File Search stores with their metadata.',
64
+ params: [],
65
+ },
66
+ {
67
+ name: 'delete_store',
68
+ description: 'Delete a File Search store. The store must be empty (no indexed files).',
69
+ params: [
70
+ { name: 'name', type: 'string', required: true, description: 'Name of the store to delete' },
71
+ ],
72
+ },
73
+ // File Search - Indexing
74
+ {
75
+ name: 'index_file',
76
+ description: 'Index a single file into a File Search store. Supports text files, code, JSON, etc.',
77
+ params: [
78
+ { name: 'storeName', type: 'string', required: true, description: 'Name of the store to index into' },
79
+ { name: 'filePath', type: 'string', required: true, description: 'Absolute path to the file to index' },
80
+ ],
81
+ },
82
+ {
83
+ name: 'index_directory',
84
+ description: 'Index all supported files in a directory into a File Search store.',
85
+ params: [
86
+ { name: 'storeName', type: 'string', required: true, description: 'Name of the store to index into' },
87
+ { name: 'directoryPath', type: 'string', required: true, description: 'Absolute path to the directory to index' },
88
+ { name: 'extensions', type: 'string', required: false, description: 'Comma-separated file extensions to include (e.g., ".py,.js,.ts"). Defaults to common code/text files.' },
89
+ { name: 'excludeDirs', type: 'string', required: false, description: 'Comma-separated directory names to exclude (e.g., "node_modules,.git,venv")' },
90
+ ],
91
+ },
92
+ // File Search - Querying
93
+ {
94
+ name: 'query_store',
95
+ description: 'Query a File Search store with a natural language question. Returns an answer with citations.',
96
+ params: [
97
+ { name: 'storeName', type: 'string', required: true, description: 'Name of the store to query' },
98
+ { name: 'question', type: 'string', required: true, description: 'Natural language question to ask about the indexed content' },
99
+ { name: 'model', type: 'string', required: false, description: 'Gemini model to use (default: gemini-2.5-flash)' },
100
+ { name: 'filter', type: 'string', required: false, description: 'Metadata filter (e.g., "file_extension=.py")' },
101
+ ],
102
+ },
103
+ ];
104
+ // ============================================================================
105
+ // Image Generation
106
+ // ============================================================================
107
+ async function generateImage(params) {
108
+ let { prompt, aspectRatio = '1:1', negativePrompt, sampleCount = 1, outputPath, } = params;
109
+ if (!prompt || typeof prompt !== 'string') {
110
+ throw new Error('prompt is required and must be a string');
111
+ }
112
+ if (!GOOGLE_API_KEY) {
113
+ throw new Error('GOOGLE_API_KEY environment variable is not set');
114
+ }
115
+ // If no outputPath provided but DEFAULT_IMAGE_OUTPUT_DIR is set, generate a filename
116
+ if (!outputPath && DEFAULT_IMAGE_OUTPUT_DIR) {
117
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
118
+ const sanitizedPrompt = prompt.slice(0, 30).replace(/[^a-zA-Z0-9]/g, '_').replace(/_+/g, '_');
119
+ outputPath = path.join(DEFAULT_IMAGE_OUTPUT_DIR, `${timestamp}_${sanitizedPrompt}.png`);
120
+ }
121
+ // Build the prompt with negative prompt if provided
122
+ let fullPrompt = prompt;
123
+ if (negativePrompt && typeof negativePrompt === 'string') {
124
+ fullPrompt = prompt + '. Avoid: ' + negativePrompt;
125
+ }
126
+ // Use Imagen 4 model via Google AI API
127
+ const endpoint = BASE_URL + '/models/imagen-4.0-generate-001:predict?key=' + GOOGLE_API_KEY;
128
+ const response = await fetch(endpoint, {
129
+ method: 'POST',
130
+ headers: {
131
+ 'Content-Type': 'application/json',
132
+ },
133
+ body: JSON.stringify({
134
+ instances: [{ prompt: fullPrompt }],
135
+ parameters: {
136
+ sampleCount: Math.min(Math.max(Number(sampleCount) || 1, 1), 4),
137
+ aspectRatio,
138
+ outputOptions: {
139
+ mimeType: 'image/png',
140
+ },
141
+ },
142
+ }),
143
+ });
144
+ if (!response.ok) {
145
+ const errorText = await response.text();
146
+ throw new Error(`Imagen API error (${response.status}): ${errorText}`);
147
+ }
148
+ const data = await response.json();
149
+ // Handle both response formats (:predict uses predictions, legacy uses generatedImages)
150
+ const predictions = data.predictions || [];
151
+ const generatedImages = data.generatedImages || [];
152
+ // Normalize to unified format
153
+ const images = predictions.length > 0
154
+ ? predictions.map(p => ({ imageBytes: p.bytesBase64Encoded }))
155
+ : generatedImages.map(g => ({ imageBytes: g.image.imageBytes }));
156
+ if (images.length === 0) {
157
+ throw new Error('No images generated. The prompt may have been filtered for safety reasons.');
158
+ }
159
+ // If outputPath is provided, save the image(s) to disk
160
+ if (outputPath && typeof outputPath === 'string' && images.length > 0) {
161
+ const savedFiles = [];
162
+ for (let i = 0; i < images.length; i++) {
163
+ const imageData = images[i].imageBytes;
164
+ const imageBuffer = Buffer.from(imageData, 'base64');
165
+ let filePath = outputPath;
166
+ if (images.length > 1) {
167
+ const ext = path.extname(outputPath);
168
+ const base = outputPath.slice(0, -ext.length);
169
+ filePath = base + '_' + (i + 1) + ext;
170
+ }
171
+ // Ensure directory exists
172
+ const dir = path.dirname(filePath);
173
+ if (!fs.existsSync(dir)) {
174
+ fs.mkdirSync(dir, { recursive: true });
175
+ }
176
+ fs.writeFileSync(filePath, imageBuffer);
177
+ savedFiles.push(filePath);
178
+ }
179
+ return {
180
+ success: true,
181
+ savedFiles,
182
+ count: savedFiles.length,
183
+ message: 'Saved ' + savedFiles.length + ' image(s) to disk',
184
+ };
185
+ }
186
+ // Return the raw response with base64 data
187
+ return {
188
+ success: true,
189
+ images: images.map(img => ({ base64: img.imageBytes })),
190
+ count: images.length,
191
+ };
192
+ }
193
+ // ============================================================================
194
+ // File Search - Store Management
195
+ // ============================================================================
196
+ async function createStore(params) {
197
+ const { name, displayName } = params;
198
+ if (!name || typeof name !== 'string') {
199
+ throw new Error('name is required and must be a string');
200
+ }
201
+ if (!GOOGLE_API_KEY) {
202
+ throw new Error('GOOGLE_API_KEY environment variable is not set');
203
+ }
204
+ // Validate store name
205
+ if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
206
+ throw new Error('Store name must start with a letter and contain only letters, numbers, hyphens, and underscores');
207
+ }
208
+ if (name.length > 64) {
209
+ throw new Error('Store name too long (max 64 characters)');
210
+ }
211
+ // Check if store already exists locally
212
+ const stores = loadStores();
213
+ if (name in stores) {
214
+ throw new Error(`Store '${name}' already exists. Use a different name or delete it first.`);
215
+ }
216
+ // Create the store via API
217
+ const endpoint = `${BASE_URL}/fileSearchStores?key=${GOOGLE_API_KEY}`;
218
+ const response = await fetch(endpoint, {
219
+ method: 'POST',
220
+ headers: {
221
+ 'Content-Type': 'application/json',
222
+ },
223
+ body: JSON.stringify({
224
+ displayName: displayName || name,
225
+ }),
226
+ });
227
+ if (!response.ok) {
228
+ const errorText = await response.text();
229
+ throw new Error(`Failed to create store: ${errorText}`);
230
+ }
231
+ const storeData = await response.json();
232
+ // Register locally
233
+ stores[name] = {
234
+ id: storeData.name,
235
+ metadata: {
236
+ display_name: displayName || name,
237
+ created_at: new Date().toISOString(),
238
+ file_count: 0,
239
+ total_size: 0,
240
+ indexed_files: [],
241
+ },
242
+ };
243
+ saveStores(stores);
244
+ return {
245
+ success: true,
246
+ name,
247
+ storeId: storeData.name,
248
+ message: `Store '${name}' created successfully`,
249
+ };
250
+ }
251
+ async function listStores() {
252
+ const stores = loadStores();
253
+ const storeList = Object.entries(stores).map(([name, data]) => ({
254
+ name,
255
+ id: data.id,
256
+ ...data.metadata,
257
+ }));
258
+ return {
259
+ stores: storeList,
260
+ count: storeList.length,
261
+ };
262
+ }
263
+ async function deleteStore(params) {
264
+ const { name } = params;
265
+ if (!name || typeof name !== 'string') {
266
+ throw new Error('name is required and must be a string');
267
+ }
268
+ if (!GOOGLE_API_KEY) {
269
+ throw new Error('GOOGLE_API_KEY environment variable is not set');
270
+ }
271
+ const stores = loadStores();
272
+ if (!(name in stores)) {
273
+ throw new Error(`Store '${name}' not found`);
274
+ }
275
+ const storeId = stores[name].id;
276
+ // Delete from Google Cloud
277
+ const endpoint = `${BASE_URL}/${storeId}?key=${GOOGLE_API_KEY}`;
278
+ const response = await fetch(endpoint, {
279
+ method: 'DELETE',
280
+ });
281
+ if (!response.ok) {
282
+ const errorText = await response.text();
283
+ throw new Error(`Failed to delete store: ${errorText}`);
284
+ }
285
+ // Remove from local registry
286
+ delete stores[name];
287
+ saveStores(stores);
288
+ return {
289
+ success: true,
290
+ message: `Store '${name}' deleted successfully`,
291
+ };
292
+ }
293
+ // ============================================================================
294
+ // File Search - Indexing
295
+ // ============================================================================
296
+ // Supported file extensions
297
+ const SUPPORTED_EXTENSIONS = new Set([
298
+ // Code files
299
+ '.py', '.js', '.ts', '.jsx', '.tsx', '.java', '.go', '.rs', '.rb',
300
+ '.php', '.swift', '.kt', '.scala', '.c', '.cpp', '.h', '.hpp',
301
+ '.cs', '.vb', '.fs', '.r', '.m', '.mm', '.pl', '.pm', '.lua',
302
+ '.sh', '.bash', '.zsh', '.ps1', '.bat', '.cmd',
303
+ // Config/data files
304
+ '.json', '.yaml', '.yml', '.xml', '.toml', '.ini', '.cfg', '.conf',
305
+ '.properties',
306
+ // Documentation
307
+ '.md', '.markdown', '.txt', '.rst', '.adoc', '.tex',
308
+ // Web
309
+ '.html', '.htm', '.css', '.scss', '.sass', '.less',
310
+ // Database
311
+ '.sql',
312
+ // Other
313
+ '.csv', '.log', '.gitignore', '.dockerfile',
314
+ ]);
315
+ // Directories to skip by default
316
+ const DEFAULT_EXCLUDES = new Set([
317
+ 'node_modules', '.git', '.svn', '.hg', 'venv', '.venv', 'env',
318
+ '__pycache__', '.pytest_cache', '.mypy_cache', 'dist', 'build',
319
+ '.next', '.nuxt', 'vendor', 'target', 'bin', 'obj',
320
+ ]);
321
+ // Sensitive paths to never index
322
+ const SENSITIVE_DIRS = new Set([
323
+ '.ssh', '.gnupg', '.gpg', '.aws', '.azure', '.gcloud',
324
+ '.env', '.secrets', 'secrets',
325
+ ]);
326
+ const SENSITIVE_PATTERNS = [
327
+ /\.env(\..+)?$/i,
328
+ /\.pem$/i,
329
+ /\.key$/i,
330
+ /\.p12$/i,
331
+ /\.pfx$/i,
332
+ /id_rsa/i,
333
+ /id_ed25519/i,
334
+ /credentials\.json$/i,
335
+ /service[_-]?account.*\.json$/i,
336
+ /.*secret.*\.json$/i,
337
+ ];
338
+ function isSensitivePath(filePath) {
339
+ const parts = filePath.split(path.sep).map(p => p.toLowerCase());
340
+ // Check for sensitive directories
341
+ for (const dir of SENSITIVE_DIRS) {
342
+ if (parts.includes(dir.toLowerCase())) {
343
+ return true;
344
+ }
345
+ }
346
+ // Check for sensitive file patterns
347
+ const filename = path.basename(filePath);
348
+ for (const pattern of SENSITIVE_PATTERNS) {
349
+ if (pattern.test(filename)) {
350
+ return true;
351
+ }
352
+ }
353
+ return false;
354
+ }
355
+ async function indexFile(params) {
356
+ const { storeName, filePath } = params;
357
+ if (!storeName || typeof storeName !== 'string') {
358
+ throw new Error('storeName is required');
359
+ }
360
+ if (!filePath || typeof filePath !== 'string') {
361
+ throw new Error('filePath is required');
362
+ }
363
+ if (!GOOGLE_API_KEY) {
364
+ throw new Error('GOOGLE_API_KEY environment variable is not set');
365
+ }
366
+ // Check if file exists
367
+ if (!fs.existsSync(filePath)) {
368
+ throw new Error(`File not found: ${filePath}`);
369
+ }
370
+ // Security check
371
+ if (isSensitivePath(filePath)) {
372
+ throw new Error(`Refusing to index sensitive file: ${filePath}`);
373
+ }
374
+ const storeId = getStoreId(storeName);
375
+ // Read file content
376
+ const fileContent = fs.readFileSync(filePath);
377
+ const fileSize = fs.statSync(filePath).size;
378
+ const fileName = path.basename(filePath);
379
+ // Determine MIME type based on extension
380
+ const ext = path.extname(filePath).toLowerCase();
381
+ let mimeType = 'text/plain';
382
+ if (['.json'].includes(ext)) {
383
+ mimeType = 'application/json';
384
+ }
385
+ else if (['.html', '.htm'].includes(ext)) {
386
+ mimeType = 'text/html';
387
+ }
388
+ else if (['.xml'].includes(ext)) {
389
+ mimeType = 'application/xml';
390
+ }
391
+ else if (['.md', '.markdown'].includes(ext)) {
392
+ mimeType = 'text/markdown';
393
+ }
394
+ else if (['.css'].includes(ext)) {
395
+ mimeType = 'text/css';
396
+ }
397
+ else if (['.js', '.ts', '.jsx', '.tsx'].includes(ext)) {
398
+ mimeType = 'text/javascript';
399
+ }
400
+ else if (['.py'].includes(ext)) {
401
+ mimeType = 'text/x-python';
402
+ }
403
+ // Step 1: Upload file to Files API first
404
+ // Use media upload endpoint
405
+ const uploadEndpoint = `https://generativelanguage.googleapis.com/upload/v1beta/files?key=${GOOGLE_API_KEY}`;
406
+ // Generate a safe file ID (alphanumeric and dashes only, max 40 chars)
407
+ const safeFileName = fileName
408
+ .replace(/[^a-zA-Z0-9-]/g, '-')
409
+ .replace(/-+/g, '-')
410
+ .replace(/^-|-$/g, '')
411
+ .toLowerCase()
412
+ .slice(0, 35);
413
+ const fileId = `${safeFileName}-${Date.now().toString(36)}`;
414
+ const uploadResponse = await fetch(uploadEndpoint, {
415
+ method: 'POST',
416
+ headers: {
417
+ 'Content-Type': mimeType,
418
+ 'X-Goog-Upload-Protocol': 'raw',
419
+ 'X-Goog-Upload-Command': 'upload, finalize',
420
+ },
421
+ body: fileContent,
422
+ });
423
+ if (!uploadResponse.ok) {
424
+ const errorText = await uploadResponse.text();
425
+ throw new Error(`Failed to upload file to Files API: ${errorText}`);
426
+ }
427
+ const uploadData = await uploadResponse.json();
428
+ const uploadedFileName = uploadData.file?.name;
429
+ if (!uploadedFileName) {
430
+ throw new Error('File upload succeeded but no file name returned');
431
+ }
432
+ // Step 2: Import the uploaded file into the FileSearchStore
433
+ const importEndpoint = `${BASE_URL}/${storeId}:importFile?key=${GOOGLE_API_KEY}`;
434
+ const importResponse = await fetch(importEndpoint, {
435
+ method: 'POST',
436
+ headers: {
437
+ 'Content-Type': 'application/json',
438
+ },
439
+ body: JSON.stringify({
440
+ fileName: uploadedFileName,
441
+ }),
442
+ });
443
+ if (!importResponse.ok) {
444
+ const errorText = await importResponse.text();
445
+ throw new Error(`Failed to import file to store: ${errorText}`);
446
+ }
447
+ // Update local metadata
448
+ const stores = loadStores();
449
+ if (storeName in stores) {
450
+ const metadata = stores[storeName].metadata;
451
+ metadata.file_count = (metadata.file_count || 0) + 1;
452
+ metadata.total_size = (metadata.total_size || 0) + fileSize;
453
+ metadata.indexed_files = metadata.indexed_files || [];
454
+ if (!metadata.indexed_files.includes(filePath)) {
455
+ metadata.indexed_files.push(filePath);
456
+ }
457
+ metadata.last_indexed = new Date().toISOString();
458
+ saveStores(stores);
459
+ }
460
+ return {
461
+ success: true,
462
+ file: fileName,
463
+ size: fileSize,
464
+ uploadedAs: uploadedFileName,
465
+ message: `File '${fileName}' indexed successfully`,
466
+ };
467
+ }
468
+ async function indexDirectory(params) {
469
+ const { storeName, directoryPath, extensions, excludeDirs } = params;
470
+ if (!storeName || typeof storeName !== 'string') {
471
+ throw new Error('storeName is required');
472
+ }
473
+ if (!directoryPath || typeof directoryPath !== 'string') {
474
+ throw new Error('directoryPath is required');
475
+ }
476
+ if (!fs.existsSync(directoryPath)) {
477
+ throw new Error(`Directory not found: ${directoryPath}`);
478
+ }
479
+ if (!fs.statSync(directoryPath).isDirectory()) {
480
+ throw new Error(`Not a directory: ${directoryPath}`);
481
+ }
482
+ // Build extension set
483
+ let includeExtensions = SUPPORTED_EXTENSIONS;
484
+ if (extensions && typeof extensions === 'string') {
485
+ includeExtensions = new Set(extensions.split(',').map(e => e.trim().toLowerCase()));
486
+ }
487
+ // Build exclusion set
488
+ const excludeSet = new Set(DEFAULT_EXCLUDES);
489
+ if (excludeDirs && typeof excludeDirs === 'string') {
490
+ excludeDirs.split(',').forEach(d => excludeSet.add(d.trim()));
491
+ }
492
+ // Collect files to index
493
+ const filesToIndex = [];
494
+ function walkDir(dir) {
495
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
496
+ for (const entry of entries) {
497
+ const fullPath = path.join(dir, entry.name);
498
+ if (entry.isDirectory()) {
499
+ // Skip excluded directories
500
+ if (excludeSet.has(entry.name) || isSensitivePath(fullPath)) {
501
+ continue;
502
+ }
503
+ walkDir(fullPath);
504
+ }
505
+ else if (entry.isFile()) {
506
+ // Check extension
507
+ const ext = path.extname(entry.name).toLowerCase();
508
+ if (!includeExtensions.has(ext)) {
509
+ continue;
510
+ }
511
+ // Skip sensitive files
512
+ if (isSensitivePath(fullPath)) {
513
+ continue;
514
+ }
515
+ filesToIndex.push(fullPath);
516
+ }
517
+ }
518
+ }
519
+ walkDir(directoryPath);
520
+ if (filesToIndex.length === 0) {
521
+ return {
522
+ success: true,
523
+ indexed: 0,
524
+ errors: [],
525
+ message: 'No supported files found to index',
526
+ };
527
+ }
528
+ // Index files one by one
529
+ const results = {
530
+ indexed: 0,
531
+ failed: 0,
532
+ errors: [],
533
+ files: [],
534
+ };
535
+ for (const file of filesToIndex) {
536
+ try {
537
+ await indexFile({ storeName, filePath: file });
538
+ results.indexed++;
539
+ results.files.push(path.basename(file));
540
+ }
541
+ catch (error) {
542
+ results.failed++;
543
+ results.errors.push({
544
+ file: path.basename(file),
545
+ error: error instanceof Error ? error.message : String(error),
546
+ });
547
+ }
548
+ }
549
+ return {
550
+ success: true,
551
+ indexed: results.indexed,
552
+ failed: results.failed,
553
+ errors: results.errors,
554
+ message: `Indexed ${results.indexed} files (${results.failed} failed)`,
555
+ };
556
+ }
557
+ // ============================================================================
558
+ // File Search - Querying
559
+ // ============================================================================
560
+ async function queryStore(params) {
561
+ const { storeName, question, model = 'gemini-2.5-flash', filter } = params;
562
+ if (!storeName || typeof storeName !== 'string') {
563
+ throw new Error('storeName is required');
564
+ }
565
+ if (!question || typeof question !== 'string') {
566
+ throw new Error('question is required');
567
+ }
568
+ if (!GOOGLE_API_KEY) {
569
+ throw new Error('GOOGLE_API_KEY environment variable is not set');
570
+ }
571
+ const storeId = getStoreId(storeName);
572
+ // Build the FileSearch tool configuration
573
+ const fileSearchConfig = {
574
+ fileSearchStoreNames: [storeId],
575
+ };
576
+ if (filter && typeof filter === 'string') {
577
+ fileSearchConfig.metadataFilter = filter;
578
+ }
579
+ // Call generateContent with File Search tool
580
+ const endpoint = `${BASE_URL}/models/${model}:generateContent?key=${GOOGLE_API_KEY}`;
581
+ const response = await fetch(endpoint, {
582
+ method: 'POST',
583
+ headers: {
584
+ 'Content-Type': 'application/json',
585
+ },
586
+ body: JSON.stringify({
587
+ contents: [
588
+ {
589
+ parts: [{ text: question }],
590
+ },
591
+ ],
592
+ tools: [
593
+ {
594
+ fileSearch: fileSearchConfig,
595
+ },
596
+ ],
597
+ }),
598
+ });
599
+ if (!response.ok) {
600
+ const errorText = await response.text();
601
+ throw new Error(`Query failed: ${errorText}`);
602
+ }
603
+ const data = await response.json();
604
+ // Extract the answer
605
+ let answer = '';
606
+ if (data.candidates && data.candidates[0]?.content?.parts) {
607
+ for (const part of data.candidates[0].content.parts) {
608
+ if (part.text) {
609
+ answer += part.text;
610
+ }
611
+ }
612
+ }
613
+ // Extract citations from grounding metadata
614
+ const citations = [];
615
+ const groundingMetadata = data.candidates?.[0]?.groundingMetadata;
616
+ if (groundingMetadata?.groundingChunks) {
617
+ for (const chunk of groundingMetadata.groundingChunks) {
618
+ const ctx = chunk.retrievedContext;
619
+ if (ctx) {
620
+ let fileName = 'Unknown';
621
+ if (ctx.uri) {
622
+ fileName = ctx.uri.split('/').pop() || ctx.uri;
623
+ }
624
+ else if (ctx.title) {
625
+ fileName = ctx.title;
626
+ }
627
+ citations.push({
628
+ file: fileName,
629
+ text: ctx.text,
630
+ });
631
+ }
632
+ }
633
+ }
634
+ return {
635
+ success: true,
636
+ answer: answer.trim(),
637
+ citations,
638
+ model,
639
+ storeName,
640
+ };
641
+ }
642
+ // ============================================================================
643
+ // Backend Export
644
+ // ============================================================================
645
+ export const geminiBackend = {
646
+ name: 'gemini',
647
+ description: 'Google Gemini services: Imagen 4 image generation and File Search RAG.',
648
+ actions,
649
+ async execute(action, params) {
650
+ switch (action) {
651
+ // Image Generation
652
+ case 'generate_image':
653
+ return generateImage(params);
654
+ // File Search - Store Management
655
+ case 'create_store':
656
+ return createStore(params);
657
+ case 'list_stores':
658
+ return listStores();
659
+ case 'delete_store':
660
+ return deleteStore(params);
661
+ // File Search - Indexing
662
+ case 'index_file':
663
+ return indexFile(params);
664
+ case 'index_directory':
665
+ return indexDirectory(params);
666
+ // File Search - Querying
667
+ case 'query_store':
668
+ return queryStore(params);
669
+ default:
670
+ throw new Error('Unknown action: ' + action);
671
+ }
672
+ },
673
+ async healthCheck() {
674
+ const startTime = Date.now();
675
+ try {
676
+ if (!GOOGLE_API_KEY) {
677
+ return {
678
+ status: 'unavailable',
679
+ latency_ms: Date.now() - startTime,
680
+ error: 'GOOGLE_API_KEY environment variable is not set'
681
+ };
682
+ }
683
+ // Quick check that the API key format looks valid
684
+ if (GOOGLE_API_KEY.length < 20) {
685
+ return {
686
+ status: 'unavailable',
687
+ latency_ms: Date.now() - startTime,
688
+ error: 'GOOGLE_API_KEY appears to be invalid (too short)'
689
+ };
690
+ }
691
+ return { status: 'healthy', latency_ms: Date.now() - startTime };
692
+ }
693
+ catch (error) {
694
+ return {
695
+ status: 'unavailable',
696
+ latency_ms: Date.now() - startTime,
697
+ error: error instanceof Error ? error.message : 'Unknown error'
698
+ };
699
+ }
700
+ },
701
+ };
702
+ //# sourceMappingURL=gemini.js.map