clementine-agent 1.1.21 → 1.1.22

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/cli/index.js CHANGED
@@ -428,17 +428,22 @@ async function cmdRestart(options) {
428
428
  }
429
429
  }
430
430
  function cmdStatus() {
431
+ const DIM = '\x1b[0;90m';
432
+ const RESET = '\x1b[0m';
431
433
  const pid = readPid();
432
434
  const name = getAssistantName();
435
+ const localVersion = readPkgVersion(PACKAGE_ROOT);
433
436
  if (!pid) {
434
- console.log(` ${name} is not running (no PID file).`);
437
+ console.log(` ${name} is not running ${DIM}(no PID file, v${localVersion})${RESET}.`);
438
+ surfaceUpdateNudge(localVersion);
435
439
  return;
436
440
  }
437
441
  if (!isProcessAlive(pid)) {
438
- console.log(` ${name} is not running (stale PID ${pid}).`);
442
+ console.log(` ${name} is not running ${DIM}(stale PID ${pid}, v${localVersion})${RESET}.`);
443
+ surfaceUpdateNudge(localVersion);
439
444
  return;
440
445
  }
441
- console.log(` ${name} is running (PID ${pid})`);
446
+ console.log(` ${name} is running ${DIM}(PID ${pid}, v${localVersion})${RESET}`);
442
447
  // Show uptime from PID file mtime
443
448
  try {
444
449
  const { mtimeMs } = statSync(getPidFilePath());
@@ -468,6 +473,36 @@ function cmdStatus() {
468
473
  if (channels.length > 0) {
469
474
  console.log(` Channels: ${channels.join(', ')}`);
470
475
  }
476
+ surfaceUpdateNudge(localVersion);
477
+ }
478
+ /**
479
+ * Print a one-line nudge if a newer version is on npm. Reads the cached
480
+ * result synchronously (no network on the hot path) and fires off an async
481
+ * refresh in the background so the next call has fresh data.
482
+ */
483
+ function surfaceUpdateNudge(localVersion) {
484
+ const DIM = '\x1b[0;90m';
485
+ const BOLD = '\x1b[1m';
486
+ const YELLOW = '\x1b[1;33m';
487
+ const RESET = '\x1b[0m';
488
+ try {
489
+ const cached = (() => {
490
+ // Lazy require to avoid pulling https/network into trivial CLI calls
491
+ // when the cache module isn't needed.
492
+ const { readCachedUpdateCheck } = require('./version-check.js');
493
+ return readCachedUpdateCheck(BASE_DIR, localVersion);
494
+ })();
495
+ if (cached?.updateAvailable && cached.latestVersion) {
496
+ console.log(` ${YELLOW}⬆${RESET} Update available: ${BOLD}v${cached.latestVersion}${RESET} ${DIM}(you're on v${localVersion})${RESET}`);
497
+ console.log(` ${DIM}Run: ${BOLD}clementine update restart${RESET}`);
498
+ }
499
+ // Fire-and-forget background refresh — never blocks status output.
500
+ const { checkForUpdate } = require('./version-check.js');
501
+ void checkForUpdate(BASE_DIR, localVersion).catch(() => { });
502
+ }
503
+ catch {
504
+ // version-check failed to load — degrade silently
505
+ }
471
506
  }
472
507
  function cmdDoctor(opts = {}) {
473
508
  const DIM = '\x1b[0;90m';
@@ -1991,11 +2026,16 @@ program
1991
2026
  });
1992
2027
  program
1993
2028
  .command('update')
1994
- .description('Pull latest code, rebuild, and reinstall (preserves config)')
1995
- .argument('[action]', 'Optional: "restart" to restart daemon after update')
2029
+ .description('Pull latest code, rebuild, and reinstall (preserves config). Pass "history" to show recent updates.')
2030
+ .argument('[action]', 'Optional: "restart" = restart daemon after update; "history" = show update log')
1996
2031
  .option('--restart', 'Restart daemon after update')
1997
2032
  .option('--dry-run', 'Preview what would happen without making changes')
2033
+ .option('-n, --limit <n>', 'For history mode: max entries to show', '10')
1998
2034
  .action((action, options) => {
2035
+ if (action === 'history') {
2036
+ cmdUpdateHistory(parseInt(options.limit ?? '10', 10));
2037
+ return;
2038
+ }
1999
2039
  if (action === 'restart')
2000
2040
  options.restart = true;
2001
2041
  cmdUpdate(options).catch((err) => {
@@ -2583,14 +2623,113 @@ projectsCmd
2583
2623
  }
2584
2624
  });
2585
2625
  // ── Update command ──────────────────────────────────────────────────
2626
+ /** Print the last N entries from update-history.jsonl. */
2627
+ function cmdUpdateHistory(limit) {
2628
+ const BOLD = '\x1b[1m';
2629
+ const DIM = '\x1b[0;90m';
2630
+ const GREEN = '\x1b[0;32m';
2631
+ const RED = '\x1b[0;31m';
2632
+ const RESET = '\x1b[0m';
2633
+ const historyPath = path.join(BASE_DIR, 'update-history.jsonl');
2634
+ if (!existsSync(historyPath)) {
2635
+ console.log();
2636
+ console.log(` ${DIM}No update history yet (${historyPath} doesn't exist).${RESET}`);
2637
+ console.log(` Run ${BOLD}clementine update${RESET} once to start the log.`);
2638
+ console.log();
2639
+ return;
2640
+ }
2641
+ const lines = readFileSync(historyPath, 'utf-8').split('\n').filter(Boolean);
2642
+ const entries = lines
2643
+ .map(l => { try {
2644
+ return JSON.parse(l);
2645
+ }
2646
+ catch {
2647
+ return null;
2648
+ } })
2649
+ .filter((e) => e !== null)
2650
+ .slice(-Math.max(1, limit))
2651
+ .reverse();
2652
+ if (entries.length === 0) {
2653
+ console.log(` ${DIM}History file exists but is empty or unparseable.${RESET}`);
2654
+ return;
2655
+ }
2656
+ console.log();
2657
+ console.log(` ${BOLD}Update history${RESET} ${DIM}(${historyPath})${RESET}`);
2658
+ console.log();
2659
+ for (const e of entries) {
2660
+ const ts = String(e.timestamp ?? '').slice(0, 19).replace('T', ' ');
2661
+ const from = String(e.fromVersion ?? '?');
2662
+ const to = String(e.toVersion ?? '?');
2663
+ const flavor = String(e.flavor ?? 'git');
2664
+ const failed = e.failed === true;
2665
+ const arrow = from === to ? '=' : '→';
2666
+ const verLabel = failed
2667
+ ? `${RED}v${from} ${arrow} v${to} FAILED${RESET}`
2668
+ : (from === to ? `${DIM}v${from}${RESET}` : `v${from} ${arrow} ${BOLD}v${to}${RESET}`);
2669
+ const dur = typeof e.durationMs === 'number' ? ` ${DIM}(${Math.round(e.durationMs / 1000)}s)${RESET}` : '';
2670
+ console.log(` ${DIM}${ts}${RESET} ${verLabel} ${DIM}[${flavor}]${RESET}${dur}`);
2671
+ if (typeof e.commitHash === 'string' && e.commitHash) {
2672
+ console.log(` ${DIM}commit ${e.commitHash}${e.commitDate ? ` (${e.commitDate})` : ''}, ${e.commitsPulled ?? 0} commit${e.commitsPulled === 1 ? '' : 's'} pulled${RESET}`);
2673
+ }
2674
+ if (typeof e.summary === 'string' && e.summary) {
2675
+ const trimmed = e.summary.length > 100 ? e.summary.slice(0, 100) + '…' : e.summary;
2676
+ console.log(` ${DIM}${trimmed}${RESET}`);
2677
+ }
2678
+ if (failed && typeof e.error === 'string') {
2679
+ console.log(` ${RED}error: ${e.error.slice(0, 120)}${RESET}`);
2680
+ }
2681
+ const modSummary = [];
2682
+ if (typeof e.modsReapplied === 'number' && e.modsReapplied > 0)
2683
+ modSummary.push(`${e.modsReapplied} re-applied`);
2684
+ if (typeof e.modsSuperseded === 'number' && e.modsSuperseded > 0)
2685
+ modSummary.push(`${e.modsSuperseded} superseded`);
2686
+ if (typeof e.modsNeedReconciliation === 'number' && e.modsNeedReconciliation > 0)
2687
+ modSummary.push(`${e.modsNeedReconciliation} need attention`);
2688
+ if (typeof e.modsFailed === 'number' && e.modsFailed > 0)
2689
+ modSummary.push(`${e.modsFailed} failed`);
2690
+ if (modSummary.length > 0) {
2691
+ console.log(` ${DIM}source mods: ${modSummary.join(', ')}${RESET}`);
2692
+ }
2693
+ }
2694
+ console.log();
2695
+ console.log(` ${GREEN}Showing ${entries.length}${RESET}${DIM} of ${lines.length} total entries.${RESET}`);
2696
+ console.log();
2697
+ }
2698
+ /** Read the npm version from a package.json (returns 'unknown' on failure). */
2699
+ function readPkgVersion(packageRoot) {
2700
+ try {
2701
+ const pkgPath = path.join(packageRoot, 'package.json');
2702
+ if (!existsSync(pkgPath))
2703
+ return 'unknown';
2704
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
2705
+ return pkg.version ?? 'unknown';
2706
+ }
2707
+ catch {
2708
+ return 'unknown';
2709
+ }
2710
+ }
2711
+ /** Append one line to the update-history log. Append-only, never throws. */
2712
+ function appendUpdateHistory(entry) {
2713
+ try {
2714
+ const historyPath = path.join(BASE_DIR, 'update-history.jsonl');
2715
+ const line = JSON.stringify({ timestamp: new Date().toISOString(), ...entry }) + '\n';
2716
+ require('node:fs').appendFileSync(historyPath, line, { mode: 0o600 });
2717
+ }
2718
+ catch {
2719
+ // Non-fatal — history is observability, not critical state.
2720
+ }
2721
+ }
2586
2722
  async function cmdUpdate(options) {
2587
2723
  const DIM = '\x1b[0;90m';
2724
+ const BOLD = '\x1b[1m';
2588
2725
  const GREEN = '\x1b[0;32m';
2589
2726
  const YELLOW = '\x1b[1;33m';
2590
2727
  const RED = '\x1b[0;31m';
2591
2728
  const RESET = '\x1b[0m';
2729
+ const updateStartedAt = Date.now();
2730
+ const previousVersion = readPkgVersion(PACKAGE_ROOT);
2592
2731
  console.log();
2593
- console.log(` ${DIM}Updating ${getAssistantName()}...${RESET}`);
2732
+ console.log(` ${DIM}Updating ${getAssistantName()} (current: v${previousVersion})...${RESET}`);
2594
2733
  console.log();
2595
2734
  // 1. Detect install flavor. Two valid paths:
2596
2735
  // - git-clone install (PACKAGE_ROOT has .git) → pull + rebuild path below
@@ -2608,12 +2747,36 @@ async function cmdUpdate(options) {
2608
2747
  console.log();
2609
2748
  try {
2610
2749
  execSync('npm install -g clementine-agent@latest', { stdio: 'inherit' });
2750
+ const newVersion = readPkgVersion(PACKAGE_ROOT);
2611
2751
  console.log();
2612
- console.log(` ${GREEN}OK${RESET} Updated via npm`);
2752
+ if (previousVersion !== 'unknown' && newVersion !== 'unknown' && previousVersion !== newVersion) {
2753
+ console.log(` ${GREEN}OK${RESET} Updated v${previousVersion} → ${BOLD}v${newVersion}${RESET}`);
2754
+ }
2755
+ else if (previousVersion === newVersion) {
2756
+ console.log(` ${GREEN}OK${RESET} Already on latest (v${newVersion})`);
2757
+ }
2758
+ else {
2759
+ console.log(` ${GREEN}OK${RESET} Updated via npm`);
2760
+ }
2761
+ appendUpdateHistory({
2762
+ flavor: 'npm-global',
2763
+ fromVersion: previousVersion,
2764
+ toVersion: newVersion,
2765
+ durationMs: Date.now() - updateStartedAt,
2766
+ restartRequested: !!options.restart,
2767
+ });
2613
2768
  }
2614
2769
  catch (err) {
2615
2770
  console.error(` ${RED}FAIL${RESET} npm update failed: ${String(err).slice(0, 200)}`);
2616
2771
  console.error(` ${YELLOW}Hint${RESET} If you see EACCES, see README "Troubleshooting" for npm prefix setup.`);
2772
+ appendUpdateHistory({
2773
+ flavor: 'npm-global',
2774
+ fromVersion: previousVersion,
2775
+ toVersion: previousVersion,
2776
+ durationMs: Date.now() - updateStartedAt,
2777
+ failed: true,
2778
+ error: String(err).slice(0, 300),
2779
+ });
2617
2780
  process.exit(1);
2618
2781
  }
2619
2782
  if (options.restart) {
@@ -3152,6 +3315,26 @@ async function cmdUpdate(options) {
3152
3315
  }).trim().slice(0, 10);
3153
3316
  }
3154
3317
  catch { /* best effort */ }
3318
+ // Capture the new version once the build is verified — package.json on
3319
+ // disk is now authoritative for the version we're about to run.
3320
+ const newVersion = readPkgVersion(PACKAGE_ROOT);
3321
+ // Persist update history before the restart (in case daemon restart fails,
3322
+ // we still have the record of what was attempted).
3323
+ appendUpdateHistory({
3324
+ flavor: 'git',
3325
+ fromVersion: previousVersion,
3326
+ toVersion: newVersion,
3327
+ commitHash,
3328
+ commitDate,
3329
+ commitsPulled,
3330
+ summary: pullSummary.split('\n').slice(0, 5).join('; '),
3331
+ modsReapplied: reconcileResult?.reapplied.length ?? 0,
3332
+ modsSuperseded: reconcileResult?.superseded.length ?? 0,
3333
+ modsNeedReconciliation: reconcileResult?.needsReconciliation.length ?? 0,
3334
+ modsFailed: reconcileResult?.failed.length ?? 0,
3335
+ durationMs: Date.now() - updateStartedAt,
3336
+ restartRequested: !!(options.restart || wasRunning),
3337
+ });
3155
3338
  if (options.restart || wasRunning) {
3156
3339
  const sentinelPath = path.join(BASE_DIR, '.restart-sentinel.json');
3157
3340
  const sentinel = {
@@ -3159,6 +3342,8 @@ async function cmdUpdate(options) {
3159
3342
  restartedAt: new Date().toISOString(),
3160
3343
  reason: 'update',
3161
3344
  updateDetails: {
3345
+ previousVersion,
3346
+ newVersion,
3162
3347
  commitHash,
3163
3348
  commitDate,
3164
3349
  commitsBehind: commitsPulled,
@@ -3249,7 +3434,13 @@ async function cmdUpdate(options) {
3249
3434
  }
3250
3435
  // 14. Show current version
3251
3436
  console.log();
3252
- if (commitHash) {
3437
+ if (previousVersion !== 'unknown' && newVersion !== 'unknown' && previousVersion !== newVersion) {
3438
+ console.log(` ${GREEN}Updated v${previousVersion} → ${BOLD}v${newVersion}${RESET}${commitHash ? ` ${DIM}(${commitHash})${RESET}` : ''}`);
3439
+ }
3440
+ else if (previousVersion === newVersion && previousVersion !== 'unknown') {
3441
+ console.log(` ${GREEN}Already on latest (v${newVersion})${RESET}${commitHash ? ` ${DIM}(${commitHash})${RESET}` : ''}`);
3442
+ }
3443
+ else if (commitHash) {
3253
3444
  console.log(` ${GREEN}Updated to ${commitHash} (${commitDate})${RESET}`);
3254
3445
  }
3255
3446
  else {
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Background "is there a newer version on npm?" check.
3
+ *
4
+ * Polls the public npm registry once per `CACHE_TTL_MS` (default 24h) and
5
+ * caches the result on disk so subsequent calls are instant. Surfaced in
6
+ * `clementine status` and the dashboard header so the user discovers
7
+ * updates without remembering to run `clementine update`.
8
+ *
9
+ * Pure read-only — never installs anything. Network failures are silent
10
+ * (offline → no nudge, not an error).
11
+ */
12
+ export interface VersionCheckResult {
13
+ localVersion: string;
14
+ latestVersion: string | null;
15
+ /** True when latestVersion is strictly greater than localVersion. */
16
+ updateAvailable: boolean;
17
+ /** ISO of last successful registry check. null = never checked or fetch failed. */
18
+ checkedAt: string | null;
19
+ /** True when the cache was used (no network call this invocation). */
20
+ fromCache: boolean;
21
+ }
22
+ /**
23
+ * Check whether a newer version is available. Uses the cache when fresh.
24
+ * Pass `force = true` to bypass the cache (e.g. from a "check now" CLI flag).
25
+ */
26
+ export declare function checkForUpdate(baseDir: string, localVersion: string, opts?: {
27
+ force?: boolean;
28
+ }): Promise<VersionCheckResult>;
29
+ /**
30
+ * Synchronous read of the cached result — used in fast paths like
31
+ * `clementine status` so we never block on a network call. Returns null
32
+ * when there's no cache yet.
33
+ */
34
+ export declare function readCachedUpdateCheck(baseDir: string, localVersion: string): VersionCheckResult | null;
35
+ //# sourceMappingURL=version-check.d.ts.map
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Background "is there a newer version on npm?" check.
3
+ *
4
+ * Polls the public npm registry once per `CACHE_TTL_MS` (default 24h) and
5
+ * caches the result on disk so subsequent calls are instant. Surfaced in
6
+ * `clementine status` and the dashboard header so the user discovers
7
+ * updates without remembering to run `clementine update`.
8
+ *
9
+ * Pure read-only — never installs anything. Network failures are silent
10
+ * (offline → no nudge, not an error).
11
+ */
12
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
13
+ import path from 'node:path';
14
+ import https from 'node:https';
15
+ const PACKAGE_NAME = 'clementine-agent';
16
+ const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
17
+ function cachePath(baseDir) {
18
+ return path.join(baseDir, '.update-check.json');
19
+ }
20
+ function readCache(baseDir) {
21
+ try {
22
+ const p = cachePath(baseDir);
23
+ if (!existsSync(p))
24
+ return null;
25
+ return JSON.parse(readFileSync(p, 'utf-8'));
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ function writeCache(baseDir, entry) {
32
+ try {
33
+ writeFileSync(cachePath(baseDir), JSON.stringify(entry, null, 2), { mode: 0o600 });
34
+ }
35
+ catch {
36
+ // Non-fatal — cache is an optimization, not state.
37
+ }
38
+ }
39
+ /**
40
+ * Fetch the latest published version of the package from npm. Resolves null
41
+ * on any network/parse error so callers can degrade silently.
42
+ */
43
+ function fetchLatestFromNpm(timeoutMs = 5000) {
44
+ return new Promise(resolve => {
45
+ const req = https.get(`https://registry.npmjs.org/${PACKAGE_NAME}/latest`, { timeout: timeoutMs, headers: { Accept: 'application/json' } }, res => {
46
+ if (res.statusCode !== 200) {
47
+ res.resume();
48
+ resolve(null);
49
+ return;
50
+ }
51
+ let body = '';
52
+ res.setEncoding('utf-8');
53
+ res.on('data', chunk => { body += chunk; });
54
+ res.on('end', () => {
55
+ try {
56
+ const parsed = JSON.parse(body);
57
+ resolve(parsed.version ?? null);
58
+ }
59
+ catch {
60
+ resolve(null);
61
+ }
62
+ });
63
+ });
64
+ req.on('error', () => resolve(null));
65
+ req.on('timeout', () => { req.destroy(); resolve(null); });
66
+ });
67
+ }
68
+ /**
69
+ * Compare two semver strings lexicographically by parts. Returns positive
70
+ * when `a` > `b`, negative when `a` < `b`, zero when equal. Tolerates
71
+ * pre-release suffixes by ignoring them (we only care about released bumps).
72
+ */
73
+ function compareSemver(a, b) {
74
+ if (a === b)
75
+ return 0;
76
+ const partsA = a.replace(/[-+].*$/, '').split('.').map(n => parseInt(n, 10) || 0);
77
+ const partsB = b.replace(/[-+].*$/, '').split('.').map(n => parseInt(n, 10) || 0);
78
+ for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
79
+ const av = partsA[i] ?? 0;
80
+ const bv = partsB[i] ?? 0;
81
+ if (av !== bv)
82
+ return av - bv;
83
+ }
84
+ return 0;
85
+ }
86
+ /**
87
+ * Check whether a newer version is available. Uses the cache when fresh.
88
+ * Pass `force = true` to bypass the cache (e.g. from a "check now" CLI flag).
89
+ */
90
+ export async function checkForUpdate(baseDir, localVersion, opts = {}) {
91
+ const cache = readCache(baseDir);
92
+ const now = Date.now();
93
+ const cacheFresh = !!cache && (now - new Date(cache.checkedAt).getTime() < CACHE_TTL_MS);
94
+ if (cache && cacheFresh && !opts.force) {
95
+ return {
96
+ localVersion,
97
+ latestVersion: cache.latestVersion,
98
+ updateAvailable: compareSemver(cache.latestVersion, localVersion) > 0,
99
+ checkedAt: cache.checkedAt,
100
+ fromCache: true,
101
+ };
102
+ }
103
+ const latest = await fetchLatestFromNpm();
104
+ if (!latest) {
105
+ // Couldn't reach the registry — fall back to stale cache if we have one.
106
+ if (cache) {
107
+ return {
108
+ localVersion,
109
+ latestVersion: cache.latestVersion,
110
+ updateAvailable: compareSemver(cache.latestVersion, localVersion) > 0,
111
+ checkedAt: cache.checkedAt,
112
+ fromCache: true,
113
+ };
114
+ }
115
+ return { localVersion, latestVersion: null, updateAvailable: false, checkedAt: null, fromCache: false };
116
+ }
117
+ writeCache(baseDir, {
118
+ checkedAt: new Date().toISOString(),
119
+ latestVersion: latest,
120
+ observedLocalVersion: localVersion,
121
+ });
122
+ return {
123
+ localVersion,
124
+ latestVersion: latest,
125
+ updateAvailable: compareSemver(latest, localVersion) > 0,
126
+ checkedAt: new Date().toISOString(),
127
+ fromCache: false,
128
+ };
129
+ }
130
+ /**
131
+ * Synchronous read of the cached result — used in fast paths like
132
+ * `clementine status` so we never block on a network call. Returns null
133
+ * when there's no cache yet.
134
+ */
135
+ export function readCachedUpdateCheck(baseDir, localVersion) {
136
+ const cache = readCache(baseDir);
137
+ if (!cache)
138
+ return null;
139
+ return {
140
+ localVersion,
141
+ latestVersion: cache.latestVersion,
142
+ updateAvailable: compareSemver(cache.latestVersion, localVersion) > 0,
143
+ checkedAt: cache.checkedAt,
144
+ fromCache: true,
145
+ };
146
+ }
147
+ //# sourceMappingURL=version-check.js.map
package/dist/index.js CHANGED
@@ -888,8 +888,14 @@ async function asyncMain() {
888
888
  else if (sentinel.reason === 'update' && sentinel.updateDetails) {
889
889
  const d = sentinel.updateDetails;
890
890
  const parts = [];
891
- // Version info
892
- if (d.commitHash) {
891
+ // Version info — prefer semver transition over commit hash for human readability.
892
+ if (d.previousVersion && d.newVersion && d.previousVersion !== d.newVersion) {
893
+ parts.push(`Updated v${d.previousVersion} → v${d.newVersion}`);
894
+ }
895
+ else if (d.newVersion) {
896
+ parts.push(`Now on v${d.newVersion}`);
897
+ }
898
+ else if (d.commitHash) {
893
899
  parts.push(`Updated to ${d.commitHash}${d.commitDate ? ` (${d.commitDate})` : ''}`);
894
900
  }
895
901
  else {
package/dist/types.d.ts CHANGED
@@ -573,6 +573,10 @@ export interface RestartSentinel {
573
573
  sessionKey?: string;
574
574
  changedFiles?: string[];
575
575
  updateDetails?: {
576
+ /** Semver before the update — read from package.json prior to git pull. */
577
+ previousVersion?: string;
578
+ /** Semver after the update — read from package.json after build. */
579
+ newVersion?: string;
576
580
  commitHash?: string;
577
581
  commitDate?: string;
578
582
  commitsBehind?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clementine-agent",
3
- "version": "1.1.21",
3
+ "version": "1.1.22",
4
4
  "description": "Clementine — Personal AI Assistant (TypeScript)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",