agileflow 2.55.0 → 2.57.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.
package/README.md CHANGED
@@ -4,9 +4,8 @@
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/agileflow?color=brightgreen)](https://www.npmjs.com/package/agileflow)
6
6
  [![Commands](https://img.shields.io/badge/commands-41-blue)](docs/04-architecture/commands.md)
7
- [![Subagents](https://img.shields.io/badge/subagents-26-orange)](docs/04-architecture/subagents.md)
7
+ [![Agents/Experts](https://img.shields.io/badge/agents%2Fexperts-26-orange)](docs/04-architecture/subagents.md)
8
8
  [![Skills](https://img.shields.io/badge/skills-23-purple)](docs/04-architecture/skills.md)
9
- [![Experts](https://img.shields.io/badge/experts-26-green)](docs/04-architecture/agent-expert-system.md)
10
9
 
11
10
  **AI-driven agile development for Claude Code, Cursor, Windsurf, OpenAI Codex CLI, and more.** Combining Scrum, Kanban, ADRs, and docs-as-code principles into one framework-agnostic system.
12
11
 
@@ -68,9 +67,8 @@ AgileFlow combines three proven methodologies:
68
67
  | Component | Count | Description |
69
68
  |-----------|-------|-------------|
70
69
  | [Commands](docs/04-architecture/commands.md) | 41 | Slash commands for agile workflows |
71
- | [Subagents](docs/04-architecture/subagents.md) | 26 | Specialized agents for focused work |
70
+ | [Agents/Experts](docs/04-architecture/subagents.md) | 26 | Specialized agents with self-improving knowledge bases |
72
71
  | [Skills](docs/04-architecture/skills.md) | 23 | Auto-activated context helpers |
73
- | [Experts](docs/04-architecture/agent-expert-system.md) | 26 | Self-improving knowledge bases |
74
72
 
75
73
  ---
76
74
 
@@ -80,7 +78,7 @@ Full documentation lives in [`docs/04-architecture/`](docs/04-architecture/):
80
78
 
81
79
  ### Reference
82
80
  - [Commands](docs/04-architecture/commands.md) - All 41 slash commands
83
- - [Subagents](docs/04-architecture/subagents.md) - All 26 specialized agents
81
+ - [Agents/Experts](docs/04-architecture/subagents.md) - 26 specialized agents with self-improving knowledge
84
82
  - [Skills](docs/04-architecture/skills.md) - 23 auto-loaded skills
85
83
 
86
84
  ### Architecture
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agileflow",
3
- "version": "2.55.0",
3
+ "version": "2.57.0",
4
4
  "description": "AI-driven agile development system for Claude Code, Cursor, Windsurf, and more",
5
5
  "keywords": [
6
6
  "agile",
@@ -18,6 +18,14 @@ const { execSync, spawnSync } = require('child_process');
18
18
  // Session manager path (relative to script location)
19
19
  const SESSION_MANAGER_PATH = path.join(__dirname, 'session-manager.js');
20
20
 
21
+ // Update checker module
22
+ let updateChecker;
23
+ try {
24
+ updateChecker = require('./check-update.js');
25
+ } catch (e) {
26
+ // Update checker not available
27
+ }
28
+
21
29
  // ANSI color codes
22
30
  const c = {
23
31
  reset: '\x1b[0m',
@@ -322,6 +330,98 @@ function compareVersions(a, b) {
322
330
  return 0;
323
331
  }
324
332
 
333
+ // Check for updates (async but we'll use sync approach for welcome)
334
+ async function checkUpdates() {
335
+ const result = {
336
+ available: false,
337
+ installed: null,
338
+ latest: null,
339
+ justUpdated: false,
340
+ previousVersion: null,
341
+ autoUpdate: false,
342
+ changelog: [],
343
+ };
344
+
345
+ if (!updateChecker) return result;
346
+
347
+ try {
348
+ const updateInfo = await updateChecker.checkForUpdates();
349
+ result.installed = updateInfo.installed;
350
+ result.latest = updateInfo.latest;
351
+ result.available = updateInfo.updateAvailable;
352
+ result.justUpdated = updateInfo.justUpdated;
353
+ result.previousVersion = updateInfo.previousVersion;
354
+ result.autoUpdate = updateInfo.autoUpdate;
355
+
356
+ // If just updated, try to get changelog entries
357
+ if (result.justUpdated && result.installed) {
358
+ result.changelog = getChangelogEntries(result.installed);
359
+ }
360
+ } catch (e) {
361
+ // Silently fail - update check is non-critical
362
+ }
363
+
364
+ return result;
365
+ }
366
+
367
+ // Parse CHANGELOG.md for entries of a specific version
368
+ function getChangelogEntries(version) {
369
+ const entries = [];
370
+
371
+ try {
372
+ // Look for CHANGELOG.md in .agileflow or package location
373
+ const possiblePaths = [
374
+ path.join(__dirname, '..', 'CHANGELOG.md'),
375
+ path.join(__dirname, 'CHANGELOG.md'),
376
+ ];
377
+
378
+ let changelogContent = null;
379
+ for (const p of possiblePaths) {
380
+ if (fs.existsSync(p)) {
381
+ changelogContent = fs.readFileSync(p, 'utf8');
382
+ break;
383
+ }
384
+ }
385
+
386
+ if (!changelogContent) return entries;
387
+
388
+ // Find the section for this version
389
+ const versionPattern = new RegExp(`## \\[${version}\\].*?\\n([\\s\\S]*?)(?=## \\[|$)`);
390
+ const match = changelogContent.match(versionPattern);
391
+
392
+ if (match) {
393
+ // Extract bullet points from Added/Changed/Fixed sections
394
+ const lines = match[1].split('\n');
395
+ for (const line of lines) {
396
+ const bulletMatch = line.match(/^- (.+)$/);
397
+ if (bulletMatch && entries.length < 3) {
398
+ entries.push(bulletMatch[1]);
399
+ }
400
+ }
401
+ }
402
+ } catch (e) {
403
+ // Silently fail
404
+ }
405
+
406
+ return entries;
407
+ }
408
+
409
+ // Run auto-update if enabled
410
+ async function runAutoUpdate(rootDir) {
411
+ try {
412
+ console.log(`${c.cyan}Updating AgileFlow...${c.reset}`);
413
+ execSync('npx agileflow update', {
414
+ cwd: rootDir,
415
+ encoding: 'utf8',
416
+ stdio: 'inherit',
417
+ });
418
+ return true;
419
+ } catch (e) {
420
+ console.log(`${c.yellow}Auto-update failed. Run manually: npx agileflow update${c.reset}`);
421
+ return false;
422
+ }
423
+ }
424
+
325
425
  function getFeatureVersions(rootDir) {
326
426
  const result = {
327
427
  hooks: { version: null, outdated: false },
@@ -394,7 +494,7 @@ function truncate(str, maxLen, suffix = '..') {
394
494
  return str.substring(0, cutIndex) + suffix;
395
495
  }
396
496
 
397
- function formatTable(info, archival, session, precompact, parallelSessions) {
497
+ function formatTable(info, archival, session, precompact, parallelSessions, updateInfo = {}) {
398
498
  const W = 58; // inner width
399
499
  const R = W - 24; // right column width (34 chars)
400
500
  const lines = [];
@@ -407,25 +507,66 @@ function formatTable(info, archival, session, precompact, parallelSessions) {
407
507
  return `${c.dim}${box.v}${c.reset} ${pad(leftStr, 20)} ${c.dim}${box.v}${c.reset} ${pad(rightStr, R)} ${c.dim}${box.v}${c.reset}`;
408
508
  };
409
509
 
510
+ // Helper for full-width row (spans both columns)
511
+ const fullRow = (content, color = '') => {
512
+ const contentStr = `${color}${content}${color ? c.reset : ''}`;
513
+ return `${c.dim}${box.v}${c.reset} ${pad(contentStr, W - 1)} ${c.dim}${box.v}${c.reset}`;
514
+ };
515
+
410
516
  const divider = () =>
411
517
  `${c.dim}${box.lT}${box.h.repeat(22)}${box.cross}${box.h.repeat(W - 22)}${box.rT}${c.reset}`;
518
+ const fullDivider = () =>
519
+ `${c.dim}${box.lT}${box.h.repeat(W)}${box.rT}${c.reset}`;
412
520
  const topBorder = `${c.dim}${box.tl}${box.h.repeat(22)}${box.tT}${box.h.repeat(W - 22)}${box.tr}${c.reset}`;
413
521
  const bottomBorder = `${c.dim}${box.bl}${box.h.repeat(22)}${box.bT}${box.h.repeat(W - 22)}${box.br}${c.reset}`;
414
522
 
415
- // Header (truncate branch name if too long)
523
+ // Header with version and optional update indicator
416
524
  const branchColor =
417
525
  info.branch === 'main' ? c.green : info.branch.startsWith('fix') ? c.red : c.cyan;
418
- // Fixed parts: "agileflow " (10) + "v" (1) + version + " " (2) + " (" (2) + commit (7) + ")" (1) = 23 + version.length
419
- const maxBranchLen = W - 1 - 23 - info.version.length;
526
+
527
+ // Build version string with update status
528
+ let versionStr = `v${info.version}`;
529
+ if (updateInfo.justUpdated && updateInfo.previousVersion) {
530
+ versionStr = `v${info.version} ${c.green}✓${c.reset}${c.dim} (was v${updateInfo.previousVersion})`;
531
+ } else if (updateInfo.available && updateInfo.latest) {
532
+ versionStr = `v${info.version} ${c.yellow}↑${updateInfo.latest}${c.reset}`;
533
+ }
534
+
535
+ // Calculate remaining space for branch
536
+ const versionVisibleLen = updateInfo.justUpdated
537
+ ? info.version.length + 20 + (updateInfo.previousVersion?.length || 0)
538
+ : updateInfo.available
539
+ ? info.version.length + 3 + (updateInfo.latest?.length || 0)
540
+ : info.version.length;
541
+ const maxBranchLen = W - 1 - 15 - versionVisibleLen;
420
542
  const branchDisplay =
421
543
  info.branch.length > maxBranchLen
422
- ? info.branch.substring(0, maxBranchLen - 2) + '..'
544
+ ? info.branch.substring(0, Math.max(5, maxBranchLen - 2)) + '..'
423
545
  : info.branch;
424
- const header = `${c.brand}${c.bold}agileflow${c.reset} ${c.dim}v${info.version}${c.reset} ${branchColor}${branchDisplay}${c.reset} ${c.dim}(${info.commit})${c.reset}`;
546
+
547
+ const header = `${c.brand}${c.bold}agileflow${c.reset} ${c.dim}${versionStr}${c.reset} ${branchColor}${branchDisplay}${c.reset} ${c.dim}(${info.commit})${c.reset}`;
425
548
  const headerLine = `${c.dim}${box.v}${c.reset} ${pad(header, W - 1)} ${c.dim}${box.v}${c.reset}`;
426
549
 
427
550
  lines.push(topBorder);
428
551
  lines.push(headerLine);
552
+
553
+ // Show update available notification
554
+ if (updateInfo.available && updateInfo.latest && !updateInfo.justUpdated) {
555
+ lines.push(fullDivider());
556
+ lines.push(fullRow(`↑ Update available: v${updateInfo.latest}`, c.yellow));
557
+ lines.push(fullRow(` Run: npx agileflow update`, c.dim));
558
+ }
559
+
560
+ // Show "just updated" changelog
561
+ if (updateInfo.justUpdated && updateInfo.changelog && updateInfo.changelog.length > 0) {
562
+ lines.push(fullDivider());
563
+ lines.push(fullRow(`What's new in v${info.version}:`, c.green));
564
+ for (const entry of updateInfo.changelog.slice(0, 2)) {
565
+ lines.push(fullRow(`• ${truncate(entry, W - 4)}`, c.dim));
566
+ }
567
+ lines.push(fullRow(`Run /agileflow:whats-new for full changelog`, c.dim));
568
+ }
569
+
429
570
  lines.push(divider());
430
571
 
431
572
  // Stories section
@@ -535,7 +676,7 @@ function formatTable(info, archival, session, precompact, parallelSessions) {
535
676
  }
536
677
 
537
678
  // Main
538
- function main() {
679
+ async function main() {
539
680
  const rootDir = getProjectRoot();
540
681
  const info = getProjectInfo(rootDir);
541
682
  const archival = runArchival(rootDir);
@@ -543,7 +684,29 @@ function main() {
543
684
  const precompact = checkPreCompact(rootDir);
544
685
  const parallelSessions = checkParallelSessions(rootDir);
545
686
 
546
- console.log(formatTable(info, archival, session, precompact, parallelSessions));
687
+ // Check for updates (async, cached)
688
+ let updateInfo = {};
689
+ try {
690
+ updateInfo = await checkUpdates();
691
+
692
+ // If auto-update is enabled and update available, run it
693
+ if (updateInfo.available && updateInfo.autoUpdate) {
694
+ const updated = await runAutoUpdate(rootDir);
695
+ if (updated) {
696
+ // Re-run welcome after update (the new version will show changelog)
697
+ return;
698
+ }
699
+ }
700
+
701
+ // Mark current version as seen to track for next update
702
+ if (updateInfo.justUpdated && updateChecker) {
703
+ updateChecker.markVersionSeen(info.version);
704
+ }
705
+ } catch (e) {
706
+ // Update check failed - continue without it
707
+ }
708
+
709
+ console.log(formatTable(info, archival, session, precompact, parallelSessions, updateInfo));
547
710
 
548
711
  // Show warning and tip if other sessions are active
549
712
  if (parallelSessions.otherActive > 0) {
@@ -554,4 +717,4 @@ function main() {
554
717
  }
555
718
  }
556
719
 
557
- main();
720
+ main().catch(console.error);
@@ -0,0 +1,325 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * check-update.js - Check for AgileFlow updates with caching
5
+ *
6
+ * Features:
7
+ * - Checks npm registry for latest version
8
+ * - Caches results to avoid excessive requests
9
+ * - Configurable check frequency (hourly, daily, weekly)
10
+ * - Returns structured JSON for easy parsing
11
+ *
12
+ * Usage:
13
+ * node check-update.js [--force] [--json]
14
+ *
15
+ * Options:
16
+ * --force Bypass cache and check npm directly
17
+ * --json Output as JSON (default is human-readable)
18
+ *
19
+ * Environment:
20
+ * DEBUG_UPDATE=1 Enable debug logging
21
+ */
22
+
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+ const https = require('https');
26
+
27
+ // Debug mode
28
+ const DEBUG = process.env.DEBUG_UPDATE === '1';
29
+
30
+ function debugLog(message, data = null) {
31
+ if (DEBUG) {
32
+ console.error(`[check-update] ${message}`, data ? JSON.stringify(data) : '');
33
+ }
34
+ }
35
+
36
+ // Find project root (has .agileflow directory)
37
+ function getProjectRoot() {
38
+ let dir = process.cwd();
39
+ while (!fs.existsSync(path.join(dir, '.agileflow')) && dir !== '/') {
40
+ dir = path.dirname(dir);
41
+ }
42
+ return dir !== '/' ? dir : process.cwd();
43
+ }
44
+
45
+ // Get installed AgileFlow version
46
+ function getInstalledVersion(rootDir) {
47
+ // First check .agileflow/package.json (installed version)
48
+ const agileflowPkg = path.join(rootDir, '.agileflow', 'package.json');
49
+ if (fs.existsSync(agileflowPkg)) {
50
+ try {
51
+ const pkg = JSON.parse(fs.readFileSync(agileflowPkg, 'utf8'));
52
+ if (pkg.version) return pkg.version;
53
+ } catch (e) {
54
+ debugLog('Error reading .agileflow/package.json', e.message);
55
+ }
56
+ }
57
+
58
+ // Fallback: check if this is the AgileFlow dev repo
59
+ const cliPkg = path.join(rootDir, 'packages/cli/package.json');
60
+ if (fs.existsSync(cliPkg)) {
61
+ try {
62
+ const pkg = JSON.parse(fs.readFileSync(cliPkg, 'utf8'));
63
+ if (pkg.name === 'agileflow' && pkg.version) return pkg.version;
64
+ } catch (e) {}
65
+ }
66
+
67
+ return null;
68
+ }
69
+
70
+ // Get update configuration from metadata
71
+ function getUpdateConfig(rootDir) {
72
+ const defaults = {
73
+ autoUpdate: false,
74
+ checkFrequency: 'daily', // hourly, daily, weekly, never
75
+ showChangelog: true,
76
+ lastCheck: null,
77
+ lastSeenVersion: null,
78
+ latestVersion: null,
79
+ };
80
+
81
+ try {
82
+ const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
83
+ if (fs.existsSync(metadataPath)) {
84
+ const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
85
+ if (metadata.updates) {
86
+ return { ...defaults, ...metadata.updates };
87
+ }
88
+ }
89
+ } catch (e) {
90
+ debugLog('Error reading update config', e.message);
91
+ }
92
+
93
+ return defaults;
94
+ }
95
+
96
+ // Save update configuration
97
+ function saveUpdateConfig(rootDir, config) {
98
+ try {
99
+ const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
100
+ let metadata = {};
101
+
102
+ if (fs.existsSync(metadataPath)) {
103
+ metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
104
+ }
105
+
106
+ metadata.updates = config;
107
+ fs.writeFileSync(metadataPath, JSON.stringify(metadata, null, 2) + '\n');
108
+ return true;
109
+ } catch (e) {
110
+ debugLog('Error saving update config', e.message);
111
+ return false;
112
+ }
113
+ }
114
+
115
+ // Check if cache is still valid
116
+ function isCacheValid(config) {
117
+ if (!config.lastCheck) return false;
118
+
119
+ const now = Date.now();
120
+ const lastCheck = new Date(config.lastCheck).getTime();
121
+ const age = now - lastCheck;
122
+
123
+ // Convert frequency to milliseconds
124
+ const frequencies = {
125
+ hourly: 60 * 60 * 1000, // 1 hour
126
+ daily: 24 * 60 * 60 * 1000, // 24 hours
127
+ weekly: 7 * 24 * 60 * 60 * 1000, // 7 days
128
+ never: Infinity,
129
+ };
130
+
131
+ const maxAge = frequencies[config.checkFrequency] || frequencies.daily;
132
+
133
+ debugLog('Cache check', {
134
+ lastCheck: config.lastCheck,
135
+ ageMs: age,
136
+ maxAgeMs: maxAge,
137
+ valid: age < maxAge,
138
+ });
139
+
140
+ return age < maxAge;
141
+ }
142
+
143
+ // Fetch latest version from npm
144
+ async function fetchLatestVersion() {
145
+ return new Promise(resolve => {
146
+ const options = {
147
+ hostname: 'registry.npmjs.org',
148
+ port: 443,
149
+ path: '/agileflow/latest',
150
+ method: 'GET',
151
+ headers: {
152
+ 'User-Agent': 'agileflow-cli',
153
+ },
154
+ };
155
+
156
+ debugLog('Fetching from npm registry');
157
+
158
+ const req = https.request(options, res => {
159
+ let data = '';
160
+
161
+ res.on('data', chunk => {
162
+ data += chunk;
163
+ });
164
+
165
+ res.on('end', () => {
166
+ if (res.statusCode !== 200) {
167
+ debugLog('Non-200 status', { statusCode: res.statusCode });
168
+ return resolve(null);
169
+ }
170
+
171
+ try {
172
+ const json = JSON.parse(data);
173
+ debugLog('Version found', { version: json.version });
174
+ resolve(json.version || null);
175
+ } catch (err) {
176
+ debugLog('JSON parse error', err.message);
177
+ resolve(null);
178
+ }
179
+ });
180
+ });
181
+
182
+ req.on('error', err => {
183
+ debugLog('Network error', err.message);
184
+ resolve(null);
185
+ });
186
+
187
+ req.setTimeout(5000, () => {
188
+ debugLog('Request timeout');
189
+ req.destroy();
190
+ resolve(null);
191
+ });
192
+
193
+ req.end();
194
+ });
195
+ }
196
+
197
+ // Compare semantic versions: returns -1 if a < b, 0 if equal, 1 if a > b
198
+ function compareVersions(a, b) {
199
+ if (!a || !b) return 0;
200
+ const partsA = a.split('.').map(Number);
201
+ const partsB = b.split('.').map(Number);
202
+ for (let i = 0; i < 3; i++) {
203
+ const numA = partsA[i] || 0;
204
+ const numB = partsB[i] || 0;
205
+ if (numA < numB) return -1;
206
+ if (numA > numB) return 1;
207
+ }
208
+ return 0;
209
+ }
210
+
211
+ // Main check function
212
+ async function checkForUpdates(options = {}) {
213
+ const rootDir = getProjectRoot();
214
+ const installedVersion = getInstalledVersion(rootDir);
215
+ const config = getUpdateConfig(rootDir);
216
+
217
+ const result = {
218
+ installed: installedVersion,
219
+ latest: null,
220
+ updateAvailable: false,
221
+ autoUpdate: config.autoUpdate,
222
+ justUpdated: false,
223
+ previousVersion: config.lastSeenVersion,
224
+ fromCache: false,
225
+ error: null,
226
+ };
227
+
228
+ // No installed version found
229
+ if (!installedVersion) {
230
+ result.error = 'Could not determine installed version';
231
+ return result;
232
+ }
233
+
234
+ // Check if we just updated (lastSeenVersion < installed)
235
+ if (
236
+ config.lastSeenVersion &&
237
+ compareVersions(config.lastSeenVersion, installedVersion) < 0
238
+ ) {
239
+ result.justUpdated = true;
240
+ result.previousVersion = config.lastSeenVersion;
241
+ }
242
+
243
+ // Use cache if valid and not forced
244
+ if (!options.force && isCacheValid(config) && config.latestVersion) {
245
+ result.latest = config.latestVersion;
246
+ result.fromCache = true;
247
+ } else if (config.checkFrequency !== 'never') {
248
+ // Fetch from npm
249
+ result.latest = await fetchLatestVersion();
250
+
251
+ // Update cache
252
+ if (result.latest) {
253
+ config.lastCheck = new Date().toISOString();
254
+ config.latestVersion = result.latest;
255
+ saveUpdateConfig(rootDir, config);
256
+ }
257
+ }
258
+
259
+ // Compare versions
260
+ if (result.latest && compareVersions(installedVersion, result.latest) < 0) {
261
+ result.updateAvailable = true;
262
+ }
263
+
264
+ return result;
265
+ }
266
+
267
+ // Mark version as seen (after update or dismissal)
268
+ function markVersionSeen(version) {
269
+ const rootDir = getProjectRoot();
270
+ const config = getUpdateConfig(rootDir);
271
+ config.lastSeenVersion = version;
272
+ saveUpdateConfig(rootDir, config);
273
+ }
274
+
275
+ // CLI interface
276
+ async function main() {
277
+ const args = process.argv.slice(2);
278
+ const force = args.includes('--force');
279
+ const jsonOutput = args.includes('--json');
280
+ const markSeen = args.includes('--mark-seen');
281
+
282
+ if (markSeen) {
283
+ const version = args[args.indexOf('--mark-seen') + 1];
284
+ if (version) {
285
+ markVersionSeen(version);
286
+ if (jsonOutput) {
287
+ console.log(JSON.stringify({ success: true, version }));
288
+ } else {
289
+ console.log(`Marked version ${version} as seen`);
290
+ }
291
+ }
292
+ return;
293
+ }
294
+
295
+ const result = await checkForUpdates({ force });
296
+
297
+ if (jsonOutput) {
298
+ console.log(JSON.stringify(result, null, 2));
299
+ } else {
300
+ if (result.error) {
301
+ console.log(`Error: ${result.error}`);
302
+ } else if (result.justUpdated) {
303
+ console.log(`Updated from v${result.previousVersion} to v${result.installed}`);
304
+ } else if (result.updateAvailable) {
305
+ console.log(`Update available: v${result.installed} -> v${result.latest}`);
306
+ console.log(`Run: npx agileflow update`);
307
+ } else {
308
+ console.log(`AgileFlow v${result.installed} is up to date`);
309
+ }
310
+ }
311
+ }
312
+
313
+ // Export for use as module
314
+ module.exports = {
315
+ checkForUpdates,
316
+ markVersionSeen,
317
+ getUpdateConfig,
318
+ saveUpdateConfig,
319
+ compareVersions,
320
+ };
321
+
322
+ // Run CLI if executed directly
323
+ if (require.main === module) {
324
+ main().catch(console.error);
325
+ }