AnExiledDev/cc-changelog-plugin
cc-changelog
一个 Claude Code 插件,会打开 /whatsnew 窗格以浏览版本发布,并注册用于变更日志、文档、参考和 mod API 查询的工具。
关于这个 mod
cc-changelog 是一个 Claude Code 插件,添加一个 /whatsnew 窗格,提供版本发布浏览、搜索和条目阅读。它还注册用于查询变更日志版本发布、文档捕获、提示/工具描述、mod API 符号以及相关站点路由的工具。仓库包含安装和插件市场说明。
安装
请先查看作者 README 确认 marketplace 和插件名称;命令可能随仓库结构改变。
claude plugin marketplace add AnExiledDev/cc-changelog-plugin claude plugin install cc-changelog@cc-changelog
原文 / README
AI-written from operator direction. The intent is the operator's; the wording and scope are an agent's.
session: 6a501c72-fa3b-4836-9a4d-bbd91a27ec9c | 2026-09-09 | source: op:2026-09-09-2117-e2bd
cc-changelog: a Claude Code plugin that draws a pane
/whatsnew opens a pane in the terminal listing every release in
https://changelogs.core-directive.com/feed.json, with a tab strip, a search
field with three filters, a reader that draws one entry in full, and a line at
the top saying how far behind the running build is. It also registers fifteen
tools the model can call, and polls for releases in the background so a
session says something when one lands.
It is here because of what it is built on rather than what it does: Claude Code v2.1.269 shipped a plugin runtime, mods, that no page of code.claude.com/docs mentioned until Anthropic published their mods docs with v2.1.287. Nothing in the 192-page docs corpus captured on 2026-09-11 named modules, surface, $.ui.render or any of it.
The build documents itself as well. Each time it loads a mod from a folder you own, it writes .claude-plugin/types/claude-code/index.d.ts beside it: 701 KB of declarations, every event's input and result, every method on $, every element's props. types/claude-code.d.ts here is the copy v2.1.323 wrote. It is the authority; the notes below are the map to it.
The site it reads is changelogs.core-directive.com.
Provenance
Operator, 2026-09-09 (op:2026-09-09-2117-e2bd):
Can you build it, enable it and make sure it works to the best of your capability?
Run it
git clone https://github.com/AnExiledDev/cc-changelog-plugin.git
claude --plugin-dir ./cc-changelog-plugin
--plugin-dir loads it for that session only and watches the folder, which is
what you want while reading the code. To keep it, install it through its own
marketplace, which this repository carries in .claude-plugin/marketplace.json:
claude plugin marketplace add AnExiledDev/cc-changelog-plugin
claude plugin install cc-changelog@cc-changelog
Upgrading later is claude plugin marketplace update cc-changelog followed by claude plugin update cc-changelog@cc-changelog.
It needs Claude Code v2.1.287 or later. From that build mods load by default. Up to v2.1.286 they sat behind a rollout flag and CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 turned them on; v2.1.287 ignores the variable. If the module doesn't load, Anthropic's Troubleshoot a mod covers why.
Then type /whatsnew. Two things make the loop fast:
claude plugin validate <path>prints the static scan of what the module hooks and calls, and refuses the module before it ever loads.claude --plugin-dir <folder>loads a plugin from disk for one session and watches it: saving a file re-runsregisterin a fresh environment.
claude --debug hooks --debug-file <path> is where the engine says what it
refused, including a tree that did not validate.
What it does
-
/whatsnewopens the pane with all 25 feed items rather than a hand-cut dozen. APanescrolls itself, so the list does not need paging: the engine draws↑↓ pgup pgdn scroll · tab moves · esc back to the promptand owns the offset. Nothing in the module tracks scroll. -
Four tabs.
Releases (25),Search, one that appears once a release is opened, labelled with its version, andEntryonce an entry has been opened. Tab state is one value in$.storeunderview, so the pane reopens where the reader left it. The detail tab keys on the feed item'sid, not its version: blog posts in the same feed carry no version, and matching on one gave every versionless item the same phantom tab. -
Search is a field and three filters. An
Inputsubmits on Enter and never on keystroke, so one search is one request to/search.json; threeSelects narrow it by tier, by area and by release, and a pick re-runs the terms already entered. The tiers are the site's four, the releases are the feed's, and the areas are read off the newest release'sentries.jsonfacets rather than spelled in the module, so a renamed area renames itself. The model's ownsearchcalls land in the same tab, terms and filters included, so a reader can narrow what the model just found from where it left off. Every hit is a button that opens the entry here and aLinkthat opens it on the site. -
The entry reader draws
/v/{version}/e/{anchor}.jsonin full: the heading, its tier and area, then its markdown split into headings, paragraphs andCode. The site serves a fenced usage line as one backtick span, so a paragraph that is one whole span is drawn as code too, through the engine's own highlighter. Entries open from a search hit or from any row of a release's "What changed" list. Under the text sit anAsk me about itbutton and the link to the page. -
Ask me about itis$.ui.ask. It raises the engine's own AskUserQuestion dialog with three choices, and the pick is written into the prompt box with$.prompt.fill, cursor at the end, for the reader to send or edit. Nothing is submitted from the pane: a press must never spend a turn on its own. A dismissed dialog rejects, which the module reads as "never mind". -
The version line is
$.process.run. Nothing on$says which Claude Code is running, so the module runsclaude --versiononce per module environment with a five-second timeout and reads the version out of what it prints. Every failure, from noclaudeon PATH to a sandbox that refuses the spawn, is answered with no line rather than an error, because the hint is a nicety and the list is the feature. -
A poll.
session.startreads the feed once, remembers the versions it saw, and$.clock.every(30 min)re-reads it. A version the session has not seen raises$.ui.toastand sets$.ui.status, both without starting a turn. The first read only seeds: a session that starts three releases behind is not three toasts. -
Twelve tools, all registered from
session.start, so they are listed by turn one. Six are the changelog:releasessays which versions exist and what shipped since a date;upgradeanswers everything published between two versions at once;entrieslists one release's entries and counts its sections, tiers and areas;entryreads one of them in full;changelogreads a release as a document, a window at a time; andsearchcrosses every release at once. They were two until 2026-09-10, and the three that were missing are the three questions the two could not answer: what versions exist, what one release holds, and what one entry actually says past its one-line summary. -
upgradeis the one that answers "what changed for me", added 2026-09-20. Before it, a caller who had upgraded from2.1.270to2.1.278had to page/releases.jsonby a date it was expected to already know, fetch every release's entries in turn, and flatten them itself: four to ten round trips for the commonest question anybody asks a changelog, and an agent driving it usually gave up partway. One call now answers the flattened span, the releases it crossed, andfacetscounting the entries by tier and area. The span is half-open,(from, to]: the reader has been runningfromfor a week and does not need it back.fromis optional here and required on the site's own route, because nothing on$says which Claude Code is running but the binary does, and this module already asks it. -
Four more, one per corpus, added the same day, because a changelog says what changed and nothing here could say what a thing is.
referenceis the mined inventory of every environment variable, flag, settings key, slash command, tool, hook event and model id in the shipped build, with which builds each has been seen in and whose sentence the description is;docsis Anthropic's own documentation as this site captured it;blogis the site owner's own writing; andpromptsis what a build actually told the model its tools do, byte for byte as it was sent. Each takes the index, the search and one document through a single tool, picked by which arguments were given, because the argument a model has is a phrase and the ones the routes need are a corpus key and a path. -
docschanges, what Anthropic quietly edited, added 2026-09-20. The site re-reads that documentation on a timer and diffs each read against the last, so it holds a history nothing upstream publishes: which pages moved, on what day, and what the words used to be. Five routes behind one tool, picked by the arguments given — the recent changes across every corpus, one day of them, the per-day totals so a caller can see which days were busy before reading any, one edit with its diff as lines, and one capture with everything that read moved. It is split fromdocsfor the reasonupgradeis split fromreleases: one answers what is true now and the other answers what moved, and a model handed only the first will summarise a page and call it news. -
A twelfth,
modsapi, over the surface this plugin is written against. The site mines the plugin runtime's declarations out of every release and publishes them at/reference/mods/api: 19 nouns, 69 verbs, 71 events and 505 declarations at v2.1.278. Its whole-document route is half a megabyte, which no tool answer may be, so the tool reads two cut routes instead: an index of every symbol, searched by part of a name and narrowed by page, and one symbol in full behind thepageandanchorthe index hands out. An event answers with the declarations of its input and its result inlined, because "what doessession.measurearrive with" is the question the index rows could not answer, and every answer names the othertypesit mentions with their anchors, soSessionUsageis one call fromSessionRateLimit. Added 2026-09-19, when an agent asked the tools whether a plugin could read rate-limit windows and per-response token usage and none of the nine could say:referencenames thehookfamily anddocsdoes not describe this API at all. -
modsapialso answers what moved. Added 2026-09-27. Withchanges: trueit reads the site's release-by-release diff of the same surface, published at/reference/mods/api/changes: every release newest first with how many symbols were added, removed, changed or only re-documented, and with aversionthat release's items, each carrying its before and after shape. A module written against last month's build can check what broke without diffing two declaration files by hand. -
mods, the other half ofmodsapi: how to actually write one. Added 2026-09-20.modsapianswers what a symbol is called and what it takes; the site's Mods guide is ten hand-written pages on what any of it means, and it is the only written explanation of this runtime anywhere. A model handed the declarations alone can name every noun on$and still not know that a module is loaded once per session, that a hook wraps the tiers below it, or which of the confirmed gotchas cost a run to find. Two routes behind one tool: nopagelists the ten with every page's headings, so a part can be named without fetching the page it is on, and apageanswers that page's markdown one window at a time. The prose is markdown in the site's repository and is served as written, so what a reader sees on the guide and what this tool returns are the same file. -
Two more,
storiesandwatch, over the two things on that site a machine could not have worked out. Added 2026-09-20. Everything above is a cut of something the pipeline produced; these two are a person's own reading of it.storiesis a feature followed across every release it was touched in, which is somebody having decided that five entries on five pages are one thing, with the timeline in the calendar's order and apresentflag on any step whose release has since been re-composed.watchis the ledger of what has happened to an entry after its release published it: a flag moving on the server, Anthropic's notes landing days later, their documentation catching up later still, an outright correction by hand. It narrows bykindand bysince, and it is the only way to find out that a release page read in August is no longer the whole story. It writes nothing and it replaces nothing: every event links to the entry its own release published, which still says exactly what that release said, so ahand-editevent means a correction was added and dated beside the entry, never that the entry now reads differently. -
Everything pages, and it pages the same way everywhere. Each answer carries
offset,count,totalandnext_offset; handnext_offsetback asoffsetto continue, and a null one means there is nothing after this. Thechangelogtool pages a document by characters rather than by rows and pulls its cut back to the last line break, so a caller reading a release in windows is never handed half a sentence. The cut is the site's:changelogis a pass-through of/v/{version}/document.json, which takessection,offsetand a characterlimitand answers one window plus the document's outline, so nothing here ever holds a release it is not going to use. -
search,upgradeandentriesnarrow ontier(use,notice,soon,internal) andarea;searchandentriesalso onversion, andentriesonsection. -
The four corpus tools window server-side too, through the same routes and the same
section/offset/sectionscontract: a documentation page, a blog post and a stock tool description are all cut by the site before they cross the wire. The hooks reference alone is past a hundred kilobytes, and the point of the whole round is that a caller after one paragraph of it pays for one paragraph of it. -
in_current_buildon a reference name means the newest mined build, not the newest release. The inventory trails the changelog by several versions, and every answer carriesnewest_minedbeside the flag so the claim can be read against what measured it. -
versionacceptslatest, resolved in the plugin rather than by the site: the routes take a literal version so that a cached URL is a cached release rather than a moving target. -
A refused argument reaches the model in the site's own words. The site answers 422 with the parameter's name and what it would have accepted, and the plugin passes that through, so a model that asked for
tier=urgentis told the four real tiers instead of "answered 422".
Fetch-friendliness
The site is one small server, and a plugin installed widely polling it every thirty minutes from every session is the thing that would hurt it. So:
- Every response is cached in
$.storeunder anhttp:prefix, keyed by URL, with theCache-Control: max-agethe server sent (15 minutes if it sent none). Inside the TTL nothing leaves the machine. ETagandIf-None-Match. Past the TTL the request carries the stored etag, and a304refreshes the TTL without transferring the body.- A failed fetch serves the cached copy rather than an error, and never clears it.
- Nothing fetches a whole release. The biggest thing that crosses the wire
is one 20,000 character window of a document; the detail tab reads
/v/{version}/entries.json?limit=8, about 9 KB, which carries the release's whole summary and the entries it leads with. Until 2026-09-10 both of those were/v/{version}.md, 207,061 characters for v2.1.267, fetched to draw eight headings and a paragraph. - The pane never waits on the network. A
ui.renderhook runs inside a dispatch with a budget, so a fetch inside one blocks the frame and a slow origin costs the whole tree. The draw reads$.storeand only$.store; a document that is missing or stale is fetched beside the dispatch and$.ui.invalidateasks for the frame again once it lands. One fetch per URL per module environment, so an origin that is down cannot turn every redraw into another request. - The cache is pruned to the 40 most recent documents.
$.storeis one 4 MiB JSON file per plugin. It was eight while a release's markdown ran to about 200 KB; the answers are single-figure kilobytes now, so the cache holds a reader's whole afternoon instead. - One identifying user agent,
cc-changelog-plugin/0.2 (+https://changelogs.core-directive.com), so a rate limit can name this plugin instead of guessing.
The Site base URL setting (baseUrl, in /plugin) points all of it
somewhere else, which is how the search tool was exercised against a local
Laravel before /search.json was live. CC_CHANGELOG_BASE_URL still does the
same thing and is the one to use for a single shell invocation; the setting
wins when both are present.
The origin holds callers to 120 requests a minute each, and the API as a whole
to 600 a minute, answering 429 with {"error", "retry_after"} past either.
Nothing this plugin does in normal use comes near that: the $.store cache
means a session with eight terminals open fetches each document once per TTL.
A caller that trips it is walking the corpus, and walking the corpus is fine at
two a second.
The runtime, as measured
Everything here was read out of data/claude-code/pretty/pretty-v2.1.267.js and
then confirmed by running it, except where it says otherwise.
- The gate.
pretty-v2.1.267.js:223878: GrowthBook flagtengu_plugin_hooks_modules, defaultfalse, overridden by the environment variableCLAUDE_CODE_ENABLE_FUNCTION_HOOKS. Also off when hooks are disabled or the process is restricted. At v2.1.287 the default istrueand the variable is no longer read. - The manifest.
hooks/hooks.jsontakesmodules(exactly one path, a module exportingregister(on)) andsurface(a second module drawingClientelements).hooksandmodulesmay both be present. - A hook is
($, e, next), in that order.on(event, hook)oron(event, matcher, hook); the event name must be a string literal, so the scanner can read what a module hooks before loading it. $is spelled$.noun.event(...)at the call site, always. It cannot be destructured, renamed, passed across an import, or read as a value.nextis the same: called, ornext.to(e, "<tier>")on the hook's own parameter.- The capabilities are one list at
pretty-v2.1.267.js:230150:model.*,audio.*,mcp.call,session.*,turn.*,flag.value,tool.register,command.register,agent.*,ui.*,fs.*,store.*,http.fetch,process.run,settings.read,env.*, plus the interception eventsprompt.section,tool.describe,skill.prompt,attribution.text,engine.create. - Surfaces are
terminalanddesktop. The components aui.renderhook may take:AskUserQuestion(engine-only),UserMessage,AssistantMessage,ToolUse,ToolResult,ToolGroup,Spinner,TurnDuration,InfoNotice,SessionMode,PromptHint,AbovePrompt,Pane. - The element table comes from
$.ui.resolve(e), and holdsBox,Text,Button,Input,Select,Link,Code,Client,Svg. Nothing else is an element.
Four things that cost a run each
-
Children go in
props.children, not as trailing arguments.Box({ children: [...] })draws;Box({}, child, child)returns a tree with no children at all, and the pane renders as an empty frame with no error anywhere. Theui.renderdispatch still logs as settled. This is a trap only for a module written in plain calls, which this one is: JSX is available in a hooks module withhas the factory, and<Box>{...}</Box>puts children where they belong on its own.jsconfig.jsonhere is already set up for it. -
A
tool.callresult must be a string, an array, orundefined. A plain object is refused with "tool.call step resolved<tool>with a result that does not match its output shape", and what reaches the model is that the call failed, not the object. Both tools here returnJSON.stringify(value, null, 2). -
A registered command must be answered. A
command.runhook that callsnext(e)instead of returning{ text }makes Claude Code print "cc-changelog registered /whatsnew but no command.run hook answered it". -
Codewraps a long line and then draws the continuation row over the row under it (2.1.269, terminal surface). The declaration promiseswrap: 'wrap'by default and the element does wrap; it just measures itself unwrapped, so a wrapped line garbles the block below. The module breaks code lines itself, at words, eight columns inside the pane's width, so the element never has a line to wrap.
What a pane render carries
{"surface":"terminal","component":"Pane","viewport":{"columns":80,"rows":24},
"requestId":"cc-changelog",
"props":{"title":"Claude Code changelog","focused":false,"bodyColumns":76,
"scroll":{"offset":0,"bodyRows":0}}}
requestId is the id passed to $.ui.open({ id, title, focus }), which is how
a hook tells its own pane from another plugin's. $.http.fetch answers
{ status, ok, headers, text }; $.store.get answers the stored value itself.
The API it reads
Every tool here is a thin pass-through of a public JSON route on
https://changelogs.core-directive.com. Thirty of them, one paging
contract (offset, limit, count, total, next_offset, where a null
next_offset ends the walk), and one error convention: 422 names the
parameter it refused and what it would have taken, 404 names what the site
does hold, 429 carries retry_after. You do not need this plugin to use
them, and there is no key.
| Route | Answers |
| --- | --- |
| /releases.json | releases, newest first |
| /upgrade.json | everything published between two versions, flattened, with the releases crossed and facets |
| /v/{version}/entries.json | one release's entries, with facets and the summary |
| /v/{version}/e/{anchor}.json | one entry in full |
| /v/{version}/document.json | one character window of a release, with its outline |
| /search.json | entries across every release |
| /reference.json, /reference/search.json, /reference/{family}.json, /reference/{family}/{slug}.json | the mined inventory of every flag, env var, settings key, command, tool, hook event and model id |
| /docs/sources.json, /docs/search.json, /docs/{source}/pages.json, /docs/{source}/{path}.json | Anthropic's documentation as this site captured it, version by version |
| /blog.json, /blog/{slug}.json | the site owner's own writing |
| /prompts/{version}/tools.json, /prompts/{version}/tools/{name}.json | what a build actually told the model its tools do, byte for byte |
| /reference/mods/api/symbols.json, /reference/mods/api/{page}/{anchor}.json | the plugin runtime's own API: every symbol on $, every event, every declared type; an event carries its input and result declarations inlined |
| /reference/mods.json, /reference/mods/{page}.json | the Mods guide: ten hand-written pages on how a hooks module is actually written, as the markdown the site's own pages render |
| /docs/changes.json, /docs/days.json, /docs/day/{date}.json, /docs/change/{id}.json, /docs/c/{capture}.json | what Anthropic quietly edited: which pages moved, on what day, and what the words used to be |
| /stories.json, /stories/{slug}.json | one feature followed across every release it was touched in, as somebody grouped it by hand |
| /watch.json | what has happened to published entries since: flags moving, notes landing, documentation catching up, corrections |
The five that answer a whole document take section, offset and a character
limit, and carry the document's outline on every answer, so reading one is a
walk rather than a download.
The types
types/claude-code.d.ts is the plugin API itself: 701 KB of declarations
covering every event's input and result, every method on $, and every
element's props. It is the authority and this README is the map to it.
Nobody wrote it. Claude Code writes it out of the running build beside every mod it loads from a folder you own (up to v2.1.286 you typed /plugin-types for it), and the copy here is what 2.1.323 wrote. It is early access and may move between releases without notice, so regenerate against your own build rather than trusting this file's age. types/README.md has how, and the
terms it is published under, which are not this repository's MIT.
Caveats
The surface is still marked early access, so the shape can change in any release. Up to v2.1.286 a reader on a build without the rollout flag sees nothing at all, and from v2.1.287 mods load by default unless Anthropic switches them off remotely. Verified on v2.1.267
and v2.1.269, on Linux, on the terminal surface. The desktop, mobile and vscode
surfaces, the surface module and Client elements are read from the bundle
and the declarations and have not been run.
获取徽章
这是你的项目吗?把这个徽章放进 README 或网站,链接回本页。


