alegra-data-agent 0.4.4 → 0.5.1

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/main.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { maybeBounceToLatest } from "./bounce.js";
2
2
  import { log, logError } from "./colors.js";
3
- import { ALL_TARGETS, getPackageVersion } from "./lib.js";
3
+ import { getPackageVersion, parseArgs } from "./lib.js";
4
4
  import { helpText } from "./ui.js";
5
5
  import { runCheck } from "./commands/check.js";
6
6
  import { runHookInstall, runHookUninstall } from "./commands/hook.js";
@@ -12,92 +12,9 @@ import { runStatus } from "./commands/status.js";
12
12
  import { runUninstall } from "./commands/uninstall.js";
13
13
  import { runUpdate } from "./commands/update.js";
14
14
  import { runWhoami } from "./commands/whoami.js";
15
- function parseTargetList(raw) {
16
- const out = [];
17
- for (const piece of raw
18
- .split(",")
19
- .map((s) => s.trim())
20
- .filter(Boolean)) {
21
- if (ALL_TARGETS.includes(piece)) {
22
- out.push(piece);
23
- }
24
- else {
25
- throw new Error(`Target desconocido: '${piece}'. Válidos: ${ALL_TARGETS.join(", ")}`);
26
- }
27
- }
28
- return out;
29
- }
30
- function parseArgs(argv) {
31
- const flags = {
32
- yes: false,
33
- dryRun: false,
34
- force: false,
35
- help: false,
36
- version: false,
37
- noHook: false,
38
- noBounce: false,
39
- autoUpdate: false,
40
- background: false,
41
- };
42
- const positional = [];
43
- const only = [];
44
- const targets = [];
45
- let i = 0;
46
- while (i < argv.length) {
47
- const a = argv[i];
48
- if (a === "-y" || a === "--yes")
49
- flags.yes = true;
50
- else if (a === "--dry-run")
51
- flags.dryRun = true;
52
- else if (a === "--force")
53
- flags.force = true;
54
- else if (a === "--no-hook")
55
- flags.noHook = true;
56
- else if (a === "--no-bounce")
57
- flags.noBounce = true;
58
- else if (a === "--auto-update")
59
- flags.autoUpdate = true;
60
- else if (a === "--background")
61
- flags.background = true;
62
- else if (a === "-h" || a === "--help")
63
- flags.help = true;
64
- else if (a === "-v" || a === "--version")
65
- flags.version = true;
66
- else if (a === "--only") {
67
- i++;
68
- if (argv[i])
69
- only.push(...argv[i].split(","));
70
- }
71
- else if (a.startsWith("--only=")) {
72
- only.push(...a.slice("--only=".length).split(","));
73
- }
74
- else if (a === "--target") {
75
- i++;
76
- if (argv[i])
77
- targets.push(...parseTargetList(argv[i]));
78
- }
79
- else if (a.startsWith("--target=")) {
80
- targets.push(...parseTargetList(a.slice("--target=".length)));
81
- }
82
- else {
83
- positional.push(a);
84
- }
85
- i++;
86
- }
87
- return {
88
- command: positional[0] ?? "install",
89
- subcommand: positional[1],
90
- flags,
91
- only,
92
- targets,
93
- };
94
- }
95
15
  async function main() {
96
16
  const args = parseArgs(process.argv.slice(2));
97
- if (!args.flags.noBounce) {
98
- await maybeBounceToLatest(process.argv.slice(2));
99
- }
100
- if (args.flags.version) {
17
+ if (args.flags.version || args.command === "version") {
101
18
  log(getPackageVersion());
102
19
  process.exit(0);
103
20
  }
@@ -105,6 +22,9 @@ async function main() {
105
22
  log(helpText());
106
23
  process.exit(0);
107
24
  }
25
+ if (!args.flags.noBounce) {
26
+ await maybeBounceToLatest(process.argv.slice(2));
27
+ }
108
28
  let exitCode = 0;
109
29
  switch (args.command) {
110
30
  case "login":
@@ -158,17 +78,18 @@ async function main() {
158
78
  exitCode = 1;
159
79
  }
160
80
  break;
161
- case "version":
162
- log(getPackageVersion());
163
- break;
164
81
  default:
165
82
  logError(`Comando desconocido: '${args.command}'.`);
166
83
  log(helpText());
167
84
  exitCode = 1;
168
85
  }
169
- process.exit(exitCode);
86
+ exitGracefully(exitCode);
87
+ }
88
+ function exitGracefully(code) {
89
+ process.exitCode = code;
90
+ setTimeout(() => process.exit(code), 2000).unref();
170
91
  }
171
92
  main().catch((err) => {
172
93
  logError(err instanceof Error ? err.message : String(err));
173
- process.exit(1);
94
+ exitGracefully(1);
174
95
  });
package/dist/render.js CHANGED
@@ -1,76 +1,4 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import { extname, join } from "node:path";
3
1
  import { stdioLaunch } from "./lazy-mcp.js";
4
- import { parseFrontmatter } from "./lib.js";
5
- function readSkillEntryPoint(skillDir) {
6
- const candidates = ["SKILL.md", "README.md"];
7
- for (const c of candidates) {
8
- const p = join(skillDir, c);
9
- if (existsSync(p)) {
10
- return { content: readFileSync(p, "utf-8"), path: p };
11
- }
12
- }
13
- throw new Error(`No SKILL.md ni README.md en ${skillDir}`);
14
- }
15
- export function renderSkillForTarget(skillSrcDir, skillName, format) {
16
- if (format === "folder") {
17
- return { kind: "folder" };
18
- }
19
- const { content } = readSkillEntryPoint(skillSrcDir);
20
- const fm = parseFrontmatter(content);
21
- const body = stripFrontmatter(content);
22
- if (format === "cursor-mdc") {
23
- const desc = fm.description ?? `Skill ${skillName}`;
24
- const cursorDesc = desc.startsWith("USE WHEN") ? desc : `USE WHEN ${desc}`;
25
- const safeDesc = cursorDesc.replace(/\n/g, " ").replace(/"/g, '\\"');
26
- const mdc = `---\ndescription: "${safeDesc}"\nalwaysApply: false\n---\n\n${body}`;
27
- return { kind: "single", filename: `${skillName}.mdc`, content: mdc };
28
- }
29
- if (format === "flat-md") {
30
- return { kind: "single", filename: `${skillName}.md`, content: body };
31
- }
32
- const _exhaustive = format;
33
- throw new Error(`SkillFormat no soportado: ${String(_exhaustive)}`);
34
- }
35
- function stripFrontmatter(content) {
36
- const m = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n([\s\S]*)$/);
37
- if (!m)
38
- return content;
39
- return m[1].replace(/^\s+/, "");
40
- }
41
- const INLINE_TEXT_EXT = new Set([".md", ".sql", ".txt", ".json", ".yaml", ".yml", ".csv"]);
42
- const INLINE_MAX_BYTES = 64 * 1024;
43
- const FENCE_LANG = {
44
- ".sql": "sql",
45
- ".json": "json",
46
- ".yaml": "yaml",
47
- ".yml": "yaml",
48
- ".csv": "csv",
49
- ".txt": "",
50
- };
51
- export function inlineReferences(refs) {
52
- const sections = [];
53
- for (const ref of refs) {
54
- const ext = extname(ref.rel).toLowerCase();
55
- const bytes = Buffer.byteLength(ref.text, "utf-8");
56
- if (!INLINE_TEXT_EXT.has(ext)) {
57
- sections.push(`### \`${ref.rel}\`\n\n> (omitido: tipo \`${ext || "sin extensión"}\` no inlineable)`);
58
- }
59
- else if (bytes > INLINE_MAX_BYTES) {
60
- sections.push(`### \`${ref.rel}\`\n\n> (omitido: ${Math.round(bytes / 1024)} KB; supera el límite de inline)`);
61
- }
62
- else if (ext === ".md") {
63
- sections.push(`### \`${ref.rel}\`\n\n${ref.text.trim()}`);
64
- }
65
- else {
66
- const lang = FENCE_LANG[ext] ?? "";
67
- sections.push(`### \`${ref.rel}\`\n\n\`\`\`${lang}\n${ref.text.replace(/\s+$/, "")}\n\`\`\``);
68
- }
69
- }
70
- if (sections.length === 0)
71
- return "";
72
- return `\n\n---\n\n# Referencias (incluidas inline)\n\n> Este cliente instala la skill como un solo archivo; el contenido de \`references/\` se incluye acá para que las menciones del cuerpo sigan disponibles.\n\n${sections.join("\n\n")}\n`;
73
- }
74
2
  export function renderMcpForTarget(mcp, spec) {
75
3
  if (mcp.transport === "stdio") {
76
4
  return renderStdioMcpForTarget(mcp, spec);
@@ -150,8 +78,3 @@ export function renderHermesMcpOps(mcp) {
150
78
  }
151
79
  return ops;
152
80
  }
153
- export function hermesManagedFields(mcp) {
154
- return renderHermesMcpOps(mcp)
155
- .map((op) => op.path)
156
- .sort();
157
- }
package/dist/signature.js CHANGED
@@ -5,10 +5,6 @@ export const TRUSTED_KEYS = [
5
5
  keyId: "alegra-data-agent-v2",
6
6
  publicKeyB64: "MCowBQYDK2VwAyEA5nrWo7oWlhyad9rJjQHqgL0xaPZ83Yn5dJotGXxCo0c=",
7
7
  },
8
- {
9
- keyId: "alegra-data-agent-v1",
10
- publicKeyB64: "MCowBQYDK2VwAyEApoojQPy6INQXl/xZ0gQumsI3tt9HvMrb0PaVlNn7TkE=",
11
- },
12
8
  ];
13
9
  export const PUBLIC_KEY_B64 = TRUSTED_KEYS[0].publicKeyB64;
14
10
  export function canonicalize(obj) {
@@ -27,6 +23,12 @@ function canonicalValue(value, isRoot) {
27
23
  return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalValue(o[k], false)}`).join(",")}}`;
28
24
  }
29
25
  const MAX_MANIFEST_AGE_DAYS = 90;
26
+ export function manifestTimestamp(m) {
27
+ if (!m || typeof m.generatedAt !== "string")
28
+ return null;
29
+ const ts = Date.parse(m.generatedAt);
30
+ return Number.isNaN(ts) ? null : ts;
31
+ }
30
32
  export function verifyManifestSignature(manifest, options = {}) {
31
33
  const sig = manifest.signature;
32
34
  if (!sig) {
@@ -46,21 +48,27 @@ export function verifyManifestSignature(manifest, options = {}) {
46
48
  reason: `keyId no reconocido: ${sig.keyId} (este CLI acepta ${keys.map((k) => k.keyId).join(", ")})`,
47
49
  };
48
50
  }
49
- if (typeof manifest.generatedAt === "string") {
50
- const ts = Date.parse(manifest.generatedAt);
51
- if (!Number.isNaN(ts)) {
52
- const ageMs = Date.now() - ts;
53
- const maxAgeMs = MAX_MANIFEST_AGE_DAYS * 24 * 3600 * 1000;
54
- if (ageMs > maxAgeMs) {
55
- return {
56
- ok: false,
57
- reason: `manifest demasiado viejo (${Math.round(ageMs / (24 * 3600 * 1000))}d, máx ${MAX_MANIFEST_AGE_DAYS}d)`,
58
- };
59
- }
60
- if (ageMs < -3600_000) {
61
- return { ok: false, reason: "manifest tiene generatedAt en el futuro" };
62
- }
63
- }
51
+ const ts = manifestTimestamp(manifest);
52
+ if (ts === null) {
53
+ return { ok: false, reason: "manifest sin generatedAt válido (ISO 8601)" };
54
+ }
55
+ const ageMs = Date.now() - ts;
56
+ const maxAgeMs = MAX_MANIFEST_AGE_DAYS * 24 * 3600 * 1000;
57
+ if (ageMs > maxAgeMs) {
58
+ return {
59
+ ok: false,
60
+ reason: `manifest demasiado viejo (${Math.round(ageMs / (24 * 3600 * 1000))}d, máx ${MAX_MANIFEST_AGE_DAYS}d)`,
61
+ };
62
+ }
63
+ if (ageMs < -3600_000) {
64
+ return { ok: false, reason: "manifest tiene generatedAt en el futuro" };
65
+ }
66
+ const floor = options.notOlderThan;
67
+ if (typeof floor === "number" && Number.isFinite(floor) && ts < floor) {
68
+ return {
69
+ ok: false,
70
+ reason: `manifest más viejo que el ya aceptado en esta máquina (generatedAt ${manifest.generatedAt} < ${new Date(floor).toISOString()}): posible downgrade`,
71
+ };
64
72
  }
65
73
  const canonical = canonicalize(manifest);
66
74
  const publicKey = createPublicKey({
package/dist/ui.js CHANGED
@@ -22,17 +22,6 @@ export async function confirm(question, defaultYes = true) {
22
22
  rl.close();
23
23
  }
24
24
  }
25
- export async function ask(question, defaultValue) {
26
- const rl = createInterface({ input, output });
27
- try {
28
- const suffix = defaultValue ? `${dim(`(${defaultValue})`)} ` : "";
29
- const answer = (await rl.question(`${question} ${suffix}`)).trim();
30
- return answer === "" && defaultValue !== undefined ? defaultValue : answer;
31
- }
32
- finally {
33
- rl.close();
34
- }
35
- }
36
25
  export function helpText() {
37
26
  return `
38
27
  ${bold(COMMAND_NAME)} — Agente de Data interno de Alegra
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alegra-data-agent",
3
- "version": "0.4.4",
3
+ "version": "0.5.1",
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",
@@ -19,7 +19,7 @@
19
19
  "README.md"
20
20
  ],
21
21
  "devDependencies": {
22
- "@types/node": "26.4.0",
22
+ "@types/node": "26.6.3",
23
23
  "typescript": "7.0.2"
24
24
  },
25
25
  "repository": {