dsh-locator 0.4.0 → 0.5.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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 — 2026-11-09
4
+
5
+ * **USN restart catch-up**: index records carry the NTFS FRN (in the old snapshot padding, so v1 snapshots still load), `scan --usn` persists `usn-state.json` (journal id, next USN, roots), and `serve --usn` replays the journal before serving. A file created while the daemon is stopped is found after restart without a full scan; a wrapped/mismatched journal falls back to the portable watcher. Validated on a Windows CI runner.
6
+ * **Panel i18n** (zh/en/es/pt/hi): follow `navigator.language`, including status, empty/error states, relative times and footer hints.
7
+ * **Panel actions**: `Ctrl+O` opens the selected file with its default app; `Ctrl+Enter` inserts a quoted `@"path"` into the composer (falls back to copying the mention); double-click still reveals it.
8
+ * `/dsh-locator/open` host route with the same root/path validation as reveal.
9
+
10
+ ## 0.4.1 — 2026-11-09
11
+
12
+ * Engine version is injected at build time (`-X main.version`) instead of a hard-coded `0.1.0`; `locator_status` now reports the package version.
13
+ * `locator_export` verified against the live 1.24 M-entry index: 1,238,602 rows, 477 MB, 6.4 s.
14
+ * Memory tuning documented: Go honours `GOMEMLIMIT` / `GOGC`, so the engine can be capped without a rebuild (e.g. `GOMEMLIMIT=600MiB`).
15
+
3
16
  ## 0.4.0 — 2026-11-09
4
17
 
5
18
  * macOS now ships **FSEvents** in the cgo darwin binaries (arm64 and x64): a pure-C stream callback serializes events into a pipe, so the recursive watcher has no per-directory descriptor limit. The previous crash was `FSEventStreamCreate` receiving the callback context instead of a real `FSEventStreamContext`.
package/README-zh.md CHANGED
@@ -55,7 +55,8 @@ npm 包内置 `linux-x64`、`linux-arm64`、`win32-x64`、`win32-arm64`、`darwi
55
55
  * 空查询按修改时间倒序列出最近条目;输入后 110 ms 防抖,走和工具完全相同的 Everything 风格语法。
56
56
  * 点列头按名称、大小、修改时间、路径排序,再点切换升降序。
57
57
  * `只看文件` / `只看目录` 过滤类型;底栏显示命中数、分页上限和查询耗时。
58
- * `↑`/`↓` 选择,`Enter` 复制选中路径,**双击在文件管理器中定位**(Explorer / Finder / `xdg-open`),`Esc` 关闭。
58
+ * `↑`/`↓` 选择,`Enter` 复制选中路径,`Ctrl+O` 打开,`Ctrl+Enter` 把带引号的 `@"路径"` 插入输入框,**双击在文件管理器中定位**(Explorer / Finder / `xdg-open`),`Esc` 关闭。
59
+ * 面板跟随 `navigator.language`(中/英/西/葡/印地语)。
59
60
  * 面板是可选的:没有 Web server 的 profile 不会挂载它,三个工具照常工作。
60
61
 
61
62
  ## 工具
@@ -135,7 +136,7 @@ npm 包内置 `linux-x64`、`linux-arm64`、`win32-x64`、`win32-arm64`、`darwi
135
136
 
136
137
  它会打印日志 id/USN、枚举 MFT(每秒记录数、根目录下文件/目录数、样例路径),并解码最近几条变更日志。非管理员运行会明确报 `Access is denied`;解析与路径重建在 Linux 上有单元测试覆盖。
137
138
 
138
- 打开 `windowsUsn: true`(或 `indexd scan --usn`)后,引擎用 MFT 枚举代替目录遍历,再按 FRN 父表重建路径。引擎保留卷句柄,每 2 秒轮询变更日志,按同一张 FRN 表就地应用新建、删除、重命名(`usn.ApplyChanges`)。USN 记录**没有大小和修改时间**:名字/路径是准的,`locator_status` 会把这种索引标成 `USN`,守护进程会对返回页现场 stat,所以面板/模型看到的行仍有真实大小和日期;但全库的 `size:`/`dm:` 过滤在元数据补齐前是近似的。任何失败(没提权、卷不支持、记录过多)都会带警告回退到目录遍历,绝不会让搜索不可用。运行中日志轮询失败会报警并停止该监视器,可移植的 `ReadDirectoryChangesW` 监听和 `locator_rebuild` 仍然可用。变更应用逻辑有合成记录测试,ioctl 路径由 Windows CI job 验证。
139
+ 打开 `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 验证。
139
140
 
140
141
  Everything 之所以首次索引近乎瞬时,是因为它通过 USN 日志直接读 NTFS 主文件表。`dsh-locator` v0.1 刻意先做可移植的目录遍历 + 变更通知:不需要服务、不需要 UAC、任何文件系统都能用。USN/MFT 枚举会作为可选快路径加入路线图(需要进程对卷持有 `GENERIC_READ`,通常意味着管理员权限),可移植路径始终保留为回退。
141
142
 
@@ -203,7 +204,6 @@ pnpm build:engine:all # 或 build:engine 只构建本机
203
204
  * WSL 写 `/mnt/c` 可能不产生 Windows 侧通知(Windows 监听看不到 9p 写入方的变更流),需显式重建。
204
205
  * 网络共享只有在能当普通目录访问时才会被扫描;没有 SMB 专用变更源。
205
206
  * macOS 随包二进制使用 FSEvents(递归);`CGO_ENABLED=0` 构建回退到 kqueue(每目录一个描述符,上限 4,096)。两种模式的内容变更都靠周期重建兜底。
206
- * 还没有 USN/MFT 快速枚举(见上)。
207
207
 
208
208
  ## 路线图
209
209
 
@@ -213,6 +213,10 @@ pnpm build:engine:all # 或 build:engine 只构建本机
213
213
  4. 可选的小写名字 arena 与 trigram 预筛。
214
214
  5. 增量快照压缩与按根目录的日志。
215
215
 
216
+ ## 内存
217
+
218
+ 引擎是单个 Go 进程,遵循标准 `GOMEMLIMIT` / `GOGC` 环境变量。124 万条索引在默认 GC 下峰值约 700–800MB RSS;内存紧张时设 `GOMEMLIMIT=600MiB`(软上限,运行时用 CPU 换内存)或 `GOGC=50`。都不改变快照格式。
219
+
216
220
  ## 安全与隐私
217
221
 
218
222
  索引完全本地:不联网、不埋点、不上传。快照里是文件名和路径,请把状态目录当私有数据;介意的话别把它纳入备份。插件绝不修改、移动或删除被索引的文件。
package/README.md CHANGED
@@ -55,7 +55,8 @@ Press `Ctrl+Shift+F` anywhere in the Web GUI, or click the magnifier at the side
55
55
  * Empty query lists the most recently modified entries; typing runs the same Everything-style syntax as the tool after a 110 ms debounce.
56
56
  * Click a column header to sort by name, size, modification time or path; toggle ascending/descending.
57
57
  * `只看文件` / `只看目录` filter the kind; the footer reports matches, the page cap and query latency.
58
- * `↑`/`↓` select, `Enter` copies the selected path, **double-click reveals it** in Explorer/Finder/`xdg-open`, `Esc` closes.
58
+ * `↑`/`↓` select, `Enter` copies the selected path, `Ctrl+O` opens it, `Ctrl+Enter` inserts a quoted `@"path"` into the composer, **double-click reveals it** in Explorer/Finder/`xdg-open`, `Esc` closes.
59
+ * The panel follows `navigator.language` (zh/en/es/pt/hi).
59
60
  * The panel is optional: profiles without a Web server never mount it, and the three tools keep working.
60
61
 
61
62
  ## Tools
@@ -135,9 +136,9 @@ You can still run the same probe yourself:
135
136
 
136
137
  It prints the journal id/USNs, enumerates the MFT (records per second, files/dirs below the root, sample paths) and decodes the last few change-journal records. A non-elevated run fails with `Access is denied` — the parser and path reconstruction are covered by unit tests on every platform.
137
138
 
138
- With `windowsUsn: true` (or `indexd scan --usn`) the engine enumerates the MFT instead of walking directories, then builds paths from the FRN parent map. It keeps the volume handle and polls the change journal every two seconds, applying creates, deletes and renames through `usn.ApplyChanges` (the same FRN map, updated in place). USN records carry **no size or modification time**: results are correct for names and paths, `locator_status` marks such an index with `USN`, and the daemon stats the returned page so the rows the panel/model sees still show real sizes and dates. `size:`/`dm:` filters over the whole index stay approximate until a metadata pass runs. Any failure (no elevation, unsupported volume, too many records) falls back to the portable walk with a warning; it never disables search. If journal polling fails mid-session, the watcher reports it and stops; the portable `ReadDirectoryChangesW` watcher and `locator_rebuild` are still there. The journal applier is covered by synthetic-record tests and the ioctl path by the Windows CI job.
139
+ With `windowsUsn: true` (or `indexd scan --usn`) the engine enumerates the MFT instead of walking directories, then builds paths from the FRN parent map. A successful USN scan also writes `usn-state.json` (journal id, next USN, roots) and keeps the FRN in each record; on the next `serve --usn` the engine replays the journal from that cursor before answering, so files created while it was stopped appear without a full scan. A wrapped, deleted or mismatched journal falls back to the portable watcher. It keeps the volume handle and polls the change journal every two seconds, applying creates, deletes and renames through `usn.ApplyChanges` (the same FRN map, updated in place). USN records carry **no size or modification time**: results are correct for names and paths, `locator_status` marks such an index with `USN`, and the daemon stats the returned page so the rows the panel/model sees still show real sizes and dates. `size:`/`dm:` filters over the whole index stay approximate until a metadata pass runs. Any failure (no elevation, unsupported volume, too many records) falls back to the portable walk with a warning; it never disables search. If journal polling fails mid-session, the watcher reports it and stops; the portable `ReadDirectoryChangesW` watcher and `locator_rebuild` are still there. The journal applier is covered by synthetic-record tests and the ioctl path by the Windows CI job.
139
140
 
140
- Everything reads the NTFS Master File Table through the USN journal, which is why its first index is nearly instant. `dsh-locator` v0.1 deliberately starts with a portable directory walk plus change notifications: it works without a service, without UAC and on every filesystem. USN/MFT enumeration is on the roadmap as an opt-in fast path for volumes where the process holds `GENERIC_READ` (admin) — the portable path stays the fallback.
141
+ Everything reads the NTFS Master File Table through the USN journal, which is why its first index is nearly instant. `dsh-locator` keeps a portable directory walk as the default (no service, no UAC, any filesystem) and uses USN/MFT as an opt-in fast path where the process holds `GENERIC_READ` (admin), with journal replay across restarts. The portable path stays the fallback.
141
142
 
142
143
  ## Performance
143
144
 
@@ -207,12 +208,16 @@ The engine is a separate Go module under `engine/`; run its tests with `cd engin
207
208
 
208
209
  ## Roadmap
209
210
 
210
- 1. USN journal catch-up after downtime (persist the journal cursor and FRN map).
211
- 2. Panel actions: insert a result as `@file`, richer previews.
212
- 3. More platform runtime coverage (Electron desktop, Windows on ARM).
211
+ 1. Electron desktop runtime check on a real Windows install.
212
+ 2. Richer previews and result grouping in the panel.
213
+ 3. Windows on ARM / Linux ARM runtime coverage.
213
214
  4. Optional lowercase-name arena and trigram prefilter for large indexes.
214
215
  5. Incremental snapshot compaction and per-root journals.
215
216
 
217
+ ## Memory
218
+
219
+ The engine is a single Go process and honours the standard `GOMEMLIMIT` and `GOGC` environment variables. A 1.24 M-entry index peaks around 700–800 MB RSS with the default GC; set `GOMEMLIMIT=600MiB` (soft limit, the runtime trades CPU for memory) or `GOGC=50` when the host is tight on RAM. Neither changes the snapshot format.
220
+
216
221
  ## Security and privacy
217
222
 
218
223
  The index is local: no network calls, no telemetry, no uploads. The snapshot contains file names and paths, so treat the state directory as private data and exclude it from backups if that matters to you. The plugin never modifies, moves or deletes indexed files.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/client.js CHANGED
@@ -100,10 +100,135 @@ window.__ModuleLoader__.load({
100
100
  })
101
101
  const data = await response.json().catch(() => null)
102
102
  if (!response.ok || data === null) throw new Error('HTTP ' + response.status)
103
- if (data.ok === false) throw new Error(typeof data.error === 'string' ? data.error : '搜索失败')
103
+ if (data.ok === false) throw new Error(typeof data.error === 'string' ? data.error : 'request failed')
104
104
  return data
105
105
  }
106
106
 
107
+
108
+ // ── i18n ────────────────────────────────────────────────────────────────
109
+ const STRINGS = {
110
+ en: {
111
+ placeholder: 'Search file names or paths… e.g. ext:pdf size:>10mb dm:thisweek path:src !tmp',
112
+ searchLabel: 'Search files', dialog: 'Local file search', closeTitle: 'Close (Esc)', closeLabel: 'Close',
113
+ filesOnly: 'Files only', dirsOnly: 'Directories only', statusLoading: 'Reading index status…',
114
+ status: 'Index {entries} entries · {mode} · {roots} roots', live: 'live', snapshot: 'snapshot', usn: 'USN',
115
+ sortTitle: 'Click to change sorting', name: 'Name', size: 'Size', mtime: 'Modified', path: 'Path',
116
+ dirBadge: 'DIR', fileBadge: 'FILE', loading: 'Searching…', errorPrefix: 'Search failed: ', empty: 'No matching files',
117
+ results: '{total} results', showing: 'showing first {n}', indexing: 'indexing, results may be incomplete',
118
+ copied: 'Path copied', revealed: 'Revealed in file manager', opened: 'Opened', inserted: 'Inserted @file',
119
+ insertFailed: 'Insert failed; @file copied', footer: '↑↓ select · Enter copy · Ctrl+O open · Ctrl+Enter insert @file · double-click reveal · Esc close',
120
+ launcher: 'Local file search (Ctrl+Shift+F)',
121
+ justNow: 'just now', minutesAgo: '{n} min ago', hoursAgo: '{n} h ago',
122
+ },
123
+ zh: {
124
+ placeholder: '搜索文件名或路径… 支持 ext:pdf size:>10mb dm:thisweek path:src !tmp',
125
+ searchLabel: '文件搜索', dialog: '本地文件搜索', closeTitle: '关闭 (Esc)', closeLabel: '关闭',
126
+ filesOnly: '只看文件', dirsOnly: '只看目录', statusLoading: '索引状态读取中…',
127
+ status: '索引 {entries} 条 · {mode} · {roots} 个根目录', live: '实时监听', snapshot: '快照', usn: 'USN',
128
+ sortTitle: '点击切换排序', name: '名称', size: '大小', mtime: '修改时间', path: '路径',
129
+ dirBadge: '目录', fileBadge: '文件', loading: '搜索中…', errorPrefix: '搜索失败:', empty: '没有匹配的文件',
130
+ results: '{total} 条结果', showing: '显示前 {n} 条', indexing: '索引构建中,结果可能不完整',
131
+ copied: '已复制路径', revealed: '已在文件管理器中定位', opened: '已打开', inserted: '已插入 @file',
132
+ insertFailed: '插入失败,已复制 @file', footer: '↑↓ 选择 · Enter 复制 · Ctrl+O 打开 · Ctrl+Enter 插入 @file · 双击定位 · Esc 关闭',
133
+ launcher: '本地文件搜索 (Ctrl+Shift+F)',
134
+ justNow: '刚刚', minutesAgo: '{n} 分钟前', hoursAgo: '{n} 小时前',
135
+ },
136
+ es: {
137
+ placeholder: 'Busca nombres o rutas… p. ej. ext:pdf size:>10mb dm:thisweek path:src !tmp',
138
+ searchLabel: 'Buscar archivos', dialog: 'Búsqueda local de archivos', closeTitle: 'Cerrar (Esc)', closeLabel: 'Cerrar',
139
+ filesOnly: 'Solo archivos', dirsOnly: 'Solo carpetas', statusLoading: 'Leyendo el estado del índice…',
140
+ status: 'Índice: {entries} entradas · {mode} · {roots} raíces', live: 'en vivo', snapshot: 'instantánea', usn: 'USN',
141
+ sortTitle: 'Clic para cambiar el orden', name: 'Nombre', size: 'Tamaño', mtime: 'Modificado', path: 'Ruta',
142
+ dirBadge: 'DIR', fileBadge: 'ARCH', loading: 'Buscando…', errorPrefix: 'Error de búsqueda: ', empty: 'Sin coincidencias',
143
+ results: '{total} resultados', showing: 'mostrando {n}', indexing: 'indexando, resultados incompletos',
144
+ copied: 'Ruta copiada', revealed: 'Mostrado en el gestor de archivos', opened: 'Abierto', inserted: '@file insertado',
145
+ insertFailed: 'No se pudo insertar; @file copiado', footer: '↑↓ elegir · Enter copiar · Ctrl+O abrir · Ctrl+Enter insertar @file · doble clic revelar · Esc cerrar',
146
+ launcher: 'Búsqueda local (Ctrl+Shift+F)',
147
+ justNow: 'ahora', minutesAgo: 'hace {n} min', hoursAgo: 'hace {n} h',
148
+ },
149
+ pt: {
150
+ placeholder: 'Busque nomes ou caminhos… ex. ext:pdf size:>10mb dm:thisweek path:src !tmp',
151
+ searchLabel: 'Buscar arquivos', dialog: 'Busca local de arquivos', closeTitle: 'Fechar (Esc)', closeLabel: 'Fechar',
152
+ filesOnly: 'Somente arquivos', dirsOnly: 'Somente pastas', statusLoading: 'Lendo o estado do índice…',
153
+ status: 'Índice: {entries} entradas · {mode} · {roots} raízes', live: 'ao vivo', snapshot: 'instantâneo', usn: 'USN',
154
+ sortTitle: 'Clique para mudar a ordem', name: 'Nome', size: 'Tamanho', mtime: 'Modificado', path: 'Caminho',
155
+ dirBadge: 'DIR', fileBadge: 'ARQ', loading: 'Buscando…', errorPrefix: 'Falha na busca: ', empty: 'Nenhum resultado',
156
+ results: '{total} resultados', showing: 'mostrando {n}', indexing: 'indexando, resultados incompletos',
157
+ copied: 'Caminho copiado', revealed: 'Mostrado no gerenciador de arquivos', opened: 'Aberto', inserted: '@file inserido',
158
+ insertFailed: 'Falha ao inserir; @file copiado', footer: '↑↓ escolher · Enter copiar · Ctrl+O abrir · Ctrl+Enter inserir @file · clique duplo revelar · Esc fechar',
159
+ launcher: 'Busca local (Ctrl+Shift+F)',
160
+ justNow: 'agora', minutesAgo: 'há {n} min', hoursAgo: 'há {n} h',
161
+ },
162
+ hi: {
163
+ placeholder: 'फ़ाइल नाम या पथ खोजें… जैसे ext:pdf size:>10mb dm:thisweek path:src !tmp',
164
+ searchLabel: 'फ़ाइल खोज', dialog: 'स्थानीय फ़ाइल खोज', closeTitle: 'बंद करें (Esc)', closeLabel: 'बंद करें',
165
+ filesOnly: 'केवल फ़ाइलें', dirsOnly: 'केवल फ़ोल्डर', statusLoading: 'इंडेक्स स्थिति पढ़ी जा रही है…',
166
+ status: 'इंडेक्स {entries} प्रविष्टियाँ · {mode} · {roots} रूट', live: 'लाइव', snapshot: 'स्नैपशॉट', usn: 'USN',
167
+ sortTitle: 'क्रम बदलने के लिए क्लिक करें', name: 'नाम', size: 'आकार', mtime: 'संशोधित', path: 'पथ',
168
+ dirBadge: 'DIR', fileBadge: 'FILE', loading: 'खोज रहे हैं…', errorPrefix: 'खोज विफल: ', empty: 'कोई मेल नहीं',
169
+ results: '{total} परिणाम', showing: 'पहले {n} दिखाए', indexing: 'इंडेक्स बन रहा है, परिणाम अधूरे हो सकते हैं',
170
+ copied: 'पथ कॉपी हुआ', revealed: 'फ़ाइल मैनेजर में दिखाया', opened: 'खोला गया', inserted: '@file डाला गया',
171
+ insertFailed: 'डालना विफल; @file कॉपी हुआ', footer: '↑↓ चुनें · Enter कॉपी · Ctrl+O खोलें · Ctrl+Enter @file डालें · डबल-क्लिक दिखाएँ · Esc बंद',
172
+ launcher: 'स्थानीय फ़ाइल खोज (Ctrl+Shift+F)',
173
+ justNow: 'अभी', minutesAgo: '{n} मिनट पहले', hoursAgo: '{n} घंटे पहले',
174
+ },
175
+ }
176
+ const LANG = (() => {
177
+ const tag = String((typeof navigator !== 'undefined' && navigator.language) || 'en').toLowerCase()
178
+ if (tag.startsWith('zh')) return 'zh'
179
+ if (tag.startsWith('es')) return 'es'
180
+ if (tag.startsWith('pt')) return 'pt'
181
+ if (tag.startsWith('hi')) return 'hi'
182
+ return 'en'
183
+ })()
184
+ const TEXT = STRINGS[LANG]
185
+ function t(key, vars) {
186
+ let value = TEXT[key] || STRINGS.en[key] || key
187
+ if (vars) value = value.replace(/\{(\w+)\}/g, (_, name) => String(vars[name] ?? ''))
188
+ return value
189
+ }
190
+
191
+ function formatRelative(ms) {
192
+ const diff = Date.now() - ms
193
+ if (diff >= 0 && diff < 60_000) return t('justNow')
194
+ if (diff >= 0 && diff < 3_600_000) return t('minutesAgo', { n: Math.floor(diff / 60_000) })
195
+ if (diff >= 0 && diff < 86_400_000) return t('hoursAgo', { n: Math.floor(diff / 3_600_000) })
196
+ return ''
197
+ }
198
+
199
+ function copyText(text) {
200
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
201
+ return navigator.clipboard.writeText(text)
202
+ }
203
+ return new Promise((resolve, reject) => {
204
+ const area = document.createElement('textarea')
205
+ area.value = text
206
+ document.body.appendChild(area)
207
+ area.select()
208
+ try {
209
+ if (document.execCommand('copy')) resolve()
210
+ else reject(new Error('copy failed'))
211
+ } finally {
212
+ area.remove()
213
+ }
214
+ })
215
+ }
216
+
217
+ function insertIntoComposer(text) {
218
+ const element = document.querySelector('textarea, input[type="text"], [contenteditable="true"], [role="textbox"]')
219
+ if (!element) return false
220
+ element.focus()
221
+ if (element.tagName === 'TEXTAREA' || element.tagName === 'INPUT') {
222
+ const start = typeof element.selectionStart === 'number' ? element.selectionStart : element.value.length
223
+ const end = typeof element.selectionEnd === 'number' ? element.selectionEnd : start
224
+ if (typeof element.setRangeText === 'function') element.setRangeText(text, start, end, 'end')
225
+ else element.value = element.value.slice(0, start) + text + element.value.slice(end)
226
+ element.dispatchEvent(new Event('input', { bubbles: true }))
227
+ return true
228
+ }
229
+ return document.execCommand('insertText', false, text)
230
+ }
231
+
107
232
  // ── helpers ─────────────────────────────────────────────────────────────
108
233
  function formatSize(bytes) {
109
234
  if (!bytes) return ''
@@ -122,11 +247,8 @@ window.__ModuleLoader__.load({
122
247
  if (!ms) return ''
123
248
  const date = new Date(ms)
124
249
  if (Number.isNaN(date.getTime())) return ''
125
- const now = Date.now()
126
- const diff = now - ms
127
- if (diff >= 0 && diff < 60_000) return '刚刚'
128
- if (diff >= 0 && diff < 3_600_000) return Math.floor(diff / 60_000) + ' 分钟前'
129
- if (diff >= 0 && diff < 86_400_000) return Math.floor(diff / 3_600_000) + ' 小时前'
250
+ const relative = formatRelative(ms)
251
+ if (relative !== '') return relative
130
252
  const pad = value => String(value).padStart(2, '0')
131
253
  return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate())
132
254
  }
@@ -239,38 +361,38 @@ window.__ModuleLoader__.load({
239
361
  }
240
362
  }, [sort])
241
363
 
364
+ const notify = useCallback(message => {
365
+ setToast(message)
366
+ setTimeout(() => setToast(''), 1600)
367
+ }, [])
368
+
242
369
  const copy = useCallback(hit => {
243
370
  if (!hit) return
244
- const text = hit.path
245
- const done = () => {
246
- setToast('已复制路径')
247
- setTimeout(() => setToast(''), 1500)
248
- }
249
- if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
250
- navigator.clipboard.writeText(text).then(done).catch(() => {})
251
- return
252
- }
253
- const area = document.createElement('textarea')
254
- area.value = text
255
- document.body.appendChild(area)
256
- area.select()
257
- try {
258
- document.execCommand('copy')
259
- done()
260
- } finally {
261
- area.remove()
262
- }
263
- }, [])
371
+ copyText(hit.path).then(() => notify(t('copied'))).catch(() => {})
372
+ }, [notify])
264
373
 
265
374
  const reveal = useCallback(hit => {
266
375
  if (!hit) return
267
- post('/dsh-locator/reveal', { path: hit.path }).then(() => {
268
- setToast('已在文件管理器中定位')
269
- setTimeout(() => setToast(''), 1500)
270
- }).catch(() => {
271
- copy(hit)
272
- })
273
- }, [copy])
376
+ post('/dsh-locator/reveal', { path: hit.path }).then(() => notify(t('revealed'))).catch(() => copy(hit))
377
+ }, [copy, notify])
378
+
379
+ const openFile = useCallback(hit => {
380
+ if (!hit || hit.dir) return
381
+ post('/dsh-locator/open', { path: hit.path }).then(() => notify(t('opened'))).catch(() => copy(hit))
382
+ }, [copy, notify])
383
+
384
+ const insert = useCallback(hit => {
385
+ if (!hit) return
386
+ const mention = '@"' + hit.path.replace(/"/g, '') + '" '
387
+ close()
388
+ setTimeout(() => {
389
+ if (insertIntoComposer(mention)) {
390
+ notify(t('inserted'))
391
+ } else {
392
+ copyText(mention).then(() => notify(t('insertFailed'))).catch(() => {})
393
+ }
394
+ }, 80)
395
+ }, [close, notify])
274
396
 
275
397
  const onKeyDown = useCallback(event => {
276
398
  if (event.key === 'Escape') {
@@ -288,11 +410,22 @@ window.__ModuleLoader__.load({
288
410
  setSelected(value => Math.max(0, value - 1))
289
411
  return
290
412
  }
413
+ const modifier = event.ctrlKey || event.metaKey
414
+ if (modifier && String(event.key).toLowerCase() === 'o') {
415
+ event.preventDefault()
416
+ openFile(hits[selected])
417
+ return
418
+ }
419
+ if (modifier && event.key === 'Enter' && !event.isComposing) {
420
+ event.preventDefault()
421
+ insert(hits[selected])
422
+ return
423
+ }
291
424
  if (event.key === 'Enter' && !event.isComposing) {
292
425
  event.preventDefault()
293
426
  copy(hits[selected])
294
427
  }
295
- }, [close, copy, hits, selected])
428
+ }, [close, copy, hits, insert, openFile, selected])
296
429
 
297
430
  useEffect(() => {
298
431
  const list = listRef.current
@@ -308,7 +441,7 @@ window.__ModuleLoader__.load({
308
441
  className: sort === key ? 'sorted' : '',
309
442
  style: { width },
310
443
  onClick: () => toggleSort(key),
311
- title: '点击切换排序',
444
+ title: t('sortTitle'),
312
445
  }, label + (sort === key ? (desc ? ' ↓' : ' ↑') : ''))
313
446
 
314
447
  return h('div', {
@@ -318,7 +451,7 @@ window.__ModuleLoader__.load({
318
451
  className: 'dsh-locator-panel',
319
452
  role: 'dialog',
320
453
  'aria-modal': 'true',
321
- 'aria-label': '本地文件搜索',
454
+ 'aria-label': t('dialog'),
322
455
  onKeyDown,
323
456
  },
324
457
  h('div', { className: 'dsh-locator-head' },
@@ -329,24 +462,28 @@ window.__ModuleLoader__.load({
329
462
  value: query,
330
463
  spellCheck: false,
331
464
  autoComplete: 'off',
332
- placeholder: '搜索文件名或路径… 支持 ext:pdf size:>10mb dm:thisweek path:src !tmp',
465
+ placeholder: t('placeholder'),
333
466
  onChange: event => setQuery(event.target.value),
334
- 'aria-label': '文件搜索',
467
+ 'aria-label': t('searchLabel'),
335
468
  }),
336
469
  h('span', { className: 'dsh-locator-kbd' }, 'Ctrl+Shift+F'),
337
- h('button', { className: 'dsh-locator-close', onClick: close, title: '关闭 (Esc)', 'aria-label': '关闭' }, '✕'),
470
+ h('button', { className: 'dsh-locator-close', onClick: close, title: t('closeTitle'), 'aria-label': t('closeLabel') }, '✕'),
338
471
  ),
339
472
  h('div', { className: 'dsh-locator-bar' },
340
473
  h('label', { className: 'dsh-locator-check' },
341
474
  h('input', { type: 'checkbox', checked: filesOnly, onChange: event => { setFilesOnly(event.target.checked); if (event.target.checked) setDirsOnly(false) } }),
342
- '只看文件'),
475
+ t('filesOnly')),
343
476
  h('label', { className: 'dsh-locator-check' },
344
477
  h('input', { type: 'checkbox', checked: dirsOnly, onChange: event => { setDirsOnly(event.target.checked); if (event.target.checked) setFilesOnly(false) } }),
345
- '只看目录'),
478
+ t('dirsOnly')),
346
479
  h('span', { className: 'dsh-locator-status' },
347
480
  status === null
348
- ? '索引状态读取中…'
349
- : '索引 ' + (status.entries || 0).toLocaleString() + ' 条 · ' + (status.watching ? '实时监听' : '快照') + (status.usn ? ' · USN' : '') + ' · ' + (status.roots || []).length + ' 个根目录'),
481
+ ? t('statusLoading')
482
+ : t('status', {
483
+ entries: (status.entries || 0).toLocaleString(),
484
+ mode: status.watching ? t('live') : t('snapshot'),
485
+ roots: (status.roots || []).length,
486
+ }) + ((status.usnActive || status.usn) ? ' · ' + t('usn') : '')),
350
487
  ),
351
488
  h('div', { className: 'dsh-locator-scroll', ref: listRef },
352
489
  h('table', { className: 'dsh-locator-table' },
@@ -357,15 +494,15 @@ window.__ModuleLoader__.load({
357
494
  h('col', { style: { width: '41%' } }),
358
495
  ),
359
496
  h('thead', null, h('tr', null,
360
- header('name', '名称'),
361
- header('size', '大小'),
362
- header('mtime', '修改时间'),
363
- header('path', '路径'),
497
+ header('name', t('name')),
498
+ header('size', t('size')),
499
+ header('mtime', t('mtime')),
500
+ header('path', t('path')),
364
501
  )),
365
502
  h('tbody', null,
366
503
  hits.length === 0
367
504
  ? h('tr', null, h('td', { colSpan: 4, className: 'dsh-locator-empty' },
368
- loading ? '搜索中…' : error !== '' ? '搜索失败:' + error : '没有匹配的文件'))
505
+ loading ? t('loading') : error !== '' ? t('errorPrefix') + error : t('empty')))
369
506
  : hits.map((hit, index) => h('tr', {
370
507
  key: hit.path,
371
508
  'data-index': index,
@@ -375,7 +512,7 @@ window.__ModuleLoader__.load({
375
512
  onDoubleClick: () => reveal(hit),
376
513
  },
377
514
  h('td', null, h('div', { className: 'dsh-locator-name' },
378
- h('span', { className: 'dsh-locator-badge' }, hit.dir ? '目录' : (hit.ext || '文件').toUpperCase()),
515
+ h('span', { className: 'dsh-locator-badge' }, hit.dir ? t('dirBadge') : (hit.ext || t('fileBadge')).toUpperCase()),
379
516
  h('span', null, highlight(hit.name, query)),
380
517
  )),
381
518
  h('td', { className: 'dsh-locator-size' }, hit.dir ? '' : formatSize(hit.size)),
@@ -388,12 +525,12 @@ window.__ModuleLoader__.load({
388
525
  h('div', { className: 'dsh-locator-foot' },
389
526
  h('span', null,
390
527
  loading
391
- ? '搜索中…'
392
- : total.toLocaleString() + ' 条结果' + (truncated ? '(显示前 ' + hits.length + ' 条)' : '') + ' · ' + took.toFixed(1) + ' ms'
393
- + (status && status.scanning ? ' · 索引构建中,结果可能不完整' : '')),
528
+ ? t('loading')
529
+ : t('results', { total: total.toLocaleString() }) + (truncated ? ' (' + t('showing', { n: hits.length }) + ')' : '') + ' · ' + took.toFixed(1) + ' ms'
530
+ + (status && status.scanning ? ' · ' + t('indexing') : '')),
394
531
  h('span', { className: 'grow' }),
395
532
  toast === '' ? null : h('span', { className: 'dsh-locator-toast' }, toast),
396
- h('span', null, '↑↓ 选择 · Enter 复制路径 · 双击定位 · Esc 关闭'),
533
+ h('span', null, t('footer')),
397
534
  ),
398
535
  ))
399
536
  }
@@ -401,8 +538,8 @@ window.__ModuleLoader__.load({
401
538
  function Launcher() {
402
539
  return h('button', {
403
540
  className: 'dsh-locator-launch',
404
- title: '本地文件搜索 (Ctrl+Shift+F)',
405
- 'aria-label': '本地文件搜索',
541
+ title: t('launcher'),
542
+ 'aria-label': t('searchLabel'),
406
543
  onClick: () => store.toggle(),
407
544
  }, '🔎')
408
545
  }
package/lib/http.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,cAAc,CAAA;AA+B9D,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,cAAc,GAAG,IAAI,CAwBnE"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAElD,OAAO,KAAK,EAAE,cAAc,EAAc,MAAM,cAAc,CAAA;AA+B9D,oEAAoE;AACpE,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,cAAc,GAAG,IAAI,CA6BnE"}
package/lib/http.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * service, and the plugin still works there through its tools.
12
12
  * @module dsh-locator/http
13
13
  */
14
- import { revealInFileManager, withinRoots } from "./reveal.js";
14
+ import { openWithDefaultApp, revealInFileManager, withinRoots } from "./reveal.js";
15
15
  import { statSync } from 'node:fs';
16
16
  const BODY_LIMIT = 64 * 1024;
17
17
  /** Mount the panel's routes when this profile runs a Web server. */
@@ -38,6 +38,11 @@ export function registerHttp(ctx, rt) {
38
38
  path: '/dsh-locator/reveal',
39
39
  handler: handler(scoped, async (body) => await reveal(rt, body)),
40
40
  }));
41
+ scoped.effect(() => server.register({
42
+ kind: 'exact',
43
+ path: '/dsh-locator/open',
44
+ handler: handler(scoped, async (body) => await openPath(rt, body)),
45
+ }));
41
46
  });
42
47
  }
43
48
  function handler(scoped, run) {
@@ -111,6 +116,27 @@ async function reveal(rt, raw) {
111
116
  return { ok: false, error: messageOf(error) };
112
117
  }
113
118
  }
119
+ async function openPath(rt, raw) {
120
+ const path = typeof raw.path === 'string' ? raw.path : '';
121
+ if (path === '' || !withinRoots(path, rt.roots)) {
122
+ return { ok: false, error: 'path is outside the indexed roots' };
123
+ }
124
+ try {
125
+ const info = statSync(path);
126
+ if (!info.isFile())
127
+ return { ok: false, error: 'not a file' };
128
+ }
129
+ catch {
130
+ return { ok: false, error: 'path does not exist' };
131
+ }
132
+ try {
133
+ await openWithDefaultApp(path);
134
+ return { ok: true };
135
+ }
136
+ catch (error) {
137
+ return { ok: false, error: messageOf(error) };
138
+ }
139
+ }
114
140
  async function status(rt) {
115
141
  rt.start();
116
142
  const engine = await rt.engineStatus();
package/lib/http.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"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"}
package/lib/reveal.d.ts CHANGED
@@ -5,9 +5,11 @@
5
5
  /** Whether target is one of the roots or lives below one of them. */
6
6
  export declare function withinRoots(target: string, roots: readonly string[]): boolean;
7
7
  /**
8
- * Ask the platform file manager to select a path. Uses an argument vector, not
9
- * a shell, so a path can never be interpreted as a command. The child is
10
- * detached: the opener outlives the plugin call.
8
+ * Ask the platform to reveal a path in its file manager. Uses an argument
9
+ * vector, not a shell, so a path can never be interpreted as a command. The
10
+ * child is detached: the opener outlives the plugin call.
11
11
  */
12
12
  export declare function revealInFileManager(target: string): Promise<void>;
13
+ /** Open a path with its default application. */
14
+ export declare function openWithDefaultApp(target: string): Promise<void>;
13
15
  //# sourceMappingURL=reveal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"reveal.d.ts","sourceRoot":"","sources":["../src/reveal.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAS7E;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBvE"}
1
+ {"version":3,"file":"reveal.d.ts","sourceRoot":"","sources":["../src/reveal.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAS7E;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKvE;AAED,gDAAgD;AAChD,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKtE"}
package/lib/reveal.js CHANGED
@@ -18,26 +18,28 @@ export function withinRoots(target, roots) {
18
18
  return false;
19
19
  }
20
20
  /**
21
- * Ask the platform file manager to select a path. Uses an argument vector, not
22
- * a shell, so a path can never be interpreted as a command. The child is
23
- * detached: the opener outlives the plugin call.
21
+ * Ask the platform to reveal a path in its file manager. Uses an argument
22
+ * vector, not a shell, so a path can never be interpreted as a command. The
23
+ * child is detached: the opener outlives the plugin call.
24
24
  */
25
25
  export async function revealInFileManager(target) {
26
26
  const absolute = resolve(target);
27
- let command;
28
- let args;
29
- if (process.platform === 'win32') {
30
- command = 'explorer.exe';
31
- args = [`/select,${absolute}`];
32
- }
33
- else if (process.platform === 'darwin') {
34
- command = 'open';
35
- args = ['-R', absolute];
36
- }
37
- else {
38
- command = 'xdg-open';
39
- args = [dirname(absolute)];
40
- }
27
+ if (process.platform === 'win32')
28
+ return spawnDetached('explorer.exe', [`/select,${absolute}`]);
29
+ if (process.platform === 'darwin')
30
+ return spawnDetached('open', ['-R', absolute]);
31
+ return spawnDetached('xdg-open', [dirname(absolute)]);
32
+ }
33
+ /** Open a path with its default application. */
34
+ export async function openWithDefaultApp(target) {
35
+ const absolute = resolve(target);
36
+ if (process.platform === 'win32')
37
+ return spawnDetached('explorer.exe', [absolute]);
38
+ if (process.platform === 'darwin')
39
+ return spawnDetached('open', [absolute]);
40
+ return spawnDetached('xdg-open', [absolute]);
41
+ }
42
+ async function spawnDetached(command, args) {
41
43
  await new Promise((resolvePromise, reject) => {
42
44
  const child = spawn(command, args, { detached: true, stdio: 'ignore', windowsHide: true });
43
45
  child.once('error', reject);
package/lib/reveal.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"reveal.js","sourceRoot":"","sources":["../src/reveal.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAElE,qEAAqE;AACrE,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,KAAwB;IAClE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;QAC1B,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QACpC,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YAAE,OAAO,IAAI,CAAA;IAC5E,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAc;IACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAChC,IAAI,OAAe,CAAA;IACnB,IAAI,IAAc,CAAA;IAClB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;QACjC,OAAO,GAAG,cAAc,CAAA;QACxB,IAAI,GAAG,CAAC,WAAW,QAAQ,EAAE,CAAC,CAAA;IAChC,CAAC;SAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACzC,OAAO,GAAG,MAAM,CAAA;QAChB,IAAI,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;IACzB,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,UAAU,CAAA;QACpB,IAAI,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC5B,CAAC;IACD,MAAM,IAAI,OAAO,CAAO,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE;QACjD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAA;QAC1F,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC3B,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YACvB,KAAK,CAAC,KAAK,EAAE,CAAA;YACb,cAAc,EAAE,CAAA;QAClB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC"}
1
+ {"version":3,"file":"reveal.js","sourceRoot":"","sources":["../src/reveal.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAC1C,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAElE,qEAAqE;AACrE,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,KAAwB;IAClE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;QAAE,OAAO,KAAK,CAAA;IACrC,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;QAC1B,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QACpC,IAAI,GAAG,KAAK,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;YAAE,OAAO,IAAI,CAAA;IAC5E,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAAc;IACtD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAChC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,aAAa,CAAC,cAAc,EAAE,CAAC,WAAW,QAAQ,EAAE,CAAC,CAAC,CAAA;IAC/F,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,aAAa,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAA;IACjF,OAAO,aAAa,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;AACvD,CAAC;AAED,gDAAgD;AAChD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAc;IACrD,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAChC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,OAAO,aAAa,CAAC,cAAc,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAClF,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,OAAO,aAAa,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC3E,OAAO,aAAa,CAAC,UAAU,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAA;AAC9C,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,OAAe,EAAE,IAAc;IAC1D,MAAM,IAAI,OAAO,CAAO,CAAC,cAAc,EAAE,MAAM,EAAE,EAAE;QACjD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAA;QAC1F,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC3B,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE;YACvB,KAAK,CAAC,KAAK,EAAE,CAAA;YACb,cAAc,EAAE,CAAA;QAClB,CAAC,CAAC,CAAA;IACJ,CAAC,CAAC,CAAA;AACJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-locator",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Whole-disk, millisecond file search for DeepSeek Harness without Everything: a self-built cross-platform index, an Everything-style query language, agent tools and a Web search panel (Ctrl+Shift+F). Windows, macOS and Linux.",
5
5
  "author": "PerryLink contributors",
6
6
  "repository": {
package/src/http.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  import type { Context } from '@deepseek-ai/cordis'
16
16
  import type { IncomingMessage, ServerResponse } from 'node:http'
17
17
  import type { LocatorRuntime, SearchArgs } from './runtime.ts'
18
- import { revealInFileManager, withinRoots } from './reveal.ts'
18
+ import { openWithDefaultApp, revealInFileManager, withinRoots } from './reveal.ts'
19
19
  import { statSync } from 'node:fs'
20
20
 
21
21
  interface WebServerLike {
@@ -69,6 +69,11 @@ export function registerHttp(ctx: Context, rt: LocatorRuntime): void {
69
69
  path: '/dsh-locator/reveal',
70
70
  handler: handler(scoped, async body => await reveal(rt, body)),
71
71
  }))
72
+ scoped.effect(() => server.register({
73
+ kind: 'exact',
74
+ path: '/dsh-locator/open',
75
+ handler: handler(scoped, async body => await openPath(rt, body)),
76
+ }))
72
77
  })
73
78
  }
74
79
 
@@ -145,6 +150,25 @@ async function reveal(rt: LocatorRuntime, raw: Record<string, unknown>): Promise
145
150
  }
146
151
  }
147
152
 
153
+ async function openPath(rt: LocatorRuntime, raw: Record<string, unknown>): Promise<unknown> {
154
+ const path = typeof raw.path === 'string' ? raw.path : ''
155
+ if (path === '' || !withinRoots(path, rt.roots)) {
156
+ return { ok: false, error: 'path is outside the indexed roots' }
157
+ }
158
+ try {
159
+ const info = statSync(path)
160
+ if (!info.isFile()) return { ok: false, error: 'not a file' }
161
+ } catch {
162
+ return { ok: false, error: 'path does not exist' }
163
+ }
164
+ try {
165
+ await openWithDefaultApp(path)
166
+ return { ok: true }
167
+ } catch (error) {
168
+ return { ok: false, error: messageOf(error) }
169
+ }
170
+ }
171
+
148
172
  async function status(rt: LocatorRuntime): Promise<unknown> {
149
173
  rt.start()
150
174
  const engine = await rt.engineStatus()
package/src/reveal.ts CHANGED
@@ -19,24 +19,26 @@ export function withinRoots(target: string, roots: readonly string[]): boolean {
19
19
  }
20
20
 
21
21
  /**
22
- * Ask the platform file manager to select a path. Uses an argument vector, not
23
- * a shell, so a path can never be interpreted as a command. The child is
24
- * detached: the opener outlives the plugin call.
22
+ * Ask the platform to reveal a path in its file manager. Uses an argument
23
+ * vector, not a shell, so a path can never be interpreted as a command. The
24
+ * child is detached: the opener outlives the plugin call.
25
25
  */
26
26
  export async function revealInFileManager(target: string): Promise<void> {
27
27
  const absolute = resolve(target)
28
- let command: string
29
- let args: string[]
30
- if (process.platform === 'win32') {
31
- command = 'explorer.exe'
32
- args = [`/select,${absolute}`]
33
- } else if (process.platform === 'darwin') {
34
- command = 'open'
35
- args = ['-R', absolute]
36
- } else {
37
- command = 'xdg-open'
38
- args = [dirname(absolute)]
39
- }
28
+ if (process.platform === 'win32') return spawnDetached('explorer.exe', [`/select,${absolute}`])
29
+ if (process.platform === 'darwin') return spawnDetached('open', ['-R', absolute])
30
+ return spawnDetached('xdg-open', [dirname(absolute)])
31
+ }
32
+
33
+ /** Open a path with its default application. */
34
+ export async function openWithDefaultApp(target: string): Promise<void> {
35
+ const absolute = resolve(target)
36
+ if (process.platform === 'win32') return spawnDetached('explorer.exe', [absolute])
37
+ if (process.platform === 'darwin') return spawnDetached('open', [absolute])
38
+ return spawnDetached('xdg-open', [absolute])
39
+ }
40
+
41
+ async function spawnDetached(command: string, args: string[]): Promise<void> {
40
42
  await new Promise<void>((resolvePromise, reject) => {
41
43
  const child = spawn(command, args, { detached: true, stdio: 'ignore', windowsHide: true })
42
44
  child.once('error', reject)