@mearl/setup 3.0.0-beta.9 → 3.0.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/README.en.md ADDED
@@ -0,0 +1,276 @@
1
+ # @mearl/setup
2
+
3
+ [简体中文](README.md) | [English](README.en.md)
4
+
5
+ Use `@3` to select the latest stable 3.x installer and install matching runtime and extension versions.
6
+
7
+ One-time installation, updates, removal, and configuration for Mearl environments. No global setup installation is needed:
8
+
9
+ Run `node --version` before installation to verify Node.js >= 20.18.0.
10
+
11
+ ```bash
12
+ npx @mearl/setup@3 [install] [--local] [--cloud] [--cloud-mode <id>] [--mcp]
13
+ npx @mearl/setup@3 update [--local] [--cloud] [--cloud-mode <id>] [--mcp]
14
+ npx @mearl/setup@3 uninstall [--local] [--cloud] [--cloud-mode <id>] [--mcp] [--full]
15
+ npx @mearl/setup@3 extension [--full] [--force]
16
+ npx @mearl/setup@3 extension --extract
17
+ npx @mearl/setup@3 plugin [name] <path>
18
+ npx @mearl/setup@3 plugin install <skills add arguments...>
19
+ npx @mearl/setup@3 plugin uninstall <name>
20
+ npx @mearl/setup@3 provider <install|update|uninstall> <package|id>
21
+ npx @mearl/setup@3 profile install <source> [--browser <id-or-name>]
22
+ npx @mearl/setup@3 preset repair [--replace-skill <name> ...]
23
+ ```
24
+
25
+ Browser Providers install and evolve independently. After installation, run `mearl <providerId>` to inspect that version's capabilities,
26
+ configuration, and launch arguments.
27
+
28
+ Install optional cloud protocols through a preset before selecting `--cloud-mode <id>`. See [`@mearl/plugin-qoder`](../plugin-qoder/README.en.md) for Qoder pairing; setup does not include or install it by default.
29
+
30
+ ## Install App emulation
31
+
32
+ ```bash
33
+ npx @mearl/setup@3 profile install mearlhq/wechat-profile
34
+ ```
35
+
36
+ The source can be a public GitHub `owner/repo`, `owner/repo@ref`, or a full HTTPS bundle manifest URL. GitHub sources load the root `bundle.json` from the resolved commit. The command installs every App emulation configuration declared by that bundle and preserves the active Preset. When multiple local extensions are connected, select a target with `--browser`. Confirm source access in the extension if prompted.
37
+
38
+ In the extension's quick settings, open the App selector and choose **Add App emulation** to select an official option or enter a source. The App button opens the installer directly when the list is empty.
39
+
40
+ ## Install and register Skill plugins
41
+
42
+ Install a Skill and register its CLI, SDK, and MCP capabilities with one command:
43
+
44
+ ```bash
45
+ npx @mearl/setup@3 plugin install https://github.com/mearlhq/mearl --skill browser-tab-manager --yes
46
+ mearl tabs summary
47
+ ```
48
+
49
+ Installation arguments pass to a pinned public Skills CLI (currently skills@1.5.0, compatible with the project's minimum Node version) using the global shared directory. Both default symlink and copy modes support registration. Copy mode uses list --global --json to confirm the canonical path for this successful install, without guessing directories or scanning other Skills. Set MEARL_SKILLS_PATH for a custom CLI. Arguments reject the -- separator, which would truncate trailing options.
50
+
51
+ The installation host's local plugin catalog determines preset-managed capabilities. install/update maintains managed registrations and preserves user registrations. Removing the last dependent environment removes managed plugins and the Mearl Skill; other dependent environments retain them. Configuration preparation, host synchronization, and actual status share the [installation contract](../../docs/preset-installation.en.md).
52
+
53
+ New registrations created by Native preset batches are setup-managed and retain that ownership through Agent updates. Existing user registrations in the same directory and custom user aliases retain their ownership. Managed removal encountering user aliases sharing a Skill removes only managed registrations and keeps Skill files.
54
+
55
+ GUI and CLI share the preset installer and override rules. If a saved installation is incomplete, `preset repair` retries accepted local resources without redownloading the preset or implicitly replacing conflicting sources. Explicit repeated `--replace-skill` authorizes replacing those Skills' sources or same-name plugin registrations for this invocation:
56
+
57
+ ```bash
58
+ npx @mearl/setup@3 preset repair --replace-skill example-skill --replace-skill another-skill
59
+ ```
60
+
61
+ Already installed Skills receive registration repair only; source conflicts reinstall. Unlisted conflicts still block installation, and undeclared tool names or changed installation identities are rejected. Replacement authorization does not persist into later repairs or automatic updates. Missing browser permissions still require extension authorization. Output uses preset JSON: ready exits `0`, pending components `3`, installation failure `1`, and invalid arguments `2`.
62
+
63
+ Automatic registration defaults to the installation directory name; an entry can declare `name` for a shorter command.
64
+ Reinstallation refreshes same-name bindings. JavaScript entries must default-export `register(app)`; Python entries must export a `register(app)` function. See the
65
+ [plugin runtime contract](../../docs/plugin-runtime.en.md).
66
+
67
+ Register a Skill distributed elsewhere or a custom entry directly by path:
68
+
69
+ ```bash
70
+ npx @mearl/setup@3 plugin /absolute/path/to/browser-tab-manager
71
+ npx @mearl/setup@3 plugin custom ./skills/custom/entry.mjs
72
+ ```
73
+
74
+ path accepts a directory or file. Directories default to `scripts/main.mjs` or `scripts/main.py`; relative paths resolve against the calling working directory. Plugin names prefer explicit name, then entry `name` metadata, then the Skill directory name. Normal registration needs only path; explicit name is for overrides or older entries. Python requires `python3 >= 3.9`; `MEARL_PYTHON` selects an interpreter. setup validates loading but does not install Python or run `pip install`.
75
+ Registration checks the entry's register function and saves its absolute path. Same-name registration updates the binding.
76
+ Both flows check local `@mearl/client` and `@mearl/native-host`. If absent or older than setup,
77
+ the existing package manager installs setup's matching versions and revalidates. Newer versions remain untouched.
78
+ native-host idempotently initializes Skills mode. If environment completion or initialization fails, the installed Skill remains and registration stops.
79
+ JavaScript plugins may export `setup({ runCommand })` to prepare Skill-owned dependencies before registration. Both installation paths await it, and registration stops on failure. Skills implement their own first-use dependency checks; see the [client contract](../client/README.en.md#skill-plugins).
80
+
81
+ After registration, run `mearl <name> --help` anywhere, use SDK `invokePlugin`, or MCP `plugin_invoke`. Ordinary calls do not update Skill sources; missing plugins in the selected catalog trigger one on-demand installation. Skill-owned dependencies are prepared by their installation hooks and first-use checks.
82
+
83
+ To remove a plugin:
84
+
85
+ ```bash
86
+ npx @mearl/setup@3 plugin uninstall tabs
87
+ ```
88
+
89
+ For entries under `~/.agents/skills/<skill>/`, setup first runs
90
+ `skills remove <skill> --global --yes`, then clears all registrations for that Skill on success.
91
+ If the entry directory was manually deleted, Skills can still identify the removal and clean its tracking records.
92
+ Removal failures preserve registrations for retry. Other manually registered paths only unregister from Mearl without deleting source.
93
+ Repeated removal of an unregistered name succeeds without changes.
94
+
95
+ ## Manage browser Providers
96
+
97
+ Providers ship as npm packages without requiring their own CLI or install scripts. setup uses the current npm package manager
98
+ for global package lifecycle and reads `package.json#mearl.provider` to register the protocol entry,
99
+ browser type name, and form arguments:
100
+
101
+ ```bash
102
+ npx @mearl/setup@3 provider install @example/mearl-provider-device
103
+ npx @mearl/setup@3 provider update example-device
104
+ npx @mearl/setup@3 provider uninstall example-device
105
+ ```
106
+
107
+ Install accepts package names, versions, and dist-tags; update/remove accept providerId or full package name. Core @mearl packages always use public npm; other scopes follow caller package-manager settings. Installation uses the package manager launching setup;
108
+ updates and removal reuse the Provider's original manager. Records contain only
109
+ validated package names, installation directories, and package-local entries. Packages cannot inject launch commands, arguments, working directories, or environment variables.
110
+ Registration clears only after successful package removal, allowing retries.
111
+
112
+ ## Behavior
113
+
114
+ - Mearl-owned npm packages install and update from `https://registry.npmjs.org/`, matching version queries. Third-party packages use current manager registry settings.
115
+ - `install` is optional: `npx @mearl/setup@3` equals `npx @mearl/setup@3 install`.
116
+ An initial option also implies installation, such as `npx @mearl/setup@3 --cloud --yes`.
117
+ - `install` defaults to `--local` when no environment is specified.
118
+ - `-h` / `--help` shows help; `-v` / `--version` shows setup's version.
119
+ - Without environment flags, `update` and `uninstall` detect
120
+ `mearl-native-host`, `mearl-cloud-server`, and `mearl-mcp-server` in the current `PATH` and may process
121
+ multiple installed environments.
122
+ - Combine `--local`, `--cloud`, and `--mcp` to process only selected environments.
123
+ `--cloud-mode <id>` also selects cloud and requires an installed mode capability.
124
+ - Existing environments keep their original package manager and global prefix; first installation uses the manager launching setup.
125
+ - `uninstall` recognizes development symlinks installed through `npm link` or `dev:link` into npm global directories,
126
+ removing packages and command links through the original npm prefix while preserving repository source. Install/update autodetection handles normal global installations only.
127
+ - Cloud manages `@mearl/cloud-server` and `@mearl/client` together. WebSocket mode starts the server after first installation
128
+ and restarts after updates only if it was running. Other modes use installed `cloud.server.<id>`; all call `stop` before removal.
129
+ - local/cloud share `@mearl/client`. Combined installation processes it once; removing one environment retains it if the other needs it.
130
+ - Updating a 2.5.x cloud environment first removes `@mearl/cloud-client` to avoid its `mearl` bin conflicting with the unified client.
131
+ - local/cloud share the global mearl Skill, located through the Skills JSON inventory and processed once.
132
+ - Install/update reinstalls the global mearl Skill from the public repository through `skills add`, associating detected Agents. Updates must confirm this installation and check the global JSON inventory, not rely only on a third-party updater's exit code.
133
+ Skip with `--skip-skill`.
134
+ - Removing one environment retains the global mearl Skill if another depends on it.
135
+ - Automatic maintenance of selected presets preserves user registrations. Managed removal keeps shared Skill files when user aliases use the same directory.
136
+ - `uninstall --full` also removes every registered CLI plugin and browser Provider.
137
+ Skills-installed plugins remove their Skills; manually registered external plugins only unregister, retaining source. Provider
138
+ registrations clear only after successful npm removal. Individual failures do not block other cleanup, but the final exit is nonzero.
139
+ This does not remove the Chrome extension. `extension --full` still means installing the extension and complete local environment.
140
+ - After all environments, plugins, and Providers are successfully removed, `uninstall --full` stops the plugin service,
141
+ confirms no active or uncertain-exit tasks remain, and removes task history, source snapshots, service logs, and page-session files of ended hosts. Partial removal, individual failures, or a restarted service retain shared caches.
142
+ Rerun to finish cache cleanup even after packages and registrations are gone. Custom `MEARL_PLUGIN_DIR` limits cleanup to that registry's execution cache.
143
+ Notes, iteration records, settings, identities, plugin persistent state, Provider-private data, browser profiles, and exported designs remain;
144
+ CDP tab-ID counters also remain so stale requests cannot target new tabs.
145
+ - `@mearl/mcp-server` commands handle `--mcp` client configuration and cleanup.
146
+ - Default `install` / `update` do not manage the browser extension. The separate `extension` command reads
147
+ matching beta extension metadata from `https://github.com/mearlhq/mearl/releases/download/v<version>/` (stable setup uses `releases/latest/download/`) and downloads `mearl-chrome-v*.zip`, atomically extracting it into
148
+ `~/.mearl/chrome-extension`. First installation uses browser-level CDP `Extensions.loadUnpacked`;
149
+ later updates use `mearl check --local --json` to confirm the running extension comes from the setup-managed directory, replace
150
+ files, and reload through Native Host. The fixed directory and manifest `key` keep the extension ID
151
+ and browser storage stable.
152
+ - `extension --full` first installs or updates the extension, then uses the local profile to install
153
+ `@mearl/client`, install and initialize `@mearl/native-host`, and install the global Mearl Skill.
154
+ Both parts are attempted; any failure returns nonzero while retaining installed components. It does not install
155
+ cloud or MCP environments.
156
+ - Before downloading, CDP checks the source of an extension with the same ID. Installation proceeds if absent or `UNPACKED`
157
+ from the fixed directory. Chrome Web Store, enterprise-policy, and other local-directory installations remain by default. Only explicit
158
+ `--force` takes over and replaces them.
159
+
160
+ ## Install or update the browser extension
161
+
162
+ For first use, run `npx @mearl/setup@3` and load the matching release ZIP. You can also use the Chrome Web Store when it offers the matching version. [Installation steps](../../README.en.md#install-locally)
163
+
164
+ Use this command to let setup manage the extension.
165
+
166
+ ```bash
167
+ npx @mearl/setup@3 extension
168
+ ```
169
+
170
+ Add `--full` to also install the local CLI, Native Host, and Skill in the same run.
171
+
172
+ To load the extension manually, run `npx @mearl/setup@3 extension --extract`. Setup downloads the release ZIP,
173
+ unpacks it into a new directory under `~/.mearl/extension-downloads/`, and prints the full path. Open
174
+ `chrome://extensions/`, enable **Developer mode**, click **Load unpacked**, and select that directory.
175
+ Keep the directory while Chrome uses the extension. This works with Chrome closed and with remote debugging disabled.
176
+
177
+ Automatic installation uses CDP for first installation, initial migration of older setup extensions, or unavailable Native Host reload. If
178
+ Chrome has not enabled CDP, interactive setup prompts you to open
179
+ `chrome://inspect/#remote-debugging`, enable remote debugging, and allow the connection, then waits for installation. Human-facing
180
+ instructions should omit `--yes`. Without a TTY, use `npx @mearl/setup@3 extension --yes`: it tries
181
+ once and stops immediately if CDP is unavailable. If setup ownership cannot be confirmed and CDP is unavailable, it does not download or modify
182
+ the extension directory. Enable CDP and rerun the same command. If Chrome lacks required extension-installation CDP methods,
183
+ the command stops and asks for a Chrome upgrade.
184
+
185
+ Each invocation reads the newest extension version from the download page, without requiring it to match setup, CLI, or Native Host.
186
+ CDP source checks and `Extensions.loadUnpacked` reuse one connection to avoid repeated authorization.
187
+ A successful installation saves a setup-management marker. `mearl check --local --json` reads the marker from the running extension too;
188
+ CDP-free updates require matching paths on both sides. If the user later installs the Store version, that running extension
189
+ does not return the marker. setup checks through CDP again and preserves the Store installation
190
+ unless `--force` is supplied.
191
+
192
+ Setup-managed extensions check remote `config.json` for updates without silently updating. A newer version adds
193
+ a red dot to the DevTools settings button and **Update now** and **Release notes** entries to quick settings.
194
+ Only clicking **Update now** makes Native Host revalidate the remote version, ownership marker, download URL,
195
+ optional SHA256, and ZIP manifest, then atomically replace the directory and reload. This path never falls back to CDP; Store,
196
+ enterprise-policy, and other-directory installations do not use setup updates. For updates downloaded by Chrome,
197
+ the extension passively listens to `runtime.onUpdateAvailable` and shows the same user-triggered reload notification, without
198
+ actively calling `runtime.requestUpdateCheck()`.
199
+
200
+ ## Automatic updates following the extension
201
+
202
+ The extension and Native Host exchange versions on first connection. Each extension background startup checks once. When the extension
203
+ moves to a higher `major.minor` release line, the older Host keeps its connection available while invoking
204
+ the newest `@mearl/setup` in that `major.minor.x` range to update both `@mearl/native-host` and
205
+ `@mearl/client` to the newest versions in that range. On success, Host exits and the extension's existing reconnect mechanism
206
+ starts the new version. Success requires both local CLI versions to match and belong to the extension's release line.
207
+
208
+ Automatic updates never downgrade a newer Host or modify cloud, MCP, or independently installed Mearl Skills.
209
+ If the npm version is unpublished, the network unavailable, or updating fails, the old Host keeps running. The next extension background
210
+ lifecycle may retry; manual updates use `npx @mearl/setup@3 update --local`.
211
+
212
+ ## Human and Agent use
213
+
214
+ - Humans running interactively should omit `--yes`; setup prompts for Skill targets or MCP clients when needed.
215
+ - Agents, CI, and other non-TTY environments should pass `--yes` or
216
+ `--non-interactive`. First installation selects detected Agent / MCP clients; removal clears
217
+ all detected relevant configuration.
218
+ - Non-TTY `install` / `uninstall` without these flags exits before changes and prints the correct command.
219
+ - Exit `0` means complete success, `1` incomplete execution, and `2` invalid arguments or interaction mode.
220
+
221
+ ## Examples
222
+
223
+ ```bash
224
+ # Default local browser environment installation
225
+ npx @mearl/setup@3
226
+
227
+ # Equivalent to the previous command
228
+ npx @mearl/setup@3 install
229
+
230
+ # Noninteractive Agent / CI installation
231
+ npx @mearl/setup@3 --yes
232
+
233
+ # Install the cloud Agent environment
234
+ npx @mearl/setup@3 --cloud
235
+
236
+ # Use one machine as both a local browser host and cloud Agent
237
+ npx @mearl/setup@3 --local --cloud
238
+
239
+ # Update all detected Mearl environments in the current PATH
240
+ npx @mearl/setup@3 update
241
+
242
+ # Independently install or update the latest browser extension
243
+ npx @mearl/setup@3 extension
244
+
245
+ # Complete local environment: client, Native Host, Skill, and extension
246
+ npx @mearl/setup@3 extension --full
247
+
248
+ # Remove only the local environment
249
+ npx @mearl/setup@3 uninstall --local
250
+
251
+ # Detect and uninstall without interaction
252
+ npx @mearl/setup@3 uninstall --yes
253
+
254
+ # Uninstall environments and all CLI plugins and Providers
255
+ npx @mearl/setup@3 uninstall --full
256
+
257
+ # Install an optional browser Provider
258
+ npx @mearl/setup@3 provider install @example/mearl-provider-device
259
+ npx @mearl/setup@3 provider install @example/mearl-provider-cloud
260
+ ```
261
+
262
+ Existing npm/npx installation methods and CLIs for Mearl environment packages remain available; setup is the unified convenience entry.
263
+
264
+ ## Preset installation
265
+
266
+ `mearl-setup preset apply <source-url-or-path>` prepares exact resources, activates local Node selections, and synchronizes browser resources to connected, compatible extensions. `preset update` preserves user overrides, and `preset status` reports local readiness and the status of connected extensions. CLI setup exits `3` when local components still need synchronization or installation. [Local installation contract](../../docs/preset-installation.en.md)
267
+
268
+ Before replacing packages, install/update saves exact resources and expected selections for the selected Preset, then synchronizes hosts and installs Skills after runtime initialization. `extension --full` prepares the same resources before replacing the extension. Existing local selections take priority; explicit `--preset` updates or repairs resources. Unavailable configuration or resources stop package changes; retries after partial upgrades use saved records. Removal never prepares or installs Presets.
269
+
270
+ The default directory is the public site's `installation.json`; override with HTTPS `MEARL_INSTALLATION_CONFIG_URL` (loopback HTTP allowed for local validation). It uses the web installation validation contract and reports pending until both local and browser sides confirm.
271
+
272
+ ## Browser presets and distribution entry points
273
+
274
+ Explicit setup first reuses the local preset. Without one, after runtime initialization and before Skill installation, it adopts the connected browser's trusted preset. Explicit `--preset <source>` wins. Differing browser selections, untrusted sources, or failed state reads require an explicit choice. First connection waits about one minute; closed browsers receive only the base environment and retry guidance. Ordinary connections and background core updates do not install an initial local preset.
275
+
276
+ Distributions can set local-process `MEARL_SETUP_DEFAULT_PRESET` to a default manifest source. It applies only when neither side has a selection. Plugin, Provider, removal, and other subcommands keep their behavior. The public installer still owns installation and status verification.
package/README.md CHANGED
@@ -1,22 +1,25 @@
1
1
  # @mearl/setup
2
2
 
3
- 3.0 当前以 beta 发行。使用 `@beta` 入口安装同版本的 setup、运行包和扩展;正式版使用 `latest` 标签。
3
+ [简体中文](README.md) | [English](README.en.md)
4
+
5
+ 使用 `@3` 选择最新稳定 3.x 安装器,并配套安装同版的运行包和扩展。
4
6
 
5
7
  Mearl 环境的一次性安装、更新、卸载与配置工具。无需先全局安装 setup:
6
8
 
7
- 要求 Node.js >= 20.18.0。安装前检查 `node --version`;3.0 beta 继续显式使用 `@beta`,
8
- 避免 npm 在未指定版本时按旧 Node 的兼容性选到历史安装器。
9
+ 安装前用 `node --version` 确认 Node.js >= 20.18.0。
9
10
 
10
11
  ```bash
11
- npx @mearl/setup@beta [install] [--local] [--cloud] [--cloud-mode <id>] [--mcp]
12
- npx @mearl/setup@beta update [--local] [--cloud] [--cloud-mode <id>] [--mcp]
13
- npx @mearl/setup@beta uninstall [--local] [--cloud] [--cloud-mode <id>] [--mcp] [--full]
14
- npx @mearl/setup@beta extension [--full] [--force]
15
- npx @mearl/setup@beta plugin [name] <path>
16
- npx @mearl/setup@beta plugin install <skills add 参数...>
17
- npx @mearl/setup@beta plugin uninstall <name>
18
- npx @mearl/setup@beta provider <install|update|uninstall> <package|id>
19
- npx @mearl/setup@beta preset repair [--replace-skill <name> ...]
12
+ npx @mearl/setup@3 [install] [--local] [--cloud] [--cloud-mode <id>] [--mcp]
13
+ npx @mearl/setup@3 update [--local] [--cloud] [--cloud-mode <id>] [--mcp]
14
+ npx @mearl/setup@3 uninstall [--local] [--cloud] [--cloud-mode <id>] [--mcp] [--full]
15
+ npx @mearl/setup@3 extension [--full] [--force]
16
+ npx @mearl/setup@3 extension --extract
17
+ npx @mearl/setup@3 plugin [name] <path>
18
+ npx @mearl/setup@3 plugin install <skills add 参数...>
19
+ npx @mearl/setup@3 plugin uninstall <name>
20
+ npx @mearl/setup@3 provider <install|update|uninstall> <package|id>
21
+ npx @mearl/setup@3 profile install <来源> [--browser <标识或名称>]
22
+ npx @mearl/setup@3 preset repair [--replace-skill <name> ...]
20
23
  ```
21
24
 
22
25
  浏览器 Provider 独立安装和演进。安装后运行 `mearl <providerId>` 查看该版本声明的能力、
@@ -24,12 +27,22 @@ npx @mearl/setup@beta preset repair [--replace-skill <name> ...]
24
27
 
25
28
  可选云协议先通过 preset 安装对应能力,再用 `--cloud-mode <id>` 选择。Qoder 配对见 [`@mearl/plugin-qoder`](../plugin-qoder/README.md);setup 不默认包含或安装它。
26
29
 
30
+ ## 安装端模拟
31
+
32
+ ```bash
33
+ npx @mearl/setup@3 profile install mearlhq/wechat-profile
34
+ ```
35
+
36
+ 来源支持公开 GitHub 仓库的 `owner/repo`、`owner/repo@ref`,或能力包清单的完整 HTTPS 地址。GitHub 来源从解析后的提交读取根目录 `bundle.json`。命令安装能力包声明的全部端模拟配置,保留当前 Preset。有多个本地扩展连接时,用 `--browser` 选择目标。若扩展提示授权,请确认来源。
37
+
38
+ 在扩展快捷设置的客户端下拉菜单中选择「添加端模拟」,可以选择官方端模拟或输入来源。列表为空时,点击「客户端」可直接打开安装弹窗。
39
+
27
40
  ## 安装与注册 Skill 插件
28
41
 
29
42
  一个命令安装 skill 并注册它的 CLI、SDK 和 MCP 能力:
30
43
 
31
44
  ```bash
32
- npx @mearl/setup@beta plugin install https://github.com/mearlhq/mearl --skill browser-tab-manager --yes
45
+ npx @mearl/setup@3 plugin install https://github.com/mearlhq/mearl --skill browser-tab-manager --yes
33
46
  mearl tabs summary
34
47
  ```
35
48
 
@@ -42,7 +55,7 @@ Native preset 批次新建的插件注册使用 setup 托管归属,后续通
42
55
  GUI 和 CLI 使用同一套 preset 安装器及覆盖规则。已保存的安装尚未完成时,运行 `preset repair` 从接受的本地资源重试,不重新下载 preset,也不隐式覆盖冲突来源。显式重复指定 `--replace-skill` 即授权本次替换这些 Skill 的来源或同名旧插件注册:
43
56
 
44
57
  ```bash
45
- npx @mearl/setup@beta preset repair --replace-skill example-skill --replace-skill another-skill
58
+ npx @mearl/setup@3 preset repair --replace-skill example-skill --replace-skill another-skill
46
59
  ```
47
60
 
48
61
  已安装的 Skill 仅修复注册;来源冲突项重新安装。未列出的冲突仍会阻止安装,声明外的工具名与变更后的安装身份也会被拒绝。覆盖授权不会保留到后续普通修复或自动更新。缺少浏览器权限时仍需在扩展中授权。输出沿用 preset JSON,已就绪退出 `0`、仍有待完成项退出 `3`、安装失败退出 `1`,无效参数退出 `2`。
@@ -54,8 +67,8 @@ npx @mearl/setup@beta preset repair --replace-skill example-skill --replace-skil
54
67
  已通过其他渠道分发的 skill 或自定义入口,可以直接传入路径注册:
55
68
 
56
69
  ```bash
57
- npx @mearl/setup@beta plugin /absolute/path/to/browser-tab-manager
58
- npx @mearl/setup@beta plugin custom ./skills/custom/entry.mjs
70
+ npx @mearl/setup@3 plugin /absolute/path/to/browser-tab-manager
71
+ npx @mearl/setup@3 plugin custom ./skills/custom/entry.mjs
59
72
  ```
60
73
 
61
74
  path 接受目录或具体文件;目录默认读取 `scripts/main.mjs` 或 `scripts/main.py`,相对路径基于调用工作目录解析。插件名优先使用显式 name,其次读取入口的 `name` metadata,最后回退到 Skill 目录名;常规注册只传 path,显式 name 仅用于覆盖或兼容旧入口。Python 插件要求 `python3 >= 3.9`,可通过 `MEARL_PYTHON` 指定解释器路径;setup 只验证入口可加载,不安装 Python,也不执行 `pip install`。
@@ -63,12 +76,14 @@ path 接受目录或具体文件;目录默认读取 `scripts/main.mjs` 或 `sc
63
76
  两种注册流程都会检查本机 `@mearl/client` 和 `@mearl/native-host`:缺失或低于当前 setup 版本时,
64
77
  复用已有包管理器安装 setup 的对应版本,更新后再次验证;更高版本保持不动。
65
78
  native-host 会幂等初始化 Skills 模式。基础环境补齐或初始化失败时保留已安装的 skill,并停止注册。
66
- 注册后可在任意目录运行 `mearl <name> --help`,也可通过 `@mearl/client` 的 `invokePlugin` 或 MCP 的 `plugin_invoke` 工具调用。已注册插件的日常调用不执行安装或版本检查;只有已选目录中的缺失插件会触发一次按需安装。
79
+ JavaScript 插件可导出 `setup({ runCommand })`,在注册前准备 Skill 自己的依赖。两种安装入口都会等待执行完成,失败时停止注册。首次使用的依赖检查由 Skill 自行处理,见 [client 契约](../client/README.md#skill-插件)。
80
+
81
+ 注册后可在任意目录运行 `mearl <name> --help`,也可通过 `@mearl/client` 的 `invokePlugin` 或 MCP 的 `plugin_invoke` 工具调用。日常调用不更新 Skill 源码;已选目录中的缺失插件会触发一次按需安装。Skill 自身的依赖由安装钩子和首次使用检查准备。
67
82
 
68
83
  不再需要插件时执行:
69
84
 
70
85
  ```bash
71
- npx @mearl/setup@beta plugin uninstall tabs
86
+ npx @mearl/setup@3 plugin uninstall tabs
72
87
  ```
73
88
 
74
89
  若入口位于 `~/.agents/skills/<skill>/`,setup 会先执行
@@ -84,9 +99,9 @@ Provider 由 npm 包分发,但包本身无需暴露 CLI 或安装脚本。setu
84
99
  可创建的浏览器类型名称及其表单参数:
85
100
 
86
101
  ```bash
87
- npx @mearl/setup@beta provider install @example/mearl-provider-device
88
- npx @mearl/setup@beta provider update example-device
89
- npx @mearl/setup@beta provider uninstall example-device
102
+ npx @mearl/setup@3 provider install @example/mearl-provider-device
103
+ npx @mearl/setup@3 provider update example-device
104
+ npx @mearl/setup@3 provider uninstall example-device
90
105
  ```
91
106
 
92
107
  安装参数接受包名、版本或 dist-tag;更新和卸载可使用 providerId 或完整包名。核心 @mearl 包固定使用公共 npm registry;其余 scope 沿用调用方的包管理器配置。安装沿用启动 setup 的包管理器,
@@ -97,8 +112,8 @@ npx @mearl/setup@beta provider uninstall example-device
97
112
  ## 行为
98
113
 
99
114
  - Mearl 自有 npm 包的安装和更新固定使用 `https://registry.npmjs.org/`,与最新版本查询使用同一来源;第三方包沿用当前包管理器的源配置。
100
- - `install` 可以省略;直接执行 `npx @mearl/setup@beta` 等同于 `npx @mearl/setup@beta install`。
101
- 以选项开头时也默认安装,例如 `npx @mearl/setup@beta --cloud --yes`。
115
+ - `install` 可以省略;直接执行 `npx @mearl/setup@3` 等同于 `npx @mearl/setup@3 install`。
116
+ 以选项开头时也默认安装,例如 `npx @mearl/setup@3 --cloud --yes`。
102
117
  - `install` 未指定环境时默认 `--local`。
103
118
  - `-h` / `--help` 显示帮助,`-v` / `--version` 显示 setup 版本。
104
119
  - `update` 和 `uninstall` 未指定环境时,从当前 `PATH` 中的
@@ -144,20 +159,25 @@ npx @mearl/setup@beta provider uninstall example-device
144
159
 
145
160
  ## 安装或更新浏览器扩展
146
161
 
147
- ```bash
148
- npx @mearl/setup@beta extension
149
- ```
162
+ 首次使用时,先运行 `npx @mearl/setup@3`,再加载同版的离线 ZIP。商店提供同版扩展时,也可以直接安装。[安装步骤](../../README.md#安装本机环境)
150
163
 
151
- 首次使用时可一次安装完整的本地浏览器环境:
164
+ 如需由 setup 管理扩展,运行下面的命令。
152
165
 
153
166
  ```bash
154
- npx @mearl/setup@beta extension --full
167
+ npx @mearl/setup@3 extension
155
168
  ```
156
169
 
157
- 首次安装、旧版 setup 扩展首次迁移,或 Native Host reload 不可用时才需要 CDP。如果
170
+ 加上 `--full` 可以在同一次运行中安装本机 CLI、Native Host 和 Skill。
171
+
172
+ 需要手动导入时,运行 `npx @mearl/setup@3 extension --extract`。setup 会下载发行 ZIP,
173
+ 在 `~/.mearl/extension-downloads/` 下创建新目录并解压,然后打印完整路径。打开
174
+ `chrome://extensions/`,启用**开发者模式**,点击**加载已解压的扩展程序**并选择该目录。
175
+ Chrome 使用扩展期间请保留目录。Chrome 关闭或远程调试关闭时也可以下载。
176
+
177
+ 自动安装在首次安装、旧版 setup 扩展首次迁移,或 Native Host reload 不可用时使用 CDP。如果
158
178
  Chrome 尚未开放 CDP,交互命令会提示打开
159
179
  `chrome://inspect/#remote-debugging`、启用远程调试并允许连接,然后等待安装完成。面向人的
160
- 安装指引不要添加 `--yes`。无 TTY 环境使用 `npx @mearl/setup@beta extension --yes`;它只尝试
180
+ 安装指引不要添加 `--yes`。无 TTY 环境使用 `npx @mearl/setup@3 extension --yes`;它只尝试
161
181
  一次,CDP 不可用时立即中断。无法确认 setup 管理来源且 CDP 不可用时不会下载或修改插件
162
182
  目录,打开 CDP 后重新执行同一命令即可。Chrome 版本过低、缺少扩展安装所需的 CDP 方法时,
163
183
  命令会中断并提示升级 Chrome。
@@ -187,7 +207,7 @@ DevTools 面板的设置按钮会显示红点,快捷设置中提供“立即
187
207
 
188
208
  自动更新不会降级较新的 Host,也不会改动 cloud、MCP 或独立安装的 Mearl Skill。
189
209
  目标 npm 版本尚未发布、网络不可用或更新失败时,旧 Host 会继续运行;下一次扩展后台
190
- 生命周期可重新尝试,也可手动执行 `npx @mearl/setup@beta update --local`。
210
+ 生命周期可重新尝试,也可手动执行 `npx @mearl/setup@3 update --local`。
191
211
 
192
212
  ## 人工与 Agent 使用
193
213
 
@@ -202,48 +222,48 @@ DevTools 面板的设置按钮会显示红点,快捷设置中提供“立即
202
222
 
203
223
  ```bash
204
224
  # 默认安装本地浏览器环境
205
- npx @mearl/setup
225
+ npx @mearl/setup@3
206
226
 
207
227
  # 与上一条等价
208
- npx @mearl/setup@beta install
228
+ npx @mearl/setup@3 install
209
229
 
210
230
  # Agent / CI 无交互安装
211
- npx @mearl/setup@beta --yes
231
+ npx @mearl/setup@3 --yes
212
232
 
213
233
  # 安装云端 Agent 环境
214
- npx @mearl/setup@beta --cloud
234
+ npx @mearl/setup@3 --cloud
215
235
 
216
236
  # 同一台机器同时作为本地浏览器宿主和云端 Agent
217
- npx @mearl/setup@beta --local --cloud
237
+ npx @mearl/setup@3 --local --cloud
218
238
 
219
239
  # 自动更新当前 PATH 中检测到的全部 Mearl 环境
220
- npx @mearl/setup@beta update
240
+ npx @mearl/setup@3 update
221
241
 
222
242
  # 独立安装或更新到最新浏览器扩展
223
- npx @mearl/setup@beta extension
243
+ npx @mearl/setup@3 extension
224
244
 
225
245
  # 安装完整本地环境(client、Native Host、Skill 和扩展)
226
- npx @mearl/setup@beta extension --full
246
+ npx @mearl/setup@3 extension --full
227
247
 
228
248
  # 只卸载本地环境
229
- npx @mearl/setup@beta uninstall --local
249
+ npx @mearl/setup@3 uninstall --local
230
250
 
231
251
  # 自动检测并无交互卸载
232
- npx @mearl/setup@beta uninstall --yes
252
+ npx @mearl/setup@3 uninstall --yes
233
253
 
234
254
  # 卸载环境,并清理全部 CLI 插件与 Provider
235
- npx @mearl/setup@beta uninstall --full
255
+ npx @mearl/setup@3 uninstall --full
236
256
 
237
257
  # 安装可选浏览器 Provider
238
- npx @mearl/setup@beta provider install @example/mearl-provider-device
239
- npx @mearl/setup@beta provider install @example/mearl-provider-cloud
258
+ npx @mearl/setup@3 provider install @example/mearl-provider-device
259
+ npx @mearl/setup@3 provider install @example/mearl-provider-cloud
240
260
  ```
241
261
 
242
262
  原有 Mearl 环境包的 npm/npx 安装方式和 CLI 保持可用;setup 是统一的便利入口。
243
263
 
244
264
  ## Preset 安装
245
265
 
246
- `mearl-setup preset apply <source-url-or-path>` 准备确切资源、激活本机 Node 选择,并向兼容扩展同步同一批浏览器资源;`preset update` 保留用户覆盖,`preset status` 只读检查实际就绪状态。退出码 3 表示存在待同步/安装组件。完整扩展、默认设置、Skills/Provider 和网页一键安装使用同一实际状态核验,见 [本机安装合同](../../docs/preset-installation.md)。
266
+ `mearl-setup preset apply <source-url-or-path>` 准备确切资源、激活本机 Node 选择,并向已连接的兼容扩展同步浏览器资源。`preset update` 保留用户覆盖,`preset status` 报告本机就绪情况和已连接扩展的状态。CLI setup 返回退出码 `3` 表示仍有本机组件待同步或安装。 [本机安装合同](../../docs/preset-installation.md)
247
267
 
248
268
  install/update 在替换包前保存选中 Preset 的确切资源和期望选择,运行时初始化后同步宿主并安装 Skills;`extension --full` 在替换扩展前完成同一准备流程。已有本机选择优先保留,显式 `--preset` 用于更新或修复资源。配置或资源不可用时停止包变更;部分升级后的重试使用已保存记录。卸载不准备或安装 Preset。
249
269
 
@@ -74,6 +74,9 @@ export declare function loadUnpackedViaCdp(extensionDirectory: string, chromeUse
74
74
  export declare function inspectMearlExtensionViaCdp(chromeUserDataDirectory?: string): Promise<InstalledBrowserExtension | null>;
75
75
  declare function inspectSetupManagedExtension(targetDirectory: string): Promise<string[]>;
76
76
  declare function reloadInstalledExtension(browserIds: string[]): Promise<void>;
77
+ export declare function downloadMearlBrowserExtension(directory?: string, dependencies?: Pick<BrowserExtensionDependencies, 'extractArchive' | 'fetch'>): Promise<BrowserExtensionRelease & {
78
+ path: string;
79
+ }>;
77
80
  export declare function updateSetupManagedBrowserExtensionFiles(options: SetupManagedBrowserExtensionUpdateOptions, dependencies?: Pick<BrowserExtensionDependencies, 'extractArchive' | 'fetch'>): Promise<SetupManagedBrowserExtensionUpdateResult>;
78
81
  export declare function installMearlBrowserExtension(options?: BrowserExtensionInstallOptions, dependencies?: BrowserExtensionDependencies): Promise<BrowserExtensionInstallResult>;
79
82
  export {};
@@ -430,7 +430,7 @@ async function extractExtensionArchive(archivePath, destination) {
430
430
  }
431
431
  await archive.extractAllToAsync(destination, false);
432
432
  }
433
- async function downloadAndReplaceExtension(releaseInput, targetDirectory, fetchImpl, extractArchive) {
433
+ async function downloadAndReplaceExtension(releaseInput, targetDirectory, fetchImpl, extractArchive, { managed = true } = {}) {
434
434
  const release = validateExtensionRelease(releaseInput);
435
435
  const parentDirectory = path.dirname(targetDirectory);
436
436
  const stagingDirectory = `${targetDirectory}.staging-${randomUUID()}`;
@@ -462,7 +462,8 @@ async function downloadAndReplaceExtension(releaseInput, targetDirectory, fetchI
462
462
  if (readExtensionReleaseVersion(manifest) !== release.version) {
463
463
  throw new Error(`Extension archive version ${String(manifest.version)} does not match ${release.version}`);
464
464
  }
465
- await writeSetupMarker(stagingDirectory, targetDirectory);
465
+ if (managed)
466
+ await writeSetupMarker(stagingDirectory, targetDirectory);
466
467
  await replaceDirectory(stagingDirectory, targetDirectory);
467
468
  return release;
468
469
  }
@@ -471,6 +472,21 @@ async function downloadAndReplaceExtension(releaseInput, targetDirectory, fetchI
471
472
  await fs.promises.rm(stagingDirectory, { force: true, recursive: true });
472
473
  }
473
474
  }
475
+ export async function downloadMearlBrowserExtension(directory = path.join(os.homedir(), '.mearl', 'extension-downloads'), dependencies = {}) {
476
+ const fetchImpl = dependencies.fetch ?? fetch;
477
+ const release = await fetchBrowserExtensionRelease(fetchImpl);
478
+ const parent = path.resolve(directory);
479
+ await fs.promises.mkdir(parent, { recursive: true });
480
+ const target = await fs.promises.mkdtemp(path.join(parent, `mearl-v${release.version}-`));
481
+ try {
482
+ await downloadAndReplaceExtension(release, target, fetchImpl, dependencies.extractArchive ?? extractExtensionArchive, { managed: false });
483
+ return { ...release, path: target };
484
+ }
485
+ catch (error) {
486
+ await fs.promises.rm(target, { force: true, recursive: true });
487
+ throw error;
488
+ }
489
+ }
474
490
  export async function updateSetupManagedBrowserExtensionFiles(options, dependencies = {}) {
475
491
  const targetDirectory = path.resolve(options.targetDirectory);
476
492
  if (!hasSetupMarker(targetDirectory)) {
@@ -4,7 +4,6 @@ import { createNodePresetLoader } from './preset.js';
4
4
  import type { StagedPresetInstallation } from './preset.js';
5
5
  interface Options {
6
6
  env?: NodeJS.ProcessEnv;
7
- waitMs?: number;
8
7
  writeLine?: (line: string) => void;
9
8
  }
10
9
  interface Dependencies {
@@ -12,8 +11,6 @@ interface Dependencies {
12
11
  invoke?: typeof invokeLocalHost;
13
12
  configuration?: () => ReturnType<typeof loadInstallationConfig>;
14
13
  load?: ReturnType<typeof createNodePresetLoader>;
15
- now?: () => number;
16
- sleep?: (ms: number) => Promise<void>;
17
14
  }
18
15
  /** Explicit setup may complete a trusted browser selection before installing any Skills. */
19
16
  export declare function prepareBrowserSetupPreset(options?: Options, dependencies?: Dependencies): Promise<StagedPresetInstallation | undefined>;
@@ -1,6 +1,6 @@
1
1
  import { invokeLocalHost, listLiveBrowsers } from '@mearl/client/local';
2
2
  import { installationOriginCovered, loadInstallationConfig, matchesInstallationOrigin, trustedInstallationPreset } from '@mearl/preset/installation';
3
- import { createNodePresetLoader, PresetInstaller, resolveInstallationConfigUrl } from './preset.js';
3
+ import { connectedPresetExtensions, createNodePresetLoader, PresetInstaller, resolveInstallationConfigUrl } from './preset.js';
4
4
  /** Explicit setup may complete a trusted browser selection before installing any Skills. */
5
5
  export async function prepareBrowserSetupPreset(options = {}, dependencies = {}) {
6
6
  const env = options.env ?? process.env;
@@ -8,27 +8,29 @@ export async function prepareBrowserSetupPreset(options = {}, dependencies = {})
8
8
  const current = await new PresetInstaller({ directory }).store.current();
9
9
  if (current.activation || current.receipt)
10
10
  return;
11
- const now = dependencies.now ?? Date.now;
12
- const deadline = now() + (options.waitMs ?? 0);
13
11
  const browsers = dependencies.browsers ?? listLiveBrowsers;
14
12
  const invoke = dependencies.invoke ?? invokeLocalHost;
15
- const sleep = dependencies.sleep ?? (ms => new Promise(resolve => setTimeout(resolve, ms)));
16
13
  const configuration = dependencies.configuration ?? (() => {
17
14
  const url = resolveInstallationConfigUrl(env.MEARL_INSTALLATION_CONFIG_URL);
18
15
  return loadInstallationConfig(url, url.origin, AbortSignal.timeout(5_000));
19
16
  });
20
- const read = async (timeoutSec = 5) => {
21
- const targets = browsers().filter(browser => !browser.provider && browser.transport === 'extension' && (browser.status === undefined || browser.status === 'connected') && (!env.MEARL_SOCKET_PATH || browser.socketPath === env.MEARL_SOCKET_PATH));
17
+ const connected = () => connectedPresetExtensions(browsers(), env.MEARL_SOCKET_PATH?.trim());
18
+ const read = async () => {
19
+ const targets = connected();
22
20
  if (!targets.length)
23
21
  return;
24
22
  return (await Promise.all(targets.map(async (browser) => {
25
23
  let value;
26
24
  try {
27
- value = await invoke('preset_browser_status', {}, { socketPath: browser.socketPath, timeoutSec });
25
+ value = await invoke('preset_browser_status', {}, { socketPath: browser.socketPath, timeoutSec: 5 });
28
26
  }
29
27
  catch {
28
+ if (!connected().some(target => target.browserId === browser.browserId && target.socketPath === browser.socketPath))
29
+ return [];
30
30
  throw new Error(`Cannot read preset from ${browser.browserId}. Reconnect the extension and retry setup, or use --preset <source>.`);
31
31
  }
32
+ if (value?.error === 'EXTENSION_NOT_CONNECTED')
33
+ return [];
32
34
  if (value?.error || typeof value?.browserReady !== 'boolean' || !Object.hasOwn(value, 'browserRevision') || (value.presetId !== undefined && (typeof value.presetId !== 'string' || !value.presetId || typeof value.presetSource !== 'string' || typeof value.browserRevision !== 'string' || value.browserReady !== true)))
33
35
  throw new Error(`Browser preset is unavailable in ${browser.browserId}. Reconnect the extension and retry setup, or use --preset <source>.`);
34
36
  if (!value.presetId)
@@ -36,21 +38,9 @@ export async function prepareBrowserSetupPreset(options = {}, dependencies = {})
36
38
  return [{ browserId: browser.browserId, presetId: value.presetId, presetSource: value.presetSource, browserRevision: value.browserRevision }];
37
39
  }))).flat();
38
40
  };
39
- let selected = await read();
40
- if (!selected && now() < deadline)
41
- options.writeLine?.('Waiting for the Mearl extension to connect and checking its installed preset…');
42
- while (!selected && now() < deadline) {
43
- await sleep(Math.min(1000, deadline - now()));
44
- if (now() < deadline)
45
- selected = await read(Math.max(0.1, Math.min(5, (deadline - now()) / 1000)));
46
- }
47
- if (!selected) {
48
- options.writeLine?.('No connected Mearl extension was found. Continuing without a browser preset; connect and rerun setup to adopt it, or use --preset <source> to select a preset.');
49
- return;
50
- }
51
- if (!selected.length)
41
+ const accepted = await read();
42
+ if (!accepted?.length)
52
43
  return;
53
- const accepted = selected;
54
44
  const first = accepted[0];
55
45
  if (accepted.some(value => value.presetId !== first.presetId || value.presetSource !== first.presetSource))
56
46
  throw new Error('Connected browsers use different presets. Select one explicitly with --preset <source>.');
@@ -58,7 +48,7 @@ export async function prepareBrowserSetupPreset(options = {}, dependencies = {})
58
48
  if (!trusted)
59
49
  throw new Error('The browser preset is not in the trusted installation catalog. Install it explicitly with --preset <source>.');
60
50
  const load = dependencies.load ?? createNodePresetLoader();
61
- const installer = new PresetInstaller({ directory, load: (source, resourceOptions) => {
51
+ const installer = new PresetInstaller({ directory, connectedExtensionsOnly: true, load: (source, resourceOptions) => {
62
52
  const url = new URL(source);
63
53
  if (url.protocol !== 'https:' || url.username || url.password || !trusted.origins.some(origin => matchesInstallationOrigin(origin, url)))
64
54
  throw new Error('Browser preset resource is outside the trusted installation catalog');
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { installMearlBrowserExtension } from './browserExtension.js';
2
+ import { downloadMearlBrowserExtension, installMearlBrowserExtension } from './browserExtension.js';
3
3
  import { runMearlLifecycle, type MearlLifecycleOperation } from './lifecycle.js';
4
4
  import type { MearlProfile } from './profiles.js';
5
5
  import { ensureOfficialPlugin, installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
@@ -9,10 +9,13 @@ import { prepareSetupPreset } from './setupPreset.js';
9
9
  export { SETUP_VERSION } from './setupVersion.js';
10
10
  interface ParsedArgs {
11
11
  cloudMode?: string;
12
+ extract?: boolean;
12
13
  force?: boolean;
13
14
  full?: boolean;
14
15
  help: boolean;
15
- operation?: MearlLifecycleOperation | 'extension' | 'plugin' | 'provider' | 'preset';
16
+ operation?: MearlLifecycleOperation | 'extension' | 'plugin' | 'provider' | 'preset' | 'profile';
17
+ profileSource?: string;
18
+ profileBrowser?: string;
16
19
  presetCommand?: 'apply' | 'update' | 'status' | 'repair';
17
20
  presetSource?: string;
18
21
  presetReplaceSkills?: string[];
@@ -30,12 +33,14 @@ interface ParsedArgs {
30
33
  yes: boolean;
31
34
  }
32
35
  export interface SetupCliDependencies {
36
+ installProfile?: typeof installProfile;
33
37
  preparePreset?: typeof prepareSetupPreset;
34
38
  runPreset?: typeof runPresetCommand;
35
39
  registerPlugin?: typeof setupPlugin;
36
40
  ensureOfficialPlugin?: typeof ensureOfficialPlugin;
37
41
  installPlugins?: typeof installPlugins;
38
42
  uninstallPlugin?: typeof uninstallPlugin;
43
+ downloadBrowserExtension?: typeof downloadMearlBrowserExtension;
39
44
  installBrowserExtension?: typeof installMearlBrowserExtension;
40
45
  installProvider?: typeof installProviderPackage;
41
46
  runLifecycle?: typeof runMearlLifecycle;
@@ -43,4 +48,5 @@ export interface SetupCliDependencies {
43
48
  updateProvider?: typeof updateProviderPackage;
44
49
  }
45
50
  export declare function parseSetupArgs(argv: string[]): ParsedArgs;
51
+ declare function installProfile(source: string, browser?: string): Promise<unknown>;
46
52
  export declare function main(argv?: string[], interactiveTerminal?: boolean, dependencies?: SetupCliDependencies): Promise<number>;
package/dist/cli.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command, CommanderError } from 'commander';
3
- import { installMearlBrowserExtension, isUnsupportedCdpMethodError } from './browserExtension.js';
3
+ import { invoke, listLiveBrowsers } from '@mearl/client/local';
4
+ import { parseProfileSource } from '@mearl/preset/bundles';
5
+ import { downloadMearlBrowserExtension, installMearlBrowserExtension, isUnsupportedCdpMethodError } from './browserExtension.js';
4
6
  import { runMearlLifecycle } from './lifecycle.js';
5
7
  import { isMearlReleaseLine } from './version.js';
6
8
  import { ensureOfficialPlugin, installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
@@ -25,6 +27,15 @@ function commanderError(error) {
25
27
  return new Error(message);
26
28
  }
27
29
  export function parseSetupArgs(argv) {
30
+ if (argv[0] === 'profile') {
31
+ if (argv.length === 2 && (argv[1] === '--help' || argv[1] === '-h'))
32
+ return { ...baseParsedArgs(), help: true };
33
+ if (argv[1] !== 'install' || !argv[2] || (argv.length !== 3 && !(argv.length === 5 && argv[3] === '--browser' && argv[4]))) {
34
+ throw new Error('profile requires install <GitHub owner/repo[@ref] or HTTPS bundle URL> [--browser <id-or-name>]');
35
+ }
36
+ parseProfileSource(argv[2]);
37
+ return { ...baseParsedArgs(), operation: 'profile', profileSource: argv[2], profileBrowser: argv[4] };
38
+ }
28
39
  if (argv[0] === 'preset') {
29
40
  if (argv[1] === 'repair') {
30
41
  const command = new Command('preset repair')
@@ -163,12 +174,17 @@ export function parseSetupArgs(argv) {
163
174
  .configureOutput({ writeErr: () => { }, writeOut: () => { } })
164
175
  .option('--full')
165
176
  .option('--force')
177
+ .option('--extract')
166
178
  .option('-y, --yes')
167
179
  .option('--non-interactive')
168
180
  .action(() => {
169
181
  const options = extension.opts();
182
+ if (options.extract && (options.full || options.force)) {
183
+ throw new Error('--extract cannot be combined with --full or --force');
184
+ }
170
185
  parsed = {
171
186
  ...baseParsedArgs(),
187
+ ...(options.extract ? { extract: true } : {}),
172
188
  ...(options.force ? { force: true } : {}),
173
189
  ...(options.full ? { full: true } : {}),
174
190
  operation: 'extension',
@@ -206,6 +222,7 @@ Environment commands:
206
222
  uninstall [options] Uninstall detected or selected environments
207
223
 
208
224
  Integration commands:
225
+ profile install <source> Install one App profile bundle in a connected extension
209
226
  preset apply <source> Apply a preset, synchronize hosts and install declared components
210
227
  preset update Update the selected preset, preserving user overrides
211
228
  preset repair Retry the installed preset using its accepted resources
@@ -219,6 +236,7 @@ Integration commands:
219
236
 
220
237
  Browser extension:
221
238
  extension [--full] [--force] Install or update the browser extension
239
+ extension --extract Download and unpack the extension for manual loading
222
240
 
223
241
  Environment options:
224
242
  --local Manage @mearl/native-host and @mearl/client
@@ -251,8 +269,8 @@ Examples:
251
269
  npx @mearl/setup --local --cloud
252
270
  npx @mearl/setup update
253
271
  npx @mearl/setup uninstall --full
254
- npx @mearl/setup plugin uninstall yuque
255
- npx @mearl/setup provider install @example/mearl-provider-brow
272
+ npx @mearl/setup plugin uninstall tabs
273
+ npx @mearl/setup provider install @example/mearl-provider-device
256
274
  npx @mearl/setup preset repair --replace-skill example-skill
257
275
  npx @mearl/setup extension --full`;
258
276
  console.log(output.replaceAll('npx @mearl/setup', SETUP_COMMAND));
@@ -274,9 +292,22 @@ async function runSetupLifecycle(options, dependencies) {
274
292
  const actual = await (dependencies.runPreset ?? runPresetCommand)('status');
275
293
  if (actual.revision !== preset.revision)
276
294
  throw new Error('The selected preset changed while setup was running');
277
- console.log(JSON.stringify(actual, null, 2));
295
+ const warnings = [...new Set([...(preset.warnings ?? []), ...(actual.warnings ?? [])])];
296
+ console.log(JSON.stringify({ ...actual, warnings }, null, 2));
278
297
  return actual.ready ? 0 : 3;
279
298
  }
299
+ async function installProfile(source, browser) {
300
+ if (!browser) {
301
+ const extensions = listLiveBrowsers().filter(target => target.transport === 'extension' && (target.status === undefined || target.status === 'connected'));
302
+ if (extensions.length !== 1)
303
+ throw new Error(extensions.length
304
+ ? 'Multiple extensions are connected. Select one with --browser <id-or-name>.'
305
+ : 'Connect a Mearl extension before installing App profiles.');
306
+ browser = extensions[0].browserId;
307
+ }
308
+ console.error('If the bundle source needs permission, confirm it in the browser.');
309
+ return invoke('profile_install', { source }, { browser, timeoutSec: 150 });
310
+ }
280
311
  export async function main(argv = process.argv.slice(2), interactiveTerminal = process.stdin.isTTY === true && process.stdout.isTTY === true, dependencies = {}) {
281
312
  let parsed;
282
313
  try {
@@ -295,6 +326,17 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
295
326
  console.log(SETUP_VERSION);
296
327
  return 0;
297
328
  }
329
+ if (parsed.operation === 'profile') {
330
+ try {
331
+ const result = await (dependencies.installProfile ?? installProfile)(parsed.profileSource, parsed.profileBrowser);
332
+ console.log(JSON.stringify(result, null, 2));
333
+ return 0;
334
+ }
335
+ catch (error) {
336
+ console.error(error instanceof Error ? error.message : String(error));
337
+ return 1;
338
+ }
339
+ }
298
340
  if (parsed.operation === 'preset') {
299
341
  try {
300
342
  const run = dependencies.runPreset ?? runPresetCommand;
@@ -376,6 +418,18 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
376
418
  }
377
419
  }
378
420
  if (parsed.operation === 'extension') {
421
+ if (parsed.extract) {
422
+ try {
423
+ const result = await (dependencies.downloadBrowserExtension ?? downloadMearlBrowserExtension)();
424
+ console.log(`Mearl v${result.version} is ready to load from ${result.path}`);
425
+ console.log('Open chrome://extensions/, enable Developer mode, click Load unpacked, and select that directory.');
426
+ return 0;
427
+ }
428
+ catch (error) {
429
+ console.error(error instanceof Error ? error.message : String(error));
430
+ return 1;
431
+ }
432
+ }
379
433
  if (!interactiveTerminal && !parsed.yes) {
380
434
  console.error('Non-interactive input detected. Re-run with --yes or --non-interactive.');
381
435
  return 2;
@@ -411,6 +465,7 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
411
465
  : `Browser extension v${result.version} was not installed.`);
412
466
  if (result.cdpError) {
413
467
  console.error(`CDP installation failed: ${result.cdpError}`);
468
+ console.error(`To load the extension manually, run: ${SETUP_COMMAND} extension --extract`);
414
469
  if (isUnsupportedCdpMethodError(result.cdpError)) {
415
470
  console.error(`This Chrome version does not support installing extensions through CDP. Update Chrome to version 145 or later, then rerun: ${SETUP_COMMAND} extension`);
416
471
  extensionFailureIsTerminal = true;
package/dist/lifecycle.js CHANGED
@@ -459,7 +459,7 @@ async function executeLifecycle(options, dependencies, acceptPreset, packageSele
459
459
  }
460
460
  if (packagesOk && selectsInitialPreset && !stagedPreset && !options.presetSource && lifecycleSelected.some(profile => profile === 'local' || profile === 'mcp')) {
461
461
  try {
462
- stagedPreset = await (dependencies.prepareBrowserPreset ?? prepareBrowserSetupPreset)({ env, waitMs: 65_000, writeLine });
462
+ stagedPreset = await (dependencies.prepareBrowserPreset ?? prepareBrowserSetupPreset)({ env, writeLine });
463
463
  if (stagedPreset)
464
464
  await acceptPreset(stagedPreset.revision);
465
465
  }
package/dist/plugin.d.ts CHANGED
@@ -5,7 +5,7 @@ export interface PluginSetupResult {
5
5
  entry: string;
6
6
  name: string;
7
7
  }
8
- export declare function setupPlugin(target: string, requestedName?: string): Promise<PluginSetupResult>;
8
+ export declare function setupPlugin(target: string, requestedName?: string, runner?: CommandRunner): Promise<PluginSetupResult>;
9
9
  export declare function ensureOfficialPlugin(name: string, runner?: CommandRunner): Promise<'existing' | 'installed'>;
10
10
  export interface PluginUninstallResult {
11
11
  entryMissing?: boolean;
package/dist/plugin.js CHANGED
@@ -30,14 +30,15 @@ function defaultPluginName(target, entry) {
30
30
  ? basename(dirname(entryDirectory))
31
31
  : basename(entryDirectory);
32
32
  }
33
- export async function setupPlugin(target, requestedName) {
33
+ export async function setupPlugin(target, requestedName, runner = runCommand) {
34
34
  if (requestedName)
35
35
  assertPluginName(requestedName);
36
36
  const entry = resolvePluginEntry(target);
37
37
  const plugin = await loadPlugin(entry, registrationContext);
38
38
  const name = requestedName || plugin.name || defaultPluginName(target, entry);
39
39
  assertPluginName(name);
40
- await ensurePluginRuntime();
40
+ await ensurePluginRuntime(runner);
41
+ await plugin.setup?.({ runCommand: runner });
41
42
  return {
42
43
  entry: registerPlugin(name, entry, plugin.description, {
43
44
  hasPublicCommands: Boolean(plugin.commandHelp(name)),
@@ -345,6 +346,7 @@ export async function installPlugins(args, runner = runCommand, options = {}) {
345
346
  throw new Error(`多个 skill 使用插件名称 ${name},请调整入口的 name metadata。`);
346
347
  plugins.set(name, {
347
348
  entry,
349
+ setup: plugin.setup,
348
350
  hasPublicCommands: Boolean(plugin.commandHelp(name)),
349
351
  description: plugin.description,
350
352
  ...(plugin.toolbox ? { toolbox: plugin.toolbox } : {}),
@@ -357,14 +359,26 @@ export async function installPlugins(args, runner = runCommand, options = {}) {
357
359
  return 0;
358
360
  }
359
361
  await ensurePluginRuntime(runner);
360
- for (const [name, { entry, description, toolbox, appProfiles, profileSources, hasPublicCommands },] of plugins) {
362
+ for (const [name, { entry, description, toolbox, appProfiles, profileSources, hasPublicCommands, setup },] of plugins) {
361
363
  await options.confirm?.();
362
364
  verifySkill();
363
- const existing = findPluginRegistration(name);
364
- if (options.registrationManagedBy && existing && existing.managedBy !== options.registrationManagedBy) {
365
+ const hasUserRegistration = () => {
366
+ const existing = findPluginRegistration(name);
367
+ return options.registrationManagedBy && existing && existing.managedBy !== options.registrationManagedBy;
368
+ };
369
+ if (hasUserRegistration()) {
365
370
  console.error(`Plugin ${name}: existing user registration kept.`);
366
371
  continue;
367
372
  }
373
+ if (setup) {
374
+ await setup({ runCommand: runner });
375
+ await options.confirm?.();
376
+ verifySkill();
377
+ if (hasUserRegistration()) {
378
+ console.error(`Plugin ${name}: existing user registration kept.`);
379
+ continue;
380
+ }
381
+ }
368
382
  registerPlugin(name, entry, description, {
369
383
  hasPublicCommands,
370
384
  managedBy: options.registrationManagedBy ?? (cli.id === 'skills' ? undefined : cli.id),
package/dist/preset.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { invoke } from '@mearl/client/local';
1
+ import { invoke, listLiveBrowsers } from '@mearl/client/local';
2
2
  import { type JsonObject } from '@mearl/preset';
3
3
  import { type BundleLoader, type PreparedPreset } from '@mearl/preset/bundles';
4
4
  import { PresetInstallationStore } from './presetStore.js';
@@ -60,6 +60,8 @@ export interface PresetInstallerOptions {
60
60
  load?: BundleLoader;
61
61
  hosts?: () => PresetHostTarget[];
62
62
  invoke?: typeof invoke;
63
+ /** CLI setup synchronizes connected extensions without requiring their acknowledgement. */
64
+ connectedExtensionsOnly?: boolean;
63
65
  }
64
66
  export interface PresetApplyOptions {
65
67
  validatePrepared?: (prepared: PreparedPreset) => Promise<void>;
@@ -76,6 +78,7 @@ export interface StagedPresetInstallation {
76
78
  }
77
79
  /** The caller chooses the source; this loader adds no identity or credential discovery. */
78
80
  export declare function createNodePresetLoader(): BundleLoader;
81
+ export declare function connectedPresetExtensions(browsers: ReturnType<typeof listLiveBrowsers>, socketPath?: string): import("@mearl/client/local").LiveBrowser[];
79
82
  /** Installation and live acknowledgements are distinct; browser/external installs remain pending. */
80
83
  export declare class PresetInstaller {
81
84
  readonly store: PresetInstallationStore;
package/dist/preset.js CHANGED
@@ -71,6 +71,10 @@ function localHosts(allowStart = false) {
71
71
  const hosts = configured ? [{ id: 'local', socketPath: configured }] : listLiveBrowsers().filter(browser => !browser.provider).map(browser => ({ id: browser.browserId, socketPath: browser.socketPath }));
72
72
  return hosts.length ? Array.from(new Map(hosts.map(host => [host.socketPath, host])).values()) : [{ id: 'local', ...(allowStart ? {} : { socketPath: SOCKET_PATH }) }];
73
73
  }
74
+ export function connectedPresetExtensions(browsers, socketPath) {
75
+ return browsers.filter(browser => !browser.provider && browser.transport === 'extension' &&
76
+ (browser.status === undefined || browser.status === 'connected') && (!socketPath || browser.socketPath === socketPath));
77
+ }
74
78
  /** Installation and live acknowledgements are distinct; browser/external installs remain pending. */
75
79
  export class PresetInstaller {
76
80
  store;
@@ -99,13 +103,18 @@ export class PresetInstaller {
99
103
  }));
100
104
  }
101
105
  async browserStatus(reload, browserRevision, installationRevision) {
102
- const targets = this.options.hosts?.() ?? localHosts(reload);
106
+ const targets = this.options.connectedExtensionsOnly
107
+ ? connectedPresetExtensions(listLiveBrowsers(), process.env.MEARL_SOCKET_PATH?.trim()).map(browser => ({ id: browser.browserId, socketPath: browser.socketPath }))
108
+ : this.options.hosts?.() ?? localHosts(reload);
103
109
  return Promise.all(targets.map(async (target) => {
104
110
  try {
105
111
  const result = await this.invokeHost(reload ? 'preset_browser_sync' : 'preset_browser_status', reload ? { installationRevision } : {}, target, reload ? 130 : 15, reload);
106
112
  if (result?.accepted === false)
107
113
  return { id: target.id, state: 'rejected', settingsReady: false, error: typeof result.error === 'string' ? result.error : 'Browser preset activation rejected' };
108
- return { id: target.id, state: result?.ready === true && result?.browserRevision === browserRevision && result?.installationRevision === installationRevision ? 'active' : 'pending', settingsReady: result?.settingsReady === true, ...(Array.isArray(result?.permissionPending) && result.permissionPending.length ? { error: 'PRESET_DOMAIN_PERMISSION_REQUIRED: Open extension settings and authorize preset domains' } : {}) };
114
+ const error = Array.isArray(result?.permissionPending) && result.permissionPending.length
115
+ ? 'PRESET_DOMAIN_PERMISSION_REQUIRED: Open extension settings and authorize preset domains'
116
+ : typeof result?.error === 'string' ? result.error : undefined;
117
+ return { id: target.id, state: result?.ready === true && result?.browserRevision === browserRevision && result?.installationRevision === installationRevision ? 'active' : 'pending', settingsReady: result?.settingsReady === true, ...(error ? { error } : {}) };
109
118
  }
110
119
  catch {
111
120
  return { id: target.id, state: 'pending', settingsReady: false, error: 'BROWSER_UNAVAILABLE' };
@@ -150,7 +159,8 @@ export class PresetInstaller {
150
159
  result.settings = mergePresetDefaults(preset.defaults, receipt.overrides);
151
160
  result.nodeRevision = receipt.nodeRevision;
152
161
  result.browserRevision = receipt.browserRevision;
153
- result.browserSyncRequired = receipt.browserSyncRequired === true;
162
+ const browserSyncRequired = receipt.browserSyncRequired === true && !this.options.connectedExtensionsOnly;
163
+ result.browserSyncRequired = browserSyncRequired;
154
164
  result.localComponentCount = Object.values(preset.packages).filter(entry => entry.enabled !== false && entry.targets?.includes('node')).length
155
165
  + [...Object.values(preset.skills), ...Object.values(preset.providers)].filter(entry => entry.enabled !== false).length;
156
166
  result.hosts = await this.hostStatus(false, receipt.nodeRevision, receipt.revision);
@@ -163,7 +173,7 @@ export class PresetInstaller {
163
173
  continue;
164
174
  for (const runtime of entry.targets ?? []) {
165
175
  if (runtime === 'browser') {
166
- if (!browserReady)
176
+ if (browserSyncRequired && !browserReady)
167
177
  result.pending.push({ kind: 'packages', id, runtime, reason: 'BROWSER_SYNC_REQUIRED' });
168
178
  }
169
179
  else if (!receipt.managedModules.includes(id) && Object.keys(current.prepared.bundles.find(bundle => bundle.manifest.id === id)?.manifest.contributions?.nodeRuntimes ?? {}).length)
@@ -189,9 +199,9 @@ export class PresetInstaller {
189
199
  }
190
200
  if (receipt.nodeSyncRequired && !nodeReady && !result.pending.some(entry => entry.reason === 'HOST_SYNC_REQUIRED'))
191
201
  result.pending.push({ kind: 'preset', id: preset.id, runtime: 'node', reason: 'HOST_SYNC_REQUIRED' });
192
- if (receipt.browserSyncRequired && !browserReady && !result.pending.some(entry => entry.reason === 'BROWSER_SYNC_REQUIRED'))
202
+ if (browserSyncRequired && !browserReady && !result.pending.some(entry => entry.reason === 'BROWSER_SYNC_REQUIRED'))
193
203
  result.pending.push({ kind: 'preset', id: preset.id, runtime: 'browser', reason: 'BROWSER_SYNC_REQUIRED' });
194
- if (receipt.browserSyncRequired && browserReady && Object.keys(result.settings ?? {}).length && !result.browsers.every(host => host.settingsReady))
204
+ if (browserSyncRequired && browserReady && Object.keys(result.settings ?? {}).length && !result.browsers.every(host => host.settingsReady))
195
205
  result.pending.push({ kind: 'preset', id: preset.id, runtime: 'browser', reason: 'SETTINGS_APPLY_REQUIRED' });
196
206
  result.ready = result.pending.length === 0;
197
207
  return result;
@@ -207,18 +217,18 @@ export class PresetInstaller {
207
217
  await this.hostStatus(true, previous.activation?.revision ?? '', previous.activation?.installationRevision ?? '');
208
218
  throw new Error(`Node activation rejected; previous selection restored: ${rejection.error}`);
209
219
  }
210
- if (receipt.browserSyncRequired) {
211
- const browsers = await this.browserStatus(true, receipt.browserRevision, receipt.revision);
212
- const rejected = browsers.find(host => host.state === 'rejected');
213
- if (rejected)
214
- throw new Error(`Browser activation rejected; installed selection retained for retry: ${rejected.error}`);
215
- }
220
+ const browsers = receipt.browserSyncRequired ? await this.browserStatus(true, receipt.browserRevision, receipt.revision) : [];
221
+ const rejected = browsers.find(host => host.state === 'rejected');
222
+ if (rejected && !this.options.connectedExtensionsOnly)
223
+ throw new Error(`Browser activation rejected; installed selection retained for retry: ${rejected.error}`);
216
224
  const installed = await this.store.current({ verifyResources: false });
217
225
  if (installed.receipt?.revision !== receipt.revision || !installed.preset)
218
226
  throw new BundleError('BUNDLE_CONFLICT', 'The selected preset changed before component installation');
219
227
  const hasExternal = [...Object.values(installed.preset.skills), ...Object.values(installed.preset.providers)].some(entry => entry.enabled !== false);
220
228
  const components = hasExternal || installed.activation?.bindings['plugins.catalog'] ? await this.componentStatus(true, receipt, replaceSkills) : {};
221
229
  const status = await this.status();
230
+ if (rejected)
231
+ status.warnings.push(`Browser preset activation rejected by ${rejected.id}: ${rejected.error}`);
222
232
  if (components.error)
223
233
  status.warnings.push(`Component installation failed; completed assets retained for retry: ${components.error}`);
224
234
  if (status.revision !== receipt.revision)
@@ -305,7 +315,7 @@ export class PresetInstaller {
305
315
  export async function runPresetCommand(command, source, options = {}) {
306
316
  if (command !== 'repair' && options.replaceSkills !== undefined)
307
317
  throw new Error('Skill replacements require preset repair');
308
- const installer = new PresetInstaller();
318
+ const installer = new PresetInstaller({ connectedExtensionsOnly: true });
309
319
  if (command === 'repair')
310
320
  return installer.repair(options);
311
321
  return command === 'status' ? installer.status() : command === 'update' ? installer.update() : installer.apply(source);
@@ -1,7 +1,7 @@
1
1
  import { PresetInstaller } from './preset.js';
2
2
  /** Preserve the accepted installation and resume its exact resources before Skill operations. */
3
3
  export async function prepareSetupPreset(options, dependencies = {}) {
4
- const installer = dependencies.installer ?? new PresetInstaller({ directory: (options.env ?? process.env).MEARL_CAPABILITY_DIR });
4
+ const installer = dependencies.installer ?? new PresetInstaller({ directory: (options.env ?? process.env).MEARL_CAPABILITY_DIR, connectedExtensionsOnly: true });
5
5
  if (options.source)
6
6
  return installer.stage(options.source);
7
7
  const current = await installer.store.current();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/setup",
3
- "version": "3.0.0-beta.9",
3
+ "version": "3.0.0",
4
4
  "description": "One-shot environment setup and configuration for Mearl",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -55,6 +55,7 @@
55
55
  "mearl-setup": "./dist/bin.js"
56
56
  },
57
57
  "files": [
58
+ "README.en.md",
58
59
  "dist",
59
60
  "!dist/**/*.map",
60
61
  "README.md"
@@ -78,11 +79,11 @@
78
79
  "ws": "^8.18.3",
79
80
  "semver": "^7.8.5",
80
81
  "adm-zip": "^0.6.1",
81
- "@mearl/client": "3.0.0-beta.9",
82
- "@mearl/provider": "3.0.0-beta.9",
83
- "@mearl/daemon-core": "3.0.0-beta.9",
84
- "@mearl/preset": "3.0.0-beta.9",
85
- "@mearl/browser-core": "3.0.0-beta.9"
82
+ "@mearl/client": "3.0.0",
83
+ "@mearl/provider": "3.0.0",
84
+ "@mearl/daemon-core": "3.0.0",
85
+ "@mearl/preset": "3.0.0",
86
+ "@mearl/browser-core": "3.0.0"
86
87
  },
87
88
  "devDependencies": {
88
89
  "@types/node": "^24.9.1",