@mobilenext/mobile-mcp 0.0.29 → 0.0.30
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 +60 -9
- package/lib/android.js +22 -0
- package/lib/ios.js +24 -0
- package/lib/iphone-simulator.js +84 -0
- package/lib/mobilecli.js +53 -0
- package/lib/server.js +33 -20
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -83,7 +83,7 @@ More details in our [wiki page](https://github.com/mobile-next/mobile-mcp/wiki)
|
|
|
83
83
|
|
|
84
84
|
## Installation and configuration
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
**Standard config** works in most of the tools:
|
|
87
87
|
|
|
88
88
|
```json
|
|
89
89
|
{
|
|
@@ -94,23 +94,74 @@ Setup our MCP with Cline, Cursor, Claude, VS Code, Github Copilot:
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
|
|
98
97
|
```
|
|
99
|
-
|
|
98
|
+
|
|
99
|
+
<details>
|
|
100
|
+
<summary>Cline</summary>
|
|
101
|
+
|
|
102
|
+
To setup Cline, just add the json above to your MCP settings file.
|
|
103
|
+
|
|
100
104
|
[More in our wiki](https://github.com/mobile-next/mobile-mcp/wiki/Cline)
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
</details>
|
|
103
107
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
```
|
|
108
|
+
<details>
|
|
109
|
+
<summary>Claude Code</summary>
|
|
107
110
|
|
|
108
|
-
|
|
111
|
+
Use the Claude Code CLI to add the Mobile MCP server:
|
|
109
112
|
|
|
113
|
+
```bash
|
|
114
|
+
claude mcp add mobile-mcp -- npx -y @mobilenext/mobile-mcp@latest
|
|
110
115
|
```
|
|
111
|
-
|
|
116
|
+
|
|
117
|
+
</details>
|
|
118
|
+
|
|
119
|
+
<details>
|
|
120
|
+
<summary>Cursor</summary>
|
|
121
|
+
|
|
122
|
+
#### Click the button to install:
|
|
123
|
+
|
|
124
|
+
[<img src="https://cursor.com/deeplink/mcp-install-dark.svg" alt="Install in Cursor">](https://cursor.com/en/install-mcp?name=Mobile%20MCP&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBtb2JpbGVuZXh0L21vYmlsZS1tY3BAbGF0ZXN0Il19)
|
|
125
|
+
|
|
126
|
+
#### Or install manually:
|
|
127
|
+
|
|
128
|
+
Go to `Cursor Settings` -> `MCP` -> `Add new MCP Server`. Name to your liking, use `command` type with the command `npx -y @mobilenext/mobile-mcp@latest`. You can also verify config or add command like arguments via clicking `Edit`.
|
|
129
|
+
|
|
130
|
+
</details>
|
|
131
|
+
|
|
132
|
+
<details>
|
|
133
|
+
<summary>Gemini CLI</summary>
|
|
134
|
+
|
|
135
|
+
Use the Gemini CLI to add the Mobile MCP server:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
gemini mcp add mobile-mcp npx -y @mobilenext/mobile-mcp@latest
|
|
112
139
|
```
|
|
113
140
|
|
|
141
|
+
</details>
|
|
142
|
+
|
|
143
|
+
<details>
|
|
144
|
+
<summary>Goose</summary>
|
|
145
|
+
|
|
146
|
+
#### Click the button to install:
|
|
147
|
+
|
|
148
|
+
[](https://block.github.io/goose/extension?cmd=npx&arg=-y&arg=%40mobilenext%2Fmobile-mcp%40latest&id=mobile-mcp&name=Mobile%20MCP&description=Mobile%20automation%20and%20development%20for%20iOS%2C%20Android%2C%20simulators%2C%20emulators%2C%20and%20real%20devices)
|
|
149
|
+
|
|
150
|
+
#### Or install manually:
|
|
151
|
+
|
|
152
|
+
Go to `Advanced settings` -> `Extensions` -> `Add custom extension`. Name to your liking, use type `STDIO`, and set the `command` to `npx -y @mobilenext/mobile-mcp@latest`. Click "Add Extension".
|
|
153
|
+
|
|
154
|
+
</details>
|
|
155
|
+
|
|
156
|
+
<details>
|
|
157
|
+
<summary>Qodo Gen</summary>
|
|
158
|
+
|
|
159
|
+
Open [Qodo Gen](https://docs.qodo.ai/qodo-documentation/qodo-gen) chat panel in VSCode or IntelliJ → Connect more tools → + Add new MCP → Paste the standard config above.
|
|
160
|
+
|
|
161
|
+
Click <code>Save</code>.
|
|
162
|
+
|
|
163
|
+
</details>
|
|
164
|
+
|
|
114
165
|
[Read more in our wiki](https://github.com/mobile-next/mobile-mcp/wiki)! 🚀
|
|
115
166
|
|
|
116
167
|
|
package/lib/android.js
CHANGED
|
@@ -273,6 +273,28 @@ class AndroidRobot {
|
|
|
273
273
|
async terminateApp(packageName) {
|
|
274
274
|
this.adb("shell", "am", "force-stop", packageName);
|
|
275
275
|
}
|
|
276
|
+
async installApp(path) {
|
|
277
|
+
try {
|
|
278
|
+
this.adb("install", "-r", path);
|
|
279
|
+
}
|
|
280
|
+
catch (error) {
|
|
281
|
+
const stdout = error.stdout ? error.stdout.toString() : "";
|
|
282
|
+
const stderr = error.stderr ? error.stderr.toString() : "";
|
|
283
|
+
const output = (stdout + stderr).trim();
|
|
284
|
+
throw new robot_1.ActionableError(output || error.message);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async uninstallApp(bundleId) {
|
|
288
|
+
try {
|
|
289
|
+
this.adb("uninstall", bundleId);
|
|
290
|
+
}
|
|
291
|
+
catch (error) {
|
|
292
|
+
const stdout = error.stdout ? error.stdout.toString() : "";
|
|
293
|
+
const stderr = error.stderr ? error.stderr.toString() : "";
|
|
294
|
+
const output = (stdout + stderr).trim();
|
|
295
|
+
throw new robot_1.ActionableError(output || error.message);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
276
298
|
async openUrl(url) {
|
|
277
299
|
this.adb("shell", "am", "start", "-a", "android.intent.action.VIEW", "-d", url);
|
|
278
300
|
}
|
package/lib/ios.js
CHANGED
|
@@ -101,6 +101,30 @@ class IosRobot {
|
|
|
101
101
|
await this.assertTunnelRunning();
|
|
102
102
|
await this.ios("kill", packageName);
|
|
103
103
|
}
|
|
104
|
+
async installApp(path) {
|
|
105
|
+
await this.assertTunnelRunning();
|
|
106
|
+
try {
|
|
107
|
+
await this.ios("install", "--path", path);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const stdout = error.stdout ? error.stdout.toString() : "";
|
|
111
|
+
const stderr = error.stderr ? error.stderr.toString() : "";
|
|
112
|
+
const output = (stdout + stderr).trim();
|
|
113
|
+
throw new robot_1.ActionableError(output || error.message);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async uninstallApp(bundleId) {
|
|
117
|
+
await this.assertTunnelRunning();
|
|
118
|
+
try {
|
|
119
|
+
await this.ios("uninstall", "--bundleid", bundleId);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
const stdout = error.stdout ? error.stdout.toString() : "";
|
|
123
|
+
const stderr = error.stderr ? error.stderr.toString() : "";
|
|
124
|
+
const output = (stdout + stderr).trim();
|
|
125
|
+
throw new robot_1.ActionableError(output || error.message);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
104
128
|
async openUrl(url) {
|
|
105
129
|
const wda = await this.wda();
|
|
106
130
|
await wda.openUrl(url);
|
package/lib/iphone-simulator.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SimctlManager = exports.Simctl = void 0;
|
|
4
4
|
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
const node_fs_1 = require("node:fs");
|
|
6
|
+
const node_os_1 = require("node:os");
|
|
7
|
+
const node_path_1 = require("node:path");
|
|
5
8
|
const logger_1 = require("./logger");
|
|
6
9
|
const webdriver_agent_1 = require("./webdriver-agent");
|
|
7
10
|
const robot_1 = require("./robot");
|
|
@@ -73,6 +76,87 @@ class Simctl {
|
|
|
73
76
|
async terminateApp(packageName) {
|
|
74
77
|
this.simctl("terminate", this.simulatorUuid, packageName);
|
|
75
78
|
}
|
|
79
|
+
findAppBundle(dir) {
|
|
80
|
+
const entries = (0, node_fs_1.readdirSync)(dir, { withFileTypes: true });
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
if (entry.isDirectory() && entry.name.endsWith(".app")) {
|
|
83
|
+
return (0, node_path_1.join)(dir, entry.name);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
validateZipPaths(zipPath) {
|
|
89
|
+
const output = (0, node_child_process_1.execFileSync)("/usr/bin/zipinfo", ["-1", zipPath], {
|
|
90
|
+
timeout: TIMEOUT,
|
|
91
|
+
maxBuffer: MAX_BUFFER_SIZE,
|
|
92
|
+
}).toString();
|
|
93
|
+
const invalidPath = output
|
|
94
|
+
.split("\n")
|
|
95
|
+
.map(s => s.trim())
|
|
96
|
+
.filter(s => s)
|
|
97
|
+
.find(s => s.startsWith("/") || s.includes(".."));
|
|
98
|
+
if (invalidPath) {
|
|
99
|
+
throw new robot_1.ActionableError(`Security violation: File path '${invalidPath}' contains invalid characters`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
async installApp(path) {
|
|
103
|
+
let tempDir = null;
|
|
104
|
+
let installPath = path;
|
|
105
|
+
try {
|
|
106
|
+
// zip files need to be extracted prior to installation
|
|
107
|
+
if ((0, node_path_1.extname)(path).toLowerCase() === ".zip") {
|
|
108
|
+
(0, logger_1.trace)(`Detected .zip file, validating contents`);
|
|
109
|
+
// before extracting, let's make sure there's no zip-slip bombs here
|
|
110
|
+
this.validateZipPaths(path);
|
|
111
|
+
tempDir = (0, node_fs_1.mkdtempSync)((0, node_path_1.join)((0, node_os_1.tmpdir)(), "ios-app-"));
|
|
112
|
+
try {
|
|
113
|
+
(0, node_child_process_1.execFileSync)("unzip", ["-q", path, "-d", tempDir], {
|
|
114
|
+
timeout: TIMEOUT,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
throw new robot_1.ActionableError(`Failed to unzip file: ${error.message}`);
|
|
119
|
+
}
|
|
120
|
+
const appBundle = this.findAppBundle(tempDir);
|
|
121
|
+
if (!appBundle) {
|
|
122
|
+
throw new robot_1.ActionableError("No .app bundle found in the .zip file, please visit wiki at https://github.com/mobile-next/mobile-mcp/wiki for assistance.");
|
|
123
|
+
}
|
|
124
|
+
installPath = appBundle;
|
|
125
|
+
(0, logger_1.trace)(`Found .app bundle at: ${(0, node_path_1.basename)(appBundle)}`);
|
|
126
|
+
}
|
|
127
|
+
// continue with installation
|
|
128
|
+
this.simctl("install", this.simulatorUuid, installPath);
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
const stdout = error.stdout ? error.stdout.toString() : "";
|
|
132
|
+
const stderr = error.stderr ? error.stderr.toString() : "";
|
|
133
|
+
const output = (stdout + stderr).trim();
|
|
134
|
+
throw new robot_1.ActionableError(output || error.message);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
// Clean up temporary directory if it was created
|
|
138
|
+
if (tempDir) {
|
|
139
|
+
try {
|
|
140
|
+
(0, logger_1.trace)(`Cleaning up temporary directory`);
|
|
141
|
+
(0, node_fs_1.rmSync)(tempDir, { recursive: true, force: true });
|
|
142
|
+
}
|
|
143
|
+
catch (cleanupError) {
|
|
144
|
+
(0, logger_1.trace)(`Warning: Failed to cleanup temporary directory: ${cleanupError}`);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async uninstallApp(bundleId) {
|
|
150
|
+
try {
|
|
151
|
+
this.simctl("uninstall", this.simulatorUuid, bundleId);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
const stdout = error.stdout ? error.stdout.toString() : "";
|
|
155
|
+
const stderr = error.stderr ? error.stderr.toString() : "";
|
|
156
|
+
const output = (stdout + stderr).trim();
|
|
157
|
+
throw new robot_1.ActionableError(output || error.message);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
76
160
|
async listApps() {
|
|
77
161
|
const text = this.simctl("listapps", this.simulatorUuid).toString();
|
|
78
162
|
const result = (0, node_child_process_1.execFileSync)("plutil", ["-convert", "json", "-o", "-", "-r", "-"], {
|
package/lib/mobilecli.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getMobilecliPath = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
const getMobilecliPath = () => {
|
|
7
|
+
if (process.env.MOBILECLI_PATH) {
|
|
8
|
+
return process.env.MOBILECLI_PATH;
|
|
9
|
+
}
|
|
10
|
+
const platform = process.platform;
|
|
11
|
+
let binaryName = "mobilecli";
|
|
12
|
+
switch (platform) {
|
|
13
|
+
case "darwin":
|
|
14
|
+
binaryName += "-darwin";
|
|
15
|
+
break;
|
|
16
|
+
case "linux":
|
|
17
|
+
const arch = process.arch;
|
|
18
|
+
if (arch === "arm64") {
|
|
19
|
+
binaryName += "-linux-arm64";
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
binaryName += "-linux-amd64";
|
|
23
|
+
}
|
|
24
|
+
break;
|
|
25
|
+
case "win32":
|
|
26
|
+
binaryName += "-windows-amd64.exe";
|
|
27
|
+
break;
|
|
28
|
+
default:
|
|
29
|
+
throw new Error(`Unsupported platform: ${platform}`);
|
|
30
|
+
}
|
|
31
|
+
// Check if mobile-mcp is installed as a package
|
|
32
|
+
const currentPath = __filename;
|
|
33
|
+
const pathParts = currentPath.split(node_path_1.sep);
|
|
34
|
+
const lastNodeModulesIndex = pathParts.lastIndexOf("node_modules");
|
|
35
|
+
if (lastNodeModulesIndex !== -1) {
|
|
36
|
+
// We're inside node_modules, go to the last node_modules in the path
|
|
37
|
+
const nodeModulesParts = pathParts.slice(0, lastNodeModulesIndex + 1);
|
|
38
|
+
const lastNodeModulesPath = nodeModulesParts.join(node_path_1.sep);
|
|
39
|
+
const mobilecliPath = (0, node_path_1.join)(lastNodeModulesPath, "@mobilenext", "mobilecli", "bin", binaryName);
|
|
40
|
+
if ((0, node_fs_1.existsSync)(mobilecliPath)) {
|
|
41
|
+
return mobilecliPath;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Not in node_modules, look one directory up from current script
|
|
45
|
+
const scriptDir = (0, node_path_1.dirname)(__filename);
|
|
46
|
+
const parentDir = (0, node_path_1.dirname)(scriptDir);
|
|
47
|
+
const mobilecliPath = (0, node_path_1.join)(parentDir, "node_modules", "@mobilenext", "mobilecli", "bin", binaryName);
|
|
48
|
+
if ((0, node_fs_1.existsSync)(mobilecliPath)) {
|
|
49
|
+
return mobilecliPath;
|
|
50
|
+
}
|
|
51
|
+
throw new Error(`Could not find mobilecli binary for platform: ${platform}`);
|
|
52
|
+
};
|
|
53
|
+
exports.getMobilecliPath = getMobilecliPath;
|
package/lib/server.js
CHANGED
|
@@ -9,6 +9,7 @@ const zod_1 = require("zod");
|
|
|
9
9
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
10
|
const node_os_1 = __importDefault(require("node:os"));
|
|
11
11
|
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
12
13
|
const logger_1 = require("./logger");
|
|
13
14
|
const android_1 = require("./android");
|
|
14
15
|
const robot_1 = require("./robot");
|
|
@@ -16,28 +17,12 @@ const iphone_simulator_1 = require("./iphone-simulator");
|
|
|
16
17
|
const ios_1 = require("./ios");
|
|
17
18
|
const png_1 = require("./png");
|
|
18
19
|
const image_utils_1 = require("./image-utils");
|
|
20
|
+
const mobilecli_1 = require("./mobilecli");
|
|
19
21
|
const getAgentVersion = () => {
|
|
20
22
|
const json = require("../package.json");
|
|
21
23
|
return json.version;
|
|
22
24
|
};
|
|
23
25
|
exports.getAgentVersion = getAgentVersion;
|
|
24
|
-
const getLatestAgentVersion = async () => {
|
|
25
|
-
const response = await fetch("https://api.github.com/repos/mobile-next/mobile-mcp/tags?per_page=1");
|
|
26
|
-
const json = await response.json();
|
|
27
|
-
return json[0].name;
|
|
28
|
-
};
|
|
29
|
-
const checkForLatestAgentVersion = async () => {
|
|
30
|
-
try {
|
|
31
|
-
const latestVersion = await getLatestAgentVersion();
|
|
32
|
-
const currentVersion = (0, exports.getAgentVersion)();
|
|
33
|
-
if (latestVersion !== currentVersion) {
|
|
34
|
-
(0, logger_1.trace)(`You are running an older version of the agent. Please update to the latest version: ${latestVersion}.`);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
// ignore
|
|
39
|
-
}
|
|
40
|
-
};
|
|
41
26
|
const createMcpServer = () => {
|
|
42
27
|
const server = new mcp_js_1.McpServer({
|
|
43
28
|
name: "mobile-mcp",
|
|
@@ -110,7 +95,21 @@ const createMcpServer = () => {
|
|
|
110
95
|
// ignore
|
|
111
96
|
}
|
|
112
97
|
};
|
|
113
|
-
|
|
98
|
+
const getMobilecliVersion = () => {
|
|
99
|
+
try {
|
|
100
|
+
const path = (0, mobilecli_1.getMobilecliPath)();
|
|
101
|
+
const output = (0, node_child_process_1.execFileSync)(path, ["--version"], { encoding: "utf8" }).toString().trim();
|
|
102
|
+
if (output.startsWith("mobilecli version ")) {
|
|
103
|
+
return output.substring("mobilecli version ".length);
|
|
104
|
+
}
|
|
105
|
+
return "failed";
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
return "failed " + error.message;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const mobilecliVersion = getMobilecliVersion();
|
|
112
|
+
posthog("launch", { "MobilecliVersion": mobilecliVersion }).then();
|
|
114
113
|
const simulatorManager = new iphone_simulator_1.SimctlManager();
|
|
115
114
|
const getRobotFromDevice = (device) => {
|
|
116
115
|
const iosManager = new ios_1.IosManager();
|
|
@@ -185,6 +184,22 @@ const createMcpServer = () => {
|
|
|
185
184
|
await robot.terminateApp(packageName);
|
|
186
185
|
return `Terminated app ${packageName}`;
|
|
187
186
|
});
|
|
187
|
+
tool("mobile_install_app", "Install an app on mobile device", {
|
|
188
|
+
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
189
|
+
path: zod_1.z.string().describe("The path to the app file to install. For iOS simulators, provide a .zip file or a .app directory. For Android provide an .apk file. For iOS real devices provide an .ipa file"),
|
|
190
|
+
}, async ({ device, path }) => {
|
|
191
|
+
const robot = getRobotFromDevice(device);
|
|
192
|
+
await robot.installApp(path);
|
|
193
|
+
return `Installed app from ${path}`;
|
|
194
|
+
});
|
|
195
|
+
tool("mobile_uninstall_app", "Uninstall an app from mobile device", {
|
|
196
|
+
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you."),
|
|
197
|
+
bundle_id: zod_1.z.string().describe("Bundle identifier (iOS) or package name (Android) of the app to be uninstalled"),
|
|
198
|
+
}, async ({ device, bundle_id }) => {
|
|
199
|
+
const robot = getRobotFromDevice(device);
|
|
200
|
+
await robot.uninstallApp(bundle_id);
|
|
201
|
+
return `Uninstalled app ${bundle_id}`;
|
|
202
|
+
});
|
|
188
203
|
tool("mobile_get_screen_size", "Get the screen size of the mobile device in pixels", {
|
|
189
204
|
device: zod_1.z.string().describe("The device identifier to use. Use mobile_list_available_devices to find which devices are available to you.")
|
|
190
205
|
}, async ({ device }) => {
|
|
@@ -348,8 +363,6 @@ const createMcpServer = () => {
|
|
|
348
363
|
const orientation = await robot.getOrientation();
|
|
349
364
|
return `Current device orientation is ${orientation}`;
|
|
350
365
|
});
|
|
351
|
-
// async check for latest agent version
|
|
352
|
-
checkForLatestAgentVersion().then();
|
|
353
366
|
return server;
|
|
354
367
|
};
|
|
355
368
|
exports.createMcpServer = createMcpServer;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mobilenext/mobile-mcp",
|
|
3
3
|
"mcpName": "io.github.mobile-next/mobile-mcp",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.30",
|
|
5
5
|
"description": "Mobile MCP",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
"fast-xml-parser": "5.2.5",
|
|
31
31
|
"zod-to-json-schema": "3.24.6"
|
|
32
32
|
},
|
|
33
|
+
"optionalDependencies": {
|
|
34
|
+
"@mobilenext/mobilecli": "0.0.27"
|
|
35
|
+
},
|
|
33
36
|
"devDependencies": {
|
|
34
37
|
"@eslint/eslintrc": "^3.2.0",
|
|
35
38
|
"@eslint/js": "^9.19.0",
|