@mearl/setup 3.0.0-beta.3 → 3.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,20 +5,20 @@
5
5
  Mearl 环境的一次性安装、更新、卸载与配置工具。无需先全局安装 setup:
6
6
 
7
7
  ```bash
8
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta [install] [--local] [--cloud|--qoder-cloud] [--mcp]
9
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta update [--local] [--cloud|--qoder-cloud] [--mcp]
10
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta uninstall [--local] [--cloud|--qoder-cloud] [--mcp] [--full]
11
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta extension [--full] [--force]
12
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta plugin [name] <path>
13
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta plugin install <skills add 参数...>
14
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta plugin uninstall <name>
15
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider <install|update|uninstall> <package|id>
8
+ npx @mearl/setup@beta [install] [--local] [--cloud|--qoder-cloud] [--mcp]
9
+ npx @mearl/setup@beta update [--local] [--cloud|--qoder-cloud] [--mcp]
10
+ npx @mearl/setup@beta uninstall [--local] [--cloud|--qoder-cloud] [--mcp] [--full]
11
+ npx @mearl/setup@beta extension [--full] [--force]
12
+ npx @mearl/setup@beta plugin [name] <path>
13
+ npx @mearl/setup@beta plugin install <skills add 参数...>
14
+ npx @mearl/setup@beta plugin uninstall <name>
15
+ npx @mearl/setup@beta provider <install|update|uninstall> <package|id>
16
16
  ```
17
17
 
18
18
  浏览器 Provider 独立安装和演进。安装后运行 `mearl <providerId>` 查看该版本声明的能力、
19
19
  配置和启动参数。
20
20
 
21
- 在 Qoder Cloud Agent 环境中,使用 `npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta --qoder-cloud --yes` 选择 Qoder
21
+ 在 Qoder Cloud Agent 环境中,使用 `npx @mearl/setup@beta --qoder-cloud --yes` 选择 Qoder
22
22
  配对模式。它与 `--cloud` 安装同一套 cloud-server、client 和 Skill,只把 cloud-server
23
23
  模式记录为 Qoder;`mearl-cloud-server` 的命令交互保持不变,`start` 和 `restart` 输出新的
24
24
  配对指令后退出,不监听端口或保留守护进程。配对 URL 不包含 Qoder PAT。
@@ -28,7 +28,7 @@ npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider <ins
28
28
  一个命令安装 skill 并注册它的 CLI、SDK 和 MCP 能力:
29
29
 
30
30
  ```bash
31
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta plugin install https://github.com/F-loat/mearl/tree/v3.0.0-beta.1 --skill browser-tab-manager --yes
31
+ npx @mearl/setup@beta plugin install https://github.com/F-loat/mearl/tree/v3.0.0-beta.1 --skill browser-tab-manager --yes
32
32
  mearl tabs summary
33
33
  ```
34
34
 
@@ -45,8 +45,8 @@ Native preset 批次新建的插件注册使用 setup 托管归属,后续通
45
45
  已通过其他渠道分发的 skill 或自定义入口,可以直接传入路径注册:
46
46
 
47
47
  ```bash
48
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta plugin /absolute/path/to/browser-tab-manager
49
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta plugin custom ./skills/custom/entry.mjs
48
+ npx @mearl/setup@beta plugin /absolute/path/to/browser-tab-manager
49
+ npx @mearl/setup@beta plugin custom ./skills/custom/entry.mjs
50
50
  ```
51
51
 
52
52
  path 接受目录或具体文件;目录默认读取 `scripts/main.mjs` 或 `scripts/main.py`,相对路径基于调用工作目录解析。插件名优先使用显式 name,其次读取入口的 `name` metadata,最后回退到 Skill 目录名;常规注册只传 path,显式 name 仅用于覆盖或兼容旧入口。Python 插件要求 `python3 >= 3.9`,可通过 `MEARL_PYTHON` 指定解释器路径;setup 只验证入口可加载,不安装 Python,也不执行 `pip install`。
@@ -59,7 +59,7 @@ native-host 会幂等初始化 Skills 模式。基础环境补齐或初始化失
59
59
  不再需要插件时执行:
60
60
 
61
61
  ```bash
62
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta plugin uninstall tabs
62
+ npx @mearl/setup@beta plugin uninstall tabs
63
63
  ```
64
64
 
65
65
  若入口位于 `~/.agents/skills/<skill>/`,setup 会先执行
@@ -75,9 +75,9 @@ Provider 由 npm 包分发,但包本身无需暴露 CLI 或安装脚本。setu
75
75
  可创建的浏览器类型名称及其表单参数:
76
76
 
77
77
  ```bash
78
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider install @example/mearl-provider-device
79
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider update example-device
80
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider uninstall example-device
78
+ npx @mearl/setup@beta provider install @example/mearl-provider-device
79
+ npx @mearl/setup@beta provider update example-device
80
+ npx @mearl/setup@beta provider uninstall example-device
81
81
  ```
82
82
 
83
83
  安装参数接受包名、版本或 dist-tag;更新和卸载可使用 providerId 或完整包名。核心 @mearl 包固定使用公共 npm registry;其余 scope 沿用调用方的包管理器配置,组织 registry 由能力安装引导提供。安装沿用启动 setup 的包管理器,
@@ -88,8 +88,8 @@ npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider unin
88
88
  ## 行为
89
89
 
90
90
  - Mearl 自有 npm 包的安装和更新固定使用 `https://registry.npmjs.org/`,与最新版本查询使用同一来源;第三方包沿用当前包管理器的源配置。
91
- - `install` 可以省略;直接执行 `npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta` 等同于 `npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta install`。
92
- 以选项开头时也默认安装,例如 `npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta --cloud --yes`。
91
+ - `install` 可以省略;直接执行 `npx @mearl/setup@beta` 等同于 `npx @mearl/setup@beta install`。
92
+ 以选项开头时也默认安装,例如 `npx @mearl/setup@beta --cloud --yes`。
93
93
  - `install` 未指定环境时默认 `--local`。
94
94
  - `-h` / `--help` 显示帮助,`-v` / `--version` 显示 setup 版本。
95
95
  - `update` 和 `uninstall` 未指定环境时,从当前 `PATH` 中的
@@ -137,19 +137,19 @@ npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider unin
137
137
  ## 安装或更新浏览器扩展
138
138
 
139
139
  ```bash
140
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta extension
140
+ npx @mearl/setup@beta extension
141
141
  ```
142
142
 
143
143
  首次使用时可一次安装完整的本地浏览器环境:
144
144
 
145
145
  ```bash
146
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta extension --full
146
+ npx @mearl/setup@beta extension --full
147
147
  ```
148
148
 
149
149
  首次安装、旧版 setup 扩展首次迁移,或 Native Host reload 不可用时才需要 CDP。如果
150
150
  Chrome 尚未开放 CDP,交互命令会提示打开
151
151
  `chrome://inspect/#remote-debugging`、启用远程调试并允许连接,然后等待安装完成。面向人的
152
- 安装指引不要添加 `--yes`。无 TTY 环境使用 `npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta extension --yes`;它只尝试
152
+ 安装指引不要添加 `--yes`。无 TTY 环境使用 `npx @mearl/setup@beta extension --yes`;它只尝试
153
153
  一次,CDP 不可用时立即中断。无法确认 setup 管理来源且 CDP 不可用时不会下载或修改插件
154
154
  目录,打开 CDP 后重新执行同一命令即可。Chrome 版本过低、缺少扩展安装所需的 CDP 方法时,
155
155
  命令会中断并提示升级 Chrome。
@@ -179,7 +179,7 @@ DevTools 面板的设置按钮会显示红点,快捷设置中提供“立即
179
179
 
180
180
  自动更新不会降级较新的 Host,也不会改动 cloud、MCP 或独立安装的 Mearl Skill。
181
181
  目标 npm 版本尚未发布、网络不可用或更新失败时,旧 Host 会继续运行;下一次扩展后台
182
- 生命周期可重新尝试,也可手动执行 `npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta update --local`。
182
+ 生命周期可重新尝试,也可手动执行 `npx @mearl/setup@beta update --local`。
183
183
 
184
184
  ## 人工与 Agent 使用
185
185
 
@@ -194,41 +194,41 @@ DevTools 面板的设置按钮会显示红点,快捷设置中提供“立即
194
194
 
195
195
  ```bash
196
196
  # 默认安装本地浏览器环境
197
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup
197
+ npx @mearl/setup
198
198
 
199
199
  # 与上一条等价
200
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta install
200
+ npx @mearl/setup@beta install
201
201
 
202
202
  # Agent / CI 无交互安装
203
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta --yes
203
+ npx @mearl/setup@beta --yes
204
204
 
205
205
  # 安装云端 Agent 环境
206
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta --cloud
206
+ npx @mearl/setup@beta --cloud
207
207
 
208
208
  # 同一台机器同时作为本地浏览器宿主和云端 Agent
209
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta --local --cloud
209
+ npx @mearl/setup@beta --local --cloud
210
210
 
211
211
  # 自动更新当前 PATH 中检测到的全部 Mearl 环境
212
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta update
212
+ npx @mearl/setup@beta update
213
213
 
214
214
  # 独立安装或更新到最新浏览器扩展
215
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta extension
215
+ npx @mearl/setup@beta extension
216
216
 
217
217
  # 安装完整本地环境(client、Native Host、Skill 和扩展)
218
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta extension --full
218
+ npx @mearl/setup@beta extension --full
219
219
 
220
220
  # 只卸载本地环境
221
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta uninstall --local
221
+ npx @mearl/setup@beta uninstall --local
222
222
 
223
223
  # 自动检测并无交互卸载
224
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta uninstall --yes
224
+ npx @mearl/setup@beta uninstall --yes
225
225
 
226
226
  # 卸载环境,并清理全部 CLI 插件与 Provider
227
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta uninstall --full
227
+ npx @mearl/setup@beta uninstall --full
228
228
 
229
229
  # 安装可选浏览器 Provider
230
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider install @example/mearl-provider-device
231
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider install @example/mearl-provider-cloud
230
+ npx @mearl/setup@beta provider install @example/mearl-provider-device
231
+ npx @mearl/setup@beta provider install @example/mearl-provider-cloud
232
232
  ```
233
233
 
234
234
  原有 Mearl 环境包的 npm/npx 安装方式和 CLI 保持可用;setup 是统一的便利入口。
@@ -237,6 +237,12 @@ npx --@mearl:registry=https://registry.npmjs.org @mearl/setup@beta provider inst
237
237
 
238
238
  `mearl-setup preset apply <source-url-or-path>` 准备确切资源、激活本机 Node 选择,并向兼容扩展同步同一批浏览器资源;`preset update` 保留用户覆盖,`preset status` 只读检查实际就绪状态。退出码 3 表示存在待同步/安装组件。完整扩展、默认设置、Skills/Provider 和网页一键安装使用同一实际状态核验,见 [本机安装合同](../../docs/preset-installation.md)。
239
239
 
240
- 普通 install/update 在替换包前保存选中 preset 的确切资源和期望选择,运行时初始化后再同步宿主和安装 Skills;`extension --full` 也在替换扩展前完成准备。已有本机 preset 优先保留,显式 `--preset` 可更新或修复资源;检测到已安装包为 3.0 以前版本且尚无选择时,读取公共部署目录的 `legacyPreset`。配置或资源不可用会在包变更前停止,不回退到无内部能力的安装。部分升级后的重试使用已保存安装记录,即使包版本已变为 3.x 也不重新下载。维护旧版本线和卸载不自动迁移。
240
+ 普通 install/update 在替换包前保存选中 preset 的确切资源和期望选择,运行时初始化后再同步宿主和安装 Skills;`extension --full` 也在替换扩展前完成准备。已有本机 preset 优先保留,显式 `--preset` 可更新或修复资源。配置或资源不可用会在包变更前停止,不回退到无内部能力的安装。部分升级后的重试使用已保存安装记录,即使包版本已变为 3.x 也不重新下载。卸载不准备或安装 preset。
241
241
 
242
242
  默认目录为公共站点的 `installation.json`,可通过 `MEARL_INSTALLATION_CONFIG_URL` 指定其他 HTTPS 目录(本地验证允许回环 HTTP)。核心不包含组织 ID 或私有来源;目录沿用网页安装的校验合同。本机与浏览器未全部确认时仍报告 pending,准备成功不表示已发行或已完成真实企业授权验收。
243
+
244
+ ## 浏览器 preset 与发行入口
245
+
246
+ 主动执行 setup 时,先沿用本机已选 preset;本机未选择时,在运行时初始化后、安装 Skill 前采用已连接浏览器的受信 preset。显式 `--preset <source>` 优先。多个浏览器的选择不同、来源不受信或状态读取失败时要求显式选择。首次连接最多等待约一分钟;浏览器关闭时只安装基础环境并说明重试方式。普通连接和后台核心更新不会自动安装首次本机 preset。
247
+
248
+ 发行包可设置本机进程环境变量 `MEARL_SETUP_DEFAULT_PRESET` 提供默认清单来源;默认值仅在两端均无选择时生效,插件、Provider、卸载等子命令保持原行为。安装与状态核验仍由公共安装器负责。
@@ -0,0 +1,20 @@
1
+ import { invokeLocalHost, listLiveBrowsers } from '@mearl/client/local';
2
+ import { loadInstallationConfig } from '@mearl/preset/installation';
3
+ import { createNodePresetLoader } from './preset.js';
4
+ import type { StagedPresetInstallation } from './preset.js';
5
+ interface Options {
6
+ env?: NodeJS.ProcessEnv;
7
+ waitMs?: number;
8
+ writeLine?: (line: string) => void;
9
+ }
10
+ interface Dependencies {
11
+ browsers?: typeof listLiveBrowsers;
12
+ invoke?: typeof invokeLocalHost;
13
+ configuration?: () => ReturnType<typeof loadInstallationConfig>;
14
+ load?: ReturnType<typeof createNodePresetLoader>;
15
+ now?: () => number;
16
+ sleep?: (ms: number) => Promise<void>;
17
+ }
18
+ /** Explicit setup may complete a trusted browser selection before installing any Skills. */
19
+ export declare function prepareBrowserSetupPreset(options?: Options, dependencies?: Dependencies): Promise<StagedPresetInstallation | undefined>;
20
+ export {};
@@ -0,0 +1,82 @@
1
+ import { invokeLocalHost, listLiveBrowsers } from '@mearl/client/local';
2
+ import { installationOriginCovered, loadInstallationConfig, matchesInstallationOrigin, trustedInstallationPreset } from '@mearl/preset/installation';
3
+ import { createNodePresetLoader, PresetInstaller, resolveInstallationConfigUrl } from './preset.js';
4
+ /** Explicit setup may complete a trusted browser selection before installing any Skills. */
5
+ export async function prepareBrowserSetupPreset(options = {}, dependencies = {}) {
6
+ const env = options.env ?? process.env;
7
+ const directory = env.MEARL_CAPABILITY_DIR;
8
+ const current = await new PresetInstaller({ directory }).store.current();
9
+ if (current.activation || current.receipt)
10
+ return;
11
+ const now = dependencies.now ?? Date.now;
12
+ const deadline = now() + (options.waitMs ?? 0);
13
+ const browsers = dependencies.browsers ?? listLiveBrowsers;
14
+ const invoke = dependencies.invoke ?? invokeLocalHost;
15
+ const sleep = dependencies.sleep ?? (ms => new Promise(resolve => setTimeout(resolve, ms)));
16
+ const configuration = dependencies.configuration ?? (() => {
17
+ const url = resolveInstallationConfigUrl(env.MEARL_INSTALLATION_CONFIG_URL);
18
+ return loadInstallationConfig(url, url.origin, AbortSignal.timeout(5_000));
19
+ });
20
+ const read = async (timeoutSec = 5) => {
21
+ const targets = browsers().filter(browser => !browser.provider && browser.transport === 'extension' && browser.status === 'connected' && (!env.MEARL_SOCKET_PATH || browser.socketPath === env.MEARL_SOCKET_PATH));
22
+ if (!targets.length)
23
+ return;
24
+ return (await Promise.all(targets.map(async (browser) => {
25
+ let value;
26
+ try {
27
+ value = await invoke('preset_browser_status', {}, { socketPath: browser.socketPath, timeoutSec });
28
+ }
29
+ catch {
30
+ throw new Error(`Cannot read preset from ${browser.browserId}. Reconnect the extension and retry setup, or use --preset <source>.`);
31
+ }
32
+ 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
+ throw new Error(`Browser preset is unavailable in ${browser.browserId}. Reconnect the extension and retry setup, or use --preset <source>.`);
34
+ if (!value.presetId)
35
+ return [];
36
+ return [{ browserId: browser.browserId, presetId: value.presetId, presetSource: value.presetSource, browserRevision: value.browserRevision }];
37
+ }))).flat();
38
+ };
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)
52
+ return;
53
+ const accepted = selected;
54
+ const first = accepted[0];
55
+ if (accepted.some(value => value.presetId !== first.presetId || value.presetSource !== first.presetSource))
56
+ throw new Error('Connected browsers use different presets. Select one explicitly with --preset <source>.');
57
+ const trusted = trustedInstallationPreset(await configuration(), first.presetId, first.presetSource);
58
+ if (!trusted)
59
+ throw new Error('The browser preset is not in the trusted installation catalog. Install it explicitly with --preset <source>.');
60
+ const load = dependencies.load ?? createNodePresetLoader();
61
+ const installer = new PresetInstaller({ directory, load: (source, resourceOptions) => {
62
+ const url = new URL(source);
63
+ if (url.protocol !== 'https:' || url.username || url.password || !trusted.origins.some(origin => matchesInstallationOrigin(origin, url)))
64
+ throw new Error('Browser preset resource is outside the trusted installation catalog');
65
+ return load(source, resourceOptions);
66
+ } });
67
+ options.writeLine?.(`Using the browser's trusted preset: ${first.presetId}`);
68
+ return installer.stage(first.presetSource, {
69
+ expectedId: first.presetId, expectedRevision: null, signal: AbortSignal.timeout(120_000),
70
+ validatePrepared: async (prepared) => {
71
+ if (prepared.preset.permissions.origins.some(origin => !installationOriginCovered(origin, trusted.origins)))
72
+ throw new Error('Browser preset permissions exceed the trusted installation catalog');
73
+ },
74
+ confirm: async () => {
75
+ if (JSON.stringify(trustedInstallationPreset(await configuration(), first.presetId, first.presetSource)) !== JSON.stringify(trusted))
76
+ throw new Error('Browser preset trust changed during setup');
77
+ const latest = await read();
78
+ if (!latest || latest.length !== accepted.length || accepted.some(value => !latest.some(other => value.browserId === other.browserId && value.presetId === other.presetId && value.presetSource === other.presetSource && value.browserRevision === other.browserRevision)))
79
+ throw new Error('The browser preset changed during setup. Retry or use --preset <source>.');
80
+ },
81
+ });
82
+ }
package/dist/cli.d.ts CHANGED
@@ -5,7 +5,7 @@ import type { MearlProfile } from './profiles.js';
5
5
  import { ensureOfficialPlugin, installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
6
6
  import { installProviderPackage, uninstallProviderPackage, updateProviderPackage } from './provider.js';
7
7
  import { runPresetCommand } from './preset.js';
8
- import { prepareSetupPreset } from './presetUpgrade.js';
8
+ import { prepareSetupPreset } from './setupPreset.js';
9
9
  export { SETUP_VERSION } from './setupVersion.js';
10
10
  interface ParsedArgs {
11
11
  cloudMode?: 'websocket' | 'qoder';
package/dist/cli.js CHANGED
@@ -6,7 +6,7 @@ import { isMearlReleaseLine } from './version.js';
6
6
  import { ensureOfficialPlugin, installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
7
7
  import { installProviderPackage, uninstallProviderPackage, updateProviderPackage, } from './provider.js';
8
8
  import { runPresetCommand } from './preset.js';
9
- import { prepareSetupPreset } from './presetUpgrade.js';
9
+ import { prepareSetupPreset } from './setupPreset.js';
10
10
  import { SETUP_VERSION, SETUP_COMMAND } from './setupVersion.js';
11
11
  export { SETUP_VERSION } from './setupVersion.js';
12
12
  const baseParsedArgs = () => ({
@@ -177,7 +177,7 @@ function printHelp() {
177
177
  const output = `Mearl Setup v${SETUP_VERSION}
178
178
 
179
179
  Usage:
180
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup [command] [options]
180
+ npx @mearl/setup [command] [options]
181
181
 
182
182
  Environment commands:
183
183
  install [options] Install an environment (default command; target: local)
@@ -221,21 +221,23 @@ General options:
221
221
  --help, -h Show this help
222
222
 
223
223
  Examples:
224
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup
225
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup --cloud
226
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup --local --cloud
227
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup update
228
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup uninstall --full
229
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup plugin uninstall yuque
230
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup provider install @example/mearl-provider-brow
231
- npx --@mearl:registry=https://registry.npmjs.org @mearl/setup extension --full`;
232
- console.log(output.replaceAll('npx --@mearl:registry=https://registry.npmjs.org @mearl/setup', SETUP_COMMAND));
224
+ npx @mearl/setup
225
+ npx @mearl/setup --cloud
226
+ npx @mearl/setup --local --cloud
227
+ npx @mearl/setup update
228
+ npx @mearl/setup uninstall --full
229
+ npx @mearl/setup plugin uninstall yuque
230
+ npx @mearl/setup provider install @example/mearl-provider-brow
231
+ npx @mearl/setup extension --full`;
232
+ console.log(output.replaceAll('npx @mearl/setup', SETUP_COMMAND));
233
233
  }
234
234
  async function runSetupLifecycle(options, dependencies) {
235
235
  let preset;
236
236
  const callback = options.operation === 'uninstall' ? undefined : { onPresetStatus: (status) => { preset = status; } };
237
237
  const lifecycle = dependencies.runLifecycle ?? runMearlLifecycle;
238
- const ok = callback ? await lifecycle(options, callback) : await lifecycle(options);
238
+ const defaultPresetSource = process.env.MEARL_SETUP_DEFAULT_PRESET;
239
+ const configured = defaultPresetSource ? { ...options, defaultPresetSource } : options;
240
+ const ok = callback ? await lifecycle(configured, callback) : await lifecycle(configured);
239
241
  if (!ok)
240
242
  return 1;
241
243
  if (!preset) {
@@ -9,11 +9,14 @@ import { installManagedPlugins, uninstallManagedPlugins } from './managedPlugins
9
9
  import { uninstallPlugin } from './plugin.js';
10
10
  import { uninstallProviderPackage } from './provider.js';
11
11
  import { cleanupUninstalledRuntime } from './runtimeCleanup.js';
12
- import { prepareSetupPreset } from './presetUpgrade.js';
12
+ import { prepareSetupPreset } from './setupPreset.js';
13
+ import { prepareBrowserSetupPreset } from './browserPreset.js';
13
14
  import type { PresetInstallStatus } from './preset.js';
14
15
  export type MearlLifecycleOperation = 'install' | 'update' | 'uninstall';
15
16
  export interface MearlLifecycleOptions {
16
17
  presetSource?: string;
18
+ /** Distribution default used only when neither local nor browser selection exists. */
19
+ defaultPresetSource?: string;
17
20
  operation: MearlLifecycleOperation;
18
21
  cloudMode?: 'websocket' | 'qoder';
19
22
  /** Uninstall every registered CLI plugin and Provider in addition to selected environments. */
@@ -27,6 +30,7 @@ export interface MearlLifecycleOptions {
27
30
  env?: NodeJS.ProcessEnv;
28
31
  }
29
32
  export interface MearlLifecycleDependencies {
33
+ prepareBrowserPreset?: typeof prepareBrowserSetupPreset;
30
34
  preparePreset?: typeof prepareSetupPreset;
31
35
  onPresetStatus?: (status: PresetInstallStatus) => void;
32
36
  /** Prepare the selected manager after packages are usable and before Skill operations. */
package/dist/lifecycle.js CHANGED
@@ -16,7 +16,8 @@ import { uninstallPlugin } from './plugin.js';
16
16
  import { uninstallProviderPackage } from './provider.js';
17
17
  import { packageRegistryEnvironment } from './npmRegistry.js';
18
18
  import { cleanupUninstalledRuntime } from './runtimeCleanup.js';
19
- import { prepareSetupPreset } from './presetUpgrade.js';
19
+ import { prepareSetupPreset } from './setupPreset.js';
20
+ import { prepareBrowserSetupPreset } from './browserPreset.js';
20
21
  const LEGACY_CLOUD_CLIENT = '@mearl/cloud-client';
21
22
  const UNIFIED_CLIENT = '@mearl/client';
22
23
  const PROFILE_ORDER = ['local', 'cloud', 'mcp'];
@@ -236,7 +237,7 @@ export async function runMearlLifecycle(options, dependencies = {}) {
236
237
  writeLine('No installed Mearl environment was detected on the current PATH.');
237
238
  if (options.operation === 'uninstall')
238
239
  return true;
239
- writeLine('Run: npx --@mearl:registry=https://registry.npmjs.org @mearl/setup');
240
+ writeLine('Run: npx @mearl/setup');
240
241
  return false;
241
242
  }
242
243
  if (options.operation === 'update') {
@@ -281,16 +282,13 @@ export async function runMearlLifecycle(options, dependencies = {}) {
281
282
  ? detectPackage(UNIFIED_CLIENT, ['mearl'], packageDetectionOptions)
282
283
  : null;
283
284
  const detectedClient = detectedClientCandidate?.packageName === UNIFIED_CLIENT ? detectedClientCandidate : null;
285
+ const preparesPreset = options.operation !== 'uninstall' && !dependencies.prepareSkills && (!options.targetLine || Number(options.targetLine.split('.')[0]) >= 3);
286
+ const selectsInitialPreset = preparesPreset && !options.skipSkill && !env.MEARL_AUTO_UPDATE_CLIENT_ROOT && !env.MEARL_AUTO_UPDATE_PROFILES;
284
287
  let stagedPreset;
285
- if (options.operation !== 'uninstall' && (options.presetSource || !options.targetLine || Number(options.targetLine.split('.')[0]) >= 3) && !dependencies.prepareSkills) {
288
+ if (preparesPreset || (options.operation !== 'uninstall' && options.presetSource && !dependencies.prepareSkills)) {
286
289
  try {
287
290
  stagedPreset = await (dependencies.preparePreset ?? prepareSetupPreset)({
288
291
  source: options.presetSource, env,
289
- installed: [
290
- ...lifecycleSelected.flatMap(profile => detected.get(profile) ? [detected.get(profile)] : []),
291
- ...(detectedClient ? [detectedClient] : []),
292
- ...(autoUpdateClient && env.MEARL_AUTO_UPDATE_CLIENT_ROOT ? [{ packageName: UNIFIED_CLIENT, packageRoot: env.MEARL_AUTO_UPDATE_CLIENT_ROOT }] : []),
293
- ],
294
292
  });
295
293
  }
296
294
  catch (error) {
@@ -422,6 +420,24 @@ export async function runMearlLifecycle(options, dependencies = {}) {
422
420
  }
423
421
  }
424
422
  }
423
+ if (packagesOk && selectsInitialPreset && !stagedPreset && !options.presetSource && lifecycleSelected.some(profile => profile === 'local' || profile === 'mcp')) {
424
+ try {
425
+ stagedPreset = await (dependencies.prepareBrowserPreset ?? prepareBrowserSetupPreset)({ env, waitMs: 65_000, writeLine });
426
+ }
427
+ catch (error) {
428
+ writeLine(`Browser preset: ${error instanceof Error ? error.message : String(error)}`);
429
+ packagesOk = false;
430
+ }
431
+ }
432
+ if (packagesOk && selectsInitialPreset && !stagedPreset && options.defaultPresetSource) {
433
+ try {
434
+ stagedPreset = await (dependencies.preparePreset ?? prepareSetupPreset)({ fallbackSource: options.defaultPresetSource, env });
435
+ }
436
+ catch (error) {
437
+ writeLine(`Default preset: ${error instanceof Error ? error.message : String(error)}`);
438
+ packagesOk = false;
439
+ }
440
+ }
425
441
  if (packagesOk && options.operation !== 'uninstall' && (stagedPreset || dependencies.prepareSkills)) {
426
442
  try {
427
443
  if (stagedPreset) {
package/dist/preset.d.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  import { invoke } from '@mearl/client/local';
2
2
  import { type JsonObject } from '@mearl/preset';
3
- import { type BundleLoader } from '@mearl/preset/bundles';
3
+ import { type BundleLoader, type PreparedPreset } from '@mearl/preset/bundles';
4
4
  import { PresetInstallationStore } from './presetStore.js';
5
+ export declare const MEARL_INSTALLATION_CONFIG_URL: string;
6
+ /** Runtime overrides apply to this local process, never to browser-supplied install requests. */
7
+ export declare function resolveInstallationConfigUrl(value?: string | undefined, allowLoopback?: boolean): URL;
5
8
  export { claimPresetInstallation } from './presetInstallationLease.js';
6
9
  export { PresetInstallationStore, presetSelectionRevision } from './presetStore.js';
7
10
  export type { PresetInstallReceipt } from './presetStore.js';
@@ -59,6 +62,8 @@ export interface PresetInstallerOptions {
59
62
  invoke?: typeof invoke;
60
63
  }
61
64
  export interface PresetApplyOptions {
65
+ validatePrepared?: (prepared: PreparedPreset) => Promise<void>;
66
+ skipUnchanged?: boolean;
62
67
  overrides?: JsonObject;
63
68
  nodeConfig?: Record<string, JsonObject>;
64
69
  signal?: AbortSignal;
@@ -87,7 +92,7 @@ export declare class PresetInstaller {
87
92
  /** Save exact resources and desired selection; activate once after runtime setup, then retry via resume. */
88
93
  stage(source: string, options?: PresetApplyOptions): Promise<StagedPresetInstallation>;
89
94
  apply(source: string, options?: PresetApplyOptions): Promise<PresetInstallStatus>;
90
- update(): Promise<PresetInstallStatus>;
95
+ update(options?: Pick<PresetApplyOptions, 'validatePrepared' | 'skipUnchanged' | 'signal'>): Promise<PresetInstallStatus>;
91
96
  resume(expectedRevision: string): Promise<PresetInstallStatus>;
92
97
  configure(value: JsonObject): Promise<PresetInstallStatus>;
93
98
  }
package/dist/preset.js CHANGED
@@ -1,11 +1,21 @@
1
1
  import { lstat, open } from 'node:fs/promises';
2
2
  import { constants } from 'node:fs';
3
+ import { createRequire } from 'node:module';
3
4
  import { resolve } from 'node:path';
4
5
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
6
  import { invoke, invokeLocalHost, listLiveBrowsers, SOCKET_PATH } from '@mearl/client/local';
6
7
  import { mergePresetDefaults } from '@mearl/preset';
7
8
  import { BundleError, createBundleFetchLoader, preparePreset } from '@mearl/preset/bundles';
8
9
  import { PresetInstallationStore, presetSelectionRevision } from './presetStore.js';
10
+ const installationSite = createRequire(import.meta.url)('@mearl/preset/installation-site.json');
11
+ export const MEARL_INSTALLATION_CONFIG_URL = new URL('installation.json', installationSite.url).href;
12
+ /** Runtime overrides apply to this local process, never to browser-supplied install requests. */
13
+ export function resolveInstallationConfigUrl(value = process.env.MEARL_INSTALLATION_CONFIG_URL, allowLoopback = false) {
14
+ const url = new URL(value || MEARL_INSTALLATION_CONFIG_URL);
15
+ if (url.username || url.password || url.hash || (url.protocol !== 'https:' && !(allowLoopback && url.protocol === 'http:' && ['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname))))
16
+ throw new Error('Invalid installation configuration URL');
17
+ return url;
18
+ }
9
19
  export { claimPresetInstallation } from './presetInstallationLease.js';
10
20
  export { PresetInstallationStore, presetSelectionRevision } from './presetStore.js';
11
21
  export { PresetBrowserTransfer } from './presetBrowserTransfer.js';
@@ -225,6 +235,20 @@ export class PresetInstaller {
225
235
  const prepared = await preparePreset(sourceUrl(source), { mearlApiVersion: this.store.mearlApiVersion, load: this.options.load ?? createNodePresetLoader(), signal: options.signal });
226
236
  if (options.expectedId !== undefined && prepared.preset.id !== options.expectedId)
227
237
  throw new BundleError('BUNDLE_INVALID', 'The preset identity differs from the accepted installation');
238
+ await options.validatePrepared?.(prepared);
239
+ if (options.skipUnchanged) {
240
+ const current = await this.store.current();
241
+ if (presetSelectionRevision(current) !== expectedRevision)
242
+ throw new BundleError('BUNDLE_CONFLICT', 'The installed preset changed during update');
243
+ if (current.receipt && current.prepared?.snapshot.revision === prepared.snapshot.revision) {
244
+ return { revision: current.receipt.revision, activate: async () => {
245
+ const status = await this.status();
246
+ if (status.revision !== expectedRevision)
247
+ throw new BundleError('BUNDLE_CONFLICT', 'The installed preset changed during update');
248
+ return status.ready ? status : this.resume(current.receipt.revision);
249
+ } };
250
+ }
251
+ }
228
252
  const receipt = await this.store.apply(prepared, { ...options, expectedRevision });
229
253
  let activated = false;
230
254
  return { revision: receipt.revision, activate: async () => {
@@ -249,11 +273,11 @@ export class PresetInstaller {
249
273
  async apply(source, options = {}) {
250
274
  return (await this.stage(source, options)).activate();
251
275
  }
252
- async update() {
276
+ async update(options = {}) {
253
277
  const current = await this.store.current({ verifyResources: false });
254
278
  if (!current.receipt)
255
279
  throw new Error('No preset is installed');
256
- return this.apply(current.receipt.snapshot.source, { expectedRevision: presetSelectionRevision(current) });
280
+ return this.apply(current.receipt.snapshot.source, { ...options, expectedRevision: presetSelectionRevision(current) });
257
281
  }
258
282
  async resume(expectedRevision) {
259
283
  const current = await this.store.current();
@@ -0,0 +1,9 @@
1
+ import { PresetInstaller, type StagedPresetInstallation } from './preset.js';
2
+ /** Preserve the accepted installation and resume its exact resources before Skill operations. */
3
+ export declare function prepareSetupPreset(options: {
4
+ source?: string;
5
+ fallbackSource?: string;
6
+ env?: NodeJS.ProcessEnv;
7
+ }, dependencies?: {
8
+ installer?: PresetInstaller;
9
+ }): Promise<StagedPresetInstallation | undefined>;
@@ -0,0 +1,14 @@
1
+ import { PresetInstaller } from './preset.js';
2
+ /** Preserve the accepted installation and resume its exact resources before Skill operations. */
3
+ export async function prepareSetupPreset(options, dependencies = {}) {
4
+ const installer = dependencies.installer ?? new PresetInstaller({ directory: (options.env ?? process.env).MEARL_CAPABILITY_DIR });
5
+ if (options.source)
6
+ return installer.stage(options.source);
7
+ const current = await installer.store.current();
8
+ if (current.receipt)
9
+ return { revision: current.receipt.revision, activate: () => installer.resume(current.receipt.revision) };
10
+ if (current.activation)
11
+ return;
12
+ if (options.fallbackSource)
13
+ return installer.stage(options.fallbackSource);
14
+ }
@@ -1,4 +1,4 @@
1
1
  import { createRequire } from 'node:module';
2
2
  const require = createRequire(import.meta.url);
3
3
  export const SETUP_VERSION = require('../package.json').version;
4
- export const SETUP_COMMAND = `npx --@mearl:registry=https://registry.npmjs.org @mearl/setup${SETUP_VERSION.includes('-beta.') ? '@' + SETUP_VERSION : ''}`;
4
+ export const SETUP_COMMAND = `npx @mearl/setup${SETUP_VERSION.includes('-beta.') ? '@beta' : ''}`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/setup",
3
- "version": "3.0.0-beta.3",
3
+ "version": "3.0.0-beta.5",
4
4
  "description": "One-shot environment setup and configuration for Mearl",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -78,11 +78,11 @@
78
78
  "extract-zip": "^2.0.1",
79
79
  "ws": "^8.18.3",
80
80
  "semver": "^7.8.5",
81
- "@mearl/daemon-core": "3.0.0-beta.3",
82
- "@mearl/provider": "3.0.0-beta.3",
83
- "@mearl/browser-core": "3.0.0-beta.3",
84
- "@mearl/client": "3.0.0-beta.3",
85
- "@mearl/preset": "3.0.0-beta.3"
81
+ "@mearl/provider": "3.0.0-beta.5",
82
+ "@mearl/daemon-core": "3.0.0-beta.5",
83
+ "@mearl/preset": "3.0.0-beta.5",
84
+ "@mearl/browser-core": "3.0.0-beta.5",
85
+ "@mearl/client": "3.0.0-beta.5"
86
86
  },
87
87
  "devDependencies": {
88
88
  "@types/node": "^24.9.1",
@@ -1,13 +0,0 @@
1
- import { type InstallationConfig } from '@mearl/preset/installation';
2
- import { PresetInstaller, type StagedPresetInstallation } from './preset.js';
3
- import { type DetectedGlobalPackage } from './packageManager.js';
4
- export declare const MEARL_INSTALLATION_CONFIG_URL = "https://f-loat.github.io/mearl/installation.json";
5
- /** Reuse the installation receipt as the retry marker; organization defaults come from deployment data. */
6
- export declare function prepareSetupPreset(options: {
7
- source?: string;
8
- installed?: Array<Pick<DetectedGlobalPackage, 'packageName' | 'packageRoot'>>;
9
- env?: NodeJS.ProcessEnv;
10
- }, dependencies?: {
11
- installer?: PresetInstaller;
12
- configuration?: () => Promise<InstallationConfig>;
13
- }): Promise<StagedPresetInstallation | undefined>;
@@ -1,42 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import { join } from 'node:path';
3
- import { loadInstallationConfig } from '@mearl/preset/installation';
4
- import { PresetInstaller } from './preset.js';
5
- import { detectGlobalPackage } from './packageManager.js';
6
- import { detectMearlProfiles } from './profiles.js';
7
- export const MEARL_INSTALLATION_CONFIG_URL = 'https://f-loat.github.io/mearl/installation.json';
8
- function isLegacyPackage(installation) {
9
- try {
10
- const manifest = JSON.parse(readFileSync(join(installation.packageRoot, 'package.json'), 'utf8'));
11
- return manifest.name === installation.packageName && typeof manifest.version === 'string' && /^[0-2]\.\d+\.\d+(?:[-+][\w.-]+)?$/.test(manifest.version);
12
- }
13
- catch {
14
- return false;
15
- }
16
- }
17
- /** Reuse the installation receipt as the retry marker; organization defaults come from deployment data. */
18
- export async function prepareSetupPreset(options, dependencies = {}) {
19
- const env = options.env ?? process.env;
20
- const installer = dependencies.installer ?? new PresetInstaller({ directory: env.MEARL_CAPABILITY_DIR });
21
- if (options.source)
22
- return installer.stage(options.source);
23
- const current = await installer.store.current();
24
- if (current.receipt)
25
- return { revision: current.receipt.revision, activate: () => installer.resume(current.receipt.revision) };
26
- if (current.activation)
27
- return;
28
- const client = options.installed === undefined ? detectGlobalPackage('@mearl/client', ['mearl'], { env }) : null;
29
- const installed = options.installed ?? [...detectMearlProfiles({ env }).values(), ...(client ? [client] : [])];
30
- if (!installed.some(isLegacyPackage))
31
- return;
32
- const configuration = dependencies.configuration ?? (() => {
33
- const url = new URL(env.MEARL_INSTALLATION_CONFIG_URL || MEARL_INSTALLATION_CONFIG_URL);
34
- if (url.protocol !== 'https:' && !(url.protocol === 'http:' && ['127.0.0.1', 'localhost', '[::1]'].includes(url.hostname)))
35
- throw new Error('Installation configuration requires HTTPS or loopback HTTP');
36
- return loadInstallationConfig(url, url.origin, AbortSignal.timeout(5_000));
37
- });
38
- const config = await configuration();
39
- if (!config.legacyPreset)
40
- throw new Error('Legacy upgrade preset is unavailable. Retry when the installation catalog is available, or select a preset with --preset.');
41
- return installer.stage(config.presets[config.legacyPreset].source, { expectedRevision: null, expectedId: config.legacyPreset });
42
- }