alegra-data-agent 0.2.2 → 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 +206 -48
- package/dist/lib.js +55 -3
- package/dist/render.js +18 -1
- package/dist/targets.js +6 -6
- 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 { ensureDir, getLockFilePath, getPackageVersion, readLockFile, sha256File, writeLockFile, } from "./lib.js";
|
|
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-]*$/;
|
|
@@ -34,7 +34,11 @@ function validateRelPath(rel) {
|
|
|
34
34
|
}
|
|
35
35
|
function mcpConfigHash(mcp) {
|
|
36
36
|
const salient = JSON.stringify({
|
|
37
|
-
|
|
37
|
+
transport: mcp.transport ?? "http",
|
|
38
|
+
url: mcp.url ?? null,
|
|
39
|
+
command: mcp.command ?? null,
|
|
40
|
+
args: mcp.args ?? null,
|
|
41
|
+
env: mcp.env ?? null,
|
|
38
42
|
type: mcp.auth.type,
|
|
39
43
|
tokenEnv: mcp.auth.tokenEnv ?? null,
|
|
40
44
|
});
|
|
@@ -90,16 +94,16 @@ function writeSkillToTarget(skillName, staged, spec) {
|
|
|
90
94
|
if (spec.skillFormat === "folder") {
|
|
91
95
|
const dst = join(spec.skillsDir, skillName);
|
|
92
96
|
if (existsSync(dst))
|
|
93
|
-
rmSync(dst, { recursive: true, force: true });
|
|
97
|
+
withFsRetry(() => rmSync(dst, { recursive: true, force: true }));
|
|
94
98
|
ensureDir(dst);
|
|
95
99
|
for (const f of staged) {
|
|
96
100
|
const dstFile = safeJoinSkillFile(dst, f.rel);
|
|
97
101
|
ensureDir(dirname(dstFile));
|
|
98
|
-
writeFileSync(dstFile, f.data);
|
|
102
|
+
withFsRetry(() => writeFileSync(dstFile, f.data));
|
|
99
103
|
}
|
|
100
104
|
return;
|
|
101
105
|
}
|
|
102
|
-
const entry = staged.find((f) => f.rel === "SKILL.md"
|
|
106
|
+
const entry = staged.find((f) => f.rel === "SKILL.md") ?? staged.find((f) => f.rel === "README.md");
|
|
103
107
|
if (!entry) {
|
|
104
108
|
throw new Error(`Para target ${spec.id} se requiere SKILL.md o README.md en la skill`);
|
|
105
109
|
}
|
|
@@ -144,11 +148,47 @@ function parseFrontmatterMinimal(content) {
|
|
|
144
148
|
}
|
|
145
149
|
return out;
|
|
146
150
|
}
|
|
151
|
+
function sleepSync(ms) {
|
|
152
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
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
|
+
}
|
|
147
170
|
function atomicWriteFile(path, content) {
|
|
148
171
|
ensureDir(dirname(path));
|
|
149
172
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
150
173
|
writeFileSync(tmp, content);
|
|
151
|
-
|
|
174
|
+
for (let attempt = 0;; attempt++) {
|
|
175
|
+
try {
|
|
176
|
+
renameSync(tmp, path);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
const code = err.code;
|
|
181
|
+
if (attempt < 4 && (code === "EPERM" || code === "EBUSY" || code === "EACCES")) {
|
|
182
|
+
sleepSync(150);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
rmSync(tmp, { force: true });
|
|
187
|
+
}
|
|
188
|
+
catch { }
|
|
189
|
+
throw err;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
152
192
|
}
|
|
153
193
|
function backupIfExists(path) {
|
|
154
194
|
if (!existsSync(path))
|
|
@@ -199,7 +239,20 @@ function tomlBasicString(s) {
|
|
|
199
239
|
return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
200
240
|
}
|
|
201
241
|
export function renderMcpToml(mcp) {
|
|
202
|
-
|
|
242
|
+
if (mcp.transport === "stdio") {
|
|
243
|
+
const { command, args } = adaptStdioCommand(mcp.command ?? "", mcp.args ?? []);
|
|
244
|
+
const lines = [`[mcp_servers.${mcp.name}]`, `command = ${tomlBasicString(command)}`];
|
|
245
|
+
if (args.length > 0) {
|
|
246
|
+
lines.push(`args = [${args.map((a) => tomlBasicString(a)).join(", ")}]`);
|
|
247
|
+
}
|
|
248
|
+
if (mcp.env && Object.keys(mcp.env).length > 0) {
|
|
249
|
+
lines.push(`[mcp_servers.${mcp.name}.env]`);
|
|
250
|
+
for (const [k, v] of Object.entries(mcp.env))
|
|
251
|
+
lines.push(`${k} = ${tomlBasicString(v)}`);
|
|
252
|
+
}
|
|
253
|
+
return lines.join("\n");
|
|
254
|
+
}
|
|
255
|
+
const lines = [`[mcp_servers.${mcp.name}]`, `url = ${tomlBasicString(mcp.url ?? "")}`];
|
|
203
256
|
if (mcp.auth.type === "gateway") {
|
|
204
257
|
if (!mcp.auth.tokenEnv) {
|
|
205
258
|
throw new Error(`MCP '${mcp.name}' es type=gateway pero no trae tokenEnv`);
|
|
@@ -246,9 +299,7 @@ export function removeTomlBlock(content, name) {
|
|
|
246
299
|
return content.slice(0, start) + content.slice(stop);
|
|
247
300
|
}
|
|
248
301
|
function registerMcpInTomlBlock(mcp, spec) {
|
|
249
|
-
const existing = existsSync(spec.mcpConfigPath)
|
|
250
|
-
? readFileSync(spec.mcpConfigPath, "utf-8")
|
|
251
|
-
: "";
|
|
302
|
+
const existing = existsSync(spec.mcpConfigPath) ? readFileSync(spec.mcpConfigPath, "utf-8") : "";
|
|
252
303
|
const next = upsertTomlBlock(existing, mcp.name, renderMcpToml(mcp));
|
|
253
304
|
if (next === existing)
|
|
254
305
|
return;
|
|
@@ -271,7 +322,83 @@ function resolveTargets(opts) {
|
|
|
271
322
|
if (ids.length === 0) {
|
|
272
323
|
ids.push("claude-code");
|
|
273
324
|
}
|
|
274
|
-
|
|
325
|
+
const specs = ids.map((id) => TARGETS[id]);
|
|
326
|
+
for (const spec of specs)
|
|
327
|
+
cleanupLegacySkillArtifacts(spec);
|
|
328
|
+
return specs;
|
|
329
|
+
}
|
|
330
|
+
const LEGACY_RENAMED_SKILLS = ["alanube", "alegra-producto", "alegra-ce-universe"];
|
|
331
|
+
function stripMcpEntries(path, names) {
|
|
332
|
+
if (!existsSync(path) || names.length === 0)
|
|
333
|
+
return;
|
|
334
|
+
let cfg;
|
|
335
|
+
try {
|
|
336
|
+
cfg = JSON.parse(readFileSync(path, "utf-8"));
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
let changed = false;
|
|
342
|
+
for (const key of ["mcpServers", "mcp"]) {
|
|
343
|
+
const section = cfg[key];
|
|
344
|
+
if (!section || typeof section !== "object")
|
|
345
|
+
continue;
|
|
346
|
+
for (const n of names) {
|
|
347
|
+
if (n in section) {
|
|
348
|
+
delete section[n];
|
|
349
|
+
changed = true;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
if (Object.keys(section).length === 0)
|
|
353
|
+
delete cfg[key];
|
|
354
|
+
}
|
|
355
|
+
if (!changed)
|
|
356
|
+
return;
|
|
357
|
+
backupIfExists(path);
|
|
358
|
+
atomicWriteFile(path, `${JSON.stringify(cfg, null, 2)}\n`);
|
|
359
|
+
}
|
|
360
|
+
function cleanupLegacySkillArtifacts(spec) {
|
|
361
|
+
try {
|
|
362
|
+
if (spec.skillFormat === "folder") {
|
|
363
|
+
for (const old of LEGACY_RENAMED_SKILLS) {
|
|
364
|
+
const dir = join(spec.skillsDir, old);
|
|
365
|
+
if (existsSync(dir))
|
|
366
|
+
rmSync(dir, { recursive: true, force: true });
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
const lock = readLockFile();
|
|
370
|
+
if (spec.id === "opencode" || spec.id === "cursor") {
|
|
371
|
+
const legacyDir = spec.id === "opencode" ? getOpenCodeLegacyPromptsDir() : getCursorLegacyRulesDir();
|
|
372
|
+
const ext = spec.id === "opencode" ? ".md" : ".mdc";
|
|
373
|
+
const managed = [...Object.keys(lock.skills), ...LEGACY_RENAMED_SKILLS];
|
|
374
|
+
for (const name of managed) {
|
|
375
|
+
const f = join(legacyDir, `${name}${ext}`);
|
|
376
|
+
if (existsSync(f))
|
|
377
|
+
rmSync(f, { force: true });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const ourMcps = Object.keys(lock.mcps ?? {});
|
|
381
|
+
if (spec.id === "claude-code") {
|
|
382
|
+
stripMcpEntries(getClaudeSettingsPath(), ourMcps);
|
|
383
|
+
}
|
|
384
|
+
if (spec.id === "antigravity") {
|
|
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
|
+
}
|
|
395
|
+
}
|
|
396
|
+
stripMcpEntries(getAntigravityLegacyMcpConfigPath(), ourMcps);
|
|
397
|
+
stripMcpEntries(getAntigravityGeminiLegacyMcpConfigPath(), ourMcps);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
catch {
|
|
401
|
+
}
|
|
275
402
|
}
|
|
276
403
|
export async function installSkill(name, manifest, opts = {}) {
|
|
277
404
|
const entry = manifest.skills[name];
|
|
@@ -305,27 +432,13 @@ export async function installSkill(name, manifest, opts = {}) {
|
|
|
305
432
|
targets: targetIds,
|
|
306
433
|
};
|
|
307
434
|
}
|
|
435
|
+
let staged;
|
|
308
436
|
try {
|
|
309
|
-
|
|
310
|
-
for (const t of targets) {
|
|
311
|
-
writeSkillToTarget(name, staged, t);
|
|
312
|
-
}
|
|
313
|
-
lock.skills[name] = {
|
|
314
|
-
name,
|
|
315
|
-
installedFrom: getPackageVersion(),
|
|
316
|
-
installedAt: new Date().toISOString(),
|
|
317
|
-
bundleHash: entry.bundleHash,
|
|
318
|
-
targets: targetIds,
|
|
319
|
-
};
|
|
320
|
-
writeLockFile(lock);
|
|
321
|
-
return {
|
|
322
|
-
name,
|
|
323
|
-
kind: "skill",
|
|
324
|
-
status: prev ? "updated" : "installed",
|
|
325
|
-
targets: targetIds,
|
|
326
|
-
};
|
|
437
|
+
staged = await stageSkill(name, entry);
|
|
327
438
|
}
|
|
328
439
|
catch (err) {
|
|
440
|
+
if (err instanceof AuthRequiredError)
|
|
441
|
+
throw err;
|
|
329
442
|
return {
|
|
330
443
|
name,
|
|
331
444
|
kind: "skill",
|
|
@@ -334,6 +447,35 @@ export async function installSkill(name, manifest, opts = {}) {
|
|
|
334
447
|
error: err instanceof Error ? err.message : String(err),
|
|
335
448
|
};
|
|
336
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
|
+
};
|
|
337
479
|
}
|
|
338
480
|
export function installMcp(mcp, opts = {}) {
|
|
339
481
|
try {
|
|
@@ -375,34 +517,41 @@ export function installMcp(mcp, opts = {}) {
|
|
|
375
517
|
targets: targetIds,
|
|
376
518
|
};
|
|
377
519
|
}
|
|
378
|
-
|
|
379
|
-
|
|
520
|
+
const okTargets = [];
|
|
521
|
+
const failures = [];
|
|
522
|
+
for (const t of wantedTargets) {
|
|
523
|
+
try {
|
|
380
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)}`);
|
|
381
529
|
}
|
|
382
|
-
lock.mcps[mcp.name] = {
|
|
383
|
-
name: mcp.name,
|
|
384
|
-
installedFrom: getPackageVersion(),
|
|
385
|
-
installedAt: new Date().toISOString(),
|
|
386
|
-
configHash: mcpConfigHash(mcp),
|
|
387
|
-
targets: targetIds,
|
|
388
|
-
};
|
|
389
|
-
writeLockFile(lock);
|
|
390
|
-
return {
|
|
391
|
-
name: mcp.name,
|
|
392
|
-
kind: "mcp",
|
|
393
|
-
status: prev ? "updated" : "installed",
|
|
394
|
-
targets: targetIds,
|
|
395
|
-
};
|
|
396
530
|
}
|
|
397
|
-
|
|
531
|
+
if (okTargets.length === 0) {
|
|
398
532
|
return {
|
|
399
533
|
name: mcp.name,
|
|
400
534
|
kind: "mcp",
|
|
401
535
|
status: "failed",
|
|
402
536
|
targets: targetIds,
|
|
403
|
-
error:
|
|
537
|
+
error: failures.join("; "),
|
|
404
538
|
};
|
|
405
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
|
+
};
|
|
406
555
|
}
|
|
407
556
|
export async function installAll(opts = {}) {
|
|
408
557
|
let manifest;
|
|
@@ -515,6 +664,15 @@ export function uninstallAll(opts = {}) {
|
|
|
515
664
|
results.push(uninstallSkill(name, opts));
|
|
516
665
|
for (const name of Object.keys(lock.mcps ?? {}))
|
|
517
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
|
+
}
|
|
518
676
|
return results;
|
|
519
677
|
}
|
|
520
678
|
export function detectDrift(manifest) {
|
package/dist/lib.js
CHANGED
|
@@ -31,13 +31,24 @@ export function getClaudeSkillsDir() {
|
|
|
31
31
|
export function getClaudeSettingsPath() {
|
|
32
32
|
return join(homedir(), ".claude", "settings.json");
|
|
33
33
|
}
|
|
34
|
+
export function getClaudeJsonPath() {
|
|
35
|
+
return join(homedir(), ".claude.json");
|
|
36
|
+
}
|
|
34
37
|
export function getCursorSkillsDir() {
|
|
38
|
+
return join(homedir(), ".cursor", "skills");
|
|
39
|
+
}
|
|
40
|
+
export function getCursorLegacyRulesDir() {
|
|
35
41
|
return join(homedir(), ".cursor", "rules");
|
|
36
42
|
}
|
|
37
43
|
export function getCursorMcpPath() {
|
|
38
44
|
return join(homedir(), ".cursor", "mcp.json");
|
|
39
45
|
}
|
|
40
46
|
export function getOpenCodeSkillsDir() {
|
|
47
|
+
const xdgHome = process.env.XDG_CONFIG_HOME;
|
|
48
|
+
const base = xdgHome && xdgHome.length > 0 ? xdgHome : join(homedir(), ".config");
|
|
49
|
+
return join(base, "opencode", "skills");
|
|
50
|
+
}
|
|
51
|
+
export function getOpenCodeLegacyPromptsDir() {
|
|
41
52
|
const xdgHome = process.env.XDG_CONFIG_HOME;
|
|
42
53
|
const base = xdgHome && xdgHome.length > 0 ? xdgHome : join(homedir(), ".config");
|
|
43
54
|
return join(base, "opencode", "prompts");
|
|
@@ -48,11 +59,42 @@ export function getOpenCodeConfigPath() {
|
|
|
48
59
|
return join(base, "opencode", "opencode.json");
|
|
49
60
|
}
|
|
50
61
|
export function getAntigravitySkillsDir() {
|
|
62
|
+
return join(homedir(), ".gemini", "skills");
|
|
63
|
+
}
|
|
64
|
+
export function getAntigravityMcpConfigPath() {
|
|
65
|
+
return join(homedir(), ".gemini", "config", "mcp_config.json");
|
|
66
|
+
}
|
|
67
|
+
export function getGeminiSettingsPath() {
|
|
68
|
+
return join(homedir(), ".gemini", "settings.json");
|
|
69
|
+
}
|
|
70
|
+
export function getAntigravityLegacySkillsDir() {
|
|
51
71
|
return join(homedir(), ".antigravity", "skills");
|
|
52
72
|
}
|
|
53
|
-
export function
|
|
73
|
+
export function getAntigravityLegacyMcpConfigPath() {
|
|
54
74
|
return join(homedir(), ".antigravity", "settings.json");
|
|
55
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
|
+
}
|
|
56
98
|
export function getCodexSkillsDir() {
|
|
57
99
|
return join(homedir(), ".agents", "skills");
|
|
58
100
|
}
|
|
@@ -78,13 +120,13 @@ export function getTargetSkillsDir(target) {
|
|
|
78
120
|
export function getTargetMcpConfigPath(target) {
|
|
79
121
|
switch (target) {
|
|
80
122
|
case "claude-code":
|
|
81
|
-
return
|
|
123
|
+
return getClaudeJsonPath();
|
|
82
124
|
case "cursor":
|
|
83
125
|
return getCursorMcpPath();
|
|
84
126
|
case "opencode":
|
|
85
127
|
return getOpenCodeConfigPath();
|
|
86
128
|
case "antigravity":
|
|
87
|
-
return
|
|
129
|
+
return getAntigravityMcpConfigPath();
|
|
88
130
|
case "codex":
|
|
89
131
|
return getCodexConfigPath();
|
|
90
132
|
}
|
|
@@ -100,6 +142,9 @@ export function getConfigDir() {
|
|
|
100
142
|
export function getAuthFilePath() {
|
|
101
143
|
return join(getConfigDir(), "auth.json");
|
|
102
144
|
}
|
|
145
|
+
export function getAutoUpdateLogPath() {
|
|
146
|
+
return join(getConfigDir(), "autoupdate.log");
|
|
147
|
+
}
|
|
103
148
|
let _cachedVersion = null;
|
|
104
149
|
export function getPackageVersion() {
|
|
105
150
|
if (_cachedVersion)
|
|
@@ -236,3 +281,10 @@ export function ensureDir(path) {
|
|
|
236
281
|
if (!existsSync(path))
|
|
237
282
|
mkdirSync(path, { recursive: true });
|
|
238
283
|
}
|
|
284
|
+
export function adaptStdioCommand(command, args, platform = process.platform) {
|
|
285
|
+
const winShims = new Set(["npx", "npm", "pnpm", "sf", "uvx", "bunx", "yarn", "deno"]);
|
|
286
|
+
if (platform === "win32" && winShims.has(command)) {
|
|
287
|
+
return { command: "cmd", args: ["/c", command, ...args] };
|
|
288
|
+
}
|
|
289
|
+
return { command, args };
|
|
290
|
+
}
|
package/dist/render.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { extname, join } from "node:path";
|
|
3
|
-
import { parseFrontmatter } from "./lib.js";
|
|
3
|
+
import { adaptStdioCommand, parseFrontmatter } from "./lib.js";
|
|
4
4
|
function readSkillEntryPoint(skillDir) {
|
|
5
5
|
const candidates = ["SKILL.md", "README.md"];
|
|
6
6
|
for (const c of candidates) {
|
|
@@ -71,6 +71,9 @@ export function inlineReferences(refs) {
|
|
|
71
71
|
return `\n\n---\n\n# Referencias (incluidas inline)\n\n> Este cliente instala la skill como un solo archivo; el contenido de \`references/\` se incluye acá para que las menciones del cuerpo sigan disponibles.\n\n${sections.join("\n\n")}\n`;
|
|
72
72
|
}
|
|
73
73
|
export function renderMcpForTarget(mcp, spec) {
|
|
74
|
+
if (mcp.transport === "stdio") {
|
|
75
|
+
return renderStdioMcpForTarget(mcp, spec);
|
|
76
|
+
}
|
|
74
77
|
const obj = {
|
|
75
78
|
[spec.mcpUrlKey]: mcp.url,
|
|
76
79
|
};
|
|
@@ -88,6 +91,20 @@ export function renderMcpForTarget(mcp, spec) {
|
|
|
88
91
|
}
|
|
89
92
|
return obj;
|
|
90
93
|
}
|
|
94
|
+
function renderStdioMcpForTarget(mcp, spec) {
|
|
95
|
+
const { command, args } = adaptStdioCommand(mcp.command ?? "", mcp.args ?? []);
|
|
96
|
+
const hasEnv = mcp.env && Object.keys(mcp.env).length > 0;
|
|
97
|
+
if (spec.id === "opencode") {
|
|
98
|
+
const obj = { type: "local", command: [command, ...args], enabled: true };
|
|
99
|
+
if (hasEnv)
|
|
100
|
+
obj.environment = mcp.env;
|
|
101
|
+
return obj;
|
|
102
|
+
}
|
|
103
|
+
const obj = { command, args };
|
|
104
|
+
if (hasEnv)
|
|
105
|
+
obj.env = mcp.env;
|
|
106
|
+
return obj;
|
|
107
|
+
}
|
|
91
108
|
function bearerInterpolation(env, target) {
|
|
92
109
|
switch (target) {
|
|
93
110
|
case "claude-code":
|
package/dist/targets.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { getAntigravityMcpConfigPath, getAntigravitySkillsDir, getClaudeJsonPath, getClaudeSkillsDir, getCodexConfigPath, getCodexSkillsDir, getCursorMcpPath, getCursorSkillsDir, getOpenCodeConfigPath, getOpenCodeSkillsDir, } from "./lib.js";
|
|
5
5
|
export const TARGETS = {
|
|
6
6
|
"claude-code": {
|
|
7
7
|
id: "claude-code",
|
|
8
8
|
label: "Claude Code",
|
|
9
9
|
skillsDir: getClaudeSkillsDir(),
|
|
10
10
|
skillFormat: "folder",
|
|
11
|
-
mcpConfigPath:
|
|
11
|
+
mcpConfigPath: getClaudeJsonPath(),
|
|
12
12
|
mcpRootKey: "mcpServers",
|
|
13
13
|
mcpUrlKey: "url",
|
|
14
14
|
},
|
|
@@ -16,7 +16,7 @@ export const TARGETS = {
|
|
|
16
16
|
id: "cursor",
|
|
17
17
|
label: "Cursor",
|
|
18
18
|
skillsDir: getCursorSkillsDir(),
|
|
19
|
-
skillFormat: "
|
|
19
|
+
skillFormat: "folder",
|
|
20
20
|
mcpConfigPath: getCursorMcpPath(),
|
|
21
21
|
mcpRootKey: "mcpServers",
|
|
22
22
|
mcpUrlKey: "url",
|
|
@@ -25,7 +25,7 @@ export const TARGETS = {
|
|
|
25
25
|
id: "opencode",
|
|
26
26
|
label: "OpenCode",
|
|
27
27
|
skillsDir: getOpenCodeSkillsDir(),
|
|
28
|
-
skillFormat: "
|
|
28
|
+
skillFormat: "folder",
|
|
29
29
|
mcpConfigPath: getOpenCodeConfigPath(),
|
|
30
30
|
mcpRootKey: "mcp",
|
|
31
31
|
mcpUrlKey: "url",
|
|
@@ -36,7 +36,7 @@ export const TARGETS = {
|
|
|
36
36
|
label: "Antigravity",
|
|
37
37
|
skillsDir: getAntigravitySkillsDir(),
|
|
38
38
|
skillFormat: "folder",
|
|
39
|
-
mcpConfigPath:
|
|
39
|
+
mcpConfigPath: getAntigravityMcpConfigPath(),
|
|
40
40
|
mcpRootKey: "mcpServers",
|
|
41
41
|
mcpUrlKey: "serverUrl",
|
|
42
42
|
},
|
|
@@ -63,7 +63,7 @@ export function detectInstalledTargets() {
|
|
|
63
63
|
"claude-code": join(home, ".claude"),
|
|
64
64
|
cursor: join(home, ".cursor"),
|
|
65
65
|
opencode: join(xdgConfig, "opencode"),
|
|
66
|
-
antigravity: join(home, ".antigravity"),
|
|
66
|
+
antigravity: join(home, ".gemini", "antigravity"),
|
|
67
67
|
codex: codexHome,
|
|
68
68
|
};
|
|
69
69
|
const out = [];
|
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
|