dsh-locator 0.5.3 → 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 CHANGED
@@ -1,5 +1,12 @@
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
+
3
10
  ## 0.5.3 — 2026-11-10
4
11
 
5
12
  * `memoryLimitMB` config (and `indexd --memory-limit-mb`) applies a soft Go heap limit; 0 leaves `GOMEMLIMIT`/`GOGC` in charge.
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:
package/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  # 🔎 dsh-locator
4
4
 
5
- **不依赖 Everything 的全盘毫秒级文件搜索,给 DeepSeek Harness。**
5
+ **给 DeepSeek Harness 的全盘文件搜索:先按名字找,再按内容找,找到就能打开。**
6
6
 
7
- 自建跨平台索引(Windows USN/MFT · macOS FSEvents · Linux inotify),四个 agent 工具 + 可排序 Web 面板。
7
+ 索引由插件自己维护,不装 Everything,也不依赖 `es.exe`。Windows、macOS、Linux 用同一套工具和同一个面板。
8
8
 
9
9
  [![npm version](https://img.shields.io/npm/v/dsh-locator)](https://www.npmjs.com/package/dsh-locator)
10
10
  [![npm downloads](https://img.shields.io/npm/dm/dsh-locator)](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
- > **官方仓库**:[GitHub `PerryLink/dsh-locator`](https://github.com/PerryLink/dsh-locator)。Gitee / GitCode 是自动同步的镜像,以 GitHub 为准。
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
- 给 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 用的全盘毫秒级文件搜索,**自建索引**:不装 Everything、不用 `es.exe`、不需要 Everything 服务。
29
+ ## 它解决什么问题
32
30
 
33
- `dsh-locator` 扫描你指定的目录,把文件名、路径、大小、修改时间写进一个紧凑的快照;之后按文件系统事件增量更新,百万条目上的名字/路径查询在几十毫秒内返回。模型拿到 `locator_search`、`locator_status`、`locator_rebuild`、`locator_export` 四个工具;人则有一个 Web GUI 全局**搜索面板**(`Ctrl+Shift+F`,或侧栏底部的放大镜),支持列排序、过滤、键盘操作和一键复制路径。
31
+ 找文件通常分两种情况。
34
32
 
35
- 思路借鉴 Everything:常驻的文件名索引、带 `ext:` / `size:` / `dm:` 过滤的查询语法、直接消费文件系统的变更流。实现是独立的,并且跨平台。
33
+ 一种是你记得名字,但不记得放在哪。`glob` 和 `grep` 每次都要重新遍历工作区,知道目录时很快,只记得半个文件名时就很慢。Everything 在 Windows 上很好用,可惜只支持 Windows;把它包一层,macOS、Linux、WSL 和远程机器仍然用不了。
36
34
 
37
- ## 为什么再做一个
35
+ 另一种是你记得某句话、某个结论、某段代码,却想不起在哪个文件、哪次对话里。这时只搜文件名没有用。
38
36
 
39
- `glob` / `grep` 每次调用都要遍历工作区——知道目录时很快,只记得名字的一部分时很慢。Everything 在 Windows 上很好用,但它只支持 Windows;包一层 Everything 仍然覆盖不了 macOS、Linux、WSL 和远程主机。`dsh-locator` 自己拥有索引,所以同一套工具在哪都能用。
37
+ `dsh-locator` 把这两件事放在一起:
38
+
39
+ - 一份常驻的文件名/路径索引,百万条记录也能在几十毫秒内返回;
40
+ - 一份内容索引,覆盖文本文件和 DSH 会话消息,支持中文和子串;
41
+ - 找到之后可以直接打开文件、定位到文件夹,或者跳到对应的会话。
40
42
 
41
43
  ## 安装
42
44
 
@@ -44,174 +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 主仓(最新 main)
49
+ # 直接跟 GitHub 主仓
48
50
  dsh plugin --profile web add github:PerryLink/dsh-locator
49
51
 
50
52
  # 从源码
51
53
  pnpm install
52
- pnpm build # TypeScript 插件
53
- pnpm build:engine # 本机平台的 Go 引擎
54
+ pnpm build
55
+ pnpm build:engine
54
56
  dsh plugin --profile web add /绝对路径/dsh-locator
55
57
 
56
- # 从打包 tarball(已实测的安装路径)
58
+ # 从 tarball
57
59
  npm pack
58
- dsh plugin --profile web add /绝对路径/dsh-locator-0.5.0.tgz
60
+ dsh plugin --profile web add /绝对路径/dsh-locator-0.6.0.tgz
59
61
  ```
60
62
 
61
- npm 包内置 `linux-x64`、`linux-arm64`、`win32-x64`、`win32-arm64`、`darwin-x64`、`darwin-arm64` 六个平台的预编译引擎。
63
+ npm 包里已经带了六个平台的预编译引擎:`linux-x64`、`linux-arm64`、`win32-x64`、`win32-arm64`、`darwin-x64`、`darwin-arm64`。
62
64
 
63
- 装进**已经在运行**的 Web profile 后,请**刷新页面**(`F5`):宿主工具可能立即热挂载,但浏览器面板(`client.js`)是在页面加载时从 boot graph 取的。headless/TUI profile 不会挂面板。
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
- 大目录首次扫描会读盘;之后启动直接加载快照。首次扫描**期间也照常响应**:搜索返回已索引的部分并带 `indexing: true`,面板底栏提示索引构建中,不会被冷扫描卡住。默认排除 `node_modules`、`.git`、`.svn`、`.hg`、`.cache`、`__pycache__`、`.venv`、`venv`、`.next`。
77
+ 第一次扫描要读盘,之后启动直接加载快照。扫描期间搜索照常可用:返回已经索引的部分,并带 `indexing: true`,面板底栏也会提示“索引构建中”。
76
78
 
77
- ## Web 搜索面板
79
+ 默认跳过 `node_modules`、`.git`、`.svn`、`.hg`、`.cache`、`__pycache__`、`.venv`、`venv`、`.next`。
78
80
 
79
- ![dsh-locator 搜索面板:按路径过滤 Markdown,显示命中数、耗时和排序](docs/assets/panel-zh.png)
81
+ ## 两种搜索
80
82
 
81
- 在 Web GUI 里按 `Ctrl+Shift+F`,或点侧栏底部的放大镜。浮层通过两个同源宿主路由(`POST /dsh-locator/search`、`POST /dsh-locator/status`)查询同一个索引,不另起服务、不加端口。
83
+ ### 按名字找:`locator_search`
82
84
 
83
- * 空查询按修改时间倒序列出最近条目;输入后 110 ms 防抖,走和工具完全相同的 Everything 风格语法。
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
- 裸词是文件名子串,多个词取 AND;可叠加 Everything 风格过滤:
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
+ ![搜索面板:按路径过滤 Markdown,显示命中数、耗时和排序](docs/assets/panel-zh.png)
120
100
 
121
- 所有字段都是 Schemastery 配置;改 profile patch 里的 `dsh-locator` 条目,或改包内 `cordis.patch.yml` 的默认值。
101
+ 在 Web GUI 里按 `Ctrl+Shift+F`,或者点侧栏底部的放大镜。
122
102
 
123
- | 键 | 默认 | 含义 |
124
- |---|---|---|
125
- | `enabled` | `true` | 关掉后不挂工具、不起引擎。 |
126
- | `roots` | `[]` | 要索引的目录;空 = 用户主目录。 |
127
- | `excludes` | 开发/缓存目录 | 扫描与监听都跳过的基础名 glob。 |
128
- | `watch` | `true` | 启用原生文件系统通知。 |
129
- | `refreshIntervalMs` | `1800000` | 没有原生监听时(kqueue 回退或监听失败)的重建周期;0 关闭。 |
130
- | `scanWorkers` | `0` | 扫描时的目录读取并发;0 = CPU 数。 |
131
- | `memoryLimitMB` | `0` | 引擎的 Go 软堆上限(MiB);0 = 交给 `GOMEMLIMIT`/`GOGC`。 |
132
- | `windowsUsn` | `false` | 扫描时先试 NTFS USN/MFT 枚举(Windows,需管理员;失败自动回退)。 |
133
- | `stateDir` | `''` | 快照目录;空 = 平台缓存目录。 |
134
- | `binaryPath` | `''` | 覆盖内置引擎路径(开发用)。 |
135
- | `maxResults` | `100` | 单页硬上限。 |
136
- | `startupTimeoutMs` | `120000` | 冷启动建索引的容忍时间。 |
137
- | `requestTimeoutMs` | `15000` | 单次请求超时。 |
138
-
139
- 主目录太大就收窄 `roots`:`~/Documents`、`~/Downloads` 加一两棵代码树,通常比全盘更划算。
103
+ - 空查询按修改时间列出最近的条目;输入后 110ms 防抖,和工具用同一套语法。
104
+ - 点列头可以按名称、大小、修改时间、路径排序。
105
+ - 勾选“只看文件”“只看目录”过滤类型;勾选“搜内容”切换到正文/会话搜索。
106
+ - `↑`/`↓` 选择,`Enter` 复制路径,`Ctrl+O` 打开,`Ctrl+Enter` 把 `@"路径"` 插进输入框。
107
+ - 双击文件结果会在文件管理器里定位;双击会话结果会打开对应会话,并尝试滚动到命中的那句话。
108
+ - 面板文案跟随 `navigator.language`(中/英/西/葡/印地语)。
140
109
 
141
- ## 平台支持
110
+ 面板通过几个同源路由工作:`/dsh-locator/search`、`/dsh-locator/content`、`/dsh-locator/status`、`/dsh-locator/reveal`、`/dsh-locator/open`。不另起服务,也不占额外端口。
142
111
 
143
- | 平台 | 扫描 + 快照 | 实时更新 | 说明 |
144
- |---|---|---|---|
145
- | Linux | 是 | inotify,递归 | 每个目录一个 watch;极大目录树可能撞 `fs.inotify.max_user_watches`。 |
146
- | Windows | 是 | `ReadDirectoryChangesW`,递归 | 不需要管理员,不需要 Everything。提权时可选用 USN/MFT 枚举。WSL 写 `/mnt/<盘>` 可能不产生 Windows 通知,用 `locator_rebuild` 补。 |
147
- | macOS | 是 | 随包发布的 cgo 二进制使用 FSEvents(递归、无上限) | `CGO_ENABLED=0` 构建回退到 kqueue(最多 4,096 个目录);回退模式下内容变化仍由 `refreshIntervalMs` 或 `locator_rebuild` 刷新。 |
112
+ ## 工具一览
148
113
 
149
- 引擎是纯 Go、无 cgo,一台机器交叉编译全部平台;运行时从不需要管理员权限。
114
+ | 工具 | 作用 |
115
+ |---|---|
116
+ | `locator_search` | 按文件名/路径搜索,支持 `ext:`、`size:`、`dm:`、`path:` 等过滤。 |
117
+ | `locator_find` | 按文件正文和会话内容搜索,返回片段与 `sessionId`/`messageId`。 |
118
+ | `locator_status` | 查看根目录、条目数、监听状态、内容索引进度和引擎版本。 |
119
+ | `locator_rebuild` | 后台重建索引;新索引就绪前,搜索继续使用旧的。 |
120
+ | `locator_export` | 把文件名索引导出成只读 SQLite,方便外部工具查询。 |
150
121
 
151
- ### Windows 与 USN 日志
122
+ ### 查询语法
152
123
 
153
- 引擎里已经有 USN 解码器、可选的快路径和探针。快路径**默认关闭**(`windowsUsn: false`):卷 ioctl 需要管理员权限;它已经在 GitHub Actions 的 Windows runner(管理员、真实 NTFS 卷)上端到端验证:
154
- - 读取 1,345,546 条 MFT 记录,解析出 1,345,539 条路径
155
- - `C:\Windows` 下 164,209 条(122,915 文件、41,294 目录)
156
- - `scan --usn --root C:\Windows` 建索引 164,216 条用 7.8 秒;查询 9.6 ms
157
- - 变更日志可读(某次运行读到 8,233 条);`D:` 卷日志未启用时带警告回退目录遍历
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
+ ```
158
137
 
159
- 你也可以自己跑同一个探针:
138
+ ## 配置
160
139
 
161
- ```powershell
162
- # 管理员 PowerShell(或:Start-Process -Verb RunAs <路径> ...)
163
- & ".\bin\win32-x64\indexd.exe" usn-probe --volume C: --root "C:\Windows" --limit 5 --journal 5
164
- ```
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` | 系统缓存 | 快照和内容数据库的位置。 |
165
155
 
166
- 它会打印日志 id/USN、枚举 MFT(每秒记录数、根目录下文件/目录数、样例路径),并解码最近几条变更日志。非管理员运行会明确报 `Access is denied`;解析与路径重建在 Linux 上有单元测试覆盖。
156
+ ## 平台支持
167
157
 
168
- 打开 `windowsUsn: true`(或 `indexd scan --usn`)后,引擎用 MFT 枚举代替目录遍历,再按 FRN 父表重建路径。成功的 USN 扫描会写 `usn-state.json`(日志 id、next USN、根目录),并把 FRN 存进索引记录;下次 `serve --usn` 会先从该游标重放日志再提供服务,停机期间新建的文件无需全量重扫即可出现。日志回绕/被删/不匹配时回退到可移植监听。引擎保留卷句柄,每 2 秒轮询变更日志,按同一张 FRN 表就地应用新建、删除、重命名(`usn.ApplyChanges`)。USN 记录**没有大小和修改时间**:名字/路径是准的,`locator_status` 会把这种索引标成 `USN`,守护进程会对返回页现场 stat,所以面板/模型看到的行仍有真实大小和日期;但全库的 `size:`/`dm:` 过滤在元数据补齐前是近似的。任何失败(没提权、卷不支持、记录过多)都会带警告回退到目录遍历,绝不会让搜索不可用。运行中日志轮询失败会报警并停止该监视器,可移植的 `ReadDirectoryChangesW` 监听和 `locator_rebuild` 仍然可用。变更应用逻辑有合成记录测试,ioctl 路径由 Windows CI job 验证。
158
+ | 平台 | 扫描 | 实时更新 |
159
+ |---|---|---|
160
+ | Linux | 并行遍历 | inotify,递归监听 |
161
+ | Windows | 并行遍历,或管理员下的 USN/MFT | `ReadDirectoryChangesW`;USN 模式下重启会补日志 |
162
+ | macOS | 并行遍历 | 发布版二进制用 FSEvents;`CGO_ENABLED=0` 构建回退到 kqueue |
169
163
 
170
- Everything 之所以首次索引近乎瞬时,是因为它通过 USN 日志直接读 NTFS 主文件表。`dsh-locator` v0.1 刻意先做可移植的目录遍历 + 变更通知:不需要服务、不需要 UAC、任何文件系统都能用。USN/MFT 枚举会作为可选快路径加入路线图(需要进程对卷持有 `GENERIC_READ`,通常意味着管理员权限),可移植路径始终保留为回退。
164
+ Windows 的 USN 快路径默认关闭(`windowsUsn: true`)。它需要管理员权限和启用中的卷变更日志;任何一步失败都会回退到普通遍历,不会让搜索不可用。USN 扫描成功后会记下日志游标,重启时先补上停机期间的变更,再开始服务。
171
165
 
172
166
  ## 性能
173
167
 
174
- 开发者 WSL2 机器、Linux 主目录实测(1,234,757 条目:1,128,288 文件、106,469 目录,排除 `node_modules`、`.git`、`.cache`、`.venv`):
175
-
176
- | 指标 | 结果 |
177
- |---|---|
178
- | 冷扫描(未命中页缓存) | 50.3 s |
179
- | 热扫描(页缓存命中,8 个读取协程) | 4.6–6.5 s(约 190–265k 条目/秒) |
180
- | 快照大小 | 133 MB |
181
- | 扫描峰值 RSS | 约 490 MB |
182
- | 首次扫描进行中的第一次搜索 | 2 ms(部分结果,`indexing: true`) |
183
- | `locator_search "report"`(981 命中) | 34 ms |
184
- | `locator_search "package.json"`(1,287 命中) | 25 ms |
185
- | 查询进程 RSS(120 万条目) | 约 197 MB |
186
- | 小目录树(3.9 万条目)扫描 | 0.47 s |
187
-
188
- 查询阶段按记录分片并行;冷扫描慢在磁盘。数字随文件系统、缓存和 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
+ - 内容索引是后台任务,首次构建时间取决于文本文件数量和会话日志大小。
189
172
 
190
173
  ## 设计
191
174
 
192
- ![dsh-locator 架构:文件系统 → 扫描/监听 → 紧凑快照 → 查询引擎 → agent 工具 / Web 面板](docs/assets/architecture.svg)
175
+ ![架构:文件系统 → 扫描/监听 → 紧凑快照 → 查询引擎 → agent 工具 / Web 面板](docs/assets/architecture.svg)
193
176
 
194
- ```
195
- DeepSeek Harness
196
- └─ dsh-locator(TypeScript 插件)
197
- ├─ locator_search / locator_status / locator_rebuild
198
- ├─ client.js:Ctrl+Shift+F 浮层(shell.overlay slot)
199
- ├─ POST /dsh-locator/{search,status}(同源、仅回环宿主)
200
- └─ 子进程:indexd serve(Go,stdio 上的 NDJSON)
201
- ├─ 扫描器:递归遍历、排除、进度
202
- ├─ 索引:名字 arena + 定长记录 + 开放寻址路径表
203
- ├─ 查询:解析、匹配、Top-K 选择、排序、分页
204
- ├─ 监听:inotify(Linux)/ ReadDirectoryChangesW(Windows)
205
- └─ 快照:单文件、原子重命名
206
- ```
207
-
208
- * **并行扫描**:目录读取协程消费一个有界任务队列,每个目录批量调用 `AddBatch` 落库,保证父记录先于子记录;队列满时改为在当前协程内联扫描,永不阻塞。
209
- * **内存布局**:记录只存名字在 arena 中的偏移/长度和父记录 id,完整路径只在结果页和 `path:` 过滤时重建,避免每个文件一个字符串头。
210
- * **路径表**:开放寻址、槽位存 `id+1`,每条约 12 字节,而 `map[string]int32` 约 50 字节。
211
- * **查询引擎**:裸词与过滤只编译一次,分片并行匹配,再用有界大顶堆选出 `offset+limit` 行,不排序上百万命中。
212
- * **持久化**:先写临时文件再重命名;generation 计数让守护进程只在索引变化时自动保存;墓碑超过阈值后压缩。
213
- * **协议**:stdio 上一行一个 JSON 请求/响应,另有 `scan`、`stats`、`warning`、`error`、`rebuilt` 事件。不开端口、无网络监听面。
214
- * **只读**:引擎只读元数据,绝不修改被索引的文件。
177
+ - **文件名索引**:Go 引擎维护一份紧凑快照。记录只存名字在 arena 里的偏移和父记录 id,完整路径在返回结果时重建。
178
+ - **并行扫描**:目录读取协程消费有界队列,每个目录批量写入,保证父目录先于子目录。
179
+ - **查询**:解析一次查询,分片并行匹配,用有界堆选出前 `offset+limit` 条,不排序全部命中。
180
+ - **内容索引**:宿主侧 SQLite FTS5,trigram 分词,支持中文和子串;文件清单来自引擎已有的扫描结果,会话日志用 Node 自带的 zstd 解压。
181
+ - **跳转**:文件走 `/reveal` 和 `/open`;会话通过 `uiWorkspace.openSession(sessionId)` 打开,再在页面里定位命中文字。
182
+ - **协议**:引擎通过 stdio 上的 NDJSON 通信,不监听端口;Web 面板只走同源宿主路由。
215
183
 
216
184
  ## 从源码构建
217
185
 
@@ -219,38 +187,25 @@ DeepSeek Harness
219
187
 
220
188
  ```sh
221
189
  pnpm install
222
- pnpm typecheck
223
- pnpm test
224
190
  pnpm build
225
- pnpm build:engine:all # 或 build:engine 只构建本机
191
+ pnpm build:engine:all
192
+ pnpm check
226
193
  ```
227
194
 
228
- 引擎是 `engine/` 下的独立 Go module;`cd engine && go test ./...`。
195
+ Go 引擎在 `engine/` 下,测试用 `cd engine && go test ./...`。macOS 的 FSEvents 版本需要 cgo,由 macOS CI 构建。
229
196
 
230
197
  ## 限制
231
198
 
232
- * 只索引文件名、路径、大小、修改时间,**不索引内容**;内容检索请用 `grep`。
233
- * 只覆盖配置的根目录,不是文件系统驱动。
234
- * Linux inotify 每个目录一个描述符;几十万目录的主目录可能超过内核上限。插件会报警告,快照仍可用,重建可以补齐。
235
- * WSL 写 `/mnt/c` 可能不产生 Windows 侧通知(Windows 监听看不到 9p 写入方的变更流),需显式重建。
236
- * 网络共享只有在能当普通目录访问时才会被扫描;没有 SMB 专用变更源。
237
- * macOS 随包二进制使用 FSEvents(递归);`CGO_ENABLED=0` 构建回退到 kqueue(每目录一个描述符,上限 4,096)。两种模式的内容变更都靠周期重建兜底。
238
-
239
- ## 路线图
240
-
241
- 1. macOS FSEvents 监听。
242
- 2. 有卷读取权限时的 Windows USN/MFT 快速枚举;停机后按 USN 日志补齐。
243
- 3. 专用 Web GUI 面板(表格、列排序、过滤、预览、插入为 `@file`)。
244
- 4. 可选的小写名字 arena 与 trigram 预筛。
245
- 5. 增量快照压缩与按根目录的日志。
246
-
247
- ## 内存
248
-
249
- 引擎是单个 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。
250
205
 
251
206
  ## 安全与隐私
252
207
 
253
- 索引完全本地:不联网、不埋点、不上传。快照里是文件名和路径,请把状态目录当私有数据;介意的话别把它纳入备份。插件绝不修改、移动或删除被索引的文件。
208
+ 索引完全在本地,不联网、不上传、不埋点。快照和内容数据库里有文件名、路径和正文,请把它们当作私有数据。插件不会修改、移动或删除被索引的文件。
254
209
 
255
210
  ## 许可证
256
211
 
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
- post('/dsh-locator/search', {
329
- query,
330
- limit: PAGE,
331
- offset: 0,
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
- setHits(Array.isArray(data.hits) ? data.hits : [])
339
- setTotal(typeof data.total === 'number' ? data.total : 0)
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))
@@ -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: () => reveal(hit),
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.dir ? t('dirBadge') : (hit.ext || t('fileBadge')).toUpperCase()),
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/lib/config.d.ts CHANGED
@@ -23,6 +23,16 @@ export interface Config {
23
23
  scanWorkers: number;
24
24
  /** Soft Go heap limit in MiB for the engine; 0 leaves it to GOMEMLIMIT/GOGC. */
25
25
  memoryLimitMB: number;
26
+ /** Index text-file bodies and DSH session messages into the content database. */
27
+ contentIndex: boolean;
28
+ /** Maximum bytes read from one text file. */
29
+ contentMaxFileBytes: number;
30
+ /** Maximum text files indexed per build; 0 means no cap. */
31
+ contentMaxFiles: number;
32
+ /** Most recent session logs indexed; 0 means all. */
33
+ contentMaxSessions: number;
34
+ /** Extra session directories; empty means $DSH_HOME/sessions. */
35
+ contentSessionDirs: string[];
26
36
  /**
27
37
  * Try the NTFS USN/MFT enumeration before the portable walk (Windows only,
28
38
  * requires running DSH as administrator). Falls back automatically.
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAE7C,oCAAoC;AACpC,MAAM,WAAW,MAAM;IACrB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAA;IACd;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAA;IACzB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAA;IACnB,gFAAgF;IAChF,aAAa,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAA;IACnB,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAA;IACxB,2CAA2C;IAC3C,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED,yEAAyE;AACzE,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,CAwBhC,CAAA"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAE7C,oCAAoC;AACpC,MAAM,WAAW,MAAM;IACrB,iEAAiE;IACjE,OAAO,EAAE,OAAO,CAAA;IAChB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,2DAA2D;IAC3D,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,2EAA2E;IAC3E,KAAK,EAAE,OAAO,CAAA;IACd;;;;OAIG;IACH,iBAAiB,EAAE,MAAM,CAAA;IACzB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAA;IACnB,gFAAgF;IAChF,aAAa,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,YAAY,EAAE,OAAO,CAAA;IACrB,6CAA6C;IAC7C,mBAAmB,EAAE,MAAM,CAAA;IAC3B,4DAA4D;IAC5D,eAAe,EAAE,MAAM,CAAA;IACvB,qDAAqD;IACrD,kBAAkB,EAAE,MAAM,CAAA;IAC1B,iEAAiE;IACjE,kBAAkB,EAAE,MAAM,EAAE,CAAA;IAC5B;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAA;IACnB,oEAAoE;IACpE,QAAQ,EAAE,MAAM,CAAA;IAChB,sEAAsE;IACtE,UAAU,EAAE,MAAM,CAAA;IAClB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAA;IAClB,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAA;IACxB,2CAA2C;IAC3C,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED,yEAAyE;AACzE,eAAO,MAAM,MAAM,EAAE,MAAM,CAAC,MAAM,CA6BhC,CAAA"}
package/lib/config.js CHANGED
@@ -22,6 +22,11 @@ export const Config = Schema.object({
22
22
  refreshIntervalMs: Schema.number().default(1_800_000),
23
23
  scanWorkers: Schema.number().default(0),
24
24
  memoryLimitMB: Schema.number().default(0),
25
+ contentIndex: Schema.boolean().default(true),
26
+ contentMaxFileBytes: Schema.number().default(1_000_000),
27
+ contentMaxFiles: Schema.number().default(50_000),
28
+ contentMaxSessions: Schema.number().default(50),
29
+ contentSessionDirs: Schema.array(Schema.string()).default([]),
25
30
  windowsUsn: Schema.boolean().default(false),
26
31
  stateDir: Schema.string().default(''),
27
32
  binaryPath: Schema.string().default(''),
package/lib/config.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAuC7C,yEAAyE;AACzE,MAAM,CAAC,MAAM,MAAM,GAAmB,MAAM,CAAC,MAAM,CAAC;IAClD,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAChD,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC;QAC9C,cAAc;QACd,MAAM;QACN,KAAK;QACL,MAAM;QACN,QAAQ;QACR,aAAa;QACb,OAAO;QACP,MAAM;QACN,OAAO;KACR,CAAC;IACF,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACrC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC;IACrD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACvC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACzC,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;IACxC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;IAClD,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;CAClD,CAAC,CAAA"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,MAAM,MAAM,0BAA0B,CAAA;AAiD7C,yEAAyE;AACzE,MAAM,CAAC,MAAM,MAAM,GAAmB,MAAM,CAAC,MAAM,CAAC;IAClD,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACvC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAChD,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC;QAC9C,cAAc;QACd,MAAM;QACN,KAAK;QACL,MAAM;QACN,QAAQ;QACR,aAAa;QACb,OAAO;QACP,MAAM;QACN,OAAO;KACR,CAAC;IACF,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IACrC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC;IACrD,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACvC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;IAC5C,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,SAAS,CAAC;IACvD,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;IAChD,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IAC/C,kBAAkB,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;IAC7D,UAAU,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IAC3C,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC;IACxC,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC;IAClD,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;CAClD,CAAC,CAAA"}