dsh-spectacles 1.0.0 → 1.1.2

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
@@ -2,6 +2,17 @@
2
2
 
3
3
  人话版。每次发版都在这儿写一段:改了什么、为什么改、有没有坑。
4
4
 
5
+ ## 1.1.2 — 2026-11-09
6
+
7
+ - 框选不再先抓一帧整屏当底图,只算坐标:铺一层半透明黑、你拖框,它记下「哪个窗口 + 相对它的哪一块」。选的时候不读任何像素。
8
+ - 置顶抓取失败时不再硬出图。有些程序(提权运行的、自带保护的)不让被提到最前,这时候会明确报 `raise-failed`,而不是给你一张压着别人窗口的画面。
9
+ - 补了两个关键词(`dsh-plugin`、`deepseek-harness`),这样插件市场的自动收录能扫到我们。
10
+
11
+ ## 1.1.0 — 2026-11-09
12
+
13
+ - 抓取任何窗口时,会先把该窗口短暂置顶一下,再抓画面,然后立刻放回去。这样 GPU 绘制的窗口(游戏、播放器)也能抓到。抓的那一下屏幕上会闪一下。
14
+ - 读不到时会明确报错,不会拿别的东西顶替。
15
+
5
16
  ## V1.0.0 — 2026-11-09
6
17
 
7
18
  **这是第一个我们认它是 V1 的版本。** 下面那些 1.0.0 / 1.0.1 / 1.1.0 / 1.1.1 都是试验期的编号,实际只到 0.5 的水平——留着当历史,不再对外提。
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Spectacles · 砚明的金丝眼镜
2
2
 
3
- **V1** — one job: point at a window, read its text clearly. Nothing else is in here on purpose.
3
+ **V1.1** — one job: point at a window, read its text clearly. Nothing else is in here on purpose.
4
4
 
5
5
  **Point at one window. Let the AI read it clearly.**
6
6
 
@@ -48,7 +48,13 @@ So the plugin does three things: **where to look, how much to enlarge, and when
48
48
  ## Install
49
49
 
50
50
  ```
51
- dsh plugin --profile desktop add github:yanming119/dsh-spectacles#1.0.0
51
+ dsh plugin --profile desktop add dsh-spectacles
52
+ ```
53
+
54
+ If you'd rather pin the source (no registry), this works too:
55
+
56
+ ```
57
+ dsh plugin --profile desktop add github:yanming119/dsh-spectacles#1.1.0
52
58
  ```
53
59
 
54
60
  Restart DSH. A pill button labelled **戴上眼镜** (put on the glasses) appears in the bottom-right corner.
@@ -138,4 +144,4 @@ You don't choose — just speak plainly.
138
144
 
139
145
  *Spectacles* — in Chinese, 砚明的金丝眼镜 ("Yanming's gold-rimmed glasses"). Yanming is the AI assistant who lives on the author's machine; she's nearsighted, so she got a pair of glasses.
140
146
 
141
- Apache-2.0.
147
+ Apache-2.0.
package/README.zh.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # 砚明的金丝眼镜 · Spectacles
2
2
 
3
- **V1 版** —— 只干一件事:指定一个窗口(或框住它里面的一块),把里面的字读清楚。别的都故意没放进来。
3
+ **V1.1 版** —— 只干一件事:指定一个窗口(或框住它里面的一块),把里面的字读清楚。别的都故意没放进来。
4
4
 
5
5
  **指定一个窗口,让 AI 把里面的字看清楚。**
6
6
 
@@ -48,7 +48,13 @@ DSH 默认只能看你粘进去的东西。这个插件补的是中间那段:*
48
48
  ## 装
49
49
 
50
50
  ```
51
- dsh plugin --profile desktop add github:yanming119/dsh-spectacles#1.0.0
51
+ dsh plugin --profile desktop add dsh-spectacles
52
+ ```
53
+
54
+ 不想走 npm 也行,直接钉源码:
55
+
56
+ ```
57
+ dsh plugin --profile desktop add github:yanming119/dsh-spectacles#1.1.0
52
58
  ```
53
59
 
54
60
  装完重启 DSH,右下角会多出一个胶囊按钮「戴上眼镜」。
@@ -138,4 +144,4 @@ powershell -NoProfile -ExecutionPolicy Bypass -File scripts\selftest.ps1
138
144
 
139
145
  「砚明的金丝眼镜」——砚明是作者家里住在这台电脑上的 AI 助手,她近视,所以给她配了副眼镜。英文名 Spectacles。
140
146
 
141
- 协议 Apache-2.0。
147
+ 协议 Apache-2.0。
package/index.js CHANGED
@@ -179,16 +179,31 @@ function runPs(script, argv) {
179
179
  })
180
180
  }
181
181
 
182
+ function captureHint(cap) {
183
+ if (!cap || cap.ok) return null
184
+ if (cap.error === 'printwindow-failed') {
185
+ return (
186
+ '这个窗口不吃「重画」这条请求(GPU 直绘的窗口都这样),所以抓不到它的画面。' +
187
+ '要么把它切到最前再让我看;要么允许临时置顶抓一帧(DSH_LOOK_RAISE=1,抓的那一下屏幕会闪一下)。'
188
+ )
189
+ }
190
+ return null
191
+ }
192
+
182
193
  function runLook(args, via) {
183
194
  guardWindow('look', args)
184
195
  guardOut(args)
185
196
  const p = runLookRaw(args)
186
197
  return p.then((cap) => {
187
- audit({ tool: 'look', window: args.window || null, crop: args.crop || null, via: via || null, region: (cap && cap.region) || null, png: (cap && cap.png) || null, ok: !!(cap && cap.ok) })
198
+ audit({ tool: 'look', window: args.window || null, crop: args.crop || null, via: via || null, source: (cap && cap.source) || null, region: (cap && cap.region) || null, png: (cap && cap.png) || null, ok: !!(cap && cap.ok) })
199
+ const hint = captureHint(cap)
200
+ if (hint) throw new Error(hint)
188
201
  return cap
189
202
  })
190
203
  }
191
204
 
205
+ const RAISE = String(process.env.DSH_LOOK_RAISE || '') !== '0'
206
+
192
207
  function runLookRaw(args) {
193
208
  if (args.window) {
194
209
  const argv = ['-Window', String(args.window)]
@@ -196,6 +211,8 @@ function runLookRaw(args) {
196
211
  if (typeof args.scale === 'number' && args.scale > 0) argv.push('-Scale', String(args.scale))
197
212
  if (args.image === true) argv.push('-Image')
198
213
  if (args.out) argv.push('-Out', String(args.out))
214
+ // 这个窗口不吃 PrintWindow(GPU 直绘)时,临时把它提到最上层、从屏幕抓它那一块、立刻还原。
215
+ if (RAISE) argv.push('-Raise')
199
216
  return runPs(WIN_SCRIPT, argv)
200
217
  }
201
218
  const argv = []
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-spectacles",
3
- "version": "1.0.0",
3
+ "version": "1.1.2",
4
4
  "description": "Point at one window and let the agent read it clearly. Window scoped screen reading for DeepSeek Harness: look (numbers first), read_text (OCR), see (crop + zoom handed to the host vision).",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -26,7 +26,9 @@
26
26
  ],
27
27
  "keywords": [
28
28
  "deepseek",
29
+ "deepseek-harness",
29
30
  "dsh",
31
+ "dsh-plugin",
30
32
  "plugin",
31
33
  "vision",
32
34
  "look",
@@ -31,19 +31,15 @@ public class Rg {
31
31
 
32
32
  $vs = [System.Windows.Forms.SystemInformation]::VirtualScreen
33
33
 
34
- # 先冻一帧屏:遮罩下面是这张定格,选框里露出原图,框外压暗 —— 不动就能选得准
35
- $bg = New-Object System.Drawing.Bitmap $vs.Width, $vs.Height
36
- $g0 = [System.Drawing.Graphics]::FromImage($bg)
37
- $g0.CopyFromScreen($vs.Left, $vs.Top, 0, 0, (New-Object System.Drawing.Size $vs.Width, $vs.Height))
38
- $g0.Dispose()
39
-
40
- $script:bg = $bg
34
+ # 不抓屏幕:这层遮罩只是一块半透明的、能透过它看见下面东西的玻璃,
35
+ # 用来接你的拖动、记下矩形坐标。框选本身不产生任何画面内容。
36
+ $script:W = $vs.Width
37
+ $script:H = $vs.Height
41
38
  $script:sel = $null
42
39
  $script:dragging = $false
43
40
  $script:done = $false
44
41
  $script:cancelled = $false
45
42
  $script:start = $null
46
- $script:dim = New-Object System.Drawing.SolidBrush ([System.Drawing.Color]::FromArgb(120, 0, 0, 0))
47
43
  $script:pen = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(255, 0, 205, 120)), 2
48
44
  $script:penDot = New-Object System.Drawing.Pen ([System.Drawing.Color]::FromArgb(200, 255, 255, 255)), 1
49
45
  $script:font = New-Object System.Drawing.Font 'Microsoft YaHei UI', 12, ([System.Drawing.FontStyle]::Bold)
@@ -59,13 +55,14 @@ $form.ShowInTaskbar = $false
59
55
  $form.Cursor = [System.Windows.Forms.Cursors]::Cross
60
56
  $form.KeyPreview = $true
61
57
  $form.BackColor = [System.Drawing.Color]::Black
58
+ $form.Opacity = 0.22
62
59
  try { $form.GetType().GetProperty('DoubleBuffered', 'Instance,NonPublic').SetValue($form, $true) } catch {}
63
60
 
64
61
  function Draw-Tag($g, $text, $x, $y) {
65
62
  $sz = $g.MeasureString($text, $script:font)
66
63
  $w = [int]$sz.Width + 16; $h = [int]$sz.Height + 8
67
- $bx = [Math]::Max(0, [Math]::Min($x, $script:bg.Width - $w))
68
- $by = [Math]::Max(0, [Math]::Min($y, $script:bg.Height - $h))
64
+ $bx = [Math]::Max(0, [Math]::Min($x, $script:W - $w))
65
+ $by = [Math]::Max(0, [Math]::Min($y, $script:H - $h))
69
66
  $g.FillRectangle($script:brBox, $bx, $by, $w, $h)
70
67
  $g.DrawString($text, $script:font, $script:brText, ($bx + 8), ($by + 4))
71
68
  }
@@ -73,19 +70,12 @@ function Draw-Tag($g, $text, $x, $y) {
73
70
  $form.Add_Paint({
74
71
  param($s, $e)
75
72
  $g = $e.Graphics
76
- $g.DrawImageUnscaled($script:bg, 0, 0)
77
73
  $r = $script:sel
78
74
  if ($r -and $r.Width -gt 0 -and $r.Height -gt 0) {
79
- $W = $script:bg.Width; $H = $script:bg.Height
80
- $g.FillRectangle($script:dim, 0, 0, $W, [Math]::Max(0, $r.Top))
81
- $g.FillRectangle($script:dim, 0, $r.Bottom, $W, [Math]::Max(0, $H - $r.Bottom))
82
- $g.FillRectangle($script:dim, 0, $r.Top, [Math]::Max(0, $r.Left), $r.Height)
83
- $g.FillRectangle($script:dim, $r.Right, $r.Top, [Math]::Max(0, $W - $r.Right), $r.Height)
84
75
  $g.DrawRectangle($script:pen, $r)
85
76
  Draw-Tag $g ("{0} x {1}" -f $r.Width, $r.Height) ($r.Left + 6) ($r.Top + 6)
86
77
  }
87
78
  else {
88
- $g.FillRectangle($script:dim, 0, 0, $script:bg.Width, $script:bg.Height)
89
79
  Draw-Tag $g '拖一个框;Esc 取消' 40 40
90
80
  }
91
81
  })
package/scripts/look2.ps1 CHANGED
@@ -3,6 +3,7 @@
3
3
  [string]$Crop = "",
4
4
  [double]$Scale = 1.0,
5
5
  [switch]$Image,
6
+ [switch]$Raise,
6
7
  [switch]$List,
7
8
  [string]$Out = "",
8
9
  [string]$SaveDir = ""
@@ -28,6 +29,7 @@ public class W2 {
28
29
  [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr h, out uint pid);
29
30
  [DllImport("user32.dll")] public static extern int GetWindowLong(IntPtr h, int idx);
30
31
  [DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr h, IntPtr hdc, uint flags);
32
+ [DllImport("user32.dll", SetLastError = true)] public static extern bool SetWindowPos(IntPtr h, IntPtr after, int x, int y, int cx, int cy, uint flags);
31
33
 
32
34
  public struct RECT { public int Left, Top, Right, Bottom; }
33
35
 
@@ -117,7 +119,36 @@ $hdc = $g.GetHdc()
117
119
  $ok = [W2]::PrintWindow($w.H, $hdc, 2)
118
120
  $g.ReleaseHdc($hdc)
119
121
  $g.Dispose()
120
- if (-not $ok) { @{ ok = $false; error = "PrintWindow failed" } | ConvertTo-Json -Compress; exit 1 }
122
+ $source = 'printwindow'
123
+ if (-not $ok) {
124
+ if (-not $Raise) {
125
+ @{ ok = $false; error = 'printwindow-failed' } | ConvertTo-Json -Compress
126
+ exit 1
127
+ }
128
+ # 这个窗口不吃「重画」这条请求(GPU 直绘常见)。临时把它提到最上层,从屏幕抓它那一块,再立刻还原。
129
+ # 不抢焦点(NOACTIVATE)、不移动、不改大小。
130
+ $exBefore = [W2]::GetWindowLong($w.H, -20)
131
+ $wasTop = (($exBefore -band 0x0008) -ne 0)
132
+ $raised = [W2]::SetWindowPos($w.H, [IntPtr](-1), 0, 0, 0, 0, (0x0002 -bor 0x0001 -bor 0x0010))
133
+ if (-not $raised) {
134
+ # 置顶被拒(有些游戏/带反截屏保护的程序会拒绝 SetWindowPos)。这时屏幕那块是别的窗口,抓了就是错图,宁可报错。
135
+ $werr = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
136
+ @{ ok = $false; error = 'raise-failed'; win32Error = $werr; hint = '这个窗口不让被提到最前,此时抓屏会抓到压在上面的窗口,所以没有出图' } | ConvertTo-Json -Compress
137
+ exit 1
138
+ }
139
+ try {
140
+ Start-Sleep -Milliseconds 90
141
+ $g2 = [System.Drawing.Graphics]::FromImage($full)
142
+ $sz = New-Object System.Drawing.Size ([int]$ww), ([int]$wh)
143
+ $g2.CopyFromScreen([int]$w.L, [int]$w.T, 0, 0, $sz)
144
+ $g2.Dispose()
145
+ $source = 'screen-raised'
146
+ } finally {
147
+ if (-not $wasTop) {
148
+ [W2]::SetWindowPos($w.H, [IntPtr](-2), 0, 0, 0, 0, (0x0002 -bor 0x0001 -bor 0x0010)) | Out-Null
149
+ }
150
+ }
151
+ }
121
152
 
122
153
  # 可选区域裁剪(相对窗口左上角)
123
154
  $rect = New-Object System.Drawing.Rectangle 0, 0, $ww, $wh
@@ -198,7 +229,7 @@ $avg = "#{0:X2}{1:X2}{2:X2}" -f [int]($sumR / $n), [int]($sumG / $n), [int]($sum
198
229
  $res = [ordered]@{
199
230
  ok = $true
200
231
  when = (Get-Date -Format 'yyyy-MM-dd HH:mm:ss.fff')
201
- source = 'printwindow'
232
+ source = $source
202
233
  window = [ordered]@{ hwnd = [int64]$w.H; title = $w.Title; pid = $w.Pid; rect = @($w.L, $w.T, $ww, $wh) }
203
234
  region = @($rect.X, $rect.Y, $rect.Width, $rect.Height)
204
235
  avgColor = $avg