komplian 0.7.1 → 0.7.2

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/README.md CHANGED
@@ -26,7 +26,7 @@ Opcional: `export POSTMAN_API_KEY=…` solo para la sesión actual (tiene priori
26
26
 
27
27
  El comando llama a `GET https://api.getpostman.com/me` y **solo continúa** si el email de la cuenta es `@komplian.com`. **Si ya existen** la colección **Komplian API** y los entornos con el mismo nombre en ese workspace, se **actualizan**; si no, se crean.
28
28
 
29
- **Variables de la API Komplian** (`apiKey`, `adminApiKey`, `workspaceId`, …) se rellenan en Postman automáticamente si están en `process.env` o en archivos `.env` (busca `api/.env`, `.env`, etc.; o `KOMPLIAN_DOTENV` / `--dotenv ruta`). Nombres típicos: `API_KEY`, `ADMIN_API_KEY`, `KOMPLIAN_WORKSPACE_ID`. Los JSON exportados en `./komplian-postman/` **no** incluyen secretos (para no commitearlos).
29
+ **Variables de la API Komplian** (`apiKey`, `adminApiKey`, `workspaceId`, …) se rellenan en Postman automáticamente si están en `process.env` o en archivos `.env` (busca `api/.env`, `.env`, etc.; o `KOMPLIAN_DOTENV` / `--dotenv ruta`). Nombres típicos: `API_KEY`, `ADMIN_API_KEY`, `KOMPLIAN_WORKSPACE_ID`. Los JSON exportados en `~/.komplian/postman-export/` (o `--out`) **no** incluyen secretos (para no commitearlos).
30
30
 
31
31
  - Solo exportar archivos (sin subir por API): `npx komplian postman --yes --export-only`
32
32
  - Otro workspace: `POSTMAN_WORKSPACE_ID=<id>`
@@ -8,7 +8,7 @@
8
8
  * npx komplian onboard --yes
9
9
  */
10
10
 
11
- import { spawnSync } from "node:child_process";
11
+ import { spawnSync, spawn } from "node:child_process";
12
12
  import { existsSync, readFileSync, mkdirSync, cpSync, rmSync, readdirSync, statSync } from "node:fs";
13
13
  import { dirname, join, resolve, normalize } from "node:path";
14
14
  import { fileURLToPath } from "node:url";
@@ -41,8 +41,13 @@ function log(s = "") {
41
41
  console.log(s);
42
42
  }
43
43
 
44
+ /** Branding and repo progress: always visible (even when KOMPLIAN_CLI_QUIET=1). */
45
+ function ux(s = "") {
46
+ console.log(s);
47
+ }
48
+
44
49
  function banner() {
45
- log(
50
+ ux(
46
51
  [
47
52
  `${c.cyan}${c.bold}`,
48
53
  "██╗ ██╗ ██████╗ ███╗ ███╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗",
@@ -52,12 +57,52 @@ function banner() {
52
57
  "██║ ██╗ ╚██████╔╝ ██║ ╚═╝ ██║ ██║ ███████╗ ██║ ██║ ██║ ██║ ╚████║",
53
58
  "╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝",
54
59
  `${c.reset}`,
55
- `${c.dim} Secure setup · GitHub CLI · git clone · Zero org OAuth setup${c.reset}`,
60
+ `${c.dim} Secure setup · GitHub CLI · git clone${c.reset}`,
56
61
  "",
57
62
  ].join("\n")
58
63
  );
59
64
  }
60
65
 
66
+ function startRepoSpinner(repoName) {
67
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
68
+ const cloud = `${c.blue}☁${c.reset}`;
69
+ let i = 0;
70
+ const id = setInterval(() => {
71
+ const fr = frames[i++ % frames.length];
72
+ process.stdout.write(
73
+ `\r ${cloud} ${c.cyan}${fr}${c.reset} ${c.bold}${repoName}${c.reset} ${c.dim}…${c.reset} `
74
+ );
75
+ }, 90);
76
+ return {
77
+ stop() {
78
+ clearInterval(id);
79
+ process.stdout.write("\r\x1b[K");
80
+ },
81
+ };
82
+ }
83
+
84
+ function runSpawn(cmd, args, cwd) {
85
+ return new Promise((resolve) => {
86
+ const child = spawn(cmd, args, {
87
+ ...spawnWin({
88
+ cwd,
89
+ stdio: ["ignore", "pipe", "pipe"],
90
+ }),
91
+ });
92
+ let stderr = "";
93
+ child.stderr?.setEncoding?.("utf8");
94
+ child.stderr?.on("data", (d) => {
95
+ stderr += String(d);
96
+ });
97
+ child.on("close", (code) => {
98
+ resolve({ status: code === 0 ? 0 : code ?? 1, stderr });
99
+ });
100
+ child.on("error", (err) => {
101
+ resolve({ status: 1, stderr: String(err?.message || err) });
102
+ });
103
+ });
104
+ }
105
+
61
106
  function ghOk() {
62
107
  const r = spawnSync(
63
108
  "gh",
@@ -71,8 +116,8 @@ function ghOk() {
71
116
  }
72
117
 
73
118
  function runGhAuth() {
74
- log(
75
- `${c.yellow}→${c.reset} Abre ${c.bold}GitHub${c.reset} en el navegador (OAuth). No vemos tu contraseña.`
119
+ ux(
120
+ `${c.yellow}→${c.reset} Opening ${c.bold}GitHub${c.reset} in your browser (OAuth). Your password is not shown here.`
76
121
  );
77
122
  const r = spawnSync(
78
123
  "gh",
@@ -102,32 +147,32 @@ function verifyOrgMembership(org) {
102
147
  try {
103
148
  const j = JSON.parse(mem.stdout);
104
149
  if (j.state === "active") return;
105
- log(
106
- `${c.red}✗${c.reset} Membresía en ${c.bold}${org}${c.reset} no activa (state: ${j.state || "?"}).`
150
+ console.error(
151
+ `${c.red}✗${c.reset} Org ${c.bold}${org}${c.reset} membership not active (state: ${j.state || "?"}).`
107
152
  );
108
153
  process.exit(1);
109
154
  } catch {
110
- log(`${c.red}✗${c.reset} Respuesta inválida al verificar la org.`);
155
+ console.error(`${c.red}✗${c.reset} Invalid response while verifying org membership.`);
111
156
  process.exit(1);
112
157
  }
113
158
  }
114
159
  const hint = (mem.stderr + mem.stdout).toLowerCase();
115
160
  if (hint.includes("404") || hint.includes("not found")) {
116
- log(
117
- `${c.red}✗${c.reset} Esta cuenta ${c.bold}no es miembro${c.reset} de la org ${c.bold}${org}${c.reset}.`
161
+ console.error(
162
+ `${c.red}✗${c.reset} This account is ${c.bold}not a member${c.reset} of org ${c.bold}${org}${c.reset}.`
118
163
  );
119
- log(
120
- `${c.dim} ¿Otra cuenta? gh auth logout && gh auth login -h github.com -s repo -s read:org -w${c.reset}`
164
+ console.error(
165
+ `${c.dim} Wrong account? gh auth logout && gh auth login -h github.com -s repo -s read:org -w${c.reset}`
121
166
  );
122
167
  process.exit(1);
123
168
  }
124
169
  if (hint.includes("403") || hint.includes("read:org") || hint.includes("scope")) {
125
- log(`${c.red}✗${c.reset} Falta scope ${c.bold}read:org${c.reset} en gh.`);
126
- log(`${c.dim} gh auth refresh -h github.com -s repo -s read:org${c.reset}`);
170
+ console.error(`${c.red}✗${c.reset} GitHub CLI needs ${c.bold}read:org${c.reset} scope.`);
171
+ console.error(`${c.dim} gh auth refresh -h github.com -s repo -s read:org${c.reset}`);
127
172
  process.exit(1);
128
173
  }
129
- log(
130
- `${c.red}✗${c.reset} No se pudo verificar la org (código ${mem.status}):\n${c.dim}${(mem.stderr || mem.stdout).trim()}${c.reset}`
174
+ console.error(
175
+ `${c.red}✗${c.reset} Could not verify org (exit ${mem.status}):\n${c.dim}${(mem.stderr || mem.stdout).trim()}${c.reset}`
131
176
  );
132
177
  process.exit(1);
133
178
  }
@@ -159,10 +204,12 @@ function needCmd(name, hint = "") {
159
204
  ? canRun("git", ["--version"])
160
205
  : canRun(name, ["--version"]);
161
206
  if (!ok) {
162
- log(`${c.red}✗${c.reset} No se puede ejecutar ${c.bold}${name}${c.reset} (¿PATH o instalación?).${hint ? ` ${hint}` : ""}`);
207
+ console.error(
208
+ `${c.red}✗${c.reset} Cannot run ${c.bold}${name}${c.reset} (PATH or install?).${hint ? ` ${hint}` : ""}`
209
+ );
163
210
  if (IS_WIN) {
164
- log(
165
- `${c.dim} Windows: cierra y abre la terminal tras instalar gh/git, o reinicia para refrescar PATH.${c.reset}`
211
+ console.error(
212
+ `${c.dim} Windows: restart the terminal after installing gh/git, or reboot to refresh PATH.${c.reset}`
166
213
  );
167
214
  }
168
215
  process.exit(1);
@@ -180,7 +227,7 @@ function ghRepoList(org) {
180
227
  spawnWin({ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] })
181
228
  );
182
229
  if (r.status !== 0) {
183
- log(`${c.red}✗${c.reset} gh repo list falló:\n${r.stderr || r.stdout}`);
230
+ console.error(`${c.red}✗${c.reset} gh repo list failed:\n${r.stderr || r.stdout}`);
184
231
  process.exit(1);
185
232
  }
186
233
  const rows = JSON.parse(r.stdout || "[]");
@@ -209,8 +256,8 @@ function resolveRepos(cfg, org, teamArg, allRepos) {
209
256
 
210
257
  const teams = cfg.teams || {};
211
258
  if (!teams[team]) {
212
- log(
213
- `${c.red}✗${c.reset} Equipo desconocido ${c.bold}${team}${c.reset}. Válidos: ${Object.keys(teams).sort().join(", ")}`
259
+ console.error(
260
+ `${c.red}✗${c.reset} Unknown team ${c.bold}${team}${c.reset}. Valid: ${Object.keys(teams).sort().join(", ")}`
214
261
  );
215
262
  process.exit(2);
216
263
  }
@@ -236,47 +283,50 @@ function isSafeTargetDir(abs) {
236
283
  function cloudLine(org, name, status) {
237
284
  const cloud = `${c.blue}☁${c.reset}`;
238
285
  if (status === "ok") {
239
- return ` ${cloud} ${c.green}✓${c.reset} ${c.bold}${org}/${name}${c.reset} ${c.dim}clonado${c.reset}`;
286
+ return ` ${cloud} ${c.green}✓${c.reset} ${c.bold}${org}/${name}${c.reset} ${c.dim}cloned${c.reset}`;
240
287
  }
241
288
  if (status === "skip") {
242
- return ` ${cloud} ${c.yellow}○${c.reset} ${org}/${name} ${c.dim}(ya existe)${c.reset}`;
289
+ return ` ${cloud} ${c.yellow}○${c.reset} ${org}/${name} ${c.dim}(already present)${c.reset}`;
243
290
  }
244
291
  if (status === "fail") {
245
- return ` ${cloud} ${c.red}✗${c.reset} ${org}/${name} ${c.dim}falló${c.reset}`;
292
+ return ` ${cloud} ${c.red}✗${c.reset} ${org}/${name} ${c.dim}failed${c.reset}`;
246
293
  }
247
- return ` ${cloud} ${c.cyan}…${c.reset} ${org}/${name} ${c.dim}clonando…${c.reset}`;
294
+ return ` ${cloud} ${c.cyan}…${c.reset} ${org}/${name} ${c.dim}cloning…${c.reset}`;
248
295
  }
249
296
 
250
- function cloneOne(org, name, workspace, useSsh) {
297
+ async function cloneOneAsync(org, name, workspace, useSsh) {
251
298
  const q = process.env.KOMPLIAN_CLI_QUIET === "1";
252
299
  const gitDir = join(workspace, name, ".git");
253
300
  if (existsSync(gitDir)) {
254
- if (q) console.log(`${c.yellow}○${c.reset} ${name}`);
255
- else log(cloudLine(org, name, "skip"));
301
+ ux(cloudLine(org, name, "skip"));
256
302
  return true;
257
303
  }
258
- if (!q) process.stdout.write(`\r${cloudLine(org, name, "pending")}`);
304
+
259
305
  const args = useSsh
260
306
  ? ["clone", `git@github.com:${org}/${name}.git`, name]
261
307
  : ["repo", "clone", `${org}/${name}`, name];
262
308
  const cmd = useSsh ? "git" : "gh";
263
- const r = spawnSync(cmd, args, {
264
- ...spawnWin({
265
- cwd: workspace,
266
- encoding: "utf8",
267
- stdio: ["ignore", "pipe", "pipe"],
268
- }),
269
- });
270
- if (!q) process.stdout.write("\r\x1b[K");
309
+
310
+ const tty = process.stdout.isTTY;
311
+ const spin = tty ? startRepoSpinner(name) : null;
312
+ if (!tty) {
313
+ ux(
314
+ `${c.blue}☁${c.reset} ${c.cyan}…${c.reset} ${c.bold}${name}${c.reset} ${c.dim}cloning…${c.reset}`
315
+ );
316
+ }
317
+
318
+ const r = await runSpawn(cmd, args, workspace);
319
+ if (spin) spin.stop();
320
+
271
321
  if (r.status === 0) {
272
- if (q) console.log(`${c.green}✓${c.reset} ${name}`);
273
- else log(cloudLine(org, name, "ok"));
322
+ ux(cloudLine(org, name, "ok"));
274
323
  return true;
275
324
  }
276
- if (q) console.error(`${c.red}✗${c.reset} ${name}`);
277
- else {
278
- log(cloudLine(org, name, "fail"));
279
- if (r.stderr) log(`${c.dim}${r.stderr.trim()}${c.reset}`);
325
+ ux(cloudLine(org, name, "fail"));
326
+ const err = (r.stderr || "").trim();
327
+ if (err) {
328
+ if (q) console.error(`${c.dim}${err.slice(0, 500)}${c.reset}`);
329
+ else log(`${c.dim}${err}${c.reset}`);
280
330
  }
281
331
  return false;
282
332
  }
@@ -286,7 +336,7 @@ function copyCursorPack(workspace, cursorRepoUrl) {
286
336
  if (cursorRepoUrl && String(cursorRepoUrl).trim()) {
287
337
  const tmp = join(workspace, ".cursor-bootstrap-tmp");
288
338
  rmSync(tmp, { recursive: true, force: true });
289
- log(`${c.dim}→${c.reset} Cursor pack (clone superficial)…`);
339
+ ux(`${c.dim}→${c.reset} Cursor rules pack…`);
290
340
  const r = spawnSync(
291
341
  "git",
292
342
  ["clone", "--depth", "1", String(cursorRepoUrl).trim(), tmp],
@@ -300,15 +350,18 @@ function copyCursorPack(workspace, cursorRepoUrl) {
300
350
  rmSync(rootCursor, { recursive: true, force: true });
301
351
  cpSync(join(tmp, ".cursor"), rootCursor, { recursive: true });
302
352
  rmSync(tmp, { recursive: true, force: true });
303
- log(`${c.green}✓${c.reset} ${c.bold}.cursor${c.reset} ${c.dim}KOMPLIAN_CURSOR_REPO${c.reset}`);
353
+ ux(`${c.green}✓${c.reset} ${c.bold}.cursor${c.reset} ${c.dim}(KOMPLIAN_CURSOR_REPO)${c.reset}`);
304
354
  return;
305
355
  }
306
356
  rmSync(tmp, { recursive: true, force: true });
307
- log(`${c.yellow}○${c.reset} KOMPLIAN_CURSOR_REPO: falló el clone o no hay .cursor/ en la raíz`);
357
+ ux(
358
+ `${c.yellow}○${c.reset} KOMPLIAN_CURSOR_REPO: clone failed or no .cursor/ at repo root`
359
+ );
360
+ } else if (!process.env.KOMPLIAN_CLI_QUIET) {
361
+ log(
362
+ `${c.dim}○ No .cursor/ (optional: KOMPLIAN_CURSOR_REPO=<git url>).${c.reset}`
363
+ );
308
364
  }
309
- log(
310
- `${c.dim}○ Sin .cursor/ en el workspace (opcional: KOMPLIAN_CURSOR_REPO=<url git>).${c.reset}`
311
- );
312
365
  }
313
366
 
314
367
  /** Sin esto, `npm install` crea o retoca package-lock.json y git muestra cambios sin querer. */
@@ -322,8 +375,8 @@ function npmInstallOneRepo(dir, name) {
322
375
  const pkg = join(dir, "package.json");
323
376
  if (!existsSync(pkg)) return { ok: true, skipped: true };
324
377
 
325
- const stdio =
326
- process.env.KOMPLIAN_CLI_QUIET === "1" ? "ignore" : "inherit";
378
+ const q = process.env.KOMPLIAN_CLI_QUIET === "1";
379
+ const stdio = q ? "ignore" : "inherit";
327
380
 
328
381
  const yarnLock = join(dir, "yarn.lock");
329
382
  const pnpmLock = join(dir, "pnpm-lock.yaml");
@@ -332,11 +385,17 @@ function npmInstallOneRepo(dir, name) {
332
385
  if (existsSync(yarnLock)) {
333
386
  if (!canRun("yarn", ["--version"])) {
334
387
  log(
335
- `${c.yellow}○${c.reset} ${name} ${c.dim}(yarn.lock; instala yarn o ejecuta yarn install a mano)${c.reset}`
388
+ `${c.yellow}○${c.reset} ${name} ${c.dim}(yarn.lock; install yarn or run yarn install manually)${c.reset}`
336
389
  );
337
390
  return { ok: true, skipped: true };
338
391
  }
339
- log(`${c.dim}→${c.reset} ${name} ${c.dim}(yarn)${c.reset}`);
392
+ if (q) {
393
+ ux(
394
+ ` ${c.blue}☁${c.reset} ${c.cyan}…${c.reset} ${c.bold}${name}${c.reset} ${c.dim}yarn install…${c.reset}`
395
+ );
396
+ } else {
397
+ log(`${c.dim}→${c.reset} ${name} ${c.dim}(yarn)${c.reset}`);
398
+ }
340
399
  const r = spawnSync(
341
400
  "yarn",
342
401
  ["install", "--frozen-lockfile"],
@@ -348,11 +407,17 @@ function npmInstallOneRepo(dir, name) {
348
407
  if (existsSync(pnpmLock)) {
349
408
  if (!canRun("pnpm", ["--version"])) {
350
409
  log(
351
- `${c.yellow}○${c.reset} ${name} ${c.dim}(pnpm-lock; instala pnpm o pnpm install a mano)${c.reset}`
410
+ `${c.yellow}○${c.reset} ${name} ${c.dim}(pnpm-lock; install pnpm or run pnpm install manually)${c.reset}`
352
411
  );
353
412
  return { ok: true, skipped: true };
354
413
  }
355
- log(`${c.dim}→${c.reset} ${name} ${c.dim}(pnpm)${c.reset}`);
414
+ if (q) {
415
+ ux(
416
+ ` ${c.blue}☁${c.reset} ${c.cyan}…${c.reset} ${c.bold}${name}${c.reset} ${c.dim}pnpm install…${c.reset}`
417
+ );
418
+ } else {
419
+ log(`${c.dim}→${c.reset} ${name} ${c.dim}(pnpm)${c.reset}`);
420
+ }
356
421
  const r = spawnSync(
357
422
  "pnpm",
358
423
  ["install", "--frozen-lockfile"],
@@ -362,23 +427,35 @@ function npmInstallOneRepo(dir, name) {
362
427
  }
363
428
 
364
429
  if (!canRun("npm", ["--version"])) {
365
- log(`${c.yellow}○${c.reset} npm no está en PATH — omito ${name}`);
430
+ log(`${c.yellow}○${c.reset} npm not in PATH — skipping ${name}`);
366
431
  return { ok: true, skipped: true };
367
432
  }
368
433
 
369
434
  const quiet = npmQuietFlags();
370
435
 
371
436
  if (existsSync(npmLock)) {
372
- log(`${c.dim}→${c.reset} ${name} ${c.dim}(npm ci — lock sin cambios)${c.reset}`);
437
+ if (q) {
438
+ ux(
439
+ ` ${c.blue}☁${c.reset} ${c.cyan}…${c.reset} ${c.bold}${name}${c.reset} ${c.dim}npm ci…${c.reset}`
440
+ );
441
+ } else {
442
+ log(`${c.dim}→${c.reset} ${name} ${c.dim}(npm ci)${c.reset}`);
443
+ }
373
444
  const r = spawnSync("npm", ["ci", ...quiet], spawnWin({ cwd: dir, stdio }));
374
445
  if (r.status === 0) return { ok: true, skipped: false };
375
446
  log(
376
- `${c.yellow}○${c.reset} ${name}: npm ci falló (¿lock desincronizado?). ${c.dim}Revisa con npm install en ese repo.${c.reset}`
447
+ `${c.yellow}○${c.reset} ${name}: npm ci failed (lock out of sync?). ${c.dim}Try npm install in that repo.${c.reset}`
377
448
  );
378
449
  return { ok: false, skipped: false };
379
450
  }
380
451
 
381
- log(`${c.dim}→${c.reset} ${name} ${c.dim}(npm install — sin crear package-lock)${c.reset}`);
452
+ if (q) {
453
+ ux(
454
+ ` ${c.blue}☁${c.reset} ${c.cyan}…${c.reset} ${c.bold}${name}${c.reset} ${c.dim}npm install…${c.reset}`
455
+ );
456
+ } else {
457
+ log(`${c.dim}→${c.reset} ${name} ${c.dim}(npm install — no new lockfile)${c.reset}`);
458
+ }
382
459
  const r = spawnSync(
383
460
  "npm",
384
461
  ["install", ...quiet, "--no-package-lock"],
@@ -392,6 +469,9 @@ function npmInstallEach(workspace) {
392
469
  if (!q) {
393
470
  log("");
394
471
  log(`${c.cyan}━━ dependencies ━━${c.reset}`);
472
+ } else {
473
+ ux("");
474
+ ux(`${c.cyan}Dependencies${c.reset}`);
395
475
  }
396
476
  for (const ent of readdirSync(workspace)) {
397
477
  const d = join(workspace, ent);
@@ -399,7 +479,12 @@ function npmInstallEach(workspace) {
399
479
  const { ok, skipped } = npmInstallOneRepo(d, ent);
400
480
  if (skipped) continue;
401
481
  if (q) {
402
- console.log(`${ok ? c.green + "✓" + c.reset : c.yellow + "○" + c.reset} ${ent}`);
482
+ const cloud = `${c.blue}☁${c.reset}`;
483
+ if (ok) {
484
+ ux(` ${cloud} ${c.green}✓${c.reset} ${c.bold}${ent}${c.reset} ${c.dim}deps${c.reset}`);
485
+ } else {
486
+ ux(` ${cloud} ${c.yellow}○${c.reset} ${c.bold}${ent}${c.reset} ${c.dim}deps${c.reset}`);
487
+ }
403
488
  } else if (ok) {
404
489
  log(`${c.green}✓${c.reset} ${ent}`);
405
490
  } else {
@@ -409,30 +494,34 @@ function npmInstallEach(workspace) {
409
494
  }
410
495
 
411
496
  function usage() {
412
- log(`Uso: komplian setup | onboard | postman | mcp-tools | db:all:dev | localhost | db …`);
413
- log(` ${c.bold}Todo en uno:${c.reset} ${c.cyan}npx komplian setup${c.reset} ${c.dim}(onboard+postman+mcp+db+localhost; formulario en navegador si hace falta)${c.reset}`);
414
- log(` ${c.bold}Setup por pasos:${c.reset}`);
415
- log(` 1. npx komplian onboard --yes`);
416
- log(` 2. npx komplian postman --yes ${c.dim}(@komplian.com)${c.reset}`);
417
- log(` 3. npx komplian mcp-tools --yes`);
418
- log(` 4. npx komplian db:all:dev ${c.dim}(Postman + 3 URLs dev → ~/.komplian + .env.local)${c.reset}`);
419
- log(` 5. npx komplian localhost --yes`);
420
- log(``);
421
- log(` npx komplian db:app:development ${c.dim}(psql una DB)${c.reset}`);
422
- log(``);
423
- log(` Antes (una vez): gh auth login -h github.com -s repo -s read:org -w`);
424
- log(` Requisitos: Node 18+, git, GitHub CLI (gh)`);
425
- log(``);
426
- log(` onboard implica --install salvo --no-install`);
427
- log(` [carpeta] Destino (por defecto: directorio actual, no ~/komplian)`);
428
- log(` -y, --yes Sin menú interactivo (equipo por defecto del JSON)`);
429
- log(` -t, --team <slug> Equipo en komplian-team-repos.json`);
430
- log(` -i, --install npm install en cada repo con package.json`);
431
- log(` --no-install No ejecutar npm install`);
432
- log(` --all-repos Todos los repos visibles (menos exclude_from_all)`);
433
- log(` --ssh Clonar con git@github.com:…`);
434
- log(` --list-teams Lista slugs de equipo (solo JSON) y sale`);
435
- log(` -h, --help`);
497
+ console.log(`Usage: komplian setup | onboard | postman | mcp-tools | db:all:dev | localhost | db …`);
498
+ console.log(
499
+ ` ${c.bold}All-in-one:${c.reset} ${c.cyan}npx komplian setup${c.reset} ${c.dim}(onboard+postman+mcp+db+localhost; browser form if needed)${c.reset}`
500
+ );
501
+ console.log(` ${c.bold}Step by step:${c.reset}`);
502
+ console.log(` 1. npx komplian onboard --yes`);
503
+ console.log(` 2. npx komplian postman --yes ${c.dim}(@komplian.com)${c.reset}`);
504
+ console.log(` 3. npx komplian mcp-tools --yes`);
505
+ console.log(
506
+ ` 4. npx komplian db:all:dev ${c.dim}(Postman + 3 dev URLs → ~/.komplian + .env.local)${c.reset}`
507
+ );
508
+ console.log(` 5. npx komplian localhost --yes`);
509
+ console.log(``);
510
+ console.log(` npx komplian db:app:development ${c.dim}(psql one DB)${c.reset}`);
511
+ console.log(``);
512
+ console.log(` Once: gh auth login -h github.com -s repo -s read:org -w`);
513
+ console.log(` Requires: Node 18+, git, GitHub CLI (gh)`);
514
+ console.log(``);
515
+ console.log(` onboard implies --install unless --no-install`);
516
+ console.log(` [folder] Target (default: cwd, not ~/komplian)`);
517
+ console.log(` -y, --yes Non-interactive (default team from JSON)`);
518
+ console.log(` -t, --team <slug> Team in komplian-team-repos.json`);
519
+ console.log(` -i, --install npm install in each repo with package.json`);
520
+ console.log(` --no-install Skip npm install`);
521
+ console.log(` --all-repos All visible repos (minus exclude_from_all)`);
522
+ console.log(` --ssh Clone with git@github.com:…`);
523
+ console.log(` --list-teams Print team slugs (JSON only) and exit`);
524
+ console.log(` -h, --help`);
436
525
  }
437
526
 
438
527
  function parseArgs(argv) {
@@ -459,8 +548,8 @@ function parseArgs(argv) {
459
548
  else if (a === "-h" || a === "--help") out.help = true;
460
549
  else if (a === "-t" || a === "--team") {
461
550
  out.team = argv[++i] || "";
462
- } else if (a.startsWith("-")) {
463
- log(`${c.red}✗${c.reset} Opción desconocida: ${a}`);
551
+ } else if (a.startsWith("-")) {
552
+ console.error(`${c.red}✗${c.reset} Unknown option: ${a}`);
464
553
  usage();
465
554
  process.exit(1);
466
555
  } else rest.push(a);
@@ -560,20 +649,22 @@ async function main() {
560
649
  }
561
650
 
562
651
  if (!existsSync(configPath)) {
563
- log(`${c.red}✗${c.reset} Falta la config: ${configPath}`);
652
+ console.error(`${c.red}✗${c.reset} Missing config: ${configPath}`);
564
653
  process.exit(1);
565
654
  }
566
655
 
567
656
  const cfg = loadConfig(configPath);
568
657
 
569
658
  if (args.listTeams) {
570
- log(Object.keys(cfg.teams || {}).sort().join("\n"));
659
+ console.log(Object.keys(cfg.teams || {}).sort().join("\n"));
571
660
  return;
572
661
  }
573
662
 
574
663
  const nodeMajor = Number(process.versions.node.split(".")[0], 10);
575
664
  if (nodeMajor < 18) {
576
- log(`${c.red}✗${c.reset} Hace falta Node 18+. Tienes ${process.versions.node}.`);
665
+ console.error(
666
+ `${c.red}✗${c.reset} Node 18+ required. You have ${process.versions.node}.`
667
+ );
577
668
  process.exit(1);
578
669
  }
579
670
 
@@ -593,18 +684,16 @@ async function main() {
593
684
  process.exit(1);
594
685
  }
595
686
  }
596
- if (!process.env.KOMPLIAN_CLI_QUIET) {
597
- log(`${c.green}✓${c.reset} GitHub CLI OK`);
598
- }
687
+
688
+ banner();
689
+ ux(`${c.green}✓${c.reset} GitHub CLI`);
599
690
 
600
691
  const org = process.env.KOMPLIAN_ORG || cfg.org || "Komplian";
601
692
  verifyOrgMembership(org);
602
- logGhIdentity();
603
- log(
604
- `${c.green}✓${c.reset} Org ${c.bold}${org}${c.reset}: ${c.dim}membresía activa${c.reset}`
605
- );
606
-
607
- banner();
693
+ if (!process.env.KOMPLIAN_CLI_QUIET) {
694
+ logGhIdentity();
695
+ }
696
+ ux(`${c.green}✓${c.reset} Org ${c.bold}${org}${c.reset}`);
608
697
 
609
698
  let team = args.team;
610
699
  if (!team && !args.yes && !args.allRepos) {
@@ -613,7 +702,9 @@ async function main() {
613
702
 
614
703
  const repos = resolveRepos(cfg, org, team, args.allRepos);
615
704
  if (repos.length === 0) {
616
- log(`${c.red}✗${c.reset} No hay repos que clonar (permisos / equipo / org).`);
705
+ console.error(
706
+ `${c.red}✗${c.reset} No repositories to clone (permissions / team / org).`
707
+ );
617
708
  process.exit(1);
618
709
  }
619
710
 
@@ -623,21 +714,28 @@ async function main() {
623
714
  }
624
715
  const abs = resolve(workspace.replace(/^~(?=$|[/\\])/, homedir()));
625
716
  if (!isSafeTargetDir(abs)) {
626
- log(`${c.red}✗${c.reset} Carpeta destino no segura: ${abs}`);
717
+ console.error(`${c.red}✗${c.reset} Unsafe destination folder: ${abs}`);
627
718
  process.exit(1);
628
719
  }
629
720
 
630
721
  mkdirSync(abs, { recursive: true });
631
- log("");
632
- log(`${c.cyan}━━ Workspace ━━${c.reset} ${c.bold}${abs}${c.reset}`);
633
- log(`${c.cyan}━━ Org ━━${c.reset} ${c.bold}${org}${c.reset}`);
634
- if (team) log(`${c.cyan}━━ Equipo ━━${c.reset} ${c.bold}${team}${c.reset}`);
635
- log(`${c.cyan}━━ Repos (${repos.length}) ━━${c.reset}`);
636
- log("");
722
+ const q = process.env.KOMPLIAN_CLI_QUIET === "1";
723
+ ux("");
724
+ if (!q) {
725
+ log(`${c.cyan}━━ Workspace ━━${c.reset} ${c.bold}${abs}${c.reset}`);
726
+ log(`${c.cyan}━━ Org ━━${c.reset} ${c.bold}${org}${c.reset}`);
727
+ if (team) log(`${c.cyan}━━ Team ━━${c.reset} ${c.bold}${team}${c.reset}`);
728
+ log(`${c.cyan}━━ Repos (${repos.length}) ━━${c.reset}`);
729
+ } else {
730
+ ux(`${c.dim}Workspace:${c.reset} ${abs}`);
731
+ if (team) ux(`${c.dim}Team:${c.reset} ${team}`);
732
+ ux(`${c.cyan}Repositories${c.reset} ${c.dim}(${repos.length})${c.reset}`);
733
+ }
734
+ ux("");
637
735
 
638
736
  let failed = 0;
639
737
  for (const name of repos) {
640
- const ok = cloneOne(org, name, abs, args.ssh);
738
+ const ok = await cloneOneAsync(org, name, abs, args.ssh);
641
739
  if (!ok) failed += 1;
642
740
  }
643
741
 
@@ -647,16 +745,22 @@ async function main() {
647
745
  npmInstallEach(abs);
648
746
  }
649
747
 
650
- log("");
651
- log(`${c.cyan}━━ Listo ━━${c.reset}`);
748
+ ux("");
749
+ ux(`${c.cyan}━━ Done ━━${c.reset}`);
652
750
  if (failed > 0) {
653
- log(`${c.yellow}○${c.reset} ${failed} repo(s) fallaron — revisa acceso y reintenta.`);
751
+ ux(
752
+ `${c.yellow}○${c.reset} ${failed} repo(s) failed — check access and retry.`
753
+ );
754
+ }
755
+ ux(`${c.green}✓${c.reset} Open in Cursor: ${c.bold}File → Open Folder → ${abs}${c.reset}`);
756
+ if (!q) {
757
+ log(
758
+ `${c.dim} package-lock: npm ci. No lock: npm install --no-package-lock. yarn/pnpm: frozen lockfile. KOMPLIAN_NPM_AUDIT=1 enables npm audit.${c.reset}`
759
+ );
760
+ log(
761
+ `${c.dim} Copy .env.example → .env per project; secrets in 1Password — never commit.${c.reset}`
762
+ );
654
763
  }
655
- log(`${c.green}✓${c.reset} Cursor: ${c.bold}File → Open Folder → ${abs}${c.reset}`);
656
- log(
657
- `${c.dim} Con package-lock.json: npm ci (no retoca el lock). Sin lock: npm install --no-package-lock. yarn/pnpm: lock congelado. KOMPLIAN_NPM_AUDIT=1 activa auditoría en npm.${c.reset}`
658
- );
659
- log(`${c.dim} .env.example → .env por proyecto; secretos en 1Password — nunca commit.${c.reset}`);
660
764
  }
661
765
 
662
766
  main().catch((e) => {
@@ -46,6 +46,11 @@ function formatHomePath(absPath) {
46
46
  return absPath;
47
47
  }
48
48
 
49
+ /** JSON exports never go into the monorepo root (avoids accidental commits). */
50
+ function defaultPostmanExportDir() {
51
+ return join(homedir(), ".komplian", "postman-export");
52
+ }
53
+
49
54
  function maskEmail(email) {
50
55
  if (!email || typeof email !== "string" || !email.includes("@")) {
51
56
  return "[sin email]";
@@ -855,7 +860,7 @@ function usage() {
855
860
  log(``);
856
861
  log(` -y, --yes Sin prompts extra (si falta API key y hay TTY, pide la clave una vez y guarda)`);
857
862
  log(` --export-only Solo escribe JSON en disco (no llama a la API de Postman)`);
858
- log(` --out <dir> Carpeta para export (por defecto: ./komplian-postman)`);
863
+ log(` --out <dir> Export folder (default: ~/.komplian/postman-export)`);
859
864
  log(` --dotenv <ruta> .env extra (además de api/.env, .env, KOMPLIAN_DOTENV)`);
860
865
  log(` -h, --help`);
861
866
  log(``);
@@ -965,23 +970,29 @@ export async function runPostman(argv) {
965
970
  apiKey,
966
971
  process.env.POSTMAN_WORKSPACE_ID
967
972
  );
968
- log(
969
- `${c.green}✓${c.reset} Workspace Postman: ${c.bold}${workspaceId}${c.reset}`
970
- );
973
+ if (!process.env.KOMPLIAN_CLI_QUIET) {
974
+ log(`${c.green}✓${c.reset} Postman workspace: ${c.bold}${workspaceId}${c.reset}`);
975
+ }
971
976
 
972
- const outBase = args.outDir
973
- ? resolve(args.outDir)
974
- : resolve(process.cwd(), "komplian-postman");
977
+ ensureSecureKomplianDir();
978
+ const outBase = args.outDir ? resolve(args.outDir) : defaultPostmanExportDir();
975
979
  mkdirSync(outBase, { recursive: true });
976
980
 
977
981
  const collPath = join(outBase, "Komplian-API.postman_collection.json");
978
982
  writeFileSync(collPath, JSON.stringify(collection, null, 2), "utf8");
979
- log(`${c.green}✓${c.reset} Export: ${c.dim}${collPath}${c.reset}`);
983
+ const quiet = process.env.KOMPLIAN_CLI_QUIET === "1";
984
+ if (!quiet) {
985
+ log(`${c.green}✓${c.reset} Export: ${c.dim}${collPath}${c.reset}`);
986
+ }
980
987
 
981
988
  for (const env of envsForExport) {
982
989
  const safe = env.name.replace(/[\\/]/g, "-") + ".postman_environment.json";
983
990
  writeFileSync(join(outBase, safe), JSON.stringify({ ...env }, null, 2), "utf8");
984
- log(`${c.green}✓${c.reset} Export: ${c.dim}${join(outBase, safe)}${c.reset} ${c.dim}(sin secretos; no commitear)${c.reset}`);
991
+ if (!quiet) {
992
+ log(
993
+ `${c.green}✓${c.reset} Export: ${c.dim}${join(outBase, safe)}${c.reset} ${c.dim}(no secrets)${c.reset}`
994
+ );
995
+ }
985
996
  }
986
997
 
987
998
  if (args.exportOnly) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "komplian",
3
- "version": "0.7.1",
3
+ "version": "0.7.2",
4
4
  "description": "Komplian CLI: setup (all-in-one), onboard, Postman, localhost, mcp-tools, db (psql). Node 18+.",
5
5
  "type": "module",
6
6
  "engines": {