dsh-origin-bridge 0.4.3 → 0.4.5

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
@@ -10,9 +10,13 @@
10
10
  拿回一张发表级图片和一个<b>能在 Origin 里继续编辑</b>的 <code>.opju</code> 工程。
11
11
  </p>
12
12
 
13
+ <p align="center">
14
+ <sub>27 个工具 · <b>1079 例全链路测试全部通过</b> · 数据不出本机 · 支持工作站 <code>.txt</code> / <code>.bin</code></sub>
15
+ </p>
16
+
13
17
  <p align="center">
14
18
  <a href="#安装"><img alt="platform" src="https://img.shields.io/badge/platform-Windows%20%2B%20Origin%202021%2B-blue"></a>
15
- <img alt="tests" src="https://img.shields.io/badge/tests-231%2B%20passing-brightgreen">
19
+ <img alt="tests" src="https://img.shields.io/badge/tests-1079%2F1079%20passing-brightgreen">
16
20
  <img alt="license" src="https://img.shields.io/badge/license-MIT-lightgrey">
17
21
  </p>
18
22
 
@@ -31,7 +35,7 @@
31
35
  - **数据不出本机。** 没有 API key、没有云端组件,dsh 拉起一个本地 Python 进程,通过 OriginLab 官方的 [`originpro`](https://pypi.org/project/originpro/) 驱动你机器上的 Origin。
32
36
  - **每一步都有证据。** Origin 的 X-Function 普遍"返回成功但什么都没发生"。这里画图后读回轴范围与数据跨度比对,导出后校验文件存在/体积/文件头魔数,样式逐项读回——读不回的进 `failed` 列表,不会假装成功。
33
37
  - **不是 74 个工具,是 27 个。** 每个都有实测用途,工具描述里写着实测限制,模型不会踩坑。
34
- - **不是玩具。** **1079 例全链路题库**(27 个工具,最少 15 例/工具,最多 134 例/工具)跑的是真实 stdio 服务器 + 本机 OriginPro,按副作用分五层:L0 纯解析 273 例、L1 只读 COM 61 例、L2 改会话 512 例、L3 落盘 155 例、L4 进程级 78 例;提示词按人话写,跑之前先用在线 `tools/list` schema 校验用例本身,避免把题库的笔误当成产品 bug。题库与模拟数据是开发侧资产,**不随包发布**;随包发布的是 214 项离线/单元与边界检查(asciiio 24、binio 30、lifecycle 37、smoke 97、edge 26),你可以直接复跑。originpro 的十几个文档陷阱全部记录在案并绕开。
38
+ - **1000+ 次真实测试,不是自称。** **1079 例**全链路用例跑在真实 stdio 服务器 + 本机 OriginPro 上,**1079 通过 / 0 失败**:27 个工具每个至少 15 例、最多 134 例,按副作用分五层(L0 纯解析 273、L1 只读 COM 61、L2 改会话 512、L3 落盘 155、L4 进程级 78)。提示词全部按人话写,跑之前先用在线 `tools/list` schema 校验用例本身,所以失败只会是产品问题,不会是题目写错。另有 **214 项**离线/单元与边界检查(asciiio 24、binio 30、lifecycle 37、smoke 97、edge 26)**随包发布,你可以直接复跑**;那 1079 例题库含提示词与模拟数据,是开发侧资产,不随包分发。originpro 的十几个文档陷阱全部记录在案并绕开。
35
39
 
36
40
  ## 它能做什么
37
41
 
Binary file
Binary file
Binary file
Binary file
package/package.json CHANGED
@@ -1,76 +1,82 @@
1
- {
2
- "name": "dsh-origin-bridge",
3
- "version": "0.4.3",
4
- "description": "Drive local Origin/OriginPro from chat: import .dat/.csv/Excel/workstation .bin data, plot, fit, and save editable .opju projects. Third-party tool, not affiliated with or endorsed by OriginLab Corporation.",
5
- "license": "MIT",
6
- "type": "module",
7
- "repository": {
8
- "type": "git",
9
- "url": "git+https://github.com/caob23/dsh-origin-bridge.git"
10
- },
11
- "bugs": {
12
- "url": "https://github.com/caob23/dsh-origin-bridge/issues"
13
- },
14
- "homepage": "https://github.com/caob23/dsh-origin-bridge#readme",
15
- "main": "./index.js",
16
- "icon": "./icon.svg",
17
- "dependencies": {
18
- "@deepseek-ai/dsh-mcp-client": ">=0.1.0-rc.1"
19
- },
20
- "bin": {
21
- "origin-bridge": "bin/origin-bridge.mjs"
22
- },
23
- "exports": {
24
- ".": "./index.js",
25
- "./package.json": "./package.json",
26
- "./locale/*.json": "./locale/*.json"
27
- },
28
- "files": [
29
- "index.js",
30
- "bin/origin-bridge.mjs",
31
- "install_dsh.ps1",
32
- "cordis.patch.yml",
33
- "icon.svg",
34
- "locale/en.json",
35
- "locale/zh.json",
36
- "docs/preview.png",
37
- "scripts/make_icon.py",
38
- "scripts/make_preview.py",
39
- "server.py",
40
- "originlab.py",
41
- "asciiio.py",
42
- "binio.py",
43
- "templates_catalog.py",
44
- "fixtures/make_fixtures.py",
45
- "fixtures/*.dat",
46
- "fixtures/*.csv",
47
- "fixtures/*.txt",
48
- "smoke_test.py",
49
- "edge_checks.py",
50
- "test_asciiio.py",
51
- "test_binio.py",
52
- "test_lifecycle.py",
53
- "client_check.py",
54
- "requirements.txt",
55
- "sample.dat",
56
- "README.md",
57
- "LICENSE"
58
- ],
59
- "dsh": {
60
- "bundle": {
61
- "patch": "./cordis.patch.yml"
62
- }
63
- },
64
- "keywords": [
65
- "dsh",
66
- "dsh-plugin",
67
- "dsh-bundle",
68
- "deepseek-harness",
69
- "mcp",
70
- "origin",
71
- "originpro",
72
- "opju",
73
- "scientific-plotting",
74
- "electrochemistry"
75
- ]
76
- }
1
+ {
2
+ "name": "dsh-origin-bridge",
3
+ "version": "0.4.5",
4
+ "description": "在对话里驱动本机 Origin / OriginPro:导入 .dat/.csv/.txt 与电化学工作站 .bin,绘图、拟合、改样式,导出图片并保存可在 Origin 里继续编辑的 .opju 工程。dsh 插件,27 个 MCP 工具。第三方工具,与 OriginLab Corporation 无关联、未获认可。",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/caob23/dsh-origin-bridge.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/caob23/dsh-origin-bridge/issues"
13
+ },
14
+ "homepage": "https://github.com/caob23/dsh-origin-bridge#readme",
15
+ "main": "./index.js",
16
+ "icon": "./icon.svg",
17
+ "dependencies": {
18
+ "@deepseek-ai/dsh-mcp-client": ">=0.1.0-rc.1"
19
+ },
20
+ "bin": {
21
+ "origin-bridge": "bin/origin-bridge.mjs"
22
+ },
23
+ "exports": {
24
+ ".": "./index.js",
25
+ "./package.json": "./package.json",
26
+ "./locale/*.json": "./locale/*.json"
27
+ },
28
+ "files": [
29
+ "index.js",
30
+ "bin/origin-bridge.mjs",
31
+ "install_dsh.ps1",
32
+ "cordis.patch.yml",
33
+ "icon.svg",
34
+ "locale/en.json",
35
+ "locale/zh.json",
36
+ "docs/preview.png",
37
+ "scripts/make_screenshots.py",
38
+ "docs/shot-workstation.png",
39
+ "docs/shot-heatmap.png",
40
+ "docs/shot-eis.png",
41
+ "docs/shot-cv.png",
42
+ "screenshots.json",
43
+ "scripts/make_icon.py",
44
+ "scripts/make_preview.py",
45
+ "server.py",
46
+ "originlab.py",
47
+ "asciiio.py",
48
+ "binio.py",
49
+ "templates_catalog.py",
50
+ "fixtures/make_fixtures.py",
51
+ "fixtures/*.dat",
52
+ "fixtures/*.csv",
53
+ "fixtures/*.txt",
54
+ "smoke_test.py",
55
+ "edge_checks.py",
56
+ "test_asciiio.py",
57
+ "test_binio.py",
58
+ "test_lifecycle.py",
59
+ "client_check.py",
60
+ "requirements.txt",
61
+ "sample.dat",
62
+ "README.md",
63
+ "LICENSE"
64
+ ],
65
+ "dsh": {
66
+ "bundle": {
67
+ "patch": "./cordis.patch.yml"
68
+ }
69
+ },
70
+ "keywords": [
71
+ "dsh",
72
+ "dsh-plugin",
73
+ "dsh-bundle",
74
+ "deepseek-harness",
75
+ "mcp",
76
+ "origin",
77
+ "originpro",
78
+ "opju",
79
+ "scientific-plotting",
80
+ "electrochemistry"
81
+ ]
82
+ }
@@ -0,0 +1,7 @@
1
+ [
2
+ "docs/preview.png",
3
+ "docs/shot-cv.png",
4
+ "docs/shot-eis.png",
5
+ "docs/shot-heatmap.png",
6
+ "docs/shot-workstation.png"
7
+ ]
@@ -0,0 +1,187 @@
1
+ """Regenerate the storefront screenshots in docs/ through the real engine.
2
+
3
+ Every image here is a genuine Origin export built from closed-form synthetic
4
+ data: no measurement from the user's lab ever appears in a screenshot, and the
5
+ files are reproducible by rerunning this script.
6
+
7
+ python scripts/make_screenshots.py # 全部四张
8
+ python scripts/make_screenshots.py --only cv # 只重做一张
9
+
10
+ The screenshots are declared to the plugin market by the repository-root
11
+ screenshots.json, which points at these paths.
12
+ """
13
+
14
+ import math
15
+ import os
16
+ import sys
17
+ import tempfile
18
+
19
+ HERE = os.path.dirname(os.path.abspath(__file__))
20
+ ROOT = os.path.dirname(HERE)
21
+ sys.path.insert(0, ROOT)
22
+
23
+ import originlab # noqa: E402
24
+
25
+ DOCS = os.path.join(ROOT, "docs")
26
+
27
+
28
+ def _cv():
29
+ """A synthetic reversible voltammogram: forward and reverse sweep, mA/cm2."""
30
+ pts = []
31
+ e_up, e_dn, step = 0.8, -0.2, 0.005
32
+ e = e_up
33
+ direction = -1.0
34
+ while True:
35
+ anodic = 0.42 * math.exp(-((e - 0.42) ** 2) / 0.0045)
36
+ cathodic = -0.36 * math.exp(-((e + 0.06) ** 2) / 0.0055)
37
+ base = 0.02 * e + 0.006 * math.sin(e * 9.0)
38
+ pts.append((round(e, 4), round(anodic + cathodic + base, 5)))
39
+ e = round(e + direction * step, 4)
40
+ if direction < 0 and e <= e_dn:
41
+ direction = 1.0
42
+ if direction > 0 and e >= e_up:
43
+ break
44
+ return {"E / V": [p[0] for p in pts], "i / mA cm-2": [p[1] for p in pts]}
45
+
46
+
47
+ def _bode(n=41):
48
+ """Randles-style synthetic impedance: log-frequency sweep."""
49
+ rs, rct, c = 12.0, 180.0, 6e-5
50
+ freq, zre, zim = [], [], []
51
+ for i in range(n):
52
+ f = 1e5 / (10 ** (i * 0.175))
53
+ w = 2 * math.pi * f
54
+ den = 1 + (w * rct * c) ** 2
55
+ freq.append(round(f, 4))
56
+ zre.append(round(rs + rct / den, 4))
57
+ zim.append(round(w * rct ** 2 * c / den, 4))
58
+ return {"Freq / Hz": freq, "Z' / ohm": zre, "Z'' / ohm": zim}
59
+
60
+
61
+ def _grid(rows=12, cols=12):
62
+ return [[round(3.0 * math.sin(i / 2.4) + 2.0 * math.cos(j / 3.1)
63
+ + 0.6 * math.sin((i + j) / 2.0), 4) for j in range(cols)] for i in range(rows)]
64
+
65
+
66
+ def _workstation_txt():
67
+ """A CH Instruments style export, written to a temp file, to show the .txt route."""
68
+ lines = [
69
+ "Jan. 04, 2026 09:12:07", "Linear Sweep Voltammetry",
70
+ "File: d:\\demo\\synthetic_lsv.txt", "Data Source: Experiment",
71
+ "Instrument Model: CHI660E", "Header: ", "Note: ", "",
72
+ "Init E (V) = 0.0", "Final E (V) = 0.6", "Scan Rate (V/s) = 0.05",
73
+ "Sample Interval (V) = 0.002", "Quiet Time (sec) = 2",
74
+ "Sensitivity (A/V) = 0.0001", "", "Results:", "", "Channel 1:",
75
+ "Segment 1:", "", "Potential/V, Current/A", "",
76
+ ]
77
+ for i in range(120):
78
+ e = i * 0.005
79
+ lines.append("%.3f, %.3e" % (e, 1.2e-5 + 3.4e-5 / (1 + math.exp(-(e - 0.24) * 26))
80
+ + 4e-7 * math.sin(i / 3.0)))
81
+ fd, path = tempfile.mkstemp(prefix="synthetic_lsv_", suffix=".txt")
82
+ os.close(fd)
83
+ with open(path, "w", encoding="utf-8", newline="\r\n") as handle:
84
+ handle.write("\r\n".join(lines) + "\r\n")
85
+ return path
86
+
87
+
88
+ def _finish(graph, out, label):
89
+ try:
90
+ originlab.legend_entries(graph, show=False)
91
+ except originlab.OriginError:
92
+ pass # a heat map has no legend box to hide
93
+ e = originlab.export(graph, out, fmt="png", width=1600)
94
+ print(" %-14s -> %s (%d 字节, %s)" % (label, os.path.basename(out), e["bytes"],
95
+ e.get("dimensions")))
96
+ return e
97
+
98
+
99
+ def _line(graph, color="blue", width=2.5, series=0, symbol=0, size=0.0):
100
+ """A 1 px black hairline reads as a broken chart in a screenshot."""
101
+ args = {"graph": graph, "series": series, "color": color, "line_width": width}
102
+ if symbol:
103
+ args["symbol_kind"] = symbol
104
+ if size:
105
+ args["symbol_size"] = size
106
+ originlab.style(**args)
107
+
108
+
109
+ def shot_cv():
110
+ sheet = originlab.write_columns(_cv())
111
+ p = originlab.plot(sheet["worksheet"], x=1, y=2, plot_type="line",
112
+ title="Synthetic cyclic voltammogram")
113
+ originlab.style(p["graph"], x_title="E / V", y_title="i / mA cm-2")
114
+ _line(p["graph"], "blue", 2.5)
115
+ _finish(p["graph"], os.path.join(DOCS, "shot-cv.png"), "CV")
116
+ return p["graph"]
117
+
118
+
119
+ def shot_bode():
120
+ data = _bode(n=25)
121
+ freq = data["Freq / Hz"]
122
+ sheet = originlab.write_columns({"Freq / Hz": freq, "|Z| / ohm":
123
+ [round(math.hypot(zr, zi), 3) for zr, zi
124
+ in zip(data["Z' / ohm"], data["Z'' / ohm"])]})
125
+ p = originlab.plot(sheet["worksheet"], x=1, y=2, plot_type="line_symbol",
126
+ title="Synthetic Bode plot")
127
+ st = originlab.style(p["graph"], x_title="Frequency / Hz", y_title="|Z| / ohm",
128
+ x_scale="log10")
129
+ read = [o for o in st.get("applied", []) if o.get("op") == "x_scale"]
130
+ if not read or read[0].get("readback") != 2:
131
+ raise SystemExit("横轴没能改成对数(读回 %r),不导出" % (read or "没有这项",))
132
+ # A log axis auto-ranges over the whole decade grid; without this the curve
133
+ # sits in a third of the panel and the tick labels collide.
134
+ originlab.style(p["graph"], xlim=[min(freq) * 0.6, max(freq) * 1.6])
135
+ _line(p["graph"], "red", 2.0, symbol=2, size=7.0)
136
+ _finish(p["graph"], os.path.join(DOCS, "shot-eis.png"), "EIS")
137
+ return p["graph"]
138
+
139
+
140
+ def shot_heatmap():
141
+ m = originlab.matrix_plot(_grid(), template="heat_map", title="Synthetic response surface")
142
+ graph = m.get("graph") or m.get("graph_page")
143
+ originlab.style(graph, x_title="Applied potential / V", y_title="Cycle",
144
+ layer=0)
145
+ _finish(graph, os.path.join(DOCS, "shot-heatmap.png"), "Heat map")
146
+ return graph
147
+
148
+
149
+ def shot_workstation():
150
+ path = _workstation_txt()
151
+ try:
152
+ imp = originlab.import_file(path)
153
+ cols = [c["label"] for c in imp["columns"]]
154
+ if cols[:2] != ["Potential", "Current"]:
155
+ raise SystemExit("工作站 txt 的列名没解析成 Potential/Current,而是 %s" % cols)
156
+ p = originlab.plot(imp["worksheet"], x=1, y=2, plot_type="line",
157
+ title="Workstation .txt import")
158
+ originlab.style(p["graph"], x_title="E / V", y_title="I / A")
159
+ _line(p["graph"], "green", 2.5)
160
+ _finish(p["graph"], os.path.join(DOCS, "shot-workstation.png"), "Workstation txt")
161
+ return p["graph"]
162
+ finally:
163
+ os.remove(path)
164
+
165
+
166
+ SHOTS = {"cv": shot_cv, "eis": shot_bode, "heatmap": shot_heatmap,
167
+ "workstation": shot_workstation}
168
+
169
+
170
+ def main():
171
+ only = None
172
+ if "--only" in sys.argv:
173
+ only = sys.argv[sys.argv.index("--only") + 1]
174
+ os.makedirs(DOCS, exist_ok=True)
175
+ for name, fn in SHOTS.items():
176
+ if only and name != only:
177
+ continue
178
+ print("生成 %s ..." % name)
179
+ fn()
180
+ originlab.close_pages("graph")
181
+ originlab.close_pages("workbook")
182
+ originlab.close_pages("matrix")
183
+ print("完成,图片在 %s" % DOCS)
184
+
185
+
186
+ if __name__ == "__main__":
187
+ main()