alvin-bot 4.16.0 ā 4.16.1
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/CHANGELOG.md +19 -0
- package/dist/handlers/commands.js +12 -0
- package/dist/services/release-highlights.js +79 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to Alvin Bot are documented here.
|
|
4
4
|
|
|
5
|
+
## [4.16.1] ā 2026-04-20
|
|
6
|
+
|
|
7
|
+
### š Feature: /update shows release highlights
|
|
8
|
+
|
|
9
|
+
After a successful `/update`, the bot now sends a second short message with a bullet-point summary of what actually changed in the newly installed version. Pulled from the CHANGELOG entry matching the version string in the update result.
|
|
10
|
+
|
|
11
|
+
**Implementation:**
|
|
12
|
+
- New module `src/services/release-highlights.ts` parses the CHANGELOG block for a given version and returns at most 5 bullet points, ā¤500 chars total.
|
|
13
|
+
- Strategy: prefer `### ` subsection headlines (feature/fix titles); fall back to first non-empty paragraph lines.
|
|
14
|
+
- Telegram-friendly output: plain bullets (`⢠...`), no tables, no code blocks, truncates gracefully with an ellipsis line if too long.
|
|
15
|
+
|
|
16
|
+
**Result format in chat:**
|
|
17
|
+
```
|
|
18
|
+
ā
Installed v4.16.1 (was v4.16.0). Restarting...
|
|
19
|
+
š What's new in v4.16.1
|
|
20
|
+
|
|
21
|
+
⢠Feature: /update shows release highlights
|
|
22
|
+
```
|
|
23
|
+
|
|
5
24
|
## [4.16.0] ā 2026-04-20
|
|
6
25
|
|
|
7
26
|
### š Feature: bot-owned CDP Chromium ā no more hub dependency
|
|
@@ -27,6 +27,7 @@ import { BOT_VERSION } from "../version.js";
|
|
|
27
27
|
import { getWebPort } from "../web/server.js";
|
|
28
28
|
import { getUsageSummary, getAllRateLimits, formatTokens } from "../services/usage-tracker.js";
|
|
29
29
|
import { runUpdate, getAutoUpdate, setAutoUpdate, startAutoUpdateLoop } from "../services/updater.js";
|
|
30
|
+
import { getReleaseHighlights } from "../services/release-highlights.js";
|
|
30
31
|
import { getHealthStatus, isFailedOver } from "../services/heartbeat.js";
|
|
31
32
|
import { t, LOCALE_NAMES, LOCALE_FLAGS } from "../i18n.js";
|
|
32
33
|
// Kick off auto-update loop on module load if the persistent flag is set.
|
|
@@ -1875,6 +1876,17 @@ export function registerCommands(bot) {
|
|
|
1875
1876
|
const result = await runUpdate();
|
|
1876
1877
|
if (result.ok) {
|
|
1877
1878
|
await ctx.reply(`ā
${result.message}`);
|
|
1879
|
+
// Extract the installed version from the message (e.g. "Installed v4.16.1 ...")
|
|
1880
|
+
// so we can look up its CHANGELOG block. Falls silently if no match.
|
|
1881
|
+
const versionMatch = result.message.match(/v(\d+\.\d+\.\d+)/);
|
|
1882
|
+
if (versionMatch) {
|
|
1883
|
+
const highlights = getReleaseHighlights(versionMatch[1]);
|
|
1884
|
+
if (highlights) {
|
|
1885
|
+
await ctx.reply(`š *What's new in v${versionMatch[1]}*\n\n${highlights}`, {
|
|
1886
|
+
parse_mode: "Markdown",
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1878
1890
|
if (result.requiresRestart) {
|
|
1879
1891
|
await ctx.reply(t("bot.update.restarting", lang));
|
|
1880
1892
|
setTimeout(() => process.exit(0), 500);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Release Highlights ā extract a short human-readable summary for a given
|
|
3
|
+
* version from CHANGELOG.md.
|
|
4
|
+
*
|
|
5
|
+
* Used by the /update command to tell users what actually changed after a
|
|
6
|
+
* successful upgrade. Deliberately short ā Telegram-friendly (<500 chars),
|
|
7
|
+
* headline + up to 5 bullets, no markdown tables, no code blocks.
|
|
8
|
+
*/
|
|
9
|
+
import fs from "fs";
|
|
10
|
+
import path from "path";
|
|
11
|
+
import { BOT_ROOT } from "../paths.js";
|
|
12
|
+
const CHANGELOG_PATH = path.resolve(BOT_ROOT, "CHANGELOG.md");
|
|
13
|
+
const MAX_BULLETS = 5;
|
|
14
|
+
const MAX_CHARS = 500;
|
|
15
|
+
/**
|
|
16
|
+
* Find the block for `## [<version>]` in CHANGELOG.md and return a
|
|
17
|
+
* compact summary (headline + a few bullet points). Returns null if the
|
|
18
|
+
* version block is not found or CHANGELOG.md is missing.
|
|
19
|
+
*/
|
|
20
|
+
export function getReleaseHighlights(version) {
|
|
21
|
+
let content;
|
|
22
|
+
try {
|
|
23
|
+
content = fs.readFileSync(CHANGELOG_PATH, "utf8");
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const versionEscaped = version.replace(/\./g, "\\.");
|
|
29
|
+
// Match from "## [X.Y.Z]" up to the next "## [" (or end of file)
|
|
30
|
+
const blockRe = new RegExp(`^##\\s*\\[${versionEscaped}\\][^\\n]*\\n([\\s\\S]*?)(?=^##\\s*\\[|\\Z)`, "m");
|
|
31
|
+
const match = content.match(blockRe);
|
|
32
|
+
if (!match)
|
|
33
|
+
return null;
|
|
34
|
+
return compactHighlights(match[1]);
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Extract up to MAX_BULLETS short lines from a CHANGELOG block.
|
|
38
|
+
* Strategy:
|
|
39
|
+
* 1. Prefer "### " subsection headlines (feature/fix titles)
|
|
40
|
+
* 2. Otherwise the first few non-empty lines of the first paragraph
|
|
41
|
+
* Truncate to MAX_CHARS total so it fits comfortably in a Telegram message.
|
|
42
|
+
*/
|
|
43
|
+
function compactHighlights(block) {
|
|
44
|
+
const lines = block.split("\n");
|
|
45
|
+
const headlines = [];
|
|
46
|
+
for (const line of lines) {
|
|
47
|
+
const m = line.match(/^###\s+(.+?)\s*$/);
|
|
48
|
+
if (!m)
|
|
49
|
+
continue;
|
|
50
|
+
// Strip leading emoji/punctuation like "š Feature: ..."
|
|
51
|
+
const title = m[1].replace(/^[^a-zA-Z0-9]+/, "").replace(/\s+/g, " ").trim();
|
|
52
|
+
if (title)
|
|
53
|
+
headlines.push(title);
|
|
54
|
+
}
|
|
55
|
+
let bullets;
|
|
56
|
+
if (headlines.length > 0) {
|
|
57
|
+
bullets = headlines.slice(0, MAX_BULLETS);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
// Fallback: grab the first non-empty lines (skip bold marker paragraphs,
|
|
61
|
+
// keep narrative). Limit to MAX_BULLETS lines.
|
|
62
|
+
const flat = lines
|
|
63
|
+
.map((l) => l.trim())
|
|
64
|
+
.filter((l) => l.length > 0 && !l.startsWith("```") && !l.startsWith("|"));
|
|
65
|
+
bullets = flat.slice(0, MAX_BULLETS);
|
|
66
|
+
}
|
|
67
|
+
const rendered = bullets.map((b) => `⢠${b}`).join("\n");
|
|
68
|
+
if (rendered.length <= MAX_CHARS)
|
|
69
|
+
return rendered;
|
|
70
|
+
// Trim to fit ā add a soft ellipsis on a whole line
|
|
71
|
+
let out = "";
|
|
72
|
+
for (const b of bullets) {
|
|
73
|
+
const next = out ? out + "\n⢠" + b : "⢠" + b;
|
|
74
|
+
if (next.length > MAX_CHARS - 4)
|
|
75
|
+
break;
|
|
76
|
+
out = next;
|
|
77
|
+
}
|
|
78
|
+
return out + "\nā¦";
|
|
79
|
+
}
|