nebula-notebook-mcp 0.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 (72) hide show
  1. package/README.md +314 -0
  2. package/bin/nebula-mcp.js +10 -0
  3. package/dist/circuit-breaker.d.ts +157 -0
  4. package/dist/circuit-breaker.d.ts.map +1 -0
  5. package/dist/circuit-breaker.js +237 -0
  6. package/dist/circuit-breaker.js.map +1 -0
  7. package/dist/errors.d.ts +72 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +314 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/index.d.ts +13 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +41 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/mcp/index.d.ts +8 -0
  16. package/dist/mcp/index.d.ts.map +1 -0
  17. package/dist/mcp/index.js +13 -0
  18. package/dist/mcp/index.js.map +1 -0
  19. package/dist/mcp/server.d.ts +31 -0
  20. package/dist/mcp/server.d.ts.map +1 -0
  21. package/dist/mcp/server.js +237 -0
  22. package/dist/mcp/server.js.map +1 -0
  23. package/dist/notebook/client.d.ts +643 -0
  24. package/dist/notebook/client.d.ts.map +1 -0
  25. package/dist/notebook/client.js +1720 -0
  26. package/dist/notebook/client.js.map +1 -0
  27. package/dist/notebook/index.d.ts +6 -0
  28. package/dist/notebook/index.d.ts.map +1 -0
  29. package/dist/notebook/index.js +6 -0
  30. package/dist/notebook/index.js.map +1 -0
  31. package/dist/notebook/tools.d.ts +244 -0
  32. package/dist/notebook/tools.d.ts.map +1 -0
  33. package/dist/notebook/tools.js +279 -0
  34. package/dist/notebook/tools.js.map +1 -0
  35. package/dist/tools/execution.d.ts +38 -0
  36. package/dist/tools/execution.d.ts.map +1 -0
  37. package/dist/tools/execution.js +116 -0
  38. package/dist/tools/execution.js.map +1 -0
  39. package/dist/tools/files.d.ts +70 -0
  40. package/dist/tools/files.d.ts.map +1 -0
  41. package/dist/tools/files.js +286 -0
  42. package/dist/tools/files.js.map +1 -0
  43. package/dist/tools/index.d.ts +74 -0
  44. package/dist/tools/index.d.ts.map +1 -0
  45. package/dist/tools/index.js +217 -0
  46. package/dist/tools/index.js.map +1 -0
  47. package/dist/tools/kernel.d.ts +36 -0
  48. package/dist/tools/kernel.d.ts.map +1 -0
  49. package/dist/tools/kernel.js +182 -0
  50. package/dist/tools/kernel.js.map +1 -0
  51. package/dist/tools/notebook.d.ts +252 -0
  52. package/dist/tools/notebook.d.ts.map +1 -0
  53. package/dist/tools/notebook.js +1089 -0
  54. package/dist/tools/notebook.js.map +1 -0
  55. package/dist/tools/types.d.ts +78 -0
  56. package/dist/tools/types.d.ts.map +1 -0
  57. package/dist/tools/types.js +8 -0
  58. package/dist/tools/types.js.map +1 -0
  59. package/dist/types.d.ts +473 -0
  60. package/dist/types.d.ts.map +1 -0
  61. package/dist/types.js +5 -0
  62. package/dist/types.js.map +1 -0
  63. package/dist/utils/imageResize.d.ts +24 -0
  64. package/dist/utils/imageResize.d.ts.map +1 -0
  65. package/dist/utils/imageResize.js +67 -0
  66. package/dist/utils/imageResize.js.map +1 -0
  67. package/dist/utils/polling.d.ts +40 -0
  68. package/dist/utils/polling.d.ts.map +1 -0
  69. package/dist/utils/polling.js +49 -0
  70. package/dist/utils/polling.js.map +1 -0
  71. package/package.json +61 -0
  72. package/setup-mcp.js +468 -0
package/setup-mcp.js ADDED
@@ -0,0 +1,468 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Nebula MCP Setup Script
5
+ *
6
+ * Automatically configures Nebula MCP server for detected AI tools:
7
+ * - Claude Code
8
+ * - Claude Desktop
9
+ * - Cursor IDE
10
+ * - Gemini CLI
11
+ * - Codex CLI (OpenAI)
12
+ * - VS Code / GitHub Copilot (workspace)
13
+ * - Antigravity
14
+ */
15
+
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ import { fileURLToPath } from 'url';
19
+ import { execSync } from 'child_process';
20
+ import readline from 'readline';
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = path.dirname(__filename);
24
+
25
+ // Get absolute path to nebula-mcp.js
26
+ const MCP_SERVER_PATH = path.join(__dirname, 'bin', 'nebula-mcp.js');
27
+
28
+ // Config file locations
29
+ const CONFIG_PATHS = {
30
+ claudeCode: path.join(process.env.HOME, '.claude.json'),
31
+ claudeDesktop: path.join(process.env.HOME, 'Library', 'Application Support', 'Claude', 'claude_desktop_config.json'),
32
+ cursor: path.join(process.env.HOME, '.cursor', 'mcp.json'),
33
+ gemini: path.join(process.env.HOME, '.gemini', 'settings.json'),
34
+ codex: path.join(process.env.HOME, '.codex', 'config.toml'),
35
+ vscodeWorkspace: path.join(process.cwd(), '.vscode', 'mcp.json'),
36
+ antigravity: path.join(process.env.HOME, '.gemini', 'antigravity', 'mcp_config.json'),
37
+ };
38
+
39
+ // When running from an npm install (node_modules, including the npx cache),
40
+ // write configs that invoke `npx -y nebula-notebook-mcp` — stable across npx
41
+ // cache evictions and package updates. A repo checkout keeps the absolute
42
+ // path (works offline and tracks local edits).
43
+ const IS_NPM_INSTALL = __dirname.split(path.sep).includes('node_modules');
44
+ const MCP_COMMAND = IS_NPM_INSTALL ? 'npx' : 'node';
45
+ const MCP_ARGS = IS_NPM_INSTALL ? ['-y', 'nebula-notebook-mcp'] : [MCP_SERVER_PATH];
46
+
47
+ // Nebula MCP server configuration
48
+ const NEBULA_MCP_CONFIG = {
49
+ command: MCP_COMMAND,
50
+ args: MCP_ARGS,
51
+ };
52
+
53
+ const NEBULA_MCP_VSCODE_CONFIG = {
54
+ type: 'stdio',
55
+ command: MCP_COMMAND,
56
+ args: MCP_ARGS,
57
+ };
58
+
59
+ // Helper to check if command exists
60
+ function commandExists(command) {
61
+ try {
62
+ execSync(`which ${command}`, { stdio: 'ignore' });
63
+ return true;
64
+ } catch {
65
+ return false;
66
+ }
67
+ }
68
+
69
+ // Helper to read JSON file
70
+ function readJSON(filePath) {
71
+ try {
72
+ return JSON.parse(fs.readFileSync(filePath, 'utf8'));
73
+ } catch {
74
+ return null;
75
+ }
76
+ }
77
+
78
+ // Helper to write JSON file
79
+ function writeJSON(filePath, data) {
80
+ const dir = path.dirname(filePath);
81
+ if (!fs.existsSync(dir)) {
82
+ fs.mkdirSync(dir, { recursive: true });
83
+ }
84
+ fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
85
+ }
86
+
87
+ // Helper to backup file
88
+ function backupFile(filePath) {
89
+ if (fs.existsSync(filePath)) {
90
+ const backupPath = `${filePath}.backup-${Date.now()}`;
91
+ fs.copyFileSync(filePath, backupPath);
92
+ return backupPath;
93
+ }
94
+ return null;
95
+ }
96
+
97
+ function isPlainObject(value) {
98
+ return value && typeof value === 'object' && !Array.isArray(value);
99
+ }
100
+
101
+ function sortKeys(value) {
102
+ if (Array.isArray(value)) {
103
+ return value.map(sortKeys);
104
+ }
105
+ if (!isPlainObject(value)) {
106
+ return value;
107
+ }
108
+ return Object.keys(value)
109
+ .sort()
110
+ .reduce((acc, key) => {
111
+ acc[key] = sortKeys(value[key]);
112
+ return acc;
113
+ }, {});
114
+ }
115
+
116
+ function stableStringify(value) {
117
+ return JSON.stringify(sortKeys(value));
118
+ }
119
+
120
+ function deepEqual(a, b) {
121
+ return stableStringify(a) === stableStringify(b);
122
+ }
123
+
124
+ function mergeServerConfig(existing, desired) {
125
+ if (!isPlainObject(existing)) {
126
+ return desired;
127
+ }
128
+ const merged = { ...existing, ...desired };
129
+ if (isPlainObject(existing.env) || isPlainObject(desired.env)) {
130
+ merged.env = {
131
+ ...(isPlainObject(existing.env) ? existing.env : {}),
132
+ ...(isPlainObject(desired.env) ? desired.env : {}),
133
+ };
134
+ }
135
+ // We intentionally do NOT rely on a default server URL via env.
136
+ // Remove legacy NEBULA_URL to avoid accidental connections to the wrong instance.
137
+ if (isPlainObject(merged.env)) {
138
+ delete merged.env.NEBULA_URL;
139
+ if (Object.keys(merged.env).length === 0) {
140
+ delete merged.env;
141
+ }
142
+ }
143
+ return merged;
144
+ }
145
+
146
+ function upsertMcpServer(config, rootKey, serverName, desiredConfig) {
147
+ if (!isPlainObject(config[rootKey])) {
148
+ config[rootKey] = {};
149
+ }
150
+ const existing = config[rootKey][serverName];
151
+ const next = mergeServerConfig(existing, desiredConfig);
152
+ if (deepEqual(existing, next)) {
153
+ return false;
154
+ }
155
+ config[rootKey][serverName] = next;
156
+ return true;
157
+ }
158
+
159
+ // Detect installed AI tools
160
+ function detectTools() {
161
+ const tools = {};
162
+ const hasVSCodeCli = commandExists('code') || commandExists('code-insiders');
163
+ const hasAntigravityCli = commandExists('antigravity') || commandExists('agy');
164
+ const hasVSCodeWorkspace = fs.existsSync(path.join(process.cwd(), '.vscode'));
165
+
166
+ // Claude Code
167
+ if (commandExists('claude')) {
168
+ tools.claudeCode = { name: 'Claude Code', configPath: CONFIG_PATHS.claudeCode };
169
+ }
170
+
171
+ // Claude Desktop
172
+ if (fs.existsSync(path.dirname(CONFIG_PATHS.claudeDesktop))) {
173
+ tools.claudeDesktop = { name: 'Claude Desktop', configPath: CONFIG_PATHS.claudeDesktop };
174
+ }
175
+
176
+ // Cursor
177
+ if (commandExists('cursor') || fs.existsSync(path.join(process.env.HOME, '.cursor'))) {
178
+ tools.cursor = { name: 'Cursor IDE', configPath: CONFIG_PATHS.cursor };
179
+ }
180
+
181
+ // Gemini CLI
182
+ if (commandExists('gemini')) {
183
+ tools.gemini = { name: 'Gemini CLI', configPath: CONFIG_PATHS.gemini };
184
+ }
185
+
186
+ // Codex CLI
187
+ if (commandExists('codex')) {
188
+ tools.codex = { name: 'Codex CLI', configPath: CONFIG_PATHS.codex };
189
+ }
190
+
191
+ // VS Code / GitHub Copilot / Antigravity workspace config
192
+ if (hasVSCodeCli || hasAntigravityCli || hasVSCodeWorkspace) {
193
+ tools.vscodeWorkspace = {
194
+ name: 'VS Code / GitHub Copilot / Antigravity (workspace)',
195
+ configPath: CONFIG_PATHS.vscodeWorkspace
196
+ };
197
+ }
198
+
199
+ // Antigravity global config (if available)
200
+ if (
201
+ hasAntigravityCli ||
202
+ fs.existsSync(CONFIG_PATHS.antigravity) ||
203
+ fs.existsSync(path.dirname(CONFIG_PATHS.antigravity))
204
+ ) {
205
+ tools.antigravity = {
206
+ name: 'Antigravity (global)',
207
+ configPath: CONFIG_PATHS.antigravity
208
+ };
209
+ }
210
+
211
+ return tools;
212
+ }
213
+
214
+ function configureJsonServer(configPath, rootKey, serverConfig, options = {}) {
215
+ const config = readJSON(configPath) || {};
216
+ const changed = upsertMcpServer(config, rootKey, 'nebula-notebook', serverConfig);
217
+
218
+ if (options.dryRun) {
219
+ return { changed };
220
+ }
221
+ if (!changed) {
222
+ return { changed: false };
223
+ }
224
+
225
+ writeJSON(configPath, config);
226
+ return { changed: true };
227
+ }
228
+
229
+ function buildCodexBlock() {
230
+ return [
231
+ '[mcp_servers.nebula-notebook]',
232
+ `command = "${MCP_COMMAND}"`,
233
+ `args = [${MCP_ARGS.map((a) => `"${a}"`).join(', ')}]`,
234
+ ].join('\n');
235
+ }
236
+
237
+ function normalizeToml(value) {
238
+ return value.trim().replace(/\s+/g, ' ');
239
+ }
240
+
241
+ function escapeRegExp(value) {
242
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
243
+ }
244
+
245
+ function extractCodexBlock(content) {
246
+ const baseMatch = content.match(/\[mcp_servers\.nebula-notebook\][\s\S]*?(?=\n\[|$)/);
247
+ const parts = [];
248
+ if (baseMatch) {
249
+ parts.push(baseMatch[0].trim());
250
+ }
251
+ return parts.join('\n\n').trim();
252
+ }
253
+
254
+ function codexHasRequiredConfig(content) {
255
+ const baseMatch = content.match(/\[mcp_servers\.nebula-notebook\][\s\S]*?(?=\n\[|$)/);
256
+ if (!baseMatch) {
257
+ return false;
258
+ }
259
+
260
+ const baseBlock = baseMatch[0];
261
+ const hasCommand = new RegExp(`command\\s*=\\s*"${escapeRegExp(MCP_COMMAND)}"`).test(baseBlock);
262
+ const expectedArgs = MCP_ARGS.map((a) => `"${escapeRegExp(a)}"`).join('\\s*,\\s*');
263
+ const hasArgs = new RegExp(`args\\s*=\\s*\\[\\s*${expectedArgs}\\s*\\]`).test(baseBlock);
264
+ const envMatch = content.match(/\[mcp_servers\.nebula-notebook\.env\][\s\S]*?(?=\n\[|$)/);
265
+ // If an env block exists, treat as not "clean" since we want to remove legacy NEBULA_URL defaults.
266
+ const hasEnvBlock = !!envMatch;
267
+
268
+ return hasCommand && hasArgs && !hasEnvBlock;
269
+ }
270
+
271
+ function stripCodexBlock(content) {
272
+ return content
273
+ .replace(/\[mcp_servers\.nebula-notebook\][\s\S]*?(?=\n\[|$)/g, '')
274
+ .replace(/\[mcp_servers\.nebula-notebook\.env\][\s\S]*?(?=\n\[|$)/g, '');
275
+ }
276
+
277
+ function prepareCodexContent(existingContent) {
278
+ const desiredBlock = buildCodexBlock().trim();
279
+ const baseCount = (existingContent.match(/\[mcp_servers\.nebula-notebook\]/g) || []).length;
280
+ const envCount = (existingContent.match(/\[mcp_servers\.nebula-notebook\.env\]/g) || []).length;
281
+ const existingBlock = extractCodexBlock(existingContent);
282
+ const matchesRequired = codexHasRequiredConfig(existingContent);
283
+ const hasDuplicates = baseCount > 1 || envCount > 1;
284
+ const hasLegacyEnv = envCount > 0;
285
+ const isSame =
286
+ !hasDuplicates &&
287
+ !hasLegacyEnv &&
288
+ (matchesRequired ||
289
+ (existingBlock &&
290
+ normalizeToml(existingBlock) === normalizeToml(desiredBlock) &&
291
+ baseCount <= 1));
292
+
293
+ if (isSame) {
294
+ return { changed: false, content: existingContent };
295
+ }
296
+
297
+ let baseContent = stripCodexBlock(existingContent).trimEnd();
298
+ if (baseContent.length > 0) {
299
+ baseContent += '\n\n';
300
+ }
301
+ const nextContent = `${baseContent}${desiredBlock}\n`;
302
+ return { changed: true, content: nextContent };
303
+ }
304
+
305
+ // Configure Claude Code
306
+ function configureClaudeCode(configPath, options = {}) {
307
+ return configureJsonServer(configPath, 'mcpServers', NEBULA_MCP_CONFIG, options);
308
+ }
309
+
310
+ // Configure Claude Desktop
311
+ function configureClaudeDesktop(configPath, options = {}) {
312
+ return configureJsonServer(configPath, 'mcpServers', NEBULA_MCP_CONFIG, options);
313
+ }
314
+
315
+ // Configure Cursor
316
+ function configureCursor(configPath, options = {}) {
317
+ return configureJsonServer(configPath, 'mcpServers', NEBULA_MCP_CONFIG, options);
318
+ }
319
+
320
+ // Configure Gemini CLI
321
+ function configureGemini(configPath, options = {}) {
322
+ return configureJsonServer(configPath, 'mcpServers', NEBULA_MCP_CONFIG, options);
323
+ }
324
+
325
+ // Configure VS Code / GitHub Copilot workspace config
326
+ function configureVSCodeWorkspace(configPath, options = {}) {
327
+ return configureJsonServer(configPath, 'servers', NEBULA_MCP_VSCODE_CONFIG, options);
328
+ }
329
+
330
+ // Configure Antigravity global config
331
+ function configureAntigravity(configPath, options = {}) {
332
+ return configureJsonServer(configPath, 'mcpServers', NEBULA_MCP_CONFIG, options);
333
+ }
334
+
335
+ // Configure Codex CLI (TOML format)
336
+ function configureCodex(configPath, options = {}) {
337
+ const existingContent = fs.existsSync(configPath) ? fs.readFileSync(configPath, 'utf8') : '';
338
+ const { changed, content } = prepareCodexContent(existingContent);
339
+
340
+ if (options.dryRun) {
341
+ return { changed };
342
+ }
343
+ if (!changed) {
344
+ return { changed: false };
345
+ }
346
+
347
+ const dir = path.dirname(configPath);
348
+ if (!fs.existsSync(dir)) {
349
+ fs.mkdirSync(dir, { recursive: true });
350
+ }
351
+
352
+ fs.writeFileSync(configPath, content);
353
+ return { changed: true };
354
+ }
355
+
356
+ // Prompt user for confirmation
357
+ function promptUser(question) {
358
+ const rl = readline.createInterface({
359
+ input: process.stdin,
360
+ output: process.stdout
361
+ });
362
+
363
+ return new Promise((resolve) => {
364
+ rl.question(question, (answer) => {
365
+ rl.close();
366
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
367
+ });
368
+ });
369
+ }
370
+
371
+ // Main setup function
372
+ async function main() {
373
+ console.log('\nšŸ” Detecting installed AI tools...\n');
374
+
375
+ const tools = detectTools();
376
+ const toolNames = Object.keys(tools);
377
+
378
+ if (toolNames.length === 0) {
379
+ console.log('āŒ No supported AI tools detected.');
380
+ console.log('\nSupported tools: Claude Code, Claude Desktop, Cursor, Gemini CLI, Codex CLI, VS Code / GitHub Copilot, Antigravity');
381
+ process.exit(1);
382
+ }
383
+
384
+ console.log('Found:');
385
+ toolNames.forEach(key => {
386
+ console.log(` āœ“ ${tools[key].name}`);
387
+ });
388
+ console.log('');
389
+
390
+ // Show configuration details
391
+ console.log('šŸ“¦ Nebula MCP server will be configured at:');
392
+ console.log(` ${MCP_SERVER_PATH}\n`);
393
+
394
+ console.log('āš™ļø Configure Nebula MCP for:');
395
+ toolNames.forEach(key => {
396
+ console.log(` [āœ“] ${tools[key].name} (${tools[key].configPath})`);
397
+ });
398
+ console.log('');
399
+
400
+ // Confirm with user
401
+ const confirmed = await promptUser('Continue? (y/N): ');
402
+ if (!confirmed) {
403
+ console.log('āŒ Setup cancelled.');
404
+ process.exit(0);
405
+ }
406
+
407
+ const configurators = {
408
+ claudeCode: configureClaudeCode,
409
+ claudeDesktop: configureClaudeDesktop,
410
+ cursor: configureCursor,
411
+ gemini: configureGemini,
412
+ codex: configureCodex,
413
+ vscodeWorkspace: configureVSCodeWorkspace,
414
+ antigravity: configureAntigravity,
415
+ };
416
+
417
+ const plan = {};
418
+ toolNames.forEach(key => {
419
+ plan[key] = configurators[key](tools[key].configPath, { dryRun: true });
420
+ });
421
+
422
+ console.log('\nšŸ’¾ Backing up configs...');
423
+ const backups = {};
424
+ toolNames.forEach(key => {
425
+ if (!plan[key]?.changed) {
426
+ return;
427
+ }
428
+ const backup = backupFile(tools[key].configPath);
429
+ if (backup) {
430
+ backups[key] = backup;
431
+ console.log(`āœ“ Backed up ${path.basename(tools[key].configPath)} → ${path.basename(backup)}`);
432
+ }
433
+ });
434
+
435
+ // Configure each tool
436
+ console.log('');
437
+ for (const key of toolNames) {
438
+ try {
439
+ console.log(`āš™ļø Configuring ${tools[key].name}...`);
440
+ if (!plan[key]?.changed) {
441
+ console.log(`↷ Already configured in ${path.basename(tools[key].configPath)}`);
442
+ continue;
443
+ }
444
+ configurators[key](tools[key].configPath);
445
+ console.log(`āœ“ Added/updated nebula-notebook in ${path.basename(tools[key].configPath)}`);
446
+ } catch (error) {
447
+ console.error(`āœ— Failed to configure ${tools[key].name}: ${error.message}`);
448
+ if (backups[key]) {
449
+ console.log(` → Restored from backup: ${backups[key]}`);
450
+ fs.copyFileSync(backups[key], tools[key].configPath);
451
+ }
452
+ }
453
+ }
454
+
455
+ console.log('\nāœ… Setup complete! Restart your AI tools to use Nebula MCP.');
456
+ console.log('\nVerify with:');
457
+ if (tools.claudeCode) console.log(' - claude mcp list');
458
+ if (tools.cursor) console.log(' - Cursor → Settings → Model Context Protocol');
459
+ if (tools.gemini) console.log(' - gemini mcp list');
460
+ if (tools.codex) console.log(' - codex mcp list');
461
+ if (tools.vscodeWorkspace) console.log(' - VS Code → Settings → Model Context Protocol');
462
+ console.log('');
463
+ }
464
+
465
+ main().catch(error => {
466
+ console.error('āŒ Setup failed:', error.message);
467
+ process.exit(1);
468
+ });