@mearl/setup 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,7 +3,8 @@
3
3
  Mearl 环境的一次性安装、更新、卸载与配置工具。无需先全局安装 setup:
4
4
 
5
5
  ```bash
6
- npx @mearl/setup <install|update|uninstall> [--local|--cloud|--mcp] [--yes]
6
+ npx @mearl/setup [install] [--local] [--cloud] [--mcp] [--yes]
7
+ npx @mearl/setup <update|uninstall> [--local] [--cloud] [--mcp] [--yes]
7
8
  ```
8
9
 
9
10
  配置 AgentBay API Key 时只需执行一次下面的命令,然后在隐藏输入提示中粘贴 Key。
@@ -18,14 +19,19 @@ npx @mearl/setup agentbay
18
19
 
19
20
  ## 行为
20
21
 
22
+ - `install` 可以省略;直接执行 `npx @mearl/setup` 等同于 `npx @mearl/setup install`。
23
+ 以选项开头时也默认安装,例如 `npx @mearl/setup --cloud --yes`。
21
24
  - `install` 未指定环境时默认 `--local`。
25
+ - `-h` / `--help` 显示帮助,`-v` / `--version` 显示 setup 版本。
22
26
  - `update` 和 `uninstall` 未指定环境时,从当前 `PATH` 中的
23
- `mearl-native-host`、`mearl-cloud`、`mearl-mcp-server` 自动检测,可同时处理
27
+ `mearl-native-host`、`mearl-cloud-server`、`mearl-mcp-server` 自动检测,可同时处理
24
28
  多个已安装环境。
25
- - 显式传入 `--local`、`--cloud` 或 `--mcp` 时只处理指定环境。
29
+ - `--local`、`--cloud` 与 `--mcp` 可以组合,显式传入时只处理选中的环境。
26
30
  - 已有环境沿用其原包管理器和全局安装前缀;首次安装沿用启动 setup 的包管理器。
27
- - cloud 环境统一管理 `@mearl/cloud-server` 与 `@mearl/cloud-client`:首次安装后启动
31
+ - cloud 环境统一管理 `@mearl/cloud-server` 与 `@mearl/client`:首次安装后启动
28
32
  Server,更新后仅在 Server 原本运行时重启,卸载前停止。
33
+ - local/cloud 共用一份 `@mearl/client`;组合安装只处理一次,卸载单个环境时若另一个环境仍在使用则保留。
34
+ - 从 2.5.x 更新 cloud 环境时会先卸载旧 `@mearl/cloud-client`,避免它遗留的 `mearl` bin 与统一 client 冲突。
29
35
  - local/cloud 共用的全局 mearl skill 通过 `ali-skills` 的 JSON 清单定位,只处理一次。
30
36
  - 卸载一个环境但另一个仍依赖全局 mearl skill 时会保留 skill。
31
37
  - `--mcp` 的客户端配置和清理由 `@mearl/mcp-server` 自己的命令完成。
@@ -55,6 +61,9 @@ npx @mearl/setup agentbay
55
61
 
56
62
  ```bash
57
63
  # 默认安装本地浏览器环境
64
+ npx @mearl/setup
65
+
66
+ # 与上一条等价
58
67
  npx @mearl/setup install
59
68
 
60
69
  # Agent / CI 无交互安装
@@ -63,6 +72,9 @@ npx @mearl/setup install --yes
63
72
  # 安装云端 Agent 环境
64
73
  npx @mearl/setup install --cloud
65
74
 
75
+ # 同一台机器同时作为本地浏览器宿主和云端 Agent
76
+ npx @mearl/setup install --local --cloud
77
+
66
78
  # 自动更新当前 PATH 中检测到的全部 Mearl 环境
67
79
  npx @mearl/setup update
68
80
 
package/dist/cli.d.ts CHANGED
@@ -2,14 +2,16 @@
2
2
  import { configureAgentBayApiKey, promptAgentBayApiKey, readAgentBayApiKeyFile, readAgentBayApiKeyStdin } from './agentBay.js';
3
3
  import { type MearlLifecycleOperation } from './lifecycle.js';
4
4
  import type { MearlProfile } from './profiles.js';
5
+ export declare const SETUP_VERSION: string;
5
6
  interface ParsedArgs {
6
7
  agentBayKeyFile?: string;
7
8
  agentBayReadStdin: boolean;
8
9
  help: boolean;
9
10
  operation?: MearlLifecycleOperation | 'agentbay';
10
- profile?: MearlProfile;
11
+ profiles?: MearlProfile[];
11
12
  skipSkill: boolean;
12
13
  targetLine?: string;
14
+ version?: boolean;
13
15
  yes: boolean;
14
16
  }
15
17
  export interface SetupCliDependencies {
package/dist/cli.js CHANGED
@@ -1,14 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'node:path';
3
+ import { createRequire } from 'node:module';
3
4
  import { fileURLToPath } from 'node:url';
4
5
  import { configureAgentBayApiKey, promptAgentBayApiKey, readAgentBayApiKeyFile, readAgentBayApiKeyStdin, } from './agentBay.js';
5
6
  import { runMearlLifecycle } from './lifecycle.js';
6
7
  import { isMearlReleaseLine } from './version.js';
8
+ const require = createRequire(import.meta.url);
9
+ export const SETUP_VERSION = require('../package.json').version;
7
10
  export function parseSetupArgs(argv) {
8
- if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {
11
+ if (argv.includes('--help') || argv.includes('-h')) {
9
12
  return { agentBayReadStdin: false, help: true, skipSkill: false, yes: false };
10
13
  }
11
- const operation = argv[0];
14
+ if (argv.includes('--version') || argv.includes('-v')) {
15
+ return {
16
+ agentBayReadStdin: false,
17
+ help: false,
18
+ skipSkill: false,
19
+ version: true,
20
+ yes: false,
21
+ };
22
+ }
23
+ const firstArg = argv[0];
24
+ const operation = firstArg === undefined || firstArg.startsWith('-') ? 'install' : firstArg;
25
+ const optionStart = operation === firstArg ? 1 : 0;
12
26
  if (operation === 'agentbay') {
13
27
  if (argv.length === 1) {
14
28
  return {
@@ -43,11 +57,11 @@ export function parseSetupArgs(argv) {
43
57
  if (operation !== 'install' && operation !== 'update' && operation !== 'uninstall') {
44
58
  throw new Error(`Unknown operation: ${operation}`);
45
59
  }
46
- let profile;
60
+ const profiles = [];
47
61
  let skipSkill = false;
48
62
  let targetLine;
49
63
  let yes = false;
50
- for (let index = 1; index < argv.length; index += 1) {
64
+ for (let index = optionStart; index < argv.length; index += 1) {
51
65
  const arg = argv[index];
52
66
  if (arg === '--yes' || arg === '-y' || arg === '--non-interactive') {
53
67
  yes = true;
@@ -75,10 +89,8 @@ export function parseSetupArgs(argv) {
75
89
  : undefined;
76
90
  if (!candidate)
77
91
  throw new Error(`Unknown option: ${arg}`);
78
- if (profile && profile !== candidate) {
79
- throw new Error('Choose only one of --local, --cloud, or --mcp');
80
- }
81
- profile = candidate;
92
+ if (!profiles.includes(candidate))
93
+ profiles.push(candidate);
82
94
  }
83
95
  if (operation === 'uninstall' && targetLine) {
84
96
  throw new Error('--target-line is not available for uninstall');
@@ -87,28 +99,29 @@ export function parseSetupArgs(argv) {
87
99
  agentBayReadStdin: false,
88
100
  help: false,
89
101
  operation,
90
- profile,
102
+ ...(profiles.length > 0 ? { profiles } : {}),
91
103
  skipSkill,
92
104
  targetLine,
93
105
  yes,
94
106
  };
95
107
  }
96
- function printHelp() {
97
- console.error(`Mearl Setup
108
+ function printHelp(toError = false) {
109
+ const output = `Mearl Setup v${SETUP_VERSION}
98
110
 
99
111
  Usage:
100
- npx @mearl/setup <install|update|uninstall> [--local|--cloud|--mcp] [--yes]
112
+ npx @mearl/setup [install] [--local] [--cloud] [--mcp] [--yes]
113
+ npx @mearl/setup <update|uninstall> [--local] [--cloud] [--mcp] [--yes]
101
114
  npx @mearl/setup agentbay [--stdin | --key-file <path>]
102
115
 
103
116
  Behavior:
104
- install Defaults to --local when no environment is specified
117
+ install Default command; defaults to --local when no environment is specified
105
118
  update Auto-detects installed environments from the current PATH
106
119
  uninstall Auto-detects installed environments and asks for confirmation
107
120
  agentbay Save or update AGENTBAY_KEY without changing other Mearl settings
108
121
 
109
122
  Options:
110
123
  --local Manage @mearl/native-host and @mearl/client
111
- --cloud Manage @mearl/cloud-server and @mearl/cloud-client
124
+ --cloud Manage @mearl/cloud-server and the unified @mearl/client
112
125
  --mcp Manage @mearl/mcp-server and its configuration
113
126
  --stdin Read the AgentBay API key from standard input
114
127
  --key-file Read the AgentBay API key from a local file
@@ -118,16 +131,25 @@ Options:
118
131
  --yes, -y Run without prompts and accept detected defaults
119
132
  --non-interactive
120
133
  Alias for --yes, intended for Agents and CI
134
+ --version, -v
135
+ Show version
121
136
  --help, -h Show this help
122
137
 
123
138
  Examples:
139
+ npx @mearl/setup
140
+ npx @mearl/setup --yes
124
141
  npx @mearl/setup install
125
142
  npx @mearl/setup install --yes
126
143
  npx @mearl/setup install --cloud
144
+ npx @mearl/setup install --local --cloud
127
145
  npx @mearl/setup update
128
146
  npx @mearl/setup agentbay
129
147
  npx @mearl/setup update --local --target-line 2.3.x --skip-skill
130
- npx @mearl/setup uninstall --local`);
148
+ npx @mearl/setup uninstall --local`;
149
+ if (toError)
150
+ console.error(output);
151
+ else
152
+ console.log(output);
131
153
  }
132
154
  export async function main(argv = process.argv.slice(2), interactiveTerminal = process.stdin.isTTY === true && process.stdout.isTTY === true, dependencies = {}) {
133
155
  let parsed;
@@ -136,13 +158,17 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
136
158
  }
137
159
  catch (error) {
138
160
  console.error(error instanceof Error ? error.message : String(error));
139
- printHelp();
161
+ printHelp(true);
140
162
  return 2;
141
163
  }
142
164
  if (parsed.help) {
143
165
  printHelp();
144
166
  return 0;
145
167
  }
168
+ if (parsed.version) {
169
+ console.log(SETUP_VERSION);
170
+ return 0;
171
+ }
146
172
  if (parsed.operation === 'agentbay') {
147
173
  try {
148
174
  const configureAgentBay = dependencies.configureAgentBay ?? configureAgentBayApiKey;
@@ -175,7 +201,7 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
175
201
  try {
176
202
  const ok = await runMearlLifecycle({
177
203
  operation: parsed.operation,
178
- profile: parsed.profile,
204
+ profiles: parsed.profiles,
179
205
  skipSkill: parsed.skipSkill,
180
206
  targetLine: parsed.targetLine,
181
207
  yes: parsed.yes,
@@ -6,7 +6,9 @@ import { type CommandRunner } from './process.js';
6
6
  export type MearlLifecycleOperation = 'install' | 'update' | 'uninstall';
7
7
  export interface MearlLifecycleOptions {
8
8
  operation: MearlLifecycleOperation;
9
+ /** @deprecated Use profiles when selecting one or more composable environments. */
9
10
  profile?: MearlProfile;
11
+ profiles?: MearlProfile[];
10
12
  yes?: boolean;
11
13
  targetLine?: string;
12
14
  skipSkill?: boolean;
package/dist/lifecycle.js CHANGED
@@ -8,6 +8,10 @@ import { MEARL_PROFILES, detectMearlProfiles, } from './profiles.js';
8
8
  import { uninstallNativeHostArtifacts } from './nativeHost.js';
9
9
  import { executableNames, prependCommandPath, runCommand, } from './process.js';
10
10
  import { isMearlReleaseLine } from './version.js';
11
+ const LEGACY_CLOUD_CLIENT = '@mearl/cloud-client';
12
+ const UNIFIED_CLIENT = '@mearl/client';
13
+ const PROFILE_ORDER = ['local', 'cloud', 'mcp'];
14
+ const CLIENT_PROFILES = ['local', 'cloud'];
11
15
  async function confirmInTerminal(message) {
12
16
  if (!input.isTTY || !output.isTTY)
13
17
  return false;
@@ -40,6 +44,18 @@ async function runLifecycleCommand(runner, command, args, env, label, writeLine,
40
44
  return null;
41
45
  }
42
46
  }
47
+ async function runPackageOperation(installation, operation, packages, targetLine, runner, env, label, writeLine) {
48
+ const command = buildGlobalPackageCommand(installation, operation, packages, targetLine);
49
+ writeLine(`${label}: ${command.manager}`);
50
+ const result = await runLifecycleCommand(runner, command.command, command.args, env, label, writeLine);
51
+ if (!result)
52
+ return { command, ok: false };
53
+ if (result.code !== 0) {
54
+ writeLine(`${label}: failed (exit ${result.code}).`);
55
+ return { command, ok: false };
56
+ }
57
+ return { command, ok: true };
58
+ }
43
59
  async function runHook(profile, operation, detected, runner, existsSync, env, writeLine, yes) {
44
60
  let executable;
45
61
  let args;
@@ -101,11 +117,13 @@ async function runHook(profile, operation, detected, runner, existsSync, env, wr
101
117
  return true;
102
118
  }
103
119
  function selectedProfiles(operation, requested, detected) {
104
- if (requested)
105
- return [requested];
120
+ if (requested?.length) {
121
+ const requestedSet = new Set(requested);
122
+ return PROFILE_ORDER.filter(profile => requestedSet.has(profile));
123
+ }
106
124
  if (operation === 'install')
107
125
  return ['local'];
108
- return [...detected.keys()];
126
+ return PROFILE_ORDER.filter(profile => detected.has(profile));
109
127
  }
110
128
  function operationVerb(operation) {
111
129
  return operation === 'uninstall' ? 'uninstall' : operation === 'update' ? 'update' : 'install';
@@ -126,7 +144,8 @@ export async function runMearlLifecycle(options, dependencies = {}) {
126
144
  const runSkill = dependencies.runSkillOperation ?? runGlobalMearlSkillOperation;
127
145
  const confirm = dependencies.confirm ?? confirmInTerminal;
128
146
  const detected = detectProfiles(env);
129
- const selected = selectedProfiles(options.operation, options.profile, detected);
147
+ const requestedProfiles = options.profiles ?? (options.profile ? [options.profile] : undefined);
148
+ const selected = selectedProfiles(options.operation, requestedProfiles, detected);
130
149
  if (selected.length === 0) {
131
150
  writeLine('No installed Mearl environment was detected on the current PATH.');
132
151
  if (options.operation === 'uninstall')
@@ -157,6 +176,38 @@ export async function runMearlLifecycle(options, dependencies = {}) {
157
176
  .map(profile => MEARL_PROFILES[profile].label)
158
177
  .join(', ')}`);
159
178
  let packagesOk = true;
179
+ if (selected.includes('cloud')) {
180
+ const legacyCloudClient = detectPackage(LEGACY_CLOUD_CLIENT, ['mearl-cloud'], { env });
181
+ if (legacyCloudClient?.packageName === LEGACY_CLOUD_CLIENT) {
182
+ const command = buildGlobalPackageCommand(legacyCloudClient, 'uninstall', [
183
+ LEGACY_CLOUD_CLIENT,
184
+ ]);
185
+ writeLine(`Migration (cloud): removing ${LEGACY_CLOUD_CLIENT}.`);
186
+ const result = await runLifecycleCommand(runner, command.command, command.args, env, 'Migration (cloud)', writeLine);
187
+ if (!result || result.code !== 0) {
188
+ if (result)
189
+ writeLine(`Migration (cloud): failed (exit ${result.code}).`);
190
+ writeLine('Mearl setup incomplete. Review the messages above.');
191
+ return false;
192
+ }
193
+ }
194
+ }
195
+ const selectedUsesClient = selected.some(profile => CLIENT_PROFILES.includes(profile));
196
+ const detectedClientCandidate = selectedUsesClient
197
+ ? detectPackage(UNIFIED_CLIENT, ['mearl'], { env })
198
+ : null;
199
+ const detectedClient = detectedClientCandidate?.packageName === UNIFIED_CLIENT ? detectedClientCandidate : null;
200
+ if (selectedUsesClient && options.operation !== 'uninstall') {
201
+ const clientInstallation = detectedClient ??
202
+ detected.get('local') ??
203
+ detected.get('cloud') ??
204
+ resolveInvokingManager(env);
205
+ const sharedClient = await runPackageOperation(clientInstallation, options.operation, [UNIFIED_CLIENT], options.targetLine, runner, env, 'Packages (shared client)', writeLine);
206
+ if (!sharedClient.ok) {
207
+ writeLine('Mearl setup incomplete. Review the messages above.');
208
+ return false;
209
+ }
210
+ }
160
211
  for (const profile of selected) {
161
212
  const config = MEARL_PROFILES[profile];
162
213
  const current = detected.get(profile);
@@ -186,23 +237,20 @@ export async function runMearlLifecycle(options, dependencies = {}) {
186
237
  packagesOk = false;
187
238
  continue;
188
239
  }
189
- const packageOperation = options.operation === 'uninstall' ? 'uninstall' : options.operation;
190
- const command = buildGlobalPackageCommand(installation, packageOperation, config.packages, options.targetLine);
191
- writeLine(`Packages (${profile}): ${command.manager}`);
192
- const result = await runLifecycleCommand(runner, command.command, command.args, env, `Packages (${profile})`, writeLine);
193
- if (!result) {
194
- packagesOk = false;
240
+ const packageOperation = options.operation;
241
+ const packages = config.packages.filter(packageName => packageName !== UNIFIED_CLIENT);
242
+ if (packages.length === 0) {
195
243
  continue;
196
244
  }
197
- if (result.code !== 0) {
198
- writeLine(`Packages (${profile}): failed (exit ${result.code}).`);
245
+ const packageResult = await runPackageOperation(installation, packageOperation, packages, options.targetLine, runner, env, `Packages (${profile})`, writeLine);
246
+ if (!packageResult.ok) {
199
247
  packagesOk = false;
200
248
  continue;
201
249
  }
202
250
  if (options.operation !== 'uninstall' &&
203
251
  (profile === 'local' || profile === 'cloud' || profile === 'mcp')) {
204
252
  const refreshed = detectPackage(config.anchorPackage, config.anchorBins, {
205
- env: prependCommandPath(command.command, env),
253
+ env: prependCommandPath(packageResult.command.command, env),
206
254
  }) ?? current;
207
255
  if (!refreshed ||
208
256
  !(await runHook(profile, options.operation, refreshed, runner, existsSync, env, writeLine, options.yes === true))) {
@@ -210,6 +258,17 @@ export async function runMearlLifecycle(options, dependencies = {}) {
210
258
  }
211
259
  }
212
260
  }
261
+ if (selectedUsesClient && options.operation === 'uninstall') {
262
+ const remainingClientProfiles = CLIENT_PROFILES.filter(profile => detected.has(profile) && !selected.includes(profile));
263
+ if (remainingClientProfiles.length > 0) {
264
+ writeLine(`Packages (shared client): kept for ${remainingClientProfiles.join(', ')}.`);
265
+ }
266
+ else if (packagesOk && detectedClient) {
267
+ const sharedClient = await runPackageOperation(detectedClient, 'uninstall', [UNIFIED_CLIENT], undefined, runner, env, 'Packages (shared client)', writeLine);
268
+ if (!sharedClient.ok)
269
+ packagesOk = false;
270
+ }
271
+ }
213
272
  let skillOk = true;
214
273
  const selectedUsesSkill = selected.some(profile => MEARL_PROFILES[profile].usesSkill);
215
274
  if (packagesOk && selectedUsesSkill && options.skipSkill !== true) {
package/dist/profiles.js CHANGED
@@ -11,9 +11,9 @@ export const MEARL_PROFILES = {
11
11
  cloud: {
12
12
  id: 'cloud',
13
13
  label: 'cloud agent',
14
- anchorPackage: '@mearl/cloud-client',
15
- anchorBins: ['mearl-cloud'],
16
- packages: ['@mearl/cloud-server', '@mearl/cloud-client'],
14
+ anchorPackage: '@mearl/cloud-server',
15
+ anchorBins: ['mearl-cloud-server'],
16
+ packages: ['@mearl/cloud-server', '@mearl/client'],
17
17
  usesSkill: true,
18
18
  },
19
19
  mcp: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/setup",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "One-shot environment setup and configuration for Mearl",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",