alegra-data-agent 0.2.6 → 0.2.8
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/commands/status.js +91 -5
- package/dist/installer.js +45 -0
- package/dist/main.js +0 -2
- package/dist/ui.js +1 -1
- package/package.json +2 -1
package/dist/commands/status.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
2
4
|
import { AuthRequiredError, fetchManifest } from "../api.js";
|
|
3
5
|
import { readAuth } from "../auth.js";
|
|
4
6
|
import { bold, cyan, dim, gray, green, log, logError, logWarn, yellow } from "../colors.js";
|
|
@@ -101,13 +103,15 @@ function reportAccountConnectorOverlap(manifest) {
|
|
|
101
103
|
}
|
|
102
104
|
if (overlap.length === 0)
|
|
103
105
|
return;
|
|
104
|
-
logWarn(`Tenés ${overlap.length} connector(s) de tu cuenta que
|
|
106
|
+
logWarn(`Tenés ${overlap.length} connector(s) de tu cuenta con el mismo nombre que un MCP:`);
|
|
105
107
|
for (const o of overlap)
|
|
106
108
|
log(gray(` ${o}`));
|
|
107
|
-
log(gray("
|
|
108
|
-
log(gray("
|
|
109
|
-
log(gray("
|
|
110
|
-
log(gray(
|
|
109
|
+
log(gray(" Suelen ser el MISMO servidor visto dos veces, pero cada uno tiene"));
|
|
110
|
+
log(gray(" su propia autenticación, y un connector personalizado apunta a la"));
|
|
111
|
+
log(gray(" URL que escribió una persona: desde acá no se puede saber cuál es."));
|
|
112
|
+
log(gray(" Lo que importa: si el del agente no está autenticado, la respuesta"));
|
|
113
|
+
log(gray(" puede venir del connector sin que se note."));
|
|
114
|
+
log(gray(" Autenticá los del agente: /mcp → click en el servicio → Authenticate."));
|
|
111
115
|
log("");
|
|
112
116
|
}
|
|
113
117
|
export function findRogueMetabase(manifest, specs = Object.values(TARGETS)) {
|
|
@@ -154,6 +158,87 @@ function reportRogueMetabase(manifest) {
|
|
|
154
158
|
log(gray(" si no los necesitás, conviene quitarlos del cliente."));
|
|
155
159
|
log("");
|
|
156
160
|
}
|
|
161
|
+
function connectorUrlsFromCredentials(ourServerNames, credentialsPath = join(homedir(), ".claude", ".credentials.json")) {
|
|
162
|
+
const urls = new Set();
|
|
163
|
+
if (!existsSync(credentialsPath))
|
|
164
|
+
return urls;
|
|
165
|
+
try {
|
|
166
|
+
const raw = JSON.parse(readFileSync(credentialsPath, "utf-8"));
|
|
167
|
+
const entries = raw.mcpOAuth;
|
|
168
|
+
if (!entries || typeof entries !== "object")
|
|
169
|
+
return urls;
|
|
170
|
+
for (const [key, val] of Object.entries(entries)) {
|
|
171
|
+
const name = key.split("|")[0];
|
|
172
|
+
if (ourServerNames.has(name))
|
|
173
|
+
continue;
|
|
174
|
+
const url = val?.serverUrl;
|
|
175
|
+
if (typeof url === "string")
|
|
176
|
+
urls.add(url.trim().replace(/\/+$/, "").toLowerCase());
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
}
|
|
181
|
+
return urls;
|
|
182
|
+
}
|
|
183
|
+
export function findClaudeAiDuplicates(manifest, claudeJsonPath = TARGETS["claude-code"].mcpConfigPath) {
|
|
184
|
+
if (!existsSync(claudeJsonPath))
|
|
185
|
+
return [];
|
|
186
|
+
let cfg;
|
|
187
|
+
try {
|
|
188
|
+
cfg = JSON.parse(readFileSync(claudeJsonPath, "utf-8"));
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
const connectors = cfg.claudeAiMcpEverConnected;
|
|
194
|
+
if (!Array.isArray(connectors))
|
|
195
|
+
return [];
|
|
196
|
+
const localServers = cfg[TARGETS["claude-code"].mcpRootKey];
|
|
197
|
+
if (!localServers || typeof localServers !== "object")
|
|
198
|
+
return [];
|
|
199
|
+
const ourNames = new Set(Object.keys(localServers));
|
|
200
|
+
const connectorUrls = connectorUrlsFromCredentials(ourNames);
|
|
201
|
+
const norm = (u) => u.trim().replace(/\/+$/, "").toLowerCase();
|
|
202
|
+
const flat = (v) => v.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
203
|
+
const out = [];
|
|
204
|
+
for (const name of ourNames) {
|
|
205
|
+
const official = manifest.mcps?.[name]?.url;
|
|
206
|
+
if (!manifest.mcps?.[name])
|
|
207
|
+
continue;
|
|
208
|
+
const needle = flat(name);
|
|
209
|
+
for (const c of connectors) {
|
|
210
|
+
if (typeof c !== "string")
|
|
211
|
+
continue;
|
|
212
|
+
const hay = flat(c.replace(/^claude\.ai\s*/i, ""));
|
|
213
|
+
if (!hay.includes(needle))
|
|
214
|
+
continue;
|
|
215
|
+
out.push({
|
|
216
|
+
mcp: name,
|
|
217
|
+
connector: c,
|
|
218
|
+
sameUrl: typeof official === "string" && connectorUrls.has(norm(official)),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return out;
|
|
223
|
+
}
|
|
224
|
+
function reportClaudeAiDuplicates(manifest) {
|
|
225
|
+
const dupes = findClaudeAiDuplicates(manifest);
|
|
226
|
+
if (dupes.length === 0)
|
|
227
|
+
return;
|
|
228
|
+
logWarn(`Hay ${dupes.length} MCP(s) que tenés por dos vías: server local y conector de claude.ai.`);
|
|
229
|
+
for (const d of dupes) {
|
|
230
|
+
const marca = d.sameUrl
|
|
231
|
+
? " — mismo servidor (misma URL)"
|
|
232
|
+
: " — mismo nombre, URL sin verificar";
|
|
233
|
+
log(gray(` ${d.mcp} (local) · ${d.connector}${marca}`));
|
|
234
|
+
}
|
|
235
|
+
log(gray(" En Claude Code gana el local y el conector queda oculto, así que"));
|
|
236
|
+
log(gray(" hoy no se pisan — pero autenticás dos veces el mismo servidor."));
|
|
237
|
+
log(gray(" Si preferís el conector (una sola auth, y sirve también en Desktop,"));
|
|
238
|
+
log(gray(" claude.ai, Cowork y móvil): claude mcp remove <nombre>."));
|
|
239
|
+
log(gray(" Verificá antes que apunte a la MISMA URL que el oficial."));
|
|
240
|
+
log("");
|
|
241
|
+
}
|
|
157
242
|
export async function runStatus() {
|
|
158
243
|
const auth = readAuth();
|
|
159
244
|
const lockForTargets = readLockFile();
|
|
@@ -216,6 +301,7 @@ export async function runStatus() {
|
|
|
216
301
|
log("");
|
|
217
302
|
reportMcps(manifest, lock);
|
|
218
303
|
reportRogueMetabase(manifest);
|
|
304
|
+
reportClaudeAiDuplicates(manifest);
|
|
219
305
|
reportAccountConnectorOverlap(manifest);
|
|
220
306
|
const total = drift.toInstall.length +
|
|
221
307
|
drift.toUpdate.length +
|
package/dist/installer.js
CHANGED
|
@@ -41,6 +41,7 @@ function mcpConfigHash(mcp) {
|
|
|
41
41
|
env: mcp.env ?? null,
|
|
42
42
|
type: mcp.auth.type,
|
|
43
43
|
tokenEnv: mcp.auth.tokenEnv ?? null,
|
|
44
|
+
...(mcp.delivery ? { delivery: mcp.delivery } : {}),
|
|
44
45
|
});
|
|
45
46
|
return createHash("sha256").update(salient).digest("hex");
|
|
46
47
|
}
|
|
@@ -624,6 +625,43 @@ export function installMcp(mcp, opts = {}) {
|
|
|
624
625
|
const lock = readLockFile();
|
|
625
626
|
if (!lock.mcps)
|
|
626
627
|
lock.mcps = {};
|
|
628
|
+
if (mcp.delivery === "connector") {
|
|
629
|
+
const hint = mcp.connectorHint ?? "se habilita como conector de la cuenta, no como server local";
|
|
630
|
+
const stale = lock.mcps[mcp.name];
|
|
631
|
+
if (!stale)
|
|
632
|
+
return { name: mcp.name, kind: "mcp", status: "skipped", error: hint };
|
|
633
|
+
const staleTargets = (stale.targets ?? ["claude-code"]);
|
|
634
|
+
if (opts.dryRun) {
|
|
635
|
+
return {
|
|
636
|
+
name: mcp.name,
|
|
637
|
+
kind: "mcp",
|
|
638
|
+
status: "skipped",
|
|
639
|
+
targets: staleTargets,
|
|
640
|
+
error: `${hint} (se retirará la entry local)`,
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
try {
|
|
644
|
+
for (const tId of staleTargets)
|
|
645
|
+
unregisterMcpInTarget(mcp.name, TARGETS[tId]);
|
|
646
|
+
}
|
|
647
|
+
catch (err) {
|
|
648
|
+
return {
|
|
649
|
+
name: mcp.name,
|
|
650
|
+
kind: "mcp",
|
|
651
|
+
status: "failed",
|
|
652
|
+
error: err instanceof Error ? err.message : String(err),
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
delete lock.mcps[mcp.name];
|
|
656
|
+
writeLockFile(lock);
|
|
657
|
+
return {
|
|
658
|
+
name: mcp.name,
|
|
659
|
+
kind: "mcp",
|
|
660
|
+
status: "skipped",
|
|
661
|
+
targets: staleTargets,
|
|
662
|
+
error: `entry local retirada — ${hint}`,
|
|
663
|
+
};
|
|
664
|
+
}
|
|
627
665
|
const wantedTargets = resolveTargets(opts).filter((t) => !mcp.targets || mcp.targets.includes(t.id));
|
|
628
666
|
const targetIds = wantedTargets.map((t) => t.id);
|
|
629
667
|
if (targetIds.length === 0) {
|
|
@@ -858,6 +896,8 @@ export function detectDrift(manifest) {
|
|
|
858
896
|
};
|
|
859
897
|
const manifestMcps = manifest.mcps ?? {};
|
|
860
898
|
for (const name of Object.keys(manifestMcps)) {
|
|
899
|
+
if (manifestMcps[name].delivery === "connector")
|
|
900
|
+
continue;
|
|
861
901
|
const prevMcp = lock.mcps?.[name];
|
|
862
902
|
if (!prevMcp) {
|
|
863
903
|
mcps.toInstall.push(name);
|
|
@@ -916,6 +956,11 @@ export function summarize(results) {
|
|
|
916
956
|
logError(`${r.name}: ${r.error}`);
|
|
917
957
|
}
|
|
918
958
|
}
|
|
959
|
+
for (const r of results) {
|
|
960
|
+
if (r.status === "skipped" && r.error) {
|
|
961
|
+
process.stdout.write(yellow(` - ${r.name}: ${r.error}\n`));
|
|
962
|
+
}
|
|
963
|
+
}
|
|
919
964
|
process.stdout.write(dim(` Lock: ${getLockFilePath()}\n`));
|
|
920
965
|
process.stdout.write("\n");
|
|
921
966
|
}
|
package/dist/main.js
CHANGED
|
@@ -118,7 +118,6 @@ async function main() {
|
|
|
118
118
|
break;
|
|
119
119
|
case "install":
|
|
120
120
|
exitCode = await runInstall({
|
|
121
|
-
yes: args.flags.yes,
|
|
122
121
|
dryRun: args.flags.dryRun,
|
|
123
122
|
force: args.flags.force,
|
|
124
123
|
only: args.only.length > 0 ? args.only : undefined,
|
|
@@ -129,7 +128,6 @@ async function main() {
|
|
|
129
128
|
break;
|
|
130
129
|
case "update":
|
|
131
130
|
exitCode = await runUpdate({
|
|
132
|
-
yes: args.flags.yes,
|
|
133
131
|
dryRun: args.flags.dryRun,
|
|
134
132
|
background: args.flags.background,
|
|
135
133
|
targets: args.targets.length > 0 ? args.targets : undefined,
|
package/dist/ui.js
CHANGED
|
@@ -56,7 +56,7 @@ ${bold("Comandos:")}
|
|
|
56
56
|
version Muestra la versión
|
|
57
57
|
|
|
58
58
|
${bold("Opciones:")}
|
|
59
|
-
-y, --yes No pedir confirmación
|
|
59
|
+
-y, --yes No pedir confirmación — solo uninstall pregunta
|
|
60
60
|
--dry-run Mostrar qué se haría, sin tocar archivos
|
|
61
61
|
--only=a,b,c Instalar solo esas skills/MCPs
|
|
62
62
|
--target=NAME Restringir al cliente NAME (claude-code|cursor|opencode|antigravity|codex)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alegra-data-agent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
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",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"test:all": "pnpm run test && pnpm run test:security",
|
|
48
48
|
"eval:run": "node tests/evals/run-phase2.mjs",
|
|
49
49
|
"verify:clients": "node scripts/verify-clients.mjs",
|
|
50
|
+
"pack:web": "node --experimental-strip-types --disable-warning=ExperimentalWarning scripts/pack-skills-web.mjs",
|
|
50
51
|
"lint": "pnpm dlx @biomejs/biome@1.9.4 check .",
|
|
51
52
|
"lint:fix": "pnpm dlx @biomejs/biome@1.9.4 check --write .",
|
|
52
53
|
"format": "pnpm dlx @biomejs/biome@1.9.4 format --write ."
|