@mearl/setup 2.14.0 → 2.15.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 +16 -18
- package/dist/cli.d.ts +4 -9
- package/dist/cli.js +15 -58
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -1
- package/dist/internalPlugins.d.ts +21 -0
- package/dist/internalPlugins.js +97 -0
- package/dist/lifecycle.d.ts +3 -0
- package/dist/lifecycle.js +39 -2
- package/dist/plugin.d.ts +4 -1
- package/dist/plugin.js +19 -3
- package/dist/provider.js +6 -4
- package/package.json +3 -7
- package/dist/agentBay.d.ts +0 -23
- package/dist/agentBay.js +0 -114
package/README.md
CHANGED
|
@@ -12,24 +12,17 @@ npx @mearl/setup plugin uninstall <name>
|
|
|
12
12
|
npx @mearl/setup provider <install|update|uninstall> <package|id>
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
npx @mearl/setup agentbay
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
无交互环境可使用 `--stdin` 或 `--key-file <path>`。为避免 Key 进入 shell history 和
|
|
23
|
-
进程参数,不接受把 Key 直接写在命令行中。
|
|
15
|
+
浏览器 Provider 独立安装和演进。安装后运行 `mearl <providerId>` 查看该版本声明的能力、
|
|
16
|
+
配置和启动参数。
|
|
24
17
|
|
|
25
18
|
在 Qoder Cloud Agent 环境中,使用 `npx @mearl/setup --qoder-cloud --yes` 选择 Qoder
|
|
26
19
|
配对模式。它与 `--cloud` 安装同一套 cloud-server、client 和 Skill,只把 cloud-server
|
|
27
20
|
模式记录为 Qoder;`mearl-cloud-server` 的命令交互保持不变,`start` 和 `restart` 输出新的
|
|
28
21
|
配对指令后退出,不监听端口或保留守护进程。配对 URL 不包含 Qoder PAT。
|
|
29
22
|
|
|
30
|
-
## 安装与注册 Skill
|
|
23
|
+
## 安装与注册 Skill 插件
|
|
31
24
|
|
|
32
|
-
一个命令安装 skill 并注册它的 CLI 能力:
|
|
25
|
+
一个命令安装 skill 并注册它的 CLI、SDK 和 MCP 能力:
|
|
33
26
|
|
|
34
27
|
```bash
|
|
35
28
|
npx @mearl/setup plugin install trip/mearl --skill yuque-doc-fetch --yes
|
|
@@ -41,9 +34,16 @@ mearl yuque read "https://aliyuque.antfin.com/group/book/doc"
|
|
|
41
34
|
setup 从 `Installed … skill(s)` 成功区块提取路径,仅注册本次安装且包含 `scripts/main.mjs` 的 skill。
|
|
42
35
|
普通 skill 正常安装。输出格式无法识别时提示手动注册,不扫描其他 skill 或猜测安装结果。
|
|
43
36
|
|
|
37
|
+
默认 `install` 和 `update` 完成后,setup 会用最多 5 秒的 `npm ping` 检查阿里内网 registry。
|
|
38
|
+
可访问时通过上述 Ali Skills 链路依次自动安装并注册 `buc-request` 与 `tdbank-account`;该 registry 不用于下载插件。
|
|
39
|
+
探测不可用或插件安装失败不会破坏 Mearl 主环境的安装;`--skip-skill` 和
|
|
40
|
+
`--target-line` 安装不执行自动插件步骤。
|
|
41
|
+
|
|
42
|
+
Mearl client 内置官方插件名称与安装来源目录。CLI、SDK、MCP 或另一个插件首次调用尚未注册的官方插件时,会通过内部幂等流程安装并重试一次;未知名称不会触发安装。
|
|
43
|
+
|
|
44
44
|
自动注册默认使用安装目录名;入口可用 `export const name = 'yuque'` 指定简短命令名。
|
|
45
45
|
重装会刷新同名绑定。每个入口必须默认导出 `register(app)`;完整协议见
|
|
46
|
-
[
|
|
46
|
+
[Skill 插件规范](../skills/mearl-skill-creator/references/cli-plugin.md)。
|
|
47
47
|
|
|
48
48
|
已通过其他渠道分发的 skill 或自定义入口,可以直接传入路径注册:
|
|
49
49
|
|
|
@@ -57,7 +57,7 @@ path 接受目录或具体文件;目录默认读取 `scripts/main.mjs`,相
|
|
|
57
57
|
两种注册流程都会检查本机 `@mearl/client` 和 `@mearl/native-host`:缺失或低于当前 setup 版本时,
|
|
58
58
|
复用已有包管理器安装 setup 的对应版本,更新后再次验证;更高版本保持不动。
|
|
59
59
|
native-host 会幂等初始化 Skills 模式。基础环境补齐或初始化失败时保留已安装的 skill,并停止注册。
|
|
60
|
-
注册后可在任意目录运行 `mearl <name> --help
|
|
60
|
+
注册后可在任意目录运行 `mearl <name> --help`,也可通过 `@mearl/client` 的 `invokePlugin` 或 MCP 的 `mearl_plugin` 调用。已注册插件的日常调用不执行安装或版本检查;只有缺失的官方插件会触发一次按需安装。
|
|
61
61
|
|
|
62
62
|
不再需要插件时执行:
|
|
63
63
|
|
|
@@ -211,11 +211,9 @@ npx @mearl/setup uninstall --local
|
|
|
211
211
|
# 自动检测并无交互卸载
|
|
212
212
|
npx @mearl/setup uninstall --yes
|
|
213
213
|
|
|
214
|
-
#
|
|
215
|
-
npx @mearl/setup
|
|
216
|
-
|
|
217
|
-
# 无交互配置(从权限受控的文件读取)
|
|
218
|
-
npx @mearl/setup agentbay --key-file ./agentbay.key
|
|
214
|
+
# 安装可选浏览器 Provider
|
|
215
|
+
npx @mearl/setup provider install @ali/mearl-provider-brow
|
|
216
|
+
npx @mearl/setup provider install @ali/mearl-provider-agentbay
|
|
219
217
|
```
|
|
220
218
|
|
|
221
219
|
原有 Mearl 环境包的 npm/npx 安装方式和 CLI 保持可用;setup 是统一的便利入口。
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { configureAgentBayApiKey, promptAgentBayApiKey, readAgentBayApiKeyFile, readAgentBayApiKeyStdin } from './agentBay.js';
|
|
3
2
|
import { installMearlBrowserExtension } from './browserExtension.js';
|
|
4
3
|
import { runMearlLifecycle, type MearlLifecycleOperation } from './lifecycle.js';
|
|
5
4
|
import type { MearlProfile } from './profiles.js';
|
|
6
|
-
import { installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
|
|
5
|
+
import { ensureOfficialPlugin, installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
|
|
7
6
|
import { installProviderPackage, uninstallProviderPackage, updateProviderPackage } from './provider.js';
|
|
8
7
|
export declare const SETUP_VERSION: string;
|
|
9
8
|
interface ParsedArgs {
|
|
10
|
-
agentBayKeyFile?: string;
|
|
11
|
-
agentBayReadStdin: boolean;
|
|
12
9
|
cloudMode?: 'websocket' | 'qoder';
|
|
13
10
|
force?: boolean;
|
|
14
11
|
full?: boolean;
|
|
15
12
|
help: boolean;
|
|
16
|
-
operation?: MearlLifecycleOperation | '
|
|
13
|
+
operation?: MearlLifecycleOperation | 'extension' | 'plugin' | 'provider';
|
|
17
14
|
pluginName?: string;
|
|
18
15
|
pluginPath?: string;
|
|
19
16
|
pluginInstallArgs?: string[];
|
|
17
|
+
pluginEnsureName?: string;
|
|
20
18
|
pluginUninstallName?: string;
|
|
21
19
|
providerCommand?: 'install' | 'uninstall' | 'update';
|
|
22
20
|
providerTarget?: string;
|
|
@@ -28,12 +26,9 @@ interface ParsedArgs {
|
|
|
28
26
|
}
|
|
29
27
|
export interface SetupCliDependencies {
|
|
30
28
|
registerPlugin?: typeof setupPlugin;
|
|
29
|
+
ensureOfficialPlugin?: typeof ensureOfficialPlugin;
|
|
31
30
|
installPlugins?: typeof installPlugins;
|
|
32
31
|
uninstallPlugin?: typeof uninstallPlugin;
|
|
33
|
-
configureAgentBay?: typeof configureAgentBayApiKey;
|
|
34
|
-
promptAgentBayKey?: typeof promptAgentBayApiKey;
|
|
35
|
-
readAgentBayKeyFile?: typeof readAgentBayApiKeyFile;
|
|
36
|
-
readAgentBayKeyStdin?: typeof readAgentBayApiKeyStdin;
|
|
37
32
|
installBrowserExtension?: typeof installMearlBrowserExtension;
|
|
38
33
|
installProvider?: typeof installProviderPackage;
|
|
39
34
|
runLifecycle?: typeof runMearlLifecycle;
|
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
3
|
import { Command, CommanderError } from 'commander';
|
|
4
|
-
import { configureAgentBayApiKey, promptAgentBayApiKey, readAgentBayApiKeyFile, readAgentBayApiKeyStdin, } from './agentBay.js';
|
|
5
4
|
import { installMearlBrowserExtension } from './browserExtension.js';
|
|
6
5
|
import { runMearlLifecycle } from './lifecycle.js';
|
|
7
6
|
import { isMearlReleaseLine } from './version.js';
|
|
8
|
-
import { installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
|
|
7
|
+
import { ensureOfficialPlugin, installPlugins, setupPlugin, uninstallPlugin } from './plugin.js';
|
|
9
8
|
import { installProviderPackage, uninstallProviderPackage, updateProviderPackage, } from './provider.js';
|
|
10
9
|
const require = createRequire(import.meta.url);
|
|
11
10
|
export const SETUP_VERSION = require('../package.json').version;
|
|
12
11
|
const baseParsedArgs = () => ({
|
|
13
|
-
agentBayReadStdin: false,
|
|
14
12
|
help: false,
|
|
15
13
|
skipSkill: false,
|
|
16
14
|
yes: false,
|
|
@@ -43,6 +41,12 @@ export function parseSetupArgs(argv) {
|
|
|
43
41
|
if (argv[0] === 'plugin' && argv[1] === 'install') {
|
|
44
42
|
return { ...baseParsedArgs(), operation: 'plugin', pluginInstallArgs: argv.slice(2) };
|
|
45
43
|
}
|
|
44
|
+
if (argv[0] === 'plugin' && argv[1] === 'ensure') {
|
|
45
|
+
if (argv.length !== 3 || !argv[2]) {
|
|
46
|
+
throw new Error('plugin ensure requires exactly one official plugin name');
|
|
47
|
+
}
|
|
48
|
+
return { ...baseParsedArgs(), operation: 'plugin', pluginEnsureName: argv[2] };
|
|
49
|
+
}
|
|
46
50
|
if (argv[0] === 'plugin' && argv[1] === 'uninstall') {
|
|
47
51
|
if (argv.length !== 3 || !argv[2]) {
|
|
48
52
|
throw new Error('plugin uninstall requires exactly one plugin name');
|
|
@@ -128,31 +132,6 @@ export function parseSetupArgs(argv) {
|
|
|
128
132
|
};
|
|
129
133
|
});
|
|
130
134
|
program.addCommand(extension);
|
|
131
|
-
const agentbay = new Command('agentbay')
|
|
132
|
-
.exitOverride()
|
|
133
|
-
.configureOutput({ writeErr: () => { }, writeOut: () => { } })
|
|
134
|
-
.argument('[api-key]')
|
|
135
|
-
.option('--stdin')
|
|
136
|
-
.option('--key-file <path>')
|
|
137
|
-
.action((apiKey) => {
|
|
138
|
-
const options = agentbay.opts();
|
|
139
|
-
if (apiKey) {
|
|
140
|
-
throw new Error('API keys are not accepted as command-line arguments');
|
|
141
|
-
}
|
|
142
|
-
if (options.stdin && options.keyFile) {
|
|
143
|
-
throw new Error('--stdin and --key-file cannot be used together');
|
|
144
|
-
}
|
|
145
|
-
if (options.keyFile !== undefined && !options.keyFile.trim()) {
|
|
146
|
-
throw new Error('--key-file requires a non-empty path');
|
|
147
|
-
}
|
|
148
|
-
parsed = {
|
|
149
|
-
...baseParsedArgs(),
|
|
150
|
-
...(options.keyFile ? { agentBayKeyFile: options.keyFile } : {}),
|
|
151
|
-
agentBayReadStdin: options.stdin === true,
|
|
152
|
-
operation: 'agentbay',
|
|
153
|
-
};
|
|
154
|
-
});
|
|
155
|
-
program.addCommand(agentbay);
|
|
156
135
|
const normalizedArgv = argv[0] === undefined || argv[0].startsWith('-') ? ['install', ...argv] : argv;
|
|
157
136
|
try {
|
|
158
137
|
program.parse(['node', 'mearl-setup', ...normalizedArgv]);
|
|
@@ -182,14 +161,12 @@ Usage:
|
|
|
182
161
|
npx @mearl/setup plugin install <ali-skills add arguments...>
|
|
183
162
|
npx @mearl/setup plugin uninstall <name>
|
|
184
163
|
npx @mearl/setup provider <install|update|uninstall> <package|id>
|
|
185
|
-
npx @mearl/setup agentbay [--stdin | --key-file <path>]
|
|
186
164
|
|
|
187
165
|
Behavior:
|
|
188
166
|
install Default command; defaults to --local when no environment is specified
|
|
189
167
|
update Auto-detects installed environments from the current PATH
|
|
190
168
|
uninstall Auto-detects installed environments and asks for confirmation
|
|
191
169
|
extension Install or update the browser extension; --full also installs the local environment
|
|
192
|
-
agentbay Save or update AGENTBAY_KEY without changing other Mearl settings
|
|
193
170
|
plugin Register, install, or uninstall a CLI plugin
|
|
194
171
|
provider Install npm Provider packages and manage their Mearl registration
|
|
195
172
|
|
|
@@ -199,8 +176,6 @@ Options:
|
|
|
199
176
|
--qoder-cloud
|
|
200
177
|
Manage the same cloud packages in Qoder pairing mode
|
|
201
178
|
--mcp Manage @mearl/mcp-server and its configuration
|
|
202
|
-
--stdin Read the AgentBay API key from standard input
|
|
203
|
-
--key-file Read the AgentBay API key from a local file
|
|
204
179
|
--target-line <major.minor.x>
|
|
205
180
|
Install the latest packages from one release line, such as 2.3.x
|
|
206
181
|
--skip-skill Keep the independently managed Mearl Skill unchanged
|
|
@@ -224,7 +199,7 @@ Examples:
|
|
|
224
199
|
npx @mearl/setup extension --full
|
|
225
200
|
npx @mearl/setup plugin uninstall yuque
|
|
226
201
|
npx @mearl/setup provider install @ali/mearl-provider-brow
|
|
227
|
-
npx @mearl/setup agentbay
|
|
202
|
+
npx @mearl/setup provider install @ali/mearl-provider-agentbay
|
|
228
203
|
npx @mearl/setup --qoder-cloud --yes
|
|
229
204
|
npx @mearl/setup update --local --target-line 2.3.x --skip-skill
|
|
230
205
|
npx @mearl/setup uninstall --local`;
|
|
@@ -256,6 +231,13 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
|
|
|
256
231
|
if (parsed.pluginInstallArgs) {
|
|
257
232
|
return await (dependencies.installPlugins ?? installPlugins)(parsed.pluginInstallArgs);
|
|
258
233
|
}
|
|
234
|
+
if (parsed.pluginEnsureName) {
|
|
235
|
+
const result = await (dependencies.ensureOfficialPlugin ?? ensureOfficialPlugin)(parsed.pluginEnsureName);
|
|
236
|
+
console.error(result === 'installed'
|
|
237
|
+
? `Official plugin ${parsed.pluginEnsureName} installed.`
|
|
238
|
+
: `Official plugin ${parsed.pluginEnsureName} is already registered.`);
|
|
239
|
+
return 0;
|
|
240
|
+
}
|
|
259
241
|
if (parsed.pluginUninstallName) {
|
|
260
242
|
const result = await (dependencies.uninstallPlugin ?? uninstallPlugin)(parsed.pluginUninstallName);
|
|
261
243
|
if (!result.removed) {
|
|
@@ -307,31 +289,6 @@ export async function main(argv = process.argv.slice(2), interactiveTerminal = p
|
|
|
307
289
|
return 1;
|
|
308
290
|
}
|
|
309
291
|
}
|
|
310
|
-
if (parsed.operation === 'agentbay') {
|
|
311
|
-
try {
|
|
312
|
-
const configureAgentBay = dependencies.configureAgentBay ?? configureAgentBayApiKey;
|
|
313
|
-
let apiKey;
|
|
314
|
-
if (parsed.agentBayKeyFile) {
|
|
315
|
-
apiKey = (dependencies.readAgentBayKeyFile ?? readAgentBayApiKeyFile)(parsed.agentBayKeyFile);
|
|
316
|
-
}
|
|
317
|
-
else if (parsed.agentBayReadStdin) {
|
|
318
|
-
apiKey = await (dependencies.readAgentBayKeyStdin ?? readAgentBayApiKeyStdin)();
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
if (!interactiveTerminal) {
|
|
322
|
-
throw new Error('Interactive input is unavailable. Re-run with --stdin or --key-file <path>.');
|
|
323
|
-
}
|
|
324
|
-
apiKey = await (dependencies.promptAgentBayKey ?? promptAgentBayApiKey)();
|
|
325
|
-
}
|
|
326
|
-
const result = configureAgentBay(apiKey);
|
|
327
|
-
console.error(`AgentBay API key saved to ${result.envFile}.`);
|
|
328
|
-
return 0;
|
|
329
|
-
}
|
|
330
|
-
catch (error) {
|
|
331
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
332
|
-
return 1;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
292
|
if (parsed.operation === 'extension') {
|
|
336
293
|
if (!interactiveTerminal && !parsed.yes) {
|
|
337
294
|
console.error('Non-interactive input detected. Re-run with --yes or --non-interactive.');
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
export { buildGlobalPackageCommand, detectGlobalPackage, resolveInvokingPackageManager, resolvePackageManagerInstallation, } from './packageManager.js';
|
|
2
|
-
export { configureAgentBayApiKey, promptAgentBayApiKey, readAgentBayApiKeyFile, readAgentBayApiKeyStdin, } from './agentBay.js';
|
|
3
2
|
export { BrowserExtensionConflictError, extensionArchiveUrl, fetchBrowserExtensionRelease, findLatestExtensionVersion, installMearlBrowserExtension, inspectMearlExtensionViaCdp, loadUnpackedViaCdp, MEARL_EXTENSION_DIRECTORY_NAME, MEARL_EXTENSION_DOWNLOAD_PAGE, MEARL_EXTENSION_ID, MEARL_EXTENSION_REMOTE_CONFIG_URL, resolveChromeUserDataDirectory, resolveMearlExtensionDirectory, updateSetupManagedBrowserExtensionFiles, } from './browserExtension.js';
|
|
4
3
|
export type { BrowserExtensionDependencies, BrowserExtensionInstallOptions, BrowserExtensionInstallResult, BrowserExtensionRelease, InstalledBrowserExtension, SetupManagedBrowserExtensionUpdateOptions, SetupManagedBrowserExtensionUpdateResult, } from './browserExtension.js';
|
|
5
|
-
export type { AgentBayConfigurationOptions, AgentBayConfigurationResult, AgentBaySecretInput, AgentBaySecretOutput, } from './agentBay.js';
|
|
6
4
|
export type { DetectedGlobalPackage, GlobalPackageCommand, GlobalPackageManager, PackageManagerInstallation, PackageOperation, PackageDetectionOptions, } from './packageManager.js';
|
|
7
5
|
export { runMearlLifecycle } from './lifecycle.js';
|
|
8
6
|
export type { MearlLifecycleDependencies, MearlLifecycleOperation, MearlLifecycleOptions, } from './lifecycle.js';
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
export { buildGlobalPackageCommand, detectGlobalPackage, resolveInvokingPackageManager, resolvePackageManagerInstallation, } from './packageManager.js';
|
|
2
|
-
export { configureAgentBayApiKey, promptAgentBayApiKey, readAgentBayApiKeyFile, readAgentBayApiKeyStdin, } from './agentBay.js';
|
|
3
2
|
export { BrowserExtensionConflictError, extensionArchiveUrl, fetchBrowserExtensionRelease, findLatestExtensionVersion, installMearlBrowserExtension, inspectMearlExtensionViaCdp, loadUnpackedViaCdp, MEARL_EXTENSION_DIRECTORY_NAME, MEARL_EXTENSION_DOWNLOAD_PAGE, MEARL_EXTENSION_ID, MEARL_EXTENSION_REMOTE_CONFIG_URL, resolveChromeUserDataDirectory, resolveMearlExtensionDirectory, updateSetupManagedBrowserExtensionFiles, } from './browserExtension.js';
|
|
4
3
|
export { runMearlLifecycle } from './lifecycle.js';
|
|
5
4
|
export { MEARL_PROFILES, detectMearlProfiles } from './profiles.js';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { findPluginRegistration } from '@mearl/client/plugin';
|
|
2
|
+
import { installPlugins, uninstallPlugin } from './plugin.js';
|
|
3
|
+
import { findExecutable, type CommandRunner } from './process.js';
|
|
4
|
+
export declare const ALIBABA_NPM_REGISTRY = "https://registry.anpm.alibaba-inc.com/";
|
|
5
|
+
export declare const ALIBABA_INTERNAL_PLUGIN_NAMES: string[];
|
|
6
|
+
export interface InternalPluginOptions {
|
|
7
|
+
env?: NodeJS.ProcessEnv;
|
|
8
|
+
runCommand?: CommandRunner;
|
|
9
|
+
writeLine?: (line: string) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface InternalPluginDependencies {
|
|
12
|
+
findExecutable?: typeof findExecutable;
|
|
13
|
+
findPluginRegistration?: typeof findPluginRegistration;
|
|
14
|
+
installPlugins?: typeof installPlugins;
|
|
15
|
+
probeAlibabaRegistry?: (options: InternalPluginOptions) => Promise<boolean>;
|
|
16
|
+
uninstallPlugin?: typeof uninstallPlugin;
|
|
17
|
+
}
|
|
18
|
+
export declare function canAccessAlibabaNpmRegistry(options?: InternalPluginOptions, dependencies?: Pick<InternalPluginDependencies, 'findExecutable'>): Promise<boolean>;
|
|
19
|
+
/** Uses the internal registry only as an intranet probe; Ali Skills installs the plugins. */
|
|
20
|
+
export declare function installAlibabaInternalPlugins(options?: InternalPluginOptions, dependencies?: InternalPluginDependencies): Promise<'installed' | 'skipped' | 'failed'>;
|
|
21
|
+
export declare function uninstallAlibabaInternalPlugins(options?: InternalPluginOptions, dependencies?: InternalPluginDependencies): Promise<'uninstalled' | 'skipped' | 'failed'>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
|
+
import { findOfficialPlugin, findPluginRegistration, listOfficialPluginNames, OFFICIAL_PLUGIN_MANAGER, } from '@mearl/client/plugin';
|
|
3
|
+
import { installPlugins, uninstallPlugin } from './plugin.js';
|
|
4
|
+
import { findExecutable, prependCommandPath, runCommand } from './process.js';
|
|
5
|
+
export const ALIBABA_NPM_REGISTRY = 'https://registry.anpm.alibaba-inc.com/';
|
|
6
|
+
export const ALIBABA_INTERNAL_PLUGIN_NAMES = listOfficialPluginNames('alibaba');
|
|
7
|
+
export async function canAccessAlibabaNpmRegistry(options = {}, dependencies = {}) {
|
|
8
|
+
const env = options.env ?? process.env;
|
|
9
|
+
const runner = options.runCommand ?? runCommand;
|
|
10
|
+
const npm = (dependencies.findExecutable ?? findExecutable)('npm', {
|
|
11
|
+
env,
|
|
12
|
+
extraDirectories: [dirname(process.execPath)],
|
|
13
|
+
});
|
|
14
|
+
if (!npm)
|
|
15
|
+
return false;
|
|
16
|
+
try {
|
|
17
|
+
const result = await runner(npm, [
|
|
18
|
+
'ping',
|
|
19
|
+
`--registry=${ALIBABA_NPM_REGISTRY}`,
|
|
20
|
+
'--fetch-retries=0',
|
|
21
|
+
'--fetch-timeout=3000',
|
|
22
|
+
'--loglevel=silent',
|
|
23
|
+
], {
|
|
24
|
+
capture: true,
|
|
25
|
+
env: prependCommandPath(npm, env),
|
|
26
|
+
timeoutMs: 5_000,
|
|
27
|
+
});
|
|
28
|
+
return result.code === 0;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Uses the internal registry only as an intranet probe; Ali Skills installs the plugins. */
|
|
35
|
+
export async function installAlibabaInternalPlugins(options = {}, dependencies = {}) {
|
|
36
|
+
const writeLine = options.writeLine ?? (line => console.error(line));
|
|
37
|
+
const registrationFor = dependencies.findPluginRegistration ?? findPluginRegistration;
|
|
38
|
+
const candidates = ALIBABA_INTERNAL_PLUGIN_NAMES.filter(name => {
|
|
39
|
+
const registration = registrationFor(name);
|
|
40
|
+
if (registration && registration.managedBy !== OFFICIAL_PLUGIN_MANAGER) {
|
|
41
|
+
writeLine(`${name} plugin: existing user registration kept.`);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
});
|
|
46
|
+
if (candidates.length === 0)
|
|
47
|
+
return 'skipped';
|
|
48
|
+
const probe = dependencies.probeAlibabaRegistry ?? (input => canAccessAlibabaNpmRegistry(input));
|
|
49
|
+
if (!(await probe(options)))
|
|
50
|
+
return 'skipped';
|
|
51
|
+
let installed = false;
|
|
52
|
+
let failed = false;
|
|
53
|
+
for (const name of candidates) {
|
|
54
|
+
const definition = findOfficialPlugin(name);
|
|
55
|
+
try {
|
|
56
|
+
const code = await (dependencies.installPlugins ?? installPlugins)([...definition.installArgs], options.runCommand, { registrationManagedBy: OFFICIAL_PLUGIN_MANAGER });
|
|
57
|
+
if (code !== 0) {
|
|
58
|
+
failed = true;
|
|
59
|
+
writeLine(`${name} plugin: install failed (exit ${code}).`);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
installed = true;
|
|
63
|
+
writeLine(`${name} plugin: installed and registered.`);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
failed = true;
|
|
67
|
+
writeLine(`${name} plugin: ${error instanceof Error ? error.message : String(error)}; skipped.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return failed ? 'failed' : installed ? 'installed' : 'skipped';
|
|
71
|
+
}
|
|
72
|
+
export async function uninstallAlibabaInternalPlugins(options = {}, dependencies = {}) {
|
|
73
|
+
const writeLine = options.writeLine ?? (line => console.error(line));
|
|
74
|
+
const registrationFor = dependencies.findPluginRegistration ?? findPluginRegistration;
|
|
75
|
+
let uninstalled = false;
|
|
76
|
+
let failed = false;
|
|
77
|
+
for (const name of [...ALIBABA_INTERNAL_PLUGIN_NAMES].reverse()) {
|
|
78
|
+
try {
|
|
79
|
+
const registration = registrationFor(name);
|
|
80
|
+
if (registration?.managedBy !== OFFICIAL_PLUGIN_MANAGER) {
|
|
81
|
+
if (registration)
|
|
82
|
+
writeLine(`${name} plugin: existing user registration kept.`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const result = await (dependencies.uninstallPlugin ?? uninstallPlugin)(name, options.runCommand);
|
|
86
|
+
if (!result.removed)
|
|
87
|
+
continue;
|
|
88
|
+
uninstalled = true;
|
|
89
|
+
writeLine(`${name} plugin: uninstalled.`);
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
failed = true;
|
|
93
|
+
writeLine(`${name} plugin: ${error instanceof Error ? error.message : String(error)}; uninstall failed.`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return failed ? 'failed' : uninstalled ? 'uninstalled' : 'skipped';
|
|
97
|
+
}
|
package/dist/lifecycle.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { detectGlobalPackage, type PackageManagerInstallation } from './packageM
|
|
|
3
3
|
import { type DetectedMearlProfiles, type MearlProfile } from './profiles.js';
|
|
4
4
|
import { uninstallNativeHostArtifacts } from './nativeHost.js';
|
|
5
5
|
import { type CommandRunner } from './process.js';
|
|
6
|
+
import { installAlibabaInternalPlugins, uninstallAlibabaInternalPlugins } from './internalPlugins.js';
|
|
6
7
|
export type MearlLifecycleOperation = 'install' | 'update' | 'uninstall';
|
|
7
8
|
export interface MearlLifecycleOptions {
|
|
8
9
|
operation: MearlLifecycleOperation;
|
|
@@ -21,9 +22,11 @@ export interface MearlLifecycleDependencies {
|
|
|
21
22
|
detectPackage?: typeof detectGlobalPackage;
|
|
22
23
|
detectProfiles?: (env: NodeJS.ProcessEnv) => DetectedMearlProfiles;
|
|
23
24
|
existsSync?: (filePath: string) => boolean;
|
|
25
|
+
installInternalPlugins?: typeof installAlibabaInternalPlugins;
|
|
24
26
|
resolveInvokingManager?: (env: NodeJS.ProcessEnv) => PackageManagerInstallation;
|
|
25
27
|
runCommand?: CommandRunner;
|
|
26
28
|
runSkillOperation?: typeof runGlobalMearlSkillOperation;
|
|
29
|
+
uninstallInternalPlugins?: typeof uninstallAlibabaInternalPlugins;
|
|
27
30
|
writeLine?: (line: string) => void;
|
|
28
31
|
}
|
|
29
32
|
export declare function runMearlLifecycle(options: MearlLifecycleOptions, dependencies?: MearlLifecycleDependencies): Promise<boolean>;
|
package/dist/lifecycle.js
CHANGED
|
@@ -8,6 +8,7 @@ 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
|
+
import { installAlibabaInternalPlugins, uninstallAlibabaInternalPlugins, } from './internalPlugins.js';
|
|
11
12
|
const LEGACY_CLOUD_CLIENT = '@mearl/cloud-client';
|
|
12
13
|
const UNIFIED_CLIENT = '@mearl/client';
|
|
13
14
|
const PROFILE_ORDER = ['local', 'cloud', 'mcp'];
|
|
@@ -202,6 +203,9 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
202
203
|
? detectPackage(UNIFIED_CLIENT, ['mearl'], { env })
|
|
203
204
|
: null;
|
|
204
205
|
const detectedClient = detectedClientCandidate?.packageName === UNIFIED_CLIENT ? detectedClientCandidate : null;
|
|
206
|
+
const remainingClientProfiles = selectedUsesClient && options.operation === 'uninstall'
|
|
207
|
+
? CLIENT_PROFILES.filter(profile => detected.has(profile) && !selected.includes(profile))
|
|
208
|
+
: [];
|
|
205
209
|
if (selectedUsesClient && options.operation !== 'uninstall') {
|
|
206
210
|
const clientInstallation = detectedClient ??
|
|
207
211
|
detected.get('local') ??
|
|
@@ -264,7 +268,6 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
264
268
|
}
|
|
265
269
|
}
|
|
266
270
|
if (selectedUsesClient && options.operation === 'uninstall') {
|
|
267
|
-
const remainingClientProfiles = CLIENT_PROFILES.filter(profile => detected.has(profile) && !selected.includes(profile));
|
|
268
271
|
if (remainingClientProfiles.length > 0) {
|
|
269
272
|
writeLine(`Packages (shared client): kept for ${remainingClientProfiles.join(', ')}.`);
|
|
270
273
|
}
|
|
@@ -306,7 +309,41 @@ export async function runMearlLifecycle(options, dependencies = {}) {
|
|
|
306
309
|
skillOk = false;
|
|
307
310
|
}
|
|
308
311
|
}
|
|
309
|
-
|
|
312
|
+
let internalPluginsOk = true;
|
|
313
|
+
if (packagesOk && skillOk && selectedUsesClient && options.skipSkill !== true) {
|
|
314
|
+
if (options.operation === 'uninstall') {
|
|
315
|
+
if (remainingClientProfiles.length > 0) {
|
|
316
|
+
writeLine(`Internal plugins: kept for ${remainingClientProfiles.join(', ')}.`);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
try {
|
|
320
|
+
internalPluginsOk =
|
|
321
|
+
(await (dependencies.uninstallInternalPlugins ?? uninstallAlibabaInternalPlugins)({
|
|
322
|
+
env,
|
|
323
|
+
runCommand: runner,
|
|
324
|
+
writeLine,
|
|
325
|
+
})) !== 'failed';
|
|
326
|
+
}
|
|
327
|
+
catch (error) {
|
|
328
|
+
writeLine(`Internal plugins: ${error instanceof Error ? error.message : String(error)}; uninstall failed.`);
|
|
329
|
+
internalPluginsOk = false;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else if (options.targetLine === undefined) {
|
|
334
|
+
try {
|
|
335
|
+
await (dependencies.installInternalPlugins ?? installAlibabaInternalPlugins)({
|
|
336
|
+
env,
|
|
337
|
+
runCommand: runner,
|
|
338
|
+
writeLine,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
writeLine(`Internal plugins: ${error instanceof Error ? error.message : String(error)}; skipped.`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (packagesOk && skillOk && internalPluginsOk) {
|
|
310
347
|
writeLine('Mearl setup complete.');
|
|
311
348
|
return true;
|
|
312
349
|
}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type CommandRunner } from './process.js';
|
|
2
2
|
export declare function setupPlugin(name: string, target: string): Promise<string>;
|
|
3
|
+
export declare function ensureOfficialPlugin(name: string, runner?: CommandRunner): Promise<'existing' | 'installed'>;
|
|
3
4
|
export interface PluginUninstallResult {
|
|
4
5
|
entryMissing?: boolean;
|
|
5
6
|
removed: boolean;
|
|
@@ -9,4 +10,6 @@ export interface PluginUninstallResult {
|
|
|
9
10
|
export declare function uninstallPlugin(name: string, runner?: CommandRunner): Promise<PluginUninstallResult>;
|
|
10
11
|
/** Ali Skills 的 Installed 区块列出成功路径;计数校验避免输出变化导致漏注册。 */
|
|
11
12
|
export declare function parseInstalledPluginPaths(output: string): string[];
|
|
12
|
-
export declare function installPlugins(args: string[], runner?: CommandRunner
|
|
13
|
+
export declare function installPlugins(args: string[], runner?: CommandRunner, options?: {
|
|
14
|
+
registrationManagedBy?: string;
|
|
15
|
+
}): Promise<number>;
|
package/dist/plugin.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, rmdirSync, statSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
4
|
-
import { assertPluginName, findPluginEntry, listPluginNames, loadPlugin, registerPlugin, resolvePluginEntry, unregisterPlugin, } from '@mearl/client/plugin';
|
|
4
|
+
import { assertPluginName, findOfficialPlugin, findPluginEntry, findPluginRegistration, listPluginNames, loadPlugin, OFFICIAL_PLUGIN_MANAGER, registerPlugin, resolvePluginEntry, unregisterPlugin, } from '@mearl/client/plugin';
|
|
5
5
|
import { ensurePluginRuntime } from './pluginRuntime.js';
|
|
6
6
|
import { resolveAliSkillsInvocation, stripAnsi } from './aliSkills.js';
|
|
7
7
|
import { findExecutable, prependNodePath, runCommand } from './process.js';
|
|
@@ -21,6 +21,22 @@ export async function setupPlugin(name, target) {
|
|
|
21
21
|
await ensurePluginRuntime();
|
|
22
22
|
return registerPlugin(name, entry, plugin.description);
|
|
23
23
|
}
|
|
24
|
+
export async function ensureOfficialPlugin(name, runner = runCommand) {
|
|
25
|
+
const definition = findOfficialPlugin(name);
|
|
26
|
+
if (!definition)
|
|
27
|
+
throw new Error(`未知官方插件: ${name}`);
|
|
28
|
+
if (findPluginRegistration(name))
|
|
29
|
+
return 'existing';
|
|
30
|
+
const code = await installPlugins([...definition.installArgs], runner, {
|
|
31
|
+
registrationManagedBy: OFFICIAL_PLUGIN_MANAGER,
|
|
32
|
+
});
|
|
33
|
+
if (code !== 0)
|
|
34
|
+
throw new Error(`官方插件 ${name} 安装失败 (exit ${code})`);
|
|
35
|
+
const registration = findPluginRegistration(name);
|
|
36
|
+
if (!registration)
|
|
37
|
+
throw new Error(`官方插件 ${name} 安装完成后仍未注册`);
|
|
38
|
+
return 'installed';
|
|
39
|
+
}
|
|
24
40
|
function globalAliSkill(entry) {
|
|
25
41
|
const globalSkillsDirectory = join(homedir(), '.agents', 'skills');
|
|
26
42
|
const relativeEntry = relative(globalSkillsDirectory, resolve(entry));
|
|
@@ -137,7 +153,7 @@ function permitsEmptyInstallResult(args, output) {
|
|
|
137
153
|
args.some(argument => ['--help', '-h', '--list'].includes(argument)) ||
|
|
138
154
|
/\b(?:installation\s+)?cancel(?:led|ed)\b/i.test(output));
|
|
139
155
|
}
|
|
140
|
-
export async function installPlugins(args, runner = runCommand) {
|
|
156
|
+
export async function installPlugins(args, runner = runCommand, options = {}) {
|
|
141
157
|
if (args.includes('--')) {
|
|
142
158
|
throw new Error('plugin install 不支持 -- 参数分隔符,以确保全局 symlink 安装选项生效。');
|
|
143
159
|
}
|
|
@@ -178,7 +194,7 @@ export async function installPlugins(args, runner = runCommand) {
|
|
|
178
194
|
}
|
|
179
195
|
await ensurePluginRuntime(runner);
|
|
180
196
|
for (const [name, { entry, description }] of plugins) {
|
|
181
|
-
registerPlugin(name, entry, description);
|
|
197
|
+
registerPlugin(name, entry, description, { managedBy: options.registrationManagedBy });
|
|
182
198
|
console.error(`Plugin ${name} registered: ${entry}\nRun: mearl ${name} --help`);
|
|
183
199
|
}
|
|
184
200
|
return 0;
|
package/dist/provider.js
CHANGED
|
@@ -11,9 +11,10 @@ export function parseProviderPackageSpec(value) {
|
|
|
11
11
|
}
|
|
12
12
|
return { packageName: nameMatch[1], target: nameMatch[2] || 'latest' };
|
|
13
13
|
}
|
|
14
|
-
function providerManager(packageName, env, dependencies) {
|
|
15
|
-
const resolved =
|
|
16
|
-
(
|
|
14
|
+
function providerManager(packageName, env, dependencies, resolvedManager) {
|
|
15
|
+
const resolved = resolvedManager ??
|
|
16
|
+
(dependencies.resolveInvokingManager ??
|
|
17
|
+
(childEnv => resolveInvokingPackageManager({ env: childEnv })))(env);
|
|
17
18
|
if (!packageName.startsWith('@ali/') || resolved.command.includes('tnpm'))
|
|
18
19
|
return resolved;
|
|
19
20
|
const tnpm = (dependencies.findExecutable ?? findExecutable)('tnpm', {
|
|
@@ -96,7 +97,8 @@ export async function updateProviderPackage(selector, dependencies = {}) {
|
|
|
96
97
|
const current = findRegisteredProvider(selector, listProviders);
|
|
97
98
|
const env = process.env;
|
|
98
99
|
const runner = dependencies.runCommand ?? runCommand;
|
|
99
|
-
const
|
|
100
|
+
const detectedManager = (dependencies.resolvePackageManager ?? resolvePackageManagerInstallation)(current.packageRoot, current.packageName) ?? undefined;
|
|
101
|
+
const installation = providerManager(current.packageName, env, dependencies, detectedManager);
|
|
100
102
|
(dependencies.stopProvider ?? stopProvider)(current.providerId);
|
|
101
103
|
await runPackageCommand(installation, 'update', current.packageName, 'latest', runner, env);
|
|
102
104
|
const manifest = (dependencies.registerPackage ?? registerProviderPackage)(current.packageRoot);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/setup",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.15.1",
|
|
4
4
|
"description": "One-shot environment setup and configuration for Mearl",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,10 +26,6 @@
|
|
|
26
26
|
"types": "./dist/nativeHost.d.ts",
|
|
27
27
|
"import": "./dist/nativeHost.js"
|
|
28
28
|
},
|
|
29
|
-
"./agentbay": {
|
|
30
|
-
"types": "./dist/agentBay.d.ts",
|
|
31
|
-
"import": "./dist/agentBay.js"
|
|
32
|
-
},
|
|
33
29
|
"./version": {
|
|
34
30
|
"types": "./dist/version.d.ts",
|
|
35
31
|
"import": "./dist/version.js"
|
|
@@ -65,8 +61,8 @@
|
|
|
65
61
|
"commander": "^15.0.0",
|
|
66
62
|
"extract-zip": "^2.0.1",
|
|
67
63
|
"ws": "^8.18.3",
|
|
68
|
-
"@mearl/client": "2.
|
|
69
|
-
"@mearl/provider": "2.
|
|
64
|
+
"@mearl/client": "2.15.1",
|
|
65
|
+
"@mearl/provider": "2.15.1"
|
|
70
66
|
},
|
|
71
67
|
"devDependencies": {
|
|
72
68
|
"@types/node": "^24.9.1",
|
package/dist/agentBay.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export interface AgentBayConfigurationOptions {
|
|
2
|
-
envFile?: string;
|
|
3
|
-
}
|
|
4
|
-
export interface AgentBayConfigurationResult {
|
|
5
|
-
envFile: string;
|
|
6
|
-
updated: boolean;
|
|
7
|
-
}
|
|
8
|
-
export interface AgentBaySecretInput {
|
|
9
|
-
isRaw?: boolean;
|
|
10
|
-
isTTY?: boolean;
|
|
11
|
-
on(event: 'data', listener: (chunk: Buffer | string) => void): unknown;
|
|
12
|
-
pause(): unknown;
|
|
13
|
-
removeListener(event: 'data', listener: (chunk: Buffer | string) => void): unknown;
|
|
14
|
-
resume(): unknown;
|
|
15
|
-
setRawMode?(mode: boolean): unknown;
|
|
16
|
-
}
|
|
17
|
-
export interface AgentBaySecretOutput {
|
|
18
|
-
write(value: string): unknown;
|
|
19
|
-
}
|
|
20
|
-
export declare function readAgentBayApiKeyFile(file: string): string;
|
|
21
|
-
export declare function readAgentBayApiKeyStdin(input?: NodeJS.ReadableStream): Promise<string>;
|
|
22
|
-
export declare function promptAgentBayApiKey(input?: AgentBaySecretInput, output?: AgentBaySecretOutput): Promise<string>;
|
|
23
|
-
export declare function configureAgentBayApiKey(apiKey: string, options?: AgentBayConfigurationOptions): AgentBayConfigurationResult;
|
package/dist/agentBay.js
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import os from 'node:os';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
const AGENTBAY_ENV_KEY = 'AGENTBAY_KEY';
|
|
5
|
-
const AGENTBAY_ENV_LINE = /^\s*(?:export\s+)?AGENTBAY_KEY\s*=/;
|
|
6
|
-
function normalizeApiKey(apiKey) {
|
|
7
|
-
const value = apiKey.trim();
|
|
8
|
-
if (!value)
|
|
9
|
-
throw new Error('AgentBay API key is required.');
|
|
10
|
-
if (/[\r\n\0]/.test(value)) {
|
|
11
|
-
throw new Error('AgentBay API key must be a single-line value.');
|
|
12
|
-
}
|
|
13
|
-
return value;
|
|
14
|
-
}
|
|
15
|
-
export function readAgentBayApiKeyFile(file) {
|
|
16
|
-
const resolved = path.resolve(file);
|
|
17
|
-
return normalizeApiKey(fs.readFileSync(resolved, 'utf8'));
|
|
18
|
-
}
|
|
19
|
-
export async function readAgentBayApiKeyStdin(input = process.stdin) {
|
|
20
|
-
let value = '';
|
|
21
|
-
for await (const chunk of input)
|
|
22
|
-
value += chunk.toString();
|
|
23
|
-
return normalizeApiKey(value);
|
|
24
|
-
}
|
|
25
|
-
export function promptAgentBayApiKey(input = process.stdin, output = process.stderr) {
|
|
26
|
-
if (!input.isTTY || !input.setRawMode) {
|
|
27
|
-
return Promise.reject(new Error('A TTY is required to enter the AgentBay API key securely.'));
|
|
28
|
-
}
|
|
29
|
-
const setRawMode = input.setRawMode.bind(input);
|
|
30
|
-
return new Promise((resolvePrompt, rejectPrompt) => {
|
|
31
|
-
const wasRaw = input.isRaw === true;
|
|
32
|
-
let value = '';
|
|
33
|
-
let settled = false;
|
|
34
|
-
const finish = (error) => {
|
|
35
|
-
if (settled)
|
|
36
|
-
return;
|
|
37
|
-
settled = true;
|
|
38
|
-
input.removeListener('data', onData);
|
|
39
|
-
setRawMode(wasRaw);
|
|
40
|
-
input.pause();
|
|
41
|
-
output.write('\n');
|
|
42
|
-
if (error)
|
|
43
|
-
rejectPrompt(error);
|
|
44
|
-
else {
|
|
45
|
-
try {
|
|
46
|
-
resolvePrompt(normalizeApiKey(value));
|
|
47
|
-
}
|
|
48
|
-
catch (validationError) {
|
|
49
|
-
rejectPrompt(validationError instanceof Error ? validationError : new Error(String(validationError)));
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
const onData = (chunk) => {
|
|
54
|
-
for (const character of chunk.toString()) {
|
|
55
|
-
if (character === '\u0003') {
|
|
56
|
-
finish(new Error('AgentBay API key input cancelled.'));
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
if (character === '\r' || character === '\n') {
|
|
60
|
-
finish();
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
if (character === '\u007f' || character === '\b') {
|
|
64
|
-
value = Array.from(value).slice(0, -1).join('');
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (character >= ' ')
|
|
68
|
-
value += character;
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
output.write('AgentBay API key: ');
|
|
72
|
-
setRawMode(true);
|
|
73
|
-
input.resume();
|
|
74
|
-
input.on('data', onData);
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
function updateEnvContent(content, apiKey) {
|
|
78
|
-
const newline = content.includes('\r\n') ? '\r\n' : '\n';
|
|
79
|
-
const lines = content ? content.split(/\r?\n/) : [];
|
|
80
|
-
if (lines.at(-1) === '')
|
|
81
|
-
lines.pop();
|
|
82
|
-
const entry = `${AGENTBAY_ENV_KEY}=${apiKey}`;
|
|
83
|
-
const output = [];
|
|
84
|
-
let updated = false;
|
|
85
|
-
for (const line of lines) {
|
|
86
|
-
if (!AGENTBAY_ENV_LINE.test(line)) {
|
|
87
|
-
output.push(line);
|
|
88
|
-
continue;
|
|
89
|
-
}
|
|
90
|
-
if (!updated)
|
|
91
|
-
output.push(entry);
|
|
92
|
-
updated = true;
|
|
93
|
-
}
|
|
94
|
-
if (!updated)
|
|
95
|
-
output.push(entry);
|
|
96
|
-
return { content: `${output.join(newline)}${newline}`, updated };
|
|
97
|
-
}
|
|
98
|
-
export function configureAgentBayApiKey(apiKey, options = {}) {
|
|
99
|
-
const value = normalizeApiKey(apiKey);
|
|
100
|
-
const envFile = options.envFile ?? path.join(os.homedir(), '.mearl', '.env');
|
|
101
|
-
let current = '';
|
|
102
|
-
try {
|
|
103
|
-
current = fs.readFileSync(envFile, 'utf8');
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
if (!(error instanceof Error && 'code' in error && error.code === 'ENOENT'))
|
|
107
|
-
throw error;
|
|
108
|
-
}
|
|
109
|
-
const next = updateEnvContent(current, value);
|
|
110
|
-
fs.mkdirSync(path.dirname(envFile), { recursive: true, mode: 0o700 });
|
|
111
|
-
fs.writeFileSync(envFile, next.content, { encoding: 'utf8', mode: 0o600 });
|
|
112
|
-
fs.chmodSync(envFile, 0o600);
|
|
113
|
-
return { envFile, updated: next.updated };
|
|
114
|
-
}
|