alegra-data-agent 0.1.0 → 0.2.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/dist/auth.js CHANGED
@@ -1,8 +1,8 @@
1
+ import { spawn } from "node:child_process";
2
+ import { randomBytes, timingSafeEqual } from "node:crypto";
1
3
  import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
4
  import { createServer } from "node:http";
3
5
  import { dirname } from "node:path";
4
- import { spawn } from "node:child_process";
5
- import { randomBytes, timingSafeEqual } from "node:crypto";
6
6
  import { GATEWAY_URL, getAuthFilePath } from "./lib.js";
7
7
  function decodeJwtEmail(jwt) {
8
8
  const parts = jwt.split(".");
@@ -39,7 +39,7 @@ export function readAuth() {
39
39
  export function writeAuth(record) {
40
40
  const path = getAuthFilePath();
41
41
  mkdirSync(dirname(path), { recursive: true });
42
- writeFileSync(path, JSON.stringify(record, null, 2) + "\n", "utf-8");
42
+ writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf-8");
43
43
  try {
44
44
  chmodSync(path, 0o600);
45
45
  }
@@ -139,7 +139,7 @@ export async function performBrowserLogin(timeoutMs = 5 * 60 * 1000) {
139
139
  }
140
140
  });
141
141
  server.listen(0, "127.0.0.1", () => {
142
- const address = server.address();
142
+ const address = server?.address();
143
143
  if (typeof address !== "object" || !address) {
144
144
  cleanup();
145
145
  reject(new Error("No se pudo determinar el puerto."));
@@ -150,26 +150,21 @@ export async function performBrowserLogin(timeoutMs = 5 * 60 * 1000) {
150
150
  authUrl.searchParams.set("port", String(port));
151
151
  authUrl.searchParams.set("state", state);
152
152
  const u = authUrl.toString();
153
- process.stdout.write("\n" +
154
- " ╭────────────────────────────────────────────────────────────╮\n" +
155
- " │ Inicia sesión con tu cuenta @alegra.com en el navegador │\n" +
156
- " ╰────────────────────────────────────────────────────────────╯\n" +
157
- "\n" +
158
- " Intento abrir tu navegador. Si no se abre solo, copia toda\n" +
159
- " esta URL (triple-click selecciona la línea completa) y\n" +
160
- " pégala en tu navegador:\n" +
161
- "\n" +
162
- " ────────────────────────────────────────────────────────────\n" +
163
- `${u}\n` +
164
- " ────────────────────────────────────────────────────────────\n" +
165
- "\n" +
166
- ` Esperando hasta ${Math.round(timeoutMs / 1000)}s a que completes el login…\n` +
167
- "\n");
153
+ process.stdout.write(`\n Abrí esta URL para iniciar sesión con tu cuenta @alegra.com:\n\n${u}\n\n Si el navegador no se abrió solo: copiá la URL de arriba y pegala en tu navegador.\n Esperando hasta ${Math.round(timeoutMs / 1000)}s…\n\n`);
154
+ try {
155
+ process.stdout._flush?.();
156
+ }
157
+ catch {
158
+ }
168
159
  openBrowser(u);
160
+ const nudgeTimer = setTimeout(() => {
161
+ if (resolved)
162
+ return;
163
+ process.stdout.write(`\n ¿No se abrió el navegador? Copiá esta URL y pegala manualmente:\n\n${u}\n\n`);
164
+ }, 8000);
169
165
  timer = setTimeout(() => {
170
- safeReject(new Error(`Timeout: pasaron ${Math.round(timeoutMs / 1000)}s sin recibir la auth. ` +
171
- "Si abriste el link en otra máquina sin conexión al CLI, debes correr el comando " +
172
- "desde la misma máquina donde abrirás el browser. Reintenta."));
166
+ clearTimeout(nudgeTimer);
167
+ safeReject(new Error(`Timeout: pasaron ${Math.round(timeoutMs / 1000)}s sin recibir la auth.\n\nSi el navegador no se abrió, copiá esta URL manualmente en tu navegador y completá el login:\n\n${u}\n\nSi abriste el link en otra máquina sin conexión al CLI, debés correr el comando desde la misma máquina donde abrirás el navegador.`));
173
168
  }, timeoutMs);
174
169
  });
175
170
  });
package/dist/colors.js CHANGED
@@ -1,7 +1,5 @@
1
- const SUPPORTS_COLOR = process.stdout.isTTY &&
2
- process.env.TERM !== "dumb" &&
3
- process.env.NO_COLOR === undefined;
4
- const wrap = (open, close) => (s) => (SUPPORTS_COLOR ? `${open}${s}${close}` : String(s));
1
+ const SUPPORTS_COLOR = process.stdout.isTTY && process.env.TERM !== "dumb" && process.env.NO_COLOR === undefined;
2
+ const wrap = (open, close) => (s) => SUPPORTS_COLOR ? `${open}${s}${close}` : String(s);
5
3
  export const bold = wrap("\x1b[1m", "\x1b[22m");
6
4
  export const dim = wrap("\x1b[2m", "\x1b[22m");
7
5
  export const red = wrap("\x1b[31m", "\x1b[39m");
@@ -19,17 +17,17 @@ export function write(s) {
19
17
  process.stdout.write(s);
20
18
  }
21
19
  export function log(s = "") {
22
- process.stdout.write(s + "\n");
20
+ process.stdout.write(`${s}\n`);
23
21
  }
24
22
  export function logError(s) {
25
- process.stderr.write(red("✗ ") + s + "\n");
23
+ process.stderr.write(`${red("✗ ") + s}\n`);
26
24
  }
27
25
  export function logWarn(s) {
28
- process.stdout.write(yellow("⚠ ") + s + "\n");
26
+ process.stdout.write(`${yellow("⚠ ") + s}\n`);
29
27
  }
30
28
  export function logOk(s) {
31
- process.stdout.write(green("✓ ") + s + "\n");
29
+ process.stdout.write(`${green("✓ ") + s}\n`);
32
30
  }
33
31
  export function logInfo(s) {
34
- process.stdout.write(cyan("ℹ ") + s + "\n");
32
+ process.stdout.write(`${cyan("ℹ ") + s}\n`);
35
33
  }
@@ -1,7 +1,7 @@
1
1
  import { fetchManifest } from "../api.js";
2
- import { detectDrift } from "../installer.js";
3
2
  import { hasAuth } from "../auth.js";
4
3
  import { bold, cyan, dim, yellow } from "../colors.js";
4
+ import { detectDrift } from "../installer.js";
5
5
  import { COMMAND_NAME } from "../lib.js";
6
6
  export async function runCheck() {
7
7
  try {
@@ -1,6 +1,6 @@
1
1
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
- import { COMMAND_NAME, getClaudeSettingsPath, getPackageVersion, PRODUCT_NAME, } from "../lib.js";
3
2
  import { logError, logOk, logWarn } from "../colors.js";
3
+ import { COMMAND_NAME, PRODUCT_NAME, getClaudeSettingsPath, getPackageVersion } from "../lib.js";
4
4
  function hookCommandFor(mode) {
5
5
  const v = getPackageVersion();
6
6
  const parts = v.split(".");
@@ -24,7 +24,7 @@ function readSettings() {
24
24
  }
25
25
  }
26
26
  function writeSettings(s) {
27
- writeFileSync(getClaudeSettingsPath(), JSON.stringify(s, null, 2) + "\n", "utf-8");
27
+ writeFileSync(getClaudeSettingsPath(), `${JSON.stringify(s, null, 2)}\n`, "utf-8");
28
28
  }
29
29
  function isOurMatcher(m) {
30
30
  if (!m || typeof m !== "object")
@@ -49,8 +49,7 @@ export function runHookInstall(mode = "check") {
49
49
  const compact = process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE === "1";
50
50
  if (typeof existing === "string") {
51
51
  if (!existing.includes(HOOK_SIGNATURE)) {
52
- logWarn(`Ya hay un SessionStart hook configurado (formato string legacy):\n ${existing}\n` +
53
- ` No lo sobrescribo. Conviértelo a formato array manualmente.`);
52
+ logWarn(`Ya hay un SessionStart hook configurado (formato string legacy):\n ${existing}\n No lo sobrescribo. Conviértelo a formato array manualmente.`);
54
53
  return 1;
55
54
  }
56
55
  settings.hooks.SessionStart = [buildOurMatcher(command)];
@@ -95,7 +94,7 @@ export function runHookUninstall() {
95
94
  logWarn(`El SessionStart hook actual no es de ${PRODUCT_NAME}. No lo toco.`);
96
95
  return 1;
97
96
  }
98
- delete settings.hooks.SessionStart;
97
+ settings.hooks.SessionStart = undefined;
99
98
  writeSettings(settings);
100
99
  logOk("Hook removido.");
101
100
  return 0;
@@ -110,7 +109,7 @@ export function runHookUninstall() {
110
109
  return 1;
111
110
  }
112
111
  if (filtered.length === 0) {
113
- delete settings.hooks.SessionStart;
112
+ settings.hooks.SessionStart = undefined;
114
113
  }
115
114
  else {
116
115
  settings.hooks.SessionStart = filtered;
@@ -1,7 +1,7 @@
1
1
  import { AuthRequiredError } from "../api.js";
2
2
  import { hasAuth, readAuth } from "../auth.js";
3
- import { installAll } from "../installer.js";
4
3
  import { bold, cyan, dim, gray, green, log, logError, red, yellow } from "../colors.js";
4
+ import { installAll } from "../installer.js";
5
5
  import { COMMAND_NAME, formatBytes, getClaudeSkillsDir, getPackageVersion } from "../lib.js";
6
6
  import { runHookInstall } from "./hook.js";
7
7
  import { runLogin } from "./login.js";
@@ -11,10 +11,10 @@ const SHORT_DESCRIPTIONS = {
11
11
  "alegra-sales": "New logos, MRR, deals, funnel, cuota",
12
12
  "alegra-accountant": "Programa Contadores, FC 2.0, halo",
13
13
  "alegra-growth": "Signups, MQL/PQL/SQL, conversión",
14
- "alegra-ce-universe": "CHS, churn, retención, voice of customer",
15
- "alegra-producto": "Métricas SaaS — MRR, ARPA, churn rate",
14
+ "alegra-cx": "CHS, churn, retención, voice of customer",
15
+ "alegra-product": "Métricas SaaS — MRR, ARPA, churn rate",
16
16
  "alegra-strategic": "MRR walk, cross-sell, upsell, pricing",
17
- "alanube": "Tablas Alanube en Redshift",
17
+ alanube: "Tablas Alanube en Redshift",
18
18
  };
19
19
  function padToWidth(text, visibleLen, width) {
20
20
  const pad = Math.max(0, width - visibleLen);
@@ -26,21 +26,18 @@ function fancyBanner() {
26
26
  const nameLen = COMMAND_NAME.length;
27
27
  const versionLen = `v${v}`.length;
28
28
  const spacesBetween = Math.max(1, innerWidth - 4 - nameLen - versionLen);
29
- const line1 = " " +
30
- bold(COMMAND_NAME) +
31
- " ".repeat(spacesBetween) +
32
- dim(`v${v}`);
29
+ const line1 = ` ${bold(COMMAND_NAME)}${" ".repeat(spacesBetween)}${dim(`v${v}`)}`;
33
30
  const line1PadCount = nameLen + spacesBetween + versionLen + 3;
34
31
  const line2Plain = " Agente de Data · Alegra";
35
- const line2 = " " + gray("Agente de Data · Alegra");
32
+ const line2 = ` ${gray("Agente de Data · Alegra")}`;
36
33
  const emptyPad = innerWidth + 2;
37
34
  log("");
38
- log(" " + cyan("╔" + "═".repeat(BANNER_WIDTH) + "╗"));
39
- log(" " + cyan("║") + " ".repeat(emptyPad) + cyan("║"));
40
- log(" " + cyan("║") + padToWidth(line1, line1PadCount, innerWidth + 2) + cyan("║"));
41
- log(" " + cyan("║") + padToWidth(line2, line2Plain.length, innerWidth + 2) + cyan("║"));
42
- log(" " + cyan("║") + " ".repeat(emptyPad) + cyan("║"));
43
- log(" " + cyan("╚" + "═".repeat(BANNER_WIDTH) + "╝"));
35
+ log(` ${cyan(`╔${"═".repeat(BANNER_WIDTH)}╗`)}`);
36
+ log(` ${cyan("║")}${" ".repeat(emptyPad)}${cyan("║")}`);
37
+ log(` ${cyan("║")}${padToWidth(line1, line1PadCount, innerWidth + 2)}${cyan("║")}`);
38
+ log(` ${cyan("║")}${padToWidth(line2, line2Plain.length, innerWidth + 2)}${cyan("║")}`);
39
+ log(` ${cyan("║")}${" ".repeat(emptyPad)}${cyan("║")}`);
40
+ log(` ${cyan(`╚${"═".repeat(BANNER_WIDTH)}╝`)}`);
44
41
  log("");
45
42
  }
46
43
  function section(title) {
@@ -52,7 +49,7 @@ function shortDesc(name, manifestDesc) {
52
49
  if (handPicked)
53
50
  return handPicked;
54
51
  const oneLine = manifestDesc.split(/\.|\n/)[0].trim();
55
- return oneLine.length > 55 ? oneLine.slice(0, 54) + "…" : oneLine;
52
+ return oneLine.length > 55 ? `${oneLine.slice(0, 54)}…` : oneLine;
56
53
  }
57
54
  function printResultRow(result, manifest) {
58
55
  const isSkill = result.kind === "skill";
@@ -61,7 +58,7 @@ function printResultRow(result, manifest) {
61
58
  ? entry
62
59
  ? shortDesc(result.name, entry.description)
63
60
  : ""
64
- : entry?.description ?? "MCP server";
61
+ : (entry?.description ?? "MCP server");
65
62
  let icon;
66
63
  switch (result.status) {
67
64
  case "installed":
@@ -82,7 +79,7 @@ function printResultRow(result, manifest) {
82
79
  }
83
80
  const kindBadge = isSkill ? "" : dim("[mcp] ");
84
81
  const padName = result.name.padEnd(22);
85
- log(` ${icon} ${kindBadge}${bold(padName)} ${gray("· " + desc)}`);
82
+ log(` ${icon} ${kindBadge}${bold(padName)} ${gray(`· ${desc}`)}`);
86
83
  }
87
84
  function printSummary(results, manifest) {
88
85
  const installed = results.filter((r) => r.status === "installed").length;
@@ -114,22 +111,24 @@ function printSummary(results, manifest) {
114
111
  parts.push(yellow(`${skipped} omitidas`));
115
112
  if (failed > 0)
116
113
  parts.push(red(`${failed} fallidas`));
117
- log(" " +
118
- green("✓") +
119
- " " +
120
- bold(parts.join(gray(" · "))) +
121
- gray(` · ${totalFiles} archivos · ${formatBytes(totalBytes)}`));
122
- log(" " + green("✓") + " " + gray("destino:") + " " + getClaudeSkillsDir());
114
+ log(` ${green("✓")} ${bold(parts.join(gray(" · ")))}${gray(` · ${totalFiles} archivos · ${formatBytes(totalBytes)}`)}`);
115
+ log(` ${green("✓")} ${gray("destino:")} ${getClaudeSkillsDir()}`);
123
116
  log("");
124
117
  for (const r of results.filter((x) => x.status === "failed")) {
125
118
  logError(`${r.name}: ${r.error}`);
126
119
  }
127
120
  }
128
121
  function printNextSteps(noHook) {
129
- section("Siguientes pasos");
130
- log(" " + bold(yellow("→ Reinicia Claude Code para activar las skills")));
122
+ section("Ya estás listo");
123
+ log(` ${bold(yellow("1."))} ${bold("Reiniciá tu cliente")} ${gray("(Claude Code, Cursor, OpenCode o Antigravity)")}.`);
124
+ log(` ${bold(yellow("2."))} ${bold("Hacele tu pregunta en español.")}`);
125
+ log(` ${gray("Ejemplo:")} ${cyan('"cuántos nuevos clientes en mayo en Colombia?"')}`);
126
+ log("");
127
+ log(` ${gray("La primera vez que use un servicio (Metabase, Amplitude, HubSpot, Wiki),")}`);
128
+ log(` ${gray("te va a pedir 1 click para darle permiso con tu cuenta")} ${bold("@alegra.com")}${gray(".")}`);
129
+ log(` ${gray("Después no te pide nada más.")}`);
131
130
  log("");
132
- log(" " + dim("Comandos útiles:"));
131
+ log(` ${dim("Comandos útiles:")}`);
133
132
  log(` ${dim("$")} ${bold(`${COMMAND_NAME} list`)} ${gray("ver skills disponibles")}`);
134
133
  log(` ${dim("$")} ${bold(`${COMMAND_NAME} status`)} ${gray("estado actual + drift")}`);
135
134
  log(` ${dim("$")} ${bold(`${COMMAND_NAME} update`)} ${gray("actualizar a la última")}`);
@@ -138,9 +137,7 @@ function printNextSteps(noHook) {
138
137
  log(` ${dim("$")} ${bold(`${COMMAND_NAME} hook uninstall`)} ${gray("apagar aviso")}`);
139
138
  }
140
139
  log("");
141
- log(" " +
142
- dim("Docs · ") +
143
- cyan("github.com/alegradev/alegra-data-agent"));
140
+ log(` ${dim("Docs · ")}${cyan("github.com/alegradev/alegra-data-agent")}`);
144
141
  log("");
145
142
  }
146
143
  function isClaudeCode() {
@@ -160,7 +157,7 @@ export async function runInstall(opts = {}) {
160
157
  const auth = readAuth();
161
158
  if (auth && !compact) {
162
159
  section("Cuenta autorizada");
163
- log(" " + green("✓") + " " + bold(auth.email));
160
+ log(` ${green("✓")} ${bold(auth.email)}`);
164
161
  log("");
165
162
  }
166
163
  if (opts.dryRun) {
@@ -187,13 +184,15 @@ export async function runInstall(opts = {}) {
187
184
  const msg = err instanceof Error ? err.message : String(err);
188
185
  logError(msg);
189
186
  if (msg.includes("EACCES") || msg.includes("permission denied")) {
190
- log(dim(` Sin permisos para escribir. Verifica permisos de ~/.claude/skills/`));
187
+ log(dim(" Sin permisos para escribir. Verifica permisos de ~/.claude/skills/"));
191
188
  }
192
189
  else if (msg.includes("ENOSPC") || msg.includes("no space")) {
193
- log(dim(` No hay espacio en disco. Libera espacio y reintenta.`));
190
+ log(dim(" No hay espacio en disco. Libera espacio y reintenta."));
194
191
  }
195
- else if (msg.includes("network") || msg.includes("ENOTFOUND") || msg.includes("ECONNREFUSED")) {
196
- log(dim(` Verifica tu conexión a internet y el estado del gateway.`));
192
+ else if (msg.includes("network") ||
193
+ msg.includes("ENOTFOUND") ||
194
+ msg.includes("ECONNREFUSED")) {
195
+ log(dim(" Verifica tu conexión a internet y el estado del gateway."));
197
196
  }
198
197
  return 1;
199
198
  }
@@ -217,7 +216,7 @@ export async function runInstall(opts = {}) {
217
216
  return 0;
218
217
  if (!opts.noHook) {
219
218
  if (!compact)
220
- log(" " + gray("Instalando hook de notificaciones…"));
219
+ log(` ${gray("Instalando hook de notificaciones…")}`);
221
220
  runHookInstall(opts.hookMode ?? "check");
222
221
  if (!compact)
223
222
  log("");
@@ -245,27 +244,23 @@ function printCompactSummary(results, manifest, email) {
245
244
  }
246
245
  }
247
246
  const verbo = updated > 0 && installed === 0 ? "actualizadas" : "instaladas";
248
- const skillList = skillOks.map((r) => r.name).sort().join(", ");
247
+ const skillList = skillOks
248
+ .map((r) => r.name)
249
+ .sort()
250
+ .join(", ");
249
251
  const userPart = email ? gray(` · ${email}`) : "";
250
252
  log("");
251
- log(" " +
252
- green("✓") +
253
- " " +
254
- bold(`${skillOks.length} skills + ${mcpOks.length} MCPs ${verbo}`) +
255
- gray(` · ${totalFiles} archivos · ${formatBytes(totalBytes)}`) +
256
- userPart);
253
+ log(` ${green("✓")} ${bold(`${skillOks.length} skills + ${mcpOks.length} MCPs ${verbo}`)}${gray(` · ${totalFiles} archivos · ${formatBytes(totalBytes)}`)}${userPart}`);
257
254
  if (skillList)
258
- log(" " + gray("skills: ") + gray(skillList));
255
+ log(` ${gray("skills: ")}${gray(skillList)}`);
259
256
  if (mcpOks.length > 0)
260
- log(" " + gray("mcps: ") + gray(mcpOks.map((r) => r.name).sort().join(", ")));
261
- log(" " +
262
- gray("destino:") +
263
- " " +
264
- cyan(getClaudeSkillsDir()) +
265
- gray(" · ") +
266
- bold(yellow("reinicia tu cliente para activarlas")));
257
+ log(` ${gray("mcps: ")}${gray(mcpOks
258
+ .map((r) => r.name)
259
+ .sort()
260
+ .join(", "))}`);
261
+ log(` ${gray("destino:")} ${cyan(getClaudeSkillsDir())}${gray(" · ")}${bold(yellow("reinicia tu cliente para activarlas"))}`);
267
262
  if (unchanged > 0 && installed === 0 && updated === 0) {
268
- log(" " + dim(`(${unchanged} sin cambios desde la última instalación)`));
263
+ log(` ${dim(`(${unchanged} sin cambios desde la última instalación)`)}`);
269
264
  }
270
265
  log("");
271
266
  for (const r of failed) {
@@ -1,6 +1,6 @@
1
1
  import { AuthRequiredError, fetchManifest } from "../api.js";
2
- import { readLockFile, formatBytes } from "../lib.js";
3
2
  import { bold, cyan, dim, gray, green, log, logError, yellow } from "../colors.js";
3
+ import { formatBytes, readLockFile } from "../lib.js";
4
4
  export async function runList() {
5
5
  let manifest;
6
6
  try {
@@ -21,7 +21,7 @@ export async function runList() {
21
21
  const lock = readLockFile();
22
22
  log("");
23
23
  log(bold(cyan(" Skills disponibles")) + dim(` — ${Object.keys(manifest.skills).length} totales`));
24
- log(gray(" " + "─".repeat(70)));
24
+ log(gray(` ${"─".repeat(70)}`));
25
25
  const names = Object.keys(manifest.skills).sort();
26
26
  for (const name of names) {
27
27
  const entry = manifest.skills[name];
@@ -36,7 +36,7 @@ export async function runList() {
36
36
  badge = yellow("[actualizar] ");
37
37
  const team = entry.team ? gray(` ${entry.team}`) : "";
38
38
  log(` ${badge} ${bold(name)}${team} ${dim(formatBytes(entry.bytes))}`);
39
- const desc = entry.description.length > 200 ? entry.description.slice(0, 200) + "…" : entry.description;
39
+ const desc = entry.description.length > 200 ? `${entry.description.slice(0, 200)}…` : entry.description;
40
40
  log(gray(` ${desc}`));
41
41
  log("");
42
42
  }
@@ -1,7 +1,7 @@
1
1
  import { performBrowserLogin, readAuth, writeAuth } from "../auth.js";
2
- import { banner } from "../ui.js";
3
2
  import { bold, dim, green, log, logError } from "../colors.js";
4
3
  import { GATEWAY_URL, getAuthFilePath } from "../lib.js";
4
+ import { banner } from "../ui.js";
5
5
  export async function runLogin(opts = {}) {
6
6
  banner();
7
7
  if (!opts.force) {
@@ -9,7 +9,7 @@ export async function runLogin(opts = {}) {
9
9
  if (existing) {
10
10
  log(` ${green("✓")} Ya estás autenticado como ${bold(existing.email)}.`);
11
11
  log(dim(` Token: ${getAuthFilePath()}`));
12
- log(dim(` Para volver a iniciar sesión: --force`));
12
+ log(dim(" Para volver a iniciar sesión: --force"));
13
13
  log("");
14
14
  return 0;
15
15
  }
@@ -1,13 +1,13 @@
1
1
  import { AuthRequiredError, fetchManifest } from "../api.js";
2
2
  import { readAuth } from "../auth.js";
3
+ import { bold, cyan, dim, gray, green, log, logError, logWarn, yellow } from "../colors.js";
3
4
  import { detectDrift } from "../installer.js";
4
5
  import { COMMAND_NAME, GATEWAY_URL, getClaudeSkillsDir, getLockFilePath, getPackageVersion, readLockFile, } from "../lib.js";
5
- import { bold, cyan, dim, gray, green, log, logError, logWarn, yellow } from "../colors.js";
6
6
  export async function runStatus() {
7
7
  const auth = readAuth();
8
8
  log("");
9
9
  log(bold(cyan(" Estado")));
10
- log(gray(" " + "─".repeat(70)));
10
+ log(gray(` ${"─".repeat(70)}`));
11
11
  log(` ${gray("Paquete:")} ${bold(COMMAND_NAME)} v${getPackageVersion()}`);
12
12
  log(` ${gray("Gateway:")} ${GATEWAY_URL}`);
13
13
  log(` ${gray("Autenticado:")} ${auth ? green(auth.email) : gray("no")}`);
@@ -1,7 +1,7 @@
1
+ import { dim, gray, log, logOk, logWarn, red } from "../colors.js";
1
2
  import { uninstallAll } from "../installer.js";
2
3
  import { readLockFile } from "../lib.js";
3
4
  import { banner, confirm } from "../ui.js";
4
- import { dim, gray, log, logOk, logWarn, red } from "../colors.js";
5
5
  export async function runUninstall(opts = {}) {
6
6
  banner();
7
7
  const lock = readLockFile();
@@ -1,7 +1,7 @@
1
1
  import { AuthRequiredError, fetchManifest } from "../api.js";
2
+ import { cyan, dim, gray, green, log, logError, yellow } from "../colors.js";
2
3
  import { detectDrift, installAll, summarize } from "../installer.js";
3
4
  import { banner } from "../ui.js";
4
- import { cyan, dim, gray, green, log, logError, yellow } from "../colors.js";
5
5
  export async function runUpdate(opts = {}) {
6
6
  banner();
7
7
  let manifest;
package/dist/installer.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { createHash } from "node:crypto";
2
- import { copyFileSync, existsSync, readFileSync, renameSync, rmSync, writeFileSync, } from "node:fs";
2
+ import { copyFileSync, existsSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join, resolve, sep } from "node:path";
4
4
  import { fetchManifest, fetchSkillFile } from "./api.js";
5
5
  import { dim, gray, green, logError, red, yellow } from "./colors.js";
6
6
  import { ensureDir, getLockFilePath, getPackageVersion, readLockFile, sha256File, writeLockFile, } from "./lib.js";
7
7
  import { renderMcpForTarget } from "./render.js";
8
- import { detectInstalledTargets, TARGETS } from "./targets.js";
8
+ import { TARGETS, detectInstalledTargets } from "./targets.js";
9
9
  const SKILL_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
10
10
  const MCP_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
11
11
  function validateSkillName(name) {
@@ -161,7 +161,7 @@ function registerMcpInTarget(mcp, spec) {
161
161
  section[mcp.name] = renderMcpForTarget(mcp, spec);
162
162
  cfg[spec.mcpRootKey] = section;
163
163
  backupIfExists(spec.mcpConfigPath);
164
- atomicWriteFile(spec.mcpConfigPath, JSON.stringify(cfg, null, 2) + "\n");
164
+ atomicWriteFile(spec.mcpConfigPath, `${JSON.stringify(cfg, null, 2)}\n`);
165
165
  }
166
166
  function unregisterMcpInTarget(mcpName, spec) {
167
167
  if (!existsSync(spec.mcpConfigPath))
@@ -173,13 +173,11 @@ function unregisterMcpInTarget(mcpName, spec) {
173
173
  delete section[mcpName];
174
174
  cfg[spec.mcpRootKey] = section;
175
175
  backupIfExists(spec.mcpConfigPath);
176
- atomicWriteFile(spec.mcpConfigPath, JSON.stringify(cfg, null, 2) + "\n");
176
+ atomicWriteFile(spec.mcpConfigPath, `${JSON.stringify(cfg, null, 2)}\n`);
177
177
  return true;
178
178
  }
179
179
  function resolveTargets(opts) {
180
- const ids = opts.targets?.length
181
- ? opts.targets
182
- : detectInstalledTargets();
180
+ const ids = opts.targets?.length ? opts.targets : detectInstalledTargets();
183
181
  if (ids.length === 0) {
184
182
  ids.push("claude-code");
185
183
  }
@@ -330,7 +328,7 @@ export async function installAll(opts = {}) {
330
328
  ? opts.only.filter((s) => manifest.skills[s])
331
329
  : Object.keys(manifest.skills);
332
330
  const mcpNames = opts.only && opts.only.length > 0
333
- ? opts.only.filter((s) => manifest.mcps && manifest.mcps[s])
331
+ ? opts.only.filter((s) => manifest.mcps?.[s])
334
332
  : Object.keys(manifest.mcps ?? {});
335
333
  const results = [];
336
334
  for (const name of skillNames) {
package/dist/lib.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync, } from "node:fs";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join, relative, sep } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
@@ -7,8 +7,7 @@ export const PRODUCT_NAME = "alegra-data-agent";
7
7
  export const COMMAND_NAME = "alegra-data-agent";
8
8
  export const REPO_SLUG = "alegradev/alegra-data-agent";
9
9
  export const REPO_DEFAULT_BRANCH = "main";
10
- export const GATEWAY_URL = process.env.ALEGRA_DATA_AGENT_GATEWAY_URL ??
11
- "https://alegra-data-agent.vercel.app";
10
+ export const GATEWAY_URL = process.env.ALEGRA_DATA_AGENT_GATEWAY_URL ?? "https://alegra-data-agent.vercel.app";
12
11
  export const ALL_TARGETS = ["claude-code", "cursor", "opencode", "antigravity"];
13
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
14
13
  export function getPackageRoot() {
@@ -122,7 +121,7 @@ export function readLockFile() {
122
121
  export function writeLockFile(lock) {
123
122
  const path = getLockFilePath();
124
123
  mkdirSync(dirname(path), { recursive: true });
125
- writeFileSync(path, JSON.stringify(lock, null, 2) + "\n", "utf-8");
124
+ writeFileSync(path, `${JSON.stringify(lock, null, 2)}\n`, "utf-8");
126
125
  }
127
126
  export function sha256(buf) {
128
127
  return createHash("sha256").update(buf).digest("hex");
package/dist/main.js CHANGED
@@ -1,6 +1,6 @@
1
+ import { log, logError } from "./colors.js";
1
2
  import { ALL_TARGETS, getPackageVersion } from "./lib.js";
2
3
  import { helpText } from "./ui.js";
3
- import { log, logError } from "./colors.js";
4
4
  import { runCheck } from "./commands/check.js";
5
5
  import { runHookInstall, runHookUninstall } from "./commands/hook.js";
6
6
  import { runInstall } from "./commands/install.js";
@@ -13,7 +13,10 @@ import { runUpdate } from "./commands/update.js";
13
13
  import { runWhoami } from "./commands/whoami.js";
14
14
  function parseTargetList(raw) {
15
15
  const out = [];
16
- for (const piece of raw.split(",").map((s) => s.trim()).filter(Boolean)) {
16
+ for (const piece of raw
17
+ .split(",")
18
+ .map((s) => s.trim())
19
+ .filter(Boolean)) {
17
20
  if (ALL_TARGETS.includes(piece)) {
18
21
  out.push(piece);
19
22
  }
package/dist/signature.js CHANGED
@@ -5,11 +5,13 @@ export function canonicalize(obj) {
5
5
  if (obj === null || typeof obj !== "object")
6
6
  return JSON.stringify(obj);
7
7
  if (Array.isArray(obj)) {
8
- return "[" + obj.map((v) => canonicalize(v)).join(",") + "]";
8
+ return `[${obj.map((v) => canonicalize(v)).join(",")}]`;
9
9
  }
10
10
  const o = obj;
11
- const keys = Object.keys(o).filter((k) => k !== "signature").sort();
12
- return "{" + keys.map((k) => JSON.stringify(k) + ":" + canonicalize(o[k])).join(",") + "}";
11
+ const keys = Object.keys(o)
12
+ .filter((k) => k !== "signature")
13
+ .sort();
14
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalize(o[k])}`).join(",")}}`;
13
15
  }
14
16
  const MAX_MANIFEST_AGE_DAYS = 90;
15
17
  export function verifyManifestSignature(manifest, options = {}) {
@@ -24,11 +26,14 @@ export function verifyManifestSignature(manifest, options = {}) {
24
26
  return { ok: false, reason: `algoritmo no soportado: ${sig.alg}` };
25
27
  }
26
28
  if (sig.keyId !== SIGNING_KEY_ID) {
27
- return { ok: false, reason: `keyId no reconocido: ${sig.keyId} (CLI espera ${SIGNING_KEY_ID})` };
29
+ return {
30
+ ok: false,
31
+ reason: `keyId no reconocido: ${sig.keyId} (CLI espera ${SIGNING_KEY_ID})`,
32
+ };
28
33
  }
29
34
  if (typeof manifest.generatedAt === "string") {
30
35
  const ts = Date.parse(manifest.generatedAt);
31
- if (!isNaN(ts)) {
36
+ if (!Number.isNaN(ts)) {
32
37
  const ageMs = Date.now() - ts;
33
38
  const maxAgeMs = MAX_MANIFEST_AGE_DAYS * 24 * 3600 * 1000;
34
39
  if (ageMs > maxAgeMs) {
package/dist/ui.js CHANGED
@@ -1,5 +1,5 @@
1
- import { createInterface } from "node:readline/promises";
2
1
  import { stdin as input, stdout as output } from "node:process";
2
+ import { createInterface } from "node:readline/promises";
3
3
  import { bold, cyan, dim, gray, log } from "./colors.js";
4
4
  import { COMMAND_NAME, getPackageVersion } from "./lib.js";
5
5
  export function banner() {
@@ -25,7 +25,7 @@ export async function confirm(question, defaultYes = true) {
25
25
  export async function ask(question, defaultValue) {
26
26
  const rl = createInterface({ input, output });
27
27
  try {
28
- const suffix = defaultValue ? dim(`(${defaultValue})`) + " " : "";
28
+ const suffix = defaultValue ? `${dim(`(${defaultValue})`)} ` : "";
29
29
  const answer = (await rl.question(`${question} ${suffix}`)).trim();
30
30
  return answer === "" && defaultValue !== undefined ? defaultValue : answer;
31
31
  }
package/index.mjs CHANGED
@@ -8,9 +8,7 @@ const [major, minor] = process.versions.node.split(".").map(Number);
8
8
 
9
9
  if (major < 22 || (major === 22 && minor < 6)) {
10
10
  console.error(
11
- `\n ⚠ alegra-data-agent requiere Node.js >= 22.6.0.` +
12
- `\n Versión actual: ${process.version}` +
13
- `\n Actualiza desde → https://nodejs.org\n`,
11
+ `\n ⚠ alegra-data-agent requiere Node.js >= 22.6.0.\n Versión actual: ${process.version}\n Actualiza desde → https://nodejs.org\n`,
14
12
  );
15
13
  process.exit(1);
16
14
  }
@@ -27,7 +25,7 @@ if (existsSync(distPath)) {
27
25
  // Desarrollo: ejecuta el .ts directo (requiere --experimental-strip-types).
28
26
  const tsPath = join(__dirname, "src", "main.ts");
29
27
  if (!existsSync(tsPath)) {
30
- console.error(`\n ⚠ Instalación incompleta: no se encontró dist/ ni src/.\n`);
28
+ console.error("\n ⚠ Instalación incompleta: no se encontró dist/ ni src/.\n");
31
29
  process.exit(1);
32
30
  }
33
31
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alegra-data-agent",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "private": false,
5
5
  "description": "Agente de Data interno de Alegra. Instala skills + MCPs en tu cliente de IA con un comando.",
6
6
  "license": "UNLICENSED",
@@ -37,7 +37,9 @@
37
37
  "manifest:generate": "node --experimental-strip-types --disable-warning=ExperimentalWarning scripts/generate-manifest.mjs",
38
38
  "manifest:sign": "node scripts/sign-manifest.mjs",
39
39
  "manifest:scan": "node scripts/scan-skills.mjs",
40
- "manifest": "pnpm run manifest:scan && pnpm run manifest:generate && pnpm run manifest:sign",
40
+ "manifest:validate": "node scripts/validate-skills.mjs --legacy-ok",
41
+ "manifest:validate:strict": "node scripts/validate-skills.mjs",
42
+ "manifest": "pnpm run manifest:validate && pnpm run manifest:scan && pnpm run manifest:generate && pnpm run manifest:sign",
41
43
  "test": "node tests/runner.mjs",
42
44
  "test:report": "node tests/runner.mjs --report markdown",
43
45
  "test:security": "node tests/security/runner.mjs",