komplian 0.7.2 → 0.7.3

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.
@@ -519,12 +519,15 @@ export async function runLocalhost(argv) {
519
519
  if (!cliQuiet()) {
520
520
  log(`${c.cyan}━━ .env.local ━━${c.reset}`);
521
521
  }
522
- for (const w of written) {
523
- const st = w.skipped ? `${c.dim}skip${c.reset}` : `${c.green}ok${c.reset}`;
524
- const name = w.label || "?";
525
- if (cliQuiet()) {
526
- logAlways(` ${st} ${name}`);
527
- } else {
522
+ if (cliQuiet()) {
523
+ const labels = written.map((w) => w.label || "?").join(`${c.dim}·${c.reset} `);
524
+ logAlways(
525
+ ` ${c.green}✓${c.reset} ${c.dim}.env.local${c.reset} ${labels}`
526
+ );
527
+ } else {
528
+ for (const w of written) {
529
+ const st = w.skipped ? `${c.dim}skip${c.reset}` : `${c.green}ok${c.reset}`;
530
+ const name = w.label || "?";
528
531
  log(` ${st} ${name}`);
529
532
  }
530
533
  }
@@ -565,7 +568,7 @@ export async function runLocalhost(argv) {
565
568
  });
566
569
 
567
570
  if (cliQuiet()) {
568
- logAlways(`${c.cyan}━━ dev servers ━━${c.reset}`);
571
+ logAlways(`${c.dim}dev servers · Ctrl+C stop${c.reset}`);
569
572
  } else {
570
573
  log(`${c.cyan}━━ dev servers (${services.length}) ━━${c.reset} ${c.dim}Ctrl+C stop${c.reset}`);
571
574
  log("");
@@ -573,9 +576,13 @@ export async function runLocalhost(argv) {
573
576
 
574
577
  const npx = process.platform === "win32" ? "npx.cmd" : "npx";
575
578
  const useShell = process.platform === "win32";
579
+ /** --raw avoids concurrently prefix logger bugs (e.g. prev.replace on Windows / npx). */
580
+ const concArgs = cliQuiet()
581
+ ? ["--yes", "concurrently@9", "--raw", ...scripts]
582
+ : ["--yes", "concurrently@9", "-c", colors, "-n", names, ...scripts];
576
583
  const child = spawn(
577
584
  npx,
578
- ["--yes", "concurrently@9", "-c", colors, "-n", names, ...scripts],
585
+ concArgs,
579
586
  {
580
587
  cwd: workspaceRoot,
581
588
  stdio: "inherit",
@@ -65,6 +65,15 @@ const KOMPLIAN_MCP_PRESET = {
65
65
  args: ["-y", "chrome-devtools-mcp@latest"],
66
66
  env: {},
67
67
  },
68
+ /** Remote MCP — OAuth in Cursor (“Needs login”). */
69
+ "KOMPLIAN-vercel": {
70
+ url: "https://mcp.vercel.com",
71
+ },
72
+ /** Remote MCP — OAuth via mcp-remote (Neon docs). */
73
+ "KOMPLIAN-neon": {
74
+ command: "npx",
75
+ args: ["-y", "mcp-remote", "https://mcp.neon.tech/mcp"],
76
+ },
68
77
  },
69
78
  };
70
79
 
@@ -83,10 +92,12 @@ Enable each row in **Cursor → Settings → MCP**. Fill \`env\` per the table.
83
92
  | **KOMPLIAN-sentry** | \`@sentry/mcp-server\` | Sentry | First time: browser login. **Komplian:** org \`komplian\`, \`regionUrl\` \`https://de.sentry.io\`, projects \`komplian-api\` / \`komplian-app\`. |
84
93
  | **KOMPLIAN-stripe** | \`@stripe/mcp\` | Stripe API | \`STRIPE_SECRET_KEY\` (restricted / test in dev). [Stripe MCP docs](https://docs.stripe.com/mcp). |
85
94
  | **KOMPLIAN-chrome-devtools** | \`chrome-devtools-mcp\` | Chrome (network, console, screenshots) | Needs **Node 20.19+** and stable Chrome. [Chrome DevTools MCP](https://developer.chrome.com/blog/chrome-devtools-mcp). |
95
+ | **KOMPLIAN-vercel** | \`url\` → \`https://mcp.vercel.com\` | Vercel projects & deploys | **OAuth** in Cursor (see [Vercel MCP](https://mcp.vercel.com/)). No token in JSON. |
96
+ | **KOMPLIAN-neon** | \`npx -y mcp-remote https://mcp.neon.tech/mcp\` | Neon Postgres / API | **OAuth** when the client starts (see [Neon MCP](https://neon.tech/docs/ai/neon-mcp-server)). Alt: local \`@neondatabase/mcp-server-neon\` + API key. |
86
97
 
87
98
  ## 2. Optional: Cursor native connectors
88
99
 
89
- For Cursor OAuth/UI connectors: **Settings → MCP** → **Atlassian**, **Sentry**, **Stripe**, **Chrome DevTools**. They can coexist with **KOMPLIAN-*** entries; avoid duplicating the same capability twice.
100
+ For Cursor OAuth/UI connectors: **Settings → MCP** → **Atlassian**, **Sentry**, **Stripe**, **Chrome DevTools**, **Vercel**, **Neon**. They can coexist with **KOMPLIAN-*** entries; avoid duplicating the same capability twice.
90
101
 
91
102
  ## 3. Restart
92
103
 
@@ -46,31 +46,38 @@ function ux(s = "") {
46
46
  console.log(s);
47
47
  }
48
48
 
49
+ /** Block-letter logo + box frame (OpenClaw-style table). */
49
50
  function banner() {
50
- ux(
51
- [
52
- `${c.cyan}${c.bold}`,
53
- "██╗ ██╗ ██████╗ ███╗ ███╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗",
54
- "██║ ██╔╝ ██╔═══██╗ ████╗ ████║ ██╔══██╗ ██║ ██║ ██╔══██╗ ████╗ ██║",
55
- "█████╔╝ ██║ ██║ ██╔████╔██║ ██████╔╝ ██║ ██║ ███████║ ██╔██╗ ██║",
56
- "██╔═██╗ ██║ ██║ ██║╚██╔╝██║ ██╔═══╝ ██║ ██║ ██╔══██║ ██║╚██╗██║",
57
- "██║ ██╗ ╚██████╔╝ ██║ ╚═╝ ██║ ██║ ███████╗ ██║ ██║ ██║ ██║ ╚████║",
58
- "╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝",
59
- `${c.reset}`,
60
- `${c.dim} Secure setup · GitHub CLI · git clone${c.reset}`,
61
- "",
62
- ].join("\n")
63
- );
51
+ const art = [
52
+ "██╗ ██╗ ██████╗ ███╗ ███╗ ██████╗ ██╗ ██╗ █████╗ ███╗ ██╗",
53
+ "██║ ██╔╝ ██╔═══██╗ ████╗ ████║ ██╔══██╗ ██║ ██║ ██╔══██╗ ████╗ ██║",
54
+ "█████╔╝ ██║ ██║ ██╔████╔██║ ██████╔╝ ██║ ██║ ███████║ ██╔██╗ ██║",
55
+ "██╔═██╗ ██║ ██║ ██║╚██╔╝██║ ██╔═══╝ ██║ ██║ ██╔══██║ ██║╚██╗██║",
56
+ "██║ ██╗ ╚██████╔╝ ██║ ╚═╝ ██║ ██║ ███████╗ ██║ ██║ ██║ ██║ ╚████║",
57
+ "╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝",
58
+ ];
59
+ const w = Math.max(...art.map((line) => [...line].length));
60
+ const pad = (line) => line + " ".repeat(w - [...line].length);
61
+ const horiz = "─".repeat(w + 2);
62
+ const b = `${c.cyan}${c.bold}`;
63
+ const x = c.reset;
64
+ ux(`${b}┌${horiz}┐${x}`);
65
+ for (const line of art) {
66
+ ux(`${b}│ ${pad(line)} │${x}`);
67
+ }
68
+ ux(`${b}└${horiz}┘${x}`);
69
+ ux(`${c.dim} Secure setup · GitHub CLI · Monorepo${c.reset}`);
70
+ ux("");
64
71
  }
65
72
 
66
- function startRepoSpinner(repoName) {
73
+ function startBatchSpinner(message) {
67
74
  const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
68
75
  const cloud = `${c.blue}☁${c.reset}`;
69
76
  let i = 0;
70
77
  const id = setInterval(() => {
71
78
  const fr = frames[i++ % frames.length];
72
79
  process.stdout.write(
73
- `\r ${cloud} ${c.cyan}${fr}${c.reset} ${c.bold}${repoName}${c.reset} ${c.dim}…${c.reset} `
80
+ `\r ${cloud} ${c.cyan}${fr}${c.reset} ${c.dim}${message}${c.reset} `
74
81
  );
75
82
  }, 90);
76
83
  return {
@@ -280,55 +287,27 @@ function isSafeTargetDir(abs) {
280
287
  return !bad.includes(n);
281
288
  }
282
289
 
283
- function cloudLine(org, name, status) {
284
- const cloud = `${c.blue}☁${c.reset}`;
285
- if (status === "ok") {
286
- return ` ${cloud} ${c.green}✓${c.reset} ${c.bold}${org}/${name}${c.reset} ${c.dim}cloned${c.reset}`;
287
- }
288
- if (status === "skip") {
289
- return ` ${cloud} ${c.yellow}○${c.reset} ${org}/${name} ${c.dim}(already present)${c.reset}`;
290
- }
291
- if (status === "fail") {
292
- return ` ${cloud} ${c.red}✗${c.reset} ${org}/${name} ${c.dim}failed${c.reset}`;
293
- }
294
- return ` ${cloud} ${c.cyan}…${c.reset} ${org}/${name} ${c.dim}cloning…${c.reset}`;
290
+ function summarizeCloneLine(repos, outcomes) {
291
+ const parts = repos.map((name) => {
292
+ const st = outcomes.get(name) || "fail";
293
+ if (st === "fail") return `${c.red}✗${c.reset} ${name}`;
294
+ if (st === "skip") return `${c.green}✓${c.reset} ${c.dim}${name}${c.reset}`;
295
+ return `${c.green}✓${c.reset} ${name}`;
296
+ });
297
+ return ` ${c.blue}☁${c.reset} ${parts.join(` `)}`;
295
298
  }
296
299
 
297
- async function cloneOneAsync(org, name, workspace, useSsh) {
298
- const q = process.env.KOMPLIAN_CLI_QUIET === "1";
299
- const gitDir = join(workspace, name, ".git");
300
- if (existsSync(gitDir)) {
301
- ux(cloudLine(org, name, "skip"));
302
- return true;
300
+ function summarizeDepsLine(results) {
301
+ const ran = results.filter((r) => !r.skipped);
302
+ if (ran.length === 0) {
303
+ return ` ${c.blue}☁${c.reset} ${c.dim}dependencies — nothing to install${c.reset}`;
303
304
  }
304
-
305
- const args = useSsh
306
- ? ["clone", `git@github.com:${org}/${name}.git`, name]
307
- : ["repo", "clone", `${org}/${name}`, name];
308
- const cmd = useSsh ? "git" : "gh";
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
-
321
- if (r.status === 0) {
322
- ux(cloudLine(org, name, "ok"));
323
- return true;
324
- }
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
- }
331
- return false;
305
+ const parts = ran.map((r) =>
306
+ r.ok
307
+ ? `${c.green}✓${c.reset} ${r.name}`
308
+ : `${c.yellow}○${c.reset} ${r.name}`
309
+ );
310
+ return ` ${c.blue}☁${c.reset} ${parts.join(` `)}`;
332
311
  }
333
312
 
334
313
  function copyCursorPack(workspace, cursorRepoUrl) {
@@ -371,12 +350,13 @@ function npmQuietFlags() {
371
350
  return audit ? [] : ["--no-audit", "--no-fund"];
372
351
  }
373
352
 
374
- function npmInstallOneRepo(dir, name) {
353
+ function npmInstallOneRepo(dir, name, opts = {}) {
354
+ const silent = opts.silent === true;
375
355
  const pkg = join(dir, "package.json");
376
356
  if (!existsSync(pkg)) return { ok: true, skipped: true };
377
357
 
378
358
  const q = process.env.KOMPLIAN_CLI_QUIET === "1";
379
- const stdio = q ? "ignore" : "inherit";
359
+ const stdio = silent || q ? "ignore" : "inherit";
380
360
 
381
361
  const yarnLock = join(dir, "yarn.lock");
382
362
  const pnpmLock = join(dir, "pnpm-lock.yaml");
@@ -384,17 +364,21 @@ function npmInstallOneRepo(dir, name) {
384
364
 
385
365
  if (existsSync(yarnLock)) {
386
366
  if (!canRun("yarn", ["--version"])) {
387
- log(
388
- `${c.yellow}○${c.reset} ${name} ${c.dim}(yarn.lock; install yarn or run yarn install manually)${c.reset}`
389
- );
367
+ if (!silent) {
368
+ log(
369
+ `${c.yellow}○${c.reset} ${name} ${c.dim}(yarn.lock; install yarn or run yarn install manually)${c.reset}`
370
+ );
371
+ }
390
372
  return { ok: true, skipped: true };
391
373
  }
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}`);
374
+ if (!silent) {
375
+ if (q) {
376
+ ux(
377
+ ` ${c.blue}☁${c.reset} ${c.cyan}…${c.reset} ${c.bold}${name}${c.reset} ${c.dim}yarn…${c.reset}`
378
+ );
379
+ } else {
380
+ log(`${c.dim}→${c.reset} ${name} ${c.dim}(yarn)${c.reset}`);
381
+ }
398
382
  }
399
383
  const r = spawnSync(
400
384
  "yarn",
@@ -406,17 +390,21 @@ function npmInstallOneRepo(dir, name) {
406
390
 
407
391
  if (existsSync(pnpmLock)) {
408
392
  if (!canRun("pnpm", ["--version"])) {
409
- log(
410
- `${c.yellow}○${c.reset} ${name} ${c.dim}(pnpm-lock; install pnpm or run pnpm install manually)${c.reset}`
411
- );
393
+ if (!silent) {
394
+ log(
395
+ `${c.yellow}○${c.reset} ${name} ${c.dim}(pnpm-lock; install pnpm or run pnpm install manually)${c.reset}`
396
+ );
397
+ }
412
398
  return { ok: true, skipped: true };
413
399
  }
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}`);
400
+ if (!silent) {
401
+ if (q) {
402
+ ux(
403
+ ` ${c.blue}☁${c.reset} ${c.cyan}…${c.reset} ${c.bold}${name}${c.reset} ${c.dim}pnpm…${c.reset}`
404
+ );
405
+ } else {
406
+ log(`${c.dim}→${c.reset} ${name} ${c.dim}(pnpm)${c.reset}`);
407
+ }
420
408
  }
421
409
  const r = spawnSync(
422
410
  "pnpm",
@@ -427,34 +415,42 @@ function npmInstallOneRepo(dir, name) {
427
415
  }
428
416
 
429
417
  if (!canRun("npm", ["--version"])) {
430
- log(`${c.yellow}○${c.reset} npm not in PATH — skipping ${name}`);
418
+ if (!silent) {
419
+ log(`${c.yellow}○${c.reset} npm not in PATH — skipping ${name}`);
420
+ }
431
421
  return { ok: true, skipped: true };
432
422
  }
433
423
 
434
424
  const quiet = npmQuietFlags();
435
425
 
436
426
  if (existsSync(npmLock)) {
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}`);
427
+ if (!silent) {
428
+ if (q) {
429
+ ux(
430
+ ` ${c.blue}☁${c.reset} ${c.cyan}…${c.reset} ${c.bold}${name}${c.reset} ${c.dim}npm ci…${c.reset}`
431
+ );
432
+ } else {
433
+ log(`${c.dim}→${c.reset} ${name} ${c.dim}(npm ci)${c.reset}`);
434
+ }
443
435
  }
444
436
  const r = spawnSync("npm", ["ci", ...quiet], spawnWin({ cwd: dir, stdio }));
445
437
  if (r.status === 0) return { ok: true, skipped: false };
446
- log(
447
- `${c.yellow}○${c.reset} ${name}: npm ci failed (lock out of sync?). ${c.dim}Try npm install in that repo.${c.reset}`
448
- );
438
+ if (!silent) {
439
+ log(
440
+ `${c.yellow}○${c.reset} ${name}: npm ci failed (lock out of sync?). ${c.dim}Try npm install in that repo.${c.reset}`
441
+ );
442
+ }
449
443
  return { ok: false, skipped: false };
450
444
  }
451
445
 
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}`);
446
+ if (!silent) {
447
+ if (q) {
448
+ ux(
449
+ ` ${c.blue}☁${c.reset} ${c.cyan}…${c.reset} ${c.bold}${name}${c.reset} ${c.dim}npm install…${c.reset}`
450
+ );
451
+ } else {
452
+ log(`${c.dim}→${c.reset} ${name} ${c.dim}(npm install — no new lockfile)${c.reset}`);
453
+ }
458
454
  }
459
455
  const r = spawnSync(
460
456
  "npm",
@@ -464,33 +460,16 @@ function npmInstallOneRepo(dir, name) {
464
460
  return { ok: r.status === 0, skipped: false };
465
461
  }
466
462
 
467
- function npmInstallEach(workspace) {
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
- }
463
+ /** Silent installs for batch UX (single spinner + one summary line). */
464
+ function npmInstallBatch(workspace) {
465
+ const results = [];
476
466
  for (const ent of readdirSync(workspace)) {
477
467
  const d = join(workspace, ent);
478
468
  if (!statSync(d).isDirectory()) continue;
479
- const { ok, skipped } = npmInstallOneRepo(d, ent);
480
- if (skipped) continue;
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) {
489
- log(`${c.green}✓${c.reset} ${ent}`);
490
- } else {
491
- log(`${c.yellow}○${c.reset} ${ent}`);
492
- }
469
+ const { ok, skipped } = npmInstallOneRepo(d, ent, { silent: true });
470
+ results.push({ name: ent, ok, skipped });
493
471
  }
472
+ return results;
494
473
  }
495
474
 
496
475
  function usage() {
@@ -721,36 +700,68 @@ async function main() {
721
700
  mkdirSync(abs, { recursive: true });
722
701
  const q = process.env.KOMPLIAN_CLI_QUIET === "1";
723
702
  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
- }
703
+ ux(`${c.dim}${abs}${c.reset}`);
704
+ if (team) ux(`${c.dim}team · ${team}${c.reset}`);
705
+ ux(`${c.dim}${repos.join(" · ")}${c.reset}`);
734
706
  ux("");
735
707
 
708
+ const outcomes = new Map();
709
+ let spin = process.stdout.isTTY ? startBatchSpinner("Cloning repositories…") : null;
710
+ if (!spin) {
711
+ ux(` ${c.blue}☁${c.reset} ${c.dim}Cloning repositories…${c.reset}`);
712
+ }
713
+
736
714
  let failed = 0;
737
- for (const name of repos) {
738
- const ok = await cloneOneAsync(org, name, abs, args.ssh);
739
- if (!ok) failed += 1;
715
+ try {
716
+ for (const name of repos) {
717
+ const gitDir = join(abs, name, ".git");
718
+ if (existsSync(gitDir)) {
719
+ outcomes.set(name, "skip");
720
+ continue;
721
+ }
722
+ const cloneArgs = args.ssh
723
+ ? ["clone", `git@github.com:${org}/${name}.git`, name]
724
+ : ["repo", "clone", `${org}/${name}`, name];
725
+ const cmd = args.ssh ? "git" : "gh";
726
+ const r = await runSpawn(cmd, cloneArgs, abs);
727
+ if (r.status === 0) {
728
+ outcomes.set(name, "ok");
729
+ } else {
730
+ outcomes.set(name, "fail");
731
+ failed += 1;
732
+ const err = (r.stderr || "").trim();
733
+ if (err) console.error(`${c.dim}${err.slice(0, 500)}${c.reset}`);
734
+ }
735
+ }
736
+ } finally {
737
+ if (spin) spin.stop();
740
738
  }
741
739
 
740
+ ux(summarizeCloneLine(repos, outcomes));
741
+
742
742
  copyCursorPack(abs, process.env.KOMPLIAN_CURSOR_REPO);
743
743
 
744
744
  if (args.install) {
745
- npmInstallEach(abs);
745
+ spin = process.stdout.isTTY ? startBatchSpinner("Installing dependencies…") : null;
746
+ if (!spin) {
747
+ ux(` ${c.blue}☁${c.reset} ${c.dim}Installing dependencies…${c.reset}`);
748
+ }
749
+ let depResults = [];
750
+ try {
751
+ depResults = npmInstallBatch(abs);
752
+ } finally {
753
+ if (spin) spin.stop();
754
+ }
755
+ ux(summarizeDepsLine(depResults));
746
756
  }
747
757
 
748
758
  ux("");
749
- ux(`${c.cyan}━━ Done ━━${c.reset}`);
750
759
  if (failed > 0) {
751
760
  ux(
752
761
  `${c.yellow}○${c.reset} ${failed} repo(s) failed — check access and retry.`
753
762
  );
763
+ } else {
764
+ ux(`${c.green}✓${c.reset} ${c.dim}Repositories ready${c.reset}`);
754
765
  }
755
766
  ux(`${c.green}✓${c.reset} Open in Cursor: ${c.bold}File → Open Folder → ${abs}${c.reset}`);
756
767
  if (!q) {
@@ -583,7 +583,6 @@ export async function runSetup(argv) {
583
583
 
584
584
  mkdirSync(workspaceAbs, { recursive: true });
585
585
 
586
- out(`${c.cyan}1/5${c.reset} repos`);
587
586
  const onboardArgs = ["--yes"];
588
587
  if (opts.team) onboardArgs.push("-t", opts.team);
589
588
  if (opts.allRepos) onboardArgs.push("--all-repos");
@@ -629,7 +628,6 @@ export async function runSetup(argv) {
629
628
  }
630
629
 
631
630
  if (useBrowser && (needsPostmanKey || needsDbUrls)) {
632
- out(`${c.cyan}browser${c.reset} local form`);
633
631
  try {
634
632
  const lp = neonOAuth && needsDbUrls ? parseListenFromRedirectUri() : null;
635
633
  const form = await runSetupBrowserForm({
@@ -652,16 +650,12 @@ export async function runSetup(argv) {
652
650
  }
653
651
  }
654
652
 
655
- out(`${c.cyan}2/5${c.reset} postman`);
656
653
  await runPostman(["--yes"]);
657
654
 
658
- out(`${c.cyan}3/5${c.reset} mcp`);
659
655
  await runMcpTools(["--yes"]);
660
656
 
661
- out(`${c.cyan}4/5${c.reset} databases`);
662
657
  await runDbAllDev(["--yes", "-w", monorepoRoot]);
663
658
 
664
- out(`${c.cyan}5/5${c.reset} dev`);
665
659
  await runLocalhost(["--yes"]);
666
660
 
667
661
  out(`${c.green}✓${c.reset} ready`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "komplian",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
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": {