@jaxzhou/dsh-file-explorer 0.1.0 → 0.1.3

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/README.md CHANGED
@@ -5,14 +5,18 @@
5
5
 
6
6
  English | [中文](README.zh.md)
7
7
 
8
- > Published on npm as **`@jaxzhou/dsh-file-explorer`**. The unscoped names
9
- > `dsh-file-explorer`, `dsh-files` and `dsh-workspace-files` belong to other
10
- > authors' plugins, so this one carries its publisher's scope.
8
+ > Published on npm as **`@jaxzhou/dsh-file-explorer`** the unscoped name
9
+ > `dsh-file-explorer` belongs to a different author's plugin.
11
10
 
12
- A [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) plugin that
13
- adds a **Files** tab to the Conversation View strip the same level as **Chat**
14
- and **Trajectory** showing the session workspace as a tree, with a preview body
15
- chosen per file category beside it.
11
+ A **Files** tab for [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness),
12
+ beside **Chat** and **Trajectory**: the session workspace as a tree, and a preview
13
+ that adapts to what the file is rendered Markdown, a JSON tree, highlighted
14
+ source, an image, or plain text. Read-only, no configuration, nothing stored.
15
+
16
+ ```sh
17
+ dsh plugin --profile web add @jaxzhou/dsh-file-explorer
18
+ dsh --profile web
19
+ ```
16
20
 
17
21
  ## Demo
18
22
 
@@ -20,74 +24,53 @@ chosen per file category beside it.
20
24
  on the right a preview that highlights source, renders Markdown and JSON, and
21
25
  offers a Rendered/Source toggle](media/demo.gif)](media/demo.mp4)
22
26
 
23
- *30-second recording — click it for the full-quality MP4.* Walking a Flutter +
24
- NestJS workspace, it previews TypeScript, Swift, Dart, HTML, YAML and JavaScript
25
- source with syntax highlighting and line numbers, shows a `README.md` rendered
26
- and then as source through the **Rendered / Source** toggle, and ends on a
27
- `tsconfig.json` that explains itself before falling back to highlighted source —
28
- its trailing comma makes it JSONC, not strict JSON, so no tree can walk it.
29
-
30
- ## What it does
31
-
32
- - **Workspace tree.** The session's working directory, listed one level at a
33
- time, directories first, then files by natural name order. A level is fetched
34
- the first time it is expanded and kept while collapsed.
35
- - **A preview body per file category.**
36
-
37
- | Category | Suffixes | Body |
38
- |---|---|---|
39
- | Markdown | `md` `markdown` `mkd` `mdown` `mdwn` | Rendered GFM document — headings, tables, task lists, quotes, KaTeX math, footnotes, and syntax-highlighted code fences — with a **Source** toggle |
40
- | JSON | `json` `jsonc` `jsonl` `ndjson` `map` `webmanifest` | Collapsible tree with per-value copy, with a **Source** toggle; a file the tree cannot walk says so above its highlighted source — a syntax error, a truncated page, a bare scalar, or the JSONC that `tsconfig.json` and friends actually contain |
41
- | Source code | 24 grammars: TypeScript/JavaScript, shell, Python, Ruby, Go, Rust, Java, C, C++, C#, Kotlin, Swift, PHP, YAML, TOML, INI, HTML, CSS, SCSS, Less, SQL, XML, Lua, MDX | Syntax-highlighted, numbered, with a copy control |
42
- | Images | `png` `apng` `jpg` `jpeg` `jfif` `gif` `webp` `avif` `bmp` `ico` `svg` | Drawn, centred and scaled to the pane. An SVG draws through `<img>`, so its scripts never run |
43
- | Anything else | every other suffix | Numbered, copyable plain text |
44
-
45
- An unmapped suffix (`.vue`, `.proto`, `.txt`, …) is deliberately plain text
46
- rather than a guess: a wrong grammar colours a file misleadingly.
47
- - **Preview reading.** Text bodies read the file's leading 2 000 lines; an image
48
- reads its complete bytes. Long lines wrap at their spaces and keep every word
49
- whole; only a token with no break opportunity inside it — a long URL, one
50
- minified run — is split, because the alternative is hiding it behind a
51
- horizontal scrollbar. A wrapped continuation hangs under the text, not under
52
- the line number. The wrap toggle applies to source bodies, where it means
53
- something.
54
- - **State that survives.** Expansion and the selected file live in an exclusive
55
- per-session store, so switching to Chat and back — which unmounts the view —
56
- does not lose your place.
57
-
58
- Everything is read-only. The plugin browses and previews; it never writes,
59
- renames, or deletes.
60
-
61
- ## Compatibility
62
-
63
- Built and verified against **DeepSeek Harness `0.1.5-rc.2`** on the **Web**
64
- surface (`dsh web`, or a profile composed from `@deepseek-ai/dsh-base` +
65
- `@deepseek-ai/dsh-web-app`). It needs the Composition's
66
- `@deepseek-ai/dsh-api-workspace-files` row, which the shipped Web bundle
67
- mounts; a headless or SDK profile has no browser and gets no tab.
68
-
69
- The plugin itself declares no configuration fields, so nothing in `cordis.yml`
70
- needs setting.
27
+ *30 seconds — click for the full-quality MP4.*
71
28
 
72
- ## Install
29
+ ## What you get
73
30
 
74
- From npm:
31
+ The left pane is the session's working directory, listed one level at a time,
32
+ directories first. An expanded level stays expanded while you move between
33
+ preview tabs.
75
34
 
76
- ```sh
77
- dsh plugin --profile web add @jaxzhou/dsh-file-explorer
78
- dsh --profile web # restart the profile; bundle membership is a startup boundary
79
- ```
35
+ Clicking a file opens it in a tab, so several files stay open at once — each with
36
+ its own body and its own wrap setting. A file that is already open is focused
37
+ rather than reopened, tabs whose names collide show their directory, and the tab's
38
+ × or a middle click closes one.
39
+
40
+ The right pane picks each tab's body from the file:
80
41
 
81
- From this repository (a checkout or a git ref), which builds nothing — the
82
- runtime artifacts are committed:
42
+ | Category | Preview |
43
+ |---|---|
44
+ | **Markdown** | Rendered GFM — headings, tables, task lists, quotes, math, footnotes, highlighted code fences — with a **Source** toggle, and **Export PDF** |
45
+ | **HTML** | Drawn as a page in a sandboxed frame: the file's own CSS applies, and its scripts run in an opaque origin that cannot reach this application. With a **Source** toggle, and **Export PDF** |
46
+ | **JSON** | A collapsible tree with per-value copy, and the same toggle |
47
+ | **Source code** | Syntax highlighting, line numbers, and a copy button for 24 grammars: TypeScript/JavaScript, shell, Python, Ruby, Go, Rust, Java, C, C++, C#, Kotlin, Swift, PHP, YAML, TOML, INI, HTML, CSS, SCSS, Less, SQL, XML, Lua, MDX |
48
+ | **Images** | PNG, JPEG, GIF, WebP, AVIF, BMP, ICO and SVG, drawn to the pane. An SVG goes through `<img>`, so its scripts never run |
49
+ | **Anything else** | Numbered plain text — an unmapped suffix (`.vue`, `.proto`, `.txt`) stays plain rather than guessing a wrong grammar |
50
+
51
+ Text previews wrap at their spaces and keep every word whole; an image reads its
52
+ complete bytes. Every text body carries a **Copy** control in the pane's toolbar,
53
+ which copies the file's own text — a rendered Markdown document copies its
54
+ Markdown source. A rendered Markdown or HTML file also carries **Export PDF**,
55
+ which hands the page to the browser's print dialog: every browser offers "Save as
56
+ PDF" there, and its own layout engine keeps the text as text rather than
57
+ rasterising the page the way a canvas-based PDF library would.
58
+
59
+ ## Requirements
60
+
61
+ DeepSeek Harness **0.1.5-rc.2** on the **Web** surface — `dsh web`, or a profile
62
+ composed from `@deepseek-ai/dsh-base` + `@deepseek-ai/dsh-web-app`. A headless or
63
+ SDK profile has no browser and gets no tab. The plugin declares no configuration,
64
+ so nothing in `cordis.yml` needs setting.
65
+
66
+ ## Install
83
67
 
84
68
  ```sh
85
- dsh plugin --profile web add /path/to/dsh-file-explorer
86
- dsh plugin --profile web add github:jaxzhou/dsh-file-explorer
69
+ dsh plugin --profile web add @jaxzhou/dsh-file-explorer
70
+ dsh --profile web # bundle membership is read at startup
87
71
  ```
88
72
 
89
- For a custom profile that is not the shipped `web` one, create it from the Web
90
- template first so the browser composition exists:
73
+ A custom profile needs the Web composition first:
91
74
 
92
75
  ```sh
93
76
  dsh --profile myprofile --from-default-profile web
@@ -95,146 +78,76 @@ dsh plugin --profile myprofile add @jaxzhou/dsh-file-explorer
95
78
  dsh --profile myprofile
96
79
  ```
97
80
 
98
- Then open a session that has a workspace and click the **Files** tab. A session
99
- with no workspace shows the tab strip but the view says there is no workspace
100
- directory.
101
-
102
- ### Verify the install
81
+ Prefer a checkout or a git ref? Nothing needs building the runtime artifacts are
82
+ committed:
103
83
 
104
84
  ```sh
105
- dsh --profile web --dump-config | grep -A 2 'jaxzhou-file-explorer'
85
+ dsh plugin --profile web add /path/to/dsh-file-explorer
86
+ dsh plugin --profile web add github:jaxzhou/dsh-file-explorer
106
87
  ```
107
88
 
108
- The dump must show a `# == @jaxzhou/dsh-file-explorer` layer and a row named
109
- `@jaxzhou/dsh-file-explorer`. Then, in the running Web UI, a browser devtools
110
- network panel shows the plugin bundle inside one `/plugins/??…` combo response,
111
- and the page head carries a `<style data-dsh-file-explorer>` tag once the plugin
112
- materializes.
113
-
114
- ## Disable and uninstall
115
-
116
- - **Disable without uninstalling** — add a row override to the profile's
117
- `cordis.patch.yml` (applied after every bundle layer):
118
-
119
- ```yaml
120
- - id: jaxzhou-file-explorer
121
- disabled: true
122
- ```
123
-
124
- Removing that entry and saving brings the tab back: a `patchReload: live`
125
- profile re-applies the file without a restart.
126
-
127
- - **Uninstall** — `dsh plugin --profile web remove @jaxzhou/dsh-file-explorer`,
128
- then restart the profile.
129
-
130
- ## Data path and permissions
131
-
132
- - **Reads** go through the harness's own `workspaceFiles` Remote namespace:
133
- `list(sessionId, path)` for one directory level and
134
- `read(sessionId, path, { offset, limit })` for one text page. The Host's
135
- composed filesystem decides readability, so the plugin holds no file access,
136
- no path resolution, and no credentials of its own.
137
- - **Writes**: none. The Remote namespace exposes no mutation operation.
138
- - **Storage**: none. The plugin persists nothing — not to disk, not to the
139
- session log. All state is in-memory view state, discarded with the session
140
- binding.
141
- - **Host authority**: the Host half of the package is inert; it registers no
142
- service, tool, prompt section, or event.
143
-
144
- ## Development
89
+ Then open a session that has a workspace and click **Files**. To confirm the layer
90
+ landed:
145
91
 
146
92
  ```sh
147
- npm install
148
- npm run check # typecheck, build both runtime artifacts, run the tests
93
+ dsh --profile web --dump-config | grep -A 2 jaxzhou-file-explorer
149
94
  ```
150
95
 
151
- `lib/index.js` and `lib/client.js` are **committed build artifacts**: a git or
152
- tarball install loads them directly and needs no build step or `allowBuilds`
153
- permission.
96
+ ## Disable or uninstall
154
97
 
155
- The browser half must be a lazy CommonJS factory handoff, because the dsh Web
156
- loader fetches it as a classic script:
98
+ Disable the tab without uninstalling by adding an override to the profile's
99
+ `cordis.patch.yml` it is applied after every bundle layer, and a
100
+ `patchReload: live` profile picks it up without a restart:
157
101
 
158
- ```js
159
- window.__ModuleLoader__.load({ id: '@jaxzhou/dsh-file-explorer', factory: (require) => { … } })
102
+ ```yaml
103
+ - id: jaxzhou-file-explorer
104
+ disabled: true
160
105
  ```
161
106
 
162
- `scripts/build.mjs` emits that shape with esbuild and then verifies it. Only the
163
- shell's frozen module table stays external (`react`, `react/jsx-runtime`,
164
- `@deepseek-ai/dsh-client-store`, `@deepseek-ai/dsh-client-ui-primitives`, …);
165
- every other dsh package is imported type-only and erased, so no `require` in the
166
- artifact can miss the table.
167
-
168
- The rendered Markdown, the JSON tree, the syntax-highlighted code block, its
169
- numbered gutter, its copy control, and the shiki grammars all come from
170
- `@deepseek-ai/dsh-client-ui-primitives`, which the shell shares into that table.
171
- This plugin contributes the format decision and the pane, not a second
172
- renderer.
107
+ Uninstall with `dsh plugin --profile web remove @jaxzhou/dsh-file-explorer`, then
108
+ restart the profile.
173
109
 
174
- ### Layout
175
-
176
- | Path | Role |
177
- |---|---|
178
- | `src/index.ts` | Host half: an inert Loader module |
179
- | `src/client/index.ts` | Client plugin: stylesheet, dictionaries, and the `conversation.view` registration |
180
- | `src/client/FilesView.tsx` | The two-pane page: tree, headers, and the per-format preview bodies |
181
- | `src/client/format.ts` | Suffix → preview format, and the grammar/media-type tables |
182
- | `src/client/store.ts` | The exclusive per-session view store |
183
- | `src/client/face.ts` | The injected face: Remote listing, paged text reads, and complete-byte image reads |
184
- | `src/client/styles.ts` | The plugin-owned stylesheet |
185
- | `src/client/locales.ts` | `fileExplorer` dictionaries (zh, en) and the namespace declaration |
186
- | `cordis.patch.yml` | The bundle layer: one inserted row || `tests/contract.test.mjs` | Contract tests over the built artifacts |
187
-
188
- Each source focus is a separate module with its own header comment; start there
189
- for the reasoning behind a choice.
190
-
191
- ### Regenerating the demo
192
-
193
- The README embeds a GIF and links the MP4 beside it. GitHub renders a committed
194
- video only on its own file page, so the animation is what plays inline; the
195
- poster frame and the link are the same recording at full quality.
196
-
197
- ```sh
198
- SRC="screen recording.mov"
199
- # Full-quality MP4: half the capture's width, which is a 2x screenshot's worth.
200
- ffmpeg -i "$SRC" -vf scale=1680:-2:flags=lanczos -r 30 \
201
- -c:v libx264 -preset slow -crf 24 -pix_fmt yuv420p -movflags +faststart -an media/demo.mp4
202
- # Inline GIF: fewer frames and a smaller palette, which is where the bytes go.
203
- ffmpeg -i "$SRC" -vf "fps=12,scale=1200:-2:flags=lanczos,split[a][b];\
204
- [a]palettegen=max_colors=128:stats_mode=diff[p];\
205
- [b][p]paletteuse=dither=bayer:bayer_scale=3:diff_mode=rectangle" -loop 0 media/demo.gif
206
- ```
110
+ ## Privacy
207
111
 
208
- `media/` is documentation only: it is outside the package's `files` list, so it
209
- never ships in the npm tarball.
112
+ - **Read-only.** Reads go through the harness's own `workspaceFiles` Remote
113
+ namespace; the Host's filesystem decides what is readable. The plugin holds no
114
+ file access, no path resolution, and no credentials of its own.
115
+ - **Nothing stored.** No disk writes and nothing added to the session log; view
116
+ state lives in memory and is discarded with the session.
117
+ - **Inert Host half.** The package's Node side registers no service, tool, prompt
118
+ section, or event.
119
+ - **HTML runs sandboxed.** A previewed page's scripts execute in an opaque origin,
120
+ which cannot read this application's DOM, storage, or session; an exported page
121
+ is printed with its scripts removed. An SVG draws through `<img>`, so its scripts
122
+ never run at all.
210
123
 
211
124
  ## Known limitations
212
125
 
213
- - **Preview only, no editing.** The viewer reads; it does not write, save, or
214
- diff.
215
- - **JSON is parsed strictly.** A `tsconfig.json` with a trailing comma is JSONC,
216
- which TypeScript accepts and `JSON.parse` does not; the pane explains that and
217
- shows the highlighted source instead of a tree. Comments and trailing commas
218
- are not stripped to guess at a tree.
219
- - **Markdown renders without workspace vocabulary.** Relative image paths and
220
- file mentions inside a Markdown file stay inert — only absolute `http(s)`
221
- images loadbecause resolving them would need the reader to vouch for real
222
- files, which this pane does not.
223
- - **Text bodies are capped at the first 2 000 lines.** Binary and non-UTF-8
224
- files report `workspace-file/not-text`; files past the Host's complete-file
225
- cap report `workspace-file/too-large`; a longer text file shows its leading
226
- page with a truncation note and no "load more". An image is capped at the
227
- Host's complete-file limit instead.
228
- - **No highlighting for unmapped grammars.** The shared highlighter carries a
229
- fixed grammar set; a suffix outside it (`.vue`, `.proto`, `.graphql`, …) shows
230
- as plain text rather than an approximation.
231
- - **Listing only.** No search, filter, rename, context menu, current-file
232
- highlight, or filesystem watching. A level changes only through **Reload**.
233
- - **One root.** The tree is rooted at the session's working directory; paths
234
- above it are not browsable, and the Host refuses directory listings outside
235
- the workspace root anyway.
236
- - **No editing.** Monospaced, numbered source text with a wrapping option;
237
- and rendered prose for Markdown — no folding, no search, no in-place edit.
126
+ - **Preview only** no editing, saving, or diffing.
127
+ - **Strict JSON.** Comments or a trailing comma make a file JSONC, which
128
+ `JSON.parse` rejects `tsconfig.json` is the common case. The pane says so and
129
+ shows the highlighted source instead of guessing at a tree.
130
+ - **Text previews stop at 2 000 lines**, with a truncation note and no "load
131
+ more"; binary files report why they cannot be shown.
132
+ - **Fixed grammar set.** A suffix the shared highlighter does not carry is plain
133
+ text, never an approximation.
134
+ - **Listing only** no search, rename, context menu, or file watching; a level
135
+ refreshes through **Reload**.
136
+ - **Loaded previews are bounded.** A working set of five tabs keeps its content;
137
+ an older tab stays open and reads again when you return to it.
138
+ - **One root.** The tree is rooted at the session's working directory, and the
139
+ Host refuses directory listings outside the workspace root.
140
+ - **Markdown has no workspace vocabulary.** Relative image paths and file
141
+ mentions stay inert; only absolute `http(s)` images load.
142
+ - **HTML previews do not resolve relative assets.** A page drawn from a Blob
143
+ document has no base to resolve its own `style.css` or images against, so only
144
+ absolute URLs load there too. An export prints the page as it renders without
145
+ its scripts, which a static PDF has no use for anyway.
146
+
147
+ ## Contributing
148
+
149
+ Build, checks, the artifact model, and how the client bundle reaches the browser:
150
+ [CONTRIBUTING.md](CONTRIBUTING.md).
238
151
 
239
152
  ## License
240
153
 
package/README.zh.md CHANGED
@@ -5,77 +5,66 @@
5
5
 
6
6
  [English](README.md) | 中文
7
7
 
8
- > npm 上的包名是 **`@jaxzhou/dsh-file-explorer`**。无作用域的
9
- > `dsh-file-explorer`、`dsh-files`、`dsh-workspace-files` 已被其他作者的插件占
10
- > 用,因此本插件带上发布者作用域。
8
+ > npm 包名是 **`@jaxzhou/dsh-file-explorer`** —— 无作用域的 `dsh-file-explorer`
9
+ > 属于另一位作者的插件。
11
10
 
12
- 一个 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件:
13
- 在对话视图的标签栏中新增 **文件** 标签,与 **对话**、**轨迹** 同级,左侧是会话
14
- 工作区的目录树,右侧是按文件类别选择的预览体。
11
+ [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 增加一个
12
+ **文件** 标签,与 **对话**、**轨迹** 并列:左侧是会话工作区的目录树,右侧的预览
13
+ 会按文件本身决定形态 —— 渲染后的 Markdown、JSON 树、语法高亮源码、图片,或纯
14
+ 文本。只读、无需配置、不落任何数据。
15
+
16
+ ```sh
17
+ dsh plugin --profile web add @jaxzhou/dsh-file-explorer
18
+ dsh --profile web
19
+ ```
15
20
 
16
21
  ## 演示
17
22
 
18
23
  [![文件标签与对话、轨迹并列:左侧是工作区目录树,右侧是预览——可高亮源码、渲染
19
24
  Markdown 与 JSON,并提供「预览/源码」切换](media/demo.gif)](media/demo.mp4)
20
25
 
21
- *30 秒录屏 —— 点击可打开完整画质的 MP4。* 录屏浏览了一个 Flutter + NestJS 工作
22
- 区,依次预览 TypeScript、Swift、Dart、HTML、YAML、JavaScript 源码(带语法高亮与
23
- 行号),用 **预览 / 源码** 切换展示同一个 `README.md` 的渲染结果与原文,最后停在
24
- 一个会先自我解释、再回退到高亮源码的 `tsconfig.json` 上——它尾部的逗号让它成为
25
- JSONC 而非严格 JSON,所以无法展开成树。
26
-
27
- ## 功能
26
+ *30 秒录屏 —— 点击可打开完整画质的 MP4。*
28
27
 
29
- - **工作区目录树**:以会话的工作目录为根,逐层读取;目录在前,文件按自然名
30
- 称排序。某一层在首次展开时读取,折叠后仍保留内容。
31
- - **按文件类别选择合适的预览体**:
28
+ ## 能做什么
32
29
 
33
- | 类别 | 后缀 | 显示 |
34
- |---|---|---|
35
- | Markdown | `md` `markdown` `mkd` `mdown` `mdwn` | 直接渲染为 GFM 文档:标题、表格、任务列表、引用、KaTeX 公式、脚注,代码围栏带语法高亮;带 **源码** 切换 |
36
- | JSON | `json` `jsonc` `jsonl` `ndjson` `map` `webmanifest` | 可折叠树,每个值可单独复制;同样带 **源码** 切换。无法展开的文件会在高亮源码上方说明原因——语法错误、被截断、纯标量,或 `tsconfig.json` 这类文件里实际存在的 JSONC |
37
- | 源码 | 24 种语法:TypeScript/JavaScript、shell、Python、Ruby、Go、Rust、Java、C、C++、C#、Kotlin、Swift、PHP、YAML、TOML、INI、HTML、CSS、SCSS、Less、SQL、XML、Lua、MDX | 语法高亮 + 行号 + 复制按钮 |
38
- | 图片 | `png` `apng` `jpg` `jpeg` `jfif` `gif` `webp` `avif` `bmp` `ico` `svg` | 直接绘制,居中并在超出时缩放到窗格宽度。SVG 通过 `<img>` 绘制,其中的脚本不会执行 |
39
- | 其它 | 其余全部后缀 | 带行号、可复制的纯文本 |
30
+ 左栏是会话的工作目录,逐层展开,目录在前;展开过的层级在切换预览标签后仍然保持。
40
31
 
41
- 未映射的语法(`.vue`、`.proto`、`.txt` 等)故意按纯文本显示,而不是猜测:错
42
- 误的语法着色比不着色更容易误导。
43
- - **预览读取**:文本类预览只读取前 2 000 行,图片类读取完整字节。超长行在空
44
- 格处折行,每个单词保持完整;只有内部没有任何断点的 token(超长 URL、压缩后的
45
- 一整段代码)才会被拆开,因为否则它只能被藏在横向滚动条之外。折行后的续行悬挂
46
- 在正文下方,而不是行号下方。换行开关只在源码视图出现(那里才有意义)。
47
- - **状态保留**:展开状态与当前选中文件保存在按会话独占的 store 中,因此切到
48
- 「对话」再切回来(组件会卸载)不会丢失浏览位置。
32
+ 点击文件会在标签中打开它,因此可以同时打开多个文件——每个标签有自己的预览体、
33
+ 自己的换行设置。已打开的文件会被聚焦而不是重复打开;同名的标签会带上所在目录;
34
+ 点标签上的 × 或中键点击即可关闭。
49
35
 
50
- 全部为只读:插件只浏览与预览,不会写入、重命名或删除任何文件。
36
+ 右栏按文件类别选择每个标签的预览体:
51
37
 
52
- ## 兼容性
53
-
54
- 针对 **DeepSeek Harness `0.1.5-rc.2`** **Web** 界面(`dsh web`,或由
55
- `@deepseek-ai/dsh-base` + `@deepseek-ai/dsh-web-app` 组合出的 profile)开发并
56
- 验证。插件依赖组合中的 `@deepseek-ai/dsh-api-workspace-files` 行,官方 Web
57
- bundle 已挂载;headless / SDK 等无浏览器界面不会出现该标签。
58
-
59
- 插件本身没有配置项,`cordis.yml` 无需任何设置。
38
+ | 类别 | 预览 |
39
+ |---|---|
40
+ | **Markdown** | 直接渲染为 GFM —— 标题、表格、任务列表、引用、公式、脚注、高亮的代码围栏 —— 并带 **源码** 切换与 **导出 PDF** |
41
+ | **HTML** | 在沙箱 iframe 中当作页面绘制:文件自己的 CSS 生效,其脚本运行在不透明源(opaque origin)里,无法触达本应用。带 **源码** 切换与 **导出 PDF** |
42
+ | **JSON** | 可折叠树,每个值可单独复制,同样带 **源码** 切换 |
43
+ | **源码** | 24 种语法的语法高亮、行号与复制按钮:TypeScript/JavaScript、shell、Python、Ruby、Go、Rust、Java、C、C++、C#、Kotlin、Swift、PHP、YAML、TOML、INI、HTML、CSS、SCSS、Less、SQL、XML、Lua、MDX |
44
+ | **图片** | PNG、JPEG、GIF、WebP、AVIF、BMP、ICO、SVG,自动适配窗格。SVG 经 `<img>` 绘制,其中的脚本不会执行 |
45
+ | **其它** | 带行号的纯文本 —— 未映射的后缀(`.vue`、`.proto`、`.txt`)保持纯文本,而不是猜测一个错误的高亮 |
46
+
47
+ 文本预览在空格处折行、保持单词完整;图片读取完整字节。每一种文本预览在窗格工具栏
48
+ 里都有 **复制** 按钮,复制的是文件自身的文本——渲染态的 Markdown 复制的是它的
49
+ Markdown 源码。渲染态的 Markdown 与 HTML 还带 **导出 PDF**:它把页面交给浏览器的
50
+ 打印对话框,那里每个浏览器都提供「另存为 PDF」,而排版由浏览器自己的引擎完成
51
+ ——文字仍是文字,不像基于 canvas 的 PDF 库那样把整页栅格化。
52
+
53
+ ## 环境要求
54
+
55
+ DeepSeek Harness **0.1.5-rc.2** 的 **Web** 界面 —— `dsh web`,或由
56
+ `@deepseek-ai/dsh-base` + `@deepseek-ai/dsh-web-app` 组合出的 profile。headless
57
+ 或 SDK profile 没有浏览器,不会出现该标签。插件没有配置项,`cordis.yml` 无需
58
+ 任何设置。
60
59
 
61
60
  ## 安装
62
61
 
63
- 从 npm 安装:
64
-
65
62
  ```sh
66
63
  dsh plugin --profile web add @jaxzhou/dsh-file-explorer
67
- dsh --profile web # 重启 profile;bundle 成员在启动时生效
64
+ dsh --profile web # bundle 成员在启动时读取
68
65
  ```
69
66
 
70
- 从本仓库安装(本地检出或 git ref),均无需构建——运行产物已提交:
71
-
72
- ```sh
73
- dsh plugin --profile web add /path/to/dsh-file-explorer
74
- dsh plugin --profile web add github:jaxzhou/dsh-file-explorer
75
- ```
76
-
77
- 如果使用自定义 profile(不是官方的 `web`),先基于 Web 模板创建,以保证浏览器
78
- 组合存在:
67
+ 自定义 profile 需要先具备 Web 组合:
79
68
 
80
69
  ```sh
81
70
  dsh --profile myprofile --from-default-profile web
@@ -83,129 +72,66 @@ dsh plugin --profile myprofile add @jaxzhou/dsh-file-explorer
83
72
  dsh --profile myprofile
84
73
  ```
85
74
 
86
- 打开一个已有工作区的会话,点击 **文件** 标签即可。若会话没有工作区目录,标签仍
87
- 会显示,但页面会提示没有工作区目录。
88
-
89
- ### 验证安装
75
+ 偏好本地检出或 git ref?无需构建 —— 运行产物已提交:
90
76
 
91
77
  ```sh
92
- dsh --profile web --dump-config | grep -A 2 'jaxzhou-file-explorer'
78
+ dsh plugin --profile web add /path/to/dsh-file-explorer
79
+ dsh plugin --profile web add github:jaxzhou/dsh-file-explorer
93
80
  ```
94
81
 
95
- 输出中应出现 `# == @jaxzhou/dsh-file-explorer` 层,以及名为
96
- `@jaxzhou/dsh-file-explorer` 的行。随后在运行中的 Web 界面里:开发者工具的网络
97
- 面板中,插件 bundle 位于某个 `/plugins/??…` 合并响应内;插件加载后页面 head 中会
98
- 出现 `<style data-dsh-file-explorer>` 标签。
99
-
100
- ## 停用与卸载
101
-
102
- - **临时停用**:在 profile 的 `cordis.patch.yml`(在所有 bundle 层之后应用)中
103
- 添加行覆盖:
104
-
105
- ```yaml
106
- - id: jaxzhou-file-explorer
107
- disabled: true
108
- ```
109
-
110
- 删掉该条目并保存即可恢复;`patchReload: live` 的 profile 无需重启。
111
-
112
- - **卸载**:`dsh plugin --profile web remove @jaxzhou/dsh-file-explorer`,然后重启
113
- profile。
114
-
115
- ## 数据路径与权限
116
-
117
- - **读取**走 Harness 自带的 `workspaceFiles` Remote 命名空间:
118
- `list(sessionId, path)` 读取一层目录,`read(sessionId, path, { offset, limit })`
119
- 读取一页文本。是否可读由 Host 组合出的文件系统决定,插件自身不持有文件权限、
120
- 不做路径解析、不接触任何凭据。
121
- - **写入**:没有。该 Remote 命名空间不提供任何写操作。
122
- - **存储**:没有。插件不落盘、不写会话日志;所有状态都是内存中的视图状态,
123
- 随会话绑定一起释放。
124
- - **Host 权限**:包的 Host 半边是空实现,不注册服务、工具、提示词片段或事件。
125
-
126
- ## 开发
82
+ 然后打开一个已有工作区的会话,点击 **文件** 标签。确认层已生效:
127
83
 
128
84
  ```sh
129
- npm install
130
- npm run check # 类型检查 + 构建两个运行产物 + 运行测试
85
+ dsh --profile web --dump-config | grep -A 2 jaxzhou-file-explorer
131
86
  ```
132
87
 
133
- `lib/index.js` 与 `lib/client.js` 是**提交进仓库的构建产物**:通过 git 或
134
- tarball 安装时直接加载,不需要构建步骤,也不需要 `allowBuilds` 授权。
88
+ ## 停用与卸载
135
89
 
136
- 浏览器半边必须是由模块加载器接收的惰性 CommonJS 工厂,因为 dsh Web 加载器以
137
- 经典脚本方式抓取它:
90
+ 不用卸载也能关掉这个标签:在 profile `cordis.patch.yml` 里加一条行覆盖
91
+ (它在所有 bundle 层之后应用;`patchReload: live` 的 profile 无需重启即可生效):
138
92
 
139
- ```js
140
- window.__ModuleLoader__.load({ id: '@jaxzhou/dsh-file-explorer', factory: (require) => { … } })
93
+ ```yaml
94
+ - id: jaxzhou-file-explorer
95
+ disabled: true
141
96
  ```
142
97
 
143
- `scripts/build.mjs` esbuild 产出该形式并随后校验。只有 shell 冻结的模块表保持
144
- 外部依赖(`react`、`react/jsx-runtime`、`@deepseek-ai/dsh-client-store`、
145
- `@deepseek-ai/dsh-client-ui-primitives` 等);其余 dsh 包都是 type-only 导入,
146
- 编译时即被擦除,因此产物中不会出现模块表无法回答的 `require`。
98
+ 卸载:`dsh plugin --profile web remove @jaxzhou/dsh-file-explorer`,然后重启
99
+ profile。
147
100
 
148
- Markdown 渲染、JSON 树、语法高亮代码块及其行号与复制控件、以及 shiki 语法本身,
149
- 全部来自 `@deepseek-ai/dsh-client-ui-primitives`——shell 已把它共享进模块表。本插
150
- 件贡献的是格式判定与窗格本身,而不是第二套渲染器。
151
-
152
- ### 目录结构
153
-
154
- | 路径 | 作用 |
155
- |---|---|
156
- | `src/index.ts` | Host 半边:空的 Loader 模块 |
157
- | `src/client/index.ts` | 客户端插件:样式表、字典与 `conversation.view` 注册 |
158
- | `src/client/FilesView.tsx` | 双栏页面:目录树、两处表头,以及按格式分发的预览体 |
159
- | `src/client/format.ts` | 后缀 → 预览格式,以及语法/媒体类型表 |
160
- | `src/client/store.ts` | 按会话独占的视图 store |
161
- | `src/client/face.ts` | 注入面:Remote 列目录、分页读文本、整块读图片字节 |
162
- | `src/client/styles.ts` | 插件自有样式表 |
163
- | `src/client/locales.ts` | `fileExplorer` 字典(zh、en)与命名空间声明 |
164
- | `cordis.patch.yml` | bundle 层:插入一行 |
165
- | `tests/contract.test.mjs` | 针对构建产物的契约测试 |
166
-
167
- 每个源文件都是一个独立模块,文件头注释说明了其中的取舍。
168
-
169
- ### 重新生成演示
170
-
171
- README 内嵌一个 GIF、并链接同一段录屏的 MP4。GitHub 只有在视频文件自己的页面上
172
- 才会播放提交进仓库的视频,所以内联动起来的是 GIF;而首帧与链接指向的是同一段
173
- 完整画质的录像。
174
-
175
- ```sh
176
- SRC="屏幕录制.mov"
177
- # 完整画质 MP4:取录制宽度的一半,相当于 2x 截图的清晰度。
178
- ffmpeg -i "$SRC" -vf scale=1680:-2:flags=lanczos -r 30 \
179
- -c:v libx264 -preset slow -crf 24 -pix_fmt yuv420p -movflags +faststart -an media/demo.mp4
180
- # 内联 GIF:降帧率、缩调色板,体积主要就省在这里。
181
- ffmpeg -i "$SRC" -vf "fps=12,scale=1200:-2:flags=lanczos,split[a][b];\
182
- [a]palettegen=max_colors=128:stats_mode=diff[p];\
183
- [b][p]paletteuse=dither=bayer:bayer_scale=3:diff_mode=rectangle" -loop 0 media/demo.gif
184
- ```
101
+ ## 隐私
185
102
 
186
- `media/` 只用于文档:不在包的 `files` 清单里,因此永远不会进入 npm 包。
103
+ - **只读**:读取走 Harness 自带的 `workspaceFiles` Remote 命名空间,可读性由
104
+ Host 的文件系统决定。插件自身不持有文件权限、不做路径解析、不接触凭据。
105
+ - **不落数据**:不写磁盘、不写会话日志;视图状态保存在内存中,随会话一起释放。
106
+ - **Host 半边是空实现**:包的 Node 侧不注册任何服务、工具、提示词片段或事件。
107
+ - **HTML 在沙箱中运行**:预览页面的脚本执行于不透明源,无法读取本应用的 DOM、
108
+ 存储或会话;导出时会先剥除脚本再打印。SVG 经 `<img>` 绘制,脚本完全不执行。
187
109
 
188
110
  ## 已知限制
189
111
 
190
- - **只预览,不编辑**:不写入、不保存、不做 diff。
191
- - **JSON 按严格语法解析**:带尾逗号的 `tsconfig.json` 属于 JSONC——TypeScript 接
192
- 受、`JSON.parse` 不接受。此时窗格会说明原因并显示高亮源码,而不是勉强猜出一棵
193
- 树;注释与尾逗号不会被静默剥除。
194
- - **Markdown 不解析工作区词汇**:Markdown 里的相对图片路径与文件提及保持原样——
195
- 只有绝对 `http(s)` 图片会加载——因为解析它们需要阅读器为真实文件背书,而这个
196
- 窗格不做这件事。
197
- - **文本预览上限为前 2 000 行**:二进制或非 UTF-8 文件返回
198
- `workspace-file/not-text`;超过 Host 完整文件上限的返回
199
- `workspace-file/too-large`;更长的文本文件只展示首页并提示已截断,没有
200
- 「加载更多」。图片受 Host 的完整文件上限约束。
201
- - **未映射语法不高亮**:共享高亮器携带固定的语法集,集外后缀(`.vue`、
202
- `.proto`、`.graphql` 等)按纯文本显示,而不是近似高亮。
203
- - **仅目录列表**:没有搜索、过滤、重命名、右键菜单、当前文件高亮,也不监听文件
204
- 系统变化;某一层只通过 **重新读取** 更新。
205
- - **单一根目录**:目录树以会话工作目录为根,不能向上浏览;Host 本身也拒绝读取
206
- 工作区根之外的目录列表。
207
- - **不做编辑**:带行号与换行开关的等宽源码视图,Markdown 则为排版后的文档——
208
- 没有折叠、搜索和就地编辑。
112
+ - **只预览** —— 不编辑、不保存、不做 diff。
113
+ - **JSON 按严格语法解析**:注释或尾逗号会让文件成为 JSONC,`JSON.parse` 会拒绝
114
+ —— `tsconfig.json` 是最常见的情况。此时窗格会说明原因并显示高亮源码,而不是
115
+ 勉强猜出一棵树。
116
+ - **文本预览上限 2 000 行**,并提示已截断、没有「加载更多」;二进制文件会说明
117
+ 为何无法显示。
118
+ - **固定的语法集**:共享高亮器未包含的后缀按纯文本显示,绝不做近似高亮。
119
+ - **仅目录列表** —— 没有搜索、重命名、右键菜单,也不监听文件变化;某一层通过
120
+ **重新读取** 更新。
121
+ - **已加载的预览有上限**:最近使用的 5 个标签保留内容;更早的标签仍然开着,只是
122
+ 回到它时会重新读取。
123
+ - **单一根目录**:目录树以会话工作目录为根,Host 本身也拒绝读取工作区根之外的
124
+ 目录列表。
125
+ - **Markdown 不解析工作区词汇**:相对图片路径与文件提及保持原样,只有绝对
126
+ `http(s)` 图片会加载。
127
+ - **HTML 预览不解析相对资源**:由 Blob 文档绘制出的页面没有可用来解析自身
128
+ `style.css` 或图片的 base,所以同样只有绝对 URL 能加载。导出时按渲染结果打印,
129
+ 且不带脚本——静态 PDF 本来也用不上脚本。
130
+
131
+ ## 参与开发
132
+
133
+ 构建、检查、产物模型,以及客户端 bundle 如何抵达浏览器:
134
+ [CONTRIBUTING.md](CONTRIBUTING.md)。
209
135
 
210
136
  ## 许可证
211
137