alegra-data-agent 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/installer.js CHANGED
@@ -1,9 +1,9 @@
1
1
  import { createHash } from "node:crypto";
2
- import { copyFileSync, existsSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
- import { dirname, join, resolve, sep } from "node:path";
4
- import { fetchManifest, fetchSkillFile } from "./api.js";
2
+ import { copyFileSync, existsSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
3
+ import { basename, dirname, join, resolve, sep } from "node:path";
4
+ import { AuthRequiredError, fetchManifest, fetchSkillFile } from "./api.js";
5
5
  import { dim, gray, green, logError, red, yellow } from "./colors.js";
6
- import { adaptStdioCommand, ensureDir, getLockFilePath, getAntigravityLegacyMcpConfigPath, getAntigravityLegacySkillsDir, getClaudeSettingsPath, getCursorLegacyRulesDir, getOpenCodeLegacyPromptsDir, 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-]*$/;
@@ -44,6 +44,17 @@ function mcpConfigHash(mcp) {
44
44
  });
45
45
  return createHash("sha256").update(salient).digest("hex");
46
46
  }
47
+ function stableStringify(value) {
48
+ if (value === null || typeof value !== "object")
49
+ return JSON.stringify(value);
50
+ if (Array.isArray(value))
51
+ return `[${value.map(stableStringify).join(",")}]`;
52
+ const o = value;
53
+ return `{${Object.keys(o)
54
+ .sort()
55
+ .map((k) => `${JSON.stringify(k)}:${stableStringify(o[k])}`)
56
+ .join(",")}}`;
57
+ }
47
58
  function safeJoinSkillFile(skillRoot, rel) {
48
59
  const candidate = resolve(skillRoot, rel);
49
60
  const root = resolve(skillRoot);
@@ -73,33 +84,51 @@ export function verifyInstalledFiles(dir, entry) {
73
84
  function sha256Buffer(buf) {
74
85
  return createHash("sha256").update(buf).digest("hex");
75
86
  }
87
+ const DOWNLOAD_CONCURRENCY = 8;
88
+ async function mapWithLimit(items, limit, fn) {
89
+ const out = new Array(items.length);
90
+ let next = 0;
91
+ const worker = async () => {
92
+ for (;;) {
93
+ const i = next++;
94
+ if (i >= items.length)
95
+ return;
96
+ out[i] = await fn(items[i]);
97
+ }
98
+ };
99
+ await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
100
+ return out;
101
+ }
76
102
  async function stageSkill(skillName, entry) {
77
103
  validateSkillName(skillName);
78
104
  for (const rel of entry.files)
79
105
  validateRelPath(rel);
80
- const staged = [];
81
- for (const rel of entry.files) {
106
+ return mapWithLimit(entry.files, DOWNLOAD_CONCURRENCY, async (rel) => {
82
107
  const data = await fetchSkillFile(skillName, rel);
83
108
  const actualHash = sha256Buffer(data);
84
109
  const expectedHash = entry.sha256[rel];
85
110
  if (actualHash !== expectedHash) {
86
111
  throw new Error(`hash inválido para ${skillName}/${rel}: esperaba ${expectedHash.slice(0, 12)}…, recibido ${actualHash.slice(0, 12)}…`);
87
112
  }
88
- staged.push({ rel, data });
89
- }
90
- return staged;
113
+ return { rel, data };
114
+ });
115
+ }
116
+ function skillDirsFor(spec) {
117
+ return [spec.skillsDir, ...(spec.mirrorSkillsDirs ?? [])];
91
118
  }
92
119
  function writeSkillToTarget(skillName, staged, spec) {
93
120
  validateSkillName(skillName);
94
121
  if (spec.skillFormat === "folder") {
95
- const dst = join(spec.skillsDir, skillName);
96
- if (existsSync(dst))
97
- rmSync(dst, { recursive: true, force: true });
98
- ensureDir(dst);
99
- for (const f of staged) {
100
- const dstFile = safeJoinSkillFile(dst, f.rel);
101
- ensureDir(dirname(dstFile));
102
- writeFileSync(dstFile, f.data);
122
+ for (const base of skillDirsFor(spec)) {
123
+ const dst = join(base, skillName);
124
+ if (existsSync(dst))
125
+ withFsRetry(() => rmSync(dst, { recursive: true, force: true }));
126
+ ensureDir(dst);
127
+ for (const f of staged) {
128
+ const dstFile = safeJoinSkillFile(dst, f.rel);
129
+ ensureDir(dirname(dstFile));
130
+ withFsRetry(() => writeFileSync(dstFile, f.data));
131
+ }
103
132
  }
104
133
  return;
105
134
  }
@@ -151,6 +180,22 @@ function parseFrontmatterMinimal(content) {
151
180
  function sleepSync(ms) {
152
181
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
153
182
  }
183
+ function withFsRetry(fn) {
184
+ for (let attempt = 0;; attempt++) {
185
+ try {
186
+ fn();
187
+ return;
188
+ }
189
+ catch (err) {
190
+ const code = err.code;
191
+ if (attempt < 4 && (code === "EPERM" || code === "EBUSY" || code === "EACCES")) {
192
+ sleepSync(150);
193
+ continue;
194
+ }
195
+ throw err;
196
+ }
197
+ }
198
+ }
154
199
  function atomicWriteFile(path, content) {
155
200
  ensureDir(dirname(path));
156
201
  const tmp = `${path}.${process.pid}.tmp`;
@@ -174,11 +219,30 @@ function atomicWriteFile(path, content) {
174
219
  }
175
220
  }
176
221
  }
222
+ const MAX_BACKUPS = 3;
223
+ const backedUpThisRun = new Set();
177
224
  function backupIfExists(path) {
178
225
  if (!existsSync(path))
179
226
  return;
180
- const bak = `${path}.bak-${Date.now()}`;
181
- copyFileSync(path, bak);
227
+ if (backedUpThisRun.has(path))
228
+ return;
229
+ copyFileSync(path, `${path}.bak-${Date.now()}`);
230
+ backedUpThisRun.add(path);
231
+ pruneBackups(path);
232
+ }
233
+ function pruneBackups(path) {
234
+ try {
235
+ const dir = dirname(path);
236
+ const prefix = `${basename(path)}.bak-`;
237
+ const stale = readdirSync(dir)
238
+ .filter((f) => f.startsWith(prefix))
239
+ .sort()
240
+ .slice(0, -MAX_BACKUPS);
241
+ for (const f of stale)
242
+ rmSync(join(dir, f), { force: true });
243
+ }
244
+ catch {
245
+ }
182
246
  }
183
247
  function readJsonOrEmpty(path) {
184
248
  if (!existsSync(path))
@@ -266,6 +330,19 @@ export function upsertTomlBlock(content, name, block) {
266
330
  const sep = content.endsWith("\n\n") ? "" : content.endsWith("\n") ? "\n" : "\n\n";
267
331
  return `${content}${sep}${fenced}\n`;
268
332
  }
333
+ export function extractTomlBlock(content, name) {
334
+ const { begin, end } = tomlMarkers(name);
335
+ const bi = content.indexOf(begin);
336
+ if (bi === -1)
337
+ return null;
338
+ const ei = content.indexOf(end, bi);
339
+ if (ei === -1)
340
+ return null;
341
+ return content
342
+ .slice(bi + begin.length, ei)
343
+ .replace(/^\n/, "")
344
+ .replace(/\n$/, "");
345
+ }
269
346
  export function removeTomlBlock(content, name) {
270
347
  const { begin, end } = tomlMarkers(name);
271
348
  const bi = content.indexOf(begin);
@@ -283,9 +360,7 @@ export function removeTomlBlock(content, name) {
283
360
  return content.slice(0, start) + content.slice(stop);
284
361
  }
285
362
  function registerMcpInTomlBlock(mcp, spec) {
286
- const existing = existsSync(spec.mcpConfigPath)
287
- ? readFileSync(spec.mcpConfigPath, "utf-8")
288
- : "";
363
+ const existing = existsSync(spec.mcpConfigPath) ? readFileSync(spec.mcpConfigPath, "utf-8") : "";
289
364
  const next = upsertTomlBlock(existing, mcp.name, renderMcpToml(mcp));
290
365
  if (next === existing)
291
366
  return;
@@ -303,14 +378,47 @@ function unregisterMcpInTomlBlock(name, spec) {
303
378
  atomicWriteFile(spec.mcpConfigPath, next);
304
379
  return true;
305
380
  }
381
+ export function inspectMcpInTarget(mcp, spec) {
382
+ try {
383
+ if (!existsSync(spec.mcpConfigPath))
384
+ return "missing";
385
+ if (spec.mcpMechanism === "toml-block") {
386
+ const block = extractTomlBlock(readFileSync(spec.mcpConfigPath, "utf-8"), mcp.name);
387
+ if (block === null)
388
+ return "missing";
389
+ return block.trim() === renderMcpToml(mcp).trim() ? "ok" : "modified";
390
+ }
391
+ const cfg = JSON.parse(readFileSync(spec.mcpConfigPath, "utf-8"));
392
+ const section = cfg[spec.mcpRootKey];
393
+ if (!section || typeof section !== "object")
394
+ return "missing";
395
+ const current = section[mcp.name];
396
+ if (current === undefined)
397
+ return "missing";
398
+ return stableStringify(current) === stableStringify(renderMcpForTarget(mcp, spec))
399
+ ? "ok"
400
+ : "modified";
401
+ }
402
+ catch {
403
+ return "missing";
404
+ }
405
+ }
406
+ function mcpMissingInTargets(mcp, targets) {
407
+ return targets.filter((id) => inspectMcpInTarget(mcp, TARGETS[id]) === "missing");
408
+ }
409
+ const cleanedTargets = new Set();
306
410
  function resolveTargets(opts) {
307
411
  const ids = opts.targets?.length ? opts.targets : detectInstalledTargets();
308
412
  if (ids.length === 0) {
309
413
  ids.push("claude-code");
310
414
  }
311
415
  const specs = ids.map((id) => TARGETS[id]);
312
- for (const spec of specs)
416
+ for (const spec of specs) {
417
+ if (cleanedTargets.has(spec.id))
418
+ continue;
419
+ cleanedTargets.add(spec.id);
313
420
  cleanupLegacySkillArtifacts(spec);
421
+ }
314
422
  return specs;
315
423
  }
316
424
  const LEGACY_RENAMED_SKILLS = ["alanube", "alegra-producto", "alegra-ce-universe"];
@@ -346,10 +454,12 @@ function stripMcpEntries(path, names) {
346
454
  function cleanupLegacySkillArtifacts(spec) {
347
455
  try {
348
456
  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 });
457
+ for (const base of skillDirsFor(spec)) {
458
+ for (const old of LEGACY_RENAMED_SKILLS) {
459
+ const dir = join(base, old);
460
+ if (existsSync(dir))
461
+ rmSync(dir, { recursive: true, force: true });
462
+ }
353
463
  }
354
464
  }
355
465
  const lock = readLockFile();
@@ -368,12 +478,19 @@ function cleanupLegacySkillArtifacts(spec) {
368
478
  stripMcpEntries(getClaudeSettingsPath(), ourMcps);
369
479
  }
370
480
  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 });
481
+ const legacySkillDirs = [
482
+ getAntigravityLegacySkillsDir(),
483
+ getAntigravityGeminiLegacySkillsDir(),
484
+ ];
485
+ for (const base of legacySkillDirs) {
486
+ for (const name of [...Object.keys(lock.skills), ...LEGACY_RENAMED_SKILLS]) {
487
+ const d = join(base, name);
488
+ if (existsSync(d))
489
+ rmSync(d, { recursive: true, force: true });
490
+ }
375
491
  }
376
492
  stripMcpEntries(getAntigravityLegacyMcpConfigPath(), ourMcps);
493
+ stripMcpEntries(getAntigravityGeminiLegacyMcpConfigPath(), ourMcps);
377
494
  }
378
495
  }
379
496
  catch {
@@ -411,35 +528,56 @@ export async function installSkill(name, manifest, opts = {}) {
411
528
  targets: targetIds,
412
529
  };
413
530
  }
531
+ let staged;
414
532
  try {
415
- const staged = await stageSkill(name, entry);
416
- for (const t of targets) {
417
- writeSkillToTarget(name, staged, t);
418
- }
419
- lock.skills[name] = {
420
- name,
421
- installedFrom: getPackageVersion(),
422
- installedAt: new Date().toISOString(),
423
- bundleHash: entry.bundleHash,
424
- targets: targetIds,
425
- };
426
- writeLockFile(lock);
533
+ staged = await stageSkill(name, entry);
534
+ }
535
+ catch (err) {
536
+ if (err instanceof AuthRequiredError)
537
+ throw err;
427
538
  return {
428
539
  name,
429
540
  kind: "skill",
430
- status: prev ? "updated" : "installed",
541
+ status: "failed",
431
542
  targets: targetIds,
543
+ error: err instanceof Error ? err.message : String(err),
432
544
  };
433
545
  }
434
- catch (err) {
546
+ const okTargets = [];
547
+ const failures = [];
548
+ for (const t of targets) {
549
+ try {
550
+ writeSkillToTarget(name, staged, t);
551
+ okTargets.push(t.id);
552
+ }
553
+ catch (err) {
554
+ failures.push(`${t.id}: ${err instanceof Error ? err.message : String(err)}`);
555
+ }
556
+ }
557
+ if (okTargets.length === 0) {
435
558
  return {
436
559
  name,
437
560
  kind: "skill",
438
561
  status: "failed",
439
562
  targets: targetIds,
440
- error: err instanceof Error ? err.message : String(err),
563
+ error: failures.join("; "),
441
564
  };
442
565
  }
566
+ lock.skills[name] = {
567
+ name,
568
+ installedFrom: getPackageVersion(),
569
+ installedAt: new Date().toISOString(),
570
+ bundleHash: entry.bundleHash,
571
+ targets: okTargets,
572
+ };
573
+ writeLockFile(lock);
574
+ return {
575
+ name,
576
+ kind: "skill",
577
+ status: prev ? "updated" : "installed",
578
+ targets: okTargets,
579
+ error: failures.length > 0 ? `targets con fallo — ${failures.join("; ")}` : undefined,
580
+ };
443
581
  }
444
582
  export function installMcp(mcp, opts = {}) {
445
583
  try {
@@ -470,7 +608,14 @@ export function installMcp(mcp, opts = {}) {
470
608
  const targetsMatch = prev?.targets &&
471
609
  prev.targets.length === targetIds.length &&
472
610
  prev.targets.every((t) => targetIds.includes(t));
473
- if (prev && targetsMatch && prev.configHash === mcpConfigHash(mcp) && !opts.force) {
611
+ const configChanged = !prev || prev.configHash !== mcpConfigHash(mcp);
612
+ const writeTargets = opts.force || configChanged
613
+ ? wantedTargets
614
+ : wantedTargets.filter((t) => inspectMcpInTarget(mcp, t) === "missing");
615
+ const alreadyThere = wantedTargets
616
+ .filter((t) => !writeTargets.includes(t) && inspectMcpInTarget(mcp, t) !== "missing")
617
+ .map((t) => t.id);
618
+ if (writeTargets.length === 0 && targetsMatch) {
474
619
  return { name: mcp.name, kind: "mcp", status: "unchanged", targets: targetIds };
475
620
  }
476
621
  if (opts.dryRun) {
@@ -478,37 +623,45 @@ export function installMcp(mcp, opts = {}) {
478
623
  name: mcp.name,
479
624
  kind: "mcp",
480
625
  status: prev ? "updated" : "installed",
481
- targets: targetIds,
626
+ targets: writeTargets.map((t) => t.id),
482
627
  };
483
628
  }
484
- try {
485
- for (const t of wantedTargets) {
629
+ const written = [];
630
+ const failures = [];
631
+ for (const t of writeTargets) {
632
+ try {
486
633
  registerMcpInTarget(mcp, t);
634
+ written.push(t.id);
635
+ }
636
+ catch (err) {
637
+ failures.push(`${t.id}: ${err instanceof Error ? err.message : String(err)}`);
487
638
  }
488
- lock.mcps[mcp.name] = {
489
- name: mcp.name,
490
- installedFrom: getPackageVersion(),
491
- installedAt: new Date().toISOString(),
492
- configHash: mcpConfigHash(mcp),
493
- targets: targetIds,
494
- };
495
- writeLockFile(lock);
496
- return {
497
- name: mcp.name,
498
- kind: "mcp",
499
- status: prev ? "updated" : "installed",
500
- targets: targetIds,
501
- };
502
639
  }
503
- catch (err) {
640
+ const okTargets = [...new Set([...alreadyThere, ...written])];
641
+ if (okTargets.length === 0) {
504
642
  return {
505
643
  name: mcp.name,
506
644
  kind: "mcp",
507
645
  status: "failed",
508
646
  targets: targetIds,
509
- error: err instanceof Error ? err.message : String(err),
647
+ error: failures.join("; "),
510
648
  };
511
649
  }
650
+ lock.mcps[mcp.name] = {
651
+ name: mcp.name,
652
+ installedFrom: getPackageVersion(),
653
+ installedAt: new Date().toISOString(),
654
+ configHash: mcpConfigHash(mcp),
655
+ targets: okTargets,
656
+ };
657
+ writeLockFile(lock);
658
+ return {
659
+ name: mcp.name,
660
+ kind: "mcp",
661
+ status: prev ? "updated" : "installed",
662
+ targets: okTargets,
663
+ error: failures.length > 0 ? `targets con fallo — ${failures.join("; ")}` : undefined,
664
+ };
512
665
  }
513
666
  export async function installAll(opts = {}) {
514
667
  let manifest;
@@ -561,9 +714,11 @@ export function uninstallSkill(name, opts = {}) {
561
714
  for (const tId of targetIds) {
562
715
  const spec = TARGETS[tId];
563
716
  if (spec.skillFormat === "folder") {
564
- const dst = join(spec.skillsDir, name);
565
- if (existsSync(dst))
566
- rmSync(dst, { recursive: true, force: true });
717
+ for (const base of skillDirsFor(spec)) {
718
+ const dst = join(base, name);
719
+ if (existsSync(dst))
720
+ rmSync(dst, { recursive: true, force: true });
721
+ }
567
722
  }
568
723
  else {
569
724
  const ext = spec.skillFormat === "cursor-mdc" ? ".mdc" : ".md";
@@ -621,6 +776,15 @@ export function uninstallAll(opts = {}) {
621
776
  results.push(uninstallSkill(name, opts));
622
777
  for (const name of Object.keys(lock.mcps ?? {}))
623
778
  results.push(uninstallMcp(name, opts));
779
+ if (!opts.dryRun) {
780
+ for (const spec of Object.values(TARGETS)) {
781
+ try {
782
+ cleanupLegacySkillArtifacts(spec);
783
+ }
784
+ catch {
785
+ }
786
+ }
787
+ }
624
788
  return results;
625
789
  }
626
790
  export function detectDrift(manifest) {
@@ -651,16 +815,27 @@ export function detectDrift(manifest) {
651
815
  toUpdate: [],
652
816
  unchanged: [],
653
817
  orphaned: [],
818
+ missing: [],
654
819
  };
655
820
  const manifestMcps = manifest.mcps ?? {};
656
821
  for (const name of Object.keys(manifestMcps)) {
657
822
  const prevMcp = lock.mcps?.[name];
658
- if (!prevMcp)
823
+ if (!prevMcp) {
659
824
  mcps.toInstall.push(name);
660
- else if (prevMcp.configHash !== mcpConfigHash(manifestMcps[name]))
825
+ continue;
826
+ }
827
+ if (prevMcp.configHash !== mcpConfigHash(manifestMcps[name])) {
661
828
  mcps.toUpdate.push(name);
662
- else
829
+ continue;
830
+ }
831
+ const missing = mcpMissingInTargets(manifestMcps[name], (prevMcp.targets ?? []));
832
+ if (missing.length > 0) {
833
+ mcps.toUpdate.push(name);
834
+ mcps.missing.push(name);
835
+ }
836
+ else {
663
837
  mcps.unchanged.push(name);
838
+ }
664
839
  }
665
840
  for (const name of Object.keys(lock.mcps ?? {})) {
666
841
  if (!manifestMcps[name])
package/dist/lib.js CHANGED
@@ -5,7 +5,7 @@ import { dirname, join, relative, sep } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  export const PRODUCT_NAME = "alegra-data-agent";
7
7
  export const COMMAND_NAME = "alegra-data-agent";
8
- export const REPO_SLUG = "alegradev/alegra-data-agent";
8
+ export const REPO_SLUG = "Alegra-Team/data-agent-selfservice";
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
11
  export const ALL_TARGETS = [
@@ -19,12 +19,6 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
19
19
  export function getPackageRoot() {
20
20
  return join(__dirname, "..");
21
21
  }
22
- export function getRegistryDir() {
23
- return join(getPackageRoot(), "skills-registry");
24
- }
25
- export function getManifestPath() {
26
- return join(getRegistryDir(), "index.json");
27
- }
28
22
  export function getClaudeSkillsDir() {
29
23
  return join(homedir(), ".claude", "skills");
30
24
  }
@@ -59,10 +53,16 @@ export function getOpenCodeConfigPath() {
59
53
  return join(base, "opencode", "opencode.json");
60
54
  }
61
55
  export function getAntigravitySkillsDir() {
62
- return join(homedir(), ".gemini", "antigravity", "skills");
56
+ return join(homedir(), ".gemini", "antigravity-cli", "skills");
57
+ }
58
+ export function getAntigravityGeminiSkillsDir() {
59
+ return join(homedir(), ".gemini", "skills");
63
60
  }
64
61
  export function getAntigravityMcpConfigPath() {
65
- return join(homedir(), ".gemini", "antigravity", "mcp_config.json");
62
+ return join(homedir(), ".gemini", "config", "mcp_config.json");
63
+ }
64
+ export function getGeminiSettingsPath() {
65
+ return join(homedir(), ".gemini", "settings.json");
66
66
  }
67
67
  export function getAntigravityLegacySkillsDir() {
68
68
  return join(homedir(), ".antigravity", "skills");
@@ -70,6 +70,28 @@ export function getAntigravityLegacySkillsDir() {
70
70
  export function getAntigravityLegacyMcpConfigPath() {
71
71
  return join(homedir(), ".antigravity", "settings.json");
72
72
  }
73
+ export function getAntigravityGeminiLegacySkillsDir() {
74
+ return join(homedir(), ".gemini", "antigravity", "skills");
75
+ }
76
+ export function getAntigravityGeminiLegacyMcpConfigPath() {
77
+ return join(homedir(), ".gemini", "antigravity", "mcp_config.json");
78
+ }
79
+ export function getCursorHooksPath() {
80
+ return join(homedir(), ".cursor", "hooks.json");
81
+ }
82
+ export function getCodexHooksPath() {
83
+ const codexHome = process.env.CODEX_HOME;
84
+ const base = codexHome && codexHome.length > 0 ? codexHome : join(homedir(), ".codex");
85
+ return join(base, "hooks.json");
86
+ }
87
+ export function getOpenCodePluginsDir() {
88
+ const xdgHome = process.env.XDG_CONFIG_HOME;
89
+ const base = xdgHome && xdgHome.length > 0 ? xdgHome : join(homedir(), ".config");
90
+ return join(base, "opencode", "plugins");
91
+ }
92
+ export function getOpenCodePluginPath() {
93
+ return join(getOpenCodePluginsDir(), "alegra-data-agent-autoupdate.js");
94
+ }
73
95
  export function getCodexSkillsDir() {
74
96
  return join(homedir(), ".agents", "skills");
75
97
  }
@@ -117,6 +139,15 @@ export function getConfigDir() {
117
139
  export function getAuthFilePath() {
118
140
  return join(getConfigDir(), "auth.json");
119
141
  }
142
+ export function getLastCheckPath() {
143
+ return join(getConfigDir(), "last-check.json");
144
+ }
145
+ export function getManifestCachePath() {
146
+ return join(getConfigDir(), "manifest-cache.json");
147
+ }
148
+ export function getAutoUpdateLogPath() {
149
+ return join(getConfigDir(), "autoupdate.log");
150
+ }
120
151
  let _cachedVersion = null;
121
152
  export function getPackageVersion() {
122
153
  if (_cachedVersion)
@@ -130,31 +161,29 @@ export function getPackageVersion() {
130
161
  }
131
162
  return _cachedVersion;
132
163
  }
133
- export function readManifest() {
134
- const path = getManifestPath();
135
- if (!existsSync(path))
136
- return null;
137
- try {
138
- return JSON.parse(readFileSync(path, "utf-8"));
139
- }
140
- catch {
141
- return null;
142
- }
164
+ let _lockCache = null;
165
+ function emptyLock() {
166
+ return { schemaVersion: 1, productName: PRODUCT_NAME, skills: {} };
143
167
  }
144
168
  export function readLockFile() {
169
+ if (_lockCache)
170
+ return _lockCache;
145
171
  const path = getLockFilePath();
146
- if (!existsSync(path)) {
147
- return { schemaVersion: 1, productName: PRODUCT_NAME, skills: {} };
148
- }
149
- try {
150
- return JSON.parse(readFileSync(path, "utf-8"));
151
- }
152
- catch {
153
- return { schemaVersion: 1, productName: PRODUCT_NAME, skills: {} };
172
+ let lock = emptyLock();
173
+ if (existsSync(path)) {
174
+ try {
175
+ lock = JSON.parse(readFileSync(path, "utf-8"));
176
+ }
177
+ catch {
178
+ lock = emptyLock();
179
+ }
154
180
  }
181
+ _lockCache = lock;
182
+ return lock;
155
183
  }
156
184
  export function writeLockFile(lock) {
157
185
  const path = getLockFilePath();
186
+ _lockCache = lock;
158
187
  mkdirSync(dirname(path), { recursive: true });
159
188
  writeFileSync(path, `${JSON.stringify(lock, null, 2)}\n`, "utf-8");
160
189
  }
@@ -254,7 +283,8 @@ export function ensureDir(path) {
254
283
  mkdirSync(path, { recursive: true });
255
284
  }
256
285
  export function adaptStdioCommand(command, args, platform = process.platform) {
257
- if (platform === "win32" && (command === "npx" || command === "npm" || command === "pnpm")) {
286
+ const winShims = new Set(["npx", "npm", "pnpm", "sf", "uvx", "bunx", "yarn", "deno"]);
287
+ if (platform === "win32" && winShims.has(command)) {
258
288
  return { command: "cmd", args: ["/c", command, ...args] };
259
289
  }
260
290
  return { command, args };
package/dist/main.js CHANGED
@@ -37,6 +37,7 @@ function parseArgs(argv) {
37
37
  noHook: false,
38
38
  noBounce: false,
39
39
  autoUpdate: false,
40
+ background: false,
40
41
  };
41
42
  const positional = [];
42
43
  const only = [];
@@ -56,6 +57,8 @@ function parseArgs(argv) {
56
57
  flags.noBounce = true;
57
58
  else if (a === "--auto-update")
58
59
  flags.autoUpdate = true;
60
+ else if (a === "--background")
61
+ flags.background = true;
59
62
  else if (a === "-h" || a === "--help")
60
63
  flags.help = true;
61
64
  else if (a === "-v" || a === "--version")
@@ -128,6 +131,7 @@ async function main() {
128
131
  exitCode = await runUpdate({
129
132
  yes: args.flags.yes,
130
133
  dryRun: args.flags.dryRun,
134
+ background: args.flags.background,
131
135
  targets: args.targets.length > 0 ? args.targets : undefined,
132
136
  });
133
137
  break;
package/dist/render.js CHANGED
@@ -81,6 +81,9 @@ export function renderMcpForTarget(mcp, spec) {
81
81
  obj.type = "remote";
82
82
  obj.enabled = true;
83
83
  }
84
+ else if (spec.mcpHttpTypeValue) {
85
+ obj.type = spec.mcpHttpTypeValue;
86
+ }
84
87
  if (mcp.auth.type === "gateway") {
85
88
  if (!mcp.auth.tokenEnv) {
86
89
  throw new Error(`MCP '${mcp.name}' es type=gateway pero no trae tokenEnv`);