@weniatall/dsh-file-viewer 0.0.0-stage → 0.2.6

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
@@ -1,3 +1,157 @@
1
- # Temporary Holding Version
1
+ # dsh-file-viewer
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 在 DeepSeek Harness 里查看工作区文件:左侧目录树,右侧按格式渲染内容。
4
+
5
+ 支持的格式:
6
+
7
+ | 格式 | 渲染方式 |
8
+ | --- | --- |
9
+ | 代码、配置、纯文本 | 语法高亮(复用 DSH 内置 shiki,26 种语言),带行号,按 500 行分页 |
10
+ | Markdown | 预览(GFM + 公式)与源码切换 |
11
+ | JSON | 可展开的结构树与源码切换 |
12
+ | 图片 | PNG、JPEG、GIF、WebP、AVIF、BMP、ICO、SVG |
13
+ | PDF | 交给浏览器内置阅读器(可搜索、翻页、缩放) |
14
+ | Excel | `.xlsx` / `.xlsm` / `.xls`,工作表标签 + 表格 |
15
+ | Word | `.docx` / `.doc`,转 Markdown / 提取文本后渲染 |
16
+ | 其他二进制 | 显示文件信息,可用本地程序打开 |
17
+
18
+ ## 安装
19
+
20
+ ```bash
21
+ dsh plugin --profile web add <release-tgz-url>
22
+ ```
23
+
24
+ ### 重启生效
25
+
26
+ `@deepseek-ai/dsh` 官方 CLI 本身没有 `service` 子命令,执行 `dsh service restart` 会报错。请使用本仓库自带的控制脚本:
27
+
28
+ - **macOS / Linux**:
29
+ ```bash
30
+ ./scripts/dsh-web.sh restart
31
+ ```
32
+ - **Windows**:
33
+ ```powershell
34
+ .\scripts\dsh-web.ps1 restart
35
+ # 或 cmd:
36
+ scripts\dsh-web.cmd restart
37
+ ```
38
+
39
+ 装完确认 profile 的 `dsh.profile.bundles` 里有 `dsh-file-viewer`,否则插件不会加载。
40
+
41
+ ## 使用
42
+
43
+ - **唤起入口**:
44
+ - **PC 端输入框工具栏**:严禁悬浮按钮;文件查看器按钮干净聚焦地内嵌于输入框操作条左侧(与快捷命令 `+` 号、权限模式标签并列),在**新建空白会话**(居中的 Hero 输入框)与**常规对话**(底部常驻输入框)全场景始终清晰可见且不遮挡内容;
45
+ - **移动端原生集成**:严禁悬浮按钮;手机端通过底部工作区工具箱菜单(四宫格图标 -> “📁 文件查看器”)或顶部适配栏原生唤出;
46
+ - **会话中点击文件**:在会话回复(变更文件卡片、产物交付卡片、行内代码引用等)中点击任何文件路径,自动唤起文件查看抽屉并直接选中预览该文件。
47
+ - `Esc` 或点击抽屉外部遮罩关闭。
48
+
49
+ 顶部可切换工作区、刷新、显示或隐藏点文件。目录在树中原地展开,子级首次展开时才加载。
50
+
51
+ 文件树支持**右键上下文菜单**与单项 `@` 快捷按钮:
52
+ - **复制相对路径**:复制文件/目录相对于工作区根目录的路径;
53
+ - **复制绝对路径**:复制文件/目录在操作系统中的完整物理路径;
54
+ - **引用到输入框 (@)**:将文件/目录以 `@path` 格式插入对话输入框。**支持在输入框当前光标处精确追加**,并带有自适应空格规整;绝不覆盖或冲掉输入框中用户已输入的已有内容;
55
+ - **文件下载到本地**:
56
+ - **顶部工具栏**:选中文件后,标题栏常驻提供 `⬇️` 下载图标按钮,一键将当前预览文件另存到本地;
57
+ - **右键上下文菜单**:在文件树右键点击任意文件,选择 `⬇️ 下载文件` 即可直接提取;
58
+ - **二进制/超大文件兜底区**:对于无法在线渲染的二进制文件或大文件,提示卡片内提供“下载到本地”按钮,完美解决 Web/远程模式下无法提取文件的痛点。
59
+
60
+ 手机端抽屉占满全屏,文件列表与内容单列切换(左上角 `‹` 返回列表)。
61
+
62
+ ## 访问边界与安全访问路径
63
+
64
+ 插件**内置可访问**所有**已注册的工作区目录**以及**各会话的工作目录**。
65
+
66
+ 如需让文件查看器访问工作区以外的目录或磁盘,可通过配置**安全访问路径**(`safePaths`)放开指定路径的访问权限:
67
+
68
+ ```yaml
69
+ - id: file-viewer
70
+ name: dsh-file-viewer
71
+ config:
72
+ # 安全访问路径列表(支持字符串路径,或带展示标签的对象)
73
+ safePaths:
74
+ - /opt/data
75
+ - /var/log
76
+ # 支持自定义标签:
77
+ # - path: /data/share
78
+ # label: 共享数据盘
79
+ # Windows 示例:
80
+ # - D:/Notes
81
+ maxBytes: 20971520
82
+ ```
83
+
84
+ 配置后:
85
+ - 在文件查看器顶部的工作区下拉框中,将分组呈现「工作区」与「安全访问路径」,可直接切换浏览;
86
+ - 在对话回复或工具调用中点击属于安全访问路径内的文件路径时,文件查看器会自动定位并直接打开预览;
87
+ - 每次请求都会先规范化路径,再用 `ctx.fs.contains` 严格校验目标落在工作区或已配置的安全访问路径之内,严防 `..` 目录穿越与符号链接逃逸;
88
+ - 为保持向后兼容,原有的 `extraRoots` 与 `safeAccessPaths` 别名依然有效并会自动合并;
89
+ - **Web 界面动态配置与双重持久化**:可在输入框工具栏文件查看器入口或文件树顶部的「安全访问路径设置」弹窗中直接添加、查看或删除安全路径,保存后会自动双重持久化写入当前 Profile 配置(`cordis.patch.yml`)与本地文件(`~/.dsh/file-viewer/settings.json`),DSH 服务重启后配置稳定保留不丢失。
90
+
91
+ 插件不注册任何 HTTP 路由,全部通过 `trusted-host` 权限的 RPC 通道通信,从而复用 DSH `/api` 的浏览器信任围栏(Host/Origin 校验、DNS rebinding 防护)。二进制文件经 RPC 取回后在浏览器侧生成 blob URL,默认单文件上限 20 MB。
92
+
93
+ ## 未知后缀文件的文本识别
94
+
95
+ 后缀无法识别时(如 `config.yaml.prod`、`env.dev`、`env.staging`),宿主端不会直接判为二进制,而是**读取文件开头的 8 KB 采样窗口**做内容判断:
96
+
97
+ - 采样中**没有 NUL 字节**(与 git、`file(1)` 相同的启发式);
98
+ - 且采样能**按 UTF-8 解码**(采样末尾被截断的多字节字符会被保留,不算错误)。
99
+
100
+ 两者同时满足即按文本渲染。这样做有两个要点:
101
+
102
+ - 采样走 `ctx.fs.readByteRange`,**以窗口为界**而非以整个文件为界,因此 20 MB 的未知后缀文本文件也能正确识别,且宿主不会整体缓冲该文件;
103
+ - 若只判断 NUL 字节,GBK 等非 UTF-8 编码的文件会被误判为文本,然后在读取阶段失败;因此这里一并校验 UTF-8,识别不出时诚实报「不是 UTF-8 文本」而不是「读取失败,可能没有访问权限」。
104
+
105
+ ## 语言格式识别与手动指定
106
+
107
+ 后缀认不出时,插件不会止步于「按纯文本显示」:它会**按内容猜一次语言**,并把最终决定权交给用户。
108
+
109
+ ### 语言是怎么定的
110
+
111
+ 按以下优先级决定,先命中者胜出:
112
+
113
+ | 优先级 | 来源 | 说明 |
114
+ | --- | --- | --- |
115
+ | 1 | **用户指定** | 在下拉框里手动选定,记在本地,之后打开这个文件一直生效 |
116
+ | 2 | **文件后缀** | 已知后缀(`.py`、`.yaml`、`.go` …)比内容启发式更可靠 |
117
+ | 3 | **文件内容** | 后缀认不出时按内容判断 |
118
+ | 4 | 无 | 都不成立则按纯文本显示,不做高亮 |
119
+
120
+ ### 内容识别的依据
121
+
122
+ 只认**有明确特征**的内容,宁可不高亮也不猜错——猜错会让高亮落在语法并不期望的记号上,把文件染成误导性的颜色。识别规则按特异性从强到弱:
123
+
124
+ - **Shebang**:`#!/bin/bash`、`#!/usr/bin/env python3`、`#!/usr/bin/env node` 等,解释器直接写在第一行,最可靠;
125
+ - **精确首标记**:`<?xml`、`<!DOCTYPE html`、`\documentclass`、`syntax = "proto3";`、`resource "…" {`(Terraform/HCL)、`query Foo {`(GraphQL);
126
+ - **JSON**:必须**真的能解析**才算,截断的采样不算证据(所以被截断的 JSON 不会被误判成 CSV);
127
+ - **YAML / INI / CSV / Dockerfile**:要求结构证据——YAML 需要多数行是 `key: value` 且不出现 `=` 赋值;INI 需要至少一个 `[section]` 或三条以上赋值;CSV 需要两行以上且**列数一致**(单行含逗号太常见,不足以判定);Dockerfile 需要以 `FROM` 开头且其余行都是合法指令。
128
+
129
+ ### 手动指定
130
+
131
+ 打开文件后,标题栏右侧会出现语言下拉框,**自动**为默认项:
132
+
133
+ - 选任意语言即固定高亮,选择**按文件路径**记忆(不是按后缀),所以同一个 `.prod` 后缀下不同文件可以各自不同;
134
+ - 选**纯文本**可关掉高亮;
135
+ - 选回**自动**即清除记忆,回到上面的自动判定;
136
+ - 下拉框旁的提示会说明当前语言是**按后缀识别**、**按内容识别**还是**未识别**,让你能分辨「这是猜的」还是「这是确定的」。
137
+
138
+ 下拉框的选项来自宿主端(`languages` RPC),与宿主校验用的同一份目录,因此界面不可能给出宿主不接受的选项;提交的指定也会在宿主端重新校验,未知 id 一律忽略并回落到自动判定。
139
+
140
+ ## 已知限制
141
+
142
+ - 文件列表不显示修改时间:`ctx.fs` 的目录项只提供名称、类型与大小。
143
+ - 工作区需要手动选择:宿主端不存在"当前活跃工作区"的概念,该状态只在前端 UI 中。
144
+ - 大表格截断到 2000 行 × 64 列。
145
+ - Word 转换保留标题、强调、列表与表格,复杂排版与图片会降级。
146
+ - 文本识别只看开头 8 KB:文件开头是 UTF-8 文本、之后才出现非 UTF-8 字节时,仍会在读取阶段报「不是 UTF-8 文本」。
147
+ - 文件树中的图标按名称判断(只有图片后缀显示图片图标),实际能否预览以打开后的元数据为准。
148
+ - 内容识别也只看开头 8 KB(`meta` 阶段);打开文件时 `read` 已持有全文,会用全文再判一次,因此特征出现在 8 KB 之后的文件在打开后仍能正确高亮。
149
+ - 语言指定只记在本机浏览器(localStorage),不跨设备同步;上限 200 条,超出后淘汰最旧的。
150
+ - 语言下拉框只对源码类视图(文本 / Markdown / JSON)显示;图片、表格、文档没有语法可指定。
151
+
152
+ ## 开发
153
+
154
+ ```bash
155
+ pnpm install
156
+ pnpm --filter dsh-file-viewer test
157
+ ```
@@ -0,0 +1,21 @@
1
+ # Ensure @deepseek-ai/dsh-client-connection has webStartup + webServer injected
2
+ # so rpc.handle can register HTTP routes.
3
+ # NOTE: webRuntime is intentionally NOT injected: the dsh-web-app web-runtime
4
+ # row that provides it no longer activates on DSH desktop 0.2.1+, so injecting
5
+ # it makes `connection` wait forever and no RPC channel gets registered --
6
+ # the SPA fallback then answers POST with HTTP 405.
7
+ # webStartup must stay listed because a patch row's `inject` REPLACES the
8
+ # previous one wholesale (last write wins), and the connection row's own
9
+ # config reads ctx.webStartup.trustedHosts.
10
+ - id: connection
11
+ inject:
12
+ - webStartup
13
+ - webServer
14
+
15
+ # Cordis bundle patch: mounts the file viewer's host half.
16
+ #
17
+ # Patches apply in `dsh.profile.bundles` order, so a later bundle (and finally
18
+ # the user's own profile patch) can override this row by reusing the same id.
19
+ - insert:
20
+ - id: file-viewer
21
+ name: '@weniatall/dsh-file-viewer'