@nerviq/cli 1.0.1 → 1.2.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.
- package/bin/cli.js +170 -73
- package/package.json +1 -1
- package/src/activity.js +20 -0
- package/src/aider/domain-packs.js +27 -2
- package/src/aider/mcp-packs.js +231 -0
- package/src/aider/techniques.js +3210 -1397
- package/src/audit.js +257 -2
- package/src/catalog.js +18 -2
- package/src/codex/domain-packs.js +23 -1
- package/src/codex/mcp-packs.js +254 -0
- package/src/codex/techniques.js +4738 -3257
- package/src/copilot/domain-packs.js +23 -1
- package/src/copilot/mcp-packs.js +254 -0
- package/src/copilot/techniques.js +3433 -1936
- package/src/cursor/domain-packs.js +23 -1
- package/src/cursor/mcp-packs.js +257 -0
- package/src/cursor/techniques.js +3697 -1869
- package/src/deprecation.js +98 -0
- package/src/domain-pack-expansion.js +571 -0
- package/src/domain-packs.js +25 -2
- package/src/formatters/otel.js +151 -0
- package/src/gemini/domain-packs.js +23 -1
- package/src/gemini/mcp-packs.js +257 -0
- package/src/gemini/techniques.js +3734 -2238
- package/src/integrations.js +194 -0
- package/src/mcp-packs.js +233 -0
- package/src/opencode/domain-packs.js +23 -1
- package/src/opencode/mcp-packs.js +231 -0
- package/src/opencode/techniques.js +3500 -1687
- package/src/org.js +68 -0
- package/src/source-urls.js +410 -260
- package/src/stack-checks.js +565 -0
- package/src/supplemental-checks.js +767 -0
- package/src/techniques.js +2929 -1449
- package/src/telemetry.js +160 -0
- package/src/windsurf/domain-packs.js +23 -1
- package/src/windsurf/mcp-packs.js +257 -0
- package/src/windsurf/techniques.js +3647 -1834
- package/src/workspace.js +233 -0
package/src/telemetry.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nerviq Opt-In Telemetry Foundation
|
|
3
|
+
*
|
|
4
|
+
* Collects anonymous usage events ONLY when NERVIQ_TELEMETRY=1 is set.
|
|
5
|
+
* No PII, no file contents, no absolute paths are ever stored.
|
|
6
|
+
* Events are stored locally in <projectDir>/.nerviq/telemetry.json.
|
|
7
|
+
*
|
|
8
|
+
* This module is the foundation layer — actual transmission to a dashboard
|
|
9
|
+
* is an explicit opt-in step configured separately.
|
|
10
|
+
*
|
|
11
|
+
* Privacy guarantees:
|
|
12
|
+
* - No usernames, emails, or identifiers
|
|
13
|
+
* - No file contents or code
|
|
14
|
+
* - No absolute paths (only hashed project fingerprint)
|
|
15
|
+
* - Stored only on local disk
|
|
16
|
+
* - Never sent anywhere without additional explicit configuration
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const os = require('os');
|
|
24
|
+
const crypto = require('crypto');
|
|
25
|
+
|
|
26
|
+
const TELEMETRY_FILE = path.join(os.homedir(), '.nerviq', 'telemetry.json');
|
|
27
|
+
const MAX_EVENTS = 500; // cap file size at ~500 events
|
|
28
|
+
|
|
29
|
+
// ─── Opt-in check ─────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns true only when the user has explicitly set NERVIQ_TELEMETRY=1.
|
|
33
|
+
* Telemetry is opt-IN, not opt-out.
|
|
34
|
+
* @returns {boolean}
|
|
35
|
+
*/
|
|
36
|
+
function shouldCollectTelemetry() {
|
|
37
|
+
return process.env.NERVIQ_TELEMETRY === '1';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ─── Anonymous fingerprinting ─────────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Creates a one-way hash of the project directory.
|
|
44
|
+
* This allows grouping events by project without exposing the path.
|
|
45
|
+
* @param {string} dir
|
|
46
|
+
* @returns {string} 8-char hex fingerprint
|
|
47
|
+
*/
|
|
48
|
+
function hashProject(dir) {
|
|
49
|
+
try {
|
|
50
|
+
return crypto.createHash('sha256').update(dir).digest('hex').slice(0, 8);
|
|
51
|
+
} catch {
|
|
52
|
+
return 'unknown';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ─── Event collection ────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Collect an anonymous usage event and append it to the local telemetry file.
|
|
60
|
+
* Does nothing unless shouldCollectTelemetry() returns true.
|
|
61
|
+
*
|
|
62
|
+
* @param {string} event - Event name (e.g. 'audit', 'setup', 'convert')
|
|
63
|
+
* @param {object} [data] - Additional anonymous data
|
|
64
|
+
* @param {string} [data.platform] - Platform name (claude, codex, etc.)
|
|
65
|
+
* @param {number} [data.score] - Audit score
|
|
66
|
+
* @param {number} [data.checkCount] - Total checks evaluated
|
|
67
|
+
* @param {number} [data.durationMs] - Execution time in ms
|
|
68
|
+
* @param {string} [data.dir] - Project dir (hashed before storage)
|
|
69
|
+
* @returns {object|null} The recorded event object, or null if telemetry is off
|
|
70
|
+
*/
|
|
71
|
+
function collectAnonymousEvent(event, data = {}) {
|
|
72
|
+
if (!shouldCollectTelemetry()) return null;
|
|
73
|
+
|
|
74
|
+
const record = {
|
|
75
|
+
event: String(event),
|
|
76
|
+
platform: data.platform || null,
|
|
77
|
+
score: typeof data.score === 'number' ? data.score : null,
|
|
78
|
+
checkCount: typeof data.checkCount === 'number' ? data.checkCount : null,
|
|
79
|
+
durationMs: typeof data.durationMs === 'number' ? Math.round(data.durationMs) : null,
|
|
80
|
+
timestamp: new Date().toISOString(),
|
|
81
|
+
nodeVersion: process.version,
|
|
82
|
+
os: `${os.platform()}-${os.arch()}`,
|
|
83
|
+
projectFingerprint: data.dir ? hashProject(data.dir) : null,
|
|
84
|
+
// Explicitly omit: paths, file contents, usernames, email, tokens
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
const telemetryDir = path.dirname(TELEMETRY_FILE);
|
|
89
|
+
fs.mkdirSync(telemetryDir, { recursive: true });
|
|
90
|
+
|
|
91
|
+
let events = [];
|
|
92
|
+
if (fs.existsSync(TELEMETRY_FILE)) {
|
|
93
|
+
try {
|
|
94
|
+
const raw = fs.readFileSync(TELEMETRY_FILE, 'utf8');
|
|
95
|
+
const parsed = JSON.parse(raw);
|
|
96
|
+
events = Array.isArray(parsed.events) ? parsed.events : [];
|
|
97
|
+
} catch {
|
|
98
|
+
events = [];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
events.push(record);
|
|
103
|
+
|
|
104
|
+
// Cap at MAX_EVENTS to prevent unbounded growth
|
|
105
|
+
if (events.length > MAX_EVENTS) {
|
|
106
|
+
events = events.slice(events.length - MAX_EVENTS);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const payload = {
|
|
110
|
+
version: 1,
|
|
111
|
+
telemetryOptIn: true,
|
|
112
|
+
note: 'Local telemetry only. Set NERVIQ_TELEMETRY=0 or unset to disable.',
|
|
113
|
+
events,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
fs.writeFileSync(TELEMETRY_FILE, JSON.stringify(payload, null, 2), 'utf8');
|
|
117
|
+
} catch {
|
|
118
|
+
// Telemetry failures are always silent — never block main flow
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return record;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ─── Read local telemetry ─────────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Read the local telemetry file.
|
|
128
|
+
* @returns {{ version: number, events: object[] } | null}
|
|
129
|
+
*/
|
|
130
|
+
function readLocalTelemetry() {
|
|
131
|
+
try {
|
|
132
|
+
if (!fs.existsSync(TELEMETRY_FILE)) return null;
|
|
133
|
+
return JSON.parse(fs.readFileSync(TELEMETRY_FILE, 'utf8'));
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Clear all local telemetry events.
|
|
141
|
+
* @returns {boolean} true if cleared successfully
|
|
142
|
+
*/
|
|
143
|
+
function clearLocalTelemetry() {
|
|
144
|
+
try {
|
|
145
|
+
if (fs.existsSync(TELEMETRY_FILE)) {
|
|
146
|
+
fs.writeFileSync(TELEMETRY_FILE, JSON.stringify({ version: 1, events: [] }, null, 2), 'utf8');
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
} catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
shouldCollectTelemetry,
|
|
156
|
+
collectAnonymousEvent,
|
|
157
|
+
readLocalTelemetry,
|
|
158
|
+
clearLocalTelemetry,
|
|
159
|
+
TELEMETRY_FILE,
|
|
160
|
+
};
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* - No background agents (unlike Cursor)
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
const
|
|
13
|
+
const { buildAdditionalDomainPacks, detectAdditionalDomainPacks } = require('../domain-pack-expansion');
|
|
14
|
+
|
|
15
|
+
const BASE_WINDSURF_DOMAIN_PACKS = [
|
|
14
16
|
{
|
|
15
17
|
key: 'baseline-general',
|
|
16
18
|
label: 'Baseline General',
|
|
@@ -173,6 +175,13 @@ const WINDSURF_DOMAIN_PACKS = [
|
|
|
173
175
|
},
|
|
174
176
|
];
|
|
175
177
|
|
|
178
|
+
const WINDSURF_DOMAIN_PACKS = [
|
|
179
|
+
...BASE_WINDSURF_DOMAIN_PACKS,
|
|
180
|
+
...buildAdditionalDomainPacks('windsurf', {
|
|
181
|
+
existingKeys: new Set(BASE_WINDSURF_DOMAIN_PACKS.map((pack) => pack.key)),
|
|
182
|
+
}),
|
|
183
|
+
];
|
|
184
|
+
|
|
176
185
|
function uniqueByKey(items) {
|
|
177
186
|
const seen = new Set();
|
|
178
187
|
const result = [];
|
|
@@ -330,6 +339,19 @@ function detectWindsurfDomainPacks(ctx, stacks = [], assets = {}) {
|
|
|
330
339
|
addMatch('security-focused', ['Detected security-focused repo signals.', ctx.fileContent('SECURITY.md') ? 'SECURITY.md present.' : null]);
|
|
331
340
|
}
|
|
332
341
|
|
|
342
|
+
detectAdditionalDomainPacks({
|
|
343
|
+
ctx,
|
|
344
|
+
pkg,
|
|
345
|
+
deps,
|
|
346
|
+
stackKeys,
|
|
347
|
+
addMatch,
|
|
348
|
+
hasBackend,
|
|
349
|
+
hasFrontend,
|
|
350
|
+
hasInfra,
|
|
351
|
+
hasCi,
|
|
352
|
+
isEnterpriseGoverned,
|
|
353
|
+
});
|
|
354
|
+
|
|
333
355
|
if (matches.length === 0) {
|
|
334
356
|
addMatch('baseline-general', [
|
|
335
357
|
'No stronger platform-specific domain dominated, so a safe general Windsurf baseline is the best starting point.',
|
|
@@ -359,6 +359,237 @@ const WINDSURF_MCP_PACKS = [
|
|
|
359
359
|
jsonProjection: { command: 'npx', args: ['-y', 'huggingface-mcp-server'], env: { HF_TOKEN: '${env:HF_TOKEN}' } },
|
|
360
360
|
excludeTools: [],
|
|
361
361
|
},
|
|
362
|
+
// ── 23 new packs ─────────────────────────────────────────────────────────
|
|
363
|
+
{
|
|
364
|
+
key: 'supabase-mcp', label: 'Supabase',
|
|
365
|
+
description: 'Database, auth, and storage for Supabase.',
|
|
366
|
+
useWhen: 'Repos using Supabase.',
|
|
367
|
+
adoption: 'Requires: SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY.',
|
|
368
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['SUPABASE_URL', 'SUPABASE_SERVICE_ROLE_KEY'],
|
|
369
|
+
serverName: 'supabase',
|
|
370
|
+
jsonProjection: { command: 'npx', args: ['-y', '@supabase/mcp-server-supabase@latest'], env: { SUPABASE_URL: '${env:SUPABASE_URL}', SUPABASE_SERVICE_ROLE_KEY: '${env:SUPABASE_SERVICE_ROLE_KEY}' } },
|
|
371
|
+
excludeTools: ['delete_project', 'drop_table'],
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
key: 'prisma-mcp', label: 'Prisma ORM',
|
|
375
|
+
description: 'Schema inspection and migrations via Prisma.',
|
|
376
|
+
useWhen: 'Repos with a Prisma schema.',
|
|
377
|
+
adoption: 'Requires: DATABASE_URL.',
|
|
378
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['DATABASE_URL'],
|
|
379
|
+
serverName: 'prisma',
|
|
380
|
+
jsonProjection: { command: 'npx', args: ['-y', 'prisma-mcp-server@latest'], env: { DATABASE_URL: '${env:DATABASE_URL}' } },
|
|
381
|
+
excludeTools: ['drop_database'],
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
key: 'vercel-mcp', label: 'Vercel',
|
|
385
|
+
description: 'Deployment management via Vercel.',
|
|
386
|
+
useWhen: 'Repos deployed on Vercel.',
|
|
387
|
+
adoption: 'Requires: VERCEL_TOKEN.',
|
|
388
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['VERCEL_TOKEN'],
|
|
389
|
+
serverName: 'vercel',
|
|
390
|
+
jsonProjection: { command: 'npx', args: ['-y', '@vercel/mcp-server@latest'], env: { VERCEL_TOKEN: '${env:VERCEL_TOKEN}' } },
|
|
391
|
+
excludeTools: ['delete_project', 'delete_deployment'],
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
key: 'cloudflare-mcp', label: 'Cloudflare',
|
|
395
|
+
description: 'Workers, KV, R2, and D1 management.',
|
|
396
|
+
useWhen: 'Repos using Cloudflare edge.',
|
|
397
|
+
adoption: 'Requires: CLOUDFLARE_API_TOKEN.',
|
|
398
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['CLOUDFLARE_API_TOKEN'],
|
|
399
|
+
serverName: 'cloudflare',
|
|
400
|
+
jsonProjection: { command: 'npx', args: ['-y', '@cloudflare/mcp-server-cloudflare@latest'], env: { CLOUDFLARE_API_TOKEN: '${env:CLOUDFLARE_API_TOKEN}' } },
|
|
401
|
+
excludeTools: ['delete_worker', 'purge_cache'],
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
key: 'aws-mcp', label: 'AWS',
|
|
405
|
+
description: 'S3, Lambda, DynamoDB access.',
|
|
406
|
+
useWhen: 'Repos using AWS.',
|
|
407
|
+
adoption: 'Requires: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION.',
|
|
408
|
+
trustLevel: 'low', transport: 'stdio', requiredAuth: ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY', 'AWS_REGION'],
|
|
409
|
+
serverName: 'aws',
|
|
410
|
+
jsonProjection: { command: 'npx', args: ['-y', '@aws-samples/mcp-server-aws@latest'], env: { AWS_ACCESS_KEY_ID: '${env:AWS_ACCESS_KEY_ID}', AWS_SECRET_ACCESS_KEY: '${env:AWS_SECRET_ACCESS_KEY}', AWS_REGION: '${env:AWS_REGION}' } },
|
|
411
|
+
excludeTools: ['delete_stack', 'terminate_instances', 'delete_bucket'],
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
key: 'redis-mcp', label: 'Redis',
|
|
415
|
+
description: 'Cache and session management.',
|
|
416
|
+
useWhen: 'Repos using Redis.',
|
|
417
|
+
adoption: 'Requires: REDIS_URL.',
|
|
418
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['REDIS_URL'],
|
|
419
|
+
serverName: 'redis',
|
|
420
|
+
jsonProjection: { command: 'npx', args: ['-y', 'redis-mcp-server@latest'], env: { REDIS_URL: '${env:REDIS_URL}' } },
|
|
421
|
+
excludeTools: ['flushall', 'flushdb'],
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
key: 'mongodb-mcp', label: 'MongoDB',
|
|
425
|
+
description: 'Document database access.',
|
|
426
|
+
useWhen: 'Repos using MongoDB.',
|
|
427
|
+
adoption: 'Requires: MONGODB_URI.',
|
|
428
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['MONGODB_URI'],
|
|
429
|
+
serverName: 'mongodb',
|
|
430
|
+
jsonProjection: { command: 'npx', args: ['-y', '@mongodb-js/mongodb-mcp-server@latest'], env: { MONGODB_URI: '${env:MONGODB_URI}' } },
|
|
431
|
+
excludeTools: ['drop_collection', 'drop_database'],
|
|
432
|
+
},
|
|
433
|
+
{
|
|
434
|
+
key: 'twilio-mcp', label: 'Twilio',
|
|
435
|
+
description: 'SMS, voice, and messaging.',
|
|
436
|
+
useWhen: 'Repos using Twilio.',
|
|
437
|
+
adoption: 'Requires: TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN.',
|
|
438
|
+
trustLevel: 'low', transport: 'stdio', requiredAuth: ['TWILIO_ACCOUNT_SID', 'TWILIO_AUTH_TOKEN'],
|
|
439
|
+
serverName: 'twilio',
|
|
440
|
+
jsonProjection: { command: 'npx', args: ['-y', 'twilio-mcp-server@latest'], env: { TWILIO_ACCOUNT_SID: '${env:TWILIO_ACCOUNT_SID}', TWILIO_AUTH_TOKEN: '${env:TWILIO_AUTH_TOKEN}' } },
|
|
441
|
+
excludeTools: ['delete_message'],
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
key: 'sendgrid-mcp', label: 'SendGrid',
|
|
445
|
+
description: 'Transactional email delivery.',
|
|
446
|
+
useWhen: 'Repos using SendGrid.',
|
|
447
|
+
adoption: 'Requires: SENDGRID_API_KEY.',
|
|
448
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['SENDGRID_API_KEY'],
|
|
449
|
+
serverName: 'sendgrid',
|
|
450
|
+
jsonProjection: { command: 'npx', args: ['-y', 'sendgrid-mcp-server@latest'], env: { SENDGRID_API_KEY: '${env:SENDGRID_API_KEY}' } },
|
|
451
|
+
excludeTools: [],
|
|
452
|
+
},
|
|
453
|
+
{
|
|
454
|
+
key: 'algolia-mcp', label: 'Algolia Search',
|
|
455
|
+
description: 'Search indexing via Algolia.',
|
|
456
|
+
useWhen: 'Repos using Algolia.',
|
|
457
|
+
adoption: 'Requires: ALGOLIA_APP_ID, ALGOLIA_API_KEY.',
|
|
458
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['ALGOLIA_APP_ID', 'ALGOLIA_API_KEY'],
|
|
459
|
+
serverName: 'algolia',
|
|
460
|
+
jsonProjection: { command: 'npx', args: ['-y', 'algolia-mcp-server@latest'], env: { ALGOLIA_APP_ID: '${env:ALGOLIA_APP_ID}', ALGOLIA_API_KEY: '${env:ALGOLIA_API_KEY}' } },
|
|
461
|
+
excludeTools: ['delete_index'],
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
key: 'planetscale-mcp', label: 'PlanetScale',
|
|
465
|
+
description: 'Serverless MySQL via PlanetScale.',
|
|
466
|
+
useWhen: 'Repos on PlanetScale.',
|
|
467
|
+
adoption: 'Requires: PLANETSCALE_TOKEN.',
|
|
468
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['PLANETSCALE_TOKEN'],
|
|
469
|
+
serverName: 'planetscale',
|
|
470
|
+
jsonProjection: { command: 'npx', args: ['-y', 'planetscale-mcp-server@latest'], env: { PLANETSCALE_TOKEN: '${env:PLANETSCALE_TOKEN}' } },
|
|
471
|
+
excludeTools: ['delete_database'],
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
key: 'neon-mcp', label: 'Neon Serverless Postgres',
|
|
475
|
+
description: 'Serverless Postgres via Neon.',
|
|
476
|
+
useWhen: 'Repos using Neon.',
|
|
477
|
+
adoption: 'Requires: NEON_API_KEY.',
|
|
478
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['NEON_API_KEY'],
|
|
479
|
+
serverName: 'neon',
|
|
480
|
+
jsonProjection: { command: 'npx', args: ['-y', '@neondatabase/mcp-server-neon@latest'], env: { NEON_API_KEY: '${env:NEON_API_KEY}' } },
|
|
481
|
+
excludeTools: ['delete_project'],
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
key: 'turso-mcp', label: 'Turso Edge SQLite',
|
|
485
|
+
description: 'Edge SQLite via Turso.',
|
|
486
|
+
useWhen: 'Repos using Turso.',
|
|
487
|
+
adoption: 'Requires: TURSO_DATABASE_URL, TURSO_AUTH_TOKEN.',
|
|
488
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['TURSO_DATABASE_URL', 'TURSO_AUTH_TOKEN'],
|
|
489
|
+
serverName: 'turso',
|
|
490
|
+
jsonProjection: { command: 'npx', args: ['-y', 'turso-mcp-server@latest'], env: { TURSO_DATABASE_URL: '${env:TURSO_DATABASE_URL}', TURSO_AUTH_TOKEN: '${env:TURSO_AUTH_TOKEN}' } },
|
|
491
|
+
excludeTools: ['destroy_database'],
|
|
492
|
+
},
|
|
493
|
+
{
|
|
494
|
+
key: 'upstash-mcp', label: 'Upstash Redis+Kafka',
|
|
495
|
+
description: 'Serverless Redis and Kafka.',
|
|
496
|
+
useWhen: 'Repos using Upstash.',
|
|
497
|
+
adoption: 'Requires: UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN.',
|
|
498
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['UPSTASH_REDIS_REST_URL', 'UPSTASH_REDIS_REST_TOKEN'],
|
|
499
|
+
serverName: 'upstash',
|
|
500
|
+
jsonProjection: { command: 'npx', args: ['-y', '@upstash/mcp-server@latest'], env: { UPSTASH_REDIS_REST_URL: '${env:UPSTASH_REDIS_REST_URL}', UPSTASH_REDIS_REST_TOKEN: '${env:UPSTASH_REDIS_REST_TOKEN}' } },
|
|
501
|
+
excludeTools: [],
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
key: 'convex-mcp', label: 'Convex',
|
|
505
|
+
description: 'Reactive backend via Convex.',
|
|
506
|
+
useWhen: 'Repos using Convex.',
|
|
507
|
+
adoption: 'Requires: CONVEX_DEPLOYMENT.',
|
|
508
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['CONVEX_DEPLOYMENT'],
|
|
509
|
+
serverName: 'convex',
|
|
510
|
+
jsonProjection: { command: 'npx', args: ['-y', '@convex-dev/mcp-server@latest'], env: { CONVEX_DEPLOYMENT: '${env:CONVEX_DEPLOYMENT}' } },
|
|
511
|
+
excludeTools: ['delete_deployment'],
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
key: 'clerk-mcp', label: 'Clerk Authentication',
|
|
515
|
+
description: 'User auth via Clerk.',
|
|
516
|
+
useWhen: 'Repos using Clerk.',
|
|
517
|
+
adoption: 'Requires: CLERK_SECRET_KEY.',
|
|
518
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['CLERK_SECRET_KEY'],
|
|
519
|
+
serverName: 'clerk',
|
|
520
|
+
jsonProjection: { command: 'npx', args: ['-y', '@clerk/mcp-server@latest'], env: { CLERK_SECRET_KEY: '${env:CLERK_SECRET_KEY}' } },
|
|
521
|
+
excludeTools: ['delete_user'],
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
key: 'resend-mcp', label: 'Resend Email',
|
|
525
|
+
description: 'Transactional email via Resend.',
|
|
526
|
+
useWhen: 'Repos using Resend.',
|
|
527
|
+
adoption: 'Requires: RESEND_API_KEY.',
|
|
528
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['RESEND_API_KEY'],
|
|
529
|
+
serverName: 'resend',
|
|
530
|
+
jsonProjection: { command: 'npx', args: ['-y', 'resend-mcp-server@latest'], env: { RESEND_API_KEY: '${env:RESEND_API_KEY}' } },
|
|
531
|
+
excludeTools: [],
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
key: 'temporal-mcp', label: 'Temporal Workflow',
|
|
535
|
+
description: 'Workflow orchestration via Temporal.',
|
|
536
|
+
useWhen: 'Repos using Temporal.',
|
|
537
|
+
adoption: 'Requires: TEMPORAL_ADDRESS.',
|
|
538
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['TEMPORAL_ADDRESS'],
|
|
539
|
+
serverName: 'temporal',
|
|
540
|
+
jsonProjection: { command: 'npx', args: ['-y', 'temporal-mcp-server@latest'], env: { TEMPORAL_ADDRESS: '${env:TEMPORAL_ADDRESS}' } },
|
|
541
|
+
excludeTools: ['terminate_workflow'],
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
key: 'launchdarkly-mcp', label: 'LaunchDarkly',
|
|
545
|
+
description: 'Feature flags via LaunchDarkly.',
|
|
546
|
+
useWhen: 'Repos using LaunchDarkly.',
|
|
547
|
+
adoption: 'Requires: LAUNCHDARKLY_ACCESS_TOKEN.',
|
|
548
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['LAUNCHDARKLY_ACCESS_TOKEN'],
|
|
549
|
+
serverName: 'launchdarkly',
|
|
550
|
+
jsonProjection: { command: 'npx', args: ['-y', 'launchdarkly-mcp-server@latest'], env: { LAUNCHDARKLY_ACCESS_TOKEN: '${env:LAUNCHDARKLY_ACCESS_TOKEN}' } },
|
|
551
|
+
excludeTools: ['delete_flag'],
|
|
552
|
+
},
|
|
553
|
+
{
|
|
554
|
+
key: 'datadog-mcp', label: 'Datadog',
|
|
555
|
+
description: 'Monitoring and APM via Datadog.',
|
|
556
|
+
useWhen: 'Repos using Datadog.',
|
|
557
|
+
adoption: 'Requires: DATADOG_API_KEY, DATADOG_APP_KEY.',
|
|
558
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['DATADOG_API_KEY', 'DATADOG_APP_KEY'],
|
|
559
|
+
serverName: 'datadog',
|
|
560
|
+
jsonProjection: { command: 'npx', args: ['-y', '@datadog/mcp-server@latest'], env: { DATADOG_API_KEY: '${env:DATADOG_API_KEY}', DATADOG_APP_KEY: '${env:DATADOG_APP_KEY}' } },
|
|
561
|
+
excludeTools: ['delete_monitor'],
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
key: 'grafana-mcp', label: 'Grafana',
|
|
565
|
+
description: 'Dashboards via Grafana.',
|
|
566
|
+
useWhen: 'Repos using Grafana.',
|
|
567
|
+
adoption: 'Requires: GRAFANA_URL, GRAFANA_API_KEY.',
|
|
568
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['GRAFANA_URL', 'GRAFANA_API_KEY'],
|
|
569
|
+
serverName: 'grafana',
|
|
570
|
+
jsonProjection: { command: 'npx', args: ['-y', 'grafana-mcp-server@latest'], env: { GRAFANA_URL: '${env:GRAFANA_URL}', GRAFANA_API_KEY: '${env:GRAFANA_API_KEY}' } },
|
|
571
|
+
excludeTools: ['delete_dashboard'],
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
key: 'circleci-mcp', label: 'CircleCI',
|
|
575
|
+
description: 'CI/CD via CircleCI.',
|
|
576
|
+
useWhen: 'Repos using CircleCI.',
|
|
577
|
+
adoption: 'Requires: CIRCLECI_TOKEN.',
|
|
578
|
+
trustLevel: 'medium', transport: 'stdio', requiredAuth: ['CIRCLECI_TOKEN'],
|
|
579
|
+
serverName: 'circleci',
|
|
580
|
+
jsonProjection: { command: 'npx', args: ['-y', 'circleci-mcp-server@latest'], env: { CIRCLECI_TOKEN: '${env:CIRCLECI_TOKEN}' } },
|
|
581
|
+
excludeTools: ['cancel_pipeline'],
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
key: 'anthropic-mcp', label: 'Anthropic Claude API',
|
|
585
|
+
description: 'Claude API for AI-powered apps.',
|
|
586
|
+
useWhen: 'Repos building on Claude API.',
|
|
587
|
+
adoption: 'Requires: ANTHROPIC_API_KEY.',
|
|
588
|
+
trustLevel: 'high', transport: 'stdio', requiredAuth: ['ANTHROPIC_API_KEY'],
|
|
589
|
+
serverName: 'anthropic',
|
|
590
|
+
jsonProjection: { command: 'npx', args: ['-y', '@anthropic-ai/mcp-server@latest'], env: { ANTHROPIC_API_KEY: '${env:ANTHROPIC_API_KEY}' } },
|
|
591
|
+
excludeTools: [],
|
|
592
|
+
},
|
|
362
593
|
];
|
|
363
594
|
|
|
364
595
|
// --- Helpers ---
|
|
@@ -462,6 +693,32 @@ function recommendWindsurfMcpPacks(stacks = [], domainPacks = [], options = {})
|
|
|
462
693
|
if (domainKeys.has('ecommerce') && (hasDependency(deps, 'shopify') || hasDependency(deps, '@shopify/shopify-api'))) recommended.add('shopify-mcp');
|
|
463
694
|
if (domainKeys.has('ai-ml')) recommended.add('huggingface-mcp');
|
|
464
695
|
if (domainKeys.has('design-system')) recommended.add('figma-mcp');
|
|
696
|
+
// ── 23 new packs recommendation logic ────────────────────────────────────
|
|
697
|
+
if (ctx) {
|
|
698
|
+
if (hasDependency(deps, '@supabase/supabase-js') || hasDependency(deps, '@supabase/auth-helpers-nextjs') || hasFileContentMatch(ctx, '.env', /SUPABASE/i) || hasFileContentMatch(ctx, '.env.example', /SUPABASE/i)) recommended.add('supabase-mcp');
|
|
699
|
+
if (hasFileContentMatch(ctx, 'schema.prisma', /\S/) || hasDependency(deps, '@prisma/client') || hasDependency(deps, 'prisma')) recommended.add('prisma-mcp');
|
|
700
|
+
if (ctx.files.includes('vercel.json') || hasFileContentMatch(ctx, 'package.json', /"deploy":\s*"vercel/i) || hasFileContentMatch(ctx, '.env', /VERCEL_TOKEN/i)) recommended.add('vercel-mcp');
|
|
701
|
+
if (hasFileContentMatch(ctx, 'wrangler.toml', /\S/) || hasDependency(deps, 'wrangler') || hasFileContentMatch(ctx, '.env', /CLOUDFLARE/i)) recommended.add('cloudflare-mcp');
|
|
702
|
+
if (hasFileContentMatch(ctx, '.env', /AWS_ACCESS_KEY/i) || ctx.files.some(f => /serverless\.yml|template\.ya?ml|cdk\.json/.test(f))) recommended.add('aws-mcp');
|
|
703
|
+
if (hasDependency(deps, 'redis') || hasDependency(deps, 'ioredis') || hasDependency(deps, '@redis/client') || hasFileContentMatch(ctx, '.env', /REDIS_URL/i)) recommended.add('redis-mcp');
|
|
704
|
+
if (hasDependency(deps, 'mongoose') || hasDependency(deps, 'mongodb') || hasFileContentMatch(ctx, '.env', /MONGODB_URI/i)) recommended.add('mongodb-mcp');
|
|
705
|
+
if (hasDependency(deps, 'twilio') || hasFileContentMatch(ctx, '.env', /TWILIO_/i)) recommended.add('twilio-mcp');
|
|
706
|
+
if (hasDependency(deps, '@sendgrid/mail') || hasFileContentMatch(ctx, '.env', /SENDGRID_API_KEY/i)) recommended.add('sendgrid-mcp');
|
|
707
|
+
if (hasDependency(deps, 'algoliasearch') || hasDependency(deps, '@algolia/client-search') || hasFileContentMatch(ctx, '.env', /ALGOLIA_/i)) recommended.add('algolia-mcp');
|
|
708
|
+
if (hasFileContentMatch(ctx, '.env', /PLANETSCALE_TOKEN/i)) recommended.add('planetscale-mcp');
|
|
709
|
+
if (hasDependency(deps, '@neondatabase/serverless') || hasFileContentMatch(ctx, '.env', /NEON_/i)) recommended.add('neon-mcp');
|
|
710
|
+
if (hasDependency(deps, '@libsql/client') || hasFileContentMatch(ctx, '.env', /TURSO_/i)) recommended.add('turso-mcp');
|
|
711
|
+
if (hasDependency(deps, '@upstash/redis') || hasDependency(deps, '@upstash/kafka') || hasFileContentMatch(ctx, '.env', /UPSTASH_/i)) recommended.add('upstash-mcp');
|
|
712
|
+
if (hasDependency(deps, 'convex') || hasFileContentMatch(ctx, 'convex.json', /\S/) || hasFileContentMatch(ctx, '.env', /CONVEX_/i)) recommended.add('convex-mcp');
|
|
713
|
+
if (hasDependency(deps, '@clerk/nextjs') || hasDependency(deps, '@clerk/backend') || hasFileContentMatch(ctx, '.env', /CLERK_/i)) recommended.add('clerk-mcp');
|
|
714
|
+
if (hasDependency(deps, 'resend') || hasFileContentMatch(ctx, '.env', /RESEND_API_KEY/i)) recommended.add('resend-mcp');
|
|
715
|
+
if (hasDependency(deps, '@temporalio/client') || hasFileContentMatch(ctx, '.env', /TEMPORAL_/i)) recommended.add('temporal-mcp');
|
|
716
|
+
if (hasDependency(deps, '@launchdarkly/node-server-sdk') || hasFileContentMatch(ctx, '.env', /LAUNCHDARKLY_/i)) recommended.add('launchdarkly-mcp');
|
|
717
|
+
if (hasDependency(deps, 'dd-trace') || hasFileContentMatch(ctx, '.env', /DATADOG_/i)) recommended.add('datadog-mcp');
|
|
718
|
+
if (hasFileContentMatch(ctx, 'docker-compose.yml', /grafana/i) || hasFileContentMatch(ctx, '.env', /GRAFANA_/i)) recommended.add('grafana-mcp');
|
|
719
|
+
if (ctx.files.some(f => /\.circleci\/config/.test(f)) || hasFileContentMatch(ctx, '.env', /CIRCLECI_/i)) recommended.add('circleci-mcp');
|
|
720
|
+
if (hasDependency(deps, '@anthropic-ai/sdk') || hasDependency(deps, 'anthropic') || hasFileContentMatch(ctx, '.env', /ANTHROPIC_API_KEY/i)) recommended.add('anthropic-mcp');
|
|
721
|
+
}
|
|
465
722
|
if (recommended.size >= 2) recommended.add('mcp-security');
|
|
466
723
|
if (recommended.size === 0) recommended.add('context7-docs');
|
|
467
724
|
|