dsh-moli 1.0.6 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -3
- package/README.zh.md +27 -2
- package/index.js +9 -9
- package/lib/install.js +25 -19
- package/lib/moli.js +31 -14
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -255,6 +255,32 @@ capabilities always visible to the model.
|
|
|
255
255
|
resolves against the plugin directory instead of the profile's `node_modules` and the plugin
|
|
256
256
|
fails to load. This plugin imports only `node:` builtins and its own files.
|
|
257
257
|
|
|
258
|
+
## Platform support
|
|
259
|
+
|
|
260
|
+
Works on **Windows, Linux and macOS** (x64 / arm64).
|
|
261
|
+
|
|
262
|
+
> **Linux and macOS support was contributed by [@nd002723](https://github.com/nd002723)** in
|
|
263
|
+
> [PR #1](https://github.com/27painter/dsh-moli/pull/1): engine discovery in the standard POSIX
|
|
264
|
+
> locations, the `sh` installer route, and the per-platform gating of the PowerShell paths.
|
|
265
|
+
|
|
266
|
+
| Platform | Status |
|
|
267
|
+
|---|---|
|
|
268
|
+
| **Windows** | Verified by the maintainer |
|
|
269
|
+
| **Linux** | Verified by the contributor on Ubuntu 24.04 LTS, Node v24.21.0, DSH 0.2.1-alpha.2, dsh-moli 1.1.0, Moli 1.1.17 |
|
|
270
|
+
| **macOS** | Implemented, not yet verified on a real host |
|
|
271
|
+
|
|
272
|
+
- **Windows** — the engine is located at the official installer paths (`%LOCALAPPDATA%\Moli\bin\moli.exe`,
|
|
273
|
+
`%USERPROFILE%\.local\bin\moli.exe`) with a recursive scan of `%LOCALAPPDATA%\Moli` as a fallback.
|
|
274
|
+
`moli_probe action=install` runs the official PowerShell installer.
|
|
275
|
+
- **Linux / macOS** — the engine is found in the standard locations (`~/.local/bin/moli`,
|
|
276
|
+
`~/.local/share/moli/bin/moli`, `~/.local/share/moli/moli`, `~/.moli/bin/moli`) and on `$PATH`.
|
|
277
|
+
`moli_probe action=install` runs the official `moli-installer.sh` via `sh`.
|
|
278
|
+
|
|
279
|
+
On Linux, `moli_probe` discovered `~/.local/bin/moli` unaided, and `moli_fetch` rendered a
|
|
280
|
+
region-gated storefront end to end — through a local forward proxy, with a Netscape `cookieFile` —
|
|
281
|
+
returning a 500+ KB page whose final URL carried the cookie-driven query parameters. That run
|
|
282
|
+
exercised the proxy download route as well as the engine paths.
|
|
283
|
+
|
|
258
284
|
## Configuration
|
|
259
285
|
|
|
260
286
|
Override any field under the plugin's `config` entry in the profile patch layer. All are optional.
|
|
@@ -277,9 +303,12 @@ installation under a versioned subdirectory is still found.
|
|
|
277
303
|
|
|
278
304
|
## Known limitations
|
|
279
305
|
|
|
280
|
-
- **
|
|
281
|
-
|
|
282
|
-
|
|
306
|
+
- **macOS is untested, and Linux was tested by the contributor rather than the maintainer.** The
|
|
307
|
+
maintainer is a student with no macOS or Linux machine, so Linux was verified independently by
|
|
308
|
+
[@nd002723](https://github.com/nd002723) on Ubuntu 24.04 (see [Platform support](#platform-support));
|
|
309
|
+
macOS has not been run on a real host at all. **If something fails on either, please open an
|
|
310
|
+
[issue](https://github.com/27painter/dsh-moli/issues) with the error output** — that is the only way
|
|
311
|
+
these paths get fixed.
|
|
283
312
|
- **Moli itself is not bundled.** Install it separately or let `moli_probe action=install` fetch it.
|
|
284
313
|
- **Only `fetch` is wrapped.** `moli serve` (CDP/WebDriver server) and `moli import` (session
|
|
285
314
|
import) are not exposed as tools yet.
|
package/README.zh.md
CHANGED
|
@@ -236,6 +236,29 @@ Moli 仓库自带官方 agent skill:[`moli-webfetch`](https://github.com/lexmo
|
|
|
236
236
|
导入(例如 `@deepseek-ai/schemastery`)都会解析到插件目录而非 profile 的 `node_modules`,导致
|
|
237
237
|
插件加载失败。本插件只导入 `node:` 内置模块和自己的文件。
|
|
238
238
|
|
|
239
|
+
## 平台支持
|
|
240
|
+
|
|
241
|
+
支持 **Windows、Linux、macOS**(x64 / arm64)。
|
|
242
|
+
|
|
243
|
+
> **Linux 与 macOS 支持由 [@nd002723](https://github.com/nd002723) 贡献**,见
|
|
244
|
+
> [PR #1](https://github.com/27painter/dsh-moli/pull/1):POSIX 标准位置的引擎探测、`sh` 安装器路径,
|
|
245
|
+
> 以及把 PowerShell 相关路径按平台门控。
|
|
246
|
+
|
|
247
|
+
| 平台 | 状态 |
|
|
248
|
+
|---|---|
|
|
249
|
+
| **Windows** | 由维护者验证 |
|
|
250
|
+
| **Linux** | 由贡献者在 Ubuntu 24.04 LTS、Node v24.21.0、DSH 0.2.1-alpha.2、dsh-moli 1.1.0、Moli 1.1.17 上验证通过 |
|
|
251
|
+
| **macOS** | 已实现,尚未在真机验证 |
|
|
252
|
+
|
|
253
|
+
- **Windows** —— 引擎在官方安装位置查找(`%LOCALAPPDATA%\Moli\bin\moli.exe` 与 `%USERPROFILE%\.local\bin\moli.exe`),
|
|
254
|
+
并以 `%LOCALAPPDATA%\Moli` 递归扫描兜底;`moli_probe action=install` 走官方 PowerShell 安装器。
|
|
255
|
+
- **Linux / macOS** —— 引擎在标准位置(`~/.local/bin/moli`、`~/.local/share/moli/bin/moli`、
|
|
256
|
+
`~/.local/share/moli/moli`、`~/.moli/bin/moli`)与 `$PATH` 中查找;`moli_probe action=install` 通过 `sh` 执行官方 `moli-installer.sh`。
|
|
257
|
+
|
|
258
|
+
在 Linux 上,`moli_probe` 无需干预就找到了 `~/.local/bin/moli`;`moli_fetch` 端到端渲染了一个地区限制的
|
|
259
|
+
电商站——经由本地正向代理、并带上 Netscape 格式的 `cookieFile`——返回 500+ KB 的页面,最终 URL 带上了
|
|
260
|
+
cookie 驱动的查询参数。那次实测同时覆盖了代理下载路由与引擎探测路径。
|
|
261
|
+
|
|
239
262
|
## 配置
|
|
240
263
|
|
|
241
264
|
在 profile 的补丁层里覆盖 `config`,全部可选:
|
|
@@ -258,8 +281,10 @@ Moli 仓库自带官方 agent skill:[`moli-webfetch`](https://github.com/lexmo
|
|
|
258
281
|
|
|
259
282
|
## 已知限制
|
|
260
283
|
|
|
261
|
-
-
|
|
262
|
-
|
|
284
|
+
- **macOS 未经验证,Linux 是由贡献者而非维护者验证的。** 维护者是一名**没有 macOS / Linux 电脑**的
|
|
285
|
+
大学生;Linux 由 [@nd002723](https://github.com/nd002723) 在 Ubuntu 24.04 上独立验证通过(见
|
|
286
|
+
[平台支持](#平台支持)),而 macOS 从未在真机运行过。**任一平台出问题,都请带上报错信息开
|
|
287
|
+
[issue](https://github.com/27painter/dsh-moli/issues)**——那是这些路径能被打通的唯一途径。
|
|
263
288
|
- **不包含 Moli 本体。** 需自行安装,或让 `moli_probe action=install` 替你拉取。
|
|
264
289
|
- **只封装了 `fetch`。** `moli serve`(CDP/WebDriver 服务器)与 `moli import`(会话导入)尚未做成工具。
|
|
265
290
|
- **不支持单次取消。** 引擎是同步执行的,被中断的回合无法中途终止抓取;已在进行的调用会跑完并
|
package/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** Host half of dsh-moli: expose the local `moli
|
|
1
|
+
/** Host half of dsh-moli: expose the local `moli` headless browser as callable tools. */
|
|
2
2
|
import { MANUAL_INSTALL, installMoli } from './lib/install.js';
|
|
3
3
|
import { findMoli, moliCandidates, probe, runFetch } from './lib/moli.js';
|
|
4
4
|
|
|
@@ -198,7 +198,7 @@ function renderProbe(_args, value) {
|
|
|
198
198
|
return [{ type: 'text', text: lines.join('\n') }];
|
|
199
199
|
}
|
|
200
200
|
if (value.installed !== true) {
|
|
201
|
-
const lines = ['moli
|
|
201
|
+
const lines = ['moli was not found. Call moli_probe with action "install", or set the plugin config field `exePath`.'];
|
|
202
202
|
lines.push(`Manual install — PowerShell: ${MANUAL_INSTALL.powershell}`);
|
|
203
203
|
lines.push(`Manual install — Linux/macOS: ${MANUAL_INSTALL.shell}`);
|
|
204
204
|
return [{ type: 'text', text: lines.join('\n') }];
|
|
@@ -239,7 +239,7 @@ export function apply(ctx, config) {
|
|
|
239
239
|
const insecureTls = options.insecureTls ?? DEFAULTS.insecureTls;
|
|
240
240
|
|
|
241
241
|
if (cachedExe === undefined) {
|
|
242
|
-
ctx.logger?.warn?.('dsh-moli: moli
|
|
242
|
+
ctx.logger?.warn?.('dsh-moli: moli not found; moli_probe can install it, or set config.exePath');
|
|
243
243
|
}
|
|
244
244
|
|
|
245
245
|
ctx.tools.register({
|
|
@@ -247,7 +247,7 @@ export function apply(ctx, config) {
|
|
|
247
247
|
description: 'Fetch a URL with the local Moli headless browser: rendered HTML or Markdown, the semantic'
|
|
248
248
|
+ ' tree, a JSON envelope with status and final_url, a JavaScript expression result, a PNG screenshot, or a'
|
|
249
249
|
+ ' PDF. Use it for pages that need real rendering or JavaScript; prefer web_fetch for plain text. Screenshot'
|
|
250
|
-
+ ' and pdf dumps require writeTo. If moli
|
|
250
|
+
+ ' and pdf dumps require writeTo. If moli is missing, run moli_probe with action "install" first.',
|
|
251
251
|
parameters: FETCH_PARAMETERS,
|
|
252
252
|
output: { schema: FETCH_OUTPUT_SCHEMA, render: renderFetch },
|
|
253
253
|
isConcurrencySafe: args => args.writeTo === undefined || args.writeTo === '',
|
|
@@ -258,9 +258,9 @@ export function apply(ctx, config) {
|
|
|
258
258
|
return {
|
|
259
259
|
ok: false,
|
|
260
260
|
url: args.url,
|
|
261
|
-
error: 'moli
|
|
261
|
+
error: 'moli was not found',
|
|
262
262
|
sandboxTlsBlock: false,
|
|
263
|
-
hint: 'Call moli_probe with action "install" to download it, or set the dsh-moli config field `exePath` to the absolute moli
|
|
263
|
+
hint: 'Call moli_probe with action "install" to download it, or set the dsh-moli config field `exePath` to the absolute moli path.',
|
|
264
264
|
manualInstall: MANUAL_INSTALL,
|
|
265
265
|
};
|
|
266
266
|
}
|
|
@@ -279,7 +279,7 @@ export function apply(ctx, config) {
|
|
|
279
279
|
description: 'Report the local Moli install: version, executable path, and whether HTTPS fetching works from'
|
|
280
280
|
+ ' this process. With action "install" it downloads and runs the official Moli installer instead, then'
|
|
281
281
|
+ ' reports the executable and the SHA256 of the installer it ran. Use "install" only when moli_fetch'
|
|
282
|
-
+ ' reports that moli
|
|
282
|
+
+ ' reports that moli is missing; it downloads and executes third-party code.',
|
|
283
283
|
parameters: {
|
|
284
284
|
type: 'object',
|
|
285
285
|
additionalProperties: false,
|
|
@@ -321,7 +321,7 @@ export function apply(ctx, config) {
|
|
|
321
321
|
|
|
322
322
|
const exe = engine();
|
|
323
323
|
if (exe === undefined) {
|
|
324
|
-
return { installed: false, detail: 'moli
|
|
324
|
+
return { installed: false, detail: 'moli not found. Re-run with action "install" to download it.', manualInstall: MANUAL_INSTALL };
|
|
325
325
|
}
|
|
326
326
|
const result = probe(exe, 'https://example.com', insecureTls);
|
|
327
327
|
return {
|
|
@@ -336,5 +336,5 @@ export function apply(ctx, config) {
|
|
|
336
336
|
},
|
|
337
337
|
});
|
|
338
338
|
|
|
339
|
-
ctx.logger?.info?.(`dsh-moli: registered moli_fetch and moli_probe (${cachedExe ?? 'moli
|
|
339
|
+
ctx.logger?.info?.(`dsh-moli: registered moli_fetch and moli_probe (${cachedExe ?? 'moli missing'})`);
|
|
340
340
|
}
|
package/lib/install.js
CHANGED
|
@@ -30,7 +30,9 @@ import { join } from 'node:path';
|
|
|
30
30
|
import { findMoli, moliVersion } from './moli.js';
|
|
31
31
|
|
|
32
32
|
/** Official installer for the latest release. */
|
|
33
|
-
export const INSTALLER_URL =
|
|
33
|
+
export const INSTALLER_URL = process.platform === 'win32'
|
|
34
|
+
? 'https://github.com/lexmount/moli/releases/latest/download/moli-installer.ps1'
|
|
35
|
+
: 'https://github.com/lexmount/moli/releases/latest/download/moli-installer.sh';
|
|
34
36
|
|
|
35
37
|
/** Moli's own README instructions, for the manual path. */
|
|
36
38
|
export const MANUAL_INSTALL = {
|
|
@@ -136,7 +138,7 @@ export async function downloadInstaller(options) {
|
|
|
136
138
|
const url = options.installerUrl ?? INSTALLER_URL;
|
|
137
139
|
const configured = typeof options.proxy === 'string' && options.proxy.length > 0 ? options.proxy : undefined;
|
|
138
140
|
const timeoutMs = options.timeoutMs ?? 60000;
|
|
139
|
-
const powershell = options.powershell ?? 'powershell.exe';
|
|
141
|
+
const powershell = options.powershell ?? (process.platform === 'win32' ? 'powershell.exe' : 'powershell');
|
|
140
142
|
|
|
141
143
|
// Never hand a loopback URL to a proxy — with a stale system proxy setting the
|
|
142
144
|
// request would go to a dead port and wait out the connect timeout. Windows
|
|
@@ -152,20 +154,22 @@ export async function downloadInstaller(options) {
|
|
|
152
154
|
`${TLS_PRELUDE}${bypass} Invoke-WebRequest -UseBasicParsing ${extra} -Uri '${url}' -OutFile $env:MOLI_DL_TARGET -TimeoutSec ${String(Math.ceil(timeoutMs / 1000))}`,
|
|
153
155
|
];
|
|
154
156
|
|
|
155
|
-
const routes =
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
157
|
+
const routes = process.platform === 'win32'
|
|
158
|
+
? [
|
|
159
|
+
{
|
|
160
|
+
method: 'Invoke-WebRequest (direct)',
|
|
161
|
+
command: powershell,
|
|
162
|
+
args: iwr(''),
|
|
163
|
+
},
|
|
164
|
+
]
|
|
165
|
+
: [];
|
|
162
166
|
|
|
163
167
|
// Only attempt proxy routes when something is actually listening there.
|
|
164
168
|
let proxyAlive = false;
|
|
165
169
|
if (configured !== undefined) {
|
|
166
170
|
proxyAlive = await proxyListens(configured);
|
|
167
171
|
}
|
|
168
|
-
if (configured !== undefined && proxyAlive) {
|
|
172
|
+
if (process.platform === 'win32' && configured !== undefined && proxyAlive) {
|
|
169
173
|
routes.push({
|
|
170
174
|
method: `Invoke-WebRequest (proxy ${configured})`,
|
|
171
175
|
command: powershell,
|
|
@@ -173,14 +177,14 @@ export async function downloadInstaller(options) {
|
|
|
173
177
|
});
|
|
174
178
|
}
|
|
175
179
|
routes.push({
|
|
176
|
-
method: 'curl
|
|
177
|
-
command: 'curl.exe',
|
|
180
|
+
method: 'curl (direct)',
|
|
181
|
+
command: process.platform === 'win32' ? 'curl.exe' : 'curl',
|
|
178
182
|
args: ['-sSL', ...curlBypass, '--max-time', String(Math.ceil(timeoutMs / 1000)), '-o', target, url],
|
|
179
183
|
});
|
|
180
184
|
if (configured !== undefined && proxyAlive) {
|
|
181
185
|
routes.push({
|
|
182
|
-
method: `curl
|
|
183
|
-
command: 'curl.exe',
|
|
186
|
+
method: `curl (proxy ${configured})`,
|
|
187
|
+
command: process.platform === 'win32' ? 'curl.exe' : 'curl',
|
|
184
188
|
args: ['-sSL', ...curlBypass, '--max-time', String(Math.ceil(timeoutMs / 1000)), '--proxy', configured, '-o', target, url],
|
|
185
189
|
});
|
|
186
190
|
}
|
|
@@ -242,7 +246,7 @@ export async function downloadInstaller(options) {
|
|
|
242
246
|
export async function installMoli(options = {}) {
|
|
243
247
|
const timeoutMs = options.timeoutMs ?? 180000;
|
|
244
248
|
const workDir = mkdtempSync(join(tmpdir(), 'dsh-moli-install-'));
|
|
245
|
-
const target = join(workDir, 'moli-installer.ps1');
|
|
249
|
+
const target = join(workDir, process.platform === 'win32' ? 'moli-installer.ps1' : 'moli-installer.sh');
|
|
246
250
|
try {
|
|
247
251
|
const download = await downloadInstaller({
|
|
248
252
|
target,
|
|
@@ -279,9 +283,11 @@ export async function installMoli(options = {}) {
|
|
|
279
283
|
}
|
|
280
284
|
}
|
|
281
285
|
|
|
282
|
-
const run =
|
|
283
|
-
|
|
284
|
-
|
|
286
|
+
const run = process.platform === 'win32'
|
|
287
|
+
? spawnSync(options.powershell ?? 'powershell.exe',
|
|
288
|
+
['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', target],
|
|
289
|
+
{ stdio: ['ignore', 'ignore', 'ignore'], timeout: timeoutMs, windowsHide: true })
|
|
290
|
+
: spawnSync('sh', [target], { stdio: ['ignore', 'ignore', 'ignore'], timeout: timeoutMs });
|
|
285
291
|
const runFailed = run.error !== undefined && run.error !== null;
|
|
286
292
|
const runStatus = runFailed ? null : (run.status ?? 0);
|
|
287
293
|
|
|
@@ -294,7 +300,7 @@ export async function installMoli(options = {}) {
|
|
|
294
300
|
ok: false,
|
|
295
301
|
error: runFailed
|
|
296
302
|
? `running the installer failed: ${run.error.message}`
|
|
297
|
-
: `the installer ${runStatus === 0 ? 'reported success' : `exited ${String(runStatus)}`}, but no working moli
|
|
303
|
+
: `the installer ${runStatus === 0 ? 'reported success' : `exited ${String(runStatus)}`}, but no working moli binary was found afterwards`,
|
|
298
304
|
installerSha256: download.sha256,
|
|
299
305
|
installerBytes: download.bytes,
|
|
300
306
|
method: download.method,
|
package/lib/moli.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
import { spawnSync } from 'node:child_process';
|
|
17
17
|
import { createHash } from 'node:crypto';
|
|
18
18
|
import { closeSync, existsSync, mkdtempSync, openSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
19
|
-
import { tmpdir } from 'node:os';
|
|
19
|
+
import { homedir, tmpdir } from 'node:os';
|
|
20
20
|
import { dirname, join } from 'node:path';
|
|
21
21
|
|
|
22
22
|
/** Output formats that are bytes, not text. */
|
|
@@ -42,27 +42,44 @@ const WAIT_STAGES = new Set(['domcontentloaded', 'load', 'networkidle', 'domstab
|
|
|
42
42
|
* @returns candidate paths, in probe order.
|
|
43
43
|
*/
|
|
44
44
|
export function moliCandidates(override) {
|
|
45
|
+
const isWin = process.platform === 'win32';
|
|
46
|
+
const home = process.env.HOME ?? homedir();
|
|
45
47
|
const listed = [
|
|
46
48
|
override,
|
|
47
49
|
process.env.MOLI_BIN,
|
|
48
|
-
|
|
49
|
-
process.env.
|
|
50
|
+
// Windows: official installer location.
|
|
51
|
+
isWin && process.env.LOCALAPPDATA !== undefined ? join(process.env.LOCALAPPDATA, 'Moli', 'bin', 'moli.exe') : undefined,
|
|
52
|
+
isWin && process.env.USERPROFILE !== undefined ? join(process.env.USERPROFILE, '.local', 'bin', 'moli.exe') : undefined,
|
|
53
|
+
// Linux/macOS: installer.sh default ~/.local/bin/moli, plus versioned/shared dirs.
|
|
54
|
+
!isWin ? join(home, '.local', 'bin', 'moli') : undefined,
|
|
55
|
+
!isWin ? join(home, '.local', 'share', 'moli', 'bin', 'moli') : undefined,
|
|
56
|
+
!isWin ? join(home, '.local', 'share', 'moli', 'moli') : undefined,
|
|
57
|
+
!isWin ? join(home, '.moli', 'bin', 'moli') : undefined,
|
|
50
58
|
].filter(candidate => typeof candidate === 'string' && candidate.length > 0);
|
|
51
59
|
|
|
52
60
|
const discovered = [];
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
if (isWin) {
|
|
62
|
+
const root = process.env.LOCALAPPDATA === undefined ? undefined : join(process.env.LOCALAPPDATA, 'Moli');
|
|
63
|
+
if (root !== undefined && existsSync(root)) {
|
|
64
|
+
let entries = [];
|
|
65
|
+
try {
|
|
66
|
+
entries = readdirSync(root, { recursive: true, withFileTypes: true });
|
|
67
|
+
} catch {
|
|
68
|
+
entries = [];
|
|
69
|
+
}
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
if (entry.isFile() && entry.name.toLowerCase() === 'moli.exe') {
|
|
72
|
+
discovered.push(join(entry.parentPath ?? root, entry.name));
|
|
73
|
+
}
|
|
64
74
|
}
|
|
65
75
|
}
|
|
76
|
+
} else {
|
|
77
|
+
const seen = new Set(listed);
|
|
78
|
+
for (const dir of String(process.env.PATH ?? '').split(':')) {
|
|
79
|
+
if (dir === '') continue;
|
|
80
|
+
const candidate = join(dir, 'moli');
|
|
81
|
+
if (existsSync(candidate) && !seen.has(candidate)) discovered.push(candidate);
|
|
82
|
+
}
|
|
66
83
|
}
|
|
67
84
|
return [...listed, ...discovered];
|
|
68
85
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-moli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DeepSeek Harness tools for the local Moli headless browser: rendered fetches, markdown, semantic trees, screenshots, and PDFs.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"url": "https://github.com/27painter/dsh-moli/issues"
|
|
27
27
|
},
|
|
28
28
|
"engines": {
|
|
29
|
-
"node": ">=20"
|
|
29
|
+
"node": ">=20.12"
|
|
30
30
|
},
|
|
31
31
|
"publishConfig": {
|
|
32
32
|
"registry": "https://registry.npmjs.org",
|