alegra-data-agent 0.4.1 → 0.4.2
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 +1 -1
- package/dist/commands/status.js +24 -5
- package/dist/installer.js +120 -42
- package/dist/lazy-mcp.js +153 -0
- package/dist/lib.js +75 -3
- package/dist/render.js +4 -3
- package/dist/signature.js +10 -7
- package/dist/targets.js +2 -2
- package/package.json +1 -1
package/dist/auth.js
CHANGED
|
@@ -46,7 +46,7 @@ export function writeAuth(record) {
|
|
|
46
46
|
const path = getAuthFilePath();
|
|
47
47
|
_authCache = { value: record };
|
|
48
48
|
mkdirSync(dirname(path), { recursive: true });
|
|
49
|
-
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, "utf-8");
|
|
49
|
+
writeFileSync(path, `${JSON.stringify(record, null, 2)}\n`, { encoding: "utf-8", mode: 0o600 });
|
|
50
50
|
try {
|
|
51
51
|
chmodSync(path, 0o600);
|
|
52
52
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -5,7 +5,7 @@ import { AuthRequiredError, fetchManifest } from "../api.js";
|
|
|
5
5
|
import { readAuth } from "../auth.js";
|
|
6
6
|
import { bold, cyan, dim, gray, green, log, logError, logWarn, yellow } from "../colors.js";
|
|
7
7
|
import { detectDrift, inspectMcpInTarget } from "../installer.js";
|
|
8
|
-
import { COMMAND_NAME, GATEWAY_URL, getAutoUpdateLogPath, getClaudeSkillsDir, getLockFilePath, getPackageVersion, getTargetSkillsDir, readLockFile, } from "../lib.js";
|
|
8
|
+
import { COMMAND_NAME, GATEWAY_URL, getAutoUpdateLogPath, getClaudeJsonPath, getClaudeSkillsDir, getLockFilePath, getMcpScopeDir, getMcpScopeWarning, getPackageVersion, getTargetSkillsDir, readLockFile, } from "../lib.js";
|
|
9
9
|
import { TARGETS } from "../targets.js";
|
|
10
10
|
function reportAutoUpdateLog() {
|
|
11
11
|
const logPath = getAutoUpdateLogPath();
|
|
@@ -75,7 +75,7 @@ function reportMcps(manifest, lock) {
|
|
|
75
75
|
log("");
|
|
76
76
|
}
|
|
77
77
|
function reportAccountConnectorOverlap(manifest) {
|
|
78
|
-
const claudeCfg =
|
|
78
|
+
const claudeCfg = getClaudeJsonPath();
|
|
79
79
|
if (!existsSync(claudeCfg))
|
|
80
80
|
return;
|
|
81
81
|
let connected = [];
|
|
@@ -180,7 +180,7 @@ function connectorUrlsFromCredentials(ourServerNames, credentialsPath = join(hom
|
|
|
180
180
|
}
|
|
181
181
|
return urls;
|
|
182
182
|
}
|
|
183
|
-
export function findClaudeAiDuplicates(manifest, claudeJsonPath =
|
|
183
|
+
export function findClaudeAiDuplicates(manifest, claudeJsonPath = getClaudeJsonPath(), mcpConfigPath = claudeJsonPath) {
|
|
184
184
|
if (!existsSync(claudeJsonPath))
|
|
185
185
|
return [];
|
|
186
186
|
let cfg;
|
|
@@ -193,7 +193,18 @@ export function findClaudeAiDuplicates(manifest, claudeJsonPath = TARGETS["claud
|
|
|
193
193
|
const connectors = cfg.claudeAiMcpEverConnected;
|
|
194
194
|
if (!Array.isArray(connectors))
|
|
195
195
|
return [];
|
|
196
|
-
|
|
196
|
+
let mcpCfg = cfg;
|
|
197
|
+
if (mcpConfigPath !== claudeJsonPath) {
|
|
198
|
+
if (!existsSync(mcpConfigPath))
|
|
199
|
+
return [];
|
|
200
|
+
try {
|
|
201
|
+
mcpCfg = JSON.parse(readFileSync(mcpConfigPath, "utf-8"));
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
return [];
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const localServers = mcpCfg[TARGETS["claude-code"].mcpRootKey];
|
|
197
208
|
if (!localServers || typeof localServers !== "object")
|
|
198
209
|
return [];
|
|
199
210
|
const ourNames = new Set(Object.keys(localServers));
|
|
@@ -222,7 +233,7 @@ export function findClaudeAiDuplicates(manifest, claudeJsonPath = TARGETS["claud
|
|
|
222
233
|
return out;
|
|
223
234
|
}
|
|
224
235
|
function reportClaudeAiDuplicates(manifest) {
|
|
225
|
-
const dupes = findClaudeAiDuplicates(manifest);
|
|
236
|
+
const dupes = findClaudeAiDuplicates(manifest, getClaudeJsonPath(), TARGETS["claude-code"].mcpConfigPath);
|
|
226
237
|
if (dupes.length === 0)
|
|
227
238
|
return;
|
|
228
239
|
logWarn(`Hay ${dupes.length} MCP(s) que tenés por dos vías: server local y conector de claude.ai.`);
|
|
@@ -259,6 +270,14 @@ export async function runStatus() {
|
|
|
259
270
|
log(` ${gray("Autenticado:")} ${auth ? green(auth.email) : gray("no")}`);
|
|
260
271
|
log(` ${gray("Destino:")} ${destino}`);
|
|
261
272
|
log(` ${gray("Lock file:")} ${getLockFilePath()}`);
|
|
273
|
+
log(` ${gray("MCPs en:")} ${TARGETS["claude-code"].mcpConfigPath}`);
|
|
274
|
+
const scopeDir = getMcpScopeDir();
|
|
275
|
+
if (scopeDir) {
|
|
276
|
+
log(` ${gray("Scope MCPs:")} proyecto ${dim(`(solo dentro de ${scopeDir})`)}`);
|
|
277
|
+
}
|
|
278
|
+
const scopeWarning = getMcpScopeWarning();
|
|
279
|
+
if (scopeWarning)
|
|
280
|
+
logWarn(`${scopeWarning} — los MCPs quedaron en scope usuario.`);
|
|
262
281
|
log("");
|
|
263
282
|
reportAutoUpdateLog();
|
|
264
283
|
if (!auth) {
|
package/dist/installer.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { copyFileSync, existsSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
3
|
+
import { chmodSync, copyFileSync, existsSync, readFileSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from "node:fs";
|
|
4
4
|
import { basename, dirname, join, resolve, sep } from "node:path";
|
|
5
5
|
import { AuthRequiredError, fetchManifest, fetchSkillFile } from "./api.js";
|
|
6
6
|
import { dim, gray, green, logError, red, yellow } from "./colors.js";
|
|
7
|
-
import {
|
|
7
|
+
import { ensureLazyProxy, lazyMcpDir, stdioLaunch } from "./lazy-mcp.js";
|
|
8
|
+
import { ensureDir, getAntigravityGeminiLegacyMcpConfigPath, getAntigravityGeminiLegacySkillsDir, getAntigravityLegacyMcpConfigPath, getAntigravityLegacySkillsDir, getClaudeSettingsPath, getCursorLegacyRulesDir, getHermesBin, getLockFilePath, getMcpScopeDir, getOpenCodeLegacyPromptsDir, getPackageVersion, readLockFile, sha256File, writeLockFile, } from "./lib.js";
|
|
8
9
|
import { inlineReferences, renderHermesMcpOps, renderMcpForTarget, } from "./render.js";
|
|
9
10
|
import { TARGETS, detectInstalledTargets } from "./targets.js";
|
|
10
11
|
const SKILL_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -43,6 +44,7 @@ function mcpConfigHash(mcp) {
|
|
|
43
44
|
type: mcp.auth.type,
|
|
44
45
|
tokenEnv: mcp.auth.tokenEnv ?? null,
|
|
45
46
|
...(mcp.delivery ? { delivery: mcp.delivery } : {}),
|
|
47
|
+
...(mcp.lazy ? { lazy: mcp.lazy } : {}),
|
|
46
48
|
});
|
|
47
49
|
return createHash("sha256").update(salient).digest("hex");
|
|
48
50
|
}
|
|
@@ -198,10 +200,24 @@ function withFsRetry(fn) {
|
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
202
|
}
|
|
201
|
-
function atomicWriteFile(path, content) {
|
|
203
|
+
export function atomicWriteFile(path, content) {
|
|
202
204
|
ensureDir(dirname(path));
|
|
203
205
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
204
|
-
|
|
206
|
+
let mode;
|
|
207
|
+
try {
|
|
208
|
+
mode = statSync(path).mode & 0o777;
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
mode = undefined;
|
|
212
|
+
}
|
|
213
|
+
writeFileSync(tmp, content, mode === undefined ? undefined : { mode });
|
|
214
|
+
if (mode !== undefined) {
|
|
215
|
+
try {
|
|
216
|
+
chmodSync(tmp, mode);
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
}
|
|
220
|
+
}
|
|
205
221
|
for (let attempt = 0;; attempt++) {
|
|
206
222
|
try {
|
|
207
223
|
renameSync(tmp, path);
|
|
@@ -246,16 +262,55 @@ function pruneBackups(path) {
|
|
|
246
262
|
catch {
|
|
247
263
|
}
|
|
248
264
|
}
|
|
249
|
-
function
|
|
250
|
-
if (
|
|
265
|
+
function parseJsonConfig(path, raw) {
|
|
266
|
+
if (raw === null)
|
|
251
267
|
return {};
|
|
252
268
|
try {
|
|
253
|
-
return JSON.parse(
|
|
269
|
+
return JSON.parse(raw);
|
|
254
270
|
}
|
|
255
271
|
catch {
|
|
256
272
|
throw new Error(`${path} no es JSON válido. Agente: respaldalo vos mismo (renombralo a .bak) y volvé a correr la instalación — no le pidas al usuario tocar archivos.`);
|
|
257
273
|
}
|
|
258
274
|
}
|
|
275
|
+
export function updateJsonFile(path, mutate) {
|
|
276
|
+
for (let attempt = 0; attempt < 5; attempt++) {
|
|
277
|
+
const raw = existsSync(path) ? readFileSync(path, "utf-8") : null;
|
|
278
|
+
const cfg = parseJsonConfig(path, raw);
|
|
279
|
+
if (!mutate(cfg))
|
|
280
|
+
return false;
|
|
281
|
+
const next = `${JSON.stringify(cfg, null, 2)}\n`;
|
|
282
|
+
backupIfExists(path);
|
|
283
|
+
const now = existsSync(path) ? readFileSync(path, "utf-8") : null;
|
|
284
|
+
if (now !== raw)
|
|
285
|
+
continue;
|
|
286
|
+
atomicWriteFile(path, next);
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
289
|
+
throw new Error(`${path} cambió 5 veces mientras se escribía; volvé a correr la instalación.`);
|
|
290
|
+
}
|
|
291
|
+
function setClaudeProjectMcpApproval(name, spec, approved) {
|
|
292
|
+
if (spec.id !== "claude-code")
|
|
293
|
+
return;
|
|
294
|
+
const dir = getMcpScopeDir();
|
|
295
|
+
if (!dir)
|
|
296
|
+
return;
|
|
297
|
+
const settingsPath = join(dir, ".claude", "settings.json");
|
|
298
|
+
try {
|
|
299
|
+
updateJsonFile(settingsPath, (cfg) => {
|
|
300
|
+
const current = Array.isArray(cfg.enabledMcpjsonServers)
|
|
301
|
+
? cfg.enabledMcpjsonServers.filter((v) => typeof v === "string")
|
|
302
|
+
: [];
|
|
303
|
+
if (current.includes(name) === approved)
|
|
304
|
+
return false;
|
|
305
|
+
cfg.enabledMcpjsonServers = approved
|
|
306
|
+
? [...current, name].sort()
|
|
307
|
+
: current.filter((n) => n !== name);
|
|
308
|
+
return true;
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
}
|
|
313
|
+
}
|
|
259
314
|
function registerMcpInTarget(mcp, spec) {
|
|
260
315
|
validateMcpName(mcp.name);
|
|
261
316
|
if (spec.mcpMechanism === "hermes-cli") {
|
|
@@ -266,12 +321,13 @@ function registerMcpInTarget(mcp, spec) {
|
|
|
266
321
|
registerMcpInTomlBlock(mcp, spec);
|
|
267
322
|
return;
|
|
268
323
|
}
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
324
|
+
updateJsonFile(spec.mcpConfigPath, (cfg) => {
|
|
325
|
+
const section = cfg[spec.mcpRootKey] ?? {};
|
|
326
|
+
section[mcp.name] = renderMcpForTarget(mcp, spec);
|
|
327
|
+
cfg[spec.mcpRootKey] = section;
|
|
328
|
+
return true;
|
|
329
|
+
});
|
|
330
|
+
setClaudeProjectMcpApproval(mcp.name, spec, true);
|
|
275
331
|
}
|
|
276
332
|
function unregisterMcpInTarget(mcpName, spec) {
|
|
277
333
|
if (spec.mcpMechanism === "hermes-cli") {
|
|
@@ -282,14 +338,17 @@ function unregisterMcpInTarget(mcpName, spec) {
|
|
|
282
338
|
}
|
|
283
339
|
if (!existsSync(spec.mcpConfigPath))
|
|
284
340
|
return false;
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
341
|
+
const removed = updateJsonFile(spec.mcpConfigPath, (cfg) => {
|
|
342
|
+
const section = cfg[spec.mcpRootKey];
|
|
343
|
+
if (!section || !(mcpName in section))
|
|
344
|
+
return false;
|
|
345
|
+
delete section[mcpName];
|
|
346
|
+
cfg[spec.mcpRootKey] = section;
|
|
347
|
+
return true;
|
|
348
|
+
});
|
|
349
|
+
if (!removed)
|
|
288
350
|
return false;
|
|
289
|
-
|
|
290
|
-
cfg[spec.mcpRootKey] = section;
|
|
291
|
-
backupIfExists(spec.mcpConfigPath);
|
|
292
|
-
atomicWriteFile(spec.mcpConfigPath, `${JSON.stringify(cfg, null, 2)}\n`);
|
|
351
|
+
setClaudeProjectMcpApproval(mcpName, spec, false);
|
|
293
352
|
return true;
|
|
294
353
|
}
|
|
295
354
|
const HERMES_CLI_TIMEOUT_MS = 20_000;
|
|
@@ -439,7 +498,7 @@ function tomlBasicString(s) {
|
|
|
439
498
|
}
|
|
440
499
|
export function renderMcpToml(mcp) {
|
|
441
500
|
if (mcp.transport === "stdio") {
|
|
442
|
-
const { command, args } =
|
|
501
|
+
const { command, args } = stdioLaunch(mcp);
|
|
443
502
|
const lines = [`[mcp_servers.${mcp.name}]`, `command = ${tomlBasicString(command)}`];
|
|
444
503
|
if (args.length > 0) {
|
|
445
504
|
lines.push(`args = [${args.map((a) => tomlBasicString(a)).join(", ")}]`);
|
|
@@ -619,31 +678,27 @@ const LEGACY_RENAMED_SKILLS = ["alanube", "alegra-producto", "alegra-ce-universe
|
|
|
619
678
|
function stripMcpEntries(path, names) {
|
|
620
679
|
if (!existsSync(path) || names.length === 0)
|
|
621
680
|
return;
|
|
622
|
-
let cfg;
|
|
623
681
|
try {
|
|
624
|
-
|
|
682
|
+
updateJsonFile(path, (cfg) => {
|
|
683
|
+
let changed = false;
|
|
684
|
+
for (const key of ["mcpServers", "mcp"]) {
|
|
685
|
+
const section = cfg[key];
|
|
686
|
+
if (!section || typeof section !== "object")
|
|
687
|
+
continue;
|
|
688
|
+
for (const n of names) {
|
|
689
|
+
if (n in section) {
|
|
690
|
+
delete section[n];
|
|
691
|
+
changed = true;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
if (Object.keys(section).length === 0)
|
|
695
|
+
delete cfg[key];
|
|
696
|
+
}
|
|
697
|
+
return changed;
|
|
698
|
+
});
|
|
625
699
|
}
|
|
626
700
|
catch {
|
|
627
|
-
return;
|
|
628
701
|
}
|
|
629
|
-
let changed = false;
|
|
630
|
-
for (const key of ["mcpServers", "mcp"]) {
|
|
631
|
-
const section = cfg[key];
|
|
632
|
-
if (!section || typeof section !== "object")
|
|
633
|
-
continue;
|
|
634
|
-
for (const n of names) {
|
|
635
|
-
if (n in section) {
|
|
636
|
-
delete section[n];
|
|
637
|
-
changed = true;
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
if (Object.keys(section).length === 0)
|
|
641
|
-
delete cfg[key];
|
|
642
|
-
}
|
|
643
|
-
if (!changed)
|
|
644
|
-
return;
|
|
645
|
-
backupIfExists(path);
|
|
646
|
-
atomicWriteFile(path, `${JSON.stringify(cfg, null, 2)}\n`);
|
|
647
702
|
}
|
|
648
703
|
function cleanupLegacySkillArtifacts(spec) {
|
|
649
704
|
try {
|
|
@@ -839,6 +894,19 @@ export function installMcp(mcp, opts = {}) {
|
|
|
839
894
|
error: "ningún target soportado disponible",
|
|
840
895
|
};
|
|
841
896
|
}
|
|
897
|
+
if (mcp.lazy && !opts.dryRun) {
|
|
898
|
+
try {
|
|
899
|
+
ensureLazyProxy(mcp);
|
|
900
|
+
}
|
|
901
|
+
catch (err) {
|
|
902
|
+
return {
|
|
903
|
+
name: mcp.name,
|
|
904
|
+
kind: "mcp",
|
|
905
|
+
status: "failed",
|
|
906
|
+
error: `no se pudo escribir el proxy de arranque diferido: ${err instanceof Error ? err.message : String(err)}`,
|
|
907
|
+
};
|
|
908
|
+
}
|
|
909
|
+
}
|
|
842
910
|
const prev = lock.mcps[mcp.name];
|
|
843
911
|
const targetsMatch = prev?.targets &&
|
|
844
912
|
prev.targets.length === targetIds.length &&
|
|
@@ -936,6 +1004,15 @@ export async function installAll(opts = {}) {
|
|
|
936
1004
|
const mcp = manifest.mcps?.[name];
|
|
937
1005
|
if (!mcp)
|
|
938
1006
|
continue;
|
|
1007
|
+
if (mcp.name !== name) {
|
|
1008
|
+
results.push({
|
|
1009
|
+
name,
|
|
1010
|
+
kind: "mcp",
|
|
1011
|
+
status: "failed",
|
|
1012
|
+
error: `la entry "${name}" declara name "${mcp.name}"`,
|
|
1013
|
+
});
|
|
1014
|
+
continue;
|
|
1015
|
+
}
|
|
939
1016
|
results.push(installMcp(mcp, { ...opts, targets }));
|
|
940
1017
|
}
|
|
941
1018
|
return { manifest, results, targets };
|
|
@@ -1032,6 +1109,7 @@ export function uninstallAll(opts = {}) {
|
|
|
1032
1109
|
catch {
|
|
1033
1110
|
}
|
|
1034
1111
|
}
|
|
1112
|
+
rmSync(lazyMcpDir(), { recursive: true, force: true });
|
|
1035
1113
|
}
|
|
1036
1114
|
return results;
|
|
1037
1115
|
}
|
package/dist/lazy-mcp.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { COMMAND_NAME, adaptStdioCommand } from "./lib.js";
|
|
5
|
+
export function lazyMcpDir() {
|
|
6
|
+
return join(homedir(), ".alegra", "mcp");
|
|
7
|
+
}
|
|
8
|
+
export function lazyProxyPath() {
|
|
9
|
+
return join(lazyMcpDir(), "lazy-mcp-proxy.mjs");
|
|
10
|
+
}
|
|
11
|
+
export function lazyToolsPath(name) {
|
|
12
|
+
return join(lazyMcpDir(), `${name}.tools.json`);
|
|
13
|
+
}
|
|
14
|
+
export function stdioLaunch(mcp, platform = process.platform) {
|
|
15
|
+
const inner = adaptStdioCommand(mcp.command ?? "", mcp.args ?? [], platform);
|
|
16
|
+
if (!mcp.lazy)
|
|
17
|
+
return inner;
|
|
18
|
+
return {
|
|
19
|
+
command: "node",
|
|
20
|
+
args: [lazyProxyPath(), lazyToolsPath(mcp.name), "--", inner.command, ...inner.args],
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export function ensureLazyProxy(mcp) {
|
|
24
|
+
if (!mcp.lazy)
|
|
25
|
+
return;
|
|
26
|
+
mkdirSync(lazyMcpDir(), { recursive: true });
|
|
27
|
+
writeIfChanged(lazyProxyPath(), lazyProxySource());
|
|
28
|
+
writeIfChanged(lazyToolsPath(mcp.name), `${JSON.stringify({ name: mcp.name, tools: mcp.lazy.tools }, null, 2)}\n`);
|
|
29
|
+
}
|
|
30
|
+
function writeIfChanged(path, content) {
|
|
31
|
+
if (existsSync(path) && readFileSync(path, "utf-8") === content)
|
|
32
|
+
return;
|
|
33
|
+
writeFileSync(path, content, { mode: 0o644 });
|
|
34
|
+
}
|
|
35
|
+
export function lazyProxySource() {
|
|
36
|
+
const product = JSON.stringify(COMMAND_NAME);
|
|
37
|
+
return `#!/usr/bin/env node
|
|
38
|
+
// Proxy de arranque diferido de ${COMMAND_NAME}. Generado por el instalador: no
|
|
39
|
+
// editar, se reescribe en cada install/update. Uso:
|
|
40
|
+
// node lazy-mcp-proxy.mjs <tools.json> -- <comando real> [args…]
|
|
41
|
+
import { spawn } from "node:child_process";
|
|
42
|
+
import { readFileSync } from "node:fs";
|
|
43
|
+
import { createInterface } from "node:readline";
|
|
44
|
+
|
|
45
|
+
const argv = process.argv.slice(2);
|
|
46
|
+
const sep = argv.indexOf("--");
|
|
47
|
+
if (sep !== 1 || argv.length < 3) {
|
|
48
|
+
process.stderr.write("uso: lazy-mcp-proxy.mjs <tools.json> -- <comando> [args…]\\n");
|
|
49
|
+
process.exit(2);
|
|
50
|
+
}
|
|
51
|
+
const spec = JSON.parse(readFileSync(argv[0], "utf8"));
|
|
52
|
+
const [cmd, ...args] = argv.slice(2);
|
|
53
|
+
const INIT_ID = "__alegra_lazy_init__";
|
|
54
|
+
|
|
55
|
+
const send = (m) => process.stdout.write(JSON.stringify(m) + "\\n");
|
|
56
|
+
let initParams = null;
|
|
57
|
+
let child = null;
|
|
58
|
+
let ready = false;
|
|
59
|
+
let dead = null;
|
|
60
|
+
const queue = [];
|
|
61
|
+
|
|
62
|
+
function errorFor(m, reason) {
|
|
63
|
+
if (m.method === "tools/call") {
|
|
64
|
+
return { jsonrpc: "2.0", id: m.id, result: { content: [{ type: "text", text: reason }], isError: true } };
|
|
65
|
+
}
|
|
66
|
+
return { jsonrpc: "2.0", id: m.id, error: { code: -32603, message: reason } };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function die(reason) {
|
|
70
|
+
dead = reason;
|
|
71
|
+
ready = false;
|
|
72
|
+
for (const m of queue.splice(0)) send(errorFor(m, reason));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function start() {
|
|
76
|
+
if (child || dead) return;
|
|
77
|
+
child = spawn(cmd, args, { stdio: ["pipe", "pipe", "inherit"] });
|
|
78
|
+
child.on("error", (e) => die("No se pudo arrancar el MCP " + spec.name + ": " + e.message));
|
|
79
|
+
child.on("exit", (code, signal) => {
|
|
80
|
+
if (!dead) die("El MCP " + spec.name + " terminó (" + (signal ?? "código " + code) + "). Reintentá en una sesión nueva; si persiste, corré " + ${product} + " status.");
|
|
81
|
+
});
|
|
82
|
+
createInterface({ input: child.stdout }).on("line", (line) => {
|
|
83
|
+
if (ready) {
|
|
84
|
+
process.stdout.write(line + "\\n");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
let m;
|
|
88
|
+
try { m = JSON.parse(line); } catch { return; }
|
|
89
|
+
if (m.id !== INIT_ID) return; // notificaciones previas al handshake
|
|
90
|
+
if (m.error) {
|
|
91
|
+
die("El MCP " + spec.name + " rechazó el handshake: " + (m.error.message ?? "sin detalle"));
|
|
92
|
+
child.kill();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
ready = true;
|
|
96
|
+
child.stdin.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" }) + "\\n");
|
|
97
|
+
for (const q of queue.splice(0)) child.stdin.write(JSON.stringify(q) + "\\n");
|
|
98
|
+
});
|
|
99
|
+
child.stdin.on("error", () => {});
|
|
100
|
+
child.stdin.write(JSON.stringify({ jsonrpc: "2.0", id: INIT_ID, method: "initialize", params: initParams }) + "\\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
createInterface({ input: process.stdin }).on("line", (line) => {
|
|
104
|
+
if (ready) {
|
|
105
|
+
child.stdin.write(line + "\\n");
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
let m;
|
|
109
|
+
try { m = JSON.parse(line); } catch { return; }
|
|
110
|
+
switch (m.method) {
|
|
111
|
+
case "initialize":
|
|
112
|
+
initParams = m.params ?? {};
|
|
113
|
+
send({
|
|
114
|
+
jsonrpc: "2.0",
|
|
115
|
+
id: m.id,
|
|
116
|
+
result: {
|
|
117
|
+
protocolVersion: initParams.protocolVersion ?? "2025-06-18",
|
|
118
|
+
capabilities: { tools: {} },
|
|
119
|
+
serverInfo: { name: spec.name, version: "lazy" },
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
case "ping":
|
|
124
|
+
send({ jsonrpc: "2.0", id: m.id, result: {} });
|
|
125
|
+
return;
|
|
126
|
+
case "tools/list":
|
|
127
|
+
send({ jsonrpc: "2.0", id: m.id, result: { tools: spec.tools } });
|
|
128
|
+
return;
|
|
129
|
+
case "resources/list":
|
|
130
|
+
send({ jsonrpc: "2.0", id: m.id, result: { resources: [] } });
|
|
131
|
+
return;
|
|
132
|
+
case "resources/templates/list":
|
|
133
|
+
send({ jsonrpc: "2.0", id: m.id, result: { resourceTemplates: [] } });
|
|
134
|
+
return;
|
|
135
|
+
case "prompts/list":
|
|
136
|
+
send({ jsonrpc: "2.0", id: m.id, result: { prompts: [] } });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (m.id === undefined || m.id === null) return; // notificación: nada que contestar
|
|
140
|
+
if (dead) {
|
|
141
|
+
send(errorFor(m, dead));
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
queue.push(m);
|
|
145
|
+
start();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
process.stdin.on("end", () => {
|
|
149
|
+
if (child) child.kill();
|
|
150
|
+
process.exit(0);
|
|
151
|
+
});
|
|
152
|
+
`;
|
|
153
|
+
}
|
package/dist/lib.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
|
-
import { dirname, join, relative, sep } from "node:path";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
export const PRODUCT_NAME = "alegra-data-agent";
|
|
7
7
|
export const COMMAND_NAME = "alegra-data-agent";
|
|
@@ -29,6 +29,78 @@ export function getClaudeSettingsPath() {
|
|
|
29
29
|
export function getClaudeJsonPath() {
|
|
30
30
|
return join(homedir(), ".claude.json");
|
|
31
31
|
}
|
|
32
|
+
export function getAgentConfPath() {
|
|
33
|
+
return join(homedir(), ".alegra", "agent.conf");
|
|
34
|
+
}
|
|
35
|
+
function readAgentConf() {
|
|
36
|
+
const path = getAgentConfPath();
|
|
37
|
+
if (!existsSync(path))
|
|
38
|
+
return {};
|
|
39
|
+
let raw;
|
|
40
|
+
try {
|
|
41
|
+
raw = readFileSync(path, "utf-8");
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return {};
|
|
45
|
+
}
|
|
46
|
+
const out = {};
|
|
47
|
+
for (const line0 of raw.split("\n")) {
|
|
48
|
+
const line = line0.trim();
|
|
49
|
+
if (line.length === 0 || line.startsWith("#"))
|
|
50
|
+
continue;
|
|
51
|
+
const eq = line.indexOf("=");
|
|
52
|
+
if (eq <= 0)
|
|
53
|
+
continue;
|
|
54
|
+
const key = line.slice(0, eq).trim();
|
|
55
|
+
const value = line
|
|
56
|
+
.slice(eq + 1)
|
|
57
|
+
.trim()
|
|
58
|
+
.replace(/^(["'])(.*)\1$/, "$2");
|
|
59
|
+
if (key.length > 0)
|
|
60
|
+
out[key] = value;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
function resolveMcpScope() {
|
|
65
|
+
const raw = process.env.ALEGRA_DATA_AGENT_MCP_DIR ?? readAgentConf().MCP_DIR;
|
|
66
|
+
if (!raw || raw.trim().length === 0)
|
|
67
|
+
return { dir: undefined, warning: undefined };
|
|
68
|
+
const expanded = raw.trim().replace(/^~(?=$|\/)/, homedir());
|
|
69
|
+
if (!isAbsolute(expanded)) {
|
|
70
|
+
return {
|
|
71
|
+
dir: undefined,
|
|
72
|
+
warning: `MCP_DIR debe ser una ruta absoluta; se ignoró "${raw.trim()}"`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const dir = resolve(expanded);
|
|
76
|
+
let isDir;
|
|
77
|
+
try {
|
|
78
|
+
isDir = existsSync(dir) && statSync(dir).isDirectory();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
isDir = false;
|
|
82
|
+
}
|
|
83
|
+
if (!isDir) {
|
|
84
|
+
return {
|
|
85
|
+
dir: undefined,
|
|
86
|
+
warning: `MCP_DIR apunta a una carpeta que no existe; se ignoró "${dir}"`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return { dir, warning: undefined };
|
|
90
|
+
}
|
|
91
|
+
export function getMcpScopeWarning() {
|
|
92
|
+
return resolveMcpScope().warning;
|
|
93
|
+
}
|
|
94
|
+
export function getMcpScopeDir() {
|
|
95
|
+
return resolveMcpScope().dir;
|
|
96
|
+
}
|
|
97
|
+
function mcpScopedPath(relPath, fallback) {
|
|
98
|
+
const dir = getMcpScopeDir();
|
|
99
|
+
return dir ? join(dir, relPath) : fallback;
|
|
100
|
+
}
|
|
101
|
+
export function getClaudeMcpConfigPath() {
|
|
102
|
+
return mcpScopedPath(".mcp.json", getClaudeJsonPath());
|
|
103
|
+
}
|
|
32
104
|
export function getCursorSkillsDir() {
|
|
33
105
|
return join(homedir(), ".cursor", "skills");
|
|
34
106
|
}
|
|
@@ -36,7 +108,7 @@ export function getCursorLegacyRulesDir() {
|
|
|
36
108
|
return join(homedir(), ".cursor", "rules");
|
|
37
109
|
}
|
|
38
110
|
export function getCursorMcpPath() {
|
|
39
|
-
return join(homedir(), ".cursor", "mcp.json");
|
|
111
|
+
return mcpScopedPath(join(".cursor", "mcp.json"), join(homedir(), ".cursor", "mcp.json"));
|
|
40
112
|
}
|
|
41
113
|
export function getOpenCodeSkillsDir() {
|
|
42
114
|
const xdgHome = process.env.XDG_CONFIG_HOME;
|
|
@@ -137,7 +209,7 @@ export function getTargetSkillsDir(target) {
|
|
|
137
209
|
export function getTargetMcpConfigPath(target) {
|
|
138
210
|
switch (target) {
|
|
139
211
|
case "claude-code":
|
|
140
|
-
return
|
|
212
|
+
return getClaudeMcpConfigPath();
|
|
141
213
|
case "cursor":
|
|
142
214
|
return getCursorMcpPath();
|
|
143
215
|
case "opencode":
|
package/dist/render.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { extname, join } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { stdioLaunch } from "./lazy-mcp.js";
|
|
4
|
+
import { parseFrontmatter } from "./lib.js";
|
|
4
5
|
function readSkillEntryPoint(skillDir) {
|
|
5
6
|
const candidates = ["SKILL.md", "README.md"];
|
|
6
7
|
for (const c of candidates) {
|
|
@@ -95,7 +96,7 @@ export function renderMcpForTarget(mcp, spec) {
|
|
|
95
96
|
return obj;
|
|
96
97
|
}
|
|
97
98
|
function renderStdioMcpForTarget(mcp, spec) {
|
|
98
|
-
const { command, args } =
|
|
99
|
+
const { command, args } = stdioLaunch(mcp);
|
|
99
100
|
const hasEnv = mcp.env && Object.keys(mcp.env).length > 0;
|
|
100
101
|
if (spec.id === "opencode") {
|
|
101
102
|
const obj = { type: "local", command: [command, ...args], enabled: true };
|
|
@@ -125,7 +126,7 @@ function bearerInterpolation(env, target) {
|
|
|
125
126
|
}
|
|
126
127
|
export function renderHermesMcpOps(mcp) {
|
|
127
128
|
if (mcp.transport === "stdio") {
|
|
128
|
-
const { command, args } =
|
|
129
|
+
const { command, args } = stdioLaunch(mcp);
|
|
129
130
|
const ops = [{ path: "command", value: command }];
|
|
130
131
|
if (args.length > 0)
|
|
131
132
|
ops.push({ path: "args", value: JSON.stringify(args) });
|
package/dist/signature.js
CHANGED
|
@@ -12,16 +12,19 @@ export const TRUSTED_KEYS = [
|
|
|
12
12
|
];
|
|
13
13
|
export const PUBLIC_KEY_B64 = TRUSTED_KEYS[0].publicKeyB64;
|
|
14
14
|
export function canonicalize(obj) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
15
|
+
return canonicalValue(obj, true);
|
|
16
|
+
}
|
|
17
|
+
function canonicalValue(value, isRoot) {
|
|
18
|
+
if (value === null || typeof value !== "object")
|
|
19
|
+
return JSON.stringify(value);
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
return `[${value.map((v) => canonicalValue(v, false)).join(",")}]`;
|
|
19
22
|
}
|
|
20
|
-
const o =
|
|
23
|
+
const o = value;
|
|
21
24
|
const keys = Object.keys(o)
|
|
22
|
-
.filter((k) => k
|
|
25
|
+
.filter((k) => !(isRoot && k === "signature"))
|
|
23
26
|
.sort();
|
|
24
|
-
return `{${keys.map((k) => `${JSON.stringify(k)}:${
|
|
27
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalValue(o[k], false)}`).join(",")}}`;
|
|
25
28
|
}
|
|
26
29
|
const MAX_MANIFEST_AGE_DAYS = 90;
|
|
27
30
|
export function verifyManifestSignature(manifest, options = {}) {
|
package/dist/targets.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { getAntigravityGeminiSkillsDir, getAntigravityMcpConfigPath, getAntigravitySkillsDir,
|
|
4
|
+
import { getAntigravityGeminiSkillsDir, getAntigravityMcpConfigPath, getAntigravitySkillsDir, getClaudeMcpConfigPath, getClaudeSkillsDir, getCodexConfigPath, getCodexSkillsDir, getCursorMcpPath, getCursorSkillsDir, getHermesConfigPath, getHermesHome, getHermesSkillsDir, getOpenCodeConfigPath, getOpenCodeSkillsDir, } from "./lib.js";
|
|
5
5
|
export const TARGETS = {
|
|
6
6
|
"claude-code": {
|
|
7
7
|
id: "claude-code",
|
|
@@ -9,7 +9,7 @@ export const TARGETS = {
|
|
|
9
9
|
mcpHttpTypeValue: "http",
|
|
10
10
|
skillsDir: getClaudeSkillsDir(),
|
|
11
11
|
skillFormat: "folder",
|
|
12
|
-
mcpConfigPath:
|
|
12
|
+
mcpConfigPath: getClaudeMcpConfigPath(),
|
|
13
13
|
mcpRootKey: "mcpServers",
|
|
14
14
|
mcpUrlKey: "url",
|
|
15
15
|
},
|