alegra-data-agent 0.7.2 → 0.7.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.
@@ -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, getAntigravityHooksPath, getClaudeSettingsPath, getCodexHooksPath, getCursorHooksPath, getGeminiSettingsPath, getHermesHome, getHermesPluginsDir, getOpenCodePluginPath, readLockFile, } from "./lib.js";
7
+ import { BACKGROUND_AUTH_FAIL_COOLDOWN_MS, BACKGROUND_COOLDOWN_MS, COMMAND_NAME, PRODUCT_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 {};
@@ -15,12 +15,21 @@ function readJsonSafe(path) {
15
15
  return null;
16
16
  }
17
17
  }
18
- function writeJson(path, data) {
18
+ function writeIfChanged(path, content) {
19
+ try {
20
+ if (readFileSync(path, "utf-8") === content)
21
+ return;
22
+ }
23
+ catch {
24
+ }
19
25
  mkdirSync(dirname(path), { recursive: true });
20
26
  const tmp = `${path}.tmp-${process.pid}`;
21
- writeFileSync(tmp, `${JSON.stringify(data, null, 2)}\n`, "utf-8");
27
+ writeFileSync(tmp, content, "utf-8");
22
28
  renameSync(tmp, path);
23
29
  }
30
+ function writeJson(path, data) {
31
+ writeIfChanged(path, `${JSON.stringify(data, null, 2)}\n`);
32
+ }
24
33
  function entryIsOurs(m) {
25
34
  if (!m || typeof m !== "object")
26
35
  return false;
@@ -126,14 +135,39 @@ function antigravityHookIsOurs() {
126
135
  }
127
136
  function installOpenCode(command) {
128
137
  const path = getOpenCodePluginPath();
129
- mkdirSync(dirname(path), { recursive: true });
130
138
  const plugin = `// alegra-data-agent: auto-update al iniciar OpenCode. Generado por el instalador.
131
139
  // Borrar este archivo desactiva el auto-update. No editar a mano.
132
140
  import { spawn } from "node:child_process"
141
+ import { readFileSync } from "node:fs"
142
+ import { homedir } from "node:os"
143
+ import { join } from "node:path"
133
144
 
134
145
  const COMMAND = ${JSON.stringify(command)}
146
+ const COOLDOWN_MS = ${BACKGROUND_COOLDOWN_MS}
147
+ const AUTH_FAIL_COOLDOWN_MS = ${BACKGROUND_AUTH_FAIL_COOLDOWN_MS}
148
+
149
+ let launchedAt = 0
150
+
151
+ function within(iso, windowMs) {
152
+ const at = Date.parse(iso || "")
153
+ if (Number.isNaN(at)) return false
154
+ const age = Date.now() - at
155
+ return age >= 0 && age < windowMs
156
+ }
157
+
158
+ function checkedRecently() {
159
+ try {
160
+ const base = process.env.XDG_CONFIG_HOME || join(homedir(), ".config")
161
+ const last = JSON.parse(readFileSync(join(base, ${JSON.stringify(PRODUCT_NAME)}, "last-check.json"), "utf-8"))
162
+ return within(last.checkedAt, COOLDOWN_MS) || within(last.authFailedAt, AUTH_FAIL_COOLDOWN_MS)
163
+ } catch (_e) {
164
+ return false
165
+ }
166
+ }
135
167
 
136
168
  function runUpdate() {
169
+ if (checkedRecently() || Date.now() - launchedAt < COOLDOWN_MS) return
170
+ launchedAt = Date.now()
137
171
  try {
138
172
  const child = spawn(COMMAND, { shell: true, detached: true, stdio: "ignore", windowsHide: true })
139
173
  child.on("error", () => {})
@@ -154,13 +188,12 @@ export default {
154
188
  },
155
189
  }
156
190
  `;
157
- writeFileSync(path, plugin, "utf-8");
191
+ writeIfChanged(path, plugin);
158
192
  }
159
193
  function installHermes(command) {
160
194
  const dir = join(getHermesPluginsDir(), HERMES_PLUGIN_NAME);
161
- mkdirSync(dir, { recursive: true });
162
- writeFileSync(join(dir, "plugin.yaml"), hermesPluginManifest(), "utf-8");
163
- writeFileSync(join(dir, "__init__.py"), hermesPluginModule(command), "utf-8");
195
+ writeIfChanged(join(dir, "plugin.yaml"), hermesPluginManifest());
196
+ writeIfChanged(join(dir, "__init__.py"), hermesPluginModule(command));
164
197
  enableHermesPlugin();
165
198
  }
166
199
  function enableHermesPlugin() {
@@ -6,15 +6,13 @@ import { hasAutoUpdate, refreshAutoUpdate } from "../autoupdate.js";
6
6
  import { approveCodexHook, hasCodexTrustConsent } from "../codex-trust.js";
7
7
  import { cyan, dim, gray, green, log, logError, yellow } from "../colors.js";
8
8
  import { detectDrift, installAll, summarize } from "../installer.js";
9
- import { COMMAND_NAME, getAutoUpdateLogPath, getLastCheckPath, getPackageVersion, } from "../lib.js";
9
+ import { BACKGROUND_AUTH_FAIL_COOLDOWN_MS, BACKGROUND_COOLDOWN_MS, COMMAND_NAME, getAutoUpdateLogPath, getLastCheckPath, getPackageVersion, } from "../lib.js";
10
10
  import { banner } from "../ui.js";
11
11
  import { runLogin } from "./login.js";
12
- const BACKGROUND_COOLDOWN_MS = 15 * 60_000;
13
12
  const BACKGROUND_LOGIN_TIMEOUT_MS = (() => {
14
13
  const v = Number(process.env.ALEGRA_DATA_AGENT_BG_LOGIN_TIMEOUT_MS);
15
14
  return Number.isFinite(v) && v > 0 ? v : 90_000;
16
15
  })();
17
- export const BACKGROUND_AUTH_FAIL_COOLDOWN_MS = 60 * 60_000;
18
16
  function readLastCheck() {
19
17
  try {
20
18
  const parsed = JSON.parse(readFileSync(getLastCheckPath(), "utf-8"));
@@ -1,4 +1,4 @@
1
- import { COMMAND_NAME } from "./lib.js";
1
+ import { BACKGROUND_AUTH_FAIL_COOLDOWN_MS, BACKGROUND_COOLDOWN_MS, COMMAND_NAME } from "./lib.js";
2
2
  export const HERMES_PLUGIN_NAME = "alegra-data-agent-autoupdate";
3
3
  export function hermesPluginManifest() {
4
4
  return `# Generado por ${COMMAND_NAME}. Borrar esta carpeta desactiva el auto-update.
@@ -18,16 +18,23 @@ Generado por el instalador — no editar a mano. Borrar esta carpeta (o correr
18
18
  \`${COMMAND_NAME} uninstall\`) desactiva el auto-update.
19
19
  """
20
20
 
21
+ import json
21
22
  import logging
22
23
  import os
23
24
  import shlex
24
25
  import shutil
25
26
  import subprocess
27
+ import time
28
+ from datetime import datetime
26
29
  from pathlib import Path
27
30
 
28
31
  logger = logging.getLogger(__name__)
29
32
 
30
33
  COMMAND = ${commandLiteral}
34
+ COOLDOWN_S = ${BACKGROUND_COOLDOWN_MS / 1000}
35
+ AUTH_FAIL_COOLDOWN_S = ${BACKGROUND_AUTH_FAIL_COOLDOWN_MS / 1000}
36
+
37
+ _launched_at = 0.0
31
38
 
32
39
  # Hermes puede arrancar sin el PATH del shell del usuario (Desktop, servicios):
33
40
  # ahí \`pnpm\` no resuelve y el auto-update moriría en silencio.
@@ -40,11 +47,35 @@ EXTRA_PATH_DIRS = (
40
47
  )
41
48
 
42
49
 
50
+ def _config_dir() -> Path:
51
+ base = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config")
52
+ return Path(base) / ${productLiteral}
53
+
54
+
55
+ def _within(iso, window_s: float) -> bool:
56
+ try:
57
+ at = datetime.fromisoformat(str(iso).replace("Z", "+00:00")).timestamp()
58
+ except Exception:
59
+ return False
60
+ age = time.time() - at
61
+ return 0 <= age < window_s
62
+
63
+
64
+ def _checked_recently() -> bool:
65
+ """Mismas ventanas que \`update --background\`: si el último chequeo es reciente, no se lanza nada."""
66
+ try:
67
+ last = json.loads((_config_dir() / "last-check.json").read_text(encoding="utf-8"))
68
+ return _within(last.get("checkedAt"), COOLDOWN_S) or _within(
69
+ last.get("authFailedAt"), AUTH_FAIL_COOLDOWN_S
70
+ )
71
+ except Exception:
72
+ return False
73
+
74
+
43
75
  def _log_skip(reason: str) -> None:
44
76
  """Deja constancia en autoupdate.log — el mismo archivo que reporta status."""
45
77
  try:
46
- base = os.environ.get("XDG_CONFIG_HOME") or os.path.expanduser("~/.config")
47
- log_file = Path(base) / ${productLiteral} / "autoupdate.log"
78
+ log_file = _config_dir() / "autoupdate.log"
48
79
  log_file.parent.mkdir(parents=True, exist_ok=True)
49
80
  with log_file.open("a", encoding="utf-8") as fh:
50
81
  fh.write(f"[hermes] {reason}\\n")
@@ -54,6 +85,9 @@ def _log_skip(reason: str) -> None:
54
85
 
55
86
  def _run_update(**_kwargs) -> None:
56
87
  """Lanza el update y vuelve: nunca bloquea ni rompe el arranque."""
88
+ global _launched_at
89
+ if _checked_recently() or time.time() - _launched_at < COOLDOWN_S:
90
+ return
57
91
  try:
58
92
  argv = shlex.split(COMMAND)
59
93
  if not argv:
@@ -73,6 +107,7 @@ def _run_update(**_kwargs) -> None:
73
107
  stderr=subprocess.DEVNULL,
74
108
  start_new_session=True,
75
109
  )
110
+ _launched_at = time.time()
76
111
  except Exception as exc:
77
112
  logger.debug("%s: auto-update no se pudo lanzar: %s", ${productLiteral}, exc)
78
113
 
package/dist/lib.js CHANGED
@@ -288,6 +288,8 @@ export function getAuthFilePath() {
288
288
  export function getLastCheckPath() {
289
289
  return join(getConfigDir(), "last-check.json");
290
290
  }
291
+ export const BACKGROUND_COOLDOWN_MS = 15 * 60_000;
292
+ export const BACKGROUND_AUTH_FAIL_COOLDOWN_MS = 60 * 60_000;
291
293
  export function getManifestCachePath() {
292
294
  return join(getConfigDir(), "manifest-cache.json");
293
295
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alegra-data-agent",
3
- "version": "0.7.2",
3
+ "version": "0.7.4",
4
4
  "private": false,
5
5
  "description": "Agente de Data interno de Alegra. Instala skills + MCPs en tu cliente de IA con un comando.",
6
6
  "license": "UNLICENSED",