atris 3.25.2 → 3.27.0

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.
@@ -2,7 +2,7 @@ const https = require('https');
2
2
  const fs = require('fs');
3
3
  const path = require('path');
4
4
  const os = require('os');
5
- const { spawnSync } = require('child_process');
5
+ const { spawn, spawnSync } = require('child_process');
6
6
 
7
7
  const PACKAGE_NAME = 'atris';
8
8
  const CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
@@ -34,6 +34,8 @@ function getCacheData() {
34
34
  return {
35
35
  lastCheck: data.lastCheck ? new Date(data.lastCheck) : null,
36
36
  latestVersion: data.latestVersion || null,
37
+ lastAutoUpdate: data.lastAutoUpdate ? new Date(data.lastAutoUpdate) : null,
38
+ lastAutoUpdateVersion: data.lastAutoUpdateVersion || null,
37
39
  };
38
40
  }
39
41
  } catch (error) {
@@ -52,7 +54,11 @@ function saveCacheData(latestVersion) {
52
54
  if (!fs.existsSync(ATRIS_DIR)) {
53
55
  fs.mkdirSync(ATRIS_DIR, { recursive: true });
54
56
  }
57
+ const existing = fs.existsSync(CACHE_FILE)
58
+ ? JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'))
59
+ : {};
55
60
  const data = {
61
+ ...existing,
56
62
  lastCheck: new Date().toISOString(),
57
63
  latestVersion: latestVersion,
58
64
  };
@@ -62,6 +68,34 @@ function saveCacheData(latestVersion) {
62
68
  }
63
69
  }
64
70
 
71
+ function markAutoUpdateStarted(latestVersion) {
72
+ try {
73
+ if (!fs.existsSync(ATRIS_DIR)) {
74
+ fs.mkdirSync(ATRIS_DIR, { recursive: true });
75
+ }
76
+ const existing = fs.existsSync(CACHE_FILE)
77
+ ? JSON.parse(fs.readFileSync(CACHE_FILE, 'utf8'))
78
+ : {};
79
+ fs.writeFileSync(CACHE_FILE, JSON.stringify({
80
+ ...existing,
81
+ lastAutoUpdate: new Date().toISOString(),
82
+ lastAutoUpdateVersion: latestVersion,
83
+ }, null, 2));
84
+ } catch (error) {
85
+ // Ignore cache write errors
86
+ }
87
+ }
88
+
89
+ function autoUpdateRecentlyStarted(latestVersion, now = new Date()) {
90
+ const cache = getCacheData();
91
+ return Boolean(
92
+ latestVersion &&
93
+ cache.lastAutoUpdate &&
94
+ cache.lastAutoUpdateVersion === latestVersion &&
95
+ now - cache.lastAutoUpdate < CHECK_INTERVAL_MS
96
+ );
97
+ }
98
+
65
99
  /**
66
100
  * Fetch the latest version of atris from npm registry.
67
101
  * @returns {Promise<string>} Latest version string
@@ -189,7 +223,7 @@ function showUpdateNotification(updateInfo) {
189
223
  // Single yellow warning line — non-intrusive
190
224
  const yellow = '\x1b[33m';
191
225
  const reset = '\x1b[0m';
192
- console.log(`${yellow}Update available: ${updateInfo.installed} → ${updateInfo.latest}. Run: npm install -g atris${reset}`);
226
+ console.log(`${yellow}Update available: ${updateInfo.installed} → ${updateInfo.latest}. Run: atris upgrade${reset}`);
193
227
  }
194
228
 
195
229
  function inspectInstallGitState(packageRoot = path.join(__dirname, '..')) {
@@ -237,37 +271,55 @@ function formatInstallGitWarning(state) {
237
271
  if (state.detached) flags.push(`detached HEAD${state.head ? ` ${state.head}` : ''}`);
238
272
  if (state.dirty) flags.push(`dirty worktree (${state.dirtyCount} file${state.dirtyCount === 1 ? '' : 's'})`);
239
273
  return `WARNING: Atris is running from a ${flags.join(' + ')} at ${state.root}.\n` +
240
- 'npm update may not change the code currently on PATH; resolve that checkout before trusting upgrade status.';
274
+ 'npm install -g atris@latest may not change the code currently on PATH; resolve that checkout before trusting upgrade status.';
241
275
  }
242
276
 
243
- function autoUpdate(updateInfo) {
277
+ function normalizeAutoUpdateMode(env = process.env) {
278
+ const raw = String(env.ATRIS_AUTO_UPDATE || '').trim().toLowerCase();
279
+ if (['0', 'false', 'no', 'off', 'notify'].includes(raw)) return 'off';
280
+ if (['1', 'true', 'yes', 'on', 'force'].includes(raw)) return 'force';
281
+ return 'auto';
282
+ }
283
+
284
+ function shouldAutoUpdate(updateInfo, state, env = process.env) {
244
285
  if (!updateInfo || !updateInfo.needsUpdate) return false;
245
286
 
246
- const { execSync } = require('child_process');
287
+ const mode = normalizeAutoUpdateMode(env);
288
+ if (mode === 'off') return false;
289
+ if (mode === 'force') return true;
247
290
 
248
- console.log('');
249
- console.log(`⬆️ Updating atris ${updateInfo.installed} ${updateInfo.latest}...`);
291
+ // Packaged npm installs are not git repositories. Git checkouts may be linked
292
+ // dev installs, where a global npm install would not update the code on PATH.
293
+ return !(state && state.isGitRepo);
294
+ }
250
295
 
251
- try {
252
- execSync('npm update -g atris', { stdio: 'pipe', timeout: 30000 });
253
- console.log(`✅ Updated to ${updateInfo.latest}`);
254
-
255
- // Auto-sync skills into current project if atris/skills/ exists
256
- try {
257
- if (fs.existsSync(path.join(process.cwd(), 'atris', 'skills'))) {
258
- execSync('atris sync', { stdio: 'pipe', timeout: 10000 });
259
- console.log(`✅ Skills synced`);
260
- }
261
- } catch (e) {
262
- // Sync failed — not critical
263
- }
296
+ function autoUpdate(updateInfo, options = {}) {
297
+ if (!updateInfo || !updateInfo.needsUpdate) return false;
298
+
299
+ const env = options.env || process.env;
300
+ const packageRoot = options.packageRoot || path.join(__dirname, '..');
301
+ const installState = options.installState || inspectInstallGitState(packageRoot);
302
+ if (!shouldAutoUpdate(updateInfo, installState, env)) return false;
303
+
304
+ const recentlyStarted = options.recentlyStarted || autoUpdateRecentlyStarted;
305
+ if (recentlyStarted(updateInfo.latest)) return false;
264
306
 
265
- console.log('');
307
+ const spawnImpl = options.spawn || spawn;
308
+ const markStarted = options.markStarted || markAutoUpdateStarted;
309
+ const log = options.log || console.log;
310
+
311
+ try {
312
+ const child = spawnImpl('npm', ['install', '-g', `${PACKAGE_NAME}@latest`], {
313
+ detached: true,
314
+ stdio: 'ignore',
315
+ windowsHide: true,
316
+ });
317
+ if (child && typeof child.on === 'function') child.on('error', () => {});
318
+ if (child && typeof child.unref === 'function') child.unref();
319
+ markStarted(updateInfo.latest);
320
+ log(`Auto-update started: atris ${updateInfo.installed} -> ${updateInfo.latest} (background)`);
266
321
  return true;
267
322
  } catch (error) {
268
- // npm update failed (permissions, network, etc) — fall back to notification
269
- console.log(`⚠️ Auto-update failed. Run manually: npm update -g atris`);
270
- console.log('');
271
323
  return false;
272
324
  }
273
325
  }
@@ -276,6 +328,7 @@ module.exports = {
276
328
  checkForUpdates,
277
329
  showUpdateNotification,
278
330
  autoUpdate,
331
+ shouldAutoUpdate,
279
332
  inspectInstallGitState,
280
333
  formatInstallGitWarning,
281
334
  };