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/README.md +2 -2
- package/dist/api.js +101 -9
- package/dist/auth.js +12 -0
- package/dist/bounce.js +11 -0
- package/dist/colors.js +0 -9
- package/dist/commands/hook.js +2 -1
- package/dist/commands/login.js +8 -0
- package/dist/commands/status.js +23 -2
- package/dist/commands/uninstall.js +0 -16
- package/dist/commands/update.js +74 -15
- package/dist/installer.js +110 -80
- package/dist/lazy-mcp.js +24 -3
- package/dist/lib.js +232 -6
- package/dist/main.js +11 -90
- package/dist/render.js +0 -77
- package/dist/signature.js +27 -19
- package/dist/ui.js +0 -11
- package/package.json +2 -2
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 {
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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.
|
|
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.
|
|
22
|
+
"@types/node": "26.6.3",
|
|
23
23
|
"typescript": "7.0.2"
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|