alegra-data-agent 0.7.0 → 0.7.2
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/autoupdate.js +64 -17
- package/dist/codex-trust.js +128 -0
- package/dist/commands/install.js +35 -0
- package/dist/commands/status.js +11 -0
- package/dist/commands/update.js +9 -1
- package/dist/installer.js +75 -31
- package/dist/lib.js +6 -0
- package/dist/main.js +1 -0
- package/dist/targets.js +7 -3
- package/package.json +1 -1
package/dist/autoupdate.js
CHANGED
|
@@ -4,7 +4,7 @@ import { logWarn } from "./colors.js";
|
|
|
4
4
|
import { hookCommandFor, runHookInstall, runHookUninstall, } from "./commands/hook.js";
|
|
5
5
|
import { HERMES_PLUGIN_NAME, hermesPluginManifest, hermesPluginModule } from "./hermes-plugin.js";
|
|
6
6
|
import { runHermesCliAt } from "./installer.js";
|
|
7
|
-
import { COMMAND_NAME, getClaudeSettingsPath, getCodexHooksPath, getCursorHooksPath, getGeminiSettingsPath, getHermesHome, getHermesPluginsDir, getOpenCodePluginPath, readLockFile, } from "./lib.js";
|
|
7
|
+
import { COMMAND_NAME, getAntigravityHooksPath, getClaudeSettingsPath, getCodexHooksPath, getCursorHooksPath, getGeminiSettingsPath, getHermesHome, getHermesPluginsDir, getOpenCodePluginPath, readLockFile, } from "./lib.js";
|
|
8
8
|
function readJsonSafe(path) {
|
|
9
9
|
if (!existsSync(path))
|
|
10
10
|
return {};
|
|
@@ -60,6 +60,7 @@ export function guiResilientCommand(base, opts) {
|
|
|
60
60
|
`else mkdir -p "$(dirname ${logFile})" && ` +
|
|
61
61
|
`echo "$(date '+%Y-%m-%d %H:%M:%S') [${opts.client}] pnpm no esta en el PATH del cliente; auto-update omitido" >> ${logFile}; fi`);
|
|
62
62
|
}
|
|
63
|
+
const CURSOR_HOOK_EVENTS = ["workspaceOpen", "sessionStart"];
|
|
63
64
|
function installCursor(command) {
|
|
64
65
|
const path = getCursorHooksPath();
|
|
65
66
|
const cfg = readJsonSafe(path);
|
|
@@ -69,9 +70,11 @@ function installCursor(command) {
|
|
|
69
70
|
}
|
|
70
71
|
if (typeof cfg.version !== "number")
|
|
71
72
|
cfg.version = 1;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
for (const event of CURSOR_HOOK_EVENTS) {
|
|
74
|
+
const list = freshHookList(cfg, event);
|
|
75
|
+
list.push({ command, timeout: 120 });
|
|
76
|
+
cfg.hooks[event] = list;
|
|
77
|
+
}
|
|
75
78
|
writeJson(path, cfg);
|
|
76
79
|
}
|
|
77
80
|
function installCodex(command) {
|
|
@@ -81,34 +84,74 @@ function installCodex(command) {
|
|
|
81
84
|
logWarn(`${path} no es JSON válido; salteo el auto-update de Codex.`);
|
|
82
85
|
return;
|
|
83
86
|
}
|
|
87
|
+
const before = JSON.stringify(cfg);
|
|
84
88
|
const list = freshHookList(cfg, "SessionStart");
|
|
85
89
|
list.push({ matcher: "startup|resume", hooks: [{ type: "command", command }] });
|
|
86
90
|
cfg.hooks.SessionStart = list;
|
|
91
|
+
if (JSON.stringify(cfg) === before)
|
|
92
|
+
return;
|
|
87
93
|
writeJson(path, cfg);
|
|
88
94
|
}
|
|
95
|
+
const ANTIGRAVITY_HOOK_NAME = "alegra-data-agent-autoupdate";
|
|
96
|
+
export function antigravityHookCommand(resilient, platform = process.platform) {
|
|
97
|
+
if (platform === "win32")
|
|
98
|
+
return resilient;
|
|
99
|
+
return `(${resilient}) </dev/null >/dev/null 2>&1 & echo '{}'`;
|
|
100
|
+
}
|
|
89
101
|
function installAntigravity(command) {
|
|
90
|
-
const path =
|
|
102
|
+
const path = getAntigravityHooksPath();
|
|
91
103
|
const cfg = readJsonSafe(path);
|
|
92
104
|
if (cfg === null) {
|
|
93
105
|
logWarn(`${path} no es JSON válido; salteo el auto-update de Antigravity.`);
|
|
94
106
|
return;
|
|
95
107
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
108
|
+
cfg[ANTIGRAVITY_HOOK_NAME] = {
|
|
109
|
+
SessionStart: [{ type: "command", command, timeout: 120 }],
|
|
110
|
+
};
|
|
99
111
|
writeJson(path, cfg);
|
|
112
|
+
removeJsonHook(getGeminiSettingsPath(), "SessionStart");
|
|
113
|
+
}
|
|
114
|
+
function removeAntigravityHook() {
|
|
115
|
+
const path = getAntigravityHooksPath();
|
|
116
|
+
const cfg = readJsonSafe(path);
|
|
117
|
+
if (cfg && ANTIGRAVITY_HOOK_NAME in cfg) {
|
|
118
|
+
delete cfg[ANTIGRAVITY_HOOK_NAME];
|
|
119
|
+
writeJson(path, cfg);
|
|
120
|
+
}
|
|
121
|
+
removeJsonHook(getGeminiSettingsPath(), "SessionStart");
|
|
122
|
+
}
|
|
123
|
+
function antigravityHookIsOurs() {
|
|
124
|
+
const cfg = readJsonSafe(getAntigravityHooksPath());
|
|
125
|
+
return Boolean(cfg && ANTIGRAVITY_HOOK_NAME in cfg);
|
|
100
126
|
}
|
|
101
127
|
function installOpenCode(command) {
|
|
102
128
|
const path = getOpenCodePluginPath();
|
|
103
129
|
mkdirSync(dirname(path), { recursive: true });
|
|
104
130
|
const plugin = `// alegra-data-agent: auto-update al iniciar OpenCode. Generado por el instalador.
|
|
105
131
|
// Borrar este archivo desactiva el auto-update. No editar a mano.
|
|
106
|
-
|
|
107
|
-
|
|
132
|
+
import { spawn } from "node:child_process"
|
|
133
|
+
|
|
134
|
+
const COMMAND = ${JSON.stringify(command)}
|
|
135
|
+
|
|
136
|
+
function runUpdate() {
|
|
108
137
|
try {
|
|
109
|
-
|
|
138
|
+
const child = spawn(COMMAND, { shell: true, detached: true, stdio: "ignore", windowsHide: true })
|
|
139
|
+
child.on("error", () => {})
|
|
140
|
+
child.unref()
|
|
110
141
|
} catch (_e) {}
|
|
111
|
-
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default {
|
|
145
|
+
id: "alegra-data-agent-autoupdate",
|
|
146
|
+
// OpenCode 1.x
|
|
147
|
+
server: async () => {
|
|
148
|
+
runUpdate()
|
|
149
|
+
return {}
|
|
150
|
+
},
|
|
151
|
+
// OpenCode 2.x
|
|
152
|
+
setup: async () => {
|
|
153
|
+
runUpdate()
|
|
154
|
+
},
|
|
112
155
|
}
|
|
113
156
|
`;
|
|
114
157
|
writeFileSync(path, plugin, "utf-8");
|
|
@@ -187,7 +230,10 @@ export function installAutoUpdate(target, mode = "auto") {
|
|
|
187
230
|
installCodex(hookCommandFor(effective));
|
|
188
231
|
break;
|
|
189
232
|
case "antigravity":
|
|
190
|
-
installAntigravity(guiResilientCommand(hookCommandFor(effective), {
|
|
233
|
+
installAntigravity(antigravityHookCommand(guiResilientCommand(hookCommandFor(effective), {
|
|
234
|
+
client: "antigravity",
|
|
235
|
+
silence: true,
|
|
236
|
+
})));
|
|
191
237
|
break;
|
|
192
238
|
case "opencode":
|
|
193
239
|
installOpenCode(hookCommandFor(effective));
|
|
@@ -208,13 +254,14 @@ export function removeAutoUpdate(target) {
|
|
|
208
254
|
runHookUninstall({ quiet: true });
|
|
209
255
|
break;
|
|
210
256
|
case "cursor":
|
|
211
|
-
|
|
257
|
+
for (const event of CURSOR_HOOK_EVENTS)
|
|
258
|
+
removeJsonHook(getCursorHooksPath(), event);
|
|
212
259
|
break;
|
|
213
260
|
case "codex":
|
|
214
261
|
removeJsonHook(getCodexHooksPath(), "SessionStart");
|
|
215
262
|
break;
|
|
216
263
|
case "antigravity":
|
|
217
|
-
|
|
264
|
+
removeAntigravityHook();
|
|
218
265
|
break;
|
|
219
266
|
case "opencode": {
|
|
220
267
|
const p = getOpenCodePluginPath();
|
|
@@ -243,11 +290,11 @@ export function hasAutoUpdate(target) {
|
|
|
243
290
|
case "claude-code":
|
|
244
291
|
return jsonHookHasOurs(getClaudeSettingsPath(), "SessionStart");
|
|
245
292
|
case "cursor":
|
|
246
|
-
return jsonHookHasOurs(getCursorHooksPath(),
|
|
293
|
+
return CURSOR_HOOK_EVENTS.some((event) => jsonHookHasOurs(getCursorHooksPath(), event));
|
|
247
294
|
case "codex":
|
|
248
295
|
return jsonHookHasOurs(getCodexHooksPath(), "SessionStart");
|
|
249
296
|
case "antigravity":
|
|
250
|
-
return jsonHookHasOurs(getGeminiSettingsPath(), "SessionStart");
|
|
297
|
+
return antigravityHookIsOurs() || jsonHookHasOurs(getGeminiSettingsPath(), "SessionStart");
|
|
251
298
|
case "opencode":
|
|
252
299
|
return existsSync(getOpenCodePluginPath());
|
|
253
300
|
case "hermes": {
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
|
+
import { COMMAND_NAME, getConfigDir } from "./lib.js";
|
|
7
|
+
function consentPath() {
|
|
8
|
+
return join(getConfigDir(), "codex-hook-trust.json");
|
|
9
|
+
}
|
|
10
|
+
export function hasCodexTrustConsent() {
|
|
11
|
+
return existsSync(consentPath());
|
|
12
|
+
}
|
|
13
|
+
export function saveCodexTrustConsent() {
|
|
14
|
+
const path = consentPath();
|
|
15
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
16
|
+
writeFileSync(path, `${JSON.stringify({ consented: true, at: new Date().toISOString() })}\n`);
|
|
17
|
+
}
|
|
18
|
+
function codexBin() {
|
|
19
|
+
const bin = process.env.CODEX_BIN;
|
|
20
|
+
return bin && bin.length > 0 ? bin : "codex";
|
|
21
|
+
}
|
|
22
|
+
async function withAppServer(fn, timeoutMs) {
|
|
23
|
+
const child = spawn(codexBin(), ["app-server"], {
|
|
24
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
25
|
+
windowsHide: true,
|
|
26
|
+
shell: process.platform === "win32",
|
|
27
|
+
});
|
|
28
|
+
const pending = new Map();
|
|
29
|
+
let nextId = 0;
|
|
30
|
+
const fail = (err) => {
|
|
31
|
+
for (const p of pending.values())
|
|
32
|
+
p.reject(err);
|
|
33
|
+
pending.clear();
|
|
34
|
+
};
|
|
35
|
+
child.on("error", (err) => fail(err));
|
|
36
|
+
child.on("exit", () => fail(new Error("codex app-server terminó antes de responder")));
|
|
37
|
+
child.stdin.on("error", (err) => fail(err));
|
|
38
|
+
const rl = createInterface({ input: child.stdout });
|
|
39
|
+
rl.on("line", (line) => {
|
|
40
|
+
let msg;
|
|
41
|
+
try {
|
|
42
|
+
msg = JSON.parse(line);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (typeof msg.id !== "number")
|
|
48
|
+
return;
|
|
49
|
+
const p = pending.get(msg.id);
|
|
50
|
+
if (!p)
|
|
51
|
+
return;
|
|
52
|
+
pending.delete(msg.id);
|
|
53
|
+
if (msg.error)
|
|
54
|
+
p.reject(new Error(msg.error.message ?? "error de codex app-server"));
|
|
55
|
+
else
|
|
56
|
+
p.resolve(msg.result);
|
|
57
|
+
});
|
|
58
|
+
const req = (method, params) => new Promise((resolve, reject) => {
|
|
59
|
+
const id = ++nextId;
|
|
60
|
+
pending.set(id, { resolve, reject });
|
|
61
|
+
child.stdin.write(`${JSON.stringify({ id, method, params })}\n`);
|
|
62
|
+
});
|
|
63
|
+
const timer = setTimeout(() => fail(new Error("codex app-server no respondió a tiempo")), timeoutMs);
|
|
64
|
+
try {
|
|
65
|
+
await req("initialize", {
|
|
66
|
+
clientInfo: { name: COMMAND_NAME, version: "1" },
|
|
67
|
+
capabilities: { experimentalApi: true },
|
|
68
|
+
});
|
|
69
|
+
child.stdin.write(`${JSON.stringify({ method: "initialized" })}\n`);
|
|
70
|
+
return await fn(req);
|
|
71
|
+
}
|
|
72
|
+
finally {
|
|
73
|
+
clearTimeout(timer);
|
|
74
|
+
rl.close();
|
|
75
|
+
child.stdout.destroy();
|
|
76
|
+
child.kill();
|
|
77
|
+
const force = setTimeout(() => child.kill("SIGKILL"), 2_000);
|
|
78
|
+
force.unref();
|
|
79
|
+
child.unref();
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function isOurCommand(command) {
|
|
83
|
+
return new RegExp(`${COMMAND_NAME}@\\S+ update\\b`).test(command);
|
|
84
|
+
}
|
|
85
|
+
async function ourHooks(req) {
|
|
86
|
+
const res = (await req("hooks/list", { cwds: [homedir()] }));
|
|
87
|
+
return (res.data ?? [])
|
|
88
|
+
.flatMap((d) => d.hooks ?? [])
|
|
89
|
+
.filter((h) => h.source === "user" && isOurCommand(h.command ?? ""));
|
|
90
|
+
}
|
|
91
|
+
export async function approveCodexHook(timeoutMs = 20_000) {
|
|
92
|
+
try {
|
|
93
|
+
return await withAppServer(async (req) => {
|
|
94
|
+
const needs = (await ourHooks(req)).filter((h) => h.trustStatus !== "trusted" && h.trustStatus !== "managed" && h.currentHash);
|
|
95
|
+
if (needs.length > 0) {
|
|
96
|
+
await req("config/batchWrite", {
|
|
97
|
+
edits: [
|
|
98
|
+
{
|
|
99
|
+
keyPath: "hooks.state",
|
|
100
|
+
value: Object.fromEntries(needs.map((h) => [h.key, { trusted_hash: h.currentHash }])),
|
|
101
|
+
mergeStrategy: "upsert",
|
|
102
|
+
},
|
|
103
|
+
],
|
|
104
|
+
reloadUserConfig: true,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const after = await ourHooks(req);
|
|
108
|
+
const pending = after.filter((h) => h.trustStatus !== "trusted" && h.trustStatus !== "managed").length;
|
|
109
|
+
return { ok: pending === 0 && after.length > 0, approved: needs.length, pending };
|
|
110
|
+
}, timeoutMs);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
approved: 0,
|
|
116
|
+
pending: 0,
|
|
117
|
+
detail: err instanceof Error ? err.message : String(err),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export async function codexHookTrustStatus(timeoutMs = 20_000) {
|
|
122
|
+
try {
|
|
123
|
+
return await withAppServer(async (req) => (await ourHooks(req)).map((h) => h.trustStatus ?? "unknown"), timeoutMs);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
package/dist/commands/install.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { AuthRequiredError } from "../api.js";
|
|
2
2
|
import { hasValidAuth, readAuth } from "../auth.js";
|
|
3
3
|
import { installAutoUpdate } from "../autoupdate.js";
|
|
4
|
+
import { approveCodexHook, hasCodexTrustConsent, saveCodexTrustConsent } from "../codex-trust.js";
|
|
4
5
|
import { bold, cyan, dim, gray, green, log, logError, red, yellow } from "../colors.js";
|
|
5
6
|
import { installAll } from "../installer.js";
|
|
6
7
|
import { COMMAND_NAME, formatBytes, getPackageVersion, getTargetSkillsDir } from "../lib.js";
|
|
8
|
+
import { confirm } from "../ui.js";
|
|
7
9
|
import { runLogin } from "./login.js";
|
|
8
10
|
const BANNER_WIDTH = 58;
|
|
9
11
|
const SHORT_DESCRIPTIONS = {
|
|
@@ -147,6 +149,37 @@ function printNextSteps(noHook) {
|
|
|
147
149
|
function isClaudeCode() {
|
|
148
150
|
return process.env.CLAUDECODE === "1" || process.env.CLAUDE_CODE === "1";
|
|
149
151
|
}
|
|
152
|
+
async function setUpCodex(manifest, results, opts) {
|
|
153
|
+
if (!opts.noHook) {
|
|
154
|
+
let consent = hasCodexTrustConsent();
|
|
155
|
+
if (!consent && !opts.yes && process.stdin.isTTY && process.stdout.isTTY) {
|
|
156
|
+
log(` ${bold("Codex")} pide aprobar cada hook antes de ejecutarlo. El nuestro solo corre ${cyan(`${COMMAND_NAME} update`)} al abrir una sesión.`);
|
|
157
|
+
consent = await confirm(" ¿Lo apruebo para que el auto-update funcione en Codex?", true);
|
|
158
|
+
if (consent)
|
|
159
|
+
saveCodexTrustConsent();
|
|
160
|
+
}
|
|
161
|
+
const res = consent ? await approveCodexHook() : null;
|
|
162
|
+
if (res?.ok) {
|
|
163
|
+
log(` ${green("✓")} Codex: auto-update aprobado.`);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
if (res?.detail)
|
|
167
|
+
log(` ${gray(`(no pude aprobarlo solo: ${res.detail})`)}`);
|
|
168
|
+
log(` ${bold(yellow("Codex:"))} al abrirlo elegí ${bold("«Trust all and continue»")} ${gray("(o /hooks)")} para activar el auto-update.`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const installedMcps = new Set(results.filter((r) => r.kind === "mcp" && r.status !== "failed").map((r) => r.name));
|
|
172
|
+
const remotes = Object.entries(manifest.mcps ?? {})
|
|
173
|
+
.filter(([name, m]) => m.url && installedMcps.has(name))
|
|
174
|
+
.map(([name]) => name)
|
|
175
|
+
.sort();
|
|
176
|
+
if (remotes.length > 0) {
|
|
177
|
+
log(` ${gray("Conectá cada MCP de Codex una vez:")}`);
|
|
178
|
+
for (const name of remotes)
|
|
179
|
+
log(` ${cyan(`codex mcp login ${name}`)}`);
|
|
180
|
+
}
|
|
181
|
+
log("");
|
|
182
|
+
}
|
|
150
183
|
export async function runInstall(opts = {}) {
|
|
151
184
|
const compact = isClaudeCode();
|
|
152
185
|
if (!compact)
|
|
@@ -243,6 +276,8 @@ export async function runInstall(opts = {}) {
|
|
|
243
276
|
if (!compact)
|
|
244
277
|
log("");
|
|
245
278
|
}
|
|
279
|
+
if (result.targets.includes("codex"))
|
|
280
|
+
await setUpCodex(result.manifest, result.results, opts);
|
|
246
281
|
if (hasFails)
|
|
247
282
|
return 1;
|
|
248
283
|
if (!compact) {
|
package/dist/commands/status.js
CHANGED
|
@@ -3,6 +3,8 @@ import { homedir } from "node:os";
|
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { AuthRequiredError, fetchManifest } from "../api.js";
|
|
5
5
|
import { readAuth } from "../auth.js";
|
|
6
|
+
import { hasAutoUpdate } from "../autoupdate.js";
|
|
7
|
+
import { codexHookTrustStatus } from "../codex-trust.js";
|
|
6
8
|
import { bold, cyan, dim, gray, green, log, logError, logWarn, yellow } from "../colors.js";
|
|
7
9
|
import { detectDrift, findMisplacedMcps, inspectMcpInTarget } from "../installer.js";
|
|
8
10
|
import { COMMAND_NAME, GATEWAY_URL, getAutoUpdateLogPath, getClaudeJsonPath, getClaudeSkillsDir, getLockFilePath, getMcpScopeDir, getMcpScopeWarning, getPackageVersion, getTargetSkillsDir, readLockFile, } from "../lib.js";
|
|
@@ -27,6 +29,13 @@ function reportAutoUpdateLog() {
|
|
|
27
29
|
catch {
|
|
28
30
|
}
|
|
29
31
|
}
|
|
32
|
+
async function reportCodexHookTrust() {
|
|
33
|
+
const states = await codexHookTrustStatus();
|
|
34
|
+
if (!states || states.every((s) => s === "trusted" || s === "managed"))
|
|
35
|
+
return;
|
|
36
|
+
logWarn(`El auto-update de Codex está sin aprobar (${states.join(", ")}): Codex no lo ejecuta. Abrí Codex y elegí «Trust all and continue» (o /hooks), o corré ${bold(`${COMMAND_NAME} install --target=codex`)} y aceptá la aprobación.`);
|
|
37
|
+
log("");
|
|
38
|
+
}
|
|
30
39
|
function reportSkillBackups() {
|
|
31
40
|
const backupsLog = join(dirname(getAutoUpdateLogPath()), "backups", "backups.log");
|
|
32
41
|
if (!existsSync(backupsLog))
|
|
@@ -309,6 +318,8 @@ export async function runStatus() {
|
|
|
309
318
|
log("");
|
|
310
319
|
reportAutoUpdateLog();
|
|
311
320
|
reportSkillBackups();
|
|
321
|
+
if (installedTargets.includes("codex") && hasAutoUpdate("codex"))
|
|
322
|
+
await reportCodexHookTrust();
|
|
312
323
|
if (!auth) {
|
|
313
324
|
logWarn(`Inicia sesión con: ${bold(`${COMMAND_NAME} login`)}`);
|
|
314
325
|
log("");
|
package/dist/commands/update.js
CHANGED
|
@@ -2,7 +2,8 @@ import { appendFileSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "
|
|
|
2
2
|
import { dirname } from "node:path";
|
|
3
3
|
import { AuthRequiredError, ensureFreshAuth, fetchManifest } from "../api.js";
|
|
4
4
|
import { hasValidAuth } from "../auth.js";
|
|
5
|
-
import { refreshAutoUpdate } from "../autoupdate.js";
|
|
5
|
+
import { hasAutoUpdate, refreshAutoUpdate } from "../autoupdate.js";
|
|
6
|
+
import { approveCodexHook, hasCodexTrustConsent } from "../codex-trust.js";
|
|
6
7
|
import { cyan, dim, gray, green, log, logError, yellow } from "../colors.js";
|
|
7
8
|
import { detectDrift, installAll, summarize } from "../installer.js";
|
|
8
9
|
import { COMMAND_NAME, getAutoUpdateLogPath, getLastCheckPath, getPackageVersion, } from "../lib.js";
|
|
@@ -123,6 +124,11 @@ export async function runUpdate(opts = {}) {
|
|
|
123
124
|
}
|
|
124
125
|
}
|
|
125
126
|
}
|
|
127
|
+
async function reapproveCodexHook() {
|
|
128
|
+
if (!hasCodexTrustConsent() || !hasAutoUpdate("codex"))
|
|
129
|
+
return;
|
|
130
|
+
await approveCodexHook();
|
|
131
|
+
}
|
|
126
132
|
async function runUpdateOnce(opts) {
|
|
127
133
|
const manifest = await fetchManifest();
|
|
128
134
|
if (!manifest) {
|
|
@@ -142,6 +148,7 @@ async function runUpdateOnce(opts) {
|
|
|
142
148
|
markChecked();
|
|
143
149
|
if (!opts.dryRun) {
|
|
144
150
|
refreshAutoUpdate();
|
|
151
|
+
await reapproveCodexHook();
|
|
145
152
|
clearAutoUpdateLog();
|
|
146
153
|
}
|
|
147
154
|
return 0;
|
|
@@ -173,6 +180,7 @@ async function runUpdateOnce(opts) {
|
|
|
173
180
|
}
|
|
174
181
|
summarize(result.results);
|
|
175
182
|
refreshAutoUpdate();
|
|
183
|
+
await reapproveCodexHook();
|
|
176
184
|
const failed = result.results.some((r) => r.status === "failed");
|
|
177
185
|
if (!failed) {
|
|
178
186
|
clearAutoUpdateLog();
|
package/dist/installer.js
CHANGED
|
@@ -5,7 +5,7 @@ import { basename, dirname, join, resolve, sep } from "node:path";
|
|
|
5
5
|
import { AuthRequiredError, fetchManifest, fetchSkillFile, recordManifestFloor } from "./api.js";
|
|
6
6
|
import { dim, gray, green, logError, logWarn, red, yellow } from "./colors.js";
|
|
7
7
|
import { ensureLazyProxy, lazyMcpDir, lazyProxyPath, lazyToolsPath, stdioLaunch, } from "./lazy-mcp.js";
|
|
8
|
-
import { computeBundleHash, ensureDir, getAntigravityGeminiLegacyMcpConfigPath, getAntigravityGeminiLegacySkillsDir, getAntigravityLegacyMcpConfigPath, getAntigravityLegacySkillsDir, getClaudeSettingsPath, getConfigDir, getCursorLegacyRulesDir, getHermesBin, getLockFilePath, getMcpScopeDir, getMcpScopeWarning, getOpenCodeLegacyPromptsDir, getPackageVersion, getUserScopeMcpConfigPath, isMcpScopeEnvOverride, readLockFile, sha256File, walkSkillFiles, writeLockFile, } from "./lib.js";
|
|
8
|
+
import { computeBundleHash, ensureDir, getAntigravityGeminiLegacyMcpConfigPath, getAntigravityGeminiLegacySkillsDir, getAntigravityGeminiSkillsDir, getAntigravityLegacyMcpConfigPath, getAntigravityLegacySkillsDir, getClaudeSettingsPath, getConfigDir, getCursorLegacyRulesDir, getHermesBin, getLockFilePath, getMcpScopeDir, getMcpScopeWarning, getOpenCodeLegacyPromptsDir, getPackageVersion, getUserScopeMcpConfigPath, isMcpScopeEnvOverride, readLockFile, sha256File, walkSkillFiles, writeLockFile, } from "./lib.js";
|
|
9
9
|
import { renderHermesMcpOps, renderMcpForTarget } from "./render.js";
|
|
10
10
|
import { TARGETS, detectInstalledTargets } from "./targets.js";
|
|
11
11
|
const SKILL_NAME_RE = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -686,15 +686,65 @@ function tomlMarkers(name) {
|
|
|
686
686
|
end: `# <<< alegra-data-agent:${name} <<<`,
|
|
687
687
|
};
|
|
688
688
|
}
|
|
689
|
+
function splitTomlBlock(inner, name) {
|
|
690
|
+
const lines = inner.split("\n");
|
|
691
|
+
const isOurs = (h) => h === `[mcp_servers.${name}]` || h === `[mcp_servers.${name}.env]`;
|
|
692
|
+
const at = lines.findIndex((l) => {
|
|
693
|
+
const t = l.trim();
|
|
694
|
+
return t.startsWith("[") && !isOurs(tomlHeader(t));
|
|
695
|
+
});
|
|
696
|
+
if (at === -1)
|
|
697
|
+
return { ours: inner, foreign: "" };
|
|
698
|
+
return {
|
|
699
|
+
ours: lines.slice(0, at).join("\n").replace(/\n+$/, ""),
|
|
700
|
+
foreign: lines.slice(at).join("\n").replace(/\n+$/, ""),
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
function tomlHeader(line) {
|
|
704
|
+
return line.replace(/\]\s*#.*$/, "]");
|
|
705
|
+
}
|
|
706
|
+
function dropServerSubtables(content, name) {
|
|
707
|
+
const prefix = `[mcp_servers.${name}.`;
|
|
708
|
+
const out = [];
|
|
709
|
+
let skipping = false;
|
|
710
|
+
let dropped = false;
|
|
711
|
+
for (const line of content.split("\n")) {
|
|
712
|
+
const t = line.trim();
|
|
713
|
+
const isMarker = t.startsWith("# >>> alegra-data-agent:") || t.startsWith("# <<< alegra-data-agent:");
|
|
714
|
+
if (t.startsWith("[") || isMarker)
|
|
715
|
+
skipping = !isMarker && tomlHeader(t).startsWith(prefix);
|
|
716
|
+
if (skipping)
|
|
717
|
+
dropped = true;
|
|
718
|
+
else
|
|
719
|
+
out.push(line);
|
|
720
|
+
}
|
|
721
|
+
return dropped ? out.join("\n").replace(/\n{3,}/g, "\n\n") : content;
|
|
722
|
+
}
|
|
723
|
+
function findTomlBlock(content, name) {
|
|
724
|
+
const { begin, end } = tomlMarkers(name);
|
|
725
|
+
const bi = content.indexOf(begin);
|
|
726
|
+
if (bi === -1)
|
|
727
|
+
return null;
|
|
728
|
+
const ei = content.indexOf(end, bi);
|
|
729
|
+
if (ei === -1)
|
|
730
|
+
return null;
|
|
731
|
+
return {
|
|
732
|
+
start: bi,
|
|
733
|
+
stop: ei + end.length,
|
|
734
|
+
inner: content
|
|
735
|
+
.slice(bi + begin.length, ei)
|
|
736
|
+
.replace(/^\n/, "")
|
|
737
|
+
.replace(/\n$/, ""),
|
|
738
|
+
};
|
|
739
|
+
}
|
|
689
740
|
export function upsertTomlBlock(content, name, block) {
|
|
690
741
|
const { begin, end } = tomlMarkers(name);
|
|
691
742
|
const fenced = `${begin}\n${block}\n${end}`;
|
|
692
|
-
const
|
|
693
|
-
if (
|
|
694
|
-
const
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
}
|
|
743
|
+
const found = findTomlBlock(content, name);
|
|
744
|
+
if (found) {
|
|
745
|
+
const { foreign } = splitTomlBlock(found.inner, name);
|
|
746
|
+
const moved = foreign ? `\n\n${foreign}` : "";
|
|
747
|
+
return content.slice(0, found.start) + fenced + moved + content.slice(found.stop);
|
|
698
748
|
}
|
|
699
749
|
if (content.length === 0)
|
|
700
750
|
return `${fenced}\n`;
|
|
@@ -702,33 +752,24 @@ export function upsertTomlBlock(content, name, block) {
|
|
|
702
752
|
return `${content}${sep}${fenced}\n`;
|
|
703
753
|
}
|
|
704
754
|
export function extractTomlBlock(content, name) {
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
if (bi === -1)
|
|
708
|
-
return null;
|
|
709
|
-
const ei = content.indexOf(end, bi);
|
|
710
|
-
if (ei === -1)
|
|
711
|
-
return null;
|
|
712
|
-
return content
|
|
713
|
-
.slice(bi + begin.length, ei)
|
|
714
|
-
.replace(/^\n/, "")
|
|
715
|
-
.replace(/\n$/, "");
|
|
755
|
+
const found = findTomlBlock(content, name);
|
|
756
|
+
return found ? splitTomlBlock(found.inner, name).ours : null;
|
|
716
757
|
}
|
|
717
758
|
export function removeTomlBlock(content, name) {
|
|
718
|
-
const
|
|
719
|
-
|
|
720
|
-
if (bi === -1)
|
|
759
|
+
const found = findTomlBlock(content, name);
|
|
760
|
+
if (!found)
|
|
721
761
|
return content;
|
|
722
|
-
const
|
|
723
|
-
if (
|
|
724
|
-
return content;
|
|
725
|
-
|
|
726
|
-
let
|
|
762
|
+
const { foreign } = splitTomlBlock(found.inner, name);
|
|
763
|
+
if (foreign) {
|
|
764
|
+
return dropServerSubtables(content.slice(0, found.start) + foreign + content.slice(found.stop), name);
|
|
765
|
+
}
|
|
766
|
+
let start = found.start;
|
|
767
|
+
let stop = found.stop;
|
|
727
768
|
if (content[stop] === "\n")
|
|
728
769
|
stop++;
|
|
729
770
|
if (start > 0 && content[start - 1] === "\n")
|
|
730
771
|
start--;
|
|
731
|
-
return content.slice(0, start) + content.slice(stop);
|
|
772
|
+
return dropServerSubtables(content.slice(0, start) + content.slice(stop), name);
|
|
732
773
|
}
|
|
733
774
|
function registerMcpInTomlBlock(mcp, spec) {
|
|
734
775
|
const existing = existsSync(spec.mcpConfigPath) ? readFileSync(spec.mcpConfigPath, "utf-8") : "";
|
|
@@ -891,6 +932,7 @@ function cleanupLegacySkillArtifacts(spec) {
|
|
|
891
932
|
const legacySkillDirs = [
|
|
892
933
|
getAntigravityLegacySkillsDir(),
|
|
893
934
|
getAntigravityGeminiLegacySkillsDir(),
|
|
935
|
+
getAntigravityGeminiSkillsDir(),
|
|
894
936
|
];
|
|
895
937
|
for (const base of legacySkillDirs) {
|
|
896
938
|
for (const name of [...Object.keys(lock.skills), ...LEGACY_RENAMED_SKILLS]) {
|
|
@@ -1309,10 +1351,6 @@ export function uninstallMcp(name, opts = {}) {
|
|
|
1309
1351
|
export function uninstallAll(opts = {}) {
|
|
1310
1352
|
const lock = readLockFile();
|
|
1311
1353
|
const results = [];
|
|
1312
|
-
for (const name of Object.keys(lock.skills))
|
|
1313
|
-
results.push(uninstallSkill(name, opts));
|
|
1314
|
-
for (const name of Object.keys(lock.mcps ?? {}))
|
|
1315
|
-
results.push(uninstallMcp(name, opts));
|
|
1316
1354
|
if (!opts.dryRun) {
|
|
1317
1355
|
for (const spec of Object.values(TARGETS)) {
|
|
1318
1356
|
try {
|
|
@@ -1321,6 +1359,12 @@ export function uninstallAll(opts = {}) {
|
|
|
1321
1359
|
catch {
|
|
1322
1360
|
}
|
|
1323
1361
|
}
|
|
1362
|
+
}
|
|
1363
|
+
for (const name of Object.keys(lock.skills))
|
|
1364
|
+
results.push(uninstallSkill(name, opts));
|
|
1365
|
+
for (const name of Object.keys(lock.mcps ?? {}))
|
|
1366
|
+
results.push(uninstallMcp(name, opts));
|
|
1367
|
+
if (!opts.dryRun) {
|
|
1324
1368
|
if (Object.keys(readLockFile().mcps ?? {}).length === 0) {
|
|
1325
1369
|
try {
|
|
1326
1370
|
rmSync(lazyMcpDir(), { recursive: true, force: true });
|
package/dist/lib.js
CHANGED
|
@@ -174,6 +174,9 @@ export function getOpenCodeConfigPath() {
|
|
|
174
174
|
export function getAntigravitySkillsDir() {
|
|
175
175
|
return join(homedir(), ".gemini", "antigravity-cli", "skills");
|
|
176
176
|
}
|
|
177
|
+
export function getAntigravityConfigSkillsDir() {
|
|
178
|
+
return join(homedir(), ".gemini", "config", "skills");
|
|
179
|
+
}
|
|
177
180
|
export function getAntigravityGeminiSkillsDir() {
|
|
178
181
|
return join(homedir(), ".gemini", "skills");
|
|
179
182
|
}
|
|
@@ -183,6 +186,9 @@ export function getAntigravityMcpConfigPath() {
|
|
|
183
186
|
export function getGeminiSettingsPath() {
|
|
184
187
|
return join(homedir(), ".gemini", "settings.json");
|
|
185
188
|
}
|
|
189
|
+
export function getAntigravityHooksPath() {
|
|
190
|
+
return join(homedir(), ".gemini", "config", "hooks.json");
|
|
191
|
+
}
|
|
186
192
|
export function getAntigravityLegacySkillsDir() {
|
|
187
193
|
return join(homedir(), ".antigravity", "skills");
|
|
188
194
|
}
|
package/dist/main.js
CHANGED
package/dist/targets.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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 { getAntigravityConfigSkillsDir, getAntigravityMcpConfigPath, getAntigravitySkillsDir, getClaudeMcpConfigPath, getClaudeSkillsDir, getCodexConfigPath, getCodexSkillsDir, getCursorMcpPath, getCursorSkillsDir, getHermesConfigPath, getHermesHome, getHermesSkillsDir, getOpenCodeConfigPath, getOpenCodeSkillsDir, } from "./lib.js";
|
|
5
5
|
export const TARGETS = {
|
|
6
6
|
"claude-code": {
|
|
7
7
|
id: "claude-code",
|
|
@@ -37,7 +37,7 @@ export const TARGETS = {
|
|
|
37
37
|
id: "antigravity",
|
|
38
38
|
label: "Antigravity",
|
|
39
39
|
skillsDir: getAntigravitySkillsDir(),
|
|
40
|
-
mirrorSkillsDirs: [
|
|
40
|
+
mirrorSkillsDirs: [getAntigravityConfigSkillsDir()],
|
|
41
41
|
skillFormat: "folder",
|
|
42
42
|
mcpConfigPath: getAntigravityMcpConfigPath(),
|
|
43
43
|
mcpRootKey: "mcpServers",
|
|
@@ -76,7 +76,11 @@ export function detectInstalledTargets() {
|
|
|
76
76
|
"claude-code": [join(home, ".claude")],
|
|
77
77
|
cursor: [join(home, ".cursor")],
|
|
78
78
|
opencode: [join(xdgConfig, "opencode")],
|
|
79
|
-
antigravity: [
|
|
79
|
+
antigravity: [
|
|
80
|
+
join(home, ".gemini", "antigravity-cli"),
|
|
81
|
+
join(home, ".gemini", "antigravity"),
|
|
82
|
+
join(home, ".gemini", "antigravity-ide"),
|
|
83
|
+
],
|
|
80
84
|
codex: [codexHome],
|
|
81
85
|
hermes: [getHermesHome()],
|
|
82
86
|
};
|