@mearl/native-host 2.2.1 → 2.3.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/dist/install.d.ts CHANGED
@@ -16,6 +16,7 @@
16
16
  * import { installNativeHost } from '@mearl/native-host';
17
17
  * await installNativeHost(); // installs both
18
18
  */
19
+ import fs from 'node:fs';
19
20
  export type IntegrationMode = 'skills' | 'mcp';
20
21
  export interface InstallOptions {
21
22
  /**
@@ -31,6 +32,23 @@ export interface InstallOptions {
31
32
  /** Skip installation if the manifest file already exists. Defaults to `false`. */
32
33
  skipIfExists?: boolean;
33
34
  }
35
+ export interface UninstallOptions {
36
+ /** Integration manifest to remove. Defaults to both Skills and MCP. */
37
+ mode?: IntegrationMode | 'both';
38
+ /** Suppress console output when `true`. */
39
+ silent?: boolean;
40
+ }
41
+ export interface UninstallDependencies {
42
+ env?: NodeJS.ProcessEnv;
43
+ existsSync?: typeof fs.existsSync;
44
+ homedir?: () => string;
45
+ hostDir?: string;
46
+ platform?: NodeJS.Platform;
47
+ unlinkSync?: typeof fs.unlinkSync;
48
+ unregisterWindowsNativeHost?: (hostName: string) => void;
49
+ writeLine?: (line: string) => void;
50
+ }
34
51
  export declare function resolveHostDir(): string;
35
52
  export declare function installNativeHost(options?: InstallOptions): Promise<boolean>;
53
+ export declare function uninstallNativeHost(options?: UninstallOptions, dependencies?: UninstallDependencies): Promise<boolean>;
36
54
  export declare function runCli(argv: string[]): Promise<void>;
package/dist/install.js CHANGED
@@ -1,39 +1,92 @@
1
1
  // src/install.ts
2
+ import fs2 from "node:fs";
3
+ import path2 from "node:path";
4
+ import { execFileSync as execFileSync2 } from "node:child_process";
5
+ import { createRequire } from "node:module";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ // ../setup/dist/nativeHost.js
2
9
  import fs from "node:fs";
3
- import path from "node:path";
4
10
  import os from "node:os";
11
+ import path from "node:path";
5
12
  import { execFileSync } from "node:child_process";
6
- import { createRequire } from "node:module";
7
- import { fileURLToPath } from "node:url";
8
- var DEFAULT_EXTENSION_ID = "aoehhjnofngknnjefamjbplchbolghkm";
9
- var HOST_NAME_BY_MODE = {
13
+ var MEARL_NATIVE_HOST_NAMES = {
10
14
  skills: "com.alibaba.mearl.skills",
11
15
  mcp: "com.alibaba.mearl.mcp"
12
16
  };
13
- var __filename = fileURLToPath(import.meta.url);
14
- var __dirname = path.dirname(__filename);
15
- var ALL_MODES = ["skills", "mcp"];
16
- function resolveHostDir() {
17
- switch (process.platform) {
17
+ function resolveNativeHostDirectory(options = {}) {
18
+ const env = options.env ?? process.env;
19
+ const home = (options.homedir ?? os.homedir)();
20
+ switch (options.platform ?? process.platform) {
18
21
  case "darwin":
19
- return path.join(
20
- os.homedir(),
21
- "Library/Application Support/Google/Chrome/NativeMessagingHosts"
22
- );
22
+ return path.join(home, "Library/Application Support/Google/Chrome/NativeMessagingHosts");
23
23
  case "linux":
24
- return path.join(os.homedir(), ".config/google-chrome/NativeMessagingHosts");
24
+ return path.join(home, ".config/google-chrome/NativeMessagingHosts");
25
25
  case "win32":
26
- return path.join(
27
- process.env.PROGRAMDATA || "C:\\ProgramData",
28
- "Google\\Chrome\\NativeMessagingHosts"
29
- );
26
+ return path.join(env.PROGRAMDATA || "C:\\ProgramData", "Google\\Chrome\\NativeMessagingHosts");
30
27
  default:
31
- throw new Error(`Unsupported platform: ${process.platform}`);
28
+ throw new Error(`Unsupported platform: ${String(options.platform ?? process.platform)}`);
29
+ }
30
+ }
31
+ function unregisterWindowsNativeHost(hostName) {
32
+ const registryKey = `HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\${hostName}`;
33
+ try {
34
+ execFileSync("reg", ["delete", registryKey, "/f"], { stdio: "pipe" });
35
+ } catch {
36
+ }
37
+ }
38
+ function uninstallNativeHostArtifacts(options = {}, dependencies = {}) {
39
+ const { mode = "both", silent = false } = options;
40
+ if (mode === "both") {
41
+ return ["skills", "mcp"].map((integrationMode) => uninstallNativeHostArtifacts({ ...options, mode: integrationMode }, dependencies)).every(Boolean);
32
42
  }
43
+ const writeLine = dependencies.writeLine ?? ((line) => console.error(line));
44
+ const log = (line) => {
45
+ if (!silent)
46
+ writeLine(line);
47
+ };
48
+ try {
49
+ const existsSync = dependencies.existsSync ?? fs.existsSync;
50
+ const platform = dependencies.platform ?? process.platform;
51
+ const hostDir = dependencies.hostDir ?? resolveNativeHostDirectory({
52
+ env: dependencies.env,
53
+ homedir: dependencies.homedir,
54
+ platform
55
+ });
56
+ const unlinkSync = dependencies.unlinkSync ?? fs.unlinkSync;
57
+ const unregister = dependencies.unregisterWindowsNativeHost ?? unregisterWindowsNativeHost;
58
+ const hostName = MEARL_NATIVE_HOST_NAMES[mode];
59
+ const launcherExtension = platform === "win32" ? "cmd" : "sh";
60
+ const artifacts = [
61
+ path.join(hostDir, `${hostName}.json`),
62
+ path.join(hostDir, `${hostName}-launcher.${launcherExtension}`)
63
+ ];
64
+ for (const artifact of artifacts) {
65
+ if (!existsSync(artifact))
66
+ continue;
67
+ unlinkSync(artifact);
68
+ log(`✅ Removed: ${artifact}`);
69
+ }
70
+ if (platform === "win32")
71
+ unregister(hostName);
72
+ return true;
73
+ } catch (error) {
74
+ log(`❌ Uninstall failed: ${error instanceof Error ? error.message : String(error)}`);
75
+ return false;
76
+ }
77
+ }
78
+
79
+ // src/install.ts
80
+ var DEFAULT_EXTENSION_ID = "aoehhjnofngknnjefamjbplchbolghkm";
81
+ var __filename = fileURLToPath(import.meta.url);
82
+ var __dirname = path2.dirname(__filename);
83
+ var ALL_MODES = ["skills", "mcp"];
84
+ function resolveHostDir() {
85
+ return resolveNativeHostDirectory();
33
86
  }
34
87
  function resolveNativeHostPath() {
35
- const siblingPath = path.join(__dirname, "index.js");
36
- if (fs.existsSync(siblingPath)) {
88
+ const siblingPath = path2.join(__dirname, "index.js");
89
+ if (fs2.existsSync(siblingPath)) {
37
90
  return siblingPath;
38
91
  }
39
92
  try {
@@ -47,21 +100,21 @@ function resolveNativeHostPath() {
47
100
  }
48
101
  function registerWindowsNativeHost(hostName, manifestPath) {
49
102
  const regKey = `HKCU\\SOFTWARE\\Google\\Chrome\\NativeMessagingHosts\\${hostName}`;
50
- execFileSync("reg", ["add", regKey, "/ve", "/t", "REG_SZ", "/d", manifestPath, "/f"], {
103
+ execFileSync2("reg", ["add", regKey, "/ve", "/t", "REG_SZ", "/d", manifestPath, "/f"], {
51
104
  stdio: "pipe"
52
105
  });
53
106
  }
54
107
  function createLauncherScript(hostDir, hostPath, hostName) {
55
- const nodePath = path.resolve(process.execPath);
108
+ const nodePath = path2.resolve(process.execPath);
56
109
  if (process.platform === "win32") {
57
- const launcherPath2 = path.join(hostDir, `${hostName}-launcher.cmd`);
110
+ const launcherPath2 = path2.join(hostDir, `${hostName}-launcher.cmd`);
58
111
  const content2 = `@echo off\r
59
112
  "${nodePath}" "${hostPath}" %*\r
60
113
  `;
61
- fs.writeFileSync(launcherPath2, content2, "utf-8");
114
+ fs2.writeFileSync(launcherPath2, content2, "utf-8");
62
115
  return launcherPath2;
63
116
  }
64
- const launcherPath = path.join(hostDir, `${hostName}-launcher.sh`);
117
+ const launcherPath = path2.join(hostDir, `${hostName}-launcher.sh`);
65
118
  const content = [
66
119
  "#!/bin/sh",
67
120
  `NODE_BIN=${JSON.stringify(nodePath)}`,
@@ -69,8 +122,8 @@ function createLauncherScript(hostDir, hostPath, hostName) {
69
122
  'exec "$NODE_BIN" "$HOST_SCRIPT" "$@"',
70
123
  ""
71
124
  ].join("\n");
72
- fs.writeFileSync(launcherPath, content, "utf-8");
73
- fs.chmodSync(launcherPath, 493);
125
+ fs2.writeFileSync(launcherPath, content, "utf-8");
126
+ fs2.chmodSync(launcherPath, 493);
74
127
  return launcherPath;
75
128
  }
76
129
  async function installNativeHost(options = {}) {
@@ -86,7 +139,7 @@ async function installNativeHost(options = {}) {
86
139
  );
87
140
  return results.every(Boolean);
88
141
  }
89
- const hostName = HOST_NAME_BY_MODE[mode];
142
+ const hostName = MEARL_NATIVE_HOST_NAMES[mode];
90
143
  if (!hostName) {
91
144
  throw new Error(`Unknown mode: ${mode}. Expected 'skills' or 'mcp'.`);
92
145
  }
@@ -96,14 +149,14 @@ async function installNativeHost(options = {}) {
96
149
  try {
97
150
  const hostDir = resolveHostDir();
98
151
  if (skipIfExists) {
99
- const manifestPath2 = path.join(hostDir, `${hostName}.json`);
100
- if (fs.existsSync(manifestPath2)) {
152
+ const manifestPath2 = path2.join(hostDir, `${hostName}.json`);
153
+ if (fs2.existsSync(manifestPath2)) {
101
154
  log(`✅ Already installed: ${manifestPath2}`);
102
155
  return true;
103
156
  }
104
157
  }
105
- if (!fs.existsSync(hostDir)) {
106
- fs.mkdirSync(hostDir, { recursive: true });
158
+ if (!fs2.existsSync(hostDir)) {
159
+ fs2.mkdirSync(hostDir, { recursive: true });
107
160
  log(`✅ Created directory: ${hostDir}`);
108
161
  }
109
162
  const resolvedHostPath = resolveNativeHostPath();
@@ -117,13 +170,13 @@ async function installNativeHost(options = {}) {
117
170
  type: "stdio",
118
171
  allowed_origins: [`chrome-extension://${extensionId}/`]
119
172
  };
120
- const manifestPath = path.join(hostDir, `${hostName}.json`);
121
- fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
173
+ const manifestPath = path2.join(hostDir, `${hostName}.json`);
174
+ fs2.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
122
175
  if (process.platform === "win32") {
123
176
  registerWindowsNativeHost(hostName, manifestPath);
124
177
  log(`✅ Windows registry key created for: ${hostName}`);
125
178
  } else {
126
- fs.chmodSync(manifestPath, 420);
179
+ fs2.chmodSync(manifestPath, 420);
127
180
  }
128
181
  log(`✅ Manifest installed: ${manifestPath}`);
129
182
  log(`✅ Extension ID: ${extensionId}`);
@@ -137,14 +190,18 @@ async function installNativeHost(options = {}) {
137
190
  return false;
138
191
  }
139
192
  }
193
+ async function uninstallNativeHost(options = {}, dependencies = {}) {
194
+ return uninstallNativeHostArtifacts(options, dependencies);
195
+ }
140
196
  function parseCliArgs(argv) {
141
197
  let mode = "both";
142
198
  let extensionId;
143
199
  let skipIfExists = false;
200
+ let uninstall = false;
144
201
  for (let i = 0; i < argv.length; i += 1) {
145
202
  const arg = argv[i];
146
203
  if (arg === "--help" || arg === "-h") {
147
- return { showHelp: true, mode, skipIfExists };
204
+ return { showHelp: true, uninstall, mode, skipIfExists };
148
205
  }
149
206
  if (arg === "--mode" || arg.startsWith("--mode=")) {
150
207
  const value = arg.includes("=") ? arg.slice("--mode=".length) : argv[++i];
@@ -163,24 +220,31 @@ function parseCliArgs(argv) {
163
220
  skipIfExists = true;
164
221
  continue;
165
222
  }
223
+ if (arg === "--uninstall") {
224
+ uninstall = true;
225
+ continue;
226
+ }
166
227
  if (arg === "--init") {
167
228
  continue;
168
229
  }
169
230
  }
170
- return { showHelp: false, mode, extensionId, skipIfExists };
231
+ return { showHelp: false, uninstall, mode, extensionId, skipIfExists };
171
232
  }
172
233
  function printHelp() {
173
234
  console.error(`Usage:
174
235
  mearl-native-host --init [options]
236
+ mearl-native-host --uninstall [options]
175
237
 
176
238
  Options:
177
239
  --mode <skills|mcp|both> Integration mode (default: both — installs both hosts)
178
240
  --extension-id <id> Chrome extension ID (default: ${DEFAULT_EXTENSION_ID})
241
+ --uninstall Remove the selected Native Messaging manifests
179
242
  -h, --help Show this help message
180
243
 
181
244
  Examples:
182
245
  mearl-native-host --init
183
246
  mearl-native-host --init --mode mcp
247
+ mearl-native-host --uninstall --mode skills
184
248
  mearl-native-host --init --extension-id abcdefghijklmnopqrstuvwxyzabcdef`);
185
249
  }
186
250
  async function runCli(argv) {
@@ -194,6 +258,11 @@ async function runCli(argv) {
194
258
  console.error(" Must be a 32-character lowercase string (see chrome://extensions/)");
195
259
  process.exit(1);
196
260
  }
261
+ if (parsed.uninstall) {
262
+ const success2 = await uninstallNativeHost({ mode: parsed.mode });
263
+ if (success2) console.error("\n✅ Uninstall complete!\n");
264
+ process.exit(success2 ? 0 : 1);
265
+ }
197
266
  const success = await installNativeHost({
198
267
  mode: parsed.mode,
199
268
  extensionId: parsed.extensionId,
@@ -216,5 +285,6 @@ Verify:
216
285
  export {
217
286
  installNativeHost,
218
287
  resolveHostDir,
219
- runCli
288
+ runCli,
289
+ uninstallNativeHost
220
290
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mearl/native-host",
3
- "version": "2.2.1",
3
+ "version": "2.3.0",
4
4
  "description": "Native Messaging Host for Mearl — bridges Chrome Extension and local socket server",
5
5
  "type": "module",
6
6
  "main": "dist/install.js",
@@ -32,8 +32,9 @@
32
32
  "@types/node": "^24.9.1",
33
33
  "esbuild": "^0.25.2",
34
34
  "typescript": "^5.8.3",
35
- "@mearl/browser-core": "2.2.1",
36
- "@mearl/daemon-core": "2.2.1"
35
+ "@mearl/daemon-core": "2.3.0",
36
+ "@mearl/browser-core": "2.3.0",
37
+ "@mearl/setup": "2.3.0"
37
38
  },
38
39
  "scripts": {
39
40
  "build": "rm -rf dist/ && node build.mjs",