@mearl/setup 2.8.2 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,9 +3,9 @@
3
3
  Mearl 环境的一次性安装、更新、卸载与配置工具。无需先全局安装 setup:
4
4
 
5
5
  ```bash
6
- npx @mearl/setup [install] [--local] [--cloud|--qoder-cloud] [--mcp] [--yes]
7
- npx @mearl/setup <update|uninstall> [--local] [--cloud|--qoder-cloud] [--mcp] [--yes]
8
- npx @mearl/setup extension [--force] [--yes]
6
+ npx @mearl/setup [install] [--local] [--cloud|--qoder-cloud] [--mcp]
7
+ npx @mearl/setup <update|uninstall> [--local] [--cloud|--qoder-cloud] [--mcp]
8
+ npx @mearl/setup extension [--full] [--force]
9
9
  ```
10
10
 
11
11
  配置 AgentBay API Key 时只需执行一次下面的命令,然后在隐藏输入提示中粘贴 Key。
@@ -51,6 +51,10 @@ npx @mearl/setup agentbay
51
51
  加载;后续更新由 `mearl check --local --json` 确认当前扩展仍来自 setup 管理目录后,直接替换
52
52
  文件并通过 Native Host 触发扩展 reload。固定目录和 manifest 中的 `key` 保证扩展 ID
53
53
  与浏览器存储保持稳定。
54
+ - `extension --full` 会先安装或更新扩展,再复用 local profile 安装
55
+ `@mearl/client`、安装并初始化 `@mearl/native-host`,最后安装全局 Mearl Skill。
56
+ 两部分都会尽量执行;任一步失败时命令返回非零退出码,已安装的部分会保留。它不安装
57
+ cloud 或 MCP 环境。
54
58
  - 下载前会通过 CDP 检查同 ID 插件的来源。未安装,或来源为固定目录中的 `UNPACKED`
55
59
  插件时可继续;Chrome 商店、企业策略或其他本地目录安装的版本默认保持不变。只有显式
56
60
  传入 `--force` 才会接管并替换这些版本。
@@ -61,6 +65,12 @@ npx @mearl/setup agentbay
61
65
  npx @mearl/setup extension
62
66
  ```
63
67
 
68
+ 首次使用时可一次安装完整的本地浏览器环境:
69
+
70
+ ```bash
71
+ npx @mearl/setup extension --full
72
+ ```
73
+
64
74
  首次安装、旧版 setup 扩展首次迁移,或 Native Host reload 不可用时才需要 CDP。如果
65
75
  Chrome 尚未开放 CDP,交互命令会提示打开
66
76
  `chrome://inspect/#remote-debugging`、启用远程调试并允许连接,然后等待安装完成。无 TTY
@@ -127,6 +137,9 @@ npx @mearl/setup update
127
137
  # 独立安装或更新到最新浏览器扩展
128
138
  npx @mearl/setup extension
129
139
 
140
+ # 安装完整本地环境(client、Native Host、Skill 和扩展)
141
+ npx @mearl/setup extension --full
142
+
130
143
  # 只卸载本地环境
131
144
  npx @mearl/setup uninstall --local
132
145
 
@@ -488,11 +488,12 @@ export async function installMearlBrowserExtension(options = {}, dependencies =
488
488
  const setupManagedAtStart = setupManagedBrowserIds.length > 0;
489
489
  let cdpSession;
490
490
  const inspectOnce = async () => {
491
- if (!useSharedCdpSession)
492
- return inspectInstallation(chromeUserDataDirectory);
491
+ if (!useSharedCdpSession) {
492
+ return options.force ? null : inspectInstallation(chromeUserDataDirectory);
493
+ }
493
494
  const candidate = await connectCdp(chromeUserDataDirectory);
494
495
  try {
495
- const installed = await candidate.inspectInstallation();
496
+ const installed = options.force ? null : await candidate.inspectInstallation();
496
497
  cdpSession?.close();
497
498
  cdpSession = candidate;
498
499
  return installed;
package/dist/cli.d.ts CHANGED
@@ -9,6 +9,7 @@ interface ParsedArgs {
9
9
  agentBayReadStdin: boolean;
10
10
  cloudMode?: 'websocket' | 'qoder';
11
11
  force?: boolean;
12
+ full?: boolean;
12
13
  help: boolean;
13
14
  operation?: MearlLifecycleOperation | 'agentbay' | 'extension';
14
15
  profiles?: MearlProfile[];
package/dist/cli.js CHANGED
@@ -1,132 +1,142 @@
1
1
  #!/usr/bin/env node
2
2
  import { createRequire } from 'node:module';
3
+ import { Command, CommanderError } from 'commander';
3
4
  import { configureAgentBayApiKey, promptAgentBayApiKey, readAgentBayApiKeyFile, readAgentBayApiKeyStdin, } from './agentBay.js';
4
5
  import { installMearlBrowserExtension } from './browserExtension.js';
5
6
  import { runMearlLifecycle } from './lifecycle.js';
6
7
  import { isMearlReleaseLine } from './version.js';
7
8
  const require = createRequire(import.meta.url);
8
9
  export const SETUP_VERSION = require('../package.json').version;
9
- export function parseSetupArgs(argv) {
10
- if (argv.includes('--help') || argv.includes('-h')) {
11
- return { agentBayReadStdin: false, help: true, skipSkill: false, yes: false };
10
+ const baseParsedArgs = () => ({
11
+ agentBayReadStdin: false,
12
+ help: false,
13
+ skipSkill: false,
14
+ yes: false,
15
+ });
16
+ function commanderError(error) {
17
+ const message = error.message.replace(/^error:\s*/i, '');
18
+ if (error.code === 'commander.unknownCommand') {
19
+ return new Error(message.replace(/^unknown command/i, 'Unknown operation'));
12
20
  }
13
- if (argv.includes('--version') || argv.includes('-v')) {
14
- return {
15
- agentBayReadStdin: false,
16
- help: false,
17
- skipSkill: false,
18
- version: true,
19
- yes: false,
20
- };
21
+ if (error.code === 'commander.unknownOption') {
22
+ return new Error(message.replace(/^unknown option/i, 'Unknown option'));
21
23
  }
22
- const firstArg = argv[0];
23
- const operation = firstArg === undefined || firstArg.startsWith('-') ? 'install' : firstArg;
24
- const optionStart = operation === firstArg ? 1 : 0;
25
- if (operation === 'agentbay') {
26
- if (argv.length === 1) {
27
- return {
28
- agentBayReadStdin: false,
29
- help: false,
24
+ return new Error(message);
25
+ }
26
+ export function parseSetupArgs(argv) {
27
+ let parsed;
28
+ const program = new Command()
29
+ .name('mearl-setup')
30
+ .version(SETUP_VERSION, '-v, --version')
31
+ .exitOverride()
32
+ .configureOutput({ writeErr: () => { }, writeOut: () => { } });
33
+ const addLifecycleCommand = (operation) => {
34
+ const command = new Command(operation)
35
+ .exitOverride()
36
+ .configureOutput({ writeErr: () => { }, writeOut: () => { } })
37
+ .option('--local')
38
+ .option('--cloud')
39
+ .option('--qoder-cloud')
40
+ .option('--mcp')
41
+ .option('--target-line <major.minor.x>')
42
+ .option('--skip-skill')
43
+ .option('-y, --yes')
44
+ .option('--non-interactive')
45
+ .action(() => {
46
+ const options = command.opts();
47
+ if (options.cloud && options.qoderCloud) {
48
+ throw new Error('--cloud and --qoder-cloud cannot be used together');
49
+ }
50
+ if (options.targetLine && !isMearlReleaseLine(options.targetLine)) {
51
+ throw new Error('--target-line requires a valid Mearl release line such as 2.3.x');
52
+ }
53
+ if (operation === 'uninstall' && options.targetLine) {
54
+ throw new Error('--target-line is not available for uninstall');
55
+ }
56
+ const profiles = [];
57
+ if (options.local)
58
+ profiles.push('local');
59
+ if (options.cloud || options.qoderCloud)
60
+ profiles.push('cloud');
61
+ if (options.mcp)
62
+ profiles.push('mcp');
63
+ parsed = {
64
+ ...baseParsedArgs(),
30
65
  operation,
31
- skipSkill: false,
32
- yes: false,
66
+ ...(options.cloud || options.qoderCloud
67
+ ? { cloudMode: options.qoderCloud ? 'qoder' : 'websocket' }
68
+ : {}),
69
+ ...(profiles.length > 0 ? { profiles } : {}),
70
+ skipSkill: options.skipSkill === true,
71
+ targetLine: options.targetLine,
72
+ yes: options.yes === true || options.nonInteractive === true,
33
73
  };
74
+ });
75
+ program.addCommand(command);
76
+ };
77
+ addLifecycleCommand('install');
78
+ addLifecycleCommand('update');
79
+ addLifecycleCommand('uninstall');
80
+ const extension = new Command('extension')
81
+ .exitOverride()
82
+ .configureOutput({ writeErr: () => { }, writeOut: () => { } })
83
+ .option('--full')
84
+ .option('--force')
85
+ .option('-y, --yes')
86
+ .option('--non-interactive')
87
+ .action(() => {
88
+ const options = extension.opts();
89
+ parsed = {
90
+ ...baseParsedArgs(),
91
+ ...(options.force ? { force: true } : {}),
92
+ ...(options.full ? { full: true } : {}),
93
+ operation: 'extension',
94
+ yes: options.yes === true || options.nonInteractive === true,
95
+ };
96
+ });
97
+ program.addCommand(extension);
98
+ const agentbay = new Command('agentbay')
99
+ .exitOverride()
100
+ .configureOutput({ writeErr: () => { }, writeOut: () => { } })
101
+ .argument('[api-key]')
102
+ .option('--stdin')
103
+ .option('--key-file <path>')
104
+ .action((apiKey) => {
105
+ const options = agentbay.opts();
106
+ if (apiKey) {
107
+ throw new Error('API keys are not accepted as command-line arguments');
34
108
  }
35
- if (argv.length === 2 && argv[1] === '--stdin') {
36
- return {
37
- agentBayReadStdin: true,
38
- help: false,
39
- operation,
40
- skipSkill: false,
41
- yes: false,
42
- };
109
+ if (options.stdin && options.keyFile) {
110
+ throw new Error('--stdin and --key-file cannot be used together');
43
111
  }
44
- if (argv.length === 3 && argv[1] === '--key-file' && argv[2]?.trim()) {
45
- return {
46
- agentBayKeyFile: argv[2],
47
- agentBayReadStdin: false,
48
- help: false,
49
- operation,
50
- skipSkill: false,
51
- yes: false,
52
- };
112
+ if (options.keyFile !== undefined && !options.keyFile.trim()) {
113
+ throw new Error('--key-file requires a non-empty path');
53
114
  }
54
- throw new Error('Usage: npx @mearl/setup agentbay [--stdin | --key-file <path>]. API keys are not accepted as command-line arguments.');
55
- }
56
- if (operation === 'extension') {
57
- const accepted = new Set(['--force', '--yes', '-y', '--non-interactive']);
58
- const confirmationFlags = new Set(['--yes', '-y', '--non-interactive']);
59
- const unknown = argv.slice(1).find(arg => !accepted.has(arg));
60
- if (unknown)
61
- throw new Error(`Unknown option: ${unknown}`);
62
- return {
63
- agentBayReadStdin: false,
64
- help: false,
65
- ...(argv.slice(1).includes('--force') ? { force: true } : {}),
66
- operation,
67
- skipSkill: false,
68
- yes: argv.slice(1).some(arg => confirmationFlags.has(arg)),
115
+ parsed = {
116
+ ...baseParsedArgs(),
117
+ ...(options.keyFile ? { agentBayKeyFile: options.keyFile } : {}),
118
+ agentBayReadStdin: options.stdin === true,
119
+ operation: 'agentbay',
69
120
  };
121
+ });
122
+ program.addCommand(agentbay);
123
+ const normalizedArgv = argv[0] === undefined || argv[0].startsWith('-') ? ['install', ...argv] : argv;
124
+ try {
125
+ program.parse(['node', 'mearl-setup', ...normalizedArgv]);
70
126
  }
71
- if (operation !== 'install' && operation !== 'update' && operation !== 'uninstall') {
72
- throw new Error(`Unknown operation: ${operation}`);
73
- }
74
- const profiles = [];
75
- let skipSkill = false;
76
- let targetLine;
77
- let yes = false;
78
- let cloudMode;
79
- for (let index = optionStart; index < argv.length; index += 1) {
80
- const arg = argv[index];
81
- if (arg === '--yes' || arg === '-y' || arg === '--non-interactive') {
82
- yes = true;
83
- continue;
84
- }
85
- if (arg === '--skip-skill') {
86
- skipSkill = true;
87
- continue;
88
- }
89
- if (arg === '--target-line') {
90
- const value = argv[index + 1];
91
- if (!isMearlReleaseLine(value)) {
92
- throw new Error('--target-line requires a valid Mearl release line such as 2.3.x');
93
- }
94
- targetLine = value;
95
- index += 1;
96
- continue;
97
- }
98
- if (arg === '--cloud' || arg === '--qoder-cloud') {
99
- const requestedMode = arg === '--qoder-cloud' ? 'qoder' : 'websocket';
100
- if (cloudMode && cloudMode !== requestedMode) {
101
- throw new Error('--cloud and --qoder-cloud cannot be used together');
102
- }
103
- cloudMode = requestedMode;
127
+ catch (error) {
128
+ if (error instanceof CommanderError) {
129
+ if (error.code === 'commander.helpDisplayed')
130
+ return { ...baseParsedArgs(), help: true };
131
+ if (error.code === 'commander.version')
132
+ return { ...baseParsedArgs(), version: true };
133
+ throw commanderError(error);
104
134
  }
105
- const candidate = arg === '--local'
106
- ? 'local'
107
- : arg === '--cloud' || arg === '--qoder-cloud'
108
- ? 'cloud'
109
- : arg === '--mcp'
110
- ? 'mcp'
111
- : undefined;
112
- if (!candidate)
113
- throw new Error(`Unknown option: ${arg}`);
114
- if (!profiles.includes(candidate))
115
- profiles.push(candidate);
135
+ throw error;
116
136
  }
117
- if (operation === 'uninstall' && targetLine) {
118
- throw new Error('--target-line is not available for uninstall');
119
- }
120
- return {
121
- agentBayReadStdin: false,
122
- help: false,
123
- operation,
124
- ...(cloudMode ? { cloudMode } : {}),
125
- ...(profiles.length > 0 ? { profiles } : {}),
126
- skipSkill,
127
- targetLine,
128
- yes,
129
- };
137
+ if (!parsed)
138
+ throw new Error('No setup operation was selected');
139
+ return parsed;
130
140
  }
131
141
  function printHelp(toError = false) {
132
142
  const output = `Mearl Setup v${SETUP_VERSION}
@@ -134,14 +144,14 @@ function printHelp(toError = false) {
134
144
  Usage:
135
145
  npx @mearl/setup [install] [--local] [--cloud|--qoder-cloud] [--mcp] [--yes]
136
146
  npx @mearl/setup <update|uninstall> [--local] [--cloud|--qoder-cloud] [--mcp] [--yes]
137
- npx @mearl/setup extension [--force] [--yes]
147
+ npx @mearl/setup extension [--full] [--force] [--yes]
138
148
  npx @mearl/setup agentbay [--stdin | --key-file <path>]
139
149
 
140
150
  Behavior:
141
151
  install Default command; defaults to --local when no environment is specified
142
152
  update Auto-detects installed environments from the current PATH
143
153
  uninstall Auto-detects installed environments and asks for confirmation
144
- extension Download the latest browser extension and install or reload it through CDP
154
+ extension Install or update the browser extension; --full also installs the local environment
145
155
  agentbay Save or update AGENTBAY_KEY without changing other Mearl settings
146
156
 
147
157
  Options:
@@ -155,6 +165,7 @@ Options:
155
165
  --target-line <major.minor.x>
156
166
  Install the latest packages from one release line, such as 2.3.x
157
167
  --skip-skill Keep the independently managed Mearl Skill unchanged
168
+ --full Also install @mearl/client, @mearl/native-host, and the Mearl Skill
158
169
  --force Replace an existing store, managed, or other local Mearl extension
159
170
  --yes, -y Run without prompts and accept detected defaults
160
171
  --non-interactive
@@ -171,6 +182,7 @@ Examples:
171
182
  npx @mearl/setup --local --cloud
172
183
  npx @mearl/setup update
173
184
  npx @mearl/setup extension
185
+ npx @mearl/setup extension --full
174
186
  npx @mearl/setup agentbay
175
187
  npx @mearl/setup --qoder-cloud --yes
176
188
  npx @mearl/setup update --local --target-line 2.3.x --skip-skill
@@ -228,6 +240,7 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
228
240
  console.error('Non-interactive input detected. Re-run with --yes or --non-interactive.');
229
241
  return 2;
230
242
  }
243
+ let extensionInstalled = false;
231
244
  try {
232
245
  const result = await (dependencies.installBrowserExtension ?? installMearlBrowserExtension)({
233
246
  force: parsed.force,
@@ -240,20 +253,35 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
240
253
  : `Browser extension v${result.version} installed or reloaded through CDP.`);
241
254
  console.error(`Extension ID: ${result.extensionId}`);
242
255
  console.error(`Extension directory: ${result.path}`);
243
- return 0;
256
+ extensionInstalled = true;
257
+ }
258
+ else {
259
+ console.error(result.filesUpdated
260
+ ? `Browser extension v${result.version} files were updated, but the extension was not reloaded.`
261
+ : `Browser extension v${result.version} was not installed.`);
262
+ if (result.cdpError)
263
+ console.error(`CDP installation pending: ${result.cdpError}`);
264
+ console.error('Open chrome://inspect/#remote-debugging, enable remote debugging, approve the connection, then rerun: npx @mearl/setup extension');
244
265
  }
245
- console.error(result.filesUpdated
246
- ? `Browser extension v${result.version} files were updated, but the extension was not reloaded.`
247
- : `Browser extension v${result.version} was not installed.`);
248
- if (result.cdpError)
249
- console.error(`CDP installation pending: ${result.cdpError}`);
250
- console.error('Open chrome://inspect/#remote-debugging, enable remote debugging, approve the connection, then rerun: npx @mearl/setup extension');
251
- return 1;
252
266
  }
253
267
  catch (error) {
254
268
  console.error(error instanceof Error ? error.message : String(error));
255
- return 1;
256
269
  }
270
+ if (!parsed.full)
271
+ return extensionInstalled ? 0 : 1;
272
+ let localInstalled = false;
273
+ try {
274
+ localInstalled = await (dependencies.runLifecycle ?? runMearlLifecycle)({
275
+ operation: 'install',
276
+ profiles: ['local'],
277
+ skipSkill: false,
278
+ yes: parsed.yes,
279
+ });
280
+ }
281
+ catch (error) {
282
+ console.error(error instanceof Error ? error.message : String(error));
283
+ }
284
+ return extensionInstalled && localInstalled ? 0 : 1;
257
285
  }
258
286
  if (!interactiveTerminal && !parsed.yes && parsed.operation !== 'update') {
259
287
  console.error('Non-interactive input detected. Re-run with --yes or --non-interactive.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/setup",
3
- "version": "2.8.2",
3
+ "version": "2.9.1",
4
4
  "description": "One-shot environment setup and configuration for Mearl",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -56,15 +56,16 @@
56
56
  ],
57
57
  "license": "ISC",
58
58
  "engines": {
59
- "node": ">=18"
59
+ "node": ">=22.12.0"
60
60
  },
61
61
  "publishConfig": {
62
62
  "registry": "https://registry.npmjs.org"
63
63
  },
64
64
  "dependencies": {
65
+ "commander": "^15.0.0",
65
66
  "extract-zip": "^2.0.1",
66
67
  "ws": "^8.18.3",
67
- "@mearl/client": "2.8.2"
68
+ "@mearl/client": "2.9.1"
68
69
  },
69
70
  "devDependencies": {
70
71
  "@types/node": "^24.9.1",