alegra-data-agent 0.2.1 → 0.2.3
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/README.md +1 -1
- package/dist/api.js +1 -1
- package/dist/auth.js +2 -2
- package/dist/bounce.js +56 -0
- package/dist/commands/check.js +1 -1
- package/dist/commands/hook.js +27 -4
- package/dist/commands/install.js +1 -1
- package/dist/commands/status.js +2 -2
- package/dist/commands/update.js +4 -0
- package/dist/commands/whoami.js +1 -1
- package/dist/installer.js +113 -7
- package/dist/lib.js +33 -4
- package/dist/main.js +9 -2
- package/dist/render.js +18 -1
- package/dist/targets.js +6 -6
- package/dist/ui.js +3 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ Solo accesible para cuentas `@alegra.com` (OAuth Google). El CLI te abre el nave
|
|
|
14
14
|
|
|
15
15
|
- Node ≥ 22.6.0
|
|
16
16
|
- pnpm ≥ 11.1.2
|
|
17
|
-
- Uno de: Claude Code, Cursor, OpenCode, Antigravity
|
|
17
|
+
- Uno de: Claude Code, Cursor, OpenCode, Antigravity, Codex
|
|
18
18
|
|
|
19
19
|
## Soporte
|
|
20
20
|
|
package/dist/api.js
CHANGED
|
@@ -79,7 +79,7 @@ async function authedFetch(path, opts = {}) {
|
|
|
79
79
|
if (res.status === 401) {
|
|
80
80
|
if (opts.requireAuth === false)
|
|
81
81
|
return res;
|
|
82
|
-
throw new AuthRequiredError(`
|
|
82
|
+
throw new AuthRequiredError(`Sesión expirada. Agente: ejecutá vos mismo \`pnpm dlx ${COMMAND_NAME} login\` — al usuario solo le toca el click del navegador.`);
|
|
83
83
|
}
|
|
84
84
|
if (res.status === 403) {
|
|
85
85
|
throw new GatewayError(403, path, "Forbidden — tu cuenta no es @alegra.com o tu acceso fue revocado. " +
|
package/dist/auth.js
CHANGED
|
@@ -252,8 +252,8 @@ function htmlPage(kind, subject) {
|
|
|
252
252
|
const color = ok ? "#10b981" : "#ef4444";
|
|
253
253
|
const title = ok ? "Autenticación exitosa" : "Error";
|
|
254
254
|
const body = ok
|
|
255
|
-
? `Conectaste como <strong>${escapeHtml(subject)}</strong>.<br/>Ya puedes cerrar esta pestaña y volver
|
|
256
|
-
: `<strong>${escapeHtml(subject)}</strong><br/>
|
|
255
|
+
? `Conectaste como <strong>${escapeHtml(subject)}</strong>.<br/>Ya puedes cerrar esta pestaña y volver a tu agente.`
|
|
256
|
+
: `<strong>${escapeHtml(subject)}</strong><br/>Volvé a tu agente e intentá de nuevo — él se encarga.`;
|
|
257
257
|
return `<!doctype html>
|
|
258
258
|
<html lang="es">
|
|
259
259
|
<head>
|
package/dist/bounce.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { COMMAND_NAME, getPackageVersion } from "./lib.js";
|
|
3
|
+
export const NO_BOUNCE_ENV = "ALEGRA_DATA_AGENT_NO_BOUNCE";
|
|
4
|
+
const REGISTRY_URL = `https://registry.npmjs.org/${COMMAND_NAME}`;
|
|
5
|
+
const FETCH_TIMEOUT_MS = 2_500;
|
|
6
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+$/;
|
|
7
|
+
export function compareSemver(a, b) {
|
|
8
|
+
const pa = a.split(".").map((n) => Number.parseInt(n, 10));
|
|
9
|
+
const pb = b.split(".").map((n) => Number.parseInt(n, 10));
|
|
10
|
+
for (let i = 0; i < 3; i++) {
|
|
11
|
+
const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
|
|
12
|
+
if (diff !== 0)
|
|
13
|
+
return diff;
|
|
14
|
+
}
|
|
15
|
+
return 0;
|
|
16
|
+
}
|
|
17
|
+
export function shouldBounce(current, latest) {
|
|
18
|
+
if (!latest)
|
|
19
|
+
return false;
|
|
20
|
+
if (!SEMVER_RE.test(latest))
|
|
21
|
+
return false;
|
|
22
|
+
if (!SEMVER_RE.test(current))
|
|
23
|
+
return false;
|
|
24
|
+
return compareSemver(latest, current) > 0;
|
|
25
|
+
}
|
|
26
|
+
async function fetchLatestVersion() {
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch(REGISTRY_URL, {
|
|
29
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
30
|
+
headers: { accept: "application/vnd.npm.install-v1+json" },
|
|
31
|
+
});
|
|
32
|
+
if (!res.ok)
|
|
33
|
+
return null;
|
|
34
|
+
const data = (await res.json());
|
|
35
|
+
return data["dist-tags"]?.latest ?? null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export async function maybeBounceToLatest(argv) {
|
|
42
|
+
if (process.env[NO_BOUNCE_ENV] === "1")
|
|
43
|
+
return;
|
|
44
|
+
const current = getPackageVersion();
|
|
45
|
+
const latest = await fetchLatestVersion();
|
|
46
|
+
if (!shouldBounce(current, latest))
|
|
47
|
+
return;
|
|
48
|
+
const child = spawnSync("pnpm", ["dlx", "--config.minimumReleaseAge=0", `${COMMAND_NAME}@${latest}`, ...argv], {
|
|
49
|
+
stdio: "inherit",
|
|
50
|
+
env: { ...process.env, [NO_BOUNCE_ENV]: "1" },
|
|
51
|
+
shell: process.platform === "win32",
|
|
52
|
+
});
|
|
53
|
+
if (child.error)
|
|
54
|
+
return;
|
|
55
|
+
process.exit(child.status ?? 0);
|
|
56
|
+
}
|
package/dist/commands/check.js
CHANGED
|
@@ -24,7 +24,7 @@ export async function runCheck() {
|
|
|
24
24
|
lines.push(` ${cyan("↻")} ${name} ${dim("(versión nueva)")}`);
|
|
25
25
|
}
|
|
26
26
|
lines.push("");
|
|
27
|
-
lines.push(` ${dim("
|
|
27
|
+
lines.push(` ${dim("Agente: ejecutá vos mismo")} ${bold(`pnpm dlx ${COMMAND_NAME} update --yes`)} ${dim("— el usuario no corre comandos.")}`);
|
|
28
28
|
lines.push("");
|
|
29
29
|
process.stdout.write(lines.join("\n"));
|
|
30
30
|
return 0;
|
package/dist/commands/hook.js
CHANGED
|
@@ -8,7 +8,7 @@ function hookCommandFor(mode) {
|
|
|
8
8
|
const minor = parts[1] ?? "0";
|
|
9
9
|
const range = `~${major}.${minor}.0`;
|
|
10
10
|
const verb = mode === "auto" ? "update --yes" : "check";
|
|
11
|
-
return `pnpm dlx --config.minimumReleaseAge=0 ${COMMAND_NAME}@${range} ${verb}`;
|
|
11
|
+
return `pnpm dlx --config.minimumReleaseAge=0 ${COMMAND_NAME}@${range} ${verb} --no-bounce`;
|
|
12
12
|
}
|
|
13
13
|
const HOOK_SIGNATURE = COMMAND_NAME;
|
|
14
14
|
function readSettings() {
|
|
@@ -19,7 +19,7 @@ function readSettings() {
|
|
|
19
19
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
20
20
|
}
|
|
21
21
|
catch {
|
|
22
|
-
logError(`No se pudo parsear ${path}.
|
|
22
|
+
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
23
|
return {};
|
|
24
24
|
}
|
|
25
25
|
}
|
|
@@ -41,7 +41,7 @@ function isOurMatcher(m) {
|
|
|
41
41
|
function buildOurMatcher(command) {
|
|
42
42
|
return { hooks: [{ type: "command", command }] };
|
|
43
43
|
}
|
|
44
|
-
export function runHookInstall(mode = "
|
|
44
|
+
export function runHookInstall(mode = "auto") {
|
|
45
45
|
const settings = readSettings();
|
|
46
46
|
settings.hooks = settings.hooks ?? {};
|
|
47
47
|
const command = hookCommandFor(mode);
|
|
@@ -49,7 +49,7 @@ export function runHookInstall(mode = "check") {
|
|
|
49
49
|
const compact = process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE === "1";
|
|
50
50
|
if (typeof existing === "string") {
|
|
51
51
|
if (!existing.includes(HOOK_SIGNATURE)) {
|
|
52
|
-
logWarn(`Ya hay un SessionStart hook
|
|
52
|
+
logWarn(`Ya hay un SessionStart hook ajeno en formato string legacy:\n ${existing}\n No lo sobrescribo. Agente: convertilo vos mismo a formato array preservando su contenido — no le pidas al usuario editar JSON.`);
|
|
53
53
|
return 1;
|
|
54
54
|
}
|
|
55
55
|
settings.hooks.SessionStart = [buildOurMatcher(command)];
|
|
@@ -82,6 +82,29 @@ export function runHookInstall(mode = "check") {
|
|
|
82
82
|
}
|
|
83
83
|
return 0;
|
|
84
84
|
}
|
|
85
|
+
export function refreshHookCommand() {
|
|
86
|
+
try {
|
|
87
|
+
const settings = readSettings();
|
|
88
|
+
if (!settings.hooks)
|
|
89
|
+
return;
|
|
90
|
+
const existing = settings.hooks.SessionStart;
|
|
91
|
+
if (!Array.isArray(existing))
|
|
92
|
+
return;
|
|
93
|
+
const ours = existing.find(isOurMatcher);
|
|
94
|
+
if (!ours)
|
|
95
|
+
return;
|
|
96
|
+
const current = ours.hooks.find((h) => h.type === "command")?.command ?? "";
|
|
97
|
+
const fresh = hookCommandFor("auto");
|
|
98
|
+
if (current === fresh)
|
|
99
|
+
return;
|
|
100
|
+
const others = existing.filter((m) => !isOurMatcher(m));
|
|
101
|
+
others.push(buildOurMatcher(fresh));
|
|
102
|
+
settings.hooks.SessionStart = others;
|
|
103
|
+
writeSettings(settings);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
}
|
|
107
|
+
}
|
|
85
108
|
export function runHookUninstall() {
|
|
86
109
|
const settings = readSettings();
|
|
87
110
|
const existing = settings.hooks?.SessionStart;
|
package/dist/commands/install.js
CHANGED
|
@@ -273,7 +273,7 @@ function printCompactSummary(results, manifest, email) {
|
|
|
273
273
|
.map((r) => r.name)
|
|
274
274
|
.sort()
|
|
275
275
|
.join(", "))}`);
|
|
276
|
-
log(` ${gray("destino:")} ${cyan(getClaudeSkillsDir())}${gray(" · ")}${bold(yellow("reinicia tu cliente para
|
|
276
|
+
log(` ${gray("destino:")} ${cyan(getClaudeSkillsDir())}${gray(" · ")}${bold(yellow("reinicia tu cliente para activar skills y MCPs"))}`);
|
|
277
277
|
if (unchanged > 0 && installed === 0 && updated === 0) {
|
|
278
278
|
log(` ${dim(`(${unchanged} sin cambios desde la última instalación)`)}`);
|
|
279
279
|
}
|
package/dist/commands/status.js
CHANGED
|
@@ -48,10 +48,10 @@ export async function runStatus() {
|
|
|
48
48
|
log("");
|
|
49
49
|
const total = drift.toInstall.length + drift.toUpdate.length;
|
|
50
50
|
if (total > 0) {
|
|
51
|
-
logWarn(`Hay ${total} skill(s) por actualizar.
|
|
51
|
+
logWarn(`Hay ${total} skill(s) por actualizar. Agente: ejecutá vos mismo ${bold(`pnpm dlx ${COMMAND_NAME} update --yes`)} — el usuario no corre comandos.`);
|
|
52
52
|
}
|
|
53
53
|
else if (Object.keys(lock.skills).length === 0) {
|
|
54
|
-
logWarn(`Aún no hay skills instaladas.
|
|
54
|
+
logWarn(`Aún no hay skills instaladas. Agente: ejecutá vos mismo ${bold(`pnpm dlx ${COMMAND_NAME}`)} — el usuario no corre comandos.`);
|
|
55
55
|
}
|
|
56
56
|
else {
|
|
57
57
|
log(` ${green("Todo al día.")}`);
|
package/dist/commands/update.js
CHANGED
|
@@ -3,6 +3,7 @@ import { hasValidAuth } from "../auth.js";
|
|
|
3
3
|
import { cyan, dim, gray, green, log, logError, yellow } from "../colors.js";
|
|
4
4
|
import { detectDrift, installAll, summarize } from "../installer.js";
|
|
5
5
|
import { banner } from "../ui.js";
|
|
6
|
+
import { refreshHookCommand } from "./hook.js";
|
|
6
7
|
import { runLogin } from "./login.js";
|
|
7
8
|
export async function runUpdate(opts = {}) {
|
|
8
9
|
banner();
|
|
@@ -49,6 +50,8 @@ async function runUpdateOnce(opts) {
|
|
|
49
50
|
];
|
|
50
51
|
if (pending.length === 0) {
|
|
51
52
|
log(green(" ✓ Todo al día (skills + MCPs).\n"));
|
|
53
|
+
if (!opts.dryRun)
|
|
54
|
+
refreshHookCommand();
|
|
52
55
|
return 0;
|
|
53
56
|
}
|
|
54
57
|
log(cyan(` → ${pending.length} elemento(s) por actualizar:\n`));
|
|
@@ -71,6 +74,7 @@ async function runUpdateOnce(opts) {
|
|
|
71
74
|
return 1;
|
|
72
75
|
}
|
|
73
76
|
summarize(result.results);
|
|
77
|
+
refreshHookCommand();
|
|
74
78
|
const failed = result.results.some((r) => r.status === "failed");
|
|
75
79
|
return failed ? 1 : 0;
|
|
76
80
|
}
|
package/dist/commands/whoami.js
CHANGED
|
@@ -4,7 +4,7 @@ import { COMMAND_NAME, getAuthFilePath } from "../lib.js";
|
|
|
4
4
|
export function runWhoami() {
|
|
5
5
|
const auth = readAuth();
|
|
6
6
|
if (!auth) {
|
|
7
|
-
logWarn(`No autenticado.
|
|
7
|
+
logWarn(`No autenticado. Agente: ejecutá vos mismo \`pnpm dlx ${COMMAND_NAME} login\` — al usuario solo le toca el click del navegador.`);
|
|
8
8
|
return 1;
|
|
9
9
|
}
|
|
10
10
|
log("");
|
package/dist/installer.js
CHANGED
|
@@ -3,7 +3,7 @@ import { copyFileSync, existsSync, readFileSync, renameSync, rmSync, writeFileSy
|
|
|
3
3
|
import { dirname, join, resolve, sep } from "node:path";
|
|
4
4
|
import { 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, getLockFilePath, getAntigravityLegacyMcpConfigPath, getAntigravityLegacySkillsDir, getClaudeSettingsPath, getCursorLegacyRulesDir, 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
|
});
|
|
@@ -99,7 +103,7 @@ function writeSkillToTarget(skillName, staged, spec) {
|
|
|
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,31 @@ 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
|
+
}
|
|
147
154
|
function atomicWriteFile(path, content) {
|
|
148
155
|
ensureDir(dirname(path));
|
|
149
156
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
150
157
|
writeFileSync(tmp, content);
|
|
151
|
-
|
|
158
|
+
for (let attempt = 0;; attempt++) {
|
|
159
|
+
try {
|
|
160
|
+
renameSync(tmp, path);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
catch (err) {
|
|
164
|
+
const code = err.code;
|
|
165
|
+
if (attempt < 4 && (code === "EPERM" || code === "EBUSY" || code === "EACCES")) {
|
|
166
|
+
sleepSync(150);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
rmSync(tmp, { force: true });
|
|
171
|
+
}
|
|
172
|
+
catch { }
|
|
173
|
+
throw err;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
152
176
|
}
|
|
153
177
|
function backupIfExists(path) {
|
|
154
178
|
if (!existsSync(path))
|
|
@@ -163,7 +187,7 @@ function readJsonOrEmpty(path) {
|
|
|
163
187
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
164
188
|
}
|
|
165
189
|
catch {
|
|
166
|
-
throw new Error(`${path} no es JSON válido.
|
|
190
|
+
throw new Error(`${path} no es JSON válido. Agente: respaldalo vos mismo (renombralo a .bak) y volvé a correr la instalación — no le pidas al usuario tocar archivos.`);
|
|
167
191
|
}
|
|
168
192
|
}
|
|
169
193
|
function registerMcpInTarget(mcp, spec) {
|
|
@@ -199,7 +223,20 @@ function tomlBasicString(s) {
|
|
|
199
223
|
return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
200
224
|
}
|
|
201
225
|
export function renderMcpToml(mcp) {
|
|
202
|
-
|
|
226
|
+
if (mcp.transport === "stdio") {
|
|
227
|
+
const { command, args } = adaptStdioCommand(mcp.command ?? "", mcp.args ?? []);
|
|
228
|
+
const lines = [`[mcp_servers.${mcp.name}]`, `command = ${tomlBasicString(command)}`];
|
|
229
|
+
if (args.length > 0) {
|
|
230
|
+
lines.push(`args = [${args.map((a) => tomlBasicString(a)).join(", ")}]`);
|
|
231
|
+
}
|
|
232
|
+
if (mcp.env && Object.keys(mcp.env).length > 0) {
|
|
233
|
+
lines.push(`[mcp_servers.${mcp.name}.env]`);
|
|
234
|
+
for (const [k, v] of Object.entries(mcp.env))
|
|
235
|
+
lines.push(`${k} = ${tomlBasicString(v)}`);
|
|
236
|
+
}
|
|
237
|
+
return lines.join("\n");
|
|
238
|
+
}
|
|
239
|
+
const lines = [`[mcp_servers.${mcp.name}]`, `url = ${tomlBasicString(mcp.url ?? "")}`];
|
|
203
240
|
if (mcp.auth.type === "gateway") {
|
|
204
241
|
if (!mcp.auth.tokenEnv) {
|
|
205
242
|
throw new Error(`MCP '${mcp.name}' es type=gateway pero no trae tokenEnv`);
|
|
@@ -271,7 +308,76 @@ function resolveTargets(opts) {
|
|
|
271
308
|
if (ids.length === 0) {
|
|
272
309
|
ids.push("claude-code");
|
|
273
310
|
}
|
|
274
|
-
|
|
311
|
+
const specs = ids.map((id) => TARGETS[id]);
|
|
312
|
+
for (const spec of specs)
|
|
313
|
+
cleanupLegacySkillArtifacts(spec);
|
|
314
|
+
return specs;
|
|
315
|
+
}
|
|
316
|
+
const LEGACY_RENAMED_SKILLS = ["alanube", "alegra-producto", "alegra-ce-universe"];
|
|
317
|
+
function stripMcpEntries(path, names) {
|
|
318
|
+
if (!existsSync(path) || names.length === 0)
|
|
319
|
+
return;
|
|
320
|
+
let cfg;
|
|
321
|
+
try {
|
|
322
|
+
cfg = JSON.parse(readFileSync(path, "utf-8"));
|
|
323
|
+
}
|
|
324
|
+
catch {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
let changed = false;
|
|
328
|
+
for (const key of ["mcpServers", "mcp"]) {
|
|
329
|
+
const section = cfg[key];
|
|
330
|
+
if (!section || typeof section !== "object")
|
|
331
|
+
continue;
|
|
332
|
+
for (const n of names) {
|
|
333
|
+
if (n in section) {
|
|
334
|
+
delete section[n];
|
|
335
|
+
changed = true;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
if (Object.keys(section).length === 0)
|
|
339
|
+
delete cfg[key];
|
|
340
|
+
}
|
|
341
|
+
if (!changed)
|
|
342
|
+
return;
|
|
343
|
+
backupIfExists(path);
|
|
344
|
+
atomicWriteFile(path, `${JSON.stringify(cfg, null, 2)}\n`);
|
|
345
|
+
}
|
|
346
|
+
function cleanupLegacySkillArtifacts(spec) {
|
|
347
|
+
try {
|
|
348
|
+
if (spec.skillFormat === "folder") {
|
|
349
|
+
for (const old of LEGACY_RENAMED_SKILLS) {
|
|
350
|
+
const dir = join(spec.skillsDir, old);
|
|
351
|
+
if (existsSync(dir))
|
|
352
|
+
rmSync(dir, { recursive: true, force: true });
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const lock = readLockFile();
|
|
356
|
+
if (spec.id === "opencode" || spec.id === "cursor") {
|
|
357
|
+
const legacyDir = spec.id === "opencode" ? getOpenCodeLegacyPromptsDir() : getCursorLegacyRulesDir();
|
|
358
|
+
const ext = spec.id === "opencode" ? ".md" : ".mdc";
|
|
359
|
+
const managed = [...Object.keys(lock.skills), ...LEGACY_RENAMED_SKILLS];
|
|
360
|
+
for (const name of managed) {
|
|
361
|
+
const f = join(legacyDir, `${name}${ext}`);
|
|
362
|
+
if (existsSync(f))
|
|
363
|
+
rmSync(f, { force: true });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const ourMcps = Object.keys(lock.mcps ?? {});
|
|
367
|
+
if (spec.id === "claude-code") {
|
|
368
|
+
stripMcpEntries(getClaudeSettingsPath(), ourMcps);
|
|
369
|
+
}
|
|
370
|
+
if (spec.id === "antigravity") {
|
|
371
|
+
for (const name of [...Object.keys(lock.skills), ...LEGACY_RENAMED_SKILLS]) {
|
|
372
|
+
const d = join(getAntigravityLegacySkillsDir(), name);
|
|
373
|
+
if (existsSync(d))
|
|
374
|
+
rmSync(d, { recursive: true, force: true });
|
|
375
|
+
}
|
|
376
|
+
stripMcpEntries(getAntigravityLegacyMcpConfigPath(), ourMcps);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
catch {
|
|
380
|
+
}
|
|
275
381
|
}
|
|
276
382
|
export async function installSkill(name, manifest, opts = {}) {
|
|
277
383
|
const entry = manifest.skills[name];
|
package/dist/lib.js
CHANGED
|
@@ -8,7 +8,13 @@ export const COMMAND_NAME = "alegra-data-agent";
|
|
|
8
8
|
export const REPO_SLUG = "alegradev/alegra-data-agent";
|
|
9
9
|
export const REPO_DEFAULT_BRANCH = "main";
|
|
10
10
|
export const GATEWAY_URL = process.env.ALEGRA_DATA_AGENT_GATEWAY_URL ?? "https://alegra-data-agent.vercel.app";
|
|
11
|
-
export const ALL_TARGETS = [
|
|
11
|
+
export const ALL_TARGETS = [
|
|
12
|
+
"claude-code",
|
|
13
|
+
"cursor",
|
|
14
|
+
"opencode",
|
|
15
|
+
"antigravity",
|
|
16
|
+
"codex",
|
|
17
|
+
];
|
|
12
18
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
19
|
export function getPackageRoot() {
|
|
14
20
|
return join(__dirname, "..");
|
|
@@ -25,13 +31,24 @@ export function getClaudeSkillsDir() {
|
|
|
25
31
|
export function getClaudeSettingsPath() {
|
|
26
32
|
return join(homedir(), ".claude", "settings.json");
|
|
27
33
|
}
|
|
34
|
+
export function getClaudeJsonPath() {
|
|
35
|
+
return join(homedir(), ".claude.json");
|
|
36
|
+
}
|
|
28
37
|
export function getCursorSkillsDir() {
|
|
38
|
+
return join(homedir(), ".cursor", "skills");
|
|
39
|
+
}
|
|
40
|
+
export function getCursorLegacyRulesDir() {
|
|
29
41
|
return join(homedir(), ".cursor", "rules");
|
|
30
42
|
}
|
|
31
43
|
export function getCursorMcpPath() {
|
|
32
44
|
return join(homedir(), ".cursor", "mcp.json");
|
|
33
45
|
}
|
|
34
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() {
|
|
35
52
|
const xdgHome = process.env.XDG_CONFIG_HOME;
|
|
36
53
|
const base = xdgHome && xdgHome.length > 0 ? xdgHome : join(homedir(), ".config");
|
|
37
54
|
return join(base, "opencode", "prompts");
|
|
@@ -42,9 +59,15 @@ export function getOpenCodeConfigPath() {
|
|
|
42
59
|
return join(base, "opencode", "opencode.json");
|
|
43
60
|
}
|
|
44
61
|
export function getAntigravitySkillsDir() {
|
|
62
|
+
return join(homedir(), ".gemini", "antigravity", "skills");
|
|
63
|
+
}
|
|
64
|
+
export function getAntigravityMcpConfigPath() {
|
|
65
|
+
return join(homedir(), ".gemini", "antigravity", "mcp_config.json");
|
|
66
|
+
}
|
|
67
|
+
export function getAntigravityLegacySkillsDir() {
|
|
45
68
|
return join(homedir(), ".antigravity", "skills");
|
|
46
69
|
}
|
|
47
|
-
export function
|
|
70
|
+
export function getAntigravityLegacyMcpConfigPath() {
|
|
48
71
|
return join(homedir(), ".antigravity", "settings.json");
|
|
49
72
|
}
|
|
50
73
|
export function getCodexSkillsDir() {
|
|
@@ -72,13 +95,13 @@ export function getTargetSkillsDir(target) {
|
|
|
72
95
|
export function getTargetMcpConfigPath(target) {
|
|
73
96
|
switch (target) {
|
|
74
97
|
case "claude-code":
|
|
75
|
-
return
|
|
98
|
+
return getClaudeJsonPath();
|
|
76
99
|
case "cursor":
|
|
77
100
|
return getCursorMcpPath();
|
|
78
101
|
case "opencode":
|
|
79
102
|
return getOpenCodeConfigPath();
|
|
80
103
|
case "antigravity":
|
|
81
|
-
return
|
|
104
|
+
return getAntigravityMcpConfigPath();
|
|
82
105
|
case "codex":
|
|
83
106
|
return getCodexConfigPath();
|
|
84
107
|
}
|
|
@@ -230,3 +253,9 @@ export function ensureDir(path) {
|
|
|
230
253
|
if (!existsSync(path))
|
|
231
254
|
mkdirSync(path, { recursive: true });
|
|
232
255
|
}
|
|
256
|
+
export function adaptStdioCommand(command, args, platform = process.platform) {
|
|
257
|
+
if (platform === "win32" && (command === "npx" || command === "npm" || command === "pnpm")) {
|
|
258
|
+
return { command: "cmd", args: ["/c", command, ...args] };
|
|
259
|
+
}
|
|
260
|
+
return { command, args };
|
|
261
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { maybeBounceToLatest } from "./bounce.js";
|
|
1
2
|
import { log, logError } from "./colors.js";
|
|
2
3
|
import { ALL_TARGETS, getPackageVersion } from "./lib.js";
|
|
3
4
|
import { helpText } from "./ui.js";
|
|
@@ -34,6 +35,7 @@ function parseArgs(argv) {
|
|
|
34
35
|
help: false,
|
|
35
36
|
version: false,
|
|
36
37
|
noHook: false,
|
|
38
|
+
noBounce: false,
|
|
37
39
|
autoUpdate: false,
|
|
38
40
|
};
|
|
39
41
|
const positional = [];
|
|
@@ -50,6 +52,8 @@ function parseArgs(argv) {
|
|
|
50
52
|
flags.force = true;
|
|
51
53
|
else if (a === "--no-hook")
|
|
52
54
|
flags.noHook = true;
|
|
55
|
+
else if (a === "--no-bounce")
|
|
56
|
+
flags.noBounce = true;
|
|
53
57
|
else if (a === "--auto-update")
|
|
54
58
|
flags.autoUpdate = true;
|
|
55
59
|
else if (a === "-h" || a === "--help")
|
|
@@ -87,6 +91,9 @@ function parseArgs(argv) {
|
|
|
87
91
|
}
|
|
88
92
|
async function main() {
|
|
89
93
|
const args = parseArgs(process.argv.slice(2));
|
|
94
|
+
if (!args.flags.noBounce) {
|
|
95
|
+
await maybeBounceToLatest(process.argv.slice(2));
|
|
96
|
+
}
|
|
90
97
|
if (args.flags.version) {
|
|
91
98
|
log(getPackageVersion());
|
|
92
99
|
process.exit(0);
|
|
@@ -114,7 +121,7 @@ async function main() {
|
|
|
114
121
|
only: args.only.length > 0 ? args.only : undefined,
|
|
115
122
|
targets: args.targets.length > 0 ? args.targets : undefined,
|
|
116
123
|
noHook: args.flags.noHook,
|
|
117
|
-
hookMode:
|
|
124
|
+
hookMode: "auto",
|
|
118
125
|
});
|
|
119
126
|
break;
|
|
120
127
|
case "update":
|
|
@@ -139,7 +146,7 @@ async function main() {
|
|
|
139
146
|
break;
|
|
140
147
|
case "hook":
|
|
141
148
|
if (args.subcommand === "install") {
|
|
142
|
-
exitCode = runHookInstall(
|
|
149
|
+
exitCode = runHookInstall("auto");
|
|
143
150
|
}
|
|
144
151
|
else if (args.subcommand === "uninstall") {
|
|
145
152
|
exitCode = runHookUninstall();
|
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
|
@@ -59,9 +59,10 @@ ${bold("Opciones:")}
|
|
|
59
59
|
-y, --yes No pedir confirmación
|
|
60
60
|
--dry-run Mostrar qué se haría, sin tocar archivos
|
|
61
61
|
--only=a,b,c Instalar solo esas skills/MCPs
|
|
62
|
-
--target=NAME Restringir al cliente NAME (claude-code|cursor|opencode|antigravity)
|
|
62
|
+
--target=NAME Restringir al cliente NAME (claude-code|cursor|opencode|antigravity|codex)
|
|
63
63
|
--no-hook No instalar el hook de SessionStart
|
|
64
|
-
--
|
|
64
|
+
--no-bounce No saltar a la última versión publicada (la usa el hook)
|
|
65
|
+
--auto-update Aceptado por compatibilidad — el hook ya es auto-update por default
|
|
65
66
|
-h, --help Mostrar ayuda
|
|
66
67
|
-v, --version Mostrar versión
|
|
67
68
|
|