@mearl/setup 3.0.0-beta.9 → 3.0.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.en.md +276 -0
- package/README.md +66 -46
- package/dist/browserExtension.d.ts +3 -0
- package/dist/browserExtension.js +18 -2
- package/dist/browserPreset.d.ts +0 -3
- package/dist/browserPreset.js +12 -22
- package/dist/cli.d.ts +8 -2
- package/dist/cli.js +59 -4
- package/dist/lifecycle.js +1 -1
- package/dist/plugin.d.ts +1 -1
- package/dist/plugin.js +19 -5
- package/dist/preset.d.ts +4 -1
- package/dist/preset.js +23 -13
- package/dist/setupPreset.js +1 -1
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +60 -14
- package/package.json +7 -6
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 [install] [--local] [--cloud] [--cloud-mode <id>] [--mcp]
|
|
13
|
+
npx @mearl/setup update [--local] [--cloud] [--cloud-mode <id>] [--mcp]
|
|
14
|
+
npx @mearl/setup uninstall [--local] [--cloud] [--cloud-mode <id>] [--mcp] [--full]
|
|
15
|
+
npx @mearl/setup extension [--full] [--force]
|
|
16
|
+
npx @mearl/setup extension --extract
|
|
17
|
+
npx @mearl/setup plugin [name] <path>
|
|
18
|
+
npx @mearl/setup plugin install <skills add arguments...>
|
|
19
|
+
npx @mearl/setup plugin uninstall <name>
|
|
20
|
+
npx @mearl/setup provider <install|update|uninstall> <package|id>
|
|
21
|
+
npx @mearl/setup profile install <source> [--browser <id-or-name>]
|
|
22
|
+
npx @mearl/setup 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 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 plugin install https://github.com/mearlhq/mearl --skill browser-tab-manager --yes
|
|
46
|
+
mearl tabs summary
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Installation arguments pass to the public Skills CLI using the global shared directory. By default, `npx` resolves `skills` for the current Node.js version. 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 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 plugin /absolute/path/to/browser-tab-manager
|
|
71
|
+
npx @mearl/setup 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 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 provider install @example/mearl-provider-device
|
|
103
|
+
npx @mearl/setup provider update example-device
|
|
104
|
+
npx @mearl/setup 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` equals `npx @mearl/setup install`.
|
|
116
|
+
An initial option also implies installation, such as `npx @mearl/setup --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` 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 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 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 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 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
|
|
226
|
+
|
|
227
|
+
# Equivalent to the previous command
|
|
228
|
+
npx @mearl/setup install
|
|
229
|
+
|
|
230
|
+
# Noninteractive Agent / CI installation
|
|
231
|
+
npx @mearl/setup --yes
|
|
232
|
+
|
|
233
|
+
# Install the cloud Agent environment
|
|
234
|
+
npx @mearl/setup --cloud
|
|
235
|
+
|
|
236
|
+
# Use one machine as both a local browser host and cloud Agent
|
|
237
|
+
npx @mearl/setup --local --cloud
|
|
238
|
+
|
|
239
|
+
# Update all detected Mearl environments in the current PATH
|
|
240
|
+
npx @mearl/setup update
|
|
241
|
+
|
|
242
|
+
# Independently install or update the latest browser extension
|
|
243
|
+
npx @mearl/setup extension
|
|
244
|
+
|
|
245
|
+
# Complete local environment: client, Native Host, Skill, and extension
|
|
246
|
+
npx @mearl/setup extension --full
|
|
247
|
+
|
|
248
|
+
# Remove only the local environment
|
|
249
|
+
npx @mearl/setup uninstall --local
|
|
250
|
+
|
|
251
|
+
# Detect and uninstall without interaction
|
|
252
|
+
npx @mearl/setup uninstall --yes
|
|
253
|
+
|
|
254
|
+
# Uninstall environments and all CLI plugins and Providers
|
|
255
|
+
npx @mearl/setup uninstall --full
|
|
256
|
+
|
|
257
|
+
# Install an optional browser Provider
|
|
258
|
+
npx @mearl/setup provider install @example/mearl-provider-device
|
|
259
|
+
npx @mearl/setup 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. Setup performs installation and verifies its status.
|
package/README.md
CHANGED
|
@@ -1,22 +1,25 @@
|
|
|
1
1
|
# @mearl/setup
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[简体中文](README.md) | [English](README.en.md)
|
|
4
|
+
|
|
5
|
+
使用 `@3` 选择最新稳定 3.x 安装器,并配套安装同版的运行包和扩展。
|
|
4
6
|
|
|
5
7
|
Mearl 环境的一次性安装、更新、卸载与配置工具。无需先全局安装 setup:
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
避免 npm 在未指定版本时按旧 Node 的兼容性选到历史安装器。
|
|
9
|
+
安装前用 `node --version` 确认 Node.js >= 20.18.0。
|
|
9
10
|
|
|
10
11
|
```bash
|
|
11
|
-
npx @mearl/setup
|
|
12
|
-
npx @mearl/setup
|
|
13
|
-
npx @mearl/setup
|
|
14
|
-
npx @mearl/setup
|
|
15
|
-
npx @mearl/setup
|
|
16
|
-
npx @mearl/setup
|
|
17
|
-
npx @mearl/setup
|
|
18
|
-
npx @mearl/setup
|
|
19
|
-
npx @mearl/setup
|
|
12
|
+
npx @mearl/setup [install] [--local] [--cloud] [--cloud-mode <id>] [--mcp]
|
|
13
|
+
npx @mearl/setup update [--local] [--cloud] [--cloud-mode <id>] [--mcp]
|
|
14
|
+
npx @mearl/setup uninstall [--local] [--cloud] [--cloud-mode <id>] [--mcp] [--full]
|
|
15
|
+
npx @mearl/setup extension [--full] [--force]
|
|
16
|
+
npx @mearl/setup extension --extract
|
|
17
|
+
npx @mearl/setup plugin [name] <path>
|
|
18
|
+
npx @mearl/setup plugin install <skills add 参数...>
|
|
19
|
+
npx @mearl/setup plugin uninstall <name>
|
|
20
|
+
npx @mearl/setup provider <install|update|uninstall> <package|id>
|
|
21
|
+
npx @mearl/setup profile install <来源> [--browser <标识或名称>]
|
|
22
|
+
npx @mearl/setup preset repair [--replace-skill <name> ...]
|
|
20
23
|
```
|
|
21
24
|
|
|
22
25
|
浏览器 Provider 独立安装和演进。安装后运行 `mearl <providerId>` 查看该版本声明的能力、
|
|
@@ -24,16 +27,26 @@ 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 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
|
|
45
|
+
npx @mearl/setup plugin install https://github.com/mearlhq/mearl --skill browser-tab-manager --yes
|
|
33
46
|
mearl tabs summary
|
|
34
47
|
```
|
|
35
48
|
|
|
36
|
-
|
|
49
|
+
安装参数传给公共 Skills CLI,使用全局共享目录。默认由 `npx` 按当前 Node.js 版本解析 `skills`。公共 CLI 的默认 symlink 和复制模式都可注册;复制模式通过 list --global --json 确认本次成功结果对应的 canonical path,不猜测目录,也不扫描其他 Skills。自定义 CLI 可通过 MEARL_SKILLS_PATH 指定。参数不接受截断后置选项的 -- 分隔符。
|
|
37
50
|
|
|
38
51
|
安装宿主提供的本地插件目录决定哪些能力由 preset 管理。install/update 维护目录中的托管注册,保留用户自行注册的插件。卸载最后一个依赖环境时移除托管插件与 Mearl Skill;其他环境需要时保留。配置准备、宿主同步和实际安装状态共用 [安装合同](../../docs/preset-installation.md)。
|
|
39
52
|
|
|
@@ -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
|
|
58
|
+
npx @mearl/setup 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
|
|
58
|
-
npx @mearl/setup
|
|
70
|
+
npx @mearl/setup plugin /absolute/path/to/browser-tab-manager
|
|
71
|
+
npx @mearl/setup 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
|
-
|
|
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
|
|
86
|
+
npx @mearl/setup 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
|
|
88
|
-
npx @mearl/setup
|
|
89
|
-
npx @mearl/setup
|
|
102
|
+
npx @mearl/setup provider install @example/mearl-provider-device
|
|
103
|
+
npx @mearl/setup provider update example-device
|
|
104
|
+
npx @mearl/setup 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
|
|
101
|
-
以选项开头时也默认安装,例如 `npx @mearl/setup
|
|
115
|
+
- `install` 可以省略;直接执行 `npx @mearl/setup` 等同于 `npx @mearl/setup install`。
|
|
116
|
+
以选项开头时也默认安装,例如 `npx @mearl/setup --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
|
-
|
|
148
|
-
npx @mearl/setup@beta extension
|
|
149
|
-
```
|
|
162
|
+
首次使用时,先运行 `npx @mearl/setup`,再加载同版的离线 ZIP。商店提供同版扩展时,也可以直接安装。[安装步骤](../../README.md#安装本机环境)
|
|
150
163
|
|
|
151
|
-
|
|
164
|
+
如需由 setup 管理扩展,运行下面的命令。
|
|
152
165
|
|
|
153
166
|
```bash
|
|
154
|
-
npx @mearl/setup
|
|
167
|
+
npx @mearl/setup extension
|
|
155
168
|
```
|
|
156
169
|
|
|
157
|
-
|
|
170
|
+
加上 `--full` 可以在同一次运行中安装本机 CLI、Native Host 和 Skill。
|
|
171
|
+
|
|
172
|
+
需要手动导入时,运行 `npx @mearl/setup 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
|
|
180
|
+
安装指引不要添加 `--yes`。无 TTY 环境使用 `npx @mearl/setup 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
|
|
210
|
+
生命周期可重新尝试,也可手动执行 `npx @mearl/setup update --local`。
|
|
191
211
|
|
|
192
212
|
## 人工与 Agent 使用
|
|
193
213
|
|
|
@@ -205,45 +225,45 @@ DevTools 面板的设置按钮会显示红点,快捷设置中提供“立即
|
|
|
205
225
|
npx @mearl/setup
|
|
206
226
|
|
|
207
227
|
# 与上一条等价
|
|
208
|
-
npx @mearl/setup
|
|
228
|
+
npx @mearl/setup install
|
|
209
229
|
|
|
210
230
|
# Agent / CI 无交互安装
|
|
211
|
-
npx @mearl/setup
|
|
231
|
+
npx @mearl/setup --yes
|
|
212
232
|
|
|
213
233
|
# 安装云端 Agent 环境
|
|
214
|
-
npx @mearl/setup
|
|
234
|
+
npx @mearl/setup --cloud
|
|
215
235
|
|
|
216
236
|
# 同一台机器同时作为本地浏览器宿主和云端 Agent
|
|
217
|
-
npx @mearl/setup
|
|
237
|
+
npx @mearl/setup --local --cloud
|
|
218
238
|
|
|
219
239
|
# 自动更新当前 PATH 中检测到的全部 Mearl 环境
|
|
220
|
-
npx @mearl/setup
|
|
240
|
+
npx @mearl/setup update
|
|
221
241
|
|
|
222
242
|
# 独立安装或更新到最新浏览器扩展
|
|
223
|
-
npx @mearl/setup
|
|
243
|
+
npx @mearl/setup extension
|
|
224
244
|
|
|
225
245
|
# 安装完整本地环境(client、Native Host、Skill 和扩展)
|
|
226
|
-
npx @mearl/setup
|
|
246
|
+
npx @mearl/setup extension --full
|
|
227
247
|
|
|
228
248
|
# 只卸载本地环境
|
|
229
|
-
npx @mearl/setup
|
|
249
|
+
npx @mearl/setup uninstall --local
|
|
230
250
|
|
|
231
251
|
# 自动检测并无交互卸载
|
|
232
|
-
npx @mearl/setup
|
|
252
|
+
npx @mearl/setup uninstall --yes
|
|
233
253
|
|
|
234
254
|
# 卸载环境,并清理全部 CLI 插件与 Provider
|
|
235
|
-
npx @mearl/setup
|
|
255
|
+
npx @mearl/setup uninstall --full
|
|
236
256
|
|
|
237
257
|
# 安装可选浏览器 Provider
|
|
238
|
-
npx @mearl/setup
|
|
239
|
-
npx @mearl/setup
|
|
258
|
+
npx @mearl/setup provider install @example/mearl-provider-device
|
|
259
|
+
npx @mearl/setup 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
|
|
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
|
|
|
@@ -253,4 +273,4 @@ install/update 在替换包前保存选中 Preset 的确切资源和期望选择
|
|
|
253
273
|
|
|
254
274
|
主动执行 setup 时,先沿用本机已选 preset;本机未选择时,在运行时初始化后、安装 Skill 前采用已连接浏览器的受信 preset。显式 `--preset <source>` 优先。多个浏览器的选择不同、来源不受信或状态读取失败时要求显式选择。首次连接最多等待约一分钟;浏览器关闭时只安装基础环境并说明重试方式。普通连接和后台核心更新不会自动安装首次本机 preset。
|
|
255
275
|
|
|
256
|
-
发行包可设置本机进程环境变量 `MEARL_SETUP_DEFAULT_PRESET` 提供默认清单来源;默认值仅在两端均无选择时生效,插件、Provider
|
|
276
|
+
发行包可设置本机进程环境变量 `MEARL_SETUP_DEFAULT_PRESET` 提供默认清单来源;默认值仅在两端均无选择时生效,插件、Provider、卸载等子命令保持原行为。安装与状态核验仍由安装器负责。
|
|
@@ -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 {};
|
package/dist/browserExtension.js
CHANGED
|
@@ -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
|
-
|
|
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)) {
|
package/dist/browserPreset.d.ts
CHANGED
|
@@ -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>;
|
package/dist/browserPreset.js
CHANGED
|
@@ -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
|
|
21
|
-
|
|
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
|
-
|
|
40
|
-
if (!
|
|
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 {
|
|
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
|
|
255
|
-
npx @mearl/setup provider install @example/mearl-provider-
|
|
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
|
-
|
|
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,
|
|
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
|
|
364
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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);
|
package/dist/setupPreset.js
CHANGED
|
@@ -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/dist/skills.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { inspectGlobalSkill } from './skillSources.js';
|
|
|
2
2
|
import type { PluginCatalogSelection } from '@mearl/client/plugin';
|
|
3
3
|
import type { ResolvedComponent } from '@mearl/preset';
|
|
4
4
|
import { type CommandRunner, type CommandResult, type RunCommandOptions } from '@mearl/daemon-core';
|
|
5
|
-
export declare const SKILLS_CLI_PACKAGE = "skills
|
|
5
|
+
export declare const SKILLS_CLI_PACKAGE = "skills";
|
|
6
6
|
export declare const MEARL_SKILL_SOURCE: string;
|
|
7
7
|
export interface ResolveSkillsOptions {
|
|
8
8
|
env?: NodeJS.ProcessEnv;
|
package/dist/skills.js
CHANGED
|
@@ -5,7 +5,7 @@ import { NodeCapabilityRegistry } from '@mearl/daemon-core/capabilities';
|
|
|
5
5
|
import { inspectGlobalSkill, readTrackedSkillSources } from './skillSources.js';
|
|
6
6
|
import { PresetInstallationStore } from './presetStore.js';
|
|
7
7
|
import { findExecutable, isExecutable, npmExecEnvironment, prependCommandPath, prependNodePath, runCommand, } from '@mearl/daemon-core';
|
|
8
|
-
export const SKILLS_CLI_PACKAGE = 'skills
|
|
8
|
+
export const SKILLS_CLI_PACKAGE = 'skills';
|
|
9
9
|
export const MEARL_SKILL_SOURCE = SETUP_VERSION.includes('-beta.')
|
|
10
10
|
? `https://github.com/mearlhq/mearl/tree/v${SETUP_VERSION}`
|
|
11
11
|
: 'https://github.com/mearlhq/mearl';
|
|
@@ -40,7 +40,7 @@ export function resolveSkillsInvocation(options = {}) {
|
|
|
40
40
|
return null;
|
|
41
41
|
return {
|
|
42
42
|
command: npx,
|
|
43
|
-
argsPrefix: ['-y', SKILLS_CLI_PACKAGE],
|
|
43
|
+
argsPrefix: ['-y', `--package=${SKILLS_CLI_PACKAGE}`, '--', 'skills'],
|
|
44
44
|
env: npmExecEnvironment(prependNodePath(prependCommandPath(npx, env), processExecPath)),
|
|
45
45
|
};
|
|
46
46
|
}
|
|
@@ -49,37 +49,81 @@ export async function captureSkillsCli(options = {}) {
|
|
|
49
49
|
const env = options.env ?? process.env;
|
|
50
50
|
const runner = options.runCommand ?? runCommand;
|
|
51
51
|
const directory = env.MEARL_CAPABILITY_DIR || path.join(homedir(), '.mearl', 'capabilities');
|
|
52
|
-
const current = await new PresetInstallationStore({ directory }).current({
|
|
52
|
+
const current = await new PresetInstallationStore({ directory }).current({
|
|
53
|
+
verifyResources: false,
|
|
54
|
+
});
|
|
53
55
|
const registry = new NodeCapabilityRegistry({ env: Object.freeze({ ...env }), runCommand: runner }, directory, { hooks: ['skills.manager'] });
|
|
54
56
|
await registry.refresh();
|
|
55
|
-
if (current.activation?.revision !== registry.revision ||
|
|
57
|
+
if (current.activation?.revision !== registry.revision ||
|
|
58
|
+
current.activation?.installationRevision !== registry.installationRevision)
|
|
56
59
|
throw new Error('SKILL_MANAGER_CHANGED');
|
|
57
60
|
const readSources = (scope, cwd) => readTrackedSkillSources(scope, cwd, { env });
|
|
58
|
-
const selection = current.rawHash
|
|
61
|
+
const selection = current.rawHash
|
|
62
|
+
? { path: path.resolve(directory, 'active-node.json'), sha256: current.rawHash }
|
|
63
|
+
: undefined;
|
|
59
64
|
const managedSkills = Object.fromEntries(Object.entries(current.preset?.skills ?? {}).filter(([, entry]) => entry.enabled !== false));
|
|
60
65
|
const backend = registry.get('skills.manager')?.();
|
|
61
66
|
if (backend !== undefined) {
|
|
62
67
|
if (backend?.readSources !== undefined && typeof backend.readSources !== 'function')
|
|
63
68
|
throw new Error('Invalid Skill source reader');
|
|
64
|
-
if (!backend ||
|
|
69
|
+
if (!backend ||
|
|
70
|
+
typeof backend.id !== 'string' ||
|
|
71
|
+
!/^[a-z][a-z0-9-]{0,63}$/.test(backend.id) ||
|
|
72
|
+
typeof backend.cli !== 'function')
|
|
65
73
|
throw new Error('The selected Skill manager requires a preset update for CLI operations');
|
|
66
|
-
return {
|
|
74
|
+
return {
|
|
75
|
+
id: backend.id,
|
|
76
|
+
managedSkills,
|
|
77
|
+
selection,
|
|
78
|
+
inspectGlobal: (source, name, version, inspection) => inspectGlobalSkill(source, name, version, {
|
|
79
|
+
migrateFrom: inspection?.migrateFrom,
|
|
80
|
+
externalManager: true,
|
|
81
|
+
normalizeSource: backend.normalizeSource,
|
|
82
|
+
readSources: backend.readSources ?? readSources,
|
|
83
|
+
}),
|
|
84
|
+
run: async (args, settings) => {
|
|
67
85
|
const result = await backend.cli([...args], settings);
|
|
68
|
-
if (!result ||
|
|
86
|
+
if (!result ||
|
|
87
|
+
!Number.isInteger(result.code) ||
|
|
88
|
+
typeof result.stdout !== 'string' ||
|
|
89
|
+
typeof result.stderr !== 'string')
|
|
69
90
|
throw new Error('Invalid Skill manager CLI result');
|
|
70
91
|
return result;
|
|
71
|
-
}
|
|
92
|
+
},
|
|
93
|
+
};
|
|
72
94
|
}
|
|
73
95
|
const invocation = resolveSkillsInvocation(options);
|
|
74
96
|
if (!invocation)
|
|
75
97
|
return undefined;
|
|
76
|
-
return {
|
|
98
|
+
return {
|
|
99
|
+
id: 'skills',
|
|
100
|
+
managedSkills,
|
|
101
|
+
selection,
|
|
102
|
+
inspectGlobal: (source, name, version, inspection) => inspectGlobalSkill(source, name, version, {
|
|
103
|
+
migrateFrom: inspection?.migrateFrom,
|
|
104
|
+
readSources,
|
|
105
|
+
}),
|
|
106
|
+
run: (args, settings) => runner(invocation.command, [...invocation.argsPrefix, ...args], {
|
|
107
|
+
...settings,
|
|
108
|
+
env: invocation.env,
|
|
109
|
+
}),
|
|
110
|
+
};
|
|
77
111
|
}
|
|
78
112
|
export function parseInstalledSkills(output) {
|
|
79
113
|
const value = JSON.parse(stripAnsi(output).trim());
|
|
80
|
-
if (!Array.isArray(value)
|
|
114
|
+
if (!Array.isArray(value))
|
|
81
115
|
throw new Error('Skills returned an invalid skill list');
|
|
82
|
-
|
|
116
|
+
const installed = [];
|
|
117
|
+
for (const item of value) {
|
|
118
|
+
if (!item || typeof item.name !== 'string' || !['global', 'project'].includes(item.scope))
|
|
119
|
+
throw new Error('Skills returned an invalid skill list');
|
|
120
|
+
if (item.status === 'missing' && item.path === null)
|
|
121
|
+
continue;
|
|
122
|
+
if (typeof item.path !== 'string')
|
|
123
|
+
throw new Error('Skills returned an invalid skill list');
|
|
124
|
+
installed.push(item);
|
|
125
|
+
}
|
|
126
|
+
return installed;
|
|
83
127
|
}
|
|
84
128
|
export function parseGlobalMearlSkillPaths(output) {
|
|
85
129
|
const parsed = parseInstalledSkills(output);
|
|
@@ -201,12 +245,14 @@ export async function runGlobalMearlSkillOperation(operation, options = {}) {
|
|
|
201
245
|
${result.stderr}`).trim();
|
|
202
246
|
if (detail)
|
|
203
247
|
writeLine(detail);
|
|
204
|
-
if (/Failed to (?:update|remove)\s+[1-9]\d*\s+skills?/i.test(detail) ||
|
|
248
|
+
if (/Failed to (?:update|remove)\s+[1-9]\d*\s+skills?/i.test(detail) ||
|
|
249
|
+
(operation === 'update' && !/\bInstalled [1-9]\d* skills?\b/.test(detail))) {
|
|
205
250
|
writeLine(`Skill: ${operation} failed.`);
|
|
206
251
|
return false;
|
|
207
252
|
}
|
|
208
253
|
const remainingPaths = await listMearlPaths();
|
|
209
|
-
if (!remainingPaths ||
|
|
254
|
+
if (!remainingPaths ||
|
|
255
|
+
(operation === 'uninstall' ? remainingPaths.length !== 0 : remainingPaths.length === 0)) {
|
|
210
256
|
writeLine(`Skill: ${operation} did not reach the expected installation state.`);
|
|
211
257
|
return false;
|
|
212
258
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/setup",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.1",
|
|
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.
|
|
82
|
-
"@mearl/provider": "3.0.
|
|
83
|
-
"@mearl/daemon-core": "3.0.
|
|
84
|
-
"@mearl/preset": "3.0.
|
|
85
|
-
"@mearl/browser-core": "3.0.
|
|
82
|
+
"@mearl/client": "3.0.1",
|
|
83
|
+
"@mearl/provider": "3.0.1",
|
|
84
|
+
"@mearl/daemon-core": "3.0.1",
|
|
85
|
+
"@mearl/preset": "3.0.1",
|
|
86
|
+
"@mearl/browser-core": "3.0.1"
|
|
86
87
|
},
|
|
87
88
|
"devDependencies": {
|
|
88
89
|
"@types/node": "^24.9.1",
|