alegra-data-agent 0.2.3 → 0.2.5
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/api.js +83 -16
- package/dist/auth.js +23 -8
- package/dist/autoupdate.js +216 -0
- package/dist/commands/hook.js +41 -45
- package/dist/commands/install.js +25 -16
- package/dist/commands/login.js +1 -1
- package/dist/commands/status.js +125 -5
- package/dist/commands/uninstall.js +16 -4
- package/dist/commands/update.js +56 -8
- package/dist/installer.js +247 -72
- package/dist/lib.js +58 -28
- package/dist/main.js +4 -0
- package/dist/render.js +3 -0
- package/dist/signature.js +17 -5
- package/dist/targets.js +10 -7
- package/dist/ui.js +2 -2
- package/package.json +12 -9
package/dist/commands/install.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { AuthRequiredError } from "../api.js";
|
|
2
2
|
import { hasValidAuth, readAuth } from "../auth.js";
|
|
3
|
+
import { installAutoUpdate } from "../autoupdate.js";
|
|
3
4
|
import { bold, cyan, dim, gray, green, log, logError, red, yellow } from "../colors.js";
|
|
4
5
|
import { installAll } from "../installer.js";
|
|
5
|
-
import { COMMAND_NAME, formatBytes,
|
|
6
|
-
import { runHookInstall } from "./hook.js";
|
|
6
|
+
import { COMMAND_NAME, formatBytes, getPackageVersion, getTargetSkillsDir } from "../lib.js";
|
|
7
7
|
import { runLogin } from "./login.js";
|
|
8
8
|
const BANNER_WIDTH = 58;
|
|
9
9
|
const SHORT_DESCRIPTIONS = {
|
|
@@ -51,6 +51,10 @@ function shortDesc(name, manifestDesc) {
|
|
|
51
51
|
const oneLine = manifestDesc.split(/\.|\n/)[0].trim();
|
|
52
52
|
return oneLine.length > 55 ? `${oneLine.slice(0, 54)}…` : oneLine;
|
|
53
53
|
}
|
|
54
|
+
function formatDestinos(targets) {
|
|
55
|
+
const dirs = targets.map((t) => getTargetSkillsDir(t));
|
|
56
|
+
return dirs.length > 0 ? dirs.join(" · ") : "(ninguno)";
|
|
57
|
+
}
|
|
54
58
|
function printResultRow(result, manifest) {
|
|
55
59
|
const isSkill = result.kind === "skill";
|
|
56
60
|
const entry = isSkill ? manifest.skills[result.name] : manifest.mcps?.[result.name];
|
|
@@ -81,7 +85,7 @@ function printResultRow(result, manifest) {
|
|
|
81
85
|
const padName = result.name.padEnd(22);
|
|
82
86
|
log(` ${icon} ${kindBadge}${bold(padName)} ${gray(`· ${desc}`)}`);
|
|
83
87
|
}
|
|
84
|
-
function printSummary(results, manifest) {
|
|
88
|
+
function printSummary(results, manifest, targets) {
|
|
85
89
|
const installed = results.filter((r) => r.status === "installed").length;
|
|
86
90
|
const updated = results.filter((r) => r.status === "updated").length;
|
|
87
91
|
const unchanged = results.filter((r) => r.status === "unchanged").length;
|
|
@@ -112,7 +116,7 @@ function printSummary(results, manifest) {
|
|
|
112
116
|
if (failed > 0)
|
|
113
117
|
parts.push(red(`${failed} fallidas`));
|
|
114
118
|
log(` ${green("✓")} ${bold(parts.join(gray(" · ")))}${gray(` · ${totalFiles} archivos · ${formatBytes(totalBytes)}`)}`);
|
|
115
|
-
log(` ${green("✓")} ${gray("destino:")} ${
|
|
119
|
+
log(` ${green("✓")} ${gray("destino:")} ${formatDestinos(targets)}`);
|
|
116
120
|
log("");
|
|
117
121
|
for (const r of results.filter((x) => x.status === "failed")) {
|
|
118
122
|
logError(`${r.name}: ${r.error}`);
|
|
@@ -120,7 +124,7 @@ function printSummary(results, manifest) {
|
|
|
120
124
|
}
|
|
121
125
|
function printNextSteps(noHook) {
|
|
122
126
|
section("Ya estás listo");
|
|
123
|
-
log(` ${bold(yellow("1."))} ${bold("Reiniciá tu cliente")} ${gray("(Claude Code, Cursor, OpenCode o
|
|
127
|
+
log(` ${bold(yellow("1."))} ${bold("Reiniciá tu cliente")} ${gray("(Claude Code, Cursor, OpenCode, Antigravity o Codex)")}.`);
|
|
124
128
|
log(` ${bold(yellow("2."))} ${bold("Hacele tu pregunta en español.")}`);
|
|
125
129
|
log(` ${gray("Ejemplo:")} ${cyan('"cuántos nuevos clientes en mayo en Colombia?"')}`);
|
|
126
130
|
log("");
|
|
@@ -137,7 +141,7 @@ function printNextSteps(noHook) {
|
|
|
137
141
|
log(` ${dim("$")} ${bold(`${COMMAND_NAME} hook uninstall`)} ${gray("apagar aviso")}`);
|
|
138
142
|
}
|
|
139
143
|
log("");
|
|
140
|
-
log(` ${dim("Docs · ")}${cyan("github.com/
|
|
144
|
+
log(` ${dim("Docs · ")}${cyan("github.com/Alegra-Team/data-agent-selfservice")}`);
|
|
141
145
|
log("");
|
|
142
146
|
}
|
|
143
147
|
function isClaudeCode() {
|
|
@@ -176,7 +180,10 @@ export async function runInstall(opts = {}) {
|
|
|
176
180
|
const msg = err instanceof Error ? err.message : String(err);
|
|
177
181
|
logError(msg);
|
|
178
182
|
if (msg.includes("EACCES") || msg.includes("permission denied")) {
|
|
179
|
-
|
|
183
|
+
const dirs = opts.targets?.length
|
|
184
|
+
? formatDestinos(opts.targets)
|
|
185
|
+
: "las carpetas de skills de tus clientes AI";
|
|
186
|
+
log(dim(` Sin permisos para escribir. Verifica permisos de ${dirs}`));
|
|
180
187
|
}
|
|
181
188
|
else if (msg.includes("ENOSPC") || msg.includes("no space")) {
|
|
182
189
|
log(dim(" No hay espacio en disco. Libera espacio y reintenta."));
|
|
@@ -216,32 +223,34 @@ export async function runInstall(opts = {}) {
|
|
|
216
223
|
return 1;
|
|
217
224
|
}
|
|
218
225
|
if (compact) {
|
|
219
|
-
printCompactSummary(result.results, result.manifest, auth?.email);
|
|
226
|
+
printCompactSummary(result.results, result.manifest, result.targets, auth?.email);
|
|
220
227
|
}
|
|
221
228
|
else {
|
|
222
229
|
for (const r of result.results) {
|
|
223
230
|
printResultRow(r, result.manifest);
|
|
224
231
|
}
|
|
225
|
-
printSummary(result.results, result.manifest);
|
|
232
|
+
printSummary(result.results, result.manifest, result.targets);
|
|
226
233
|
}
|
|
227
234
|
const hasFails = result.results.some((r) => r.status === "failed");
|
|
228
|
-
if (hasFails)
|
|
229
|
-
return 1;
|
|
230
235
|
if (opts.dryRun)
|
|
231
|
-
return 0;
|
|
236
|
+
return hasFails ? 1 : 0;
|
|
232
237
|
if (!opts.noHook) {
|
|
233
238
|
if (!compact)
|
|
234
|
-
log(` ${gray("
|
|
235
|
-
|
|
239
|
+
log(` ${gray("Configurando auto-update por cliente…")}`);
|
|
240
|
+
for (const t of result.targets) {
|
|
241
|
+
installAutoUpdate(t, opts.hookMode ?? "auto");
|
|
242
|
+
}
|
|
236
243
|
if (!compact)
|
|
237
244
|
log("");
|
|
238
245
|
}
|
|
246
|
+
if (hasFails)
|
|
247
|
+
return 1;
|
|
239
248
|
if (!compact) {
|
|
240
249
|
printNextSteps(opts.noHook ?? false);
|
|
241
250
|
}
|
|
242
251
|
return 0;
|
|
243
252
|
}
|
|
244
|
-
function printCompactSummary(results, manifest, email) {
|
|
253
|
+
function printCompactSummary(results, manifest, targets, email) {
|
|
245
254
|
const ok = results.filter((r) => r.status === "installed" || r.status === "updated" || r.status === "unchanged");
|
|
246
255
|
const failed = results.filter((r) => r.status === "failed");
|
|
247
256
|
const installed = results.filter((r) => r.status === "installed").length;
|
|
@@ -273,7 +282,7 @@ function printCompactSummary(results, manifest, email) {
|
|
|
273
282
|
.map((r) => r.name)
|
|
274
283
|
.sort()
|
|
275
284
|
.join(", "))}`);
|
|
276
|
-
log(` ${gray("destino:")} ${cyan(
|
|
285
|
+
log(` ${gray("destino:")} ${cyan(formatDestinos(targets))}${gray(" · ")}${bold(yellow("reinicia tu cliente para activar skills y MCPs"))}`);
|
|
277
286
|
if (unchanged > 0 && installed === 0 && updated === 0) {
|
|
278
287
|
log(` ${dim(`(${unchanged} sin cambios desde la última instalación)`)}`);
|
|
279
288
|
}
|
package/dist/commands/login.js
CHANGED
|
@@ -19,7 +19,7 @@ export async function runLogin(opts = {}) {
|
|
|
19
19
|
}
|
|
20
20
|
log(` Gateway: ${dim(GATEWAY_URL)}`);
|
|
21
21
|
try {
|
|
22
|
-
const record = await performBrowserLogin();
|
|
22
|
+
const record = await performBrowserLogin(opts.timeoutMs);
|
|
23
23
|
writeAuth(record);
|
|
24
24
|
log("");
|
|
25
25
|
log(` ${green("✓")} Autenticado como ${bold(record.email)}.`);
|
package/dist/commands/status.js
CHANGED
|
@@ -1,19 +1,128 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
1
2
|
import { AuthRequiredError, fetchManifest } from "../api.js";
|
|
2
3
|
import { readAuth } from "../auth.js";
|
|
3
4
|
import { bold, cyan, dim, gray, green, log, logError, logWarn, yellow } from "../colors.js";
|
|
4
|
-
import { detectDrift } from "../installer.js";
|
|
5
|
-
import { COMMAND_NAME, GATEWAY_URL, getClaudeSkillsDir, getLockFilePath, getPackageVersion, readLockFile, } from "../lib.js";
|
|
5
|
+
import { detectDrift, inspectMcpInTarget } from "../installer.js";
|
|
6
|
+
import { COMMAND_NAME, GATEWAY_URL, getAutoUpdateLogPath, getClaudeSkillsDir, getLockFilePath, getPackageVersion, getTargetSkillsDir, readLockFile, } from "../lib.js";
|
|
7
|
+
import { TARGETS } from "../targets.js";
|
|
8
|
+
function reportAutoUpdateLog() {
|
|
9
|
+
const logPath = getAutoUpdateLogPath();
|
|
10
|
+
if (!existsSync(logPath))
|
|
11
|
+
return;
|
|
12
|
+
try {
|
|
13
|
+
const lines = readFileSync(logPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
14
|
+
if (lines.length === 0)
|
|
15
|
+
return;
|
|
16
|
+
logWarn(`El auto-update no pudo correr ${lines.length} vez/veces en un cliente GUI — última: "${lines[lines.length - 1]}". Agente: corré ${bold(`${COMMAND_NAME} update`)} (actualiza ya mismo y limpia este aviso); si reaparece, el cliente GUI no encuentra pnpm en su PATH y hay que reinstalar con ${bold(`${COMMAND_NAME} install`)} desde una terminal con pnpm disponible.`);
|
|
17
|
+
log("");
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function reportMcps(manifest, lock) {
|
|
23
|
+
const mcps = manifest.mcps ?? {};
|
|
24
|
+
const names = Object.keys(mcps);
|
|
25
|
+
if (names.length === 0)
|
|
26
|
+
return;
|
|
27
|
+
log(bold(" MCPs:"));
|
|
28
|
+
for (const name of names) {
|
|
29
|
+
const entry = mcps[name];
|
|
30
|
+
const targets = (lock.mcps?.[name]?.targets ?? []);
|
|
31
|
+
if (targets.length === 0) {
|
|
32
|
+
log(` ${cyan("+")} ${name} ${gray("(disponible, sin instalar)")}`);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const ok = [];
|
|
36
|
+
const missing = [];
|
|
37
|
+
const modified = [];
|
|
38
|
+
for (const t of targets) {
|
|
39
|
+
const label = TARGETS[t].label;
|
|
40
|
+
const state = inspectMcpInTarget(entry, TARGETS[t]);
|
|
41
|
+
if (state === "ok")
|
|
42
|
+
ok.push(label);
|
|
43
|
+
else if (state === "missing")
|
|
44
|
+
missing.push(label);
|
|
45
|
+
else
|
|
46
|
+
modified.push(label);
|
|
47
|
+
}
|
|
48
|
+
const notes = [];
|
|
49
|
+
if (ok.length > 0)
|
|
50
|
+
notes.push(gray(`ok en ${ok.join(", ")}`));
|
|
51
|
+
if (modified.length > 0)
|
|
52
|
+
notes.push(dim(`editado a mano en ${modified.join(", ")}`));
|
|
53
|
+
if (missing.length > 0)
|
|
54
|
+
notes.push(yellow(`falta en ${missing.join(", ")}`));
|
|
55
|
+
const icon = missing.length > 0 ? yellow("✗") : modified.length > 0 ? dim("~") : green("✓");
|
|
56
|
+
log(` ${icon} ${name} ${gray("·")} ${notes.join(gray(" · "))}`);
|
|
57
|
+
}
|
|
58
|
+
log("");
|
|
59
|
+
}
|
|
60
|
+
export function findRogueMetabase(manifest, specs = Object.values(TARGETS)) {
|
|
61
|
+
const official = manifest.mcps?.metabase?.url;
|
|
62
|
+
if (!official)
|
|
63
|
+
return [];
|
|
64
|
+
const norm = (u) => u.trim().replace(/\/+$/, "").toLowerCase();
|
|
65
|
+
const officialNorm = norm(official);
|
|
66
|
+
const found = [];
|
|
67
|
+
for (const spec of specs) {
|
|
68
|
+
if (!spec.mcpConfigPath.endsWith(".json"))
|
|
69
|
+
continue;
|
|
70
|
+
if (!existsSync(spec.mcpConfigPath))
|
|
71
|
+
continue;
|
|
72
|
+
try {
|
|
73
|
+
const cfg = JSON.parse(readFileSync(spec.mcpConfigPath, "utf-8"));
|
|
74
|
+
const servers = cfg[spec.mcpRootKey];
|
|
75
|
+
if (!servers || typeof servers !== "object")
|
|
76
|
+
continue;
|
|
77
|
+
for (const [name, def] of Object.entries(servers)) {
|
|
78
|
+
if (!/metabase/i.test(name))
|
|
79
|
+
continue;
|
|
80
|
+
const url = def?.[spec.mcpUrlKey];
|
|
81
|
+
if (typeof url === "string" && norm(url) === officialNorm)
|
|
82
|
+
continue;
|
|
83
|
+
found.push(`${spec.label} → ${name}${typeof url === "string" ? ` (${url})` : ""}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return found;
|
|
90
|
+
}
|
|
91
|
+
function reportRogueMetabase(manifest) {
|
|
92
|
+
const official = manifest.mcps?.metabase?.url;
|
|
93
|
+
if (!official)
|
|
94
|
+
return;
|
|
95
|
+
const found = findRogueMetabase(manifest);
|
|
96
|
+
if (found.length === 0)
|
|
97
|
+
return;
|
|
98
|
+
logWarn(`Hay ${found.length} MCP(s) de Metabase que NO son el oficial de Alegra: ${found.join(" · ")}`);
|
|
99
|
+
log(gray(` El oficial es ${official}. Los otros son otra fuente y dan otros números.`));
|
|
100
|
+
log(gray(" Las skills tienen instrucción de no usarlos, pero eso no los bloquea:"));
|
|
101
|
+
log(gray(" si no los necesitás, conviene quitarlos del cliente."));
|
|
102
|
+
log("");
|
|
103
|
+
}
|
|
6
104
|
export async function runStatus() {
|
|
7
105
|
const auth = readAuth();
|
|
106
|
+
const lockForTargets = readLockFile();
|
|
107
|
+
const installedTargets = [
|
|
108
|
+
...new Set([
|
|
109
|
+
...Object.values(lockForTargets.skills).flatMap((s) => s.targets ?? []),
|
|
110
|
+
...Object.values(lockForTargets.mcps ?? {}).flatMap((m) => m.targets ?? []),
|
|
111
|
+
]),
|
|
112
|
+
];
|
|
113
|
+
const destino = installedTargets.length
|
|
114
|
+
? installedTargets.map((t) => getTargetSkillsDir(t)).join(" · ")
|
|
115
|
+
: getClaudeSkillsDir();
|
|
8
116
|
log("");
|
|
9
117
|
log(bold(cyan(" Estado")));
|
|
10
118
|
log(gray(` ${"─".repeat(70)}`));
|
|
11
119
|
log(` ${gray("Paquete:")} ${bold(COMMAND_NAME)} v${getPackageVersion()}`);
|
|
12
120
|
log(` ${gray("Gateway:")} ${GATEWAY_URL}`);
|
|
13
121
|
log(` ${gray("Autenticado:")} ${auth ? green(auth.email) : gray("no")}`);
|
|
14
|
-
log(` ${gray("Destino:")} ${
|
|
122
|
+
log(` ${gray("Destino:")} ${destino}`);
|
|
15
123
|
log(` ${gray("Lock file:")} ${getLockFilePath()}`);
|
|
16
124
|
log("");
|
|
125
|
+
reportAutoUpdateLog();
|
|
17
126
|
if (!auth) {
|
|
18
127
|
logWarn(`Inicia sesión con: ${bold(`${COMMAND_NAME} login`)}`);
|
|
19
128
|
log("");
|
|
@@ -37,6 +146,12 @@ export async function runStatus() {
|
|
|
37
146
|
}
|
|
38
147
|
const lock = readLockFile();
|
|
39
148
|
const drift = detectDrift(manifest);
|
|
149
|
+
const cliVersion = getPackageVersion();
|
|
150
|
+
if (manifest.productVersion && manifest.productVersion !== cliVersion) {
|
|
151
|
+
log(` ${gray("Manifest:")} generado por v${manifest.productVersion} ${gray(`· tu CLI corre v${cliVersion}`)}`);
|
|
152
|
+
log(gray(" No es un error: el gateway y npm se despliegan por separado."));
|
|
153
|
+
log("");
|
|
154
|
+
}
|
|
40
155
|
log(bold(" Skills:"));
|
|
41
156
|
log(` ${green("✓")} ${drift.unchanged.length} al día`);
|
|
42
157
|
if (drift.toInstall.length > 0)
|
|
@@ -46,9 +161,14 @@ export async function runStatus() {
|
|
|
46
161
|
if (drift.orphaned.length > 0)
|
|
47
162
|
log(` ${dim("·")} ${drift.orphaned.length} huérfanas: ${drift.orphaned.join(", ")}`);
|
|
48
163
|
log("");
|
|
49
|
-
|
|
164
|
+
reportMcps(manifest, lock);
|
|
165
|
+
reportRogueMetabase(manifest);
|
|
166
|
+
const total = drift.toInstall.length +
|
|
167
|
+
drift.toUpdate.length +
|
|
168
|
+
drift.mcps.toInstall.length +
|
|
169
|
+
drift.mcps.toUpdate.length;
|
|
50
170
|
if (total > 0) {
|
|
51
|
-
logWarn(`Hay ${total}
|
|
171
|
+
logWarn(`Hay ${total} elemento(s) (skills + MCPs) por actualizar. Agente: ejecutá vos mismo ${bold(`pnpm dlx ${COMMAND_NAME} update --yes`)} — el usuario no corre comandos.`);
|
|
52
172
|
}
|
|
53
173
|
else if (Object.keys(lock.skills).length === 0) {
|
|
54
174
|
logWarn(`Aún no hay skills instaladas. Agente: ejecutá vos mismo ${bold(`pnpm dlx ${COMMAND_NAME}`)} — el usuario no corre comandos.`);
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { removeAutoUpdate } from "../autoupdate.js";
|
|
4
5
|
import { dim, gray, log, logOk, logWarn, red } from "../colors.js";
|
|
5
6
|
import { uninstallAll } from "../installer.js";
|
|
6
7
|
import { readLockFile } from "../lib.js";
|
|
8
|
+
import { TARGETS } from "../targets.js";
|
|
7
9
|
import { banner, confirm } from "../ui.js";
|
|
8
10
|
export async function runUninstall(opts = {}) {
|
|
9
11
|
banner();
|
|
10
12
|
const lock = readLockFile();
|
|
11
13
|
const skills = Object.keys(lock.skills);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
+
const mcps = Object.keys(lock.mcps ?? {});
|
|
15
|
+
if (skills.length === 0 && mcps.length === 0) {
|
|
16
|
+
logWarn("No hay skills ni MCPs instalados por este paquete.");
|
|
17
|
+
if (!opts.dryRun) {
|
|
18
|
+
for (const t of Object.keys(TARGETS))
|
|
19
|
+
removeAutoUpdate(t);
|
|
20
|
+
}
|
|
14
21
|
return 0;
|
|
15
22
|
}
|
|
16
|
-
log(red(` Vas a eliminar ${skills.length} skill(s):`));
|
|
23
|
+
log(red(` Vas a eliminar ${skills.length} skill(s) y ${mcps.length} MCP(s):`));
|
|
17
24
|
for (const s of skills)
|
|
18
25
|
log(gray(` - ${s}`));
|
|
26
|
+
for (const m of mcps)
|
|
27
|
+
log(gray(` - ${m} (MCP)`));
|
|
19
28
|
log("");
|
|
20
29
|
if (!opts.yes && !opts.dryRun) {
|
|
21
30
|
const ok = await confirm("¿Continuar?", false);
|
|
@@ -25,8 +34,11 @@ export async function runUninstall(opts = {}) {
|
|
|
25
34
|
}
|
|
26
35
|
}
|
|
27
36
|
const results = uninstallAll({ dryRun: opts.dryRun });
|
|
28
|
-
if (!opts.dryRun)
|
|
37
|
+
if (!opts.dryRun) {
|
|
38
|
+
for (const t of Object.keys(TARGETS))
|
|
39
|
+
removeAutoUpdate(t);
|
|
29
40
|
removeNpmrcCooldownExclude();
|
|
41
|
+
}
|
|
30
42
|
log("");
|
|
31
43
|
if (opts.dryRun) {
|
|
32
44
|
logOk(`${results.length} skill(s) se eliminarían (dry-run, nada borrado).`);
|
package/dist/commands/update.js
CHANGED
|
@@ -1,16 +1,45 @@
|
|
|
1
|
+
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
1
3
|
import { AuthRequiredError, fetchManifest } from "../api.js";
|
|
2
4
|
import { hasValidAuth } from "../auth.js";
|
|
5
|
+
import { refreshAutoUpdate } from "../autoupdate.js";
|
|
3
6
|
import { cyan, dim, gray, green, log, logError, yellow } from "../colors.js";
|
|
4
7
|
import { detectDrift, installAll, summarize } from "../installer.js";
|
|
8
|
+
import { getAutoUpdateLogPath, getLastCheckPath } from "../lib.js";
|
|
5
9
|
import { banner } from "../ui.js";
|
|
6
|
-
import { refreshHookCommand } from "./hook.js";
|
|
7
10
|
import { runLogin } from "./login.js";
|
|
11
|
+
const BACKGROUND_COOLDOWN_MS = 15 * 60_000;
|
|
12
|
+
const BACKGROUND_LOGIN_TIMEOUT_MS = 90_000;
|
|
13
|
+
function checkedRecently() {
|
|
14
|
+
try {
|
|
15
|
+
const raw = readFileSync(getLastCheckPath(), "utf-8");
|
|
16
|
+
const at = Date.parse(JSON.parse(raw).checkedAt ?? "");
|
|
17
|
+
if (Number.isNaN(at))
|
|
18
|
+
return false;
|
|
19
|
+
const age = Date.now() - at;
|
|
20
|
+
return age >= 0 && age < BACKGROUND_COOLDOWN_MS;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function markChecked() {
|
|
27
|
+
try {
|
|
28
|
+
const path = getLastCheckPath();
|
|
29
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
30
|
+
writeFileSync(path, `${JSON.stringify({ checkedAt: new Date().toISOString() }, null, 2)}\n`);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
}
|
|
34
|
+
}
|
|
8
35
|
export async function runUpdate(opts = {}) {
|
|
36
|
+
if (opts.background && !opts.dryRun && checkedRecently())
|
|
37
|
+
return 0;
|
|
9
38
|
banner();
|
|
10
39
|
if (!hasValidAuth()) {
|
|
11
|
-
const code = await runLogin({});
|
|
40
|
+
const code = await runLogin(opts.background ? { timeoutMs: BACKGROUND_LOGIN_TIMEOUT_MS } : {});
|
|
12
41
|
if (code !== 0)
|
|
13
|
-
return code;
|
|
42
|
+
return opts.background ? 0 : code;
|
|
14
43
|
}
|
|
15
44
|
let reauthed = false;
|
|
16
45
|
for (;;) {
|
|
@@ -51,7 +80,11 @@ async function runUpdateOnce(opts) {
|
|
|
51
80
|
if (pending.length === 0) {
|
|
52
81
|
log(green(" ✓ Todo al día (skills + MCPs).\n"));
|
|
53
82
|
if (!opts.dryRun)
|
|
54
|
-
|
|
83
|
+
markChecked();
|
|
84
|
+
if (!opts.dryRun) {
|
|
85
|
+
refreshAutoUpdate();
|
|
86
|
+
clearAutoUpdateLog();
|
|
87
|
+
}
|
|
55
88
|
return 0;
|
|
56
89
|
}
|
|
57
90
|
log(cyan(` → ${pending.length} elemento(s) por actualizar:\n`));
|
|
@@ -61,20 +94,35 @@ async function runUpdateOnce(opts) {
|
|
|
61
94
|
log(` ${yellow("↻")} ${name} ${gray("(skill nueva versión)")}`);
|
|
62
95
|
for (const name of drift.mcps.toInstall)
|
|
63
96
|
log(` ${cyan("+")} ${name} ${gray("(MCP nuevo)")}`);
|
|
64
|
-
for (const name of drift.mcps.toUpdate)
|
|
65
|
-
|
|
97
|
+
for (const name of drift.mcps.toUpdate) {
|
|
98
|
+
const motivo = drift.mcps.missing.includes(name)
|
|
99
|
+
? "(MCP ausente en el cliente — se repone)"
|
|
100
|
+
: "(MCP actualizado)";
|
|
101
|
+
log(` ${yellow("↻")} ${name} ${gray(motivo)}`);
|
|
102
|
+
}
|
|
66
103
|
log("");
|
|
67
104
|
if (opts.dryRun) {
|
|
68
105
|
log(dim(" Modo dry-run: no se modificará nada.\n"));
|
|
69
106
|
return 0;
|
|
70
107
|
}
|
|
71
|
-
const result = await installAll({ only: pending, targets: opts.targets
|
|
108
|
+
const result = await installAll({ only: pending, targets: opts.targets });
|
|
72
109
|
if ("error" in result) {
|
|
73
110
|
logError(result.error);
|
|
74
111
|
return 1;
|
|
75
112
|
}
|
|
76
113
|
summarize(result.results);
|
|
77
|
-
|
|
114
|
+
refreshAutoUpdate();
|
|
78
115
|
const failed = result.results.some((r) => r.status === "failed");
|
|
116
|
+
if (!failed) {
|
|
117
|
+
clearAutoUpdateLog();
|
|
118
|
+
markChecked();
|
|
119
|
+
}
|
|
79
120
|
return failed ? 1 : 0;
|
|
80
121
|
}
|
|
122
|
+
function clearAutoUpdateLog() {
|
|
123
|
+
try {
|
|
124
|
+
rmSync(getAutoUpdateLogPath(), { force: true });
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
}
|
|
128
|
+
}
|