@mearl/client 2.20.4 → 2.20.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/dist/check.d.ts +2 -0
- package/dist/check.js +7 -0
- package/dist/commands.d.ts +9 -1
- package/dist/commands.js +11 -1
- package/dist/generated-app-profile-definition.d.ts +130 -0
- package/dist/generated-app-profile-definition.js +448 -0
- package/dist/generated-app-profile-runtime-types.d.ts +209 -0
- package/dist/generated-app-profile-runtime-types.js +1 -0
- package/dist/generated-browser-action-protocol.d.ts +8 -3
- package/dist/nativeHost.d.ts +21 -0
- package/dist/nativeHost.js +107 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/plugin.js +15 -3
- package/dist/pluginAppProfiles.d.ts +6 -0
- package/dist/pluginAppProfiles.js +40 -0
- package/dist/pluginRegistry.d.ts +5 -0
- package/dist/pluginRegistry.js +21 -0
- package/dist/pythonPlugin.d.ts +1 -0
- package/dist/pythonPlugin.js +8 -3
- package/package.json +7 -3
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import type { AppProfileBinding, AppProfilePolicies, AppProfileSetting } from './generated-app-profile-definition.js';
|
|
2
|
+
import type { BrowserDevicePresetName } from './generated-browser-action-protocol.js';
|
|
3
|
+
type DeviceCutout = 'none' | 'dynamic-island' | 'notch' | 'side-camera';
|
|
4
|
+
/** 桥调用风格,与页面适配器使用的端内调用约定一一对应。 */
|
|
5
|
+
export type AppBridgeStyle = 'windvane' | 'fliggy' | 'alipay' | 'callback' | 'btrip' | 'invoke' | 'callHandler' | 'amap' | 'didi' | 'easybikeNative' | 'mega' | 'wechat' | 'wechatMiniProgram';
|
|
6
|
+
export interface AppBridgeSpec {
|
|
7
|
+
global: string;
|
|
8
|
+
style: AppBridgeStyle;
|
|
9
|
+
/** 页面脚本重设全局对象时仍保留桥叶子,同时接纳页面对象上的其他 API。 */
|
|
10
|
+
persistent?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export type AppBridgeBehavior = 'navigate' | 'closeAll' | 'closeBackData' | 'postMessage' | 'notifySubscribe' | 'notifyUnsubscribe' | 'notifyPost' | 'sharedFileRead' | 'sharedFileWrite' | 'sharedDataGet' | 'sharedDataSet' | 'legacyBroadcastCreate' | 'legacyBroadcastPost' | 'legacyBroadcastClose' | 'memoryGet' | 'memorySet' | 'memoryDelete' | 'availabilityMap' | 'userInfo' | 'authCode' | 'networkInfo' | 'geolocation' | 'permission' | 'imagePicker' | 'prefetch' | 'fptTrack' | 'mtopStream' | 'previewFile' | 'toast' | 'unsupportedNativeInput' | 'payment' | 'userTrack' | 'calendar' | 'networkQualityMonitor' | 'beaconRequest' | 'voiceRecognition';
|
|
13
|
+
/** 桥方法名到标准化动作的映射。WindVane 风格使用 `Class.method`,其余使用 api 名。 */
|
|
14
|
+
export interface BridgeActionMap {
|
|
15
|
+
open: string[];
|
|
16
|
+
close: string[];
|
|
17
|
+
title: string[];
|
|
18
|
+
navigationBar?: string[];
|
|
19
|
+
statusBar?: string[];
|
|
20
|
+
systemInfo: string[];
|
|
21
|
+
mtop: string[];
|
|
22
|
+
kvGet?: string[];
|
|
23
|
+
kvSet?: string[];
|
|
24
|
+
kvDelete?: string[];
|
|
25
|
+
pageContext?: string[];
|
|
26
|
+
responses?: Record<string, unknown>;
|
|
27
|
+
behaviors?: Record<string, AppBridgeBehavior>;
|
|
28
|
+
noop: string[];
|
|
29
|
+
silent?: string[];
|
|
30
|
+
}
|
|
31
|
+
export interface AppSchemeRule {
|
|
32
|
+
match: string;
|
|
33
|
+
/** 规则仅在目标或当前页面 host 命中这些根域时生效。 */
|
|
34
|
+
hostSuffixes?: string[];
|
|
35
|
+
param?: string;
|
|
36
|
+
/** 将参数解出的 H5 地址保留 path/query/hash,并投影到当前页面 origin。 */
|
|
37
|
+
useCurrentOrigin?: boolean;
|
|
38
|
+
targetPath?: string;
|
|
39
|
+
targetHostPrefix?: string;
|
|
40
|
+
targetParams?: Record<string, string>;
|
|
41
|
+
}
|
|
42
|
+
export interface AppMiniProgramPaymentRoute {
|
|
43
|
+
/** 小程序支付中转页 pathname。 */
|
|
44
|
+
pathname: string;
|
|
45
|
+
/** 限制业务根域,避免同名小程序路由被其他项目接管。 */
|
|
46
|
+
hostSuffixes?: string[];
|
|
47
|
+
/** JSON 字符串形式的支付桥入参。 */
|
|
48
|
+
paymentParamsParam: string;
|
|
49
|
+
/** 按顺序读取的支付成功回跳参数,前者为空时使用后者。 */
|
|
50
|
+
successUrlParams: string[];
|
|
51
|
+
/** 按顺序读取的支付失败或取消回跳参数。 */
|
|
52
|
+
failureUrlParams: string[];
|
|
53
|
+
/** 支付成功结果写入回跳 URL 的参数名。 */
|
|
54
|
+
successResultParam?: string;
|
|
55
|
+
/** 从支付页路由透传到回跳 URL 的默认参数。 */
|
|
56
|
+
forwardParams?: string[];
|
|
57
|
+
/** 将回跳 H5 的 path/query/hash 投影到当前页面 origin。 */
|
|
58
|
+
useCurrentOrigin?: boolean;
|
|
59
|
+
}
|
|
60
|
+
export type AppMiniProgramMessageAction = 'stageBackData' | 'closeAll';
|
|
61
|
+
export interface AppMiniProgramMessageRule {
|
|
62
|
+
/** H5 通过 wx.miniProgram.postMessage 发送的 type。 */
|
|
63
|
+
type: string;
|
|
64
|
+
/** 容器只提供有限的页面栈动作,业务消息名由档案映射。 */
|
|
65
|
+
action: AppMiniProgramMessageAction;
|
|
66
|
+
/** 限制发送消息的 H5 根域。 */
|
|
67
|
+
hostSuffixes?: string[];
|
|
68
|
+
}
|
|
69
|
+
export type AppMiniProgramNativeAction = 'navigateMiniProgram' | 'subscribeMessage';
|
|
70
|
+
export interface AppMiniProgramNativeRoute {
|
|
71
|
+
/** 承接微信原生能力的小程序页面 pathname。 */
|
|
72
|
+
pathname: string;
|
|
73
|
+
/** 该页面允许模拟的固定原生动作,动作名读取 URL 的 action 参数。 */
|
|
74
|
+
actions: AppMiniProgramNativeAction[];
|
|
75
|
+
/** 限制业务根域,避免其他小程序的同名页面被接管。 */
|
|
76
|
+
hostSuffixes?: string[];
|
|
77
|
+
}
|
|
78
|
+
/** 微信小程序容器相关规则统一收口,避免为每种业务页增加顶层配置。 */
|
|
79
|
+
export interface AppMiniProgramConfig {
|
|
80
|
+
paymentRoutes?: AppMiniProgramPaymentRoute[];
|
|
81
|
+
nativeRoutes?: AppMiniProgramNativeRoute[];
|
|
82
|
+
messageRules?: AppMiniProgramMessageRule[];
|
|
83
|
+
}
|
|
84
|
+
export interface AppImagePickerConfig {
|
|
85
|
+
schemes: string[];
|
|
86
|
+
api: string;
|
|
87
|
+
version: string;
|
|
88
|
+
fcId: string;
|
|
89
|
+
fcGroup: string;
|
|
90
|
+
uploadUrlFunction: string;
|
|
91
|
+
prefix: string;
|
|
92
|
+
region: string;
|
|
93
|
+
}
|
|
94
|
+
export interface AppOverlayConfig {
|
|
95
|
+
enabledParam: string;
|
|
96
|
+
topRatioParam: string;
|
|
97
|
+
/** 弹层高度占屏比例参数,与 topRatioParam 二选一即可。 */
|
|
98
|
+
heightRatioParam?: string;
|
|
99
|
+
radiusParam: string;
|
|
100
|
+
maskAlphaParam: string;
|
|
101
|
+
tapCloseParam: string;
|
|
102
|
+
/** 蒙层关闭时优先触发当前 WebView 内的关闭控件,以复用页面定义的回传逻辑。 */
|
|
103
|
+
tapCloseSelectors?: string[];
|
|
104
|
+
/** 蒙层点击只退出当前 WebView,不调用页面关闭控件或打开回调。 */
|
|
105
|
+
silentTapClose?: boolean;
|
|
106
|
+
defaultTopRatio: number;
|
|
107
|
+
defaultRadius: number;
|
|
108
|
+
defaultMaskAlpha: number;
|
|
109
|
+
defaultTapClose: boolean;
|
|
110
|
+
}
|
|
111
|
+
export interface AppNativeBackConfig {
|
|
112
|
+
/** 原生返回键触发的 document 事件。 */
|
|
113
|
+
event?: string;
|
|
114
|
+
/** 返回 true 时由页面接管返回,再分发 event。 */
|
|
115
|
+
controlFunction?: string;
|
|
116
|
+
/** 原生返回键直接调用的 window 函数。 */
|
|
117
|
+
handler?: string;
|
|
118
|
+
}
|
|
119
|
+
export interface AppLayerUrlProxyConfig {
|
|
120
|
+
sourceHostPrefix: string;
|
|
121
|
+
pathPrefix: string;
|
|
122
|
+
targetHostSuffixes: string[];
|
|
123
|
+
defaultTargetHost?: string;
|
|
124
|
+
}
|
|
125
|
+
export interface AppTitleBarConfig {
|
|
126
|
+
navigationBarHeight: number;
|
|
127
|
+
backgroundColor: string;
|
|
128
|
+
textColor: string;
|
|
129
|
+
hiddenPathnames?: string[];
|
|
130
|
+
statusBarBackgroundColors?: Record<string, string>;
|
|
131
|
+
hostStyles?: AppTitleBarHostStyle[];
|
|
132
|
+
}
|
|
133
|
+
export interface AppTitleBarHostStyle {
|
|
134
|
+
/** 同时匹配该 host 与其全部子域,配置值不含前导点。 */
|
|
135
|
+
hostSuffix: string;
|
|
136
|
+
titleBarBackgroundColor?: string;
|
|
137
|
+
statusBarBackgroundColor?: string;
|
|
138
|
+
}
|
|
139
|
+
export interface AppSafeAreaInsets {
|
|
140
|
+
top: number;
|
|
141
|
+
right: number;
|
|
142
|
+
bottom: number;
|
|
143
|
+
left: number;
|
|
144
|
+
}
|
|
145
|
+
/** App 运行时使用的设备外观快照,避免通过机型字符串反推物理特征。 */
|
|
146
|
+
export interface AppDeviceAppearance {
|
|
147
|
+
preset: BrowserDevicePresetName;
|
|
148
|
+
orientation: 'portrait' | 'landscape';
|
|
149
|
+
cutout: DeviceCutout;
|
|
150
|
+
}
|
|
151
|
+
export interface AppRootContainerCookie {
|
|
152
|
+
name: string;
|
|
153
|
+
value: string;
|
|
154
|
+
domain?: string;
|
|
155
|
+
path?: string;
|
|
156
|
+
}
|
|
157
|
+
export interface AppRootContainerConfig {
|
|
158
|
+
enabled: boolean;
|
|
159
|
+
initialCookies?: AppRootContainerCookie[];
|
|
160
|
+
/** 横屏设备外壳中保留根页面,并将后续业务 WebView 呈现在右半屏。 */
|
|
161
|
+
splitScreen?: boolean;
|
|
162
|
+
}
|
|
163
|
+
/** 注入页内的运行时配置,可经 Server-Timing 或 CDP new-document 脚本传输。 */
|
|
164
|
+
export interface InjectedAppProfileConfig {
|
|
165
|
+
binding?: AppProfileBinding;
|
|
166
|
+
policies?: AppProfilePolicies;
|
|
167
|
+
settings?: AppProfileSetting[];
|
|
168
|
+
profile: string;
|
|
169
|
+
label: string;
|
|
170
|
+
globals: Record<string, string>;
|
|
171
|
+
bridges: AppBridgeSpec[];
|
|
172
|
+
activeEvents: string[];
|
|
173
|
+
maxTouchPoints: number;
|
|
174
|
+
statusBarHeight: number;
|
|
175
|
+
safeAreaInsets?: AppSafeAreaInsets;
|
|
176
|
+
/** 可选,以兼容未携带设备外观快照的持久化配置。 */
|
|
177
|
+
deviceAppearance?: AppDeviceAppearance;
|
|
178
|
+
userAgent: string;
|
|
179
|
+
navigatorPlatform: string;
|
|
180
|
+
platform: string;
|
|
181
|
+
systemVersion: string;
|
|
182
|
+
clientType: string;
|
|
183
|
+
language: string;
|
|
184
|
+
actions: BridgeActionMap;
|
|
185
|
+
schemes: AppSchemeRule[];
|
|
186
|
+
miniProgram?: AppMiniProgramConfig;
|
|
187
|
+
imagePicker?: AppImagePickerConfig;
|
|
188
|
+
/** 原生定位返回地址时使用的逆地理编码服务,复用容器的 MTop 传输。 */
|
|
189
|
+
geolocation?: {
|
|
190
|
+
reverseGeocodeApi: string;
|
|
191
|
+
version: string;
|
|
192
|
+
};
|
|
193
|
+
overlay?: AppOverlayConfig;
|
|
194
|
+
/** URL 参数为真时以新 WebView 替换当前业务层。 */
|
|
195
|
+
popBeforeOpenParam?: string;
|
|
196
|
+
/** 跨 WebView 消息抵达 opener 时分发的 document 事件。 */
|
|
197
|
+
messageEvent?: string;
|
|
198
|
+
nativeBack?: AppNativeBackConfig;
|
|
199
|
+
layerUrlProxy?: AppLayerUrlProxyConfig;
|
|
200
|
+
urlSyncHostSuffixes?: string[];
|
|
201
|
+
animationMs?: number;
|
|
202
|
+
forceH5Mtop?: boolean;
|
|
203
|
+
titleBar?: AppTitleBarConfig;
|
|
204
|
+
rootContainer?: AppRootContainerConfig;
|
|
205
|
+
simulateSystemBars?: boolean;
|
|
206
|
+
/** 物理状态栏与底部操作区由独立设备运行时渲染;本运行时只保留客户端导航栏。 */
|
|
207
|
+
externalDeviceChrome?: boolean;
|
|
208
|
+
}
|
|
209
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -512,17 +512,22 @@ export type TabGroupParams = {
|
|
|
512
512
|
* 因此新增档案时只需改两处(名单 + 档案数据),且漏改会在类型检查时暴露。
|
|
513
513
|
*/
|
|
514
514
|
export declare const APP_PROFILE_NAMES: readonly ["fliggy", "taobao", "wechat", "tmall", "alipay", "koubei", "taobao-tj", "taobao-live", "xianyu", "etao", "eleme", "damai", "btrip", "amap", "qianwen", "lingyou", "didi", "easybike"];
|
|
515
|
-
export type
|
|
515
|
+
export type BuiltinAppProfileName = (typeof APP_PROFILE_NAMES)[number];
|
|
516
|
+
export type AppProfileName = BuiltinAppProfileName | `${string}/${string}`;
|
|
516
517
|
export interface SetAppProfileParams {
|
|
517
518
|
tabId: number;
|
|
518
519
|
/** false 时清除该 tab 的容器模拟并刷新 */
|
|
519
520
|
enabled: boolean;
|
|
520
521
|
/** 容器档案名(enabled=true 时必填) */
|
|
521
522
|
profile?: AppProfileName;
|
|
523
|
+
/** Immediately use the latest installed definition while preserving explicit options. */
|
|
524
|
+
refreshDefinition?: boolean;
|
|
525
|
+
/** 固定当前档案定义;默认在下次文档导航时自动采用可用更新。 */
|
|
526
|
+
lockDefinition?: boolean;
|
|
522
527
|
os?: 'ios' | 'android';
|
|
523
528
|
appVersion?: string;
|
|
524
|
-
/**
|
|
525
|
-
language?:
|
|
529
|
+
/** 端定义支持的语言选项。 */
|
|
530
|
+
language?: string;
|
|
526
531
|
deviceModel?: string;
|
|
527
532
|
/** 按方法覆盖档案内置桥配置,仅支持受控行为、固定 JSON 回包和成功空实现 */
|
|
528
533
|
bridgeConfig?: AppBridgeConfig;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export type NativeHostIntegrationMode = 'skills' | 'mcp';
|
|
2
|
+
export declare const MEARL_NATIVE_HOST_NAMES: Record<NativeHostIntegrationMode, string>;
|
|
3
|
+
export interface NativeHostDirectoryOptions {
|
|
4
|
+
env?: NodeJS.ProcessEnv;
|
|
5
|
+
homedir?: () => string;
|
|
6
|
+
platform?: NodeJS.Platform;
|
|
7
|
+
}
|
|
8
|
+
export declare function resolveNativeHostDirectory(options?: NativeHostDirectoryOptions): string;
|
|
9
|
+
export interface NativeHostDiagnostic {
|
|
10
|
+
label: string;
|
|
11
|
+
details: string[];
|
|
12
|
+
}
|
|
13
|
+
export interface NativeHostLauncherMetadata {
|
|
14
|
+
version: 1;
|
|
15
|
+
nodePaths: string[];
|
|
16
|
+
hostPath: string;
|
|
17
|
+
}
|
|
18
|
+
/** Inspect registrations even when Chrome cannot start the native host. Never execute launchers. */
|
|
19
|
+
export declare function inspectNativeHostInstallation(options?: NativeHostDirectoryOptions & {
|
|
20
|
+
hostDir?: string;
|
|
21
|
+
}): NativeHostDiagnostic[];
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export const MEARL_NATIVE_HOST_NAMES = {
|
|
5
|
+
skills: 'com.alibaba.mearl.skills',
|
|
6
|
+
mcp: 'com.alibaba.mearl.mcp',
|
|
7
|
+
};
|
|
8
|
+
export function resolveNativeHostDirectory(options = {}) {
|
|
9
|
+
const env = options.env ?? process.env;
|
|
10
|
+
const home = (options.homedir ?? os.homedir)();
|
|
11
|
+
switch (options.platform ?? process.platform) {
|
|
12
|
+
case 'darwin':
|
|
13
|
+
return path.join(home, 'Library/Application Support/Google/Chrome/NativeMessagingHosts');
|
|
14
|
+
case 'linux':
|
|
15
|
+
return path.join(home, '.config/google-chrome/NativeMessagingHosts');
|
|
16
|
+
case 'win32':
|
|
17
|
+
return path.join(env.PROGRAMDATA || 'C:\\ProgramData', 'Google\\Chrome\\NativeMessagingHosts');
|
|
18
|
+
default:
|
|
19
|
+
throw new Error(`Unsupported platform: ${String(options.platform ?? process.platform)}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function readLauncherMetadata(content) {
|
|
23
|
+
const metadata = content.match(/^(?:#|rem) mearl-native-host-launcher: (.+)\r?$/m);
|
|
24
|
+
if (metadata) {
|
|
25
|
+
const parsed = JSON.parse(metadata[1]);
|
|
26
|
+
if (parsed.version === 1 &&
|
|
27
|
+
Array.isArray(parsed.nodePaths) &&
|
|
28
|
+
parsed.nodePaths.length > 0 &&
|
|
29
|
+
parsed.nodePaths.every(value => typeof value === 'string' && path.isAbsolute(value)) &&
|
|
30
|
+
typeof parsed.hostPath === 'string' &&
|
|
31
|
+
path.isAbsolute(parsed.hostPath)) {
|
|
32
|
+
return { version: 1, nodePaths: parsed.nodePaths, hostPath: parsed.hostPath };
|
|
33
|
+
}
|
|
34
|
+
throw new Error('Invalid native host launcher metadata');
|
|
35
|
+
}
|
|
36
|
+
// Legacy launchers stored these assignments as JSON strings.
|
|
37
|
+
const node = content.match(/^NODE_BIN=(".*")\r?$/m);
|
|
38
|
+
const host = content.match(/^HOST_SCRIPT=(".*")\r?$/m);
|
|
39
|
+
if (!node || !host)
|
|
40
|
+
return null;
|
|
41
|
+
const nodePath = JSON.parse(node[1]);
|
|
42
|
+
const hostPath = JSON.parse(host[1]);
|
|
43
|
+
if (typeof nodePath !== 'string' ||
|
|
44
|
+
!path.isAbsolute(nodePath) ||
|
|
45
|
+
typeof hostPath !== 'string' ||
|
|
46
|
+
!path.isAbsolute(hostPath)) {
|
|
47
|
+
throw new Error('Invalid native host launcher paths');
|
|
48
|
+
}
|
|
49
|
+
return { version: 1, nodePaths: [nodePath], hostPath };
|
|
50
|
+
}
|
|
51
|
+
/** Inspect registrations even when Chrome cannot start the native host. Never execute launchers. */
|
|
52
|
+
export function inspectNativeHostInstallation(options = {}) {
|
|
53
|
+
const platform = options.platform ?? process.platform;
|
|
54
|
+
if (!['darwin', 'linux', 'win32'].includes(platform))
|
|
55
|
+
return [];
|
|
56
|
+
const hostDir = options.hostDir ?? resolveNativeHostDirectory(options);
|
|
57
|
+
const diagnostics = [];
|
|
58
|
+
const isUsable = (filePath, executable) => {
|
|
59
|
+
try {
|
|
60
|
+
fs.accessSync(filePath, executable && platform !== 'win32' ? fs.constants.X_OK : fs.constants.R_OK);
|
|
61
|
+
return fs.statSync(filePath).isFile();
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
for (const hostName of Object.values(MEARL_NATIVE_HOST_NAMES)) {
|
|
68
|
+
const manifestPath = path.join(hostDir, `${hostName}.json`);
|
|
69
|
+
const details = [];
|
|
70
|
+
try {
|
|
71
|
+
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
72
|
+
if (typeof manifest?.path !== 'string' || !path.isAbsolute(manifest.path)) {
|
|
73
|
+
details.push(`Invalid launcher path in ${manifestPath}`);
|
|
74
|
+
}
|
|
75
|
+
else if (!isUsable(manifest.path, true)) {
|
|
76
|
+
details.push(`Launcher is missing or not executable: ${manifest.path}`);
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const metadata = readLauncherMetadata(fs.readFileSync(manifest.path, 'utf-8'));
|
|
80
|
+
if (metadata) {
|
|
81
|
+
if (!metadata.nodePaths.some(candidate => isUsable(candidate, true))) {
|
|
82
|
+
details.push(`No executable Node.js found. Launcher candidates: ${metadata.nodePaths.join(', ')}`);
|
|
83
|
+
}
|
|
84
|
+
if (!isUsable(metadata.hostPath, false)) {
|
|
85
|
+
details.push(`Native host runtime script is missing or unreadable: ${metadata.hostPath}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
if (error.code === 'ENOENT' && !fs.existsSync(manifestPath))
|
|
92
|
+
continue;
|
|
93
|
+
details.push(`Cannot inspect ${manifestPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
94
|
+
}
|
|
95
|
+
if (details.length) {
|
|
96
|
+
diagnostics.push({
|
|
97
|
+
label: `Native Messaging registration needs repair: ${hostName}`,
|
|
98
|
+
details: [
|
|
99
|
+
...details,
|
|
100
|
+
`Registration: ${manifestPath}`,
|
|
101
|
+
'Run `mearl-native-host init` with a working Node.js, then reopen the extension. If the runtime package is missing, run `npx @mearl/setup update --local`.',
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return diagnostics;
|
|
107
|
+
}
|
package/dist/plugin.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AppProfileContribution } from './generated-app-profile-definition.js';
|
|
2
|
+
export type { AppProfileContribution, AppProfileDefinition, } from './generated-app-profile-definition.js';
|
|
1
3
|
export { invokePageEvalWithArgs } from './pageEvalArguments.js';
|
|
2
4
|
import { type OfficialPluginInstaller } from './officialPluginInstaller.js';
|
|
3
5
|
import { type PluginToolboxContribution, type PluginRegistration } from './pluginRegistry.js';
|
|
@@ -38,6 +40,8 @@ export declare function loadPlugin(entry: string, context: Pick<PluginApp, 'invo
|
|
|
38
40
|
name: string | undefined;
|
|
39
41
|
description: string | undefined;
|
|
40
42
|
toolbox: PluginToolboxContribution | undefined;
|
|
43
|
+
appProfiles: AppProfileContribution | undefined;
|
|
44
|
+
profileSources: Record<string, string> | undefined;
|
|
41
45
|
version: string | undefined;
|
|
42
46
|
commandHelp(namespace: string): string;
|
|
43
47
|
help(namespace: string, name?: string): string;
|
|
@@ -48,6 +52,8 @@ export declare function loadRegisteredPlugin(name: string, context: PluginExecut
|
|
|
48
52
|
name: string | undefined;
|
|
49
53
|
description: string | undefined;
|
|
50
54
|
toolbox: PluginToolboxContribution | undefined;
|
|
55
|
+
appProfiles: AppProfileContribution | undefined;
|
|
56
|
+
profileSources: Record<string, string> | undefined;
|
|
51
57
|
version: string | undefined;
|
|
52
58
|
commandHelp(namespace: string): string;
|
|
53
59
|
help(namespace: string, name?: string): string;
|
package/dist/plugin.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loadPluginAppProfiles } from './pluginAppProfiles.js';
|
|
1
2
|
export { invokePageEvalWithArgs } from './pageEvalArguments.js';
|
|
2
3
|
import { readFileSync } from 'node:fs';
|
|
3
4
|
import { dirname, extname, resolve } from 'node:path';
|
|
@@ -107,6 +108,8 @@ export async function refreshRegisteredPlugin(name, includePage = false) {
|
|
|
107
108
|
}
|
|
108
109
|
registerPlugin(name, registration.entry, loaded.description, {
|
|
109
110
|
toolbox: loaded.toolbox,
|
|
111
|
+
appProfiles: loaded.appProfiles,
|
|
112
|
+
profileSources: 'profileSources' in loaded ? loaded.profileSources : loaded.source?.profiles,
|
|
110
113
|
...(registration.managedBy ? { managedBy: registration.managedBy } : {}),
|
|
111
114
|
});
|
|
112
115
|
return findPluginRegistration(name);
|
|
@@ -134,6 +137,8 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
134
137
|
let name;
|
|
135
138
|
let description;
|
|
136
139
|
let toolbox;
|
|
140
|
+
let appProfiles;
|
|
141
|
+
let profileSources;
|
|
137
142
|
const registerCommand = (signature, handler, description = '', examples = [], toolboxOnly = false) => {
|
|
138
143
|
if (!registering)
|
|
139
144
|
throw new Error('命令只能在入口注册阶段声明');
|
|
@@ -178,6 +183,7 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
178
183
|
name = descriptor.name;
|
|
179
184
|
description = descriptor.description;
|
|
180
185
|
toolbox = normalizePluginToolboxContribution(descriptor.toolbox);
|
|
186
|
+
({ appProfiles, profileSources } = loadPluginAppProfiles(entry, descriptor.appProfiles));
|
|
181
187
|
for (const command of descriptor.commands) {
|
|
182
188
|
const commandName = command.signature.trim().split(/\s+/)[0];
|
|
183
189
|
const register = command.toolboxOnly ? app.toolboxCommand : app.command;
|
|
@@ -195,11 +201,15 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
195
201
|
throw new Error('入口的 description 导出必须为字符串');
|
|
196
202
|
description = module.description;
|
|
197
203
|
toolbox = normalizePluginToolboxContribution(module.toolbox);
|
|
198
|
-
|
|
204
|
+
({ appProfiles, profileSources } = loadPluginAppProfiles(entry, module.appProfiles));
|
|
205
|
+
if (module.default !== undefined && typeof module.default !== 'function')
|
|
206
|
+
throw new Error('入口必须默认导出 register(app) 函数');
|
|
207
|
+
if (typeof module.default === 'function')
|
|
208
|
+
await module.default(app);
|
|
209
|
+
else if (!appProfiles)
|
|
199
210
|
throw new Error('入口必须默认导出 register(app) 函数');
|
|
200
|
-
await module.default(app);
|
|
201
211
|
}
|
|
202
|
-
if (commands.size === 0)
|
|
212
|
+
if (commands.size === 0 && !appProfiles)
|
|
203
213
|
throw new Error('入口未注册任何命令');
|
|
204
214
|
const toolboxCommands = toolbox?.page.commands ?? [];
|
|
205
215
|
for (const [name, command] of commands) {
|
|
@@ -290,6 +300,8 @@ export async function loadPlugin(entry, context, pluginName) {
|
|
|
290
300
|
name,
|
|
291
301
|
description,
|
|
292
302
|
toolbox,
|
|
303
|
+
appProfiles,
|
|
304
|
+
profileSources,
|
|
293
305
|
version: readPluginVersion(entry),
|
|
294
306
|
commandHelp(namespace) {
|
|
295
307
|
return publicCommands()
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type AppProfileContribution } from './generated-app-profile-definition.js';
|
|
2
|
+
/** Profile resources share the Skill root and metadata refresh rules of toolbox resources. */
|
|
3
|
+
export declare function loadPluginAppProfiles(entry: string, value: unknown): {
|
|
4
|
+
appProfiles?: AppProfileContribution;
|
|
5
|
+
profileSources?: Record<string, string>;
|
|
6
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
2
|
+
import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
3
|
+
import { validateAppProfileContribution, } from './generated-app-profile-definition.js';
|
|
4
|
+
import { pluginFileVersion } from './pluginSource.js';
|
|
5
|
+
/** Profile resources share the Skill root and metadata refresh rules of toolbox resources. */
|
|
6
|
+
export function loadPluginAppProfiles(entry, value) {
|
|
7
|
+
if (value === undefined)
|
|
8
|
+
return {};
|
|
9
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
10
|
+
throw new Error('appProfiles must be an object');
|
|
11
|
+
const declaration = value;
|
|
12
|
+
if (!Array.isArray(declaration.profiles))
|
|
13
|
+
throw new Error('appProfiles.profiles must be an array');
|
|
14
|
+
const root = realpathSync(basename(dirname(entry)) === 'scripts' ? dirname(dirname(entry)) : dirname(entry));
|
|
15
|
+
const sources = {};
|
|
16
|
+
const profiles = declaration.profiles.map(profile => {
|
|
17
|
+
if (typeof profile !== 'string')
|
|
18
|
+
return profile;
|
|
19
|
+
if (isAbsolute(profile) || !profile.endsWith('.json'))
|
|
20
|
+
throw new Error('Profile resource must be a relative JSON path');
|
|
21
|
+
const reference = resolve(root, profile);
|
|
22
|
+
const file = realpathSync(reference);
|
|
23
|
+
const inside = relative(root, file);
|
|
24
|
+
if (inside.startsWith('..') || isAbsolute(inside))
|
|
25
|
+
throw new Error('Profile resource must stay inside the Skill directory');
|
|
26
|
+
const version = pluginFileVersion(file);
|
|
27
|
+
const content = readFileSync(file, 'utf8');
|
|
28
|
+
if (content.length > 65_536)
|
|
29
|
+
throw new Error('Profile resource exceeds 64 KiB');
|
|
30
|
+
const parsed = JSON.parse(content);
|
|
31
|
+
if (pluginFileVersion(reference) !== version)
|
|
32
|
+
throw new Error('Profile resource changed during registration');
|
|
33
|
+
sources[reference] = version;
|
|
34
|
+
return parsed;
|
|
35
|
+
});
|
|
36
|
+
return {
|
|
37
|
+
appProfiles: validateAppProfileContribution({ ...declaration, profiles }),
|
|
38
|
+
profileSources: sources,
|
|
39
|
+
};
|
|
40
|
+
}
|
package/dist/pluginRegistry.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type AppProfileContribution } from './generated-app-profile-definition.js';
|
|
1
2
|
export declare class PluginError extends Error {
|
|
2
3
|
readonly code: string;
|
|
3
4
|
constructor(code: string, message: string);
|
|
@@ -40,9 +41,11 @@ export interface PluginRegistration {
|
|
|
40
41
|
description?: string;
|
|
41
42
|
managedBy?: string;
|
|
42
43
|
toolbox?: PluginToolboxContribution;
|
|
44
|
+
appProfiles?: AppProfileContribution;
|
|
43
45
|
pageBundle?: PluginPageBundleRegistration;
|
|
44
46
|
source?: {
|
|
45
47
|
entry: string;
|
|
48
|
+
profiles?: Record<string, string>;
|
|
46
49
|
page?: Record<string, string>;
|
|
47
50
|
pageRuntime?: string;
|
|
48
51
|
};
|
|
@@ -51,6 +54,8 @@ export declare function pluginRegistrationNeedsRefresh(registration: PluginRegis
|
|
|
51
54
|
export interface PluginRegistrationOptions {
|
|
52
55
|
managedBy?: string;
|
|
53
56
|
toolbox?: PluginToolboxContribution;
|
|
57
|
+
appProfiles?: AppProfileContribution;
|
|
58
|
+
profileSources?: Record<string, string>;
|
|
54
59
|
}
|
|
55
60
|
export declare function pluginDirectory(): string;
|
|
56
61
|
export declare function assertPluginName(name: string): void;
|
package/dist/pluginRegistry.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validateAppProfileContribution, } from './generated-app-profile-definition.js';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import { closeSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, renameSync, rmSync, statSync, writeFileSync, } from 'node:fs';
|
|
3
4
|
import { homedir } from 'node:os';
|
|
@@ -25,6 +26,15 @@ export function pluginRegistrationNeedsRefresh(registration, includePage = false
|
|
|
25
26
|
return true;
|
|
26
27
|
if (pluginFileVersion(registration.entry) !== registration.source.entry)
|
|
27
28
|
return true;
|
|
29
|
+
if (Object.entries(registration.source.profiles ?? {}).some(([path, version]) => {
|
|
30
|
+
try {
|
|
31
|
+
return pluginFileVersion(path) !== version;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}))
|
|
37
|
+
return true;
|
|
28
38
|
if (!includePage || !registration.toolbox)
|
|
29
39
|
return false;
|
|
30
40
|
if (registration.source.pageRuntime !== PLUGIN_PAGE_RUNTIME_HASH)
|
|
@@ -301,11 +311,15 @@ export function registerPlugin(name, target, description, options = {}) {
|
|
|
301
311
|
entry,
|
|
302
312
|
source: {
|
|
303
313
|
entry: entryVersion,
|
|
314
|
+
...(options.profileSources ? { profiles: options.profileSources } : {}),
|
|
304
315
|
...(builtPage ? { page: builtPage.sources, pageRuntime: PLUGIN_PAGE_RUNTIME_HASH } : {}),
|
|
305
316
|
},
|
|
306
317
|
...(summary ? { description: summary } : {}),
|
|
307
318
|
...(managedBy ? { managedBy } : {}),
|
|
308
319
|
...(toolbox ? { toolbox } : {}),
|
|
320
|
+
...(options.appProfiles
|
|
321
|
+
? { appProfiles: validateAppProfileContribution(options.appProfiles) }
|
|
322
|
+
: {}),
|
|
309
323
|
...(builtPage
|
|
310
324
|
? { pageBundle: { hash: builtPage.hash, size: builtPage.size, chunks: builtPage.chunks } }
|
|
311
325
|
: {}),
|
|
@@ -359,12 +373,18 @@ export function findPluginRegistration(name) {
|
|
|
359
373
|
: undefined;
|
|
360
374
|
const toolbox = normalizePluginToolboxContribution(record.toolbox);
|
|
361
375
|
const pageBundle = toolbox ? normalizePageBundle(record.pageBundle) : undefined;
|
|
376
|
+
const appProfiles = validateAppProfileContribution(record.appProfiles);
|
|
362
377
|
const source = record.source;
|
|
363
378
|
if (source !== undefined &&
|
|
364
379
|
(!source ||
|
|
365
380
|
typeof source !== 'object' ||
|
|
366
381
|
Array.isArray(source) ||
|
|
367
382
|
typeof source.entry !== 'string' ||
|
|
383
|
+
(source.profiles !== undefined &&
|
|
384
|
+
(!source.profiles ||
|
|
385
|
+
typeof source.profiles !== 'object' ||
|
|
386
|
+
Array.isArray(source.profiles) ||
|
|
387
|
+
Object.entries(source.profiles).some(([path, version]) => !isAbsolute(path) || typeof version !== 'string'))) ||
|
|
368
388
|
(source.pageRuntime !== undefined &&
|
|
369
389
|
(typeof source.pageRuntime !== 'string' || !/^[a-f0-9]{64}$/.test(source.pageRuntime))) ||
|
|
370
390
|
(source.page !== undefined &&
|
|
@@ -380,6 +400,7 @@ export function findPluginRegistration(name) {
|
|
|
380
400
|
...(managedBy ? { managedBy } : {}),
|
|
381
401
|
...(toolbox ? { toolbox } : {}),
|
|
382
402
|
...(pageBundle ? { pageBundle } : {}),
|
|
403
|
+
...(appProfiles ? { appProfiles } : {}),
|
|
383
404
|
...(source ? { source } : {}),
|
|
384
405
|
};
|
|
385
406
|
}
|
package/dist/pythonPlugin.d.ts
CHANGED
package/dist/pythonPlugin.js
CHANGED
|
@@ -157,14 +157,16 @@ try:
|
|
|
157
157
|
raise TypeError("入口的 description 必须为字符串")
|
|
158
158
|
if plugin_toolbox is not None and not isinstance(plugin_toolbox, dict):
|
|
159
159
|
raise TypeError("入口的 toolbox 必须为对象")
|
|
160
|
+
plugin_app_profiles = getattr(module, "appProfiles", None)
|
|
160
161
|
register = getattr(module, "register", None)
|
|
161
|
-
if not callable(register):
|
|
162
|
+
if not callable(register) and plugin_app_profiles is None:
|
|
162
163
|
raise TypeError("入口必须导出 register(app) 函数")
|
|
163
164
|
|
|
164
165
|
app = _App(options)
|
|
165
|
-
|
|
166
|
+
if callable(register):
|
|
167
|
+
_resolve(register(app))
|
|
166
168
|
app.registering = False
|
|
167
|
-
if not app.commands:
|
|
169
|
+
if not app.commands and plugin_app_profiles is None:
|
|
168
170
|
raise RuntimeError("入口未注册任何命令")
|
|
169
171
|
|
|
170
172
|
descriptor = {
|
|
@@ -180,6 +182,8 @@ try:
|
|
|
180
182
|
descriptor["description"] = plugin_description
|
|
181
183
|
if plugin_toolbox is not None:
|
|
182
184
|
descriptor["toolbox"] = plugin_toolbox
|
|
185
|
+
if plugin_app_profiles is not None:
|
|
186
|
+
descriptor["appProfiles"] = plugin_app_profiles
|
|
183
187
|
_send(descriptor)
|
|
184
188
|
|
|
185
189
|
if mode == "run":
|
|
@@ -251,6 +255,7 @@ function parseDescriptor(message) {
|
|
|
251
255
|
...(typeof message.name === 'string' ? { name: message.name } : {}),
|
|
252
256
|
...(typeof message.description === 'string' ? { description: message.description } : {}),
|
|
253
257
|
...(message.toolbox !== undefined ? { toolbox: message.toolbox } : {}),
|
|
258
|
+
...(message.appProfiles !== undefined ? { appProfiles: message.appProfiles } : {}),
|
|
254
259
|
};
|
|
255
260
|
}
|
|
256
261
|
function bridgedError(message) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mearl/client",
|
|
3
|
-
"version": "2.20.
|
|
3
|
+
"version": "2.20.5",
|
|
4
4
|
"description": "Unified Mearl SDK & CLI for local and remote browsers",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
"types": "./dist/check.d.ts",
|
|
27
27
|
"import": "./dist/check.js"
|
|
28
28
|
},
|
|
29
|
+
"./native-host": {
|
|
30
|
+
"types": "./dist/nativeHost.d.ts",
|
|
31
|
+
"import": "./dist/nativeHost.js"
|
|
32
|
+
},
|
|
29
33
|
"./cli": {
|
|
30
34
|
"types": "./dist/cli.d.ts",
|
|
31
35
|
"import": "./dist/cli.js"
|
|
@@ -59,8 +63,8 @@
|
|
|
59
63
|
"commander": "^15.0.0",
|
|
60
64
|
"fastest-levenshtein": "^1.0.16",
|
|
61
65
|
"ws": "^8.18.0",
|
|
62
|
-
"@mearl/cloud-types": "2.20.
|
|
63
|
-
"@mearl/provider": "2.20.
|
|
66
|
+
"@mearl/cloud-types": "2.20.5",
|
|
67
|
+
"@mearl/provider": "2.20.5"
|
|
64
68
|
},
|
|
65
69
|
"devDependencies": {
|
|
66
70
|
"@types/node": "^24.9.1",
|