komplian 0.4.7 → 0.5.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.
@@ -0,0 +1,26 @@
1
+ # Copia a KOMPLIAN_DATABASE_URLS.env (raíz del monorepo) y rellena.
2
+ # No commitees URLs con contraseña. Hosts: Neon (*.neon.tech).
3
+
4
+ # --- App DB (Prisma en app/, raw SQL en api/) ---
5
+ KOMPLIAN_DATABASE_URL_APP_DEVELOPMENT=
6
+ KOMPLIAN_DATABASE_URL_APP_STAGING=
7
+ KOMPLIAN_DATABASE_URL_APP_PRODUCTION=
8
+
9
+ # --- Admin DB ---
10
+ KOMPLIAN_DATABASE_URL_ADMIN_DEVELOPMENT=
11
+ KOMPLIAN_DATABASE_URL_ADMIN_STAGING=
12
+ KOMPLIAN_DATABASE_URL_ADMIN_PRODUCTION=
13
+
14
+ # --- Web / pilot DB ---
15
+ KOMPLIAN_DATABASE_URL_WEB_DEVELOPMENT=
16
+ KOMPLIAN_DATABASE_URL_WEB_STAGING=
17
+ KOMPLIAN_DATABASE_URL_WEB_PRODUCTION=
18
+
19
+ # Producción: solo @komplian.com; lista separada por comas (opcional; por defecto josue.santana@komplian.com).
20
+ # KOMPLIAN_DATABASE_PRODUCTION_ALLOWLIST=josue.santana@komplian.com,otro@komplian.com
21
+
22
+ # Operador para comprobar acceso a producción (alternativa: git config user.email).
23
+ # KOMPLIAN_DATABASE_OPERATOR_EMAIL=josue.santana@komplian.com
24
+
25
+ # Solo si NO usas Neon (p. ej. Postgres local en Docker):
26
+ # KOMPLIAN_DATABASE_ALLOW_NON_NEON=1
@@ -0,0 +1,451 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Komplian db — psql contra Neon (PostgreSQL). Convención KOMPLIAN_* para URLs y acceso.
4
+ *
5
+ * Sintaxis recomendada:
6
+ * npx komplian db:app:development
7
+ * npx komplian db:admin:staging
8
+ * npx komplian db:web:production
9
+ *
10
+ * URLs (prioridad: process.env → KOMPLIAN_DATABASE_URLS.env → KOMPLIAN_LOCALHOST_SECRETS.env → .env.local solo development):
11
+ * KOMPLIAN_DATABASE_URL_APP_DEVELOPMENT | _STAGING | _PRODUCTION
12
+ * KOMPLIAN_DATABASE_URL_ADMIN_*
13
+ * KOMPLIAN_DATABASE_URL_WEB_*
14
+ * Legacy development: KOMPLIAN_LOCALHOST_*_DATABASE_URL
15
+ *
16
+ * Producción: solo emails @komplian.com en KOMPLIAN_DATABASE_PRODUCTION_ALLOWLIST
17
+ * (por defecto josue.santana@komplian.com). Operador: KOMPLIAN_DATABASE_OPERATOR_EMAIL o git config user.email.
18
+ *
19
+ * Neon: host debe ser *.neon.tech salvo KOMPLIAN_DATABASE_ALLOW_NON_NEON=1
20
+ */
21
+
22
+ import { spawn, spawnSync } from "node:child_process";
23
+ import { existsSync, readFileSync } from "node:fs";
24
+ import { dirname, join, resolve } from "node:path";
25
+ import { createInterface } from "node:readline/promises";
26
+ import { stdin as input, stdout as output } from "node:process";
27
+
28
+ const c = {
29
+ reset: "\x1b[0m",
30
+ dim: "\x1b[2m",
31
+ bold: "\x1b[1m",
32
+ cyan: "\x1b[36m",
33
+ red: "\x1b[31m",
34
+ yellow: "\x1b[33m",
35
+ };
36
+
37
+ function log(s = "") {
38
+ console.log(s);
39
+ }
40
+
41
+ const PLATFORMS = new Set([
42
+ "app",
43
+ "admin",
44
+ "web",
45
+ "api-app",
46
+ "api-admin",
47
+ "api-web",
48
+ ]);
49
+
50
+ const ENVIRONMENTS = new Set(["development", "staging", "production"]);
51
+
52
+ /** Lista por defecto si no configuras KOMPLIAN_DATABASE_PRODUCTION_ALLOWLIST */
53
+ const DEFAULT_PRODUCTION_ALLOWLIST = ["josue.santana@komplian.com"];
54
+
55
+ function parseEnvFile(content) {
56
+ const out = {};
57
+ for (const line of content.split(/\r?\n/)) {
58
+ const t = line.trim();
59
+ if (!t || t.startsWith("#")) continue;
60
+ const eq = t.indexOf("=");
61
+ if (eq === -1) continue;
62
+ const key = t.slice(0, eq).trim();
63
+ let val = t.slice(eq + 1).trim();
64
+ if (
65
+ (val.startsWith('"') && val.endsWith('"')) ||
66
+ (val.startsWith("'") && val.endsWith("'"))
67
+ ) {
68
+ val = val.slice(1, -1);
69
+ }
70
+ out[key] = val;
71
+ }
72
+ return out;
73
+ }
74
+
75
+ function loadMergedEnvFiles(workspaceRoot) {
76
+ const out = {};
77
+ const paths = [
78
+ join(workspaceRoot, "KOMPLIAN_DATABASE_URLS.env"),
79
+ join(workspaceRoot, ".komplian", "KOMPLIAN_DATABASE_URLS.env"),
80
+ join(workspaceRoot, "KOMPLIAN_LOCALHOST_SECRETS.env"),
81
+ join(workspaceRoot, ".komplian", "KOMPLIAN_LOCALHOST_SECRETS.env"),
82
+ join(workspaceRoot, "komplian-localhost.secrets.env"),
83
+ join(workspaceRoot, ".komplian", "localhost-secrets.env"),
84
+ ];
85
+ for (const p of paths) {
86
+ if (!existsSync(p)) continue;
87
+ try {
88
+ Object.assign(out, parseEnvFile(readFileSync(p, "utf8")));
89
+ } catch {
90
+ /* ignore */
91
+ }
92
+ }
93
+ return out;
94
+ }
95
+
96
+ function readEnvLocalKey(workspaceRoot, project, keys) {
97
+ const p = join(workspaceRoot, project, ".env.local");
98
+ if (!existsSync(p)) return "";
99
+ let map;
100
+ try {
101
+ map = parseEnvFile(readFileSync(p, "utf8"));
102
+ } catch {
103
+ return "";
104
+ }
105
+ for (const k of keys) {
106
+ const v = map[k];
107
+ if (v && String(v).trim()) return String(v).trim();
108
+ }
109
+ return "";
110
+ }
111
+
112
+ function findWorkspaceRoot(start) {
113
+ let dir = resolve(start);
114
+ for (let i = 0; i < 8; i++) {
115
+ if (
116
+ existsSync(join(dir, "api", "package.json")) &&
117
+ existsSync(join(dir, "app", "package.json"))
118
+ ) {
119
+ return dir;
120
+ }
121
+ const parent = dirname(dir);
122
+ if (parent === dir) break;
123
+ dir = parent;
124
+ }
125
+ return resolve(start);
126
+ }
127
+
128
+ function maskDatabaseUrl(url) {
129
+ try {
130
+ const u = new URL(url);
131
+ if (u.password) u.password = "***";
132
+ return u.toString();
133
+ } catch {
134
+ return "[URL inválida]";
135
+ }
136
+ }
137
+
138
+ /** api-app → APP (misma base que app). */
139
+ function platformToDbKey(platform) {
140
+ const p = platform.toLowerCase();
141
+ if (p === "app" || p === "api-app") return "APP";
142
+ if (p === "admin" || p === "api-admin") return "ADMIN";
143
+ if (p === "web" || p === "api-web") return "WEB";
144
+ return null;
145
+ }
146
+
147
+ function loadProductionAllowlist(workspaceRoot) {
148
+ const raw = process.env.KOMPLIAN_DATABASE_PRODUCTION_ALLOWLIST?.trim();
149
+ if (raw) {
150
+ return raw
151
+ .split(",")
152
+ .map((s) => s.trim().toLowerCase())
153
+ .filter(Boolean);
154
+ }
155
+ const p = join(
156
+ workspaceRoot,
157
+ ".komplian",
158
+ "KOMPLIAN_DATABASE_PRODUCTION_ALLOWLIST"
159
+ );
160
+ if (existsSync(p)) {
161
+ try {
162
+ return readFileSync(p, "utf8")
163
+ .split(/\r?\n/)
164
+ .map((l) => l.trim().toLowerCase())
165
+ .filter((l) => l && !l.startsWith("#"));
166
+ } catch {
167
+ /* ignore */
168
+ }
169
+ }
170
+ return DEFAULT_PRODUCTION_ALLOWLIST.map((e) => e.toLowerCase());
171
+ }
172
+
173
+ function assertNeonHost(url) {
174
+ if (process.env.KOMPLIAN_DATABASE_ALLOW_NON_NEON === "1") return;
175
+ try {
176
+ const u = new URL(url);
177
+ const h = u.hostname.toLowerCase();
178
+ if (!h.includes("neon.tech")) {
179
+ log(
180
+ `${c.red}✗${c.reset} Solo hosts Neon (postgresql://*.neon.tech). Para excepciones: ${c.dim}KOMPLIAN_DATABASE_ALLOW_NON_NEON=1${c.reset}`
181
+ );
182
+ process.exit(1);
183
+ }
184
+ } catch {
185
+ log(`${c.red}✗${c.reset} URL de base de datos inválida.`);
186
+ process.exit(1);
187
+ }
188
+ }
189
+
190
+ async function resolveOperatorEmail() {
191
+ const fromEnv = process.env.KOMPLIAN_DATABASE_OPERATOR_EMAIL?.trim();
192
+ if (fromEnv) return fromEnv;
193
+ try {
194
+ const r = spawnSync("git", ["config", "user.email"], {
195
+ encoding: "utf8",
196
+ shell: false,
197
+ });
198
+ if (r.status === 0 && r.stdout?.trim()) return r.stdout.trim();
199
+ } catch {
200
+ /* ignore */
201
+ }
202
+ const rl = createInterface({ input, output });
203
+ const ans = await rl.question(
204
+ `${c.bold}Email operador (@komplian.com) para acceso producción:${c.reset} `
205
+ );
206
+ rl.close();
207
+ return (ans || "").trim();
208
+ }
209
+
210
+ function assertProductionAccess(email, allowlist) {
211
+ const e = email.toLowerCase();
212
+ if (!e || !e.endsWith("@komplian.com")) {
213
+ log(
214
+ `${c.red}✗${c.reset} Producción: solo cuentas ${c.bold}@komplian.com${c.reset}. Define ${c.dim}KOMPLIAN_DATABASE_OPERATOR_EMAIL${c.reset} o ${c.dim}git config user.email${c.reset}.`
215
+ );
216
+ process.exit(1);
217
+ }
218
+ if (!allowlist.includes(e)) {
219
+ log(
220
+ `${c.red}✗${c.reset} Producción: este email no está autorizado. Añádelo en ${c.bold}KOMPLIAN_DATABASE_PRODUCTION_ALLOWLIST${c.reset} o en ${c.dim}.komplian/KOMPLIAN_DATABASE_PRODUCTION_ALLOWLIST${c.reset}.`
221
+ );
222
+ process.exit(1);
223
+ }
224
+ }
225
+
226
+ function resolveDatabaseUrl(workspaceRoot, platform, environment) {
227
+ const dbKey = platformToDbKey(platform);
228
+ if (!dbKey) return "";
229
+ const file = loadMergedEnvFiles(workspaceRoot);
230
+ const envU = environment.toUpperCase();
231
+ const primaryKey = `KOMPLIAN_DATABASE_URL_${dbKey}_${envU}`;
232
+
233
+ let url =
234
+ process.env[primaryKey]?.trim() || file[primaryKey]?.trim() || "";
235
+
236
+ if (environment === "development" && !url) {
237
+ const single =
238
+ process.env.KOMPLIAN_LOCALHOST_DATABASE_URL?.trim() ||
239
+ file.KOMPLIAN_LOCALHOST_DATABASE_URL?.trim();
240
+ if (dbKey === "APP") {
241
+ url =
242
+ process.env.KOMPLIAN_LOCALHOST_APP_DATABASE_URL?.trim() ||
243
+ file.KOMPLIAN_LOCALHOST_APP_DATABASE_URL?.trim() ||
244
+ single ||
245
+ readEnvLocalKey(workspaceRoot, "app", ["DATABASE_URL"]) ||
246
+ readEnvLocalKey(workspaceRoot, "api", ["APP_DATABASE_URL"]);
247
+ } else if (dbKey === "ADMIN") {
248
+ url =
249
+ process.env.KOMPLIAN_LOCALHOST_ADMIN_DATABASE_URL?.trim() ||
250
+ file.KOMPLIAN_LOCALHOST_ADMIN_DATABASE_URL?.trim() ||
251
+ single ||
252
+ readEnvLocalKey(workspaceRoot, "admin", ["DATABASE_URL"]) ||
253
+ readEnvLocalKey(workspaceRoot, "api", ["ADMIN_DATABASE_URL"]);
254
+ } else if (dbKey === "WEB") {
255
+ url =
256
+ process.env.KOMPLIAN_LOCALHOST_WEB_DATABASE_URL?.trim() ||
257
+ file.KOMPLIAN_LOCALHOST_WEB_DATABASE_URL?.trim() ||
258
+ single ||
259
+ readEnvLocalKey(workspaceRoot, "web", ["DATABASE_URL"]) ||
260
+ readEnvLocalKey(workspaceRoot, "api", ["WEB_DATABASE_URL"]);
261
+ }
262
+ }
263
+
264
+ return url;
265
+ }
266
+
267
+ function findPsqlBinary() {
268
+ const candidates = [
269
+ "psql",
270
+ "/opt/homebrew/opt/libpq/bin/psql",
271
+ "/usr/local/opt/libpq/bin/psql",
272
+ ];
273
+ for (const p of candidates) {
274
+ if (p !== "psql" && !existsSync(p)) continue;
275
+ const r = spawnSync(p, ["--version"], {
276
+ encoding: "utf8",
277
+ shell: false,
278
+ windowsHide: true,
279
+ });
280
+ if (!r.error && r.status === 0) return p;
281
+ }
282
+ return null;
283
+ }
284
+
285
+ function parseDbArgs(argv) {
286
+ const opts = {
287
+ platform: "app",
288
+ environment: "development",
289
+ workspace: "",
290
+ command: "",
291
+ url: "",
292
+ quiet: false,
293
+ help: false,
294
+ };
295
+
296
+ let rest = argv;
297
+ if (
298
+ argv.length >= 2 &&
299
+ PLATFORMS.has(argv[0].toLowerCase()) &&
300
+ ENVIRONMENTS.has(argv[1].toLowerCase())
301
+ ) {
302
+ opts.platform = argv[0].toLowerCase();
303
+ opts.environment = argv[1].toLowerCase();
304
+ rest = argv.slice(2);
305
+ }
306
+
307
+ for (let i = 0; i < rest.length; i++) {
308
+ const a = rest[i];
309
+ if (a === "-h" || a === "--help") opts.help = true;
310
+ else if (a === "--quiet" || a === "-q") opts.quiet = true;
311
+ else if (a === "--target" || a === "--platform" || a === "-t") {
312
+ opts.platform = (rest[++i] || "app").toLowerCase();
313
+ } else if (a === "--env" || a === "-e") {
314
+ opts.environment = (rest[++i] || "development").toLowerCase();
315
+ } else if (a === "--workspace" || a === "-w") {
316
+ opts.workspace = rest[++i] || "";
317
+ } else if (a === "-c" || a === "--command") {
318
+ opts.command = rest[++i] || "";
319
+ } else if (a === "--url" || a === "-u") {
320
+ opts.url = rest[++i] || "";
321
+ } else if (a.startsWith("-")) {
322
+ log(`${c.red}✗${c.reset} Opción desconocida: ${a}`);
323
+ process.exit(1);
324
+ } else if (!opts.workspace) opts.workspace = a;
325
+ }
326
+
327
+ return opts;
328
+ }
329
+
330
+ function usageDb() {
331
+ log(`Uso:`);
332
+ log(` ${c.bold}npx komplian db:app:development${c.reset}`);
333
+ log(` ${c.bold}npx komplian db:admin:staging${c.reset}`);
334
+ log(` ${c.bold}npx komplian db:web:production${c.reset} ${c.dim}(solo @komplian.com + allowlist)${c.reset}`);
335
+ log(` npx komplian db --platform app --env development`);
336
+ log(` npx komplian db -c "SELECT 1" --platform web --env development`);
337
+ log(``);
338
+ log(` Plataformas: ${c.dim}app | admin | web | api-app | api-admin | api-web${c.reset}`);
339
+ log(` Entornos: ${c.dim}development | staging | production${c.reset}`);
340
+ log(``);
341
+ log(` Variables KOMPLIAN (recomendado):`);
342
+ log(` ${c.dim}KOMPLIAN_DATABASE_URL_APP_DEVELOPMENT${c.reset}`);
343
+ log(` ${c.dim}KOMPLIAN_DATABASE_URL_APP_STAGING / _PRODUCTION${c.reset} (idem ADMIN, WEB)`);
344
+ log(` Archivo opcional: ${c.dim}KOMPLIAN_DATABASE_URLS.env${c.reset} (gitignored)`);
345
+ log(``);
346
+ log(` Producción:`);
347
+ log(` ${c.dim}KOMPLIAN_DATABASE_OPERATOR_EMAIL${c.reset} o git user.email`);
348
+ log(` ${c.dim}KOMPLIAN_DATABASE_PRODUCTION_ALLOWLIST${c.reset} (coma) o .komplian/KOMPLIAN_DATABASE_PRODUCTION_ALLOWLIST`);
349
+ log(``);
350
+ log(` -c, --command SQL no interactivo`);
351
+ log(` -u, --url URL explícita (sigue reglas Neon / producción)`);
352
+ log(` -w, --workspace Raíz monorepo`);
353
+ log(` -q, --quiet`);
354
+ log(` -h, --help`);
355
+ log(``);
356
+ log(` Requisito: ${c.bold}psql${c.reset} (${c.dim}brew install libpq${c.reset} en macOS).`);
357
+ }
358
+
359
+ export async function runDb(argv) {
360
+ const opts = parseDbArgs(argv);
361
+ if (opts.help) {
362
+ usageDb();
363
+ return;
364
+ }
365
+
366
+ if (!PLATFORMS.has(opts.platform)) {
367
+ log(`${c.red}✗${c.reset} Plataforma inválida: ${opts.platform}`);
368
+ usageDb();
369
+ process.exit(1);
370
+ }
371
+ if (!ENVIRONMENTS.has(opts.environment)) {
372
+ log(`${c.red}✗${c.reset} Entorno inválido: ${opts.environment}`);
373
+ usageDb();
374
+ process.exit(1);
375
+ }
376
+
377
+ const workspaceRoot = opts.workspace.trim()
378
+ ? resolve(opts.workspace.replace(/^~(?=$|[/\\])/, process.env.HOME || ""))
379
+ : findWorkspaceRoot(process.cwd());
380
+
381
+ const allowlist = loadProductionAllowlist(workspaceRoot);
382
+
383
+ if (opts.environment === "production") {
384
+ const email = await resolveOperatorEmail();
385
+ assertProductionAccess(email, allowlist);
386
+ if (!opts.quiet) {
387
+ log(
388
+ `${c.yellow}⚠${c.reset} Producción · operador ${c.bold}${email.toLowerCase()}${c.reset} (allowlist Komplian)`
389
+ );
390
+ }
391
+ }
392
+
393
+ if (
394
+ !(opts.url || "").trim() &&
395
+ !existsSync(join(workspaceRoot, "api", "package.json"))
396
+ ) {
397
+ log(`${c.red}✗${c.reset} No parece un monorepo Komplian: ${workspaceRoot}`);
398
+ process.exit(1);
399
+ }
400
+
401
+ let url =
402
+ (opts.url || "").trim() ||
403
+ resolveDatabaseUrl(workspaceRoot, opts.platform, opts.environment);
404
+
405
+ if (!url) {
406
+ log(`${c.red}✗${c.reset} Sin URL para ${opts.platform} / ${opts.environment}.`);
407
+ log(
408
+ ` Define ${c.bold}KOMPLIAN_DATABASE_URL_${platformToDbKey(opts.platform)}_${opts.environment.toUpperCase()}${c.reset} o ${c.dim}KOMPLIAN_DATABASE_URLS.env${c.reset}.`
409
+ );
410
+ process.exit(1);
411
+ }
412
+
413
+ assertNeonHost(url);
414
+
415
+ const psql = findPsqlBinary();
416
+ if (!psql) {
417
+ log(`${c.red}✗${c.reset} No se encontró ${c.bold}psql${c.reset}.`);
418
+ log(` macOS: ${c.dim}brew install libpq${c.reset}`);
419
+ process.exit(1);
420
+ }
421
+
422
+ if (!opts.quiet) {
423
+ log(
424
+ `${c.cyan}→${c.reset} ${c.bold}${opts.platform}${c.reset} / ${c.bold}${opts.environment}${c.reset} ${maskDatabaseUrl(url)}`
425
+ );
426
+ log(`${c.dim} ${psql}${c.reset}`);
427
+ }
428
+
429
+ const args = [url];
430
+ if (opts.command) {
431
+ args.push("-c", opts.command, "-X");
432
+ }
433
+
434
+ const child = spawn(psql, args, {
435
+ stdio: "inherit",
436
+ shell: false,
437
+ env: { ...process.env, PAGER: process.env.PAGER || "less" },
438
+ });
439
+
440
+ child.on("error", (err) => {
441
+ if (err && err.code === "ENOENT") {
442
+ log(`${c.red}✗${c.reset} No se pudo ejecutar psql.`);
443
+ process.exit(1);
444
+ }
445
+ throw err;
446
+ });
447
+
448
+ child.on("exit", (code) => {
449
+ process.exit(code ?? 0);
450
+ });
451
+ }
@@ -38,6 +38,15 @@ const KOMPLIAN_MCP_PRESET = {
38
38
  GITHUB_PERSONAL_ACCESS_TOKEN: "",
39
39
  },
40
40
  },
41
+ "KOMPLIAN-atlassian": {
42
+ command: "npx",
43
+ args: ["-y", "mcp-atlassian"],
44
+ env: {
45
+ ATLASSIAN_BASE_URL: "https://komplian.atlassian.net",
46
+ ATLASSIAN_EMAIL: "",
47
+ ATLASSIAN_API_TOKEN: "",
48
+ },
49
+ },
41
50
  "KOMPLIAN-sentry": {
42
51
  command: "npx",
43
52
  args: ["-y", "@sentry/mcp-server@latest"],
@@ -50,6 +59,11 @@ const KOMPLIAN_MCP_PRESET = {
50
59
  STRIPE_SECRET_KEY: "",
51
60
  },
52
61
  },
62
+ "KOMPLIAN-chrome-devtools": {
63
+ command: "npx",
64
+ args: ["-y", "chrome-devtools-mcp@latest"],
65
+ env: {},
66
+ },
53
67
  },
54
68
  };
55
69
 
@@ -59,28 +73,27 @@ Este archivo lo genera \`npx komplian mcp-tools\`. **No commitees** tokens; \`.c
59
73
 
60
74
  ## 1. Servidores en \`.cursor/mcp.json\` (stdio / npx)
61
75
 
62
- | ID | Qué es | Rellena |
63
- |----|--------|---------|
64
- | **KOMPLIAN-github** | GitHub API (issues, PRs) | \`GITHUB_PERSONAL_ACCESS_TOKEN\` en el bloque \`env\` del servidor (PAT con scopes repo/workflow según necesidad). El paquete npm está deprecado a favor del servidor oficial; si falla, sustituye por la config que indique [github/github-mcp-server](https://github.com/github/github-mcp-server). |
65
- | **KOMPLIAN-sentry** | Sentry (issues, trazas) | Primera vez: login por navegador (device code). **Komplian:** \`organizationSlug\` = \`komplian\`, \`regionUrl\` = \`https://de.sentry.io\`. Proyectos: \`komplian-api\`, \`komplian-app\`. |
66
- | **KOMPLIAN-stripe** | Stripe API | \`STRIPE_SECRET_KEY\` en \`env\` (mejor **Restricted key** / test en dev). Modo test vs live según [docs Stripe MCP](https://docs.stripe.com/mcp). |
76
+ Activa cada fila en **Cursor Settings → MCP** (interruptor). Rellena \`env\` según la tabla.
67
77
 
68
- ## 2. Solo en Cursor (plugins / integraciones)
78
+ | ID | Paquete | Qué es | Rellena / notas |
79
+ |----|---------|--------|-----------------|
80
+ | **KOMPLIAN-github** | \`@modelcontextprotocol/server-github\` | GitHub API | \`GITHUB_PERSONAL_ACCESS_TOKEN\` (PAT). El paquete npm está deprecado; alternativa: [github/github-mcp-server](https://github.com/github/github-mcp-server). |
81
+ | **KOMPLIAN-atlassian** | \`mcp-atlassian\` | Jira + Confluence (Cloud) | \`ATLASSIAN_EMAIL\` + \`ATLASSIAN_API_TOKEN\` ([crear token](https://id.atlassian.com/manage-profile/security/api-tokens)). \`ATLASSIAN_BASE_URL\` ya apunta a \`https://komplian.atlassian.net\`. Jira proyecto **KAPP**. |
82
+ | **KOMPLIAN-sentry** | \`@sentry/mcp-server\` | Sentry | Primera vez: login en navegador. **Komplian:** org \`komplian\`, \`regionUrl\` \`https://de.sentry.io\`, proyectos \`komplian-api\` / \`komplian-app\`. |
83
+ | **KOMPLIAN-stripe** | \`@stripe/mcp\` | Stripe API | \`STRIPE_SECRET_KEY\` (restricted key / test en dev). [Docs Stripe MCP](https://docs.stripe.com/mcp). |
84
+ | **KOMPLIAN-chrome-devtools** | \`chrome-devtools-mcp\` | Chrome (red, consola, screenshots) | Requiere **Node 20.19+** y Chrome estable. Ver [Chrome DevTools MCP](https://developer.chrome.com/blog/chrome-devtools-mcp). |
69
85
 
70
- Estos **no** se instalan con npx en este JSON; actívalos en **Cursor → Settings → MCP** (o integraciones) e inicia sesión:
86
+ ## 2. Opcional: integraciones nativas de Cursor
71
87
 
72
- | Integración | Uso en Komplian |
73
- |-------------|-----------------|
74
- | **Atlassian** (Jira + Confluence) | Site \`komplian.atlassian.net\`, proyecto Jira **KAPP**. Plugin \`plugin-atlassian-atlassian\` / \`user-mcp-atlassian\`. |
75
- | **Chrome DevTools** | Depuración del navegador (\`user-chrome-devtools\`). |
88
+ Si además quieres los conectores de Cursor (OAuth / UI): **Settings → MCP** y busca **Atlassian**, **Sentry**, **Stripe**, **Chrome DevTools** — pueden coexistir con las entradas **KOMPLIAN-***; evita duplicar la misma acción en dos servidores.
76
89
 
77
90
  ## 3. Reinicio
78
91
 
79
- Tras editar \`mcp.json\`, **reinicia Cursor** (o recarga ventana) para cargar MCP.
92
+ Tras editar \`mcp.json\`, **reinicia Cursor** para cargar MCP.
80
93
 
81
94
  ## 4. Referencia interna
82
95
 
83
- Ver \`.cursor/rules/mcp-integrations.mdc\` en el monorepo (org Sentry, Stripe, reglas KAPP).
96
+ \`.cursor/rules/mcp-integrations.mdc\` (org Sentry, Stripe, KAPP).
84
97
  `;
85
98
 
86
99
  function findWorkspaceRoot(start) {
@@ -143,8 +156,8 @@ function parseMcpArgs(argv) {
143
156
  function usageMcpTools() {
144
157
  log(`Uso: npx komplian mcp-tools [opciones] [carpeta-monorepo]`);
145
158
  log(``);
146
- log(` Añade servidores MCP KOMPLIAN-* en .cursor/mcp.json (GitHub, Sentry, Stripe)`);
147
- log(` y genera .cursor/KOMPLIAN_MCP_SETUP.md (Atlassian + Chrome DevTools = solo Cursor).`);
159
+ log(` Añade servidores MCP KOMPLIAN-* en .cursor/mcp.json (GitHub, Atlassian, Sentry, Stripe, Chrome).`);
160
+ log(` y genera .cursor/KOMPLIAN_MCP_SETUP.md.`);
148
161
  log(``);
149
162
  log(` ${c.dim}Secretos: vacíos en JSON; no se publican en el paquete npm komplian.${c.reset}`);
150
163
  log(``);
@@ -394,12 +394,13 @@ function npmInstallEach(workspace) {
394
394
  }
395
395
 
396
396
  function usage() {
397
- log(`Uso: komplian onboard | postman | localhost | mcp-tools [opciones]`);
397
+ log(`Uso: komplian onboard | postman | localhost | mcp-tools | db [opciones]`);
398
398
  log(` npx komplian onboard --yes`);
399
399
  log(` npx komplian postman login ${c.dim}(una vez · guarda API key)${c.reset}`);
400
400
  log(` npx komplian postman --yes ${c.dim}(email @komplian.com)${c.reset}`);
401
401
  log(` npx komplian localhost --yes ${c.dim}(env local + api app web admin docs)${c.reset}`);
402
402
  log(` npx komplian mcp-tools --yes ${c.dim}(.cursor/mcp.json + guía MCP Komplian)${c.reset}`);
403
+ log(` npx komplian db:app:development ${c.dim}(psql Neon · KOMPLIAN_DATABASE_URL_*)${c.reset}`);
403
404
  log(``);
404
405
  log(` Antes (una vez): gh auth login -h github.com -s repo -s read:org -w`);
405
406
  log(` Requisitos: Node 18+, git, GitHub CLI (gh)`);
@@ -498,6 +499,25 @@ async function main() {
498
499
  await runMcpTools(rawArgv.slice(1));
499
500
  return;
500
501
  }
502
+ if (rawArgv[0]?.startsWith("db:")) {
503
+ const { runDb } = await import("./komplian-db.mjs");
504
+ const seg = rawArgv[0].split(":").filter(Boolean);
505
+ if (seg.length === 3 && seg[0] === "db") {
506
+ await runDb([
507
+ seg[1].toLowerCase(),
508
+ seg[2].toLowerCase(),
509
+ ...rawArgv.slice(1),
510
+ ]);
511
+ return;
512
+ }
513
+ log(`${c.red}✗${c.reset} Uso: npx komplian db:app:development`);
514
+ process.exit(1);
515
+ }
516
+ if (rawArgv[0] === "db") {
517
+ const { runDb } = await import("./komplian-db.mjs");
518
+ await runDb(rawArgv.slice(1));
519
+ return;
520
+ }
501
521
 
502
522
  const configPath = join(__dirname, "komplian-team-repos.json");
503
523
  const { argv, fromOnboardSubcommand } = normalizeArgv(rawArgv);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "komplian",
3
- "version": "0.4.7",
4
- "description": "Komplian CLI: onboard, Postman, localhost, mcp-tools (Cursor MCP). Node 18+. Published tarball has no .env / secrets.",
3
+ "version": "0.5.0",
4
+ "description": "Komplian CLI: onboard, Postman, localhost, mcp-tools, db (psql). Node 18+. Published tarball has no .env / secrets.",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=18"
@@ -14,6 +14,8 @@
14
14
  "komplian-postman.mjs",
15
15
  "komplian-localhost.mjs",
16
16
  "komplian-mcp-tools.mjs",
17
+ "komplian-db.mjs",
18
+ "KOMPLIAN_DATABASE_URLS.env.example",
17
19
  "komplian-team-repos.json",
18
20
  "README.md"
19
21
  ],
@@ -21,7 +23,7 @@
21
23
  "access": "public"
22
24
  },
23
25
  "scripts": {
24
- "prepublishOnly": "node --check komplian-onboard.mjs && node --check komplian-postman.mjs && node --check komplian-localhost.mjs && node --check komplian-mcp-tools.mjs"
26
+ "prepublishOnly": "node --check komplian-onboard.mjs && node --check komplian-postman.mjs && node --check komplian-localhost.mjs && node --check komplian-mcp-tools.mjs && node --check komplian-db.mjs"
25
27
  },
26
28
  "keywords": [
27
29
  "komplian",