alegra-data-agent 0.2.7 → 0.2.9
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 +84 -0
- package/dist/installer.js +45 -0
- package/dist/main.js +0 -2
- package/dist/ui.js +1 -1
- package/package.json +3 -2
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";
|
|
@@ -156,6 +158,87 @@ function reportRogueMetabase(manifest) {
|
|
|
156
158
|
log(gray(" si no los necesitás, conviene quitarlos del cliente."));
|
|
157
159
|
log("");
|
|
158
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
|
+
}
|
|
159
242
|
export async function runStatus() {
|
|
160
243
|
const auth = readAuth();
|
|
161
244
|
const lockForTargets = readLockFile();
|
|
@@ -218,6 +301,7 @@ export async function runStatus() {
|
|
|
218
301
|
log("");
|
|
219
302
|
reportMcps(manifest, lock);
|
|
220
303
|
reportRogueMetabase(manifest);
|
|
304
|
+
reportClaudeAiDuplicates(manifest);
|
|
221
305
|
reportAccountConnectorOverlap(manifest);
|
|
222
306
|
const total = drift.toInstall.length +
|
|
223
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.9",
|
|
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.4.0",
|
|
23
23
|
"typescript": "7.0.2"
|
|
24
24
|
},
|
|
25
25
|
"repository": {
|
|
@@ -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 ."
|