dsh-output-styles 0.3.2 → 0.4.0

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 CHANGED
@@ -4,6 +4,21 @@ All notable changes to this project are documented in this file. The format
4
4
  follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the
5
5
  project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.4.0] - 2026-08-16
8
+
9
+ ### Added
10
+
11
+ - **Renderer registry (`output.render.*` protocol)**: `ctx.outputRenderers` service with reversible `register()` / `list()` / `resolve()` / `renderText()`. A renderer is `{ id, match (tool/content-type), priority, presenter }` — the presenter is a pure function (args → display data, no DOM). Every render request passes the `output.render/before` waterfall first (listeners transform `{ text, context }` and must call `next()`), then the rule table, then matching renderers in priority order. Built-in renderers: `concise` and `step-by-step`.
12
+ - **Per-session/per-tool style rules**: `rules: [{ match: { tool, contentType, session }, style, priority }]` in Config and the new `output-style-rules` settings section (validated at write time; unknown renderer ids fail loudly at render time).
13
+ - **`/export` command**: renders the current session's message surface (official `deriveEventMessage` projection) to Markdown or sanitized HTML through the renderer pipeline — `/export [markdown|html] [--renderer=<id>]`. Every render keeps `{ original, rendered, rendererId, changed }`, so rendered output and its session-log source reconstruct together.
14
+ - `sanitizeText` / `toMarkdown` / `toHtml` / `renderExport` pure functions with extreme-case coverage (tags, control characters, huge inputs).
15
+ - Renderer protocol reference: `docs/renderer-protocol.md` (+ 中文).
16
+
17
+ ### Changed
18
+
19
+ - `/style` command and per-session persistence are fully unchanged (0.3.x compatible).
20
+ - Five-language READMEs: renderer protocol section, two new Config rows, `/export` reference; test count updated to 107.
21
+
7
22
  ## [0.3.2] - 2026-08-15
8
23
 
9
24
  ### Fixed
package/README.es.md CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
8
8
  [![CI](https://github.com/PerryLink/dsh-output-styles/actions/workflows/ci.yml/badge.svg)](https://github.com/PerryLink/dsh-output-styles/actions/workflows/ci.yml)
9
+ [![npm version](https://img.shields.io/npm/v/dsh-output-styles)](https://www.npmjs.com/package/dsh-output-styles)
10
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-output-styles)](https://www.npmjs.com/package/dsh-output-styles)
9
11
  [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
10
12
  [![DSH](https://img.shields.io/badge/deepseek--harness-0.1.0--rc.6-4d6bfe.svg)](#)
11
13
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg)](#)
@@ -107,7 +109,12 @@ Todo ajuste es un campo `Config` de Schemastery validado (los valores inválidos
107
109
  | `truncationMarker` | `"\n\n[style truncated]"` | Marcador añadido en el punto de truncado. |
108
110
  | `includeBuiltins` | `true` | Incluye el `styles/` incluido en el paquete como la capa de menor prioridad. |
109
111
  | `watchStyles` | `true` | Recarga la biblioteca cuando un archivo de estilo cambia en disco. |
112
+ | `rules` | `[]` | Reglas de renderizado por sesión/herramienta: `[{ match: { tool?, contentType?, session? }, style, priority? }]` — `style` nombra un id de renderer (los integrados coinciden con los nombres de estilo). |
113
+ | `enableExport` | `true` | Registrar el comando `/export` (exportación de sesión a Markdown/HTML, con renderers). |
110
114
 
115
+ ## 🎨 Protocolo de renderers
116
+
117
+ El protocolo `output.render.*` convierte la capa de presentación en un punto de extensión. Un renderer es un **presenter puro** — `presenter(text, context)` mapea argumentos a datos de presentación y nunca toca el DOM — emparejado por nombre de herramienta y tipo de contenido, ordenado por prioridad. Los plugins de terceros se registran con `ctx.outputRenderers.register({ id, match, priority, presenter })` (register devuelve el disposer, propiedad del ctx.effect del llamador). Cada renderizado pasa primero por el waterfall `output.render/before` (los listeners deben llamar `next()`), luego por la tabla de reglas (por sesión/herramienta: `rules: [{ match: { tool: 'bash' }, style: 'concise' }]`, editable en la sección de ajustes `output-style-rules`). Renderers integrados: `concise` (compresión de espacios + truncado con presupuesto) y `step-by-step` (numeración uniforme de pasos). Auditabilidad: cada resultado lleva `{ original, rendered, rendererId, changed }`; el original es el propio registro de sesión y la aplicación de renderers es determinista. `/export [markdown|html] [--renderer=<id>]` exporta la sesión actual por el mismo pipeline. Especificación completa: docs/renderer-protocol.md (inglés) / docs/renderer-protocol.zh.md.
111
118
  ## 📚 Biblioteca de estilos
112
119
 
113
120
  <details>
@@ -193,6 +200,14 @@ Lanzamientos: hacer push de una etiqueta `v*` cuyo sufijo coincida con la versi
193
200
 
194
201
  La estructura sigue [omdsh-dev/plugin-template](https://github.com/omdsh-dev/plugin-template): `src/index.ts` (metadatos del plugin), `src/config.ts` (esquema), `src/runtime.ts` (servicio de runtime + activación), `src/invariant.ts` (invariantes), `src/client/` (selector web), `styles/` (estilos integrados).
195
202
 
203
+ ## 👥 Contribuyentes
204
+
205
+ Gracias a todas las personas que han contribuido a este proyecto:
206
+
207
+ - [@PerryLink](https://github.com/PerryLink) — autor y mantenedor: arquitectura del plugin, biblioteca de estilos, instalación del bundle, selector web, documentación en cinco idiomas y herramientas de CI/publicación.
208
+
209
+ ¿Encontraste un error o tienes una idea? Abre un [issue](https://github.com/PerryLink/dsh-output-styles/issues) o envía un [pull request](https://github.com/PerryLink/dsh-output-styles/pulls) — las contribuciones en cualquier idioma son bienvenidas.
210
+
196
211
  ## 📄 Licencia
197
212
 
198
213
  [Apache-2.0](LICENSE) © 2026 dsh-output-styles contributors
package/README.ja.md CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
8
8
  [![CI](https://github.com/PerryLink/dsh-output-styles/actions/workflows/ci.yml/badge.svg)](https://github.com/PerryLink/dsh-output-styles/actions/workflows/ci.yml)
9
+ [![npm version](https://img.shields.io/npm/v/dsh-output-styles)](https://www.npmjs.com/package/dsh-output-styles)
10
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-output-styles)](https://www.npmjs.com/package/dsh-output-styles)
9
11
  [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
10
12
  [![DSH](https://img.shields.io/badge/deepseek--harness-0.1.0--rc.6-4d6bfe.svg)](#)
11
13
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg)](#)
@@ -107,7 +109,12 @@ flowchart LR
107
109
  | `truncationMarker` | `"\n\n[style truncated]"` | 切り詰め位置に付加されるマーカー。 |
108
110
  | `includeBuiltins` | `true` | パッケージ同梱の `styles/` を最優先度の低いレイヤーとして含めます。 |
109
111
  | `watchStyles` | `true` | スタイルファイルがディスク上で変更されたときにライブラリを再読み込みします。 |
112
+ | `rules` | `[]` | セッション/ツール別の描画ルール:`[{ match: { tool?, contentType?, session? }, style, priority? }]`——`style` はレンダラー id(ビルトインはスタイル名と同名)。 |
113
+ | `enableExport` | `true` | `/export` コマンドを登録(Markdown/HTML セッション書き出し、レンダラー対応)。 |
110
114
 
115
+ ## 🎨 レンダラープロトコル
116
+
117
+ `output.render.*` プロトコルはプレゼンテーション層を拡張点にします。レンダラーは**純粋な presenter**——`presenter(text, context)` が引数を表示データに写し、DOM には触れません——ツール名とコンテンツタイプでマッチし、優先度で並びます。サードパーティは `ctx.outputRenderers.register({ id, match, priority, presenter })` で登録(register は disposer を返し、呼び出し側の ctx.effect が所有)。各レンダーはまず `output.render/before` waterfall(リスナーは必ず `next()`)を通り、次にルール表(セッション/ツール別:`rules: [{ match: { tool: 'bash' }, style: 'concise' }]`、設定画面 `output-style-rules` で編集可)。ビルトインは `concise`(空白圧縮 + 予算切り詰め)と `step-by-step`(手順の連番化)。監査可能:各結果は `{ original, rendered, rendererId, changed }` を持ち、原文はセッションログそのもので決定論的に再構築できます。`/export [markdown|html] [--renderer=<id>]` は同じパイプラインで現在のセッションを書き出します。完全な仕様:docs/renderer-protocol.md(英語)/ docs/renderer-protocol.zh.md。
111
118
  ## 📚 スタイルライブラリ
112
119
 
113
120
  <details>
@@ -193,6 +200,14 @@ pnpm pack # dsh plugin add 用の tarball
193
200
 
194
201
  構成は [omdsh-dev/plugin-template](https://github.com/omdsh-dev/plugin-template) に従います:`src/index.ts`(プラグインメタデータ)、`src/config.ts`(スキーマ)、`src/runtime.ts`(ランタイムサービス + アクティベーション)、`src/invariant.ts`(不変条件)、`src/client/`(Web ピッカー)、`styles/`(組み込みスタイル)。
195
202
 
203
+ ## 👥 コントリビューター
204
+
205
+ 本プロジェクトに貢献してくださったすべての方に感謝します:
206
+
207
+ - [@PerryLink](https://github.com/PerryLink) — 作者兼メンテナー:プラグインアーキテクチャ、スタイルライブラリ、bundle インストール、Web ピッカー、5 言語ドキュメント、CI/リリースツール。
208
+
209
+ バグやアイデアがあれば [issue](https://github.com/PerryLink/dsh-output-styles/issues) や [pull request](https://github.com/PerryLink/dsh-output-styles/pulls) をどうぞ。どの言語での貢献も歓迎します。
210
+
196
211
  ## 📄 ライセンス
197
212
 
198
213
  [Apache-2.0](LICENSE) © 2026 dsh-output-styles contributors
package/README.ko.md CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
8
8
  [![CI](https://github.com/PerryLink/dsh-output-styles/actions/workflows/ci.yml/badge.svg)](https://github.com/PerryLink/dsh-output-styles/actions/workflows/ci.yml)
9
+ [![npm version](https://img.shields.io/npm/v/dsh-output-styles)](https://www.npmjs.com/package/dsh-output-styles)
10
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-output-styles)](https://www.npmjs.com/package/dsh-output-styles)
9
11
  [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
10
12
  [![DSH](https://img.shields.io/badge/deepseek--harness-0.1.0--rc.6-4d6bfe.svg)](#)
11
13
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg)](#)
@@ -107,7 +109,12 @@ flowchart LR
107
109
  | `truncationMarker` | `"\n\n[style truncated]"` | 잘린 지점에 추가되는 마커. |
108
110
  | `includeBuiltins` | `true` | 패키지 내장 `styles/`를 최저 우선순위 계층으로 포함합니다. |
109
111
  | `watchStyles` | `true` | 디스크에서 스타일 파일이 변경되면 라이브러리를 다시 로드합니다. |
112
+ | `rules` | `[]` | 세션/도구별 렌더 규칙:`[{ match: { tool?, contentType?, session? }, style, priority? }]`——`style` 은 렌더러 id(내장은 스타일과 동명)。 |
113
+ | `enableExport` | `true` | `/export` 명령 등록(Markdown/HTML 세션 내보내기、렌더러 인식)。 |
110
114
 
115
+ ## 🎨 렌더러 프로토콜
116
+
117
+ `output.render.*` 프로토콜은 프레젠테이션 계층을 확장점으로 만듭니다。렌더러는 **순수 presenter**——`presenter(text, context)` 가 인자를 표시 데이터로 매핑하고 DOM 에는 손대지 않습니다——도구 이름과 콘텐츠 타입으로 매칭되고 우선순위로 정렬됩니다。서드파티는 `ctx.outputRenderers.register({ id, match, priority, presenter })` 로 등록합니다(register 는 disposer 를 반환、호출자의 ctx.effect 소유)。모든 렌더는 먼저 `output.render/before` waterfall(리스너는 반드시 `next()`)을 거친 뒤 규칙표(세션/도구별:`rules: [{ match: { tool: 'bash' }, style: 'concise' }]`、설정 페이지 `output-style-rules` 에서 편집)를 적용합니다。내장 렌더러는 `concise`(공백 압축 + 예산 절단)와 `step-by-step`(단계 일련번호)입니다。감사 가능:모든 결과는 `{ original, rendered, rendererId, changed }` 를 가지며 원문은 세션 로그 그 자체로 결정적으로 재구성됩니다。`/export [markdown|html] [--renderer=<id>]` 는 같은 파이프라인으로 현재 세션을 내보냅니다。전체 규격:docs/renderer-protocol.md(영어)/ docs/renderer-protocol.zh.md。
111
118
  ## 📚 스타일 라이브러리
112
119
 
113
120
  <details>
@@ -193,6 +200,14 @@ pnpm pack # dsh plugin add용 tarball
193
200
 
194
201
  구조는 [omdsh-dev/plugin-template](https://github.com/omdsh-dev/plugin-template)를 따릅니다: `src/index.ts`(플러그인 메타데이터), `src/config.ts`(스키마), `src/runtime.ts`(런타임 서비스 + 활성화), `src/invariant.ts`(불변식), `src/client/`(웹 피커), `styles/`(내장 스타일).
195
202
 
203
+ ## 👥 기여자
204
+
205
+ 이 프로젝트에 기여해 주신 모든 분께 감사드립니다:
206
+
207
+ - [@PerryLink](https://github.com/PerryLink) — 작성자이자 메인테이너: 플러그인 아키텍처, 스타일 라이브러리, 번들 설치, 웹 피커, 5개 언어 문서, CI/릴리스 도구.
208
+
209
+ 버그나 아이디어가 있으신가요? [issue](https://github.com/PerryLink/dsh-output-styles/issues) 또는 [pull request](https://github.com/PerryLink/dsh-output-styles/pulls)를 보내 주세요. 어떤 언어의 기여든 환영합니다.
210
+
196
211
  ## 📄 라이선스
197
212
 
198
213
  [Apache-2.0](LICENSE) © 2026 dsh-output-styles contributors
package/README.md CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
8
8
  [![CI](https://github.com/PerryLink/dsh-output-styles/actions/workflows/ci.yml/badge.svg)](https://github.com/PerryLink/dsh-output-styles/actions/workflows/ci.yml)
9
+ [![npm version](https://img.shields.io/npm/v/dsh-output-styles)](https://www.npmjs.com/package/dsh-output-styles)
10
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-output-styles)](https://www.npmjs.com/package/dsh-output-styles)
9
11
  [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
10
12
  [![DSH](https://img.shields.io/badge/deepseek--harness-0.1.0--rc.6-4d6bfe.svg)](#)
11
13
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg)](#)
@@ -34,6 +36,9 @@
34
36
  | ⚙️ **Project default over settings** | Sessions that never selected one fall back to `output-style.style` from the DSH settings seam, then to `defaultStyle`. |
35
37
  | 🖱️ **Web picker** | A `dsh.client` entry (`dsh-output-styles/client`) decorates the host `/style` command with a projection-backed popup picker. |
36
38
  | 📊 **Session projection** | A `style` projection (`{ options, currentValue }`) for the Web UI, folded from settled commands in the session log. |
39
+ | 🎨 **Renderer registry (`output.render.*`)** | `ctx.outputRenderers` lets any plugin register a presenter — `{ id, match (tool/content-type), presenter, priority }` — applied through the `output.render/before` waterfall (listeners must `next()`). Built-in renderers: `concise`, `step-by-step`. Registration is reversible and owned by the caller's `ctx.effect`. |
40
+ | 🧾 **Per-session/per-tool rules** | `rules: [{ match: { tool: 'bash' }, style: 'concise' }]` — first matching rule (by priority) names the renderer; `match.session` scopes a rule to one session. Editable through the `output-style-rules` settings section. |
41
+ | 📤 **`/export`** | Renders the current session's message surface to Markdown or sanitized HTML through the render pipeline (`/export [markdown|html] [--renderer=<id>]`); every render keeps its original text beside the rendered one, and the original always stays reconstructable from the session log. |
37
42
  | 🧯 **Fail loud, skip cleanly** | Misconfiguration throws at load; a bad style file is skipped with a warning and never breaks the profile. |
38
43
  | 🌐 **Five-language docs** | EN · 中文 · 日本語 · 한국어 · Español. |
39
44
 
@@ -107,6 +112,8 @@ Every tunable is a validated Schemastery `Config` field (invalid values fail the
107
112
  | `truncationMarker` | `"\n\n[style truncated]"` | Marker appended at the truncation point. |
108
113
  | `includeBuiltins` | `true` | Include the package's bundled `styles/` as the lowest-priority layer. |
109
114
  | `watchStyles` | `true` | Reload the library when a style file changes on disk. |
115
+ | `rules` | `[]` | Per-session/per-tool render rules: `[{ match: { tool?, contentType?, session? }, style, priority? }]` — `style` names a renderer id (built-ins mirror the style names). |
116
+ | `enableExport` | `true` | Register the `/export` command (Markdown/HTML session export, renderer-aware). |
110
117
 
111
118
  ## 📚 Style library
112
119
 
@@ -158,6 +165,31 @@ Entries accept `keep-coding-instructions` and `force-for-plugin` exactly as Clau
158
165
  | `/style Diagrams first` | Multi-word names are the whole remainder |
159
166
  | `/style off` | Restore the project default (settings default, then `defaultStyle`) |
160
167
  | `/style nope` | `error: unknown output style "nope" (available: …)` |
168
+ | `/export` | Render the current session to Markdown through the renderer pipeline |
169
+ | `/export html` | Render to sanitized HTML |
170
+ | `/export --renderer=concise` | Render with one renderer forced (rules bypassed) |
171
+
172
+ ## 🎨 Renderer protocol
173
+
174
+ The `output.render.*` protocol turns the presentation layer into an extension point. A renderer is a **pure presenter** — `presenter(text, context)` maps args to display data, never touches the DOM — matched by tool name and content type, ordered by priority:
175
+
176
+ ```ts
177
+ // Third-party plugin registering a custom renderer (register() returns the disposer)
178
+ ctx.effect(() => ctx.outputRenderers.register({
179
+ id: 'sql-table',
180
+ name: 'SQL table compactor',
181
+ description: 'Truncates oversized SQL result sets to the head plus a row count.',
182
+ match: [{ tool: 'sql', contentType: 'text' }],
183
+ priority: 20,
184
+ presenter: (text, context) => compactRows(text, 50),
185
+ }))
186
+ ```
187
+
188
+ - **Waterfall first**: every render request passes through `output.render/before` (`{ text, context }`) — listeners transform the request and **must call `next()`**; returning without it short-circuits the pipeline.
189
+ - **Rules**: `rules: [{ match: { tool: 'bash' }, style: 'concise' }]` names the renderer for matching requests (tool, content type, or exact session); ties break by `priority`, then rule order. Rules live in cordis.yml and the `output-style-rules` settings section.
190
+ - **Built-ins**: `concise` (whitespace compaction + budget truncation) and `step-by-step` (consistent step numbering) — ids mirror the two headline style names.
191
+ - **Auditability**: every render result carries `{ original, rendered, rendererId, changed }`. The rendered text is what surfaces; the original text is the session log itself, and the render application is deterministic — so rendered output and its source always reconstruct together.
192
+ - Full protocol reference (including a worked third-party example): [docs/renderer-protocol.md](docs/renderer-protocol.md) (中文: [docs/renderer-protocol.zh.md](docs/renderer-protocol.zh.md)).
161
193
 
162
194
  ## 🖱️ Web picker
163
195
 
@@ -183,7 +215,7 @@ Screened against the DSH ecosystem before development (2026-08 snapshot): no `st
183
215
  ```sh
184
216
  pnpm install
185
217
  pnpm run typecheck # both tsc projects
186
- pnpm test # vitest — 93 tests
218
+ pnpm test # vitest — 107 tests
187
219
  pnpm run verify # typecheck + tests + self-contained (the prepublishOnly gate)
188
220
  pnpm run build # lib/ artifacts (host + client bundles)
189
221
  pnpm pack # tarball for dsh plugin add
@@ -193,6 +225,36 @@ Releases: pushing a `v*` tag whose suffix matches the `package.json` version tri
193
225
 
194
226
  Structure follows the [omdsh-dev/plugin-template](https://github.com/omdsh-dev/plugin-template): `src/index.ts` (plugin metadata), `src/config.ts` (schema), `src/runtime.ts` (runtime service + activation), `src/invariant.ts` (invariants), `src/client/` (Web picker), `styles/` (built-ins).
195
227
 
228
+ ## 👥 Contributors
229
+
230
+ Thanks to everyone who helped build this project:
231
+
232
+ - [@PerryLink](https://github.com/PerryLink) — author and maintainer: plugin architecture, style library, bundle install, Web picker, five-language docs, and CI/release tooling.
233
+
234
+ Found a bug or an idea? Open an [issue](https://github.com/PerryLink/dsh-output-styles/issues) or send a [pull request](https://github.com/PerryLink/dsh-output-styles/pulls) — contributions in any language are welcome.
235
+
236
+ ## PerryLink DSH Plugin Family
237
+
238
+ This project is one of the [15 DeepSeek Harness plugins](https://github.com/PerryLink) maintained by [PerryLink](https://github.com/PerryLink). If this one helps you, the others likely will too:
239
+
240
+ | Plugin | One-liner |
241
+ |---|---|
242
+ | [dsh-mcp-panel](https://github.com/PerryLink/dsh-mcp-panel) | Read-only MCP runtime panel: /mcp command + Settings tab with status, tools and errors |
243
+ | [dsh-doublecheck](https://github.com/PerryLink/dsh-doublecheck) | Engineering-discipline guard: requirements grill, test gates, adversary review |
244
+ | [dsh-background-agents](https://github.com/PerryLink/dsh-background-agents) | Durable background child agents with a Web UI sidebar, messaging and interrupt |
245
+ | [dsh-lsp-actions](https://github.com/PerryLink/dsh-lsp-actions) | LSP diagnostics, formatting, completion, code actions and rename over language servers |
246
+ | **[dsh-output-styles](https://github.com/PerryLink/dsh-output-styles)** | Claude Code outputStyles-equivalent runtime style switching |
247
+ | [dsh-checkpoint-rewind](https://github.com/PerryLink/dsh-checkpoint-rewind) | Claude Code /rewind-equivalent: snapshots, session forks, one-shot restore |
248
+ | [dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules) | Claude Code-style declarative allow/deny/ask permission rules with audit |
249
+ | [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) | Second-model auto-review on the approval chain, fail-closed by default |
250
+ | [dsh-memento](https://github.com/PerryLink/dsh-memento) | Approval-gated cross-session memory: ctx.memory seam + SQLite + memory tool |
251
+ | [dsh-skill-pack-security](https://github.com/PerryLink/dsh-skill-pack-security) | Security-audit skill pack: secret scan, dependency and supply-chain review |
252
+ | [dsh-session-pin](https://github.com/PerryLink/dsh-session-pin) | Pin sessions in the Web sidebar with durable ordering |
253
+ | [dsh-composer-history](https://github.com/PerryLink/dsh-composer-history) | Terminal-style input history for the web composer: arrows, Ctrl+R search |
254
+ | [dsh-github](https://github.com/PerryLink/dsh-github) | GitHub PR/issues integration for DSH, every write gated by approval |
255
+ | [dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide) | Plugin-development knowledge base as an on-demand agent skill |
256
+ | [dsh-claude-move](https://github.com/PerryLink/dsh-claude-move) | Migrate Claude Code sessions, memory, skills and CLAUDE.md into DSH |
257
+
196
258
  ## 📄 License
197
259
 
198
260
  [Apache-2.0](LICENSE) © 2026 dsh-output-styles contributors
package/README.zh.md CHANGED
@@ -6,6 +6,8 @@
6
6
 
7
7
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
8
8
  [![CI](https://github.com/PerryLink/dsh-output-styles/actions/workflows/ci.yml/badge.svg)](https://github.com/PerryLink/dsh-output-styles/actions/workflows/ci.yml)
9
+ [![npm version](https://img.shields.io/npm/v/dsh-output-styles)](https://www.npmjs.com/package/dsh-output-styles)
10
+ [![npm downloads](https://img.shields.io/npm/dm/dsh-output-styles)](https://www.npmjs.com/package/dsh-output-styles)
9
11
  [![Node](https://img.shields.io/badge/node-%5E22.19%20%7C%7C%20%3E%3D24-brightgreen.svg)](#)
10
12
  [![DSH](https://img.shields.io/badge/deepseek--harness-0.1.0--rc.6-4d6bfe.svg)](#)
11
13
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg)](#)
@@ -107,7 +109,12 @@ flowchart LR
107
109
  | `truncationMarker` | `"\n\n[style truncated]"` | 截断点追加的标记。 |
108
110
  | `includeBuiltins` | `true` | 将包内置 `styles/` 作为最低优先级层纳入。 |
109
111
  | `watchStyles` | `true` | 风格文件在磁盘上变化时重载风格库。 |
112
+ | `rules` | `[]` | 按会话/按工具的渲染规则:`[{ match: { tool?, contentType?, session? }, style, priority? }]`——`style` 指定渲染器 id(内置与风格同名)。 |
113
+ | `enableExport` | `true` | 注册 `/export` 命令(Markdown/HTML 会话导出,渲染器感知)。 |
110
114
 
115
+ ## 🎨 渲染器协议
116
+
117
+ `output.render.*` 协议把呈现层变成扩展点。渲染器是**纯 presenter**——`presenter(text, context)` 把参数映射为展示数据、绝不碰 DOM——按工具名与内容类型匹配、按优先级排序:第三方插件经 `ctx.outputRenderers.register({ id, match, priority, presenter })` 注册(register 返回 disposer,归调用方 ctx.effect)。每次渲染先过 `output.render/before` waterfall(监听器必须 `next()`),再走规则表(按会话/按工具:`rules: [{ match: { tool: 'bash' }, style: 'concise' }]`,设置页 `output-style-rules` 可编辑)。内置渲染器 `concise`(空白折叠 + 预算截断)与 `step-by-step`(步骤统一编号)。可审计:每个结果携带 `{ original, rendered, rendererId, changed }`,原始文本即会话日志、渲染确定可重建。`/export [markdown|html] [--renderer=<id>]` 经同一流水线导出当前会话。完整协议:docs/renderer-protocol.md(英文)/ docs/renderer-protocol.zh.md。
111
118
  ## 📚 风格库
112
119
 
113
120
  <details>
@@ -193,6 +200,36 @@ pnpm pack # 供 dsh plugin add 使用的 tarball
193
200
 
194
201
  结构遵循 [omdsh-dev/plugin-template](https://github.com/omdsh-dev/plugin-template):`src/index.ts`(插件元数据)、`src/config.ts`(schema)、`src/runtime.ts`(运行时服务与激活)、`src/invariant.ts`(不变量)、`src/client/`(Web 选择器)、`styles/`(内置风格)。
195
202
 
203
+ ## 👥 贡献者
204
+
205
+ 感谢每一位为本项目做出贡献的人:
206
+
207
+ - [@PerryLink](https://github.com/PerryLink) — 作者与维护者:插件架构、风格库、bundle 安装、Web 选择器、五语文档与 CI/发布工具链。
208
+
209
+ 发现 bug 或有想法?欢迎提交 [issue](https://github.com/PerryLink/dsh-output-styles/issues) 或 [pull request](https://github.com/PerryLink/dsh-output-styles/pulls),任何语言的贡献都欢迎。
210
+
211
+ ## PerryLink DSH 插件家族
212
+
213
+ 本项目是 [PerryLink](https://github.com/PerryLink) 维护的 [15 个 DeepSeek Harness 插件](https://github.com/PerryLink)之一。如果你觉得这个插件有用,其余的很可能同样有用:
214
+
215
+ | 插件 | 一句话说明 |
216
+ |---|---|
217
+ | [dsh-mcp-panel](https://github.com/PerryLink/dsh-mcp-panel) | 只读 MCP 运行时面板:/mcp 命令 + 设置页,状态/工具/错误一览 |
218
+ | [dsh-doublecheck](https://github.com/PerryLink/dsh-doublecheck) | 工程纪律守门:需求审讯、测试证据门、对抗评审 |
219
+ | [dsh-background-agents](https://github.com/PerryLink/dsh-background-agents) | 持久化后台子代理:Web 侧边栏进度、随时留言与打断 |
220
+ | [dsh-lsp-actions](https://github.com/PerryLink/dsh-lsp-actions) | 基于语言服务器的诊断/格式化/补全/代码动作/重命名 |
221
+ | **[dsh-output-styles](https://github.com/PerryLink/dsh-output-styles)** | 对标 Claude Code outputStyles 的运行时风格切换 |
222
+ | [dsh-checkpoint-rewind](https://github.com/PerryLink/dsh-checkpoint-rewind) | 对标 Claude Code /rewind:快照、会话 fork、一键回退 |
223
+ | [dsh-permission-rules](https://github.com/PerryLink/dsh-permission-rules) | Claude Code 风格声明式 allow/deny/ask 权限规则,带审计 |
224
+ | [dsh-auto-review](https://github.com/PerryLink/dsh-auto-review) | 审批链上的第二模型自动审查,默认 fail-closed |
225
+ | [dsh-memento](https://github.com/PerryLink/dsh-memento) | 带审批门的跨会话记忆:ctx.memory + SQLite + memory 工具 |
226
+ | [dsh-skill-pack-security](https://github.com/PerryLink/dsh-skill-pack-security) | 安全审计技能包:密钥扫描、依赖与供应链审查 |
227
+ | [dsh-session-pin](https://github.com/PerryLink/dsh-session-pin) | 在 Web 侧边栏置顶会话,持久排序 |
228
+ | [dsh-composer-history](https://github.com/PerryLink/dsh-composer-history) | Web 作曲器终端式输入历史:方向键、Ctrl+R 搜索 |
229
+ | [dsh-github](https://github.com/PerryLink/dsh-github) | DSH 的 GitHub PR/issue 集成,所有写操作经审批门 |
230
+ | [dsh-plugin-guide](https://github.com/PerryLink/dsh-plugin-guide) | 插件开发知识库,随 bundle 安装的按需 agent 技能 |
231
+ | [dsh-claude-move](https://github.com/PerryLink/dsh-claude-move) | 把 Claude Code 会话、记忆、技能和 CLAUDE.md 迁入 DSH |
232
+
196
233
  ## 📄 License
197
234
 
198
235
  [Apache-2.0](LICENSE) © 2026 dsh-output-styles contributors
@@ -0,0 +1,134 @@
1
+ # output.render.* — the dsh-output-styles renderer protocol
2
+
3
+ > 中文版见 [renderer-protocol.zh.md](renderer-protocol.zh.md)。Style switching stays fully
4
+ > compatible; this document covers the presentation layer added in 0.4.0.
5
+
6
+ The renderer protocol turns output presentation into an extension point: any
7
+ plugin can register a **renderer** — a pure function that maps raw
8
+ model-visible text to display text — and the harness-facing surfaces of this
9
+ plugin (`/export`, the `ctx.outputRenderers.renderText` service) apply them
10
+ through one auditable pipeline.
11
+
12
+ ## Renderer contract
13
+
14
+ ```ts
15
+ interface OutputRenderer {
16
+ id: string // kebab-case, unique in the registry
17
+ name: string // human-readable
18
+ description: string // one sentence
19
+ match: RendererMatch[] // [] = matches everything
20
+ priority: number // higher wins; ties break by registration order
21
+ presenter: (text: string, context: RenderContext) => string // PURE — no DOM, no I/O
22
+ }
23
+
24
+ interface RendererMatch {
25
+ tool?: string | string[] // tool names; '*' = any; omitted = any
26
+ contentType?: 'text' | 'markdown' | 'html' | ContentType[]
27
+ }
28
+
29
+ interface RenderContext {
30
+ tool: string // '' for assistant/user prose
31
+ contentType: ContentType
32
+ sessionId?: string
33
+ meta?: Record<string, string>
34
+ }
35
+ ```
36
+
37
+ Rules the registry enforces (fail-loud):
38
+
39
+ - Invalid renderers (bad id grammar, missing fields, non-function presenter,
40
+ non-finite priority) throw at registration and never enter the registry.
41
+ - Duplicate ids throw; `register()` returns a disposer that removes exactly
42
+ that registration — reversibility is the caller's `ctx.effect` job.
43
+
44
+ ## Render pipeline
45
+
46
+ ```text
47
+ renderText(text, context)
48
+ → output.render/before waterfall (listeners transform {text, context}, MUST next())
49
+ → rule table (first match by priority)
50
+ ├─ rule hit → the named renderer applies (explicit; no other renderer runs)
51
+ └─ no hit → every matching renderer applies in priority order (composition)
52
+ → { original, rendered, rendererId?, changed }
53
+ ```
54
+
55
+ - The waterfall listener contract is the ordinary Cordis waterfall semantics:
56
+ a listener that returns without calling `next()` short-circuits the whole
57
+ pipeline — do that only on purpose.
58
+ - A rule naming an unregistered renderer fails loudly at render time (the
59
+ registry can gain and lose renderers at runtime; silent fallback would
60
+ hide drift).
61
+
62
+ ## Built-in renderers
63
+
64
+ | id | Behavior |
65
+ | --- | --- |
66
+ | `concise` | Collapses whitespace runs and blank-line stacks, caps the presented text at a budget with a `[truncated]` marker. |
67
+ | `step-by-step` | Renumbers list items (dashes, bullets, or digits) consistently from 1; leaves prose untouched. |
68
+
69
+ Their ids mirror the two headline style names, so a rule like
70
+ `{ match: { tool: 'bash' }, style: 'concise' }` reads naturally.
71
+
72
+ ## Per-session / per-tool rules
73
+
74
+ ```yaml
75
+ # cordis.yml, under the dsh-output-styles row
76
+ config:
77
+ rules:
78
+ - match: { tool: bash }
79
+ style: concise
80
+ - match: { tool: read, contentType: text }
81
+ style: step-by-step
82
+ priority: 5
83
+ - match: { session: "session-id-here" }
84
+ style: step-by-step
85
+ ```
86
+
87
+ Matching is exact (no globs except `'*'` for any tool); `match.session`
88
+ scopes a rule to one session. Rules can also be edited in the settings UI
89
+ (`output-style-rules` namespace), where the same shape is validated at
90
+ write time.
91
+
92
+ ## Auditability
93
+
94
+ Presentation never destroys the source:
95
+
96
+ - every result object carries `original` beside `rendered`;
97
+ - the original text of an exported conversation is the session log itself —
98
+ `/export` projects it through the official `deriveEventMessage` surface
99
+ rule, the same rule the harness uses to build model requests;
100
+ - the render application is deterministic (same rules + same renderers in the
101
+ same order), so the rendered output and its source reconstruct together.
102
+
103
+ ## Worked third-party example
104
+
105
+ ```ts
106
+ // my-plugin/renderers.ts
107
+ export const tableCompactor = {
108
+ id: 'sql-table',
109
+ name: 'SQL table compactor',
110
+ description: 'Truncates oversized SQL result sets to the head plus a row count.',
111
+ match: [{ tool: 'sql', contentType: 'text' }],
112
+ priority: 20,
113
+ presenter: (text: string): string => {
114
+ const rows = text.split('\n')
115
+ if (rows.length <= 50) return text
116
+ return [...rows.slice(0, 50), `… ${rows.length - 50} more rows`].join('\n')
117
+ },
118
+ }
119
+
120
+ // my-plugin/index.ts
121
+ export function apply(ctx: Context): void {
122
+ const renderers = ctx.get('outputRenderers') // optional: dsh-output-styles may be absent
123
+ if (renderers !== undefined) {
124
+ ctx.effect(() => renderers.register(tableCompactor))
125
+ }
126
+ }
127
+ ```
128
+
129
+ ## Consuming the pipeline
130
+
131
+ ```ts
132
+ const result = await ctx.outputRenderers.renderText(rawText, { tool: 'sql', contentType: 'text' })
133
+ // { original, rendered, rendererId, changed } — log both halves wherever you surface it.
134
+ ```
@@ -0,0 +1,125 @@
1
+ # output.render.* —— dsh-output-styles 渲染器协议
2
+
3
+ > English version: [renderer-protocol.md](renderer-protocol.md)。`/style` 切换行为完全兼容;
4
+ > 本文档只覆盖 0.4.0 新增的呈现层。
5
+
6
+ 渲染器协议把输出呈现变成扩展点:任何插件都可以注册一个**渲染器**——把原始模型可见文本
7
+ 映射为展示文本的纯函数——本插件面向外部的表面(`/export`、`ctx.outputRenderers.renderText`
8
+ 服务)统一经一条可审计的流水线应用它们。
9
+
10
+ ## 渲染器契约
11
+
12
+ ```ts
13
+ interface OutputRenderer {
14
+ id: string // kebab-case,注册表内唯一
15
+ name: string // 人类可读名称
16
+ description: string // 一句话说明
17
+ match: RendererMatch[] // [] = 匹配一切
18
+ priority: number // 数值越大越优先;平局按注册顺序
19
+ presenter: (text: string, context: RenderContext) => string // 纯函数——无 DOM、无 I/O
20
+ }
21
+
22
+ interface RendererMatch {
23
+ tool?: string | string[] // 工具名;'*' = 任意;缺省 = 任意
24
+ contentType?: 'text' | 'markdown' | 'html' | ContentType[]
25
+ }
26
+
27
+ interface RenderContext {
28
+ tool: string // assistant/user 散文为 ''
29
+ contentType: ContentType
30
+ sessionId?: string
31
+ meta?: Record<string, string>
32
+ }
33
+ ```
34
+
35
+ 注册表强制执行的规则(失败大声):
36
+
37
+ - 非法渲染器(id 语法错、缺字段、presenter 非函数、priority 非有限数)在注册时抛错,
38
+ 绝不进入注册表。
39
+ - id 重复抛错;`register()` 返回 disposer,精确移除本次注册——可逆性由调用方的
40
+ `ctx.effect` 负责。
41
+
42
+ ## 渲染流水线
43
+
44
+ ```text
45
+ renderText(text, context)
46
+ → output.render/before waterfall (监听器转换 {text, context},必须 next())
47
+ → 规则表(按优先级取第一条命中)
48
+ ├─ 规则命中 → 只应用该规则指定的渲染器(显式,其余渲染器不参与)
49
+ └─ 未命中 → 按优先级依次应用所有匹配的渲染器(组合)
50
+ → { original, rendered, rendererId?, changed }
51
+ ```
52
+
53
+ - waterfall 监听器契约就是普通 Cordis waterfall 语义:不调用 `next()` 就返回会短路整条
54
+ 流水线——只在有意为之的时候这样做。
55
+ - 规则指向未注册渲染器时在渲染期响亮失败(注册表在运行期可增删渲染器;静默回退会掩盖漂移)。
56
+
57
+ ## 内置渲染器
58
+
59
+ | id | 行为 |
60
+ | --- | --- |
61
+ | `concise` | 折叠空白串与空行堆,在预算处截断并加 `[truncated]` 标记。 |
62
+ | `step-by-step` | 把列表项(短横线、圆点或数字)统一从 1 重新编号;散文保持原样。 |
63
+
64
+ 两个 id 与两大招牌风格同名,因此规则
65
+ `{ match: { tool: 'bash' }, style: 'concise' }` 读起来很自然。
66
+
67
+ ## 按会话 / 按工具规则
68
+
69
+ ```yaml
70
+ # cordis.yml,dsh-output-styles 行的 config 下
71
+ config:
72
+ rules:
73
+ - match: { tool: bash }
74
+ style: concise
75
+ - match: { tool: read, contentType: text }
76
+ style: step-by-step
77
+ priority: 5
78
+ - match: { session: "session-id-here" }
79
+ style: step-by-step
80
+ ```
81
+
82
+ 匹配是精确匹配(除 `'*'` 表示任意工具外没有通配符);`match.session` 把规则限定在一个会话。
83
+ 规则也可以在设置页编辑(`output-style-rules` 命名空间),同一形状在写入时校验。
84
+
85
+ ## 可审计性
86
+
87
+ 呈现绝不销毁来源:
88
+
89
+ - 每个结果对象在 `rendered` 旁边携带 `original`;
90
+ - 导出会话的原始文本就是会话日志本身——`/export` 经官方 `deriveEventMessage` surface
91
+ 规则投影它,与 harness 构建模型请求用的是同一条规则;
92
+ - 渲染应用是确定性的(同样的规则 + 同样顺序的渲染器),因此渲染输出与其来源总是一起重建。
93
+
94
+ ## 第三方示例(完整)
95
+
96
+ ```ts
97
+ // my-plugin/renderers.ts
98
+ export const tableCompactor = {
99
+ id: 'sql-table',
100
+ name: 'SQL table compactor',
101
+ description: 'Truncates oversized SQL result sets to the head plus a row count.',
102
+ match: [{ tool: 'sql', contentType: 'text' }],
103
+ priority: 20,
104
+ presenter: (text: string): string => {
105
+ const rows = text.split('\n')
106
+ if (rows.length <= 50) return text
107
+ return [...rows.slice(0, 50), `… ${rows.length - 50} more rows`].join('\n')
108
+ },
109
+ }
110
+
111
+ // my-plugin/index.ts
112
+ export function apply(ctx: Context): void {
113
+ const renderers = ctx.get('outputRenderers') // 可选依赖:dsh-output-styles 可能未挂载
114
+ if (renderers !== undefined) {
115
+ ctx.effect(() => renderers.register(tableCompactor))
116
+ }
117
+ }
118
+ ```
119
+
120
+ ## 消费流水线
121
+
122
+ ```ts
123
+ const result = await ctx.outputRenderers.renderText(rawText, { tool: 'sql', contentType: 'text' })
124
+ // { original, rendered, rendererId, changed } —— 在任何展示它的地方把两半都记下来。
125
+ ```