alegra-data-agent 0.2.3 → 0.2.4
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 +10 -3
- package/dist/autoupdate.js +216 -0
- package/dist/commands/hook.js +30 -16
- package/dist/commands/install.js +24 -15
- package/dist/commands/status.js +28 -2
- package/dist/commands/uninstall.js +16 -4
- package/dist/commands/update.js +17 -4
- package/dist/installer.js +99 -47
- package/dist/lib.js +32 -3
- package/dist/ui.js +1 -1
- package/package.json +1 -1
package/dist/api.js
CHANGED
|
@@ -34,6 +34,7 @@ function userAgent() {
|
|
|
34
34
|
function sleep(ms) {
|
|
35
35
|
return new Promise((r) => setTimeout(r, ms));
|
|
36
36
|
}
|
|
37
|
+
let rateLimitCooldownUntil = 0;
|
|
37
38
|
async function authedFetch(path, opts = {}) {
|
|
38
39
|
const auth = readAuth();
|
|
39
40
|
if (!auth) {
|
|
@@ -49,6 +50,9 @@ async function authedFetch(path, opts = {}) {
|
|
|
49
50
|
let attempt = 0;
|
|
50
51
|
const delays = [250, 750, 2250];
|
|
51
52
|
while (true) {
|
|
53
|
+
const pending = rateLimitCooldownUntil - Date.now();
|
|
54
|
+
if (pending > 0)
|
|
55
|
+
await sleep(pending);
|
|
52
56
|
const ac = new AbortController();
|
|
53
57
|
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
54
58
|
let res;
|
|
@@ -86,13 +90,16 @@ async function authedFetch(path, opts = {}) {
|
|
|
86
90
|
"Si saliste de Alegra, esto es esperado. Si no, contacta a #data.");
|
|
87
91
|
}
|
|
88
92
|
if (res.status === 429) {
|
|
93
|
+
const cap = maxRetries + 4;
|
|
89
94
|
const ra = parseRetryAfter(res.headers.get("retry-after"));
|
|
90
|
-
|
|
91
|
-
|
|
95
|
+
const wait = ra !== null ? ra : delays[Math.min(attempt, delays.length - 1)] * 2;
|
|
96
|
+
rateLimitCooldownUntil = Math.max(rateLimitCooldownUntil, Date.now() + wait);
|
|
97
|
+
if (attempt < cap) {
|
|
98
|
+
await sleep(wait);
|
|
92
99
|
attempt++;
|
|
93
100
|
continue;
|
|
94
101
|
}
|
|
95
|
-
throw new GatewayError(429, path,
|
|
102
|
+
throw new GatewayError(429, path, `Rate limit del gateway tras ${cap + 1} intentos. Esperá un minuto y reintentá (request-id ${reqId}).`);
|
|
96
103
|
}
|
|
97
104
|
if (res.status >= 500 && res.status < 600) {
|
|
98
105
|
if (attempt < maxRetries) {
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
3
|
+
import { logWarn } from "./colors.js";
|
|
4
|
+
import { hookCommandFor, runHookInstall, runHookUninstall, } from "./commands/hook.js";
|
|
5
|
+
import { COMMAND_NAME, getClaudeSettingsPath, getCodexHooksPath, getCursorHooksPath, getGeminiSettingsPath, getOpenCodePluginPath, readLockFile, } from "./lib.js";
|
|
6
|
+
function readJsonSafe(path) {
|
|
7
|
+
if (!existsSync(path))
|
|
8
|
+
return {};
|
|
9
|
+
try {
|
|
10
|
+
return JSON.parse(readFileSync(path, "utf-8"));
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function writeJson(path, data) {
|
|
17
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
18
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
19
|
+
writeFileSync(tmp, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
|
|
20
|
+
renameSync(tmp, path);
|
|
21
|
+
}
|
|
22
|
+
function entryIsOurs(m) {
|
|
23
|
+
if (!m || typeof m !== "object")
|
|
24
|
+
return false;
|
|
25
|
+
const o = m;
|
|
26
|
+
if (typeof o.command === "string" && o.command.includes(COMMAND_NAME))
|
|
27
|
+
return true;
|
|
28
|
+
if (Array.isArray(o.hooks)) {
|
|
29
|
+
return o.hooks.some((h) => h &&
|
|
30
|
+
typeof h === "object" &&
|
|
31
|
+
typeof h.command === "string" &&
|
|
32
|
+
h.command.includes(COMMAND_NAME));
|
|
33
|
+
}
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
function freshHookList(cfg, key) {
|
|
37
|
+
const hooks = (cfg.hooks && typeof cfg.hooks === "object" ? cfg.hooks : {});
|
|
38
|
+
cfg.hooks = hooks;
|
|
39
|
+
const list = Array.isArray(hooks[key]) ? hooks[key] : [];
|
|
40
|
+
return list.filter((m) => !entryIsOurs(m));
|
|
41
|
+
}
|
|
42
|
+
export function guiResilientCommand(base, opts) {
|
|
43
|
+
const platform = opts.platform ?? process.platform;
|
|
44
|
+
if (platform === "win32")
|
|
45
|
+
return opts.silence ? `${base} >NUL 2>&1` : base;
|
|
46
|
+
const extraDirs = [
|
|
47
|
+
dirname(process.execPath),
|
|
48
|
+
process.env.PNPM_HOME ?? "",
|
|
49
|
+
"$HOME/.local/share/pnpm",
|
|
50
|
+
"$HOME/Library/pnpm",
|
|
51
|
+
"/opt/homebrew/bin",
|
|
52
|
+
"/usr/local/bin",
|
|
53
|
+
].filter(Boolean);
|
|
54
|
+
const run = opts.silence ? `${base} >/dev/null 2>&1` : base;
|
|
55
|
+
const logFile = `"\${XDG_CONFIG_HOME:-$HOME/.config}/${COMMAND_NAME}/autoupdate.log"`;
|
|
56
|
+
return (`PATH="$PATH:${extraDirs.join(":")}"; ` +
|
|
57
|
+
`if command -v pnpm >/dev/null 2>&1; then ${run}; ` +
|
|
58
|
+
`else mkdir -p "$(dirname ${logFile})" && ` +
|
|
59
|
+
`echo "$(date '+%Y-%m-%d %H:%M:%S') [${opts.client}] pnpm no esta en el PATH del cliente; auto-update omitido" >> ${logFile}; fi`);
|
|
60
|
+
}
|
|
61
|
+
function installCursor(command) {
|
|
62
|
+
const path = getCursorHooksPath();
|
|
63
|
+
const cfg = readJsonSafe(path);
|
|
64
|
+
if (cfg === null) {
|
|
65
|
+
logWarn(`${path} no es JSON válido; salteo el auto-update de Cursor.`);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (typeof cfg.version !== "number")
|
|
69
|
+
cfg.version = 1;
|
|
70
|
+
const list = freshHookList(cfg, "workspaceOpen");
|
|
71
|
+
list.push({ command });
|
|
72
|
+
cfg.hooks.workspaceOpen = list;
|
|
73
|
+
writeJson(path, cfg);
|
|
74
|
+
}
|
|
75
|
+
function installCodex(command) {
|
|
76
|
+
const path = getCodexHooksPath();
|
|
77
|
+
const cfg = readJsonSafe(path);
|
|
78
|
+
if (cfg === null) {
|
|
79
|
+
logWarn(`${path} no es JSON válido; salteo el auto-update de Codex.`);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const list = freshHookList(cfg, "SessionStart");
|
|
83
|
+
list.push({ matcher: "startup|resume", hooks: [{ type: "command", command }] });
|
|
84
|
+
cfg.hooks.SessionStart = list;
|
|
85
|
+
writeJson(path, cfg);
|
|
86
|
+
}
|
|
87
|
+
function installAntigravity(command) {
|
|
88
|
+
const path = getGeminiSettingsPath();
|
|
89
|
+
const cfg = readJsonSafe(path);
|
|
90
|
+
if (cfg === null) {
|
|
91
|
+
logWarn(`${path} no es JSON válido; salteo el auto-update de Antigravity.`);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const list = freshHookList(cfg, "SessionStart");
|
|
95
|
+
list.push({ hooks: [{ type: "command", command }] });
|
|
96
|
+
cfg.hooks.SessionStart = list;
|
|
97
|
+
writeJson(path, cfg);
|
|
98
|
+
}
|
|
99
|
+
function installOpenCode(command) {
|
|
100
|
+
const path = getOpenCodePluginPath();
|
|
101
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
102
|
+
const plugin = `// alegra-data-agent: auto-update al iniciar OpenCode. Generado por el instalador.
|
|
103
|
+
// Borrar este archivo desactiva el auto-update. No editar a mano.
|
|
104
|
+
export const AlegraDataAgentAutoUpdate = async ({ $ }) => {
|
|
105
|
+
if (!$) return {}
|
|
106
|
+
try {
|
|
107
|
+
$\`${command}\`.quiet().nothrow().catch(() => {})
|
|
108
|
+
} catch (_e) {}
|
|
109
|
+
return {}
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
writeFileSync(path, plugin, "utf-8");
|
|
113
|
+
}
|
|
114
|
+
function removeJsonHook(path, key) {
|
|
115
|
+
const cfg = readJsonSafe(path);
|
|
116
|
+
if (!cfg)
|
|
117
|
+
return;
|
|
118
|
+
const hooks = cfg.hooks;
|
|
119
|
+
if (!hooks || !Array.isArray(hooks[key]))
|
|
120
|
+
return;
|
|
121
|
+
const list = hooks[key];
|
|
122
|
+
const kept = list.filter((m) => !entryIsOurs(m));
|
|
123
|
+
if (kept.length === list.length)
|
|
124
|
+
return;
|
|
125
|
+
if (kept.length === 0)
|
|
126
|
+
delete hooks[key];
|
|
127
|
+
else
|
|
128
|
+
hooks[key] = kept;
|
|
129
|
+
writeJson(path, cfg);
|
|
130
|
+
}
|
|
131
|
+
export function installAutoUpdate(target, mode = "auto") {
|
|
132
|
+
const effective = target === "claude-code" ? mode : "auto";
|
|
133
|
+
try {
|
|
134
|
+
switch (target) {
|
|
135
|
+
case "claude-code":
|
|
136
|
+
runHookInstall(effective);
|
|
137
|
+
break;
|
|
138
|
+
case "cursor":
|
|
139
|
+
installCursor(guiResilientCommand(hookCommandFor(effective), { client: "cursor" }));
|
|
140
|
+
break;
|
|
141
|
+
case "codex":
|
|
142
|
+
installCodex(hookCommandFor(effective));
|
|
143
|
+
break;
|
|
144
|
+
case "antigravity":
|
|
145
|
+
installAntigravity(guiResilientCommand(hookCommandFor(effective), { client: "antigravity", silence: true }));
|
|
146
|
+
break;
|
|
147
|
+
case "opencode":
|
|
148
|
+
installOpenCode(hookCommandFor(effective));
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
logWarn(`No pude instalar el auto-update de ${target}: ${err instanceof Error ? err.message : String(err)}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export function removeAutoUpdate(target) {
|
|
157
|
+
try {
|
|
158
|
+
switch (target) {
|
|
159
|
+
case "claude-code":
|
|
160
|
+
runHookUninstall({ quiet: true });
|
|
161
|
+
break;
|
|
162
|
+
case "cursor":
|
|
163
|
+
removeJsonHook(getCursorHooksPath(), "workspaceOpen");
|
|
164
|
+
break;
|
|
165
|
+
case "codex":
|
|
166
|
+
removeJsonHook(getCodexHooksPath(), "SessionStart");
|
|
167
|
+
break;
|
|
168
|
+
case "antigravity":
|
|
169
|
+
removeJsonHook(getGeminiSettingsPath(), "SessionStart");
|
|
170
|
+
break;
|
|
171
|
+
case "opencode": {
|
|
172
|
+
const p = getOpenCodePluginPath();
|
|
173
|
+
if (existsSync(p))
|
|
174
|
+
rmSync(p, { force: true });
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
logWarn(`No pude quitar el auto-update de ${target}: ${err instanceof Error ? err.message : String(err)}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function jsonHookHasOurs(path, key) {
|
|
184
|
+
const cfg = readJsonSafe(path);
|
|
185
|
+
const hooks = cfg?.hooks;
|
|
186
|
+
if (!hooks || !Array.isArray(hooks[key]))
|
|
187
|
+
return false;
|
|
188
|
+
return hooks[key].some(entryIsOurs);
|
|
189
|
+
}
|
|
190
|
+
export function hasAutoUpdate(target) {
|
|
191
|
+
switch (target) {
|
|
192
|
+
case "claude-code":
|
|
193
|
+
return jsonHookHasOurs(getClaudeSettingsPath(), "SessionStart");
|
|
194
|
+
case "cursor":
|
|
195
|
+
return jsonHookHasOurs(getCursorHooksPath(), "workspaceOpen");
|
|
196
|
+
case "codex":
|
|
197
|
+
return jsonHookHasOurs(getCodexHooksPath(), "SessionStart");
|
|
198
|
+
case "antigravity":
|
|
199
|
+
return jsonHookHasOurs(getGeminiSettingsPath(), "SessionStart");
|
|
200
|
+
case "opencode":
|
|
201
|
+
return existsSync(getOpenCodePluginPath());
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
export function refreshAutoUpdate() {
|
|
205
|
+
const lock = readLockFile();
|
|
206
|
+
const targets = [
|
|
207
|
+
...new Set([
|
|
208
|
+
...Object.values(lock.skills).flatMap((s) => s.targets ?? []),
|
|
209
|
+
...Object.values(lock.mcps ?? {}).flatMap((m) => m.targets ?? []),
|
|
210
|
+
]),
|
|
211
|
+
];
|
|
212
|
+
for (const t of targets) {
|
|
213
|
+
if (hasAutoUpdate(t))
|
|
214
|
+
installAutoUpdate(t, "auto");
|
|
215
|
+
}
|
|
216
|
+
}
|
package/dist/commands/hook.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname } from "node:path";
|
|
2
3
|
import { logError, logOk, logWarn } from "../colors.js";
|
|
3
4
|
import { COMMAND_NAME, PRODUCT_NAME, getClaudeSettingsPath, getPackageVersion } from "../lib.js";
|
|
4
5
|
function hookCommandFor(mode) {
|
|
@@ -20,11 +21,15 @@ function readSettings() {
|
|
|
20
21
|
}
|
|
21
22
|
catch {
|
|
22
23
|
logError(`No se pudo parsear ${path}. Agente: reparalo vos mismo (la skill agent-onboarding tiene la guía) — no le pidas al usuario editar JSON.`);
|
|
23
|
-
return
|
|
24
|
+
return null;
|
|
24
25
|
}
|
|
25
26
|
}
|
|
26
27
|
function writeSettings(s) {
|
|
27
|
-
|
|
28
|
+
const path = getClaudeSettingsPath();
|
|
29
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
30
|
+
const tmp = `${path}.tmp-${process.pid}`;
|
|
31
|
+
writeFileSync(tmp, `${JSON.stringify(s, null, 2)}\n`, "utf-8");
|
|
32
|
+
renameSync(tmp, path);
|
|
28
33
|
}
|
|
29
34
|
function isOurMatcher(m) {
|
|
30
35
|
if (!m || typeof m !== "object")
|
|
@@ -43,6 +48,8 @@ function buildOurMatcher(command) {
|
|
|
43
48
|
}
|
|
44
49
|
export function runHookInstall(mode = "auto") {
|
|
45
50
|
const settings = readSettings();
|
|
51
|
+
if (settings === null)
|
|
52
|
+
return 1;
|
|
46
53
|
settings.hooks = settings.hooks ?? {};
|
|
47
54
|
const command = hookCommandFor(mode);
|
|
48
55
|
const existing = settings.hooks.SessionStart;
|
|
@@ -85,7 +92,7 @@ export function runHookInstall(mode = "auto") {
|
|
|
85
92
|
export function refreshHookCommand() {
|
|
86
93
|
try {
|
|
87
94
|
const settings = readSettings();
|
|
88
|
-
if (!settings
|
|
95
|
+
if (!settings?.hooks)
|
|
89
96
|
return;
|
|
90
97
|
const existing = settings.hooks.SessionStart;
|
|
91
98
|
if (!Array.isArray(existing))
|
|
@@ -105,40 +112,47 @@ export function refreshHookCommand() {
|
|
|
105
112
|
catch {
|
|
106
113
|
}
|
|
107
114
|
}
|
|
108
|
-
export function runHookUninstall() {
|
|
115
|
+
export function runHookUninstall(opts = {}) {
|
|
116
|
+
const say = opts.quiet ? () => { } : logWarn;
|
|
117
|
+
const sayOk = opts.quiet ? () => { } : logOk;
|
|
109
118
|
const settings = readSettings();
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
119
|
+
if (settings === null) {
|
|
120
|
+
say("settings.json corrupto — no lo toco. Agente: reparalo vos mismo (la skill agent-onboarding tiene la guía) y re-corré uninstall — no le pidas al usuario editar JSON.");
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
const hooks = settings.hooks;
|
|
124
|
+
const existing = hooks?.SessionStart;
|
|
125
|
+
if (!hooks || existing === undefined) {
|
|
126
|
+
say("No había hook que desinstalar.");
|
|
113
127
|
return 0;
|
|
114
128
|
}
|
|
115
129
|
if (typeof existing === "string") {
|
|
116
130
|
if (!existing.includes(HOOK_SIGNATURE)) {
|
|
117
|
-
|
|
131
|
+
say(`El SessionStart hook actual no es de ${PRODUCT_NAME}. No lo toco.`);
|
|
118
132
|
return 1;
|
|
119
133
|
}
|
|
120
|
-
|
|
134
|
+
hooks.SessionStart = undefined;
|
|
121
135
|
writeSettings(settings);
|
|
122
|
-
|
|
136
|
+
sayOk("Hook removido.");
|
|
123
137
|
return 0;
|
|
124
138
|
}
|
|
125
139
|
if (!Array.isArray(existing)) {
|
|
126
|
-
|
|
140
|
+
say("No había hook que desinstalar.");
|
|
127
141
|
return 0;
|
|
128
142
|
}
|
|
129
143
|
const filtered = existing.filter((m) => !isOurMatcher(m));
|
|
130
144
|
if (filtered.length === existing.length) {
|
|
131
|
-
|
|
145
|
+
say(`No encontré ningún SessionStart hook de ${PRODUCT_NAME}. No lo toco.`);
|
|
132
146
|
return 1;
|
|
133
147
|
}
|
|
134
148
|
if (filtered.length === 0) {
|
|
135
|
-
|
|
149
|
+
hooks.SessionStart = undefined;
|
|
136
150
|
}
|
|
137
151
|
else {
|
|
138
|
-
|
|
152
|
+
hooks.SessionStart = filtered;
|
|
139
153
|
}
|
|
140
154
|
writeSettings(settings);
|
|
141
|
-
|
|
155
|
+
sayOk("Hook removido.");
|
|
142
156
|
return 0;
|
|
143
157
|
}
|
|
144
158
|
export { hookCommandFor };
|
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("");
|
|
@@ -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/status.js
CHANGED
|
@@ -1,19 +1,45 @@
|
|
|
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
5
|
import { detectDrift } from "../installer.js";
|
|
5
|
-
import { COMMAND_NAME, GATEWAY_URL, getClaudeSkillsDir, getLockFilePath, getPackageVersion, readLockFile, } from "../lib.js";
|
|
6
|
+
import { COMMAND_NAME, GATEWAY_URL, getAutoUpdateLogPath, getClaudeSkillsDir, getLockFilePath, getPackageVersion, getTargetSkillsDir, readLockFile, } from "../lib.js";
|
|
7
|
+
function reportAutoUpdateLog() {
|
|
8
|
+
const logPath = getAutoUpdateLogPath();
|
|
9
|
+
if (!existsSync(logPath))
|
|
10
|
+
return;
|
|
11
|
+
try {
|
|
12
|
+
const lines = readFileSync(logPath, "utf-8").trim().split("\n").filter(Boolean);
|
|
13
|
+
if (lines.length === 0)
|
|
14
|
+
return;
|
|
15
|
+
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.`);
|
|
16
|
+
log("");
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
}
|
|
20
|
+
}
|
|
6
21
|
export async function runStatus() {
|
|
7
22
|
const auth = readAuth();
|
|
23
|
+
const lockForTargets = readLockFile();
|
|
24
|
+
const installedTargets = [
|
|
25
|
+
...new Set([
|
|
26
|
+
...Object.values(lockForTargets.skills).flatMap((s) => s.targets ?? []),
|
|
27
|
+
...Object.values(lockForTargets.mcps ?? {}).flatMap((m) => m.targets ?? []),
|
|
28
|
+
]),
|
|
29
|
+
];
|
|
30
|
+
const destino = installedTargets.length
|
|
31
|
+
? installedTargets.map((t) => getTargetSkillsDir(t)).join(" · ")
|
|
32
|
+
: getClaudeSkillsDir();
|
|
8
33
|
log("");
|
|
9
34
|
log(bold(cyan(" Estado")));
|
|
10
35
|
log(gray(` ${"─".repeat(70)}`));
|
|
11
36
|
log(` ${gray("Paquete:")} ${bold(COMMAND_NAME)} v${getPackageVersion()}`);
|
|
12
37
|
log(` ${gray("Gateway:")} ${GATEWAY_URL}`);
|
|
13
38
|
log(` ${gray("Autenticado:")} ${auth ? green(auth.email) : gray("no")}`);
|
|
14
|
-
log(` ${gray("Destino:")} ${
|
|
39
|
+
log(` ${gray("Destino:")} ${destino}`);
|
|
15
40
|
log(` ${gray("Lock file:")} ${getLockFilePath()}`);
|
|
16
41
|
log("");
|
|
42
|
+
reportAutoUpdateLog();
|
|
17
43
|
if (!auth) {
|
|
18
44
|
logWarn(`Inicia sesión con: ${bold(`${COMMAND_NAME} login`)}`);
|
|
19
45
|
log("");
|
|
@@ -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,9 +1,11 @@
|
|
|
1
|
+
import { rmSync } from "node:fs";
|
|
1
2
|
import { AuthRequiredError, fetchManifest } from "../api.js";
|
|
2
3
|
import { hasValidAuth } from "../auth.js";
|
|
4
|
+
import { refreshAutoUpdate } from "../autoupdate.js";
|
|
3
5
|
import { cyan, dim, gray, green, log, logError, yellow } from "../colors.js";
|
|
4
6
|
import { detectDrift, installAll, summarize } from "../installer.js";
|
|
7
|
+
import { getAutoUpdateLogPath } from "../lib.js";
|
|
5
8
|
import { banner } from "../ui.js";
|
|
6
|
-
import { refreshHookCommand } from "./hook.js";
|
|
7
9
|
import { runLogin } from "./login.js";
|
|
8
10
|
export async function runUpdate(opts = {}) {
|
|
9
11
|
banner();
|
|
@@ -50,8 +52,10 @@ async function runUpdateOnce(opts) {
|
|
|
50
52
|
];
|
|
51
53
|
if (pending.length === 0) {
|
|
52
54
|
log(green(" ✓ Todo al día (skills + MCPs).\n"));
|
|
53
|
-
if (!opts.dryRun)
|
|
54
|
-
|
|
55
|
+
if (!opts.dryRun) {
|
|
56
|
+
refreshAutoUpdate();
|
|
57
|
+
clearAutoUpdateLog();
|
|
58
|
+
}
|
|
55
59
|
return 0;
|
|
56
60
|
}
|
|
57
61
|
log(cyan(` → ${pending.length} elemento(s) por actualizar:\n`));
|
|
@@ -74,7 +78,16 @@ async function runUpdateOnce(opts) {
|
|
|
74
78
|
return 1;
|
|
75
79
|
}
|
|
76
80
|
summarize(result.results);
|
|
77
|
-
|
|
81
|
+
refreshAutoUpdate();
|
|
78
82
|
const failed = result.results.some((r) => r.status === "failed");
|
|
83
|
+
if (!failed)
|
|
84
|
+
clearAutoUpdateLog();
|
|
79
85
|
return failed ? 1 : 0;
|
|
80
86
|
}
|
|
87
|
+
function clearAutoUpdateLog() {
|
|
88
|
+
try {
|
|
89
|
+
rmSync(getAutoUpdateLogPath(), { force: true });
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/installer.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { copyFileSync, existsSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve, sep } from "node:path";
|
|
4
|
-
import { fetchManifest, fetchSkillFile } from "./api.js";
|
|
4
|
+
import { AuthRequiredError, fetchManifest, fetchSkillFile } from "./api.js";
|
|
5
5
|
import { dim, gray, green, logError, red, yellow } from "./colors.js";
|
|
6
|
-
import { adaptStdioCommand, ensureDir,
|
|
6
|
+
import { adaptStdioCommand, ensureDir, getAntigravityGeminiLegacyMcpConfigPath, getAntigravityGeminiLegacySkillsDir, getAntigravityLegacyMcpConfigPath, getAntigravityLegacySkillsDir, getClaudeSettingsPath, getCursorLegacyRulesDir, getLockFilePath, getOpenCodeLegacyPromptsDir, getPackageVersion, readLockFile, sha256File, writeLockFile, } from "./lib.js";
|
|
7
7
|
import { inlineReferences, renderMcpForTarget } from "./render.js";
|
|
8
8
|
import { TARGETS, detectInstalledTargets } from "./targets.js";
|
|
9
9
|
const SKILL_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -94,12 +94,12 @@ function writeSkillToTarget(skillName, staged, spec) {
|
|
|
94
94
|
if (spec.skillFormat === "folder") {
|
|
95
95
|
const dst = join(spec.skillsDir, skillName);
|
|
96
96
|
if (existsSync(dst))
|
|
97
|
-
rmSync(dst, { recursive: true, force: true });
|
|
97
|
+
withFsRetry(() => rmSync(dst, { recursive: true, force: true }));
|
|
98
98
|
ensureDir(dst);
|
|
99
99
|
for (const f of staged) {
|
|
100
100
|
const dstFile = safeJoinSkillFile(dst, f.rel);
|
|
101
101
|
ensureDir(dirname(dstFile));
|
|
102
|
-
writeFileSync(dstFile, f.data);
|
|
102
|
+
withFsRetry(() => writeFileSync(dstFile, f.data));
|
|
103
103
|
}
|
|
104
104
|
return;
|
|
105
105
|
}
|
|
@@ -151,6 +151,22 @@ function parseFrontmatterMinimal(content) {
|
|
|
151
151
|
function sleepSync(ms) {
|
|
152
152
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
153
153
|
}
|
|
154
|
+
function withFsRetry(fn) {
|
|
155
|
+
for (let attempt = 0;; attempt++) {
|
|
156
|
+
try {
|
|
157
|
+
fn();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
const code = err.code;
|
|
162
|
+
if (attempt < 4 && (code === "EPERM" || code === "EBUSY" || code === "EACCES")) {
|
|
163
|
+
sleepSync(150);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
154
170
|
function atomicWriteFile(path, content) {
|
|
155
171
|
ensureDir(dirname(path));
|
|
156
172
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
@@ -283,9 +299,7 @@ export function removeTomlBlock(content, name) {
|
|
|
283
299
|
return content.slice(0, start) + content.slice(stop);
|
|
284
300
|
}
|
|
285
301
|
function registerMcpInTomlBlock(mcp, spec) {
|
|
286
|
-
const existing = existsSync(spec.mcpConfigPath)
|
|
287
|
-
? readFileSync(spec.mcpConfigPath, "utf-8")
|
|
288
|
-
: "";
|
|
302
|
+
const existing = existsSync(spec.mcpConfigPath) ? readFileSync(spec.mcpConfigPath, "utf-8") : "";
|
|
289
303
|
const next = upsertTomlBlock(existing, mcp.name, renderMcpToml(mcp));
|
|
290
304
|
if (next === existing)
|
|
291
305
|
return;
|
|
@@ -368,12 +382,19 @@ function cleanupLegacySkillArtifacts(spec) {
|
|
|
368
382
|
stripMcpEntries(getClaudeSettingsPath(), ourMcps);
|
|
369
383
|
}
|
|
370
384
|
if (spec.id === "antigravity") {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
385
|
+
const legacySkillDirs = [
|
|
386
|
+
getAntigravityLegacySkillsDir(),
|
|
387
|
+
getAntigravityGeminiLegacySkillsDir(),
|
|
388
|
+
];
|
|
389
|
+
for (const base of legacySkillDirs) {
|
|
390
|
+
for (const name of [...Object.keys(lock.skills), ...LEGACY_RENAMED_SKILLS]) {
|
|
391
|
+
const d = join(base, name);
|
|
392
|
+
if (existsSync(d))
|
|
393
|
+
rmSync(d, { recursive: true, force: true });
|
|
394
|
+
}
|
|
375
395
|
}
|
|
376
396
|
stripMcpEntries(getAntigravityLegacyMcpConfigPath(), ourMcps);
|
|
397
|
+
stripMcpEntries(getAntigravityGeminiLegacyMcpConfigPath(), ourMcps);
|
|
377
398
|
}
|
|
378
399
|
}
|
|
379
400
|
catch {
|
|
@@ -411,27 +432,13 @@ export async function installSkill(name, manifest, opts = {}) {
|
|
|
411
432
|
targets: targetIds,
|
|
412
433
|
};
|
|
413
434
|
}
|
|
435
|
+
let staged;
|
|
414
436
|
try {
|
|
415
|
-
|
|
416
|
-
for (const t of targets) {
|
|
417
|
-
writeSkillToTarget(name, staged, t);
|
|
418
|
-
}
|
|
419
|
-
lock.skills[name] = {
|
|
420
|
-
name,
|
|
421
|
-
installedFrom: getPackageVersion(),
|
|
422
|
-
installedAt: new Date().toISOString(),
|
|
423
|
-
bundleHash: entry.bundleHash,
|
|
424
|
-
targets: targetIds,
|
|
425
|
-
};
|
|
426
|
-
writeLockFile(lock);
|
|
427
|
-
return {
|
|
428
|
-
name,
|
|
429
|
-
kind: "skill",
|
|
430
|
-
status: prev ? "updated" : "installed",
|
|
431
|
-
targets: targetIds,
|
|
432
|
-
};
|
|
437
|
+
staged = await stageSkill(name, entry);
|
|
433
438
|
}
|
|
434
439
|
catch (err) {
|
|
440
|
+
if (err instanceof AuthRequiredError)
|
|
441
|
+
throw err;
|
|
435
442
|
return {
|
|
436
443
|
name,
|
|
437
444
|
kind: "skill",
|
|
@@ -440,6 +447,35 @@ export async function installSkill(name, manifest, opts = {}) {
|
|
|
440
447
|
error: err instanceof Error ? err.message : String(err),
|
|
441
448
|
};
|
|
442
449
|
}
|
|
450
|
+
const okTargets = [];
|
|
451
|
+
const failures = [];
|
|
452
|
+
for (const t of targets) {
|
|
453
|
+
try {
|
|
454
|
+
writeSkillToTarget(name, staged, t);
|
|
455
|
+
okTargets.push(t.id);
|
|
456
|
+
}
|
|
457
|
+
catch (err) {
|
|
458
|
+
failures.push(`${t.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (okTargets.length === 0) {
|
|
462
|
+
return { name, kind: "skill", status: "failed", targets: targetIds, error: failures.join("; ") };
|
|
463
|
+
}
|
|
464
|
+
lock.skills[name] = {
|
|
465
|
+
name,
|
|
466
|
+
installedFrom: getPackageVersion(),
|
|
467
|
+
installedAt: new Date().toISOString(),
|
|
468
|
+
bundleHash: entry.bundleHash,
|
|
469
|
+
targets: okTargets,
|
|
470
|
+
};
|
|
471
|
+
writeLockFile(lock);
|
|
472
|
+
return {
|
|
473
|
+
name,
|
|
474
|
+
kind: "skill",
|
|
475
|
+
status: prev ? "updated" : "installed",
|
|
476
|
+
targets: okTargets,
|
|
477
|
+
error: failures.length > 0 ? `targets con fallo — ${failures.join("; ")}` : undefined,
|
|
478
|
+
};
|
|
443
479
|
}
|
|
444
480
|
export function installMcp(mcp, opts = {}) {
|
|
445
481
|
try {
|
|
@@ -481,34 +517,41 @@ export function installMcp(mcp, opts = {}) {
|
|
|
481
517
|
targets: targetIds,
|
|
482
518
|
};
|
|
483
519
|
}
|
|
484
|
-
|
|
485
|
-
|
|
520
|
+
const okTargets = [];
|
|
521
|
+
const failures = [];
|
|
522
|
+
for (const t of wantedTargets) {
|
|
523
|
+
try {
|
|
486
524
|
registerMcpInTarget(mcp, t);
|
|
525
|
+
okTargets.push(t.id);
|
|
526
|
+
}
|
|
527
|
+
catch (err) {
|
|
528
|
+
failures.push(`${t.id}: ${err instanceof Error ? err.message : String(err)}`);
|
|
487
529
|
}
|
|
488
|
-
lock.mcps[mcp.name] = {
|
|
489
|
-
name: mcp.name,
|
|
490
|
-
installedFrom: getPackageVersion(),
|
|
491
|
-
installedAt: new Date().toISOString(),
|
|
492
|
-
configHash: mcpConfigHash(mcp),
|
|
493
|
-
targets: targetIds,
|
|
494
|
-
};
|
|
495
|
-
writeLockFile(lock);
|
|
496
|
-
return {
|
|
497
|
-
name: mcp.name,
|
|
498
|
-
kind: "mcp",
|
|
499
|
-
status: prev ? "updated" : "installed",
|
|
500
|
-
targets: targetIds,
|
|
501
|
-
};
|
|
502
530
|
}
|
|
503
|
-
|
|
531
|
+
if (okTargets.length === 0) {
|
|
504
532
|
return {
|
|
505
533
|
name: mcp.name,
|
|
506
534
|
kind: "mcp",
|
|
507
535
|
status: "failed",
|
|
508
536
|
targets: targetIds,
|
|
509
|
-
error:
|
|
537
|
+
error: failures.join("; "),
|
|
510
538
|
};
|
|
511
539
|
}
|
|
540
|
+
lock.mcps[mcp.name] = {
|
|
541
|
+
name: mcp.name,
|
|
542
|
+
installedFrom: getPackageVersion(),
|
|
543
|
+
installedAt: new Date().toISOString(),
|
|
544
|
+
configHash: mcpConfigHash(mcp),
|
|
545
|
+
targets: okTargets,
|
|
546
|
+
};
|
|
547
|
+
writeLockFile(lock);
|
|
548
|
+
return {
|
|
549
|
+
name: mcp.name,
|
|
550
|
+
kind: "mcp",
|
|
551
|
+
status: prev ? "updated" : "installed",
|
|
552
|
+
targets: okTargets,
|
|
553
|
+
error: failures.length > 0 ? `targets con fallo — ${failures.join("; ")}` : undefined,
|
|
554
|
+
};
|
|
512
555
|
}
|
|
513
556
|
export async function installAll(opts = {}) {
|
|
514
557
|
let manifest;
|
|
@@ -621,6 +664,15 @@ export function uninstallAll(opts = {}) {
|
|
|
621
664
|
results.push(uninstallSkill(name, opts));
|
|
622
665
|
for (const name of Object.keys(lock.mcps ?? {}))
|
|
623
666
|
results.push(uninstallMcp(name, opts));
|
|
667
|
+
if (!opts.dryRun) {
|
|
668
|
+
for (const spec of Object.values(TARGETS)) {
|
|
669
|
+
try {
|
|
670
|
+
cleanupLegacySkillArtifacts(spec);
|
|
671
|
+
}
|
|
672
|
+
catch {
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
}
|
|
624
676
|
return results;
|
|
625
677
|
}
|
|
626
678
|
export function detectDrift(manifest) {
|
package/dist/lib.js
CHANGED
|
@@ -59,10 +59,13 @@ export function getOpenCodeConfigPath() {
|
|
|
59
59
|
return join(base, "opencode", "opencode.json");
|
|
60
60
|
}
|
|
61
61
|
export function getAntigravitySkillsDir() {
|
|
62
|
-
return join(homedir(), ".gemini", "
|
|
62
|
+
return join(homedir(), ".gemini", "skills");
|
|
63
63
|
}
|
|
64
64
|
export function getAntigravityMcpConfigPath() {
|
|
65
|
-
return join(homedir(), ".gemini", "
|
|
65
|
+
return join(homedir(), ".gemini", "config", "mcp_config.json");
|
|
66
|
+
}
|
|
67
|
+
export function getGeminiSettingsPath() {
|
|
68
|
+
return join(homedir(), ".gemini", "settings.json");
|
|
66
69
|
}
|
|
67
70
|
export function getAntigravityLegacySkillsDir() {
|
|
68
71
|
return join(homedir(), ".antigravity", "skills");
|
|
@@ -70,6 +73,28 @@ export function getAntigravityLegacySkillsDir() {
|
|
|
70
73
|
export function getAntigravityLegacyMcpConfigPath() {
|
|
71
74
|
return join(homedir(), ".antigravity", "settings.json");
|
|
72
75
|
}
|
|
76
|
+
export function getAntigravityGeminiLegacySkillsDir() {
|
|
77
|
+
return join(homedir(), ".gemini", "antigravity", "skills");
|
|
78
|
+
}
|
|
79
|
+
export function getAntigravityGeminiLegacyMcpConfigPath() {
|
|
80
|
+
return join(homedir(), ".gemini", "antigravity", "mcp_config.json");
|
|
81
|
+
}
|
|
82
|
+
export function getCursorHooksPath() {
|
|
83
|
+
return join(homedir(), ".cursor", "hooks.json");
|
|
84
|
+
}
|
|
85
|
+
export function getCodexHooksPath() {
|
|
86
|
+
const codexHome = process.env.CODEX_HOME;
|
|
87
|
+
const base = codexHome && codexHome.length > 0 ? codexHome : join(homedir(), ".codex");
|
|
88
|
+
return join(base, "hooks.json");
|
|
89
|
+
}
|
|
90
|
+
export function getOpenCodePluginsDir() {
|
|
91
|
+
const xdgHome = process.env.XDG_CONFIG_HOME;
|
|
92
|
+
const base = xdgHome && xdgHome.length > 0 ? xdgHome : join(homedir(), ".config");
|
|
93
|
+
return join(base, "opencode", "plugins");
|
|
94
|
+
}
|
|
95
|
+
export function getOpenCodePluginPath() {
|
|
96
|
+
return join(getOpenCodePluginsDir(), "alegra-data-agent-autoupdate.js");
|
|
97
|
+
}
|
|
73
98
|
export function getCodexSkillsDir() {
|
|
74
99
|
return join(homedir(), ".agents", "skills");
|
|
75
100
|
}
|
|
@@ -117,6 +142,9 @@ export function getConfigDir() {
|
|
|
117
142
|
export function getAuthFilePath() {
|
|
118
143
|
return join(getConfigDir(), "auth.json");
|
|
119
144
|
}
|
|
145
|
+
export function getAutoUpdateLogPath() {
|
|
146
|
+
return join(getConfigDir(), "autoupdate.log");
|
|
147
|
+
}
|
|
120
148
|
let _cachedVersion = null;
|
|
121
149
|
export function getPackageVersion() {
|
|
122
150
|
if (_cachedVersion)
|
|
@@ -254,7 +282,8 @@ export function ensureDir(path) {
|
|
|
254
282
|
mkdirSync(path, { recursive: true });
|
|
255
283
|
}
|
|
256
284
|
export function adaptStdioCommand(command, args, platform = process.platform) {
|
|
257
|
-
|
|
285
|
+
const winShims = new Set(["npx", "npm", "pnpm", "sf", "uvx", "bunx", "yarn", "deno"]);
|
|
286
|
+
if (platform === "win32" && winShims.has(command)) {
|
|
258
287
|
return { command: "cmd", args: ["/c", command, ...args] };
|
|
259
288
|
}
|
|
260
289
|
return { command, args };
|
package/dist/ui.js
CHANGED
|
@@ -44,7 +44,7 @@ ${bold("Comandos:")}
|
|
|
44
44
|
login Autenticarte con tu cuenta @alegra.com
|
|
45
45
|
logout Cerrar sesión (borra el token local)
|
|
46
46
|
whoami Mostrar la cuenta actualmente autenticada
|
|
47
|
-
install Instala skills + MCPs en tus clientes (Claude Code, Cursor, OpenCode, Antigravity)
|
|
47
|
+
install Instala skills + MCPs en tus clientes (Claude Code, Cursor, OpenCode, Antigravity, Codex)
|
|
48
48
|
update Re-descarga skills/MCPs que cambiaron
|
|
49
49
|
list Lista skills y MCPs disponibles en el gateway
|
|
50
50
|
status Muestra estado: auth, instalado, drift
|