dsh-locator 0.5.2 → 0.6.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/CHANGELOG.md +13 -0
- package/LICENSE +0 -0
- package/README-es.md +0 -0
- package/README-hi.md +0 -0
- package/README-pt.md +0 -0
- package/README.en.md +5 -0
- package/README.md +109 -153
- package/SECURITY.md +0 -0
- package/bin/darwin-arm64/indexd +0 -0
- package/bin/darwin-x64/indexd +0 -0
- package/bin/linux-arm64/indexd +0 -0
- package/bin/linux-x64/indexd +0 -0
- package/bin/win32-arm64/indexd.exe +0 -0
- package/bin/win32-x64/indexd.exe +0 -0
- package/client.js +83 -16
- package/cordis.patch.yml +0 -0
- package/docs/assets/architecture.svg +0 -0
- package/docs/assets/panel-en.png +0 -0
- package/docs/assets/panel-zh.png +0 -0
- package/lib/config.d.ts +12 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +6 -0
- package/lib/config.js.map +1 -1
- package/lib/content.d.ts +56 -0
- package/lib/content.d.ts.map +1 -0
- package/lib/content.js +274 -0
- package/lib/content.js.map +1 -0
- package/lib/export.d.ts +0 -0
- package/lib/export.d.ts.map +0 -0
- package/lib/export.js +0 -0
- package/lib/export.js.map +0 -0
- package/lib/find.d.ts +8 -0
- package/lib/find.d.ts.map +1 -0
- package/lib/find.js +75 -0
- package/lib/find.js.map +1 -0
- package/lib/http.d.ts +2 -1
- package/lib/http.d.ts.map +1 -1
- package/lib/http.js +12 -1
- package/lib/http.js.map +1 -1
- package/lib/index.d.ts +0 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +33 -1
- package/lib/index.js.map +1 -1
- package/lib/paths.d.ts +0 -0
- package/lib/paths.d.ts.map +0 -0
- package/lib/paths.js +0 -0
- package/lib/paths.js.map +0 -0
- package/lib/reveal.d.ts +0 -0
- package/lib/reveal.d.ts.map +0 -0
- package/lib/reveal.js +0 -0
- package/lib/reveal.js.map +0 -0
- package/lib/runtime.d.ts +0 -0
- package/lib/runtime.d.ts.map +1 -1
- package/lib/runtime.js +2 -0
- package/lib/runtime.js.map +1 -1
- package/lib/tools.d.ts +0 -0
- package/lib/tools.d.ts.map +0 -0
- package/lib/tools.js +0 -0
- package/lib/tools.js.map +0 -0
- package/package.json +1 -1
- package/src/config.ts +18 -0
- package/src/content.ts +253 -0
- package/src/export.ts +0 -0
- package/src/find.ts +76 -0
- package/src/http.ts +13 -1
- package/src/index.ts +30 -1
- package/src/paths.ts +0 -0
- package/src/reveal.ts +0 -0
- package/src/runtime.ts +1 -0
- package/src/tools.ts +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.6.0 — 2026-11-10
|
|
4
|
+
|
|
5
|
+
* Content search: text-file bodies and DSH session messages are indexed into a local SQLite FTS5 database; `locator_find` returns snippets plus `sessionId`/`messageId`.
|
|
6
|
+
* The panel has a “搜内容 / Content” mode. File hits open/reveal; session hits call `uiWorkspace.openSession(sessionId)` and scroll to the matched text.
|
|
7
|
+
* New `/dsh-locator/content` route; content build runs after the name scan and reports progress.
|
|
8
|
+
* Chinese README rewritten from scratch in natural Chinese, with the new content/jump behaviour documented.
|
|
9
|
+
|
|
10
|
+
## 0.5.3 — 2026-11-10
|
|
11
|
+
|
|
12
|
+
* `memoryLimitMB` config (and `indexd --memory-limit-mb`) applies a soft Go heap limit; 0 leaves `GOMEMLIMIT`/`GOGC` in charge.
|
|
13
|
+
* Panel close glyph is `×` instead of the missing `✕` symbol.
|
|
14
|
+
* Published through the provenance workflow (`npm publish --provenance`).
|
|
15
|
+
|
|
3
16
|
## 0.5.2 — 2026-11-10
|
|
4
17
|
|
|
5
18
|
* Replaced both README screenshots with demo data (`/tmp/demo`); the previous images exposed a home-directory path and have been withdrawn (npm 0.5.1 unpublished, tag/history rewritten).
|
package/LICENSE
CHANGED
|
File without changes
|
package/README-es.md
CHANGED
|
File without changes
|
package/README-hi.md
CHANGED
|
File without changes
|
package/README-pt.md
CHANGED
|
File without changes
|
package/README.en.md
CHANGED
|
@@ -96,6 +96,10 @@ Press `Ctrl+Shift+F` anywhere in the Web GUI, or click the magnifier at the side
|
|
|
96
96
|
| `locator_rebuild` | Replace the index with a fresh background scan. Searches keep using the old index until the new one is ready. |
|
|
97
97
|
| `locator_export` | Write a read-only SQLite mirror of the index (`entries` table) for external SQL tools. Streams page by page; the binary snapshot stays primary. |
|
|
98
98
|
|
|
99
|
+
### Content search
|
|
100
|
+
|
|
101
|
+
`locator_find` searches the **bodies** of text files and DSH session messages through a local SQLite FTS5 index. Hits return a snippet plus `sessionId`/`messageId` for conversations, so the panel can open the session and scroll to the matched text. The index is built in the background after the name scan; text files are capped by `contentMaxFileBytes`, and the most recent `contentMaxSessions` session logs are indexed by default.
|
|
102
|
+
|
|
99
103
|
### Query language
|
|
100
104
|
|
|
101
105
|
Bare words are ANDed substrings of the file name. Everything-style filters combine with them:
|
|
@@ -128,6 +132,7 @@ Every field is a Schemastery config field; edit the `dsh-locator` entry in the p
|
|
|
128
132
|
| `watch` | `true` | Apply native filesystem notifications. |
|
|
129
133
|
| `refreshIntervalMs` | `1800000` | Rebuild cadence when no native watcher is active (kqueue fallback or a watcher failure); `0` disables. |
|
|
130
134
|
| `scanWorkers` | `0` | Directory reader concurrency during scans; `0` uses the CPU count. |
|
|
135
|
+
| `memoryLimitMB` | `0` | Soft Go heap limit for the engine in MiB; `0` leaves it to `GOMEMLIMIT`/`GOGC`. |
|
|
131
136
|
| `windowsUsn` | `false` | Try the NTFS USN/MFT enumeration before the walk (Windows, administrator; falls back automatically). |
|
|
132
137
|
| `stateDir` | `''` | Snapshot directory; empty means the platform cache directory. |
|
|
133
138
|
| `binaryPath` | `''` | Override the bundled engine (development). |
|
package/README.md
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
# 🔎 dsh-locator
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
**给 DeepSeek Harness 的全盘文件搜索:先按名字找,再按内容找,找到就能打开。**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
索引由插件自己维护,不装 Everything,也不依赖 `es.exe`。Windows、macOS、Linux 用同一套工具和同一个面板。
|
|
8
8
|
|
|
9
9
|
[](https://www.npmjs.com/package/dsh-locator)
|
|
10
10
|
[](https://www.npmjs.com/package/dsh-locator)
|
|
@@ -24,19 +24,21 @@ dsh plugin --profile web add dsh-locator
|
|
|
24
24
|
|
|
25
25
|
</div>
|
|
26
26
|
|
|
27
|
-
>
|
|
28
|
-
>
|
|
29
|
-
> 这是 [DSH 插件家族](https://github.com/PerryLink) 的一员(Apache-2.0)。如果它帮到了你,**给个 ⭐**——不会解锁任何功能,但会让下一个人更容易找到它。
|
|
27
|
+
> 官方仓库是 [GitHub `PerryLink/dsh-locator`](https://github.com/PerryLink/dsh-locator),Gitee 和 GitCode 是自动同步的镜像。这个插件属于 [DSH 插件家族](https://github.com/PerryLink),Apache-2.0;如果它帮你省了时间,点个 ⭐ 就行。
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
## 它解决什么问题
|
|
32
30
|
|
|
33
|
-
|
|
31
|
+
找文件通常分两种情况。
|
|
34
32
|
|
|
35
|
-
|
|
33
|
+
一种是你记得名字,但不记得放在哪。`glob` 和 `grep` 每次都要重新遍历工作区,知道目录时很快,只记得半个文件名时就很慢。Everything 在 Windows 上很好用,可惜只支持 Windows;把它包一层,macOS、Linux、WSL 和远程机器仍然用不了。
|
|
36
34
|
|
|
37
|
-
|
|
35
|
+
另一种是你记得某句话、某个结论、某段代码,却想不起在哪个文件、哪次对话里。这时只搜文件名没有用。
|
|
38
36
|
|
|
39
|
-
`
|
|
37
|
+
`dsh-locator` 把这两件事放在一起:
|
|
38
|
+
|
|
39
|
+
- 一份常驻的文件名/路径索引,百万条记录也能在几十毫秒内返回;
|
|
40
|
+
- 一份内容索引,覆盖文本文件和 DSH 会话消息,支持中文和子串;
|
|
41
|
+
- 找到之后可以直接打开文件、定位到文件夹,或者跳到对应的会话。
|
|
40
42
|
|
|
41
43
|
## 安装
|
|
42
44
|
|
|
@@ -44,173 +46,140 @@ dsh plugin --profile web add dsh-locator
|
|
|
44
46
|
# npm(推荐)
|
|
45
47
|
dsh plugin --profile web add dsh-locator
|
|
46
48
|
|
|
47
|
-
# GitHub
|
|
49
|
+
# 直接跟 GitHub 主仓
|
|
48
50
|
dsh plugin --profile web add github:PerryLink/dsh-locator
|
|
49
51
|
|
|
50
52
|
# 从源码
|
|
51
53
|
pnpm install
|
|
52
|
-
pnpm build
|
|
53
|
-
pnpm build:engine
|
|
54
|
+
pnpm build
|
|
55
|
+
pnpm build:engine
|
|
54
56
|
dsh plugin --profile web add /绝对路径/dsh-locator
|
|
55
57
|
|
|
56
|
-
#
|
|
58
|
+
# 从 tarball
|
|
57
59
|
npm pack
|
|
58
|
-
dsh plugin --profile web add /绝对路径/dsh-locator-0.
|
|
60
|
+
dsh plugin --profile web add /绝对路径/dsh-locator-0.6.0.tgz
|
|
59
61
|
```
|
|
60
62
|
|
|
61
|
-
npm
|
|
63
|
+
npm 包里已经带了六个平台的预编译引擎:`linux-x64`、`linux-arm64`、`win32-x64`、`win32-arm64`、`darwin-x64`、`darwin-arm64`。
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
如果装进一个**正在运行**的 Web profile,装完刷新一下页面(F5)。宿主工具可能会热挂载,但浏览器面板是页面加载时从 boot graph 里取的。headless/TUI profile 不会显示面板。
|
|
64
66
|
|
|
65
|
-
##
|
|
67
|
+
## 第一次运行
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
默认索引你的主目录,快照放在系统缓存目录:
|
|
68
70
|
|
|
69
|
-
| 平台 |
|
|
71
|
+
| 平台 | 默认位置 |
|
|
70
72
|
|---|---|
|
|
71
73
|
| Windows | `%LOCALAPPDATA%\dsh-locator` |
|
|
72
74
|
| macOS | `~/Library/Application Support/dsh-locator` |
|
|
73
75
|
| Linux | `$XDG_CACHE_HOME/dsh-locator` 或 `~/.cache/dsh-locator` |
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
第一次扫描要读盘,之后启动直接加载快照。扫描期间搜索照常可用:返回已经索引的部分,并带 `indexing: true`,面板底栏也会提示“索引构建中”。
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
默认跳过 `node_modules`、`.git`、`.svn`、`.hg`、`.cache`、`__pycache__`、`.venv`、`venv`、`.next`。
|
|
78
80
|
|
|
79
|
-
|
|
81
|
+
## 两种搜索
|
|
80
82
|
|
|
81
|
-
|
|
83
|
+
### 按名字找:`locator_search`
|
|
82
84
|
|
|
83
|
-
|
|
84
|
-
* 点列头按名称、大小、修改时间、路径排序,再点切换升降序。
|
|
85
|
-
* `只看文件` / `只看目录` 过滤类型;底栏显示命中数、分页上限和查询耗时。
|
|
86
|
-
* `↑`/`↓` 选择,`Enter` 复制选中路径,`Ctrl+O` 打开,`Ctrl+Enter` 把带引号的 `@"路径"` 插入输入框,**双击在文件管理器中定位**(Explorer / Finder / `xdg-open`),`Esc` 关闭。
|
|
87
|
-
* 面板跟随 `navigator.language`(中/英/西/葡/印地语)。
|
|
88
|
-
* 面板是可选的:没有 Web server 的 profile 不会挂载它,三个工具照常工作。
|
|
85
|
+
Everything 风格的查询语法,返回路径、大小、修改时间、扩展名、类型。适合“我记得文件名里有 report”“找上周改过的 PDF”这类问题。
|
|
89
86
|
|
|
90
|
-
|
|
87
|
+
### 按内容找:`locator_find`
|
|
91
88
|
|
|
92
|
-
|
|
93
|
-
|---|---|
|
|
94
|
-
| `locator_search` | 查询索引,返回路径、大小、修改时间、扩展名、类型,并给出 total/truncated,截断页面不会被当成完整结果。 |
|
|
95
|
-
| `locator_status` | 根目录、条目数、监听状态、状态目录与引擎路径。用来解释覆盖范围或排查结果过期。 |
|
|
96
|
-
| `locator_rebuild` | 后台重建索引;新索引就绪前,搜索继续用旧索引。 |
|
|
97
|
-
| `locator_export` | 把索引导出成只读 SQLite(`entries` 表),供外部 SQL 工具查询;逐页流式导出,二进制快照仍是主存储。 |
|
|
89
|
+
在文本文件和 DSH 会话消息里搜正文,返回命中片段,以及来源信息。
|
|
98
90
|
|
|
99
|
-
|
|
91
|
+
- 文件命中带路径,直接打开或定位;
|
|
92
|
+
- 会话命中带 `sessionId` 和 `messageId`,可以跳回那次对话;
|
|
93
|
+
- 中文和英文子串都能搜,不要求分词。
|
|
100
94
|
|
|
101
|
-
|
|
95
|
+
内容索引在后台构建,刚启动时可能只覆盖了一部分,工具和面板都会显示构建状态。默认只索引 1MB 以内的文本文件,最多 5 万个文件、最近 50 个会话;这些都能在配置里改。
|
|
102
96
|
|
|
103
|
-
|
|
104
|
-
|---|---|
|
|
105
|
-
| `report` | 文件名含 `report` |
|
|
106
|
-
| `report ext:pdf` | 且扩展名是 `pdf` |
|
|
107
|
-
| `ext:ts,js` / `ext:!tmp` | 包含/排除扩展名 |
|
|
108
|
-
| `*.sln` / `?ata` | `*`、`?` 通配 |
|
|
109
|
-
| `"annual report"` | 带空格的短语 |
|
|
110
|
-
| `!archive` | 取反 |
|
|
111
|
-
| `path:src` | 完整路径含 `src` |
|
|
112
|
-
| `size:>10mb` / `size:1kb..2mb` | 大小比较 |
|
|
113
|
-
| `dm:today` / `dm:thisweek` / `dm:2026-01-01` | 修改时间 |
|
|
114
|
-
| `folder:` / `file:` | 只看目录 / 只看文件 |
|
|
115
|
-
| `case:1` / `wholeword:1` | 区分大小写 / 全词匹配 |
|
|
116
|
-
|
|
117
|
-
`locator_search` 还接受 `sort`(`name`、`path`、`size`、`mtime`、`ext`)、`desc`、`offset`、`limit`、`regex`、`caseSensitive`、`wholeWord`。
|
|
97
|
+
## Web 面板
|
|
118
98
|
|
|
119
|
-
|
|
99
|
+

|
|
120
100
|
|
|
121
|
-
|
|
101
|
+
在 Web GUI 里按 `Ctrl+Shift+F`,或者点侧栏底部的放大镜。
|
|
122
102
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
| `refreshIntervalMs` | `1800000` | 没有原生监听时(kqueue 回退或监听失败)的重建周期;0 关闭。 |
|
|
130
|
-
| `scanWorkers` | `0` | 扫描时的目录读取并发;0 = CPU 数。 |
|
|
131
|
-
| `windowsUsn` | `false` | 扫描时先试 NTFS USN/MFT 枚举(Windows,需管理员;失败自动回退)。 |
|
|
132
|
-
| `stateDir` | `''` | 快照目录;空 = 平台缓存目录。 |
|
|
133
|
-
| `binaryPath` | `''` | 覆盖内置引擎路径(开发用)。 |
|
|
134
|
-
| `maxResults` | `100` | 单页硬上限。 |
|
|
135
|
-
| `startupTimeoutMs` | `120000` | 冷启动建索引的容忍时间。 |
|
|
136
|
-
| `requestTimeoutMs` | `15000` | 单次请求超时。 |
|
|
137
|
-
|
|
138
|
-
主目录太大就收窄 `roots`:`~/Documents`、`~/Downloads` 加一两棵代码树,通常比全盘更划算。
|
|
103
|
+
- 空查询按修改时间列出最近的条目;输入后 110ms 防抖,和工具用同一套语法。
|
|
104
|
+
- 点列头可以按名称、大小、修改时间、路径排序。
|
|
105
|
+
- 勾选“只看文件”“只看目录”过滤类型;勾选“搜内容”切换到正文/会话搜索。
|
|
106
|
+
- `↑`/`↓` 选择,`Enter` 复制路径,`Ctrl+O` 打开,`Ctrl+Enter` 把 `@"路径"` 插进输入框。
|
|
107
|
+
- 双击文件结果会在文件管理器里定位;双击会话结果会打开对应会话,并尝试滚动到命中的那句话。
|
|
108
|
+
- 面板文案跟随 `navigator.language`(中/英/西/葡/印地语)。
|
|
139
109
|
|
|
140
|
-
|
|
110
|
+
面板通过几个同源路由工作:`/dsh-locator/search`、`/dsh-locator/content`、`/dsh-locator/status`、`/dsh-locator/reveal`、`/dsh-locator/open`。不另起服务,也不占额外端口。
|
|
141
111
|
|
|
142
|
-
|
|
143
|
-
|---|---|---|---|
|
|
144
|
-
| Linux | 是 | inotify,递归 | 每个目录一个 watch;极大目录树可能撞 `fs.inotify.max_user_watches`。 |
|
|
145
|
-
| Windows | 是 | `ReadDirectoryChangesW`,递归 | 不需要管理员,不需要 Everything。提权时可选用 USN/MFT 枚举。WSL 写 `/mnt/<盘>` 可能不产生 Windows 通知,用 `locator_rebuild` 补。 |
|
|
146
|
-
| macOS | 是 | 随包发布的 cgo 二进制使用 FSEvents(递归、无上限) | `CGO_ENABLED=0` 构建回退到 kqueue(最多 4,096 个目录);回退模式下内容变化仍由 `refreshIntervalMs` 或 `locator_rebuild` 刷新。 |
|
|
112
|
+
## 工具一览
|
|
147
113
|
|
|
148
|
-
|
|
114
|
+
| 工具 | 作用 |
|
|
115
|
+
|---|---|
|
|
116
|
+
| `locator_search` | 按文件名/路径搜索,支持 `ext:`、`size:`、`dm:`、`path:` 等过滤。 |
|
|
117
|
+
| `locator_find` | 按文件正文和会话内容搜索,返回片段与 `sessionId`/`messageId`。 |
|
|
118
|
+
| `locator_status` | 查看根目录、条目数、监听状态、内容索引进度和引擎版本。 |
|
|
119
|
+
| `locator_rebuild` | 后台重建索引;新索引就绪前,搜索继续使用旧的。 |
|
|
120
|
+
| `locator_export` | 把文件名索引导出成只读 SQLite,方便外部工具查询。 |
|
|
149
121
|
|
|
150
|
-
###
|
|
122
|
+
### 查询语法
|
|
151
123
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
124
|
+
```text
|
|
125
|
+
report 文件名里同时包含这些词
|
|
126
|
+
"two words" 完整短语
|
|
127
|
+
*.go ? 通配符
|
|
128
|
+
ext:pdf,docx 按扩展名
|
|
129
|
+
ext:!tmp 排除某类扩展名
|
|
130
|
+
path:src 路径里包含 src
|
|
131
|
+
size:>10mb 1kb..2mb 按大小
|
|
132
|
+
dm:today dm:thisweek 按修改日期
|
|
133
|
+
folder: file: 只看目录 / 只看文件
|
|
134
|
+
!tmp 排除包含 tmp 的
|
|
135
|
+
case:1 wholeword:1 区分大小写 / 全词匹配
|
|
136
|
+
```
|
|
157
137
|
|
|
158
|
-
|
|
138
|
+
## 配置
|
|
159
139
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
140
|
+
| 配置项 | 默认值 | 说明 |
|
|
141
|
+
|---|---|---|
|
|
142
|
+
| `roots` | 主目录 | 要索引的目录。 |
|
|
143
|
+
| `excludes` | `node_modules` 等 | 按目录名或路径跳过的 glob。 |
|
|
144
|
+
| `watch` | `true` | 是否监听文件系统变化。 |
|
|
145
|
+
| `refreshIntervalMs` | 30 分钟 | 没有原生监听时的重建周期。 |
|
|
146
|
+
| `scanWorkers` | 0 | 扫描并发;0 表示按 CPU 数。 |
|
|
147
|
+
| `windowsUsn` | `false` | Windows 上尝试 USN/MFT 快路径,需要管理员权限。 |
|
|
148
|
+
| `memoryLimitMB` | 0 | 引擎的 Go 软堆上限;0 表示交给 `GOMEMLIMIT`/`GOGC`。 |
|
|
149
|
+
| `contentIndex` | `true` | 是否构建文件正文/会话内容索引。 |
|
|
150
|
+
| `contentMaxFileBytes` | 1000000 | 单个文本文件最多读多少字节。 |
|
|
151
|
+
| `contentMaxFiles` | 50000 | 最多索引多少个文件;0 表示不限制。 |
|
|
152
|
+
| `contentMaxSessions` | 50 | 最多索引最近多少个会话;0 表示全部。 |
|
|
153
|
+
| `contentSessionDirs` | `$DSH_HOME/sessions` | 额外的会话目录。 |
|
|
154
|
+
| `stateDir` | 系统缓存 | 快照和内容数据库的位置。 |
|
|
164
155
|
|
|
165
|
-
|
|
156
|
+
## 平台支持
|
|
166
157
|
|
|
167
|
-
|
|
158
|
+
| 平台 | 扫描 | 实时更新 |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| Linux | 并行遍历 | inotify,递归监听 |
|
|
161
|
+
| Windows | 并行遍历,或管理员下的 USN/MFT | `ReadDirectoryChangesW`;USN 模式下重启会补日志 |
|
|
162
|
+
| macOS | 并行遍历 | 发布版二进制用 FSEvents;`CGO_ENABLED=0` 构建回退到 kqueue |
|
|
168
163
|
|
|
169
|
-
|
|
164
|
+
Windows 的 USN 快路径默认关闭(`windowsUsn: true`)。它需要管理员权限和启用中的卷变更日志;任何一步失败都会回退到普通遍历,不会让搜索不可用。USN 扫描成功后会记下日志游标,重启时先补上停机期间的变更,再开始服务。
|
|
170
165
|
|
|
171
166
|
## 性能
|
|
172
167
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
| 冷扫描(未命中页缓存) | 50.3 s |
|
|
178
|
-
| 热扫描(页缓存命中,8 个读取协程) | 4.6–6.5 s(约 190–265k 条目/秒) |
|
|
179
|
-
| 快照大小 | 133 MB |
|
|
180
|
-
| 扫描峰值 RSS | 约 490 MB |
|
|
181
|
-
| 首次扫描进行中的第一次搜索 | 2 ms(部分结果,`indexing: true`) |
|
|
182
|
-
| `locator_search "report"`(981 命中) | 34 ms |
|
|
183
|
-
| `locator_search "package.json"`(1,287 命中) | 25 ms |
|
|
184
|
-
| 查询进程 RSS(120 万条目) | 约 197 MB |
|
|
185
|
-
| 小目录树(3.9 万条目)扫描 | 0.47 s |
|
|
186
|
-
|
|
187
|
-
查询阶段按记录分片并行;冷扫描慢在磁盘。数字随文件系统、缓存和 CPU 变化。
|
|
168
|
+
- 约 124 万条记录:热查询 25–35ms;扫描进行中查询约 2ms。
|
|
169
|
+
- Windows CI 上 `C:\Windows` 16.4 万条用 USN 索引约 7.8 秒,查询 9.6ms。
|
|
170
|
+
- 导出 124 万行 SQLite:约 477MB,6.4 秒。
|
|
171
|
+
- 内容索引是后台任务,首次构建时间取决于文本文件数量和会话日志大小。
|
|
188
172
|
|
|
189
173
|
## 设计
|
|
190
174
|
|
|
191
|
-

|
|
192
176
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
└─ 子进程:indexd serve(Go,stdio 上的 NDJSON)
|
|
200
|
-
├─ 扫描器:递归遍历、排除、进度
|
|
201
|
-
├─ 索引:名字 arena + 定长记录 + 开放寻址路径表
|
|
202
|
-
├─ 查询:解析、匹配、Top-K 选择、排序、分页
|
|
203
|
-
├─ 监听:inotify(Linux)/ ReadDirectoryChangesW(Windows)
|
|
204
|
-
└─ 快照:单文件、原子重命名
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
* **并行扫描**:目录读取协程消费一个有界任务队列,每个目录批量调用 `AddBatch` 落库,保证父记录先于子记录;队列满时改为在当前协程内联扫描,永不阻塞。
|
|
208
|
-
* **内存布局**:记录只存名字在 arena 中的偏移/长度和父记录 id,完整路径只在结果页和 `path:` 过滤时重建,避免每个文件一个字符串头。
|
|
209
|
-
* **路径表**:开放寻址、槽位存 `id+1`,每条约 12 字节,而 `map[string]int32` 约 50 字节。
|
|
210
|
-
* **查询引擎**:裸词与过滤只编译一次,分片并行匹配,再用有界大顶堆选出 `offset+limit` 行,不排序上百万命中。
|
|
211
|
-
* **持久化**:先写临时文件再重命名;generation 计数让守护进程只在索引变化时自动保存;墓碑超过阈值后压缩。
|
|
212
|
-
* **协议**:stdio 上一行一个 JSON 请求/响应,另有 `scan`、`stats`、`warning`、`error`、`rebuilt` 事件。不开端口、无网络监听面。
|
|
213
|
-
* **只读**:引擎只读元数据,绝不修改被索引的文件。
|
|
177
|
+
- **文件名索引**:Go 引擎维护一份紧凑快照。记录只存名字在 arena 里的偏移和父记录 id,完整路径在返回结果时重建。
|
|
178
|
+
- **并行扫描**:目录读取协程消费有界队列,每个目录批量写入,保证父目录先于子目录。
|
|
179
|
+
- **查询**:解析一次查询,分片并行匹配,用有界堆选出前 `offset+limit` 条,不排序全部命中。
|
|
180
|
+
- **内容索引**:宿主侧 SQLite FTS5,trigram 分词,支持中文和子串;文件清单来自引擎已有的扫描结果,会话日志用 Node 自带的 zstd 解压。
|
|
181
|
+
- **跳转**:文件走 `/reveal` 和 `/open`;会话通过 `uiWorkspace.openSession(sessionId)` 打开,再在页面里定位命中文字。
|
|
182
|
+
- **协议**:引擎通过 stdio 上的 NDJSON 通信,不监听端口;Web 面板只走同源宿主路由。
|
|
214
183
|
|
|
215
184
|
## 从源码构建
|
|
216
185
|
|
|
@@ -218,38 +187,25 @@ DeepSeek Harness
|
|
|
218
187
|
|
|
219
188
|
```sh
|
|
220
189
|
pnpm install
|
|
221
|
-
pnpm typecheck
|
|
222
|
-
pnpm test
|
|
223
190
|
pnpm build
|
|
224
|
-
pnpm build:engine:all
|
|
191
|
+
pnpm build:engine:all
|
|
192
|
+
pnpm check
|
|
225
193
|
```
|
|
226
194
|
|
|
227
|
-
|
|
195
|
+
Go 引擎在 `engine/` 下,测试用 `cd engine && go test ./...`。macOS 的 FSEvents 版本需要 cgo,由 macOS CI 构建。
|
|
228
196
|
|
|
229
197
|
## 限制
|
|
230
198
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
## 路线图
|
|
239
|
-
|
|
240
|
-
1. macOS FSEvents 监听。
|
|
241
|
-
2. 有卷读取权限时的 Windows USN/MFT 快速枚举;停机后按 USN 日志补齐。
|
|
242
|
-
3. 专用 Web GUI 面板(表格、列排序、过滤、预览、插入为 `@file`)。
|
|
243
|
-
4. 可选的小写名字 arena 与 trigram 预筛。
|
|
244
|
-
5. 增量快照压缩与按根目录的日志。
|
|
245
|
-
|
|
246
|
-
## 内存
|
|
247
|
-
|
|
248
|
-
引擎是单个 Go 进程,遵循标准 `GOMEMLIMIT` / `GOGC` 环境变量。124 万条索引在默认 GC 下峰值约 700–800MB RSS;内存紧张时设 `GOMEMLIMIT=600MiB`(软上限,运行时用 CPU 换内存)或 `GOGC=50`。都不改变快照格式。
|
|
199
|
+
- 内容索引只处理文本类文件;PDF、Word、图片里的文字暂时不提取。
|
|
200
|
+
- 文件名索引不是文件系统驱动,只覆盖配置的根目录。
|
|
201
|
+
- Linux inotify 每个目录占一个描述符,目录特别多时可能碰到内核上限;插件会报警,快照仍然可用。
|
|
202
|
+
- 通过 WSL 写入 `/mnt/c` 的文件,Windows 端可能收不到通知,需要重建对应根目录。
|
|
203
|
+
- 会话内容来自本地日志;如果会话还没落盘,搜不到最新几条消息。
|
|
204
|
+
- macOS 的发布版二进制用 FSEvents;`CGO_ENABLED=0` 构建只有 kqueue,目录上限 4096。
|
|
249
205
|
|
|
250
206
|
## 安全与隐私
|
|
251
207
|
|
|
252
|
-
|
|
208
|
+
索引完全在本地,不联网、不上传、不埋点。快照和内容数据库里有文件名、路径和正文,请把它们当作私有数据。插件不会修改、移动或删除被索引的文件。
|
|
253
209
|
|
|
254
210
|
## 许可证
|
|
255
211
|
|
package/SECURITY.md
CHANGED
|
File without changes
|
package/bin/darwin-arm64/indexd
CHANGED
|
Binary file
|
package/bin/darwin-x64/indexd
CHANGED
|
Binary file
|
package/bin/linux-arm64/indexd
CHANGED
|
Binary file
|
package/bin/linux-x64/indexd
CHANGED
|
Binary file
|
|
Binary file
|
package/bin/win32-x64/indexd.exe
CHANGED
|
Binary file
|
package/client.js
CHANGED
|
@@ -108,6 +108,7 @@ window.__ModuleLoader__.load({
|
|
|
108
108
|
// ── i18n ────────────────────────────────────────────────────────────────
|
|
109
109
|
const STRINGS = {
|
|
110
110
|
en: {
|
|
111
|
+
contentMode: 'Content', sessionBadge: 'CHAT', jumped: 'Opened session',
|
|
111
112
|
placeholder: 'Search file names or paths… e.g. ext:pdf size:>10mb dm:thisweek path:src !tmp',
|
|
112
113
|
searchLabel: 'Search files', dialog: 'Local file search', closeTitle: 'Close (Esc)', closeLabel: 'Close',
|
|
113
114
|
filesOnly: 'Files only', dirsOnly: 'Directories only', statusLoading: 'Reading index status…',
|
|
@@ -121,6 +122,7 @@ window.__ModuleLoader__.load({
|
|
|
121
122
|
justNow: 'just now', minutesAgo: '{n} min ago', hoursAgo: '{n} h ago',
|
|
122
123
|
},
|
|
123
124
|
zh: {
|
|
125
|
+
contentMode: '搜内容', sessionBadge: '会话', jumped: '已打开会话',
|
|
124
126
|
placeholder: '搜索文件名或路径… 支持 ext:pdf size:>10mb dm:thisweek path:src !tmp',
|
|
125
127
|
searchLabel: '文件搜索', dialog: '本地文件搜索', closeTitle: '关闭 (Esc)', closeLabel: '关闭',
|
|
126
128
|
filesOnly: '只看文件', dirsOnly: '只看目录', statusLoading: '索引状态读取中…',
|
|
@@ -134,6 +136,7 @@ window.__ModuleLoader__.load({
|
|
|
134
136
|
justNow: '刚刚', minutesAgo: '{n} 分钟前', hoursAgo: '{n} 小时前',
|
|
135
137
|
},
|
|
136
138
|
es: {
|
|
139
|
+
contentMode: 'Contenido', sessionBadge: 'CHAT', jumped: 'Sesión abierta',
|
|
137
140
|
placeholder: 'Busca nombres o rutas… p. ej. ext:pdf size:>10mb dm:thisweek path:src !tmp',
|
|
138
141
|
searchLabel: 'Buscar archivos', dialog: 'Búsqueda local de archivos', closeTitle: 'Cerrar (Esc)', closeLabel: 'Cerrar',
|
|
139
142
|
filesOnly: 'Solo archivos', dirsOnly: 'Solo carpetas', statusLoading: 'Leyendo el estado del índice…',
|
|
@@ -147,6 +150,7 @@ window.__ModuleLoader__.load({
|
|
|
147
150
|
justNow: 'ahora', minutesAgo: 'hace {n} min', hoursAgo: 'hace {n} h',
|
|
148
151
|
},
|
|
149
152
|
pt: {
|
|
153
|
+
contentMode: 'Conteúdo', sessionBadge: 'CHAT', jumped: 'Sessão aberta',
|
|
150
154
|
placeholder: 'Busque nomes ou caminhos… ex. ext:pdf size:>10mb dm:thisweek path:src !tmp',
|
|
151
155
|
searchLabel: 'Buscar arquivos', dialog: 'Busca local de arquivos', closeTitle: 'Fechar (Esc)', closeLabel: 'Fechar',
|
|
152
156
|
filesOnly: 'Somente arquivos', dirsOnly: 'Somente pastas', statusLoading: 'Lendo o estado do índice…',
|
|
@@ -160,6 +164,7 @@ window.__ModuleLoader__.load({
|
|
|
160
164
|
justNow: 'agora', minutesAgo: 'há {n} min', hoursAgo: 'há {n} h',
|
|
161
165
|
},
|
|
162
166
|
hi: {
|
|
167
|
+
contentMode: 'सामग्री', sessionBadge: 'चैट', jumped: 'सत्र खुला',
|
|
163
168
|
placeholder: 'फ़ाइल नाम या पथ खोजें… जैसे ext:pdf size:>10mb dm:thisweek path:src !tmp',
|
|
164
169
|
searchLabel: 'फ़ाइल खोज', dialog: 'स्थानीय फ़ाइल खोज', closeTitle: 'बंद करें (Esc)', closeLabel: 'बंद करें',
|
|
165
170
|
filesOnly: 'केवल फ़ाइलें', dirsOnly: 'केवल फ़ोल्डर', statusLoading: 'इंडेक्स स्थिति पढ़ी जा रही है…',
|
|
@@ -270,6 +275,29 @@ window.__ModuleLoader__.load({
|
|
|
270
275
|
}
|
|
271
276
|
|
|
272
277
|
// ── panel ───────────────────────────────────────────────────────────────
|
|
278
|
+
let workspaceService = null
|
|
279
|
+
|
|
280
|
+
function jumpHighlight(snippet) {
|
|
281
|
+
const phrase = String(snippet || '').replace(/\u0001|\u0002/g, '').replace(/…/g, ' ').trim().split(/\s+/).slice(0, 8).join(' ')
|
|
282
|
+
if (phrase.length < 2) return false
|
|
283
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT)
|
|
284
|
+
let node
|
|
285
|
+
while ((node = walker.nextNode())) {
|
|
286
|
+
const text = node.nodeValue || ''
|
|
287
|
+
const index = text.toLowerCase().indexOf(phrase.toLowerCase())
|
|
288
|
+
if (index < 0) continue
|
|
289
|
+
const element = node.parentElement
|
|
290
|
+
if (!element) continue
|
|
291
|
+
element.scrollIntoView({ block: 'center' })
|
|
292
|
+
const previous = element.style.outline
|
|
293
|
+
element.style.outline = '2px solid #4f7cff'
|
|
294
|
+
element.style.outlineOffset = '4px'
|
|
295
|
+
setTimeout(() => { element.style.outline = previous; element.style.outlineOffset = '' }, 2400)
|
|
296
|
+
return true
|
|
297
|
+
}
|
|
298
|
+
return false
|
|
299
|
+
}
|
|
300
|
+
|
|
273
301
|
const PAGE = 100
|
|
274
302
|
|
|
275
303
|
function Panel() {
|
|
@@ -279,6 +307,7 @@ window.__ModuleLoader__.load({
|
|
|
279
307
|
const [desc, setDesc] = useState(true)
|
|
280
308
|
const [filesOnly, setFilesOnly] = useState(false)
|
|
281
309
|
const [dirsOnly, setDirsOnly] = useState(false)
|
|
310
|
+
const [contentMode, setContentMode] = useState(false)
|
|
282
311
|
const [hits, setHits] = useState([])
|
|
283
312
|
const [total, setTotal] = useState(0)
|
|
284
313
|
const [took, setTook] = useState(0)
|
|
@@ -325,18 +354,23 @@ window.__ModuleLoader__.load({
|
|
|
325
354
|
abortRef.current = controller
|
|
326
355
|
setLoading(true)
|
|
327
356
|
setError('')
|
|
328
|
-
|
|
329
|
-
query,
|
|
330
|
-
limit: PAGE,
|
|
331
|
-
|
|
332
|
-
sort,
|
|
333
|
-
desc,
|
|
334
|
-
filesOnly,
|
|
335
|
-
dirsOnly,
|
|
336
|
-
}, controller.signal).then(data => {
|
|
357
|
+
const request = contentMode
|
|
358
|
+
? post('/dsh-locator/content', { query, limit: PAGE }, controller.signal)
|
|
359
|
+
: post('/dsh-locator/search', { query, limit: PAGE, offset: 0, sort, desc, filesOnly, dirsOnly }, controller.signal)
|
|
360
|
+
request.then(data => {
|
|
337
361
|
if (controller.signal.aborted) return
|
|
338
|
-
|
|
339
|
-
|
|
362
|
+
const nextHits = contentMode
|
|
363
|
+
? (Array.isArray(data.hits) ? data.hits : []).map(hit => ({
|
|
364
|
+
...hit,
|
|
365
|
+
name: hit.title || String(hit.path).split('/').pop() || hit.path,
|
|
366
|
+
dir: false,
|
|
367
|
+
size: 0,
|
|
368
|
+
mtime: 0,
|
|
369
|
+
content: true,
|
|
370
|
+
}))
|
|
371
|
+
: (Array.isArray(data.hits) ? data.hits : []).map(hit => ({ ...hit, content: false }))
|
|
372
|
+
setHits(nextHits)
|
|
373
|
+
setTotal(typeof data.total === 'number' ? data.total : nextHits.length)
|
|
340
374
|
setTook(typeof data.tookMs === 'number' ? data.tookMs : 0)
|
|
341
375
|
setTruncated(data.truncated === true)
|
|
342
376
|
setSelected(0)
|
|
@@ -350,7 +384,7 @@ window.__ModuleLoader__.load({
|
|
|
350
384
|
})
|
|
351
385
|
}, 110)
|
|
352
386
|
return () => clearTimeout(timer)
|
|
353
|
-
}, [open, query, sort, desc, filesOnly, dirsOnly])
|
|
387
|
+
}, [open, query, sort, desc, filesOnly, dirsOnly, contentMode])
|
|
354
388
|
|
|
355
389
|
const toggleSort = useCallback(key => {
|
|
356
390
|
if (sort === key) {
|
|
@@ -376,6 +410,34 @@ window.__ModuleLoader__.load({
|
|
|
376
410
|
post('/dsh-locator/reveal', { path: hit.path }).then(() => notify(t('revealed'))).catch(() => copy(hit))
|
|
377
411
|
}, [copy, notify])
|
|
378
412
|
|
|
413
|
+
const jumpSession = useCallback(hit => {
|
|
414
|
+
if (!hit || hit.kind !== 'session' || !hit.sessionId) return
|
|
415
|
+
close()
|
|
416
|
+
if (!workspaceService || typeof workspaceService.openSession !== 'function') {
|
|
417
|
+
copy(hit)
|
|
418
|
+
return
|
|
419
|
+
}
|
|
420
|
+
try {
|
|
421
|
+
workspaceService.openSession(hit.sessionId)
|
|
422
|
+
notify(t('jumped'))
|
|
423
|
+
} catch {
|
|
424
|
+
copy(hit)
|
|
425
|
+
return
|
|
426
|
+
}
|
|
427
|
+
const snippet = hit.snippet
|
|
428
|
+
let tries = 0
|
|
429
|
+
const timer = setInterval(() => {
|
|
430
|
+
tries++
|
|
431
|
+
if (jumpHighlight(snippet) || tries > 40) clearInterval(timer)
|
|
432
|
+
}, 150)
|
|
433
|
+
}, [close, copy, notify])
|
|
434
|
+
|
|
435
|
+
const activate = useCallback(hit => {
|
|
436
|
+
if (!hit) return
|
|
437
|
+
if (hit.kind === 'session') jumpSession(hit)
|
|
438
|
+
else reveal(hit)
|
|
439
|
+
}, [jumpSession, reveal])
|
|
440
|
+
|
|
379
441
|
const openFile = useCallback(hit => {
|
|
380
442
|
if (!hit || hit.dir) return
|
|
381
443
|
post('/dsh-locator/open', { path: hit.path }).then(() => notify(t('opened'))).catch(() => copy(hit))
|
|
@@ -467,7 +529,7 @@ window.__ModuleLoader__.load({
|
|
|
467
529
|
'aria-label': t('searchLabel'),
|
|
468
530
|
}),
|
|
469
531
|
h('span', { className: 'dsh-locator-kbd' }, 'Ctrl+Shift+F'),
|
|
470
|
-
h('button', { className: 'dsh-locator-close', onClick: close, title: t('closeTitle'), 'aria-label': t('closeLabel') }, '
|
|
532
|
+
h('button', { className: 'dsh-locator-close', onClick: close, title: t('closeTitle'), 'aria-label': t('closeLabel') }, '×'),
|
|
471
533
|
),
|
|
472
534
|
h('div', { className: 'dsh-locator-bar' },
|
|
473
535
|
h('label', { className: 'dsh-locator-check' },
|
|
@@ -476,6 +538,9 @@ window.__ModuleLoader__.load({
|
|
|
476
538
|
h('label', { className: 'dsh-locator-check' },
|
|
477
539
|
h('input', { type: 'checkbox', checked: dirsOnly, onChange: event => { setDirsOnly(event.target.checked); if (event.target.checked) setFilesOnly(false) } }),
|
|
478
540
|
t('dirsOnly')),
|
|
541
|
+
h('label', { className: 'dsh-locator-check' },
|
|
542
|
+
h('input', { type: 'checkbox', checked: contentMode, onChange: event => setContentMode(event.target.checked) }),
|
|
543
|
+
t('contentMode')),
|
|
479
544
|
h('span', { className: 'dsh-locator-status' },
|
|
480
545
|
status === null
|
|
481
546
|
? t('statusLoading')
|
|
@@ -509,11 +574,12 @@ window.__ModuleLoader__.load({
|
|
|
509
574
|
className: 'dsh-locator-row' + (index === selected ? ' sel' : ''),
|
|
510
575
|
title: hit.path,
|
|
511
576
|
onClick: () => setSelected(index),
|
|
512
|
-
onDoubleClick: () =>
|
|
577
|
+
onDoubleClick: () => activate(hit),
|
|
513
578
|
},
|
|
514
579
|
h('td', null, h('div', { className: 'dsh-locator-name' },
|
|
515
|
-
h('span', { className: 'dsh-locator-badge' }, hit.
|
|
516
|
-
h('span', null, highlight(hit.name, query)),
|
|
580
|
+
h('span', { className: 'dsh-locator-badge' }, hit.kind === 'session' ? t('sessionBadge') : (hit.ext || t('fileBadge')).toUpperCase()),
|
|
581
|
+
h('span', null, hit.content ? hit.name : highlight(hit.name, query)),
|
|
582
|
+
hit.content && hit.snippet ? h('span', { className: 'dsh-locator-snippet' }, hit.snippet.slice(0, 180)) : null,
|
|
517
583
|
)),
|
|
518
584
|
h('td', { className: 'dsh-locator-size' }, hit.dir ? '' : formatSize(hit.size)),
|
|
519
585
|
h('td', { className: 'dsh-locator-time' }, formatTime(hit.mtime)),
|
|
@@ -549,6 +615,7 @@ window.__ModuleLoader__.load({
|
|
|
549
615
|
name: 'dsh-locator-client',
|
|
550
616
|
inject: ['slots'],
|
|
551
617
|
apply(ctx) {
|
|
618
|
+
workspaceService = typeof ctx.get === 'function' ? ctx.get('uiWorkspace') : null
|
|
552
619
|
ensureStyles()
|
|
553
620
|
ctx.effect(() => {
|
|
554
621
|
const onKey = event => {
|
package/cordis.patch.yml
CHANGED
|
File without changes
|
|
File without changes
|
package/docs/assets/panel-en.png
CHANGED
|
File without changes
|
package/docs/assets/panel-zh.png
CHANGED
|
File without changes
|