Repository navigation
Releases
v0.4.1 — plugin icon, and collapsed-by-default settings sections
The plugin now has an icon
package.json declares a top-level "icon": "./icon.svg", and the file ships in
files. DSH reads it without running any plugin code and renders it in the
bundle's card (36 px), its detail page header (36 px), and its component rows
(30 px inside a 40 px frame) — in the Plugins panel and in Settings.
Two framing fixes were applied to the Affinity export: the root viewBox is
rewritten to the smallest square box centred on the artwork (a non-square
viewBox letterboxes inside the panel's object-fit: contain box and renders
smaller), and the external DTD reference is dropped — an <img> neither parses
nor fetches it. The drawing itself is untouched.
test/config.test.mjs guards every rule the Host enforces, because each one fails
silently at runtime: a bad icon keeps the title and description and only adds a
metadata diagnostic, and an undecodable image falls back to the built-in artwork.
The three settings sections start collapsed
0.4.0 seeded all three open, on the theory that the page exists to make keys and
backends reachable. In practice the page is read far more often than it is edited,
and an expanded form buries the thing a reader actually wants — the two status
badges saying whether a key is configured and which Scholar backend is live.
It now reads as a table of contents first: three headers carrying their badges,
and you open only the group you came to change. The disclosure contract is
unchanged — the header is still the <button> carrying aria-expanded, a
collapsed section still renders no fields, and the badge still stays in the header.
Upgrading
dsh plugin --profile desktop add @astervolans/dsh-literature-searchthen restart DSH (plugin metadata is read at host start, so the icon needs the
restart). Versions ≤ 0.3.2 are rejected at install time on DSH 0.2.0.
Verification
Offline suite, 75 cases: MEDLINE parsing 7, Scholar parsing/paging 6, plugin
and tools 24, settings routes 17, client bundle 15, config drift 6. No network,
stub fetch.
The icon was confirmed end-to-end in a standalone dsh web instance: the
Plugins panel card, the plugin page header and the component row all render it
from the manifest's data URL at 36 / 36 / 30 px, and the three sections report
aria-expanded="false" with their badges visible and no fields in the tree.
v0.4.0 — configuration moves to the plugin page, in three collapsible sections
One level up: the configuration is now on the plugin's own page
0.3.3 put the page on the bundle's row, so reaching it meant opening
@astervolans/dsh-literature-search and then clicking the literature-search
row — a control nested inside a list of components, for a form that configures the
package as a whole.
It now registers plugins.bundle.config, keyed by the bundle's package name.
The plugin manager renders that cell in PackageDetail, between the package's
one-liner and its row list — so the configuration is the first thing on
插件 → @astervolans/dsh-literature-search, above the rows.
Nothing depends on the patch row id any more. cordis.patch.yml, the
/plugin/literature-search route tree, the literature-search settings
namespace, the credential slots and every saved key are unchanged.
Three collapsible sections
PubMed, Google Scholar and 通用 are now three expandable sections. Each
header is a real <button> — the whole strip is the hit target, and it is
keyboard-reachable with the browser's own focus ring — and a collapsed section
does not render its fields at all.
The status badge stays in the header, so a folded section still tells you whether
its key is configured.
All three start expanded: the page exists to make keys and backends reachable,
and collapsing everything by default would hide exactly that.
Upgrading
dsh plugin --profile desktop add @astervolans/dsh-literature-searchthen refresh the page (restart DSH if the section does not appear). Versions
≤ 0.3.2 are rejected at install time on DSH 0.2.0
(Plugin … is incompatible with dsh 0.2.0-rc.2) — 0.3.3 fixed that.
Verification
Offline suite, 73 cases: MEDLINE parsing 7, Scholar parsing/paging 6, plugin
and tools 24, settings routes 17, client bundle 15, config drift 4. No network,
stub fetch.
The client suite now drives the disclosure directly: the header carries
aria-expanded, a collapsed section renders no fields but keeps its badge, and
the page seeds all three sections open. It also pins the new key against
package.json#name — the manager dispatches by package name, and a key it never
dispatches renders nothing and raises nothing.
v0.3.3 — configuration page moves to the Plugins panel
Configuration page moved to the Plugins panel
The settings page used to register a tab into settings.plugins.tab, which put it
next to the built-in inventory under Settings → Built-in plugins — a section
whose only job is to report what a deployment ships. DSH's plugin manager is
explicit about the split: the sidebar 插件 / Plugins panel is where plugins are
configured, and "内置插件列表及运行状态可在「设置 → 内置插件」中查看".
The page now registers into plugins.row.config — the keyed slot
@deepseek-ai/dsh-client-ui-plugin-manager declares for a row a bundle owns —
with the key `${package name}#${row id}`,
@astervolans/dsh-literature-search#literature-search here. That is the same
string the panel recomputes through its own rowConfigKey(bundle, rowId) to decide
whether a row gets a configure control, so the page now hangs off the row itself:
插件 → @astervolans/dsh-literature-search → the literature-search row
The slot owner renders the entry twice, so the bundle now serves
PluginConfigViewProps.view: a hook-free one-liner for summary (the row's
description fallback) and the full form for page.
Nothing else moved — the /plugin/literature-search route tree, the
literature-search settings namespace, the credential slots and every saved key
are unchanged.
DSH 0.2.0 no longer disables the plugin
Also in this release (prepared as 0.3.3 on 2026-09-29, never published until now).
Starting with DSH 0.2.0, @deepseek-ai/dsh-app-boot's
evaluatePluginCompatibility() pre-flights every profile row and disables the
whole row when a @deepseek-ai/dsh / @deepseek-ai/dsh-* peer fails against the
running runtime. Both declared peers were ^0.1.0-rc.6, whose semantic upper
bound is <0.2.0 — so on a 0.2.0 host the bundle layer was skipped entirely: no
pubmed_* / scholar_* tool, no prompt-guidance section, no route tree.
They are now >=0.1.7-rc.1 <0.3.0, covering the 0.1.7 line and the whole
0.2.0 line. @deepseek-ai/cordis and @deepseek-ai/schemastery are not gated
and keep their ranges.
Upgrading
dsh plugin --profile desktop add @astervolans/dsh-literature-searchthen restart DSH. Versions ≤ 0.3.2 are rejected at install time on DSH 0.2.0
(Plugin … is incompatible with dsh 0.2.0-rc.2).
Verification
Offline suite, 71 cases: MEDLINE parsing 7, Scholar parsing/paging 6, plugin
and tools 24, settings routes 17, client bundle 13, config drift 4. No network,
stub fetch. The client bundle suite now pins the slot name, the key against both
package.json and cordis.patch.yml, and the summary/page split — a key mismatch
costs the row its configure control without raising anything, so a test is the
only place it can be caught before a user finds a row that will not open.
0.3.2
Fixed
-
A cleared key slot still read as "已配置(保存在设置中)".
collectSlottreated the settings descriptor's secret metadata (secrets[].set) as proof of a stored key, but the host reportsset: truefor anyrole('secret')field merely present in the projected value — and this namespace declarespubmedApiKey/scholarSerpApiKeyasdefault(''), so the flag was already true before a key was ever entered and stayed true after a clear. A non-empty inline value — whatresolveSecretactually reads — now decides thesettings-inlinestate; the flag stays infactsfor the diagnostic line, which printssettings-secret=setorsettings-secret=declared/empty. -
Clearing a slot left an inline key live.
POST /credentialskipped the inline-field cleanup wheneverclear: true, so a value inpubmedApiKey/scholarSerpApiKey— which outranks the credential store inresolveSecret— survived the clear while the card reported the slot as cleared. A clear now empties both layers.
Added
- Regression tests in
test/web.test.mjs: a declared-but-empty secret slot is not configured, a genuinely set inline key still reports thesettings-inlinestate, and a clear with an inline key present writes it away.
Full changelog: https://github.com/Astervolans/dsh-literature-search/blob/main/CHANGELOG.md
0.3.1 - client bundle registers under the scoped package name
@deepseek-ai/dsh-client-modules keys a client boot-graph row by the package name the row resolved to and arrives it on that id. 0.3.0 renamed the npm package to @astervolans/dsh-literature-search but left lib/client.js registering the pre-rename dsh-literature-search, so the row never arrived, the loader retried the bundle on its one-resource URL, and the second execution aborted the page:
Uncaught Error: client-modules: duplicate factory registration for "dsh-literature-search" (bundle executed twice without invalidate?)
web boot: 1 entry did not activate
@astervolans/dsh-literature-search: import failed (see console for the import error)
The desktop host then marked the profile unhealthy, wrote the plugin into desktopNextDeselectedBundles ("Disabled bundle for desktop"), and only started healthy with the plugin switched off -- which is what made the install look like DSH would not start.
Fixed
lib/client.jsnow registers underpackage.json#name, the convention the loader itself follows (its own bootstrap bundle registers@deepseek-ai/dsh-client-modules). The Settings -> Plugins literature card loads again.
Added
- Client-registration contract tests in
test/client.test.mjs: one asserts the registration id equalspackage.json#name, the other replays the loader's two load-bearing rules (a row is keyed by package name; a second registration throws) against the real bundle. Both cases fail on the shipped 0.3.0 bundle and pass on this fix, verified locally.
Changed
cordis.patch.yml, both READMEs and the CHANGELOG now state which identifiers stay unscoped and which two must be the scoped name. The 0.3.0 changelog entry, which claimed the client module id had been left alone, carries a correction note.
Upgrading from 0.3.0: reinstall with dsh plugin --profile desktop add @astervolans/dsh-literature-search@0.3.1 (or re-run install.ps1), then restart DSH. Settings and saved API keys are untouched.
Full changelog: https://github.com/Astervolans/dsh-literature-search/blob/v0.3.1/CHANGELOG.md
v0.3.0 — scoped package name and GitHub Packages publishing
The npm package is renamed to the scoped name @astervolans/dsh-literature-search, and releases now publish to GitHub Packages alongside npm.
Why the rename
GitHub Packages only accepts scoped npm packages, so the unscoped dsh-literature-search could never be published there. Rather than let the two registries drift apart under different names, 0.3.0 adopts the scoped name everywhere — one install command, both registries:
dsh plugin --profile desktop add @astervolans/dsh-literature-searchWhat did not change
The plugin's runtime identity is deliberately untouched, so an existing profile keeps its configuration and its saved keys:
| unchanged | value |
|---|---|
| plugin name | literature-search |
| settings namespace | literature-search |
| routes | /plugin/literature-search |
| client module id | dsh-literature-search |
| diagnostics directory | $DSH_HOME/.dsh-literature-search |
NCBI tool parameter |
dsh-literature-search |
What did have to move
cordis.patch.yml's row name is the module specifier the cordis loader imports from the profile directory, where only the scoped package exists — an unscoped value there makes dsh web abort at startup with Cannot find package. It is now "@astervolans/dsh-literature-search" (quoted, because a leading @ is a YAML reserved indicator). test/config.test.mjs already asserts row.name === package.json.name, so the two cannot diverge silently again.
install.ps1 / uninstall.ps1 copy to node_modules\@astervolans\dsh-literature-search. install.ps1 rewrites a pre-0.3.0 unscoped row in place, and uninstall.ps1 also removes a pre-0.3.0 copy and prunes the scope directory once it empties.
Upgrading from 0.2.x
dsh plugin --profile desktop remove dsh-literature-search
dsh plugin --profile desktop add @astervolans/dsh-literature-searchThen restart DSH. Nothing else to change.
GitHub Packages publishing
.github/workflows/release-package.yml follows the GitHub Packages quickstart — a build job gates publish-gpr on release: created — and publishes with the workflow's own GITHUB_TOKEN: no secret, no token rotation. It also carries workflow_dispatch, and re-running an already-published version is a no-op rather than a red run.
Only the @astervolans scope is remapped in CI, so npmjs.org stays the default registry and the same scope can still be published there from a local clone. Installing from GitHub Packages does need a token even though the package is public:
@astervolans:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=YOUR_GITHUB_PAT
On npmjs.org nothing changes; publishConfig.access=public is declared, which a scoped package needs there.
Fixed
npm pack --jsonshape drift in the CI packaging gate. npm ≤ 11 emits an array and npm ≥ 12 an object keyed by package name, so thepackagejob'srequire('./pack.json')[0]would have thrown aTypeErroron a runner with a newer npm. Both thepackagejob and the new release workflow now accept either shape.
Full diff: v0.2.4...v0.3.0
v0.2.4 — fix npm page showing the Chinese README
Fixes the npm package page showing the Chinese README instead of the English one.
What went wrong
npm chooses the package readme by globbing {README,README.*} in the package
root and taking the first result whose name ends in a markdown extension. In
that glob's order:
["README.zh-CN.md", "README.md"]
README.zh-CN.md sorts first, so the 0.2.3 publish shipped the translation
as the package readme and README.md was ignored entirely. The file was in the
tarball and correct — npm simply never looked at it.
This is a display-only defect: the shipped code in 0.2.3 is unchanged and
worked correctly. Only the npm page rendered the wrong language.
The fix
The Chinese README now lives at docs/README.zh-CN.md. npm's glob is not
recursive, so it no longer matches, and the selection is unambiguous. Verified
against the same glob and selection logic npm itself uses rather than by
assumption:
glob order: ["README.md"]
=> npm will pick: README.md
Moving the file broke its relative links (it now reaches LICENSE,
package.json, CHANGELOG.md, CONTRIBUTING.md and SECURITY.md through
../, and API-RESEARCH.md is a sibling). All of them were repaired, and every
relative link in both READMEs was re-checked and resolves.
CONTRIBUTING.md now documents the trap along with the command to re-check the
choice before publishing, so a future translation cannot reintroduce it.
Package contents
15 files. package.json (with its dsh.bundle.patch declaration) and
cordis.patch.yml are both included, as before.
Full diff: v0.2.3...v0.2.4
v0.2.3 — first npm release, English README
First release published to npm, so installing no longer needs a script:
dsh plugin --profile desktop add dsh-literature-searchdsh plugin is a thin pnpm forwarder that reconciles the profile's bundle layer
list by installed state, so a dependency declaring dsh.bundle joins the stack
on its own. This is also what gives the plugin an install command in the
1024 Store catalog, which lists store installs from
npm only.
Added
- English README as the primary
README.md, with the Chinese original kept
in full asREADME.zh-CN.md. The two link to each other at the top. npm
rendersREADME.mdto the package page, which is why English leads. docs/andREADME.zh-CN.mdnow ship in the package, so the links the README
makes resolve for npm consumers instead of only for a git clone.dsh-pluginkeyword, matching the GitHub topic used for catalog discovery.
Changed
- The DSH peer dependencies are now optional via
peerDependenciesMeta.
They are supplied by the DSH runtime; without this, npm tries to resolve their
own peer graph during install and fails with anERESOLVEconflict on
@deepseek-ai/dsh-tools. Marking them optional keeps
npm install dsh-literature-searchworking while preserving the declaration
for hosts that do resolve them. - The README install section leads with the npm command and keeps
install.ps1
as the documented offline route.
Security
npm publishis gated behindprepublishOnly, which runs the full offline
suite first, so a broken package cannot be published by accident.
Package contents
15 files, 60.1 kB packed / 183.9 kB unpacked. Both package.json (with its
dsh.bundle.patch declaration) and cordis.patch.yml are included — the
harness needs the patch, and the catalog's detection reads the declaration.
Full diff: v0.2.2...v0.2.3
v0.2.2 — dark-mode settings card fix
Patch release fixing a dark-mode legibility bug in the settings card.
Fixed
- The settings card was unreadable in dark mode. The primary buttons paired
a--dsw-alias-brand-primaryfill — which is near-white (#f9fafb) in dark
mode — with a literal#ffflabel, so the text disappeared into the button.
They now use the first-party pairing--dsw-alias-label-primaryfill over
--dsw-alias-bg-layer-3label; those two tokens swap together per theme. - Status colours come from the
--dsw-alias-state-{success,warn,error}-*tokens
instead of hardcoded hex, so badges and hints survive a theme switch. The
non-existent--dsw-alias-label-errortoken is no longer referenced.
Changed
- Secondary buttons use the first-party ghost treatment: transparent fill, a
--dsw-alias-border-l2outline and the secondary label colour. - Disabled buttons use the first-party 40% opacity, down from 50%.
- CI:
actions/checkoutandactions/setup-nodebumped to v6, which runs on the
Node 24 runtime and clears the Node 20 deprecation annotations.
Tests
The offline suite is now 62 cases (client bundle 6 → 10). The new
deep-render helpers expand function components the way React would, so nodes
owned by child components — the credential fields' buttons — are reachable from
a test. The added cases guard the theme regressions directly: primary buttons
must use the paired tokens, no style may paint --dsw-alias-brand-primary
behind a fixed colour, disabled buttons must sit at 0.4 opacity, and status
colours must come from state tokens rather than literals.
Full diff: v0.2.1...v0.2.2
v0.2.1 — first public release
First public release of dsh-literature-search — a DeepSeek Harness (dsh) plugin
that searches literature through the official PubMed NCBI E-utilities API and
Google Scholar, and returns both backends as one shared paper structure.
Tools
| Tool | Upstream | Notes |
|---|---|---|
pubmed_search |
esearch + esummary + efetch |
Field-tagged queries, sort, date windows, offset paging |
pubmed_paper |
efetch + esummary |
Full record: abstract, MeSH, keywords, publication types |
pubmed_related |
elink pubmed_pubmed |
NCBI related-article ranking |
scholar_search |
SerpApi / HTML | Cited-by counts, PDF links, year window, paging |
scholar_cite |
SerpApi | MLA / APA / Chicago / Harvard / Vancouver / BibTeX |
Highlights
- PubMed over the official API — free, keyless, documented, rate-limited
correctly (350 ms without a key, 110 ms with one), with retry/backoff that
honoursRetry-After. - Google Scholar has no official API. With a SerpApi key it uses the
google_scholarengine; without one it parsesscholar.google.comHTML and
reports throttling or anti-bot pages as explicit errors rather than
silently returning nothing. - Zero runtime dependencies — only the Node built-in
fetch/AbortSignal
plus the@deepseek-ai/*peers DSH itself provides. Nonpm installneeded
to install the plugin. - Settings page under Settings → Plugins → Literature Search: credential
slots, backend switching, rate/result limits, and a one-click connectivity
test for both backends. - 58 offline test cases covering MEDLINE parsing, Scholar parsing/paging,
plugin and tool contracts, settings routes, the client bundle and config
drift. The suite uses stubfetchand never touches the network.
Install
powershell -ExecutionPolicy Bypass -File install.ps1 -Profile desktopThen restart DSH. See the README for configuration and the full option table.
Requirements
Node ^22.19.0 || >=24.0.0. Windows install scripts are included; the plugin
itself is platform-independent.
Known limitations
- PubMed date filtering uses the print publication date (
pdat), so
ahead-of-print records can show a laterpubdate. That is upstream behaviour. - The Scholar HTML backend is best-effort and can be blocked by Google; a
SerpApi key is the reliable option. - PubMed abstracts may be copyright-protected — NCBI's disclaimer asks users to
respect the rights holders, and bulk mining should use local PubMed copies.