dsh-figmark 0.0.0-stage → 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 KafukaTree
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PUBLISHING.md ADDED
@@ -0,0 +1,79 @@
1
+ # 发布清单
2
+
3
+ ## 一次发布做这几步
4
+
5
+ ```sh
6
+ cd ~/.dsh/plugins/dsh-figmark
7
+
8
+ pnpm run check # typecheck + lint + coverage 三道门禁
9
+ pnpm run build # 重建 lib/(prepack 也会跑)
10
+
11
+ # 1) 改版本号(索引和包里只有这一个 version 要同步)
12
+ # package.json "version": "1.0.0"
13
+ # market/index.json plugins[0].versions[0].version
14
+ # market/index.json plugins[0].versions[0].publishTimestamp(Unix 秒)
15
+ node -e "console.log(Math.floor(Date.now()/1000))"
16
+
17
+ # 2) 先干跑一次,确认包内容
18
+ npm pack --dry-run
19
+
20
+ # 3) 发布到 npm
21
+ npm login # 只需一次
22
+ npm publish --access public
23
+
24
+ # npm 还没发布时:索引里 versions[0].source 用 git 类型(见下),
25
+ # 钉到当前 commit 即可让别人装上源码版本。npm 发布之后把 source 换回
26
+ # {"type":"npm","registry":"https://registry.npmjs.org"},并重跑上面的校验。
27
+
28
+ # 4) 提交并推 tag
29
+ git add -A && git commit -m "release: dsh-figmark@1.0.0"
30
+ git tag v1.0.0 && git push origin main --tags
31
+ ```
32
+
33
+ 推完之后,索引地址 `https://raw.githubusercontent.com/KafukaTree/dsh-figmark/main/market/index.json`
34
+ 会立刻反映新版本;DSH 的发现页缓存 `cacheTtlMs` 默认 10 分钟,写 `refresh` 可绕过。
35
+
36
+ ## git 源 vs npm 源
37
+
38
+ | source | 用户装到的东西 | 代价 |
39
+ |---|---|---|
40
+ | `npm` | 构建好的 `lib/`(3.3 MB,含 7.9 MB 未压缩 PDF 分块) | 要先把包发布到 npm |
41
+ | `git` | **源码**,pnpm 跑 `prepare` 现场构建 | pnpm 11 会拦下构建脚本,用户要在插件页点**允许这些脚本并重试**;而且要能连 GitHub |
42
+
43
+ git 源切换只需改 `market/index.json` 里那一个 `source` 对象并推送——`commit` 必须是**小写 40 位**
44
+ 且是真实存在的提交(校验器就是这么检查的)。
45
+
46
+ ## 每次发布前确认
47
+
48
+ - [ ] `package.json` 的 `version` 与 `market/index.json` 的 `versions[0].version` **一致**
49
+ - [ ] `publishTimestamp` 是**这一次**的 Unix 秒(不是上一次的残留)
50
+ - [ ] `engines.dsh` 没跟着 DSH 升级漂移(它决定装不装得上)
51
+ - [ ] `npm pack --dry-run` 里没有 `src/`、`tests/`、`docs/`
52
+ - [ ] 图标改过的话,内联进 `market/index.json` 的 base64 也要重新生成
53
+ - [ ] 新功能/新改动在 `docs/notes/implemented/` 有决策记录(AGENTS.md 要求)
54
+
55
+ ## 索引图标是怎么来的
56
+
57
+ `market/index.json` 里的 `icon` 是 `icon.svg` 的 base64 内联(`data:image/svg+xml;base64,...`)。
58
+ 索引校验器的上限是**解码后 32 KiB**,当前 icon 2.8 KB。改图标后重新内联:
59
+
60
+ ```sh
61
+ node -e "
62
+ const fs=require('fs');
63
+ const icon=fs.readFileSync('icon.svg','utf8').trim();
64
+ const p=JSON.parse(fs.readFileSync('market/index.json','utf8'));
65
+ p.plugins[0].icon='data:image/svg+xml;base64,'+Buffer.from(icon).toString('base64');
66
+ fs.writeFileSync('market/index.json', JSON.stringify(p,null,2)+'\n');
67
+ "
68
+
69
+ # 校验(用 DSH 自己的校验器,别信自己的眼睛)
70
+ node -e "
71
+ const {parseMarketIndex}=require('/Applications/DeepSeek Harness.app/Contents/Resources/app.asar.unpacked/../app.asar' );
72
+ " 2>/dev/null || echo '用 /tmp 里解出来的 dsh-plugin-market-index/lib/index.js 校验'
73
+ ```
74
+
75
+ ## 别人装的时候会看到什么
76
+
77
+ 1. **侧栏 → 插件 → 实验性插件 → 更多 → 自定义插件市场**(启用)
78
+ 2. 发现页 → 添加索引 → 粘贴 raw 地址
79
+ 3. 搜 `figmark` → 安装 → **Cmd+Q 完全退出重开**
package/README.md CHANGED
@@ -1,3 +1,139 @@
1
- # Temporary Holding Version
1
+ # dsh-figmark
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 插件:在右侧栏的文档预览里**直接圈画批注**科研配图,然后把批注**当作一条用户消息交给智能体**——让它按你圈的位置去改生成源(Python 脚本、DOT、SVG),而不是靠你打字描述「左边第二条曲线的图例要挪上去」。
4
+
5
+ <p align="center"><img src="./icon.svg" width="72" height="72" alt="dsh-figmark icon"></p>
6
+
7
+ > annotate a figure (image / SVG / PDF / HTML) in the right-sidebar preview and hand the marks to the agent as one user message. The original file is never modified: marks live in a sidecar next to it.
8
+
9
+ ## 它解决的问题
10
+
11
+ 科研图的返工意见大多是**位置性的**:「这个标号指错了」「这里少一条图例」「这条误差棒太长了」。用文字描述既慢又容易误解,而只发一张截图给模型,它只知道「图上有个红框」,不知道红框框的是哪一行元素。
12
+
13
+ dsh-figmark 把三样东西一起交给智能体:
14
+
15
+ 1. **图面坐标** —— 每条标注的几何(箭头两端 / 框的对角 / 单点的位置);
16
+ 2. **落在哪个元素上** —— SVG 给 `tag`/`id`/`title`/元素文字/包围盒,HTML 给一条从 `body` 起可直接命中的 `selector`;
17
+ 3. **你那句话** —— 每条标注一段「这个元素要改什么」,外加一段「这张图整体要往哪个方向改」。
18
+
19
+ ## 支持范围
20
+
21
+ | 文档类型 | 后缀 | 状态 |
22
+ |---|---|---|
23
+ | 图片 | `png` `jpg` `jpeg` `gif` `webp` `bmp` `ico` | ✅ |
24
+ | SVG | `svg` | ✅ 内联渲染,支持图元锚定 |
25
+ | PDF | `pdf` | ✅ 整份文档一份批注:逐页画、一次保存,写回**原生 PDF 注释对象** |
26
+ | HTML | `html` `htm` | ✅ 固定 1024px 渲染面 + 元素锚定(只写批注文件,不产出标注图) |
27
+ | Markdown | `md` `markdown` | ✅ **编辑**而非批注:改动作为 unified diff 交给智能体,插件不落盘 |
28
+ | Office | `doc` `docx` `ppt` `pptx` `xls` `xlsx` | ⏳ 待接入,仍由内置预览器打开 |
29
+
30
+ ## 安装
31
+
32
+ ### 从插件市场安装(推荐)
33
+
34
+ 在 DSH 里打开 **侧栏 → 插件 → 实验性插件 → 更多 → 自定义插件市场**,添加索引地址:
35
+
36
+ ```
37
+ https://raw.githubusercontent.com/KafukaTree/dsh-figmark/main/market/index.json
38
+ ```
39
+
40
+ 然后在**发现**页搜 `figmark` 安装。安装完成后 **Cmd+Q 完全退出重开** DSH。
41
+
42
+ ### 从 npm 安装
43
+
44
+ ```sh
45
+ dsh plugin add dsh-figmark
46
+ ```
47
+
48
+ `dsh plugin add` 会把它写进 profile 的依赖并加入 `dsh.profile.bundles`。装完同样要完全退出重开。
49
+
50
+ ### 从源码开发
51
+
52
+ ```sh
53
+ git clone https://github.com/KafukaTree/dsh-figmark.git
54
+ cd dsh-figmark
55
+ pnpm install
56
+ pnpm run build # 产出 lib/index.js(Host)与 lib/client.js + lib/client.pdf.js(Client)
57
+ dsh plugin add "$PWD"
58
+ ```
59
+
60
+ **改动 Host 半后必须重启宿主进程**——Node 会缓存已导入的模块;Client 半只需刷新页面。开发时建议用隔离实例:
61
+
62
+ ```sh
63
+ DSH_HOME=/tmp/figmarkdev dsh --profile figmarkdev --port 3099 --no-open
64
+ ```
65
+
66
+ ## 用法
67
+
68
+ 打开右栏的图片 / SVG / PDF / HTML,底部就是全部控件:
69
+
70
+ - **左侧浮条**:选择(鼠标指针)· 箭头 · 框选 · 圈选 · 手绘 · 单点。除「选择」外每个工具画完**自动回到选择**并选中刚画的那个,说明框随之弹出、光标已经在里面。
71
+ - **底部浮条**:标注样式(颜色折叠面板)· 撤销 / 重做 · 总体目标 · 标注清单 · 发送给智能体 / 发送到对话框。
72
+ - 两条浮条都能**拖动**,位置记在浏览器里,下次打开还在原地。
73
+ - 图上只画标记,**不把文字画到图上**。单点是地图水滴,编号写在滴头里;其余形状不带编号。
74
+ - 选中标注后可以**拖动改位置**、拖角/边上的手柄**改大小**、拖顶部手柄**旋转**(按住 Shift 吸附 15°)。一次手势 = 一步撤销。
75
+ - 触控板捏合缩放,缩放中心跟指针;「选择」工具下直接拖动就是平移。
76
+ - 双击图上的标注 = 打开它的说明框;`Esc` 取消选中;`Delete`/`Backspace` 删除选中标注。
77
+ - 编辑会**自动保存**(防抖 600ms),刷新、重开都不丢;只有点「发送」成功后才清空面板并删掉磁盘上的批注文件。
78
+
79
+ ### 两个发送按钮
80
+
81
+ | 按钮 | 行为 |
82
+ |---|---|
83
+ | **发送给智能体** | 批注+标注图作为一条用户消息**直接发进当前会话** |
84
+ | **发送到对话框** | 只**写进输入框草稿**(连同图片附件),你自己看完再按回车 |
85
+
86
+ 纯单点批注(一个坐标 + 一句话,没有框和箭头)不生成标注图——消息里会写明「本条标注只有位置点,没有标注图」,让智能体按坐标去原图定位。
87
+
88
+ ## 产物
89
+
90
+ 批注**从不修改原文档**,只在旁边写文件:
91
+
92
+ | 文件 | 内容 |
93
+ |---|---|
94
+ | `<名>.<后缀>.annot.json` | 批注文档:图信息(绝对路径、sha256,PDF 另有 `pageCount`)、`marks[]`(kind/color/points/rotation/page/anchor/detail)、总体目标 |
95
+ | `<名>.<后缀>.annotated.png` | 图片/SVG:原图 + 批注的合成图(最高 2× 分辨率),作为图片附件随消息发送 |
96
+ | `<名>.<后缀>.annotated.pdf` | PDF:原件副本,批注写成**原生 PDF 注释对象**,作为附件随消息发送 |
97
+ | —— | HTML:只有批注文件(浏览器无法把渲染好的文档栅格化);定位靠 `anchor.selector` |
98
+ | —— | Markdown:**不留任何文件**,改动以 unified diff 进会话 |
99
+
100
+ 名字**带着被批注文件自己的扩展名**(`fig1.svg` → `fig1.svg.annot.json`),所以同目录下同主名不同后缀的两份文件各有各的批注、绝不会互相读错。
101
+
102
+ `marks[].points` 是**图面像素坐标**(原点左上);PDF 的坐标是该页的页面单位(PDF 点),因此缩放、翻页、旋转页都不影响指向。原图被重新生成后 sha256 变化,插件会提示旧批注可能失准。
103
+
104
+ 带批注的 PDF 里,箭头是 `/Line`、框选 `/Square`、圈选 `/Circle`、手绘 `/Ink`、单点 `/Text`,各自带自绘外观流,所以在 Acrobat、Preview、浏览器内置查看器里看到的形状与页面上一致,也能继续编辑或删除。旋转过的框会烘焙成折线(PDF 的 `/Square` 按定义轴对齐,转不了)。
105
+
106
+ 插件还会注入一段系统提示,告诉智能体拿到【附图标注】消息后该怎么处理:**改生成源,不涂改导出的位图**,未标注处不动,逐条回应。
107
+
108
+ ## 与同工作区的其它批注器互通
109
+
110
+ 同一工作区里的姊妹批注器(Sati)把批注写进同一份侧车文件,但用 **v2 形状**(`target` 而非 `figure`)。本插件读得懂 v2、继续写自己的 v1,所以两边都能看到对方留下的批注;回存时 v2 独有、本插件不建模的字段会丢。
111
+
112
+ 早期版本的两种命名仍会被读回(只读回退):按主名的 `fig1.annot.json`,以及 PDF 按页各一份的 `report.pdf.p3.annot.json`(会被合并成一份显示,旧文件原样保留)。
113
+
114
+ ## 已知限制
115
+
116
+ - **不做图面编辑**:插件只采集批注,不改图;修图仍由智能体改生成源完成。
117
+ - **栅格图没有元素锚定**:PNG/JPEG 只有像素坐标,锚定信息仅 SVG 与 HTML 有。
118
+ - **PDF 只写原生注释对象**:不做「把标记烧进页面内容」的副本。好处是标记在阅读器里仍可编辑删除;代价是只看页面内容、不渲染注释的流程看不到标记(那种场合用消息里附的每页图片)。
119
+ - **PDF 一次一页**,无文本层、无连续滚动。
120
+ - **HTML 渲染是静态的**:框架不执行脚本、不允许外部请求(自带 `default-src 'none'` CSP),相对引用的图片与外部样式不加载。脚本渲染出来的内容在预览里是空的,批注也落不到上面。
121
+ - **Markdown 的差异基线是「你打开时的那一版」**,不是 git HEAD。
122
+ - **Markdown 没有语法高亮**:编辑框是原生 `<textarea>`(插件因此至今零新增运行时依赖)。
123
+ - **Office 尚未接入。**
124
+
125
+ ## 开发
126
+
127
+ 开发规范(门禁、负控制、决策记录、架构红线)的家在 [`AGENTS.md`](./AGENTS.md),开工前先读 §2。决策背景在 [`docs/notes/`](./docs/notes)。
128
+
129
+ ```sh
130
+ pnpm run check # 三个门禁一次跑完:typecheck + lint + test:coverage
131
+ pnpm run build # tsc --noEmit + tsdown(Host ESM / Client 单文件 CJS + PDF 懒加载分块)
132
+ pnpm test # vitest
133
+ ```
134
+
135
+ Client 半边必须匹配 DSH 动态客户端契约:单文件 CJS、唯一副作用是 `window.__ModuleLoader__.load({id, factory})`、只把 `react`/`react/jsx-runtime`/`react-dom` 留给模块表,其余全部内联——插件路由只发 `client.*.js`,发不了字体等静态资产。PDF 那一半(PDF.js + pdf-lib)住在懒加载分块里,且**分块不得与主包共享任何模块**,否则主包会同步 `require` 一个它拿不到的文件。
136
+
137
+ ## 许可
138
+
139
+ MIT,见 [LICENSE](./LICENSE)。
@@ -0,0 +1,6 @@
1
+ # dsh-figmark bundle layer: 文档标注插件。
2
+ # 一条 row 同时挂载 Host 半(路由 + 智能体指引段)与 Client 半(右侧栏预览器里的标注画布)。
3
+ - insert:
4
+ - id: dsh-figmark
5
+ name: 'dsh-figmark'
6
+ config: {}
package/icon.svg ADDED
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 16 16" shape-rendering="crispEdges"><rect width="16" height="16" rx="3.5" fill="#22252b"/><rect x="2" y="2" width="1" height="1" fill="#e8eaed"/><rect x="3" y="2" width="1" height="1" fill="#e8eaed"/><rect x="4" y="2" width="1" height="1" fill="#e8eaed"/><rect x="5" y="2" width="1" height="1" fill="#e8eaed"/><rect x="10" y="2" width="1" height="1" fill="#e8eaed"/><rect x="11" y="2" width="1" height="1" fill="#e8eaed"/><rect x="12" y="2" width="1" height="1" fill="#e8eaed"/><rect x="13" y="2" width="1" height="1" fill="#e8eaed"/><rect x="2" y="3" width="1" height="1" fill="#e8eaed"/><rect x="13" y="3" width="1" height="1" fill="#e8eaed"/><rect x="2" y="4" width="1" height="1" fill="#e8eaed"/><rect x="13" y="4" width="1" height="1" fill="#e8eaed"/><rect x="2" y="5" width="1" height="1" fill="#e8eaed"/><rect x="5" y="5" width="1" height="1" fill="#f2721d"/><rect x="13" y="5" width="1" height="1" fill="#e8eaed"/><rect x="5" y="6" width="1" height="1" fill="#f2721d"/><rect x="6" y="6" width="1" height="1" fill="#f2721d"/><rect x="5" y="7" width="1" height="1" fill="#f2721d"/><rect x="7" y="7" width="1" height="1" fill="#f2721d"/><rect x="5" y="8" width="1" height="1" fill="#f2721d"/><rect x="8" y="8" width="1" height="1" fill="#f2721d"/><rect x="5" y="9" width="1" height="1" fill="#f2721d"/><rect x="9" y="9" width="1" height="1" fill="#f2721d"/><rect x="2" y="10" width="1" height="1" fill="#e8eaed"/><rect x="5" y="10" width="1" height="1" fill="#f2721d"/><rect x="10" y="10" width="1" height="1" fill="#f2721d"/><rect x="13" y="10" width="1" height="1" fill="#e8eaed"/><rect x="2" y="11" width="1" height="1" fill="#e8eaed"/><rect x="5" y="11" width="1" height="1" fill="#f2721d"/><rect x="11" y="11" width="1" height="1" fill="#f2721d"/><rect x="13" y="11" width="1" height="1" fill="#e8eaed"/><rect x="2" y="12" width="1" height="1" fill="#e8eaed"/><rect x="5" y="12" width="1" height="1" fill="#f2721d"/><rect x="8" y="12" width="1" height="1" fill="#f2721d"/><rect x="9" y="12" width="1" height="1" fill="#f2721d"/><rect x="13" y="12" width="1" height="1" fill="#e8eaed"/><rect x="2" y="13" width="1" height="1" fill="#e8eaed"/><rect x="3" y="13" width="1" height="1" fill="#e8eaed"/><rect x="4" y="13" width="1" height="1" fill="#e8eaed"/><rect x="5" y="13" width="1" height="1" fill="#f2721d"/><rect x="7" y="13" width="1" height="1" fill="#f2721d"/><rect x="10" y="13" width="1" height="1" fill="#e8eaed"/><rect x="11" y="13" width="1" height="1" fill="#e8eaed"/><rect x="12" y="13" width="1" height="1" fill="#e8eaed"/><rect x="13" y="13" width="1" height="1" fill="#e8eaed"/><rect x="5" y="14" width="1" height="1" fill="#f2721d"/><rect x="6" y="14" width="1" height="1" fill="#f2721d"/></svg>