DeepSeek Harness Plugin Hub

发布与管理完整 Harness Profiles,发现适合你的插件。

探索

插件目录环境预设文档中心动态

社区

发布插件联系我们报告问题

相关链接

Plugin Hub GitHubDeepSeek Harness 官方项目系统状态隐私说明
© 2026 DeepSeek Harness Plugin HubPowered byPaxTech

独立、非官方社区项目,与 DeepSeek 官方无隶属、授权或背书关系。

Ab Chart — DeepSeek Harness 插件(DSH Plugin)
DeepSeek Harness Plugin Hub
ProfilesPlugins分类动态文档登录管理 Profiles
ProfilesPlugins分类动态文档登录
← Plugins

dsh-ab-chart

Ab Chart

带有绘制其调用的浏览器卡片的图表渲染工具,封装在一个可发布的插件包中

插件会安装到这里;不确定时保持 web。

npx -y @deepseek-ai/dsh plugin --profile web add dsh-ab-chart@0.1.19
README兼容性版本

兼容性与来源证明

Ab Chart 以 dsh-ab-chart 发布,当前版本为 0.1.19。Plugin Hub 会校验它的 manifest,并保存精确安装来源,便于复现安装结果。

DSH 兼容范围
*
运行环境
web
发布来源
npm
Registry 更新时间
2026/10/16

版本

0.1.19stable
2026/10/13
0.1.18stable
2026/10/13
0.1.17stable
2026/10/11
查看其余 15 个版本收起版本
0.1.16stable
2026/10/11
0.1.15stable
2026/10/11
0.1.14stable
2026/10/11
0.1.13stable
2026/10/11
0.1.12stable
2026/10/9
0.1.11stable
2026/10/9
0.1.10stable
2026/10/9
0.1.9stable
2026/10/9
0.1.8stable
2026/10/8
0.1.7stable
2026/10/4
0.1.5stable
2026/10/2
0.1.6stable
2026/10/2
0.1.4stable
2026/9/28
0.1.3stable
2026/9/28
0.1.2stable
2026/9/27

相关插件

正在加载相关插件…

最新版
0.1.19
DSH
*
HMR
重启进程
Tree shaking
未声明可安全裁剪
解包体积
3.7 MB
文件数
25
Surface
web
许可证
MIT
发布源
npm
GitHub
★ 250.9k
周下载
1,703
安全扫描
✓ v0.1.19 扫描通过
最近提交
2026/10/15
查看源码 ↗项目主页 ↗
README Badge

点击下方 Badge 复制 Markdown,粘贴到 README 即可。

这是你的 Plugin?认领权益 · 优先安全扫描

验证 package.json 声明的 GitHub 仓库,即可管理这个公开页面。认领后,Hub 会优先安排当前版本的安全扫描,并在通过后公开展示结果。

认领这个 Plugin →
报告问题

相关插件

继续浏览 ui-customization 分类下经过校验的插件。

Web App@deepseek-ai/dsh-web-appdsh 浏览器界面捆绑包:位于 dsh-base 之上的 Web 补丁层,加上运行时粘合插件(提供前端 dist、Web 界面提示符、bash 运行时变量和 URL 行)Experimental Agent Team Web Profile@deepseek-ai/dsh-experimental-agent-team-web-profile用于 Agent Teams Remote 和 UI 插件的实验性 Web 配置层Client Ui Skill Explorer@linxin666/dsh-client-ui-skill-explorerDSH 技能中心:按来源(bundled / project / user / custom / runtime)浏览已加载的技能,在 Web GUI 面板中启用或禁用、创建和删除技能。Remote Web Ui@linxin666/dsh-remote-web-ui通过扫码配对访问 dsh Web GUI,共享一个官方界面:设置按钮旁的二维码可将手机和 PC 配对到同一个 Web GUI(手机采用竖屏触控适配层,PC 使用完整桌面界面),通过一次性令牌和 r

README


description: "The publishable dsh-ab-chart package: the chart tool, its scope-aware prompt section, and the browser card that draws its calls." kind: "package-reference"

dsh-ab-chart

Summary

One package holding both halves of the chart feature. The host half registers the wire name on ctx.tools, contributes its routing section to ctx.systemPrompt, and writes the chart files; the browser half registers the matching key in the tool.call.toolview slot and draws the chart from the logged call and the persisted result metadata. The manifest declares dsh.bundle.patch for the host row and dsh.client for the card, so one install — and one patch row — carries both.

Built against the dsh 0.2.0 runtime (harness 0.2.0-rc.2; the @deepseek-ai/dsh-* packages at 0.2.0-rc.2). The plugin installs from the npm registry only — never via link: or file: — which is what keeps a fresh install on a real tarball rather than a node_modules reparse point borrowed from a sibling checkout.

# <DSH_HOME>/profiles/web/cordis.patch.yml
- insert:
    - id: chart
      name: 'dsh-ab-chart'
// <DSH_HOME>/profiles/web/package.json  (installed from the npm registry; see Mounting)
"dsh-ab-chart": "^0.2.0-rc.2"

The card is wired from the same package, so mounting that one row mounts both the tool and the card; nothing else reads the browser half.

Build, test, and publish

npm run build    # tsc -p tsconfig.json && tsdown (lib/index.js and lib/client.js)
npm run verify   # typecheck + build + test + packaging lint

Publishing is the dsh-plugin-release skill's step; its scripts take this package as an argument. Write <skill> for that skill base directory, which the loader prints when it loads the skill:

node <skill>/scripts/src/index.mjs check   --package <this package>
node <skill>/scripts/src/index.mjs release --package <this package> --bump patch
node <skill>/scripts/src/index.mjs publish --package <this package> --registry <url>

What one call does

render_chart records; save_chart draws.

ToolStepDetail
render_chartReadThe table from data (inline, any accepted shape) or from a file named by from, through the mounted filesystem capability
render_chartValidateNon-empty trimmed title and axes, unique series names, one finite value per category, the deployment's title/series/point ceilings, and (for cartesian charts) a sane axis per series
render_chartRecord<chartDir>/<slug>.<digest>.chart.json, skipped entirely when the file already holds this chart
render_chartReportOne line naming the chart, its size, its axes, and the recorded description's path
save_chartReadThe recorded description, rebuilt from the durable file rather than trusted
save_chartDrawOne ECharts option, rendered to SVG with no browser and no canvas, at the deployment's size or the call's own
save_chartSave<chartDir>/<slug>.<digest>.svg, skipped entirely when the file already holds this drawing
save_chartReportOne line naming the file and the size it was drawn at

Separating the two is what keeps the picture the logged call. render_chart returns a bounded description the card draws, so nothing about what the conversation shows depends on a render step having run; the file is a second, explicit action taken only when somebody wants one, and the table does not travel again to get it.

The families

Every family is one set of named numeric series over one shared list of category labels, which is why one data grammar feeds all of them. A pie, a funnel, a radar, and a gauge take the same table as a bar chart; only the drawing differs.

KindSeriesShape
bar, line, area, scattermanyCartesian: categories on one axis, values on the other; an axis field splits series across a left and a right Y axis
pie, funnelexactly oneThe single series is the whole picture
radarmanyEach category is an axis of the web
heatmapmanyThe series become the rows (the second, categorical axis); each [category, series] cell carries one value, shaded by a shared visualMap
gaugeexactly oneOne dial per category from that single series; range sets the dial span

Four families carry a shape rather than a category × series table: candlestick, boxplot, sankey, and treemap. They share one parser — src/chart-data.ts — that both the host and the card import, so a saved file and a replayed card cannot disagree on their data the way the classic grammar (duplicated between the two surfaces by necessity) could. A call names one of these four kinds and passes the shape in data or in a file named by from, through the same size ceiling and filesystem read the table uses.

KindShapeWhat the data carries
candlestickK-lineOne category (date) per row plus a four-number tuple open, close, low, high
boxplotFive-number boxOne category per row plus a five-number tuple min, Q1, median, Q3, max
sankeyFlowsource, target, value flows; nodes are the labels the flows name
treemapHierarchyname, value, parent rows (parent blank for a root); the value may be absent on a pure container
KindDelimited exampleJSON example
candlestickcategory,open,close,low,high then Mon,10,12,9,13{"categories":["Mon"],"ohlc":[[10,12,9,13]]} or {"categories":["Mon"],"open":[10],"close":[12],"low":[9],"high":[13]}
boxplotcategory,min,Q1,median,Q3,max then G1,1,2,3,4,5{"categories":["G1"],"boxes":[[1,2,3,4,5]]} or {"categories":["G1"],"min":[1],"q1":[2],"median":[3],"q3":[4],"max":[5]}
sankeysource,target,value then A,B,10{"links":[{"source":"A","target":"B","value":10}],"nodes":[{"name":"A"},{"name":"B"}]} — nodes is optional; missing, it is the set of labels the links name
treemapname,value,parent then root,100, / a,60,root{"nodes":[{"name":"root","value":100,"children":[{"name":"a","value":60},{"name":"b","value":40}]}]} — children nests; a node with no value is a pure container

A candlestick or a boxplot keeps the shared category axis, so its JSON still names categories; a sankey or a treemap carries no categories and the recorded description stores nodes/links or tree instead. Every reader throws on an unusable input in the same voice as the classic grammar — a header of the wrong width, a tuple that is not the right number of finite numbers, a sankey value below zero, or a treemap that names a parent box that is never listed.

The table grammar

data accepts one table in several shapes. The data argument is one short description rather than one argument per format, so a new accepted shape costs nothing on requests that do not use it.

ShapeExample
Delimited, columnarRegion,Q1 then North,10 — the header names the columns, the first column holds the labels
Delimited, wide1月,2月 then 销售额,120,150 — the header holds the category labels, each later row leads with its series name
Markdown tableA table pasted straight out of an earlier result, alignment row and all
JSON records[{"month":"Jan","sales":120}, …] — the label column is the one named like one, else the first non-numeric one
JSON matrix[["Jan","Feb"],["sales",120,150]]
JSON explicit{"categories":[…],"series":[{"name":…,"data":[… ],"axis":1}]}, or series as a name-to-values record

Numeric cells are read the way a spreadsheet export writes them: a currency mark, a thousands separator, a trailing percent, and parentheses for a negative are all the same number. Fields may be double-quoted, so a comma inside a label or a thousands separator inside a value survives. A row whose width matches neither accepted shape is rejected with the row number.

from reads the same grammar out of a file — .csv, .tsv, .json, .md, or .txt — through ctx.fs, so the deployment's file policy and sandbox see the read. Pointing at a file the deployment already has costs no output tokens at all.

A series may name the Y axis it is drawn on in the explicit JSON form: {"categories":["a","b"],"series":[{"name":"s","data":[1,2]},{"name":"r","data":[3,4],"axis":1}]}. axis is 0 (left, the default) or 1 (right); it applies to cartesian families only and is rejected on any other family.

The switches

options is a comma-separated list, deliberately small and closed.

TokenApplies toEffect
stack:normalbar, line, areaStack series instead of placing them side by side
stack:percentbar, line, areaStack, with each value re-expressed as its share of the category
smoothline, areaCurved line
stepline, areaStaircase line that holds each value across its category; wins when both smooth and step are named
horizontalbarCategories on the value axis
labelsbar, line, area, pie, scatter, funnel, heatmapPrint each point's (or, on a heatmap, each cell's) value beside it
donutpieDraw a ring instead of a disc
range:lo-higaugeSpan the dial from lo to hi (for example range:0-100); omitted, a gauge spans zero to just above its largest value
axis:0 / axis:1bar, line, area, scatter (per series, in the explicit JSON form)Which Y axis the series is drawn on

A switch that cannot apply to the call's family is rejected rather than ignored: donut on a bar chart is a caller mistake, and a silent no-op would return a picture that does not match what was asked for. range and axis apply to a gauge and to cartesian charts alone; naming either on any other family is refused. stack:percent is computed by the renderer, so the recorded specification keeps the values that were actually measured.

Drawing quality

The mapping is a closed, pure function of the specification, so the saved file and the on-screen card draw the same picture; tests/option.test.mjs feeds one corpus to both and fails the moment they would drift. Beyond the thirteen families, the drawing carries a set of professional touches:

  • Locale-stable number formatting. Axis ticks, data labels, and tooltips are re-spelled by a formatter that trims float noise to two decimals, drops the trailing zeros, and groups the integer part by thousands — 12345678 reads 12,345,678. It is the same implementation on the host (Node) and the card (browser), so a saved file and a replayed chart agree on every glyph, and the chart's unit is named on the tooltip.
  • Color-blind-safe default palette. When no palette is configured, series take the Okabe–Ito set, which stays distinguishable under the common forms of color blindness. A deployment that names palette draws with its own colors instead; the card and the file always use the same set.
  • Translucent area fill. An area series fills at 20% opacity over its series color, for a lighter wash than the library's opaque default.
  • Soft bar corners. Bars carry a small border radius, so a bar chart reads as a modern dashboard rather than a block chart.
  • step line. A line or area series drawn with step holds each value across its category — a staircase — which suits discrete or cumulative measures; when smooth and step are both named, step wins.

Where the files go

<chartDir>/<slug>.<spec-digest>.chart.json   the recorded description (render_chart)
<chartDir>/<slug>.<draw-digest>.svg          the drawing (save_chart)

  slug         the chart title, made path-safe and bounded to 80 characters ("chart" when nothing survives)
  spec-digest  sha256 over the specification alone, first 12 hex characters
  draw-digest  sha256 over the specification and the drawing size, first 12 hex characters

Two files, two names, each covering the inputs its own bytes depend on: the description's bytes follow from the specification, and the drawing's from the specification and the size it was drawn at. Both are content-addressed the way this tree's artifact store names its files, so the same value always names the same file, a repeated call is one existence check, and a changed value cannot be mistaken for the previous one because it is not reachable under the old name.

The drawing carries its own white background, because it is read outside the page that drew it. The description carries none, because the card takes the page's background.

Writing under the session's sandbox policy

A deployment that mounts @deepseek-ai/dsh-fs-sandbox fences every write by a per-call sandbox policy, and that policy — not the backend's own default — is the only thing naming the workspace the calling session runs in. Both writes resolve ctx.sandboxPolicy for the call and stamp it onto the write, so a description or a drawing lands inside the session's own workspace; a confining backend that receives no policy falls back to the directory the server was launched from and refuses everything outside it.

src/sandbox.ts owns that seam. callFence resolves the policy once per call and refuses a composition that mounts a confining backend with no policy service, which cannot be caught at load because that backend declares the service as its own injection and is never constructed without it. callWorkspace prefers the fence's root over the session cwd and reads no process.cwd(), leaving a call that named no workspace to the provider's own default. saveText is the one path every artifact is written through, so a new call site cannot omit the fence; it restates a refusal with the path, the mode, and the workspace to move under, keeping the FS_SANDBOX_DENIED code the backend raised.

The host half and the browser half

The host half lives in src/: index.ts is the composition root that wires the pure core (core.ts) into ctx.tools and ctx.systemPrompt; core.ts holds the validation, recording, and drawing path with no Cordis or Schemastery in sight, so a suite can drive the whole tool without a registry; chart-option.ts is the single shared ECharts mapping every surface draws — the tree-shaken echarts.use registration plus the family branches — so the host file renderer and the browser card cannot drift apart; format.ts, options.ts, render.ts, store.ts, card.ts, and types.ts are the data grammar, the switch grammar, the host-only background override, the file naming, the bounded card payload, and the shared vocabulary.

The browser half lives in src/client/: option.ts re-exports the shared chartOption from ../chart-option.ts (no second copy of the mapping), parse.ts and format.ts are a second copy of the host's table and call readers, index.ts registers the render_chart keyed tool view and the chart dictionaries, FeatureRow.tsx draws it, and styles.ts carries the sheet. The card reads no live state: it draws from the logged call slice, so a replayed session draws what the live call drew, and it falls back to a text row when no source parses.

The card still carries its own copy of the table and call readers — a browser bundle cannot reach the host package without carrying its chart library — but the option mapping is now shared; tests/option.test.mjs feeds one corpus to both chartOption imports (host index.ts and the card's re-export) and fails the moment the picture on screen could differ from the picture in a saved file.

Configuration

Nine bounds are required and have no default: a hidden one would decide for the deployment how much of a chart belongs in the log, the card, and the chart directory. Two drawing overrides are optional — stated only to change the saved file's look; the card always takes the page's background and the built-in palette, so a deployment that names neither draws exactly what shipped before these fields existed.

FieldGroupMeaning
chartDirstoreDirectory descriptions and drawings are written to; a relative value resolves against the session workspace
widthstoreWidth save_chart draws at unless the call names its own, in CSS pixels
heightstoreHeight save_chart draws at unless the call names its own, in CSS pixels
maxSeriesguardMost named series one call may declare
maxPointsguardMost plotted points (series × categories) one call may declare
maxTitleLengthguardMost characters in the chart title
maxDataBytesguardLargest table one call may read or accept inline, in bytes
maxResultCharsguardLargest model-facing result one call may return, in UTF-8 bytes
maxMetaBytesguardLargest chart payload a call may persist on its result, in UTF-8 bytes
backgroundoverrideBackground painted into the SVG save_chart writes, or undefined for the host default (white). Optional: only the saved file is repainted, never the on-screen card
paletteoverrideSeries color palette save_chart paints with, or undefined for ECharts' built-in palette. Optional: only the saved file is repainted, never the on-screen card

maxMetaBytes bounds a different value from maxResultChars, which is why it is a second field rather than the same number. maxResultChars caps the confirmation line the model reads; maxMetaBytes caps the chart payload the result persists, which rides the session log and is re-sent on every request, and which the deployment's output budget never shrinks. The recorded specification is already bounded by maxPoints and maxDataBytes, but those two together still allow a payload far larger than the line the model sees.

A chart has no partial form — half a bar chart is not a smaller answer, it is a wrong one — so the ceiling has one degradation rather than a truncating one: past maxMetaBytes the payload is withheld whole, and the card falls back to the logged arguments, which carry the same table and which it already reads for a call dispatched inside run_code. The persisted payload is therefore either a whole chart or nothing.

Commands

npm run build    # tsc -p tsconfig.json && tsdown
npm run test     # node --test (build first)

npm run test runs against lib/, so build first.

Mounting

The package is out-of-tree and installs from the npm registry only — never as a link: or file: dependency. The dsh_install_dsh_ab_plugin.py dispatcher runs dsh plugin add dsh-ab-chart, then pnpm install in the web profile; there is no local/junction path. The package's own dsh.bundle.patch (cordis.patch.yml, shipped in the package) already inserts the host and card half under id: chart, so the profile needs only an id-only config row — never a second - insert: row. The Config schema declares every bound .required() on purpose, so the deployment must state these numbers; an id-only row feeds them into the plugin the bundle patch already loaded, rather than registering it twice (which would warn tool "render_chart" is already registered).

## <DSH_HOME>/profiles/web/cordis.patch.yml
# id-only config row: the package loads itself via its own bundle patch.
# Add config ONLY here — a `- insert:` row with the same id would double-register.
# background / palette are OPTIONAL overrides of the saved file's look; omit them
# (then the host default white background and ECharts' built-in palette apply).
- id: chart
  config:
    chartDir: charts
    width: 960
    height: 540
    maxSeries: 8
    maxPoints: 2000
    maxTitleLength: 120
    maxDataBytes: 1048576
    maxResultChars: 2000
    maxMetaBytes: 262144
# Install (registry only) — no link:
python dsh_install_dsh_ab_plugin.py chart
#   or, by hand:  dsh plugin --profile web add dsh-ab-chart  &&  pnpm -C .dsh/profiles/web install

The row needs no filesystem provider of its own: it reads and writes through whatever ctx.fs the composition mounts. A recording call in a composition that mounts none still returns the chart — the card draws it — and reports that it was not recorded, so save_chart has nothing to draw.

Model Experience

System-prompt section

tool:render_chart at order 2960, after the built-in tool sections. It says when a result reads faster as a picture than as text, to name the axes and the unit and pass the table in data or in from, that the chart is drawn in the conversation from that call, that save_chart writes the recorded chart to a file whose path present can deliver, and not to hand-write a chart file. The section is empty in any scope where the tool is not visible.

Tool schema

Two schemas. render_chart takes kind, title, xLabel, yLabel, unit, data, from, and options. save_chart takes chartPath, width, and height — three short arguments, because the table does not travel again.

Tool-call history and result

The render_chart call, whose arguments carry the table, then one line naming the chart, its series and point counts, its axes, and the recorded description's path. A save_chart call and one line naming the file and its drawing size.

Auto-invocation

The plugin does not wait for the model to notice data. An agent/pre-step listener scans the inbound user message, and when it finds a markdown table carrying a number or an explicit chart-drawing intent ("画图" / "可视化" / "chart" / "plot"), it folds a priming instruction in front of the step so render_chart fires deterministically rather than after a missed cue. The trigger is the pure detectChartTriggers in src/trigger.ts; the listener gates on ctx.tools.get('render_chart'), so a composition that cannot see the tool never primes, and it acts on the inbound user turn alone, never on a tool result or its own prime. See .dsh/skills/dsh-plugin-development/references/auto-invocation.md.

Evidence

FileProves
tests/format.test.mjsEvery accepted table shape, the cell decorations, and each rejection (host)
tests/client/format.test.mjsThe card's table grammar agrees with the host's over one corpus, on the shapes it reads and on the ones it refuses
tests/options.test.mjsThe switch grammar, its rejections, and the round-trip back to the compact spelling
tests/option.test.mjsThe card's option mapping is identical to the host's for every family and switch, including heatmap, gauge, and dual-axis
tests/render.test.mjsEach family's option, percent stacking, and a standalone SVG for every kind including heatmap and gauge
tests/store.test.mjsThe slug rules, both digests' inputs, and that an existing file is not written again
tests/chart-data.test.mjsThe four structured families: STRUCTURED_KINDS, the isStructuredKind guard, and every JSON and delimited parse plus each rejection (host)
tests/index.test.mjsThe core through the built entry: every ceiling, the recording write, the drawing write, and both result lines
tests/parse.test.mjsBoth card readers accept a well-formed payload and decline every malformed one, including the new families
tests/presentation.test.mjsThe persisted card payload: a bounded chart rather than the canonical value, withheld whole past maxMetaBytes
tests/load-path.test.mjsThe module form through the real Loader: no default export, inject kept
tests/loader-composition.test.mjsA real cordis.yml boots, the bounds are live, and a recording call followed by a drawing call writes both files
tests/hmr-safety.test.mjsBoth tools and the routing section leave with their contributing fiber
tests/client-hmr-safety.test.mjsThe dictionaries leave with the fiber, and the keyed view is registered under the wire name the host tool registers
tests/contract.test.mjsThe installability and wiring contract: manifest scope, built artifacts, routing order above the built-in band, and the output projections the card and the model read
tests/conventions.test.mjsThe source rules the skill scaffolds a package with
tests/trigger.test.mjs

./invariant

The package publishes none. An invariant companion is warranted only when independent observations of one owned relation can diverge, and here one process both derives a file's name and writes the file, from the same specification. There is no second reader whose view could disagree; the suites cover the relationships observable from outside instead.

Known Limitations and Deferred Work

  • The card redraws from the arguments when a call ran inside another program, or when the payload was withheld. The core projects presentation metadata for root calls alone, and maxMetaBytes withholds the payload whole rather than cutting a chart, so a chart dispatched inside run_code — or one whose payload exceeded the ceiling — is drawn from its logged arguments. A chart that read its table from a file falls back to a text row.
  • The card keeps its own copy of the table and call readers. A browser bundle cannot reach this package without carrying its chart library, so src/client/parse.ts and src/client/format.ts mirror the host's parse.ts and format.ts; tests/option.test.mjs and tests/format.test.mjs feed one corpus to both and fail if they disagree. The option mapping itself is shared via src/chart-option.ts, so there is a single source of drawing truth. Keep the readers in step when adding a table shape.
  • A saved drawing is a second call. Keeping the picture as a file costs one save_chart call after the recording, and the two are separate entries in the transcript.
  • A recorded description is kept until the deployment removes it. Nothing sweeps chartDir; a recording call skips the write when the description already exists, so repeats do not accumulate, but every distinct chart stays until an operator clears the directory.
  • A negative written as -50 and one written as (50) are the same value. Both are read; the recorded series carries no memory of which spelling arrived.
  • Percent stacking is a drawing decision, not data. The recorded specification keeps the measured values; the shares exist only in the option handed to ECharts.
  • The host carries ECharts in its bundle. The tree-shaken import registers thirteen chart families, six components, and the SVG renderer, and only save_chart uses it, but one package entry means one bundle: the built entry is about 1.5 MB. The card carries the same tree-shaken set.
  • No PNG. SVG needs no rasterizer and previews in the sidebar; a PNG would add a native dependency for no capability the deployment has asked for.
  • A drawing is made at one size. The call may name width and height, but the size is part of the drawing's name, so the same chart saved at two sizes is two files.
  • The section order is a literal because repository-owned tool sections allocate theirs centrally; a built-in tool would call .

Dev Note

npm run verify runs tsc, tsdown, node --test, and the packaging linters over the built lib/, so a green suite means the artifact the profile row resolves is the artifact that behaves. The host draws nothing inside render_chart — that is what keeps the picture the logged call — and every rendering path lives behind save_chart. The card re-exports the shared option mapping from src/chart-option.ts, so there is one drawing implementation; it keeps its own copy of the table grammar only because a browser bundle cannot reach this package without carrying its chart library, and tests/option.test.mjs and tests/format.test.mjs feed one corpus to both copies and fail if they disagree. When you add a family or a switch, add it to types.ts, src/chart-option.ts, and the card's re-export together, then extend tests/render.test.mjs and tests/option.test.mjs.

The auto-invocation detector: trigger-free text yields [], a numeric table yields its block, an intent yields the note, and both appear without duplication
ctx.systemPrompt.getSectionOrder(name)