feat: /release-notes and what's news
Browse files- package.json +1 -1
- src/components/LogoV2/CondensedLogo.tsx +2 -2
- src/components/LogoV2/LogoV2.tsx +2 -2
- src/entrypoints/cli.tsx +1 -1
- src/utils/releaseNotes.ts +39 -6
package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"name": "claude-code-source-snapshot",
|
| 3 |
-
"version": "2.1.
|
| 4 |
"private": true,
|
| 5 |
"workspaces": [
|
| 6 |
"packages/*"
|
|
|
|
| 1 |
{
|
| 2 |
"name": "claude-code-source-snapshot",
|
| 3 |
+
"version": "2.1.0",
|
| 4 |
"private": true,
|
| 5 |
"workspaces": [
|
| 6 |
"packages/*"
|
src/components/LogoV2/CondensedLogo.tsx
CHANGED
|
@@ -58,7 +58,7 @@ export function CondensedLogo(): ReactNode {
|
|
| 58 |
const textWidth = Math.max(columns - 15, 20)
|
| 59 |
|
| 60 |
// Truncate version to fit within available width, accounting for "Claude Code v" prefix
|
| 61 |
-
const versionPrefix = '
|
| 62 |
const truncatedVersion = truncate(
|
| 63 |
version,
|
| 64 |
Math.max(textWidth - versionPrefix.length, 6),
|
|
@@ -92,7 +92,7 @@ export function CondensedLogo(): ReactNode {
|
|
| 92 |
{/* Info */}
|
| 93 |
<Box flexDirection="column">
|
| 94 |
<Text>
|
| 95 |
-
<Text bold>
|
| 96 |
<Text dimColor>v{truncatedVersion}</Text>
|
| 97 |
</Text>
|
| 98 |
{shouldSplit ? (
|
|
|
|
| 58 |
const textWidth = Math.max(columns - 15, 20)
|
| 59 |
|
| 60 |
// Truncate version to fit within available width, accounting for "Claude Code v" prefix
|
| 61 |
+
const versionPrefix = 'Versper Claw v'
|
| 62 |
const truncatedVersion = truncate(
|
| 63 |
version,
|
| 64 |
Math.max(textWidth - versionPrefix.length, 6),
|
|
|
|
| 92 |
{/* Info */}
|
| 93 |
<Box flexDirection="column">
|
| 94 |
<Text>
|
| 95 |
+
<Text bold>Versper Claw</Text>{' '}
|
| 96 |
<Text dimColor>v{truncatedVersion}</Text>
|
| 97 |
</Text>
|
| 98 |
{shouldSplit ? (
|
src/components/LogoV2/LogoV2.tsx
CHANGED
|
@@ -250,8 +250,8 @@ export function LogoV2(): React.ReactNode {
|
|
| 250 |
const layoutMode = getLayoutMode(columns)
|
| 251 |
|
| 252 |
const userTheme = resolveThemeSetting(getGlobalConfig().theme)
|
| 253 |
-
const borderTitle = ` ${color('claude', userTheme)('
|
| 254 |
-
const compactBorderTitle = color('claude', userTheme)('
|
| 255 |
|
| 256 |
// Early return for compact mode
|
| 257 |
if (layoutMode === 'compact') {
|
|
|
|
| 250 |
const layoutMode = getLayoutMode(columns)
|
| 251 |
|
| 252 |
const userTheme = resolveThemeSetting(getGlobalConfig().theme)
|
| 253 |
+
const borderTitle = ` ${color('claude', userTheme)('Versper Claw')} ${color('inactive', userTheme)(`v${version}`)} `
|
| 254 |
+
const compactBorderTitle = color('claude', userTheme)(' Versper Claw ')
|
| 255 |
|
| 256 |
// Early return for compact mode
|
| 257 |
if (layoutMode === 'compact') {
|
src/entrypoints/cli.tsx
CHANGED
|
@@ -37,7 +37,7 @@ async function main(): Promise<void> {
|
|
| 37 |
if (args.length === 1 && (args[0] === '--version' || args[0] === '-v' || args[0] === '-V')) {
|
| 38 |
// MACRO.VERSION is inlined at build time
|
| 39 |
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
| 40 |
-
console.log(`${MACRO.VERSION} (
|
| 41 |
return;
|
| 42 |
}
|
| 43 |
|
|
|
|
| 37 |
if (args.length === 1 && (args[0] === '--version' || args[0] === '-v' || args[0] === '-V')) {
|
| 38 |
// MACRO.VERSION is inlined at build time
|
| 39 |
// biome-ignore lint/suspicious/noConsole:: intentional console output
|
| 40 |
+
console.log(`${MACRO.VERSION} (Versper Claw)`);
|
| 41 |
return;
|
| 42 |
}
|
| 43 |
|
src/utils/releaseNotes.ts
CHANGED
|
@@ -26,9 +26,9 @@ const MAX_RELEASE_NOTES_SHOWN = 5
|
|
| 26 |
* 3. Next time the user starts Claude, the cached changelog is available immediately
|
| 27 |
*/
|
| 28 |
export const CHANGELOG_URL =
|
| 29 |
-
'https://github.com/
|
| 30 |
const RAW_CHANGELOG_URL =
|
| 31 |
-
'https://
|
| 32 |
|
| 33 |
/**
|
| 34 |
* Get the path for the cached changelog file.
|
|
@@ -90,9 +90,29 @@ export async function fetchAndStoreChangelog(): Promise<void> {
|
|
| 90 |
return
|
| 91 |
}
|
| 92 |
|
| 93 |
-
const response = await axios.get(RAW_CHANGELOG_URL
|
|
|
|
|
|
|
| 94 |
if (response.status === 200) {
|
| 95 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
// Skip write if content unchanged — writing Date.now() defeats the
|
| 98 |
// dirty-check in saveGlobalConfig since the timestamp always differs.
|
|
@@ -244,12 +264,13 @@ export function getRecentReleaseNoteGroups(
|
|
| 244 |
|
| 245 |
if (
|
| 246 |
basePreviousVersion &&
|
| 247 |
-
!gt(baseCurrentVersion.version, basePreviousVersion.version)
|
|
|
|
| 248 |
) {
|
| 249 |
return []
|
| 250 |
}
|
| 251 |
|
| 252 |
-
|
| 253 |
.filter(
|
| 254 |
([version]) =>
|
| 255 |
!basePreviousVersion || gt(version, basePreviousVersion.version),
|
|
@@ -258,6 +279,18 @@ export function getRecentReleaseNoteGroups(
|
|
| 258 |
.slice(0, maxVersions)
|
| 259 |
.map(([version, notes]) => [version, notes.filter(Boolean)] as [string, string[]])
|
| 260 |
.filter(([, notes]) => notes.length > 0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
} catch (error) {
|
| 262 |
logError(toError(error))
|
| 263 |
return []
|
|
|
|
| 26 |
* 3. Next time the user starts Claude, the cached changelog is available immediately
|
| 27 |
*/
|
| 28 |
export const CHANGELOG_URL =
|
| 29 |
+
'https://github.com/versperai/VersperClaw/releases'
|
| 30 |
const RAW_CHANGELOG_URL =
|
| 31 |
+
'https://api.github.com/repos/versperai/VersperClaw/releases'
|
| 32 |
|
| 33 |
/**
|
| 34 |
* Get the path for the cached changelog file.
|
|
|
|
| 90 |
return
|
| 91 |
}
|
| 92 |
|
| 93 |
+
const response = await axios.get(RAW_CHANGELOG_URL, {
|
| 94 |
+
headers: { 'User-Agent': 'VersperClaw' },
|
| 95 |
+
})
|
| 96 |
if (response.status === 200) {
|
| 97 |
+
const releases = response.data as Array<{
|
| 98 |
+
tag_name: string
|
| 99 |
+
name: string
|
| 100 |
+
}>
|
| 101 |
+
|
| 102 |
+
// Transform releases into markdown format that parseChangelog expects:
|
| 103 |
+
// ## version
|
| 104 |
+
// - description
|
| 105 |
+
const changelogContent = releases
|
| 106 |
+
.map(release => {
|
| 107 |
+
const version = release.tag_name.replace(/^v/, '')
|
| 108 |
+
const lines = [`## ${version}`]
|
| 109 |
+
if (release.name) {
|
| 110 |
+
lines.push(`- ${release.name}`)
|
| 111 |
+
}
|
| 112 |
+
return lines.length > 1 ? lines.join('\n') : ''
|
| 113 |
+
})
|
| 114 |
+
.filter(Boolean)
|
| 115 |
+
.join('\n\n')
|
| 116 |
|
| 117 |
// Skip write if content unchanged — writing Date.now() defeats the
|
| 118 |
// dirty-check in saveGlobalConfig since the timestamp always differs.
|
|
|
|
| 264 |
|
| 265 |
if (
|
| 266 |
basePreviousVersion &&
|
| 267 |
+
!gt(baseCurrentVersion.version, basePreviousVersion.version) &&
|
| 268 |
+
Object.keys(releaseNotes).length === 0
|
| 269 |
) {
|
| 270 |
return []
|
| 271 |
}
|
| 272 |
|
| 273 |
+
const filtered = Object.entries(releaseNotes)
|
| 274 |
.filter(
|
| 275 |
([version]) =>
|
| 276 |
!basePreviousVersion || gt(version, basePreviousVersion.version),
|
|
|
|
| 279 |
.slice(0, maxVersions)
|
| 280 |
.map(([version, notes]) => [version, notes.filter(Boolean)] as [string, string[]])
|
| 281 |
.filter(([, notes]) => notes.length > 0)
|
| 282 |
+
|
| 283 |
+
// Fallback: if version-filter returns empty but releases exist
|
| 284 |
+
// (e.g., fork where app version exceeds all releases), show latest releases
|
| 285 |
+
if (filtered.length === 0 && Object.keys(releaseNotes).length > 0) {
|
| 286 |
+
return Object.entries(releaseNotes)
|
| 287 |
+
.sort(([versionA], [versionB]) => (gt(versionA, versionB) ? -1 : 1))
|
| 288 |
+
.slice(0, maxVersions)
|
| 289 |
+
.map(([version, notes]) => [version, notes.filter(Boolean)] as [string, string[]])
|
| 290 |
+
.filter(([, notes]) => notes.length > 0)
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
return filtered
|
| 294 |
} catch (error) {
|
| 295 |
logError(toError(error))
|
| 296 |
return []
|