komplian 0.7.0 → 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.
@@ -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";
@@ -37,11 +37,17 @@ const c = {
37
37
  };
38
38
 
39
39
  function log(s = "") {
40
+ if (process.env.KOMPLIAN_CLI_QUIET === "1") return;
41
+ console.log(s);
42
+ }
43
+
44
+ /** Branding and repo progress: always visible (even when KOMPLIAN_CLI_QUIET=1). */
45
+ function ux(s = "") {
40
46
  console.log(s);
41
47
  }
42
48
 
43
49
  function banner() {
44
- log(
50
+ ux(
45
51
  [
46
52
  `${c.cyan}${c.bold}`,
47
53
  "██╗ ██╗ ██████╗ ███╗ ███╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗",
@@ -51,12 +57,52 @@ function banner() {
51
57
  "██║ ██╗ ╚██████╔╝ ██║ ╚═╝ ██║ ██║ ███████╗ ██║ ██║ ██║ ██║ ╚████║",
52
58
  "╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝",
53
59
  `${c.reset}`,
54
- `${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}`,
55
61
  "",
56
62
  ].join("\n")
57
63
  );
58
64
  }
59
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
+
60
106
  function ghOk() {
61
107
  const r = spawnSync(
62
108
  "gh",
@@ -70,8 +116,8 @@ function ghOk() {
70
116
  }
71
117
 
72
118
  function runGhAuth() {
73
- log(
74
- `${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.`
75
121
  );
76
122
  const r = spawnSync(
77
123
  "gh",
@@ -101,32 +147,32 @@ function verifyOrgMembership(org) {
101
147
  try {
102
148
  const j = JSON.parse(mem.stdout);
103
149
  if (j.state === "active") return;
104
- log(
105
- `${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 || "?"}).`
106
152
  );
107
153
  process.exit(1);
108
154
  } catch {
109
- 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.`);
110
156
  process.exit(1);
111
157
  }
112
158
  }
113
159
  const hint = (mem.stderr + mem.stdout).toLowerCase();
114
160
  if (hint.includes("404") || hint.includes("not found")) {
115
- log(
116
- `${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}.`
117
163
  );
118
- log(
119
- `${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}`
120
166
  );
121
167
  process.exit(1);
122
168
  }
123
169
  if (hint.includes("403") || hint.includes("read:org") || hint.includes("scope")) {
124
- log(`${c.red}✗${c.reset} Falta scope ${c.bold}read:org${c.reset} en gh.`);
125
- 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}`);
126
172
  process.exit(1);
127
173
  }
128
- log(
129
- `${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}`
130
176
  );
131
177
  process.exit(1);
132
178
  }
@@ -158,10 +204,12 @@ function needCmd(name, hint = "") {
158
204
  ? canRun("git", ["--version"])
159
205
  : canRun(name, ["--version"]);
160
206
  if (!ok) {
161
- 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
+ );
162
210
  if (IS_WIN) {
163
- log(
164
- `${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}`
165
213
  );
166
214
  }
167
215
  process.exit(1);
@@ -179,7 +227,7 @@ function ghRepoList(org) {
179
227
  spawnWin({ encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] })
180
228
  );
181
229
  if (r.status !== 0) {
182
- 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}`);
183
231
  process.exit(1);
184
232
  }
185
233
  const rows = JSON.parse(r.stdout || "[]");
@@ -208,8 +256,8 @@ function resolveRepos(cfg, org, teamArg, allRepos) {
208
256
 
209
257
  const teams = cfg.teams || {};
210
258
  if (!teams[team]) {
211
- log(
212
- `${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(", ")}`
213
261
  );
214
262
  process.exit(2);
215
263
  }
@@ -235,42 +283,51 @@ function isSafeTargetDir(abs) {
235
283
  function cloudLine(org, name, status) {
236
284
  const cloud = `${c.blue}☁${c.reset}`;
237
285
  if (status === "ok") {
238
- 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}`;
239
287
  }
240
288
  if (status === "skip") {
241
- 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}`;
242
290
  }
243
291
  if (status === "fail") {
244
- 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}`;
245
293
  }
246
- 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}`;
247
295
  }
248
296
 
249
- function cloneOne(org, name, workspace, useSsh) {
297
+ async function cloneOneAsync(org, name, workspace, useSsh) {
298
+ const q = process.env.KOMPLIAN_CLI_QUIET === "1";
250
299
  const gitDir = join(workspace, name, ".git");
251
300
  if (existsSync(gitDir)) {
252
- log(cloudLine(org, name, "skip"));
301
+ ux(cloudLine(org, name, "skip"));
253
302
  return true;
254
303
  }
255
- process.stdout.write(`\r${cloudLine(org, name, "pending")}`);
304
+
256
305
  const args = useSsh
257
306
  ? ["clone", `git@github.com:${org}/${name}.git`, name]
258
307
  : ["repo", "clone", `${org}/${name}`, name];
259
308
  const cmd = useSsh ? "git" : "gh";
260
- const r = spawnSync(cmd, args, {
261
- ...spawnWin({
262
- cwd: workspace,
263
- encoding: "utf8",
264
- stdio: ["ignore", "pipe", "pipe"],
265
- }),
266
- });
267
- 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
+
268
321
  if (r.status === 0) {
269
- log(cloudLine(org, name, "ok"));
322
+ ux(cloudLine(org, name, "ok"));
270
323
  return true;
271
324
  }
272
- log(cloudLine(org, name, "fail"));
273
- 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}`);
330
+ }
274
331
  return false;
275
332
  }
276
333
 
@@ -279,7 +336,7 @@ function copyCursorPack(workspace, cursorRepoUrl) {
279
336
  if (cursorRepoUrl && String(cursorRepoUrl).trim()) {
280
337
  const tmp = join(workspace, ".cursor-bootstrap-tmp");
281
338
  rmSync(tmp, { recursive: true, force: true });
282
- log(`${c.dim}→${c.reset} Cursor pack (clone superficial)…`);
339
+ ux(`${c.dim}→${c.reset} Cursor rules pack…`);
283
340
  const r = spawnSync(
284
341
  "git",
285
342
  ["clone", "--depth", "1", String(cursorRepoUrl).trim(), tmp],
@@ -293,15 +350,18 @@ function copyCursorPack(workspace, cursorRepoUrl) {
293
350
  rmSync(rootCursor, { recursive: true, force: true });
294
351
  cpSync(join(tmp, ".cursor"), rootCursor, { recursive: true });
295
352
  rmSync(tmp, { recursive: true, force: true });
296
- 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}`);
297
354
  return;
298
355
  }
299
356
  rmSync(tmp, { recursive: true, force: true });
300
- 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
+ );
301
364
  }
302
- log(
303
- `${c.dim}○ Sin .cursor/ en el workspace (opcional: KOMPLIAN_CURSOR_REPO=<url git>).${c.reset}`
304
- );
305
365
  }
306
366
 
307
367
  /** Sin esto, `npm install` crea o retoca package-lock.json y git muestra cambios sin querer. */
@@ -315,6 +375,9 @@ function npmInstallOneRepo(dir, name) {
315
375
  const pkg = join(dir, "package.json");
316
376
  if (!existsSync(pkg)) return { ok: true, skipped: true };
317
377
 
378
+ const q = process.env.KOMPLIAN_CLI_QUIET === "1";
379
+ const stdio = q ? "ignore" : "inherit";
380
+
318
381
  const yarnLock = join(dir, "yarn.lock");
319
382
  const pnpmLock = join(dir, "pnpm-lock.yaml");
320
383
  const npmLock = join(dir, "package-lock.json");
@@ -322,15 +385,21 @@ function npmInstallOneRepo(dir, name) {
322
385
  if (existsSync(yarnLock)) {
323
386
  if (!canRun("yarn", ["--version"])) {
324
387
  log(
325
- `${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}`
326
389
  );
327
390
  return { ok: true, skipped: true };
328
391
  }
329
- 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
+ }
330
399
  const r = spawnSync(
331
400
  "yarn",
332
401
  ["install", "--frozen-lockfile"],
333
- spawnWin({ cwd: dir, stdio: "inherit" })
402
+ spawnWin({ cwd: dir, stdio })
334
403
  );
335
404
  return { ok: r.status === 0, skipped: false };
336
405
  }
@@ -338,54 +407,85 @@ function npmInstallOneRepo(dir, name) {
338
407
  if (existsSync(pnpmLock)) {
339
408
  if (!canRun("pnpm", ["--version"])) {
340
409
  log(
341
- `${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}`
342
411
  );
343
412
  return { ok: true, skipped: true };
344
413
  }
345
- 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
+ }
346
421
  const r = spawnSync(
347
422
  "pnpm",
348
423
  ["install", "--frozen-lockfile"],
349
- spawnWin({ cwd: dir, stdio: "inherit" })
424
+ spawnWin({ cwd: dir, stdio })
350
425
  );
351
426
  return { ok: r.status === 0, skipped: false };
352
427
  }
353
428
 
354
429
  if (!canRun("npm", ["--version"])) {
355
- log(`${c.yellow}○${c.reset} npm no está en PATH — omito ${name}`);
430
+ log(`${c.yellow}○${c.reset} npm not in PATH — skipping ${name}`);
356
431
  return { ok: true, skipped: true };
357
432
  }
358
433
 
359
434
  const quiet = npmQuietFlags();
360
435
 
361
436
  if (existsSync(npmLock)) {
362
- log(`${c.dim}→${c.reset} ${name} ${c.dim}(npm ci — lock sin cambios)${c.reset}`);
363
- const r = spawnSync("npm", ["ci", ...quiet], spawnWin({ cwd: dir, stdio: "inherit" }));
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
+ }
444
+ const r = spawnSync("npm", ["ci", ...quiet], spawnWin({ cwd: dir, stdio }));
364
445
  if (r.status === 0) return { ok: true, skipped: false };
365
446
  log(
366
- `${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}`
367
448
  );
368
449
  return { ok: false, skipped: false };
369
450
  }
370
451
 
371
- 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
+ }
372
459
  const r = spawnSync(
373
460
  "npm",
374
461
  ["install", ...quiet, "--no-package-lock"],
375
- spawnWin({ cwd: dir, stdio: "inherit" })
462
+ spawnWin({ cwd: dir, stdio })
376
463
  );
377
464
  return { ok: r.status === 0, skipped: false };
378
465
  }
379
466
 
380
467
  function npmInstallEach(workspace) {
381
- log("");
382
- log(`${c.cyan}━━ Dependencias por repo ━━${c.reset}`);
468
+ const q = process.env.KOMPLIAN_CLI_QUIET === "1";
469
+ if (!q) {
470
+ log("");
471
+ log(`${c.cyan}━━ dependencies ━━${c.reset}`);
472
+ } else {
473
+ ux("");
474
+ ux(`${c.cyan}Dependencies${c.reset}`);
475
+ }
383
476
  for (const ent of readdirSync(workspace)) {
384
477
  const d = join(workspace, ent);
385
478
  if (!statSync(d).isDirectory()) continue;
386
479
  const { ok, skipped } = npmInstallOneRepo(d, ent);
387
480
  if (skipped) continue;
388
- if (ok) {
481
+ if (q) {
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
+ }
488
+ } else if (ok) {
389
489
  log(`${c.green}✓${c.reset} ${ent}`);
390
490
  } else {
391
491
  log(`${c.yellow}○${c.reset} ${ent}`);
@@ -394,30 +494,34 @@ function npmInstallEach(workspace) {
394
494
  }
395
495
 
396
496
  function usage() {
397
- log(`Uso: komplian setup | onboard | postman | mcp-tools | db:all:dev | localhost | db …`);
398
- 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}`);
399
- log(` ${c.bold}Setup por pasos:${c.reset}`);
400
- log(` 1. npx komplian onboard --yes`);
401
- log(` 2. npx komplian postman --yes ${c.dim}(@komplian.com)${c.reset}`);
402
- log(` 3. npx komplian mcp-tools --yes`);
403
- log(` 4. npx komplian db:all:dev ${c.dim}(Postman + 3 URLs dev → ~/.komplian + .env.local)${c.reset}`);
404
- log(` 5. npx komplian localhost --yes`);
405
- log(``);
406
- log(` npx komplian db:app:development ${c.dim}(psql una DB)${c.reset}`);
407
- log(``);
408
- log(` Antes (una vez): gh auth login -h github.com -s repo -s read:org -w`);
409
- log(` Requisitos: Node 18+, git, GitHub CLI (gh)`);
410
- log(``);
411
- log(` onboard implica --install salvo --no-install`);
412
- log(` [carpeta] Destino (por defecto: directorio actual, no ~/komplian)`);
413
- log(` -y, --yes Sin menú interactivo (equipo por defecto del JSON)`);
414
- log(` -t, --team <slug> Equipo en komplian-team-repos.json`);
415
- log(` -i, --install npm install en cada repo con package.json`);
416
- log(` --no-install No ejecutar npm install`);
417
- log(` --all-repos Todos los repos visibles (menos exclude_from_all)`);
418
- log(` --ssh Clonar con git@github.com:…`);
419
- log(` --list-teams Lista slugs de equipo (solo JSON) y sale`);
420
- 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`);
421
525
  }
422
526
 
423
527
  function parseArgs(argv) {
@@ -444,8 +548,8 @@ function parseArgs(argv) {
444
548
  else if (a === "-h" || a === "--help") out.help = true;
445
549
  else if (a === "-t" || a === "--team") {
446
550
  out.team = argv[++i] || "";
447
- } else if (a.startsWith("-")) {
448
- log(`${c.red}✗${c.reset} Opción desconocida: ${a}`);
551
+ } else if (a.startsWith("-")) {
552
+ console.error(`${c.red}✗${c.reset} Unknown option: ${a}`);
449
553
  usage();
450
554
  process.exit(1);
451
555
  } else rest.push(a);
@@ -545,20 +649,22 @@ async function main() {
545
649
  }
546
650
 
547
651
  if (!existsSync(configPath)) {
548
- log(`${c.red}✗${c.reset} Falta la config: ${configPath}`);
652
+ console.error(`${c.red}✗${c.reset} Missing config: ${configPath}`);
549
653
  process.exit(1);
550
654
  }
551
655
 
552
656
  const cfg = loadConfig(configPath);
553
657
 
554
658
  if (args.listTeams) {
555
- log(Object.keys(cfg.teams || {}).sort().join("\n"));
659
+ console.log(Object.keys(cfg.teams || {}).sort().join("\n"));
556
660
  return;
557
661
  }
558
662
 
559
663
  const nodeMajor = Number(process.versions.node.split(".")[0], 10);
560
664
  if (nodeMajor < 18) {
561
- 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
+ );
562
668
  process.exit(1);
563
669
  }
564
670
 
@@ -572,22 +678,22 @@ async function main() {
572
678
  );
573
679
 
574
680
  if (!ghOk()) {
575
- log(`${c.red}✗${c.reset} No hay sesión en GitHub CLI.`);
681
+ console.error(`${c.red}✗${c.reset} GitHub CLI not logged in.`);
576
682
  if (!runGhAuth()) {
577
- log(`${c.red}✗${c.reset} Autenticación cancelada.`);
683
+ console.error(`${c.red}✗${c.reset} GitHub auth cancelled.`);
578
684
  process.exit(1);
579
685
  }
580
686
  }
581
- log(`${c.green}✓${c.reset} GitHub CLI OK`);
687
+
688
+ banner();
689
+ ux(`${c.green}✓${c.reset} GitHub CLI`);
582
690
 
583
691
  const org = process.env.KOMPLIAN_ORG || cfg.org || "Komplian";
584
692
  verifyOrgMembership(org);
585
- logGhIdentity();
586
- log(
587
- `${c.green}✓${c.reset} Org ${c.bold}${org}${c.reset}: ${c.dim}membresía activa${c.reset}`
588
- );
589
-
590
- banner();
693
+ if (!process.env.KOMPLIAN_CLI_QUIET) {
694
+ logGhIdentity();
695
+ }
696
+ ux(`${c.green}✓${c.reset} Org ${c.bold}${org}${c.reset}`);
591
697
 
592
698
  let team = args.team;
593
699
  if (!team && !args.yes && !args.allRepos) {
@@ -596,7 +702,9 @@ async function main() {
596
702
 
597
703
  const repos = resolveRepos(cfg, org, team, args.allRepos);
598
704
  if (repos.length === 0) {
599
- 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
+ );
600
708
  process.exit(1);
601
709
  }
602
710
 
@@ -606,21 +714,28 @@ async function main() {
606
714
  }
607
715
  const abs = resolve(workspace.replace(/^~(?=$|[/\\])/, homedir()));
608
716
  if (!isSafeTargetDir(abs)) {
609
- log(`${c.red}✗${c.reset} Carpeta destino no segura: ${abs}`);
717
+ console.error(`${c.red}✗${c.reset} Unsafe destination folder: ${abs}`);
610
718
  process.exit(1);
611
719
  }
612
720
 
613
721
  mkdirSync(abs, { recursive: true });
614
- log("");
615
- log(`${c.cyan}━━ Workspace ━━${c.reset} ${c.bold}${abs}${c.reset}`);
616
- log(`${c.cyan}━━ Org ━━${c.reset} ${c.bold}${org}${c.reset}`);
617
- if (team) log(`${c.cyan}━━ Equipo ━━${c.reset} ${c.bold}${team}${c.reset}`);
618
- log(`${c.cyan}━━ Repos (${repos.length}) ━━${c.reset}`);
619
- 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("");
620
735
 
621
736
  let failed = 0;
622
737
  for (const name of repos) {
623
- const ok = cloneOne(org, name, abs, args.ssh);
738
+ const ok = await cloneOneAsync(org, name, abs, args.ssh);
624
739
  if (!ok) failed += 1;
625
740
  }
626
741
 
@@ -630,16 +745,22 @@ async function main() {
630
745
  npmInstallEach(abs);
631
746
  }
632
747
 
633
- log("");
634
- log(`${c.cyan}━━ Listo ━━${c.reset}`);
748
+ ux("");
749
+ ux(`${c.cyan}━━ Done ━━${c.reset}`);
635
750
  if (failed > 0) {
636
- 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
+ );
637
763
  }
638
- log(`${c.green}✓${c.reset} Cursor: ${c.bold}File → Open Folder → ${abs}${c.reset}`);
639
- log(
640
- `${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}`
641
- );
642
- log(`${c.dim} .env.example → .env por proyecto; secretos en 1Password — nunca commit.${c.reset}`);
643
764
  }
644
765
 
645
766
  main().catch((e) => {