komplian 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,21 @@
1
1
  # komplian (npm CLI)
2
2
 
3
- **Developers:** from any machine with Node 18+ and GitHub CLI (`gh`):
3
+ **Developers**
4
4
 
5
- ```bash
6
- npx komplian onboard --yes
7
- ```
5
+ 1. Install [GitHub CLI](https://cli.github.com) and **git** (one-time per machine).
6
+ 2. Browser login: `gh auth login -h github.com -s repo -s read:org -w`
7
+ 3. `npx komplian onboard --yes`
8
8
 
9
- This downloads the CLI from npm (no manual git clone), opens GitHub login if needed, and clones the repos your account can access (filtered by team in the bundled `komplian-team-repos.json`). Default workspace: `~/komplian` (Windows: `%USERPROFILE%\komplian`).
9
+ No OAuth App registration `gh` uses GitHub’s built-in flow. Default workspace: `~/komplian`.
10
10
 
11
- **Maintainers:** publish **from this folder** (the one that contains `package.json` not the monorepo root):
11
+ **Maintainers:** publish from **`scripts/`** (folder with `package.json`), not the monorepo root:
12
12
 
13
13
  ```bash
14
- cd path/to/monorepo/scripts # e.g. cd ~/Desktop/temp/scripts
15
- npm login # once per machine
14
+ cd path/to/monorepo/scripts
15
+ npm login
16
16
  npm publish --access public
17
17
  ```
18
18
 
19
- Running `npm publish` from the repo root causes `ENOENT package.json` because the npm package lives only under `scripts/`.
20
-
21
- Bump `version` in `package.json` before each publish.
19
+ Bump `version` before each publish.
22
20
 
23
- **If publish fails with `403` / “Two-factor authentication … required”:** enable **2FA** on your npm account (*npmjs.com → Account → Security*), then run `npm publish` again (npm may prompt for an OTP). For CI, create a **granular access token** (*Access Tokens → Granular token*) with **Publish** on the `komplian` package and “**Bypass two-factor authentication**” if your org allows it; set `NPM_TOKEN` in GitHub Actions.
21
+ **If publish fails with `403` / 2FA:** enable **2FA** on npm (*Account → Security*) or use a **granular token** with publish rights; set `NPM_TOKEN` in CI (see `.github/workflows/publish-komplian-npm.yml`).
@@ -1,12 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Komplian onboard — one-command dev setup (GitHub CLI auth + clone + optional npm install).
4
- * Zero npm deps: Node 18+, git, gh, and komplian-team-repos.json beside this file.
3
+ * Komplian onboard — GitHub CLI (browser login) + clone. No OAuth App to register.
4
+ * Needs: Node 18+, git, gh (https://cli.github.com)
5
5
  *
6
6
  * Usage:
7
- * npx komplian onboard --yes # npm ships this package; no manual monorepo clone for developers
8
- *
9
- * Security: `gh` OAuth; active org membership verified via GitHub API before clones; repo ACLs enforced by GitHub. See ONBOARDING.md.
7
+ * gh auth login -h github.com -s repo -s read:org -w # once
8
+ * npx komplian onboard --yes
10
9
  */
11
10
 
12
11
  import { spawnSync } from "node:child_process";
@@ -35,7 +34,6 @@ function log(s = "") {
35
34
  }
36
35
 
37
36
  function banner() {
38
- // Block Unicode (cfonts "block"). Fourth letter is P: row 4 uses ██╔═══╝; R would use ██╔══██╗.
39
37
  log(
40
38
  [
41
39
  `${c.cyan}${c.bold}`,
@@ -46,7 +44,7 @@ function banner() {
46
44
  "██║ ██╗ ╚██████╔╝ ██║ ╚═╝ ██║ ██║ ███████╗ ██║ ██║ ██║ ██║ ╚████║",
47
45
  "╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝",
48
46
  `${c.reset}`,
49
- `${c.dim} Secure setup · GitHub CLI · HTTPS clones · No secrets stored by this CLI${c.reset}`,
47
+ `${c.dim} Secure setup · GitHub CLI · git clone · Zero org OAuth setup${c.reset}`,
50
48
  "",
51
49
  ].join("\n")
52
50
  );
@@ -60,10 +58,9 @@ function ghOk() {
60
58
  return r.status === 0;
61
59
  }
62
60
 
63
- /** Scopes: repo (clone), read:org (verify org membership — required for private orgs). */
64
61
  function runGhAuth() {
65
62
  log(
66
- `${c.yellow}→${c.reset} Opening ${c.bold}GitHub${c.reset} login in the browser (OAuth). We never see your password.`
63
+ `${c.yellow}→${c.reset} Abre ${c.bold}GitHub${c.reset} en el navegador (OAuth). No vemos tu contraseña.`
67
64
  );
68
65
  const r = spawnSync(
69
66
  "gh",
@@ -81,47 +78,39 @@ function ghApiJson(path) {
81
78
  return { status: r.status, stdout: r.stdout || "", stderr: r.stderr || "" };
82
79
  }
83
80
 
84
- /**
85
- * Ensures the authenticated user is an active member of the GitHub org (not just any GitHub account).
86
- * Relies on GitHub as source of truth; optional SAML/SSO is enforced by the org on github.com.
87
- */
88
81
  function verifyOrgMembership(org) {
89
82
  const enc = encodeURIComponent(org);
90
83
  const mem = ghApiJson(`user/memberships/orgs/${enc}`);
91
84
  if (mem.status === 200) {
92
85
  try {
93
86
  const j = JSON.parse(mem.stdout);
94
- if (j.state === "active") {
95
- return;
96
- }
87
+ if (j.state === "active") return;
97
88
  log(
98
- `${c.red}✗${c.reset} Organization ${c.bold}${org}${c.reset} membership is not active (state: ${j.state || "?"}).`
89
+ `${c.red}✗${c.reset} Membresía en ${c.bold}${org}${c.reset} no activa (state: ${j.state || "?"}).`
99
90
  );
100
91
  process.exit(1);
101
92
  } catch {
102
- log(`${c.red}✗${c.reset} Invalid response verifying org membership.`);
93
+ log(`${c.red}✗${c.reset} Respuesta inválida al verificar la org.`);
103
94
  process.exit(1);
104
95
  }
105
96
  }
106
97
  if (mem.status === 404) {
107
98
  log(
108
- `${c.red}✗${c.reset} This GitHub account is ${c.bold}not a member${c.reset} of org ${c.bold}${org}${c.reset}.`
99
+ `${c.red}✗${c.reset} Esta cuenta ${c.bold}no es miembro${c.reset} de la org ${c.bold}${org}${c.reset}.`
109
100
  );
110
101
  log(
111
- `${c.dim} Only invited org members can run this tool. Wrong account? Run: gh auth logout && gh auth login${c.reset}`
102
+ `${c.dim} ¿Otra cuenta? gh auth logout && gh auth login -h github.com -s repo -s read:org -w${c.reset}`
112
103
  );
113
104
  process.exit(1);
114
105
  }
115
106
  const hint = (mem.stderr + mem.stdout).toLowerCase();
116
107
  if (mem.status === 403 || hint.includes("read:org") || hint.includes("scope")) {
117
- log(`${c.red}✗${c.reset} Token cannot verify org membership (needs ${c.bold}read:org${c.reset} scope).`);
118
- log(
119
- `${c.dim} Run: gh auth refresh -h github.com -s repo -s read:org${c.reset}`
120
- );
108
+ log(`${c.red}✗${c.reset} Falta scope ${c.bold}read:org${c.reset} en gh.`);
109
+ log(`${c.dim} gh auth refresh -h github.com -s repo -s read:org${c.reset}`);
121
110
  process.exit(1);
122
111
  }
123
112
  log(
124
- `${c.red}✗${c.reset} Could not verify org membership (${mem.status}):\n${c.dim}${(mem.stderr || mem.stdout).trim()}${c.reset}`
113
+ `${c.red}✗${c.reset} No se pudo verificar la org (${mem.status}):\n${c.dim}${(mem.stderr || mem.stdout).trim()}${c.reset}`
125
114
  );
126
115
  process.exit(1);
127
116
  }
@@ -132,17 +121,17 @@ function logGhIdentity() {
132
121
  try {
133
122
  const j = JSON.parse(u.stdout);
134
123
  if (j.login) {
135
- log(`${c.dim} Signed in as ${c.bold}@${j.login}${c.reset}${c.dim} (github.com)${c.reset}`);
124
+ log(`${c.dim} Sesión: ${c.bold}@${j.login}${c.reset}${c.dim} (github.com)${c.reset}`);
136
125
  }
137
126
  } catch {
138
127
  /* ignore */
139
128
  }
140
129
  }
141
130
 
142
- function needCmd(name) {
131
+ function needCmd(name, hint = "") {
143
132
  const r = spawnSync("command", ["-v", name], { shell: true, stdio: "ignore" });
144
133
  if (r.status !== 0) {
145
- log(`${c.red}✗${c.reset} Missing ${c.bold}${name}${c.reset}. Install it and retry.`);
134
+ log(`${c.red}✗${c.reset} Falta ${c.bold}${name}${c.reset} en el PATH.${hint ? ` ${hint}` : ""}`);
146
135
  process.exit(1);
147
136
  }
148
137
  }
@@ -158,7 +147,7 @@ function ghRepoList(org) {
158
147
  { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
159
148
  );
160
149
  if (r.status !== 0) {
161
- log(`${c.red}✗${c.reset} gh repo list failed:\n${r.stderr || r.stdout}`);
150
+ log(`${c.red}✗${c.reset} gh repo list falló:\n${r.stderr || r.stdout}`);
162
151
  process.exit(1);
163
152
  }
164
153
  const rows = JSON.parse(r.stdout || "[]");
@@ -188,7 +177,7 @@ function resolveRepos(cfg, org, teamArg, allRepos) {
188
177
  const teams = cfg.teams || {};
189
178
  if (!teams[team]) {
190
179
  log(
191
- `${c.red}✗${c.reset} Unknown team ${c.bold}${team}${c.reset}. Valid: ${Object.keys(teams).sort().join(", ")}`
180
+ `${c.red}✗${c.reset} Equipo desconocido ${c.bold}${team}${c.reset}. Válidos: ${Object.keys(teams).sort().join(", ")}`
192
181
  );
193
182
  process.exit(2);
194
183
  }
@@ -197,7 +186,7 @@ function resolveRepos(cfg, org, teamArg, allRepos) {
197
186
  const out = [];
198
187
  for (const name of [...allowed].sort()) {
199
188
  if (!visible.has(name)) {
200
- log(`${c.dim}○ skip ${org}/${name} (no access or not in org)${c.reset}`);
189
+ log(`${c.dim}○ omito ${org}/${name} (sin acceso o no está en la org)${c.reset}`);
201
190
  continue;
202
191
  }
203
192
  out.push(name);
@@ -214,15 +203,15 @@ function isSafeTargetDir(abs) {
214
203
  function cloudLine(org, name, status) {
215
204
  const cloud = `${c.blue}☁${c.reset}`;
216
205
  if (status === "ok") {
217
- return ` ${cloud} ${c.green}✓${c.reset} ${c.bold}${org}/${name}${c.reset} ${c.dim}cloned${c.reset}`;
206
+ return ` ${cloud} ${c.green}✓${c.reset} ${c.bold}${org}/${name}${c.reset} ${c.dim}clonado${c.reset}`;
218
207
  }
219
208
  if (status === "skip") {
220
- return ` ${cloud} ${c.yellow}○${c.reset} ${org}/${name} ${c.dim}(already there)${c.reset}`;
209
+ return ` ${cloud} ${c.yellow}○${c.reset} ${org}/${name} ${c.dim}(ya existe)${c.reset}`;
221
210
  }
222
211
  if (status === "fail") {
223
- return ` ${cloud} ${c.red}✗${c.reset} ${org}/${name} ${c.dim}failed${c.reset}`;
212
+ return ` ${cloud} ${c.red}✗${c.reset} ${org}/${name} ${c.dim}falló${c.reset}`;
224
213
  }
225
- return ` ${cloud} ${c.cyan}…${c.reset} ${org}/${name} ${c.dim}cloning…${c.reset}`;
214
+ return ` ${cloud} ${c.cyan}…${c.reset} ${org}/${name} ${c.dim}clonando…${c.reset}`;
226
215
  }
227
216
 
228
217
  function cloneOne(org, name, workspace, useSsh) {
@@ -256,7 +245,7 @@ function copyCursorPack(workspace, cursorRepoUrl) {
256
245
  if (cursorRepoUrl && String(cursorRepoUrl).trim()) {
257
246
  const tmp = join(workspace, ".cursor-bootstrap-tmp");
258
247
  rmSync(tmp, { recursive: true, force: true });
259
- log(`${c.dim}→${c.reset} Shallow clone Cursor pack…`);
248
+ log(`${c.dim}→${c.reset} Cursor pack (clone superficial)…`);
260
249
  const r = spawnSync("git", ["clone", "--depth", "1", String(cursorRepoUrl).trim(), tmp], {
261
250
  cwd: workspace,
262
251
  stdio: ["ignore", "pipe", "pipe"],
@@ -270,33 +259,30 @@ function copyCursorPack(workspace, cursorRepoUrl) {
270
259
  return;
271
260
  }
272
261
  rmSync(tmp, { recursive: true, force: true });
273
- log(`${c.yellow}○${c.reset} KOMPLIAN_CURSOR_REPO clone failed or missing .cursor/ at repo root`);
274
- }
275
-
276
- const aiToolsCursor = join(workspace, "ai-tools", ".cursor");
277
- if (existsSync(aiToolsCursor)) {
278
- rmSync(rootCursor, { recursive: true, force: true });
279
- cpSync(aiToolsCursor, rootCursor, { recursive: true });
280
- log(`${c.green}✓${c.reset} ${c.bold}.cursor${c.reset} ${c.dim}← ai-tools${c.reset}`);
281
- return;
262
+ log(`${c.yellow}○${c.reset} KOMPLIAN_CURSOR_REPO: falló el clone o no hay .cursor/ en la raíz`);
282
263
  }
283
264
  log(
284
- `${c.dim}○ No workspace .cursor/ (add ai-tools to your team or set KOMPLIAN_CURSOR_REPO).${c.reset}`
265
+ `${c.dim}○ Sin .cursor/ en el workspace (opcional: KOMPLIAN_CURSOR_REPO=<url git>).${c.reset}`
285
266
  );
286
267
  }
287
268
 
288
269
  function npmInstallEach(workspace) {
289
270
  log("");
290
- log(`${c.cyan}━━ npm install per repo ━━${c.reset}`);
271
+ log(`${c.cyan}━━ npm install por repo ━━${c.reset}`);
272
+ const r = spawnSync("command", ["-v", "npm"], { shell: true, stdio: "ignore" });
273
+ if (r.status !== 0) {
274
+ log(`${c.yellow}○${c.reset} npm no está en PATH — omito installs`);
275
+ return;
276
+ }
291
277
  for (const ent of readdirSync(workspace)) {
292
278
  const d = join(workspace, ent);
293
279
  if (!statSync(d).isDirectory()) continue;
294
280
  const pkg = join(d, "package.json");
295
281
  if (!existsSync(pkg)) continue;
296
282
  log(`${c.dim}→${c.reset} ${ent}`);
297
- const r = spawnSync("npm", ["install"], { cwd: d, stdio: "inherit" });
298
- if (r.status !== 0) {
299
- log(`${c.yellow}○${c.reset} npm install had issues in ${ent}`);
283
+ const ir = spawnSync("npm", ["install"], { cwd: d, stdio: "inherit" });
284
+ if (ir.status !== 0) {
285
+ log(`${c.yellow}○${c.reset} npm install con avisos en ${ent}`);
300
286
  } else {
301
287
  log(`${c.green}✓${c.reset} ${ent}`);
302
288
  }
@@ -304,18 +290,21 @@ function npmInstallEach(workspace) {
304
290
  }
305
291
 
306
292
  function usage() {
307
- log(`Usage: komplian onboard [options] [dir]`);
308
- log(` npx komplian onboard --yes clones + npm install (default dir: ~/komplian)`);
293
+ log(`Uso: komplian onboard [opciones] [carpeta]`);
294
+ log(` npx komplian onboard --yes`);
309
295
  log(``);
310
- log(` Subcommand onboard implies --install; add --no-install to skip npm install.`);
311
- log(` -y, --yes Non-interactive (default team from JSON)`);
312
- log(` -t, --team <slug> Team from komplian-team-repos.json`);
313
- log(` -i, --install npm install (on by default with onboard; optional without subcommand)`);
314
- log(` --no-install Skip npm install (only after onboard)`);
315
- log(` --all-repos Clone all visible org repos (minus exclude list)`);
316
- log(` --ssh Use SSH URLs (git@github.com:...)`);
317
- log(` --list-teams Print team slugs and exit`);
318
- log(` -h, --help This help`);
296
+ log(` Antes (una vez): gh auth login -h github.com -s repo -s read:org -w`);
297
+ log(` Requisitos: Node 18+, git, GitHub CLI (gh)`);
298
+ log(``);
299
+ log(` onboard implica --install salvo --no-install`);
300
+ log(` -y, --yes Sin menú interactivo (equipo por defecto del JSON)`);
301
+ log(` -t, --team <slug> Equipo en komplian-team-repos.json`);
302
+ log(` -i, --install npm install en cada repo con package.json`);
303
+ log(` --no-install No ejecutar npm install`);
304
+ log(` --all-repos Todos los repos visibles (menos exclude_from_all)`);
305
+ log(` --ssh Clonar con git@github.com:…`);
306
+ log(` --list-teams Lista slugs de equipo (solo JSON) y sale`);
307
+ log(` -h, --help`);
319
308
  }
320
309
 
321
310
  function parseArgs(argv) {
@@ -343,7 +332,7 @@ function parseArgs(argv) {
343
332
  else if (a === "-t" || a === "--team") {
344
333
  out.team = argv[++i] || "";
345
334
  } else if (a.startsWith("-")) {
346
- log(`${c.red}✗${c.reset} Unknown option: ${a}`);
335
+ log(`${c.red}✗${c.reset} Opción desconocida: ${a}`);
347
336
  usage();
348
337
  process.exit(1);
349
338
  } else rest.push(a);
@@ -355,14 +344,14 @@ function parseArgs(argv) {
355
344
  async function pickTeam(cfg) {
356
345
  const teams = Object.keys(cfg.teams || {}).sort();
357
346
  const def = (cfg.default_team || "").trim();
358
- log(`${c.cyan}Teams:${c.reset}`);
347
+ log(`${c.cyan}Equipos:${c.reset}`);
359
348
  teams.forEach((t, i) => {
360
- const mark = t === def ? ` ${c.dim}(default)${c.reset}` : "";
349
+ const mark = t === def ? ` ${c.dim}(por defecto)${c.reset}` : "";
361
350
  log(` ${i + 1}. ${t}${mark}`);
362
351
  });
363
352
  const rl = createInterface({ input, output });
364
353
  const ans = await rl.question(
365
- `\n${c.bold}Choose number or name${c.reset} [${def}]: `
354
+ `\n${c.bold}Número o nombre${c.reset} [${def}]: `
366
355
  );
367
356
  rl.close();
368
357
  const trimmed = (ans || "").trim();
@@ -396,52 +385,47 @@ async function main() {
396
385
  return;
397
386
  }
398
387
 
399
- if (args.listTeams) {
400
- if (!existsSync(configPath)) {
401
- log(`${c.red}✗${c.reset} Missing ${configPath}`);
402
- process.exit(1);
403
- }
404
- needCmd("gh");
405
- if (!ghOk()) {
406
- log(`${c.red}✗${c.reset} Not logged in to GitHub. Run: ${c.bold}gh auth login -h github.com -s repo -s read:org -w${c.reset}`);
407
- process.exit(1);
408
- }
409
- const cfg = loadConfig(configPath);
410
- const orgList = process.env.KOMPLIAN_ORG || cfg.org || "Komplian";
411
- verifyOrgMembership(orgList);
412
- log(Object.keys(cfg.teams || {}).sort().join("\n"));
413
- return;
414
- }
415
-
416
388
  if (!existsSync(configPath)) {
417
- log(`${c.red}✗${c.reset} Missing config: ${configPath}`);
389
+ log(`${c.red}✗${c.reset} Falta la config: ${configPath}`);
418
390
  process.exit(1);
419
391
  }
420
392
 
421
- needCmd("git");
422
- needCmd("gh");
393
+ const cfg = loadConfig(configPath);
394
+
395
+ if (args.listTeams) {
396
+ log(Object.keys(cfg.teams || {}).sort().join("\n"));
397
+ return;
398
+ }
423
399
 
424
400
  const nodeMajor = Number(process.versions.node.split(".")[0], 10);
425
401
  if (nodeMajor < 18) {
426
- log(`${c.red}✗${c.reset} Need Node 18+. You have ${process.versions.node}.`);
402
+ log(`${c.red}✗${c.reset} Hace falta Node 18+. Tienes ${process.versions.node}.`);
427
403
  process.exit(1);
428
404
  }
429
405
 
406
+ needCmd(
407
+ "git",
408
+ `${c.dim}https://git-scm.com${c.reset}`
409
+ );
410
+ needCmd(
411
+ "gh",
412
+ `${c.dim}https://cli.github.com${c.reset}`
413
+ );
414
+
430
415
  if (!ghOk()) {
416
+ log(`${c.red}✗${c.reset} No hay sesión en GitHub CLI.`);
431
417
  if (!runGhAuth()) {
432
- log(`${c.red}✗${c.reset} GitHub authentication did not complete.`);
418
+ log(`${c.red}✗${c.reset} Autenticación cancelada.`);
433
419
  process.exit(1);
434
420
  }
435
421
  }
436
- log(`${c.green}✓${c.reset} GitHub CLI session OK`);
422
+ log(`${c.green}✓${c.reset} GitHub CLI OK`);
437
423
 
438
- const cfg = loadConfig(configPath);
439
424
  const org = process.env.KOMPLIAN_ORG || cfg.org || "Komplian";
440
-
441
425
  verifyOrgMembership(org);
442
426
  logGhIdentity();
443
427
  log(
444
- `${c.green}✓${c.reset} Org ${c.bold}${org}${c.reset}: ${c.dim}active membership verified (GitHub API)${c.reset}`
428
+ `${c.green}✓${c.reset} Org ${c.bold}${org}${c.reset}: ${c.dim}membresía activa${c.reset}`
445
429
  );
446
430
 
447
431
  banner();
@@ -453,7 +437,7 @@ async function main() {
453
437
 
454
438
  const repos = resolveRepos(cfg, org, team, args.allRepos);
455
439
  if (repos.length === 0) {
456
- log(`${c.red}✗${c.reset} No repositories to clone (permissions / team / org).`);
440
+ log(`${c.red}✗${c.reset} No hay repos que clonar (permisos / equipo / org).`);
457
441
  process.exit(1);
458
442
  }
459
443
 
@@ -463,7 +447,7 @@ async function main() {
463
447
  }
464
448
  const abs = resolve(workspace.replace(/^~(?=$|[/\\])/, homedir()));
465
449
  if (!isSafeTargetDir(abs)) {
466
- log(`${c.red}✗${c.reset} Refusing unsafe target directory: ${abs}`);
450
+ log(`${c.red}✗${c.reset} Carpeta destino no segura: ${abs}`);
467
451
  process.exit(1);
468
452
  }
469
453
 
@@ -471,7 +455,7 @@ async function main() {
471
455
  log("");
472
456
  log(`${c.cyan}━━ Workspace ━━${c.reset} ${c.bold}${abs}${c.reset}`);
473
457
  log(`${c.cyan}━━ Org ━━${c.reset} ${c.bold}${org}${c.reset}`);
474
- if (team) log(`${c.cyan}━━ Team ━━${c.reset} ${c.bold}${team}${c.reset}`);
458
+ if (team) log(`${c.cyan}━━ Equipo ━━${c.reset} ${c.bold}${team}${c.reset}`);
475
459
  log(`${c.cyan}━━ Repos (${repos.length}) ━━${c.reset}`);
476
460
  log("");
477
461
 
@@ -484,17 +468,16 @@ async function main() {
484
468
  copyCursorPack(abs, process.env.KOMPLIAN_CURSOR_REPO);
485
469
 
486
470
  if (args.install) {
487
- needCmd("npm");
488
471
  npmInstallEach(abs);
489
472
  }
490
473
 
491
474
  log("");
492
- log(`${c.cyan}━━ Done ━━${c.reset}`);
475
+ log(`${c.cyan}━━ Listo ━━${c.reset}`);
493
476
  if (failed > 0) {
494
- log(`${c.yellow}○${c.reset} ${failed} repo(s) failedcheck access and retry.`);
477
+ log(`${c.yellow}○${c.reset} ${failed} repo(s) fallaronrevisa acceso y reintenta.`);
495
478
  }
496
- log(`${c.green}✓${c.reset} Open in Cursor: ${c.bold}File → Open Folder → ${abs}${c.reset}`);
497
- log(`${c.dim} Copy .env.example → .env per project; secrets via 1Password — never commit.${c.reset}`);
479
+ log(`${c.green}✓${c.reset} Cursor: ${c.bold}File → Open Folder → ${abs}${c.reset}`);
480
+ log(`${c.dim} .env.example → .env por proyecto; secretos en 1Password — nunca commit.${c.reset}`);
498
481
  }
499
482
 
500
483
  main().catch((e) => {
@@ -1,14 +1,14 @@
1
1
  {
2
- "_comment": "Map team slug (-t) repo names in org. Align slugs with GitHub Team names or internal labels. Adjust when teams/repos change.",
2
+ "_comment": "Teams = solo repos de producto (app, api, web, admin, docs). exclude_from_all evita ai-tools, scripts, infra, odoo-module en --all-repos.",
3
3
  "org": "Komplian",
4
4
  "default_team": "platform",
5
- "exclude_from_all": [],
5
+ "exclude_from_all": ["ai-tools", "scripts", "infra", "odoo-module"],
6
6
  "teams": {
7
- "platform": ["app", "api", "web", "admin", "docs", "ai-tools"],
8
- "backend": ["api", "ai-tools"],
9
- "frontend": ["app", "web", "docs", "ai-tools"],
10
- "admin": ["admin", "api", "ai-tools"],
11
- "docs": ["docs", "web", "ai-tools"],
12
- "growth": ["web", "docs", "ai-tools"]
7
+ "platform": ["app", "api", "web", "admin", "docs"],
8
+ "backend": ["api"],
9
+ "frontend": ["app", "web", "docs"],
10
+ "admin": ["admin", "api"],
11
+ "docs": ["docs", "web"],
12
+ "growth": ["web", "docs"]
13
13
  }
14
14
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "komplian",
3
- "version": "0.1.0",
4
- "description": "Komplian developer workspace setup: GitHub CLI auth + clone repos by team (zero manual repo clone for developers use npx).",
3
+ "version": "0.3.0",
4
+ "description": "Komplian developer workspace setup: GitHub CLI (browser login) + git clone by team. Node 18+, git, gh no OAuth App to register.",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=18"