replicant-mcp 1.4.7 → 1.5.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/README.md +28 -2
- package/dist/adapters/adb.d.ts +2 -0
- package/dist/adapters/adb.js +12 -1
- package/dist/adapters/ui-fallback-find.js +26 -1
- package/dist/cli/doctor.d.ts +10 -0
- package/dist/cli/doctor.js +154 -0
- package/dist/cli/emulator.js +2 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli.js +5 -3
- package/dist/index.js +22 -5
- package/dist/parsers/gradle-output.d.ts +1 -0
- package/dist/parsers/gradle-output.js +7 -0
- package/dist/server.js +39 -19
- package/dist/services/config.js +3 -2
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/services/process-runner.d.ts +1 -0
- package/dist/services/process-runner.js +51 -0
- package/dist/services/test-baseline.d.ts +18 -0
- package/dist/services/test-baseline.js +51 -0
- package/dist/tools/adb-app.js +19 -13
- package/dist/tools/adb-device.js +7 -6
- package/dist/tools/adb-logcat.d.ts +3 -3
- package/dist/tools/adb-logcat.js +4 -2
- package/dist/tools/adb-shell.d.ts +15 -0
- package/dist/tools/adb-shell.js +31 -3
- package/dist/tools/cache.js +2 -1
- package/dist/tools/emulator-device.js +18 -13
- package/dist/tools/gradle-get-details.d.ts +15 -0
- package/dist/tools/gradle-get-details.js +131 -40
- package/dist/tools/gradle-list.js +2 -2
- package/dist/tools/gradle-test.d.ts +14 -0
- package/dist/tools/gradle-test.js +71 -6
- package/dist/tools/rtfm.js +4 -4
- package/dist/tools/ui-find.js +87 -50
- package/dist/tools/ui.d.ts +7 -0
- package/dist/tools/ui.js +20 -12
- package/dist/types/errors.d.ts +3 -0
- package/dist/types/errors.js +4 -0
- package/dist/types/icon-recognition.d.ts +5 -0
- package/dist/types/schemas/adb-app-output.d.ts +83 -0
- package/dist/types/schemas/adb-app-output.js +60 -0
- package/dist/types/schemas/adb-device-output.d.ts +148 -0
- package/dist/types/schemas/adb-device-output.js +72 -0
- package/dist/types/schemas/adb-logcat-output.d.ts +15 -0
- package/dist/types/schemas/adb-logcat-output.js +14 -0
- package/dist/types/schemas/adb-shell-output.d.ts +17 -0
- package/dist/types/schemas/adb-shell-output.js +16 -0
- package/dist/types/schemas/cache-output.d.ts +88 -0
- package/dist/types/schemas/cache-output.js +51 -0
- package/dist/types/schemas/emulator-device-output.d.ts +100 -0
- package/dist/types/schemas/emulator-device-output.js +76 -0
- package/dist/types/schemas/gradle-build-output.d.ts +16 -0
- package/dist/types/schemas/gradle-build-output.js +15 -0
- package/dist/types/schemas/gradle-get-details-output.d.ts +129 -0
- package/dist/types/schemas/gradle-get-details-output.js +86 -0
- package/dist/types/schemas/gradle-list-output.d.ts +56 -0
- package/dist/types/schemas/gradle-list-output.js +39 -0
- package/dist/types/schemas/gradle-test-output.d.ts +82 -0
- package/dist/types/schemas/gradle-test-output.js +54 -0
- package/dist/types/schemas/index.d.ts +12 -0
- package/dist/types/schemas/index.js +12 -0
- package/dist/types/schemas/rtfm-output.d.ts +8 -0
- package/dist/types/schemas/rtfm-output.js +7 -0
- package/dist/types/schemas/ui-output.d.ts +361 -0
- package/dist/types/schemas/ui-output.js +188 -0
- package/dist/utils/logger.d.ts +6 -0
- package/dist/utils/logger.js +34 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +4 -0
- package/docs/contracts/replicant-mcp.contract.json +2361 -0
- package/docs/rtfm/adb.md +11 -1
- package/docs/rtfm/build.md +7 -1
- package/docs/rtfm/cache.md +21 -0
- package/docs/rtfm/ui.md +16 -7
- package/package.json +13 -5
|
@@ -7,6 +7,20 @@ const BLOCKED_PATTERNS = [
|
|
|
7
7
|
/^sudo(\s|$)/, // sudo
|
|
8
8
|
/\bformat\b/, // format commands
|
|
9
9
|
];
|
|
10
|
+
const BLOCKED_SHELL_PATTERNS = [
|
|
11
|
+
/^rm\s+(-[rf]+\s+)*\/\s*$/, // rm -rf / (root itself)
|
|
12
|
+
/^rm\s+(-[rf]+\s+)*\/(system|vendor|oem|product)(\/|\s|$)/, // rm on system partitions
|
|
13
|
+
/^su(\s|$)/, // su
|
|
14
|
+
/^sudo(\s|$)/, // sudo
|
|
15
|
+
/\bformat\b/, // format commands
|
|
16
|
+
/^setprop\s+persist\./, // persistent property changes
|
|
17
|
+
/^dd\s/, // raw disk operations
|
|
18
|
+
/^mkfs/, // filesystem creation
|
|
19
|
+
/^flash/, // flash operations
|
|
20
|
+
/^wipe/, // wipe data/cache
|
|
21
|
+
/^recovery\b/, // recovery mode
|
|
22
|
+
/^reboot\b/, // reboot device (also in BLOCKED_COMMANDS)
|
|
23
|
+
];
|
|
10
24
|
export class ProcessRunner {
|
|
11
25
|
environment;
|
|
12
26
|
defaultTimeoutMs = 30_000;
|
|
@@ -74,5 +88,42 @@ export class ProcessRunner {
|
|
|
74
88
|
throw new ReplicantError(ErrorCode.COMMAND_BLOCKED, `Command '${fullCommand}' is not allowed`, "Use safe commands only");
|
|
75
89
|
}
|
|
76
90
|
}
|
|
91
|
+
this.validateShellPayload(command, args);
|
|
92
|
+
}
|
|
93
|
+
validateShellPayload(command, args) {
|
|
94
|
+
// Only validate shell payloads for adb commands
|
|
95
|
+
const basename = command.split("/").pop() ?? command;
|
|
96
|
+
if (basename !== "adb")
|
|
97
|
+
return;
|
|
98
|
+
const shellIndex = args.indexOf("shell");
|
|
99
|
+
if (shellIndex === -1 || shellIndex >= args.length - 1)
|
|
100
|
+
return;
|
|
101
|
+
let payloadArgs = args.slice(shellIndex + 1);
|
|
102
|
+
// Strip leading "--" (end-of-options marker)
|
|
103
|
+
if (payloadArgs[0] === "--") {
|
|
104
|
+
payloadArgs = payloadArgs.slice(1);
|
|
105
|
+
}
|
|
106
|
+
const shellPayload = payloadArgs.join(" ").trim();
|
|
107
|
+
if (!shellPayload)
|
|
108
|
+
return;
|
|
109
|
+
// Block shell metacharacters that enable command chaining/substitution.
|
|
110
|
+
// $letter/$(/$ are blocked (variable expansion, command substitution) but
|
|
111
|
+
// bare $ before digits is allowed (e.g., input text '$100').
|
|
112
|
+
if (/[;&|`()]|\$[({a-zA-Z_]/.test(shellPayload)) {
|
|
113
|
+
throw new ReplicantError(ErrorCode.COMMAND_BLOCKED, "Shell metacharacters are not allowed in shell commands", "Use simple commands without chaining, pipes, or substitution");
|
|
114
|
+
}
|
|
115
|
+
// Block shell wrapper commands (sh -c, bash -c)
|
|
116
|
+
if (/^(sh|bash|dash|zsh)\s+-c\b/.test(shellPayload)) {
|
|
117
|
+
throw new ReplicantError(ErrorCode.COMMAND_BLOCKED, "Shell interpreters with -c are not allowed", "Run the command directly without a shell wrapper");
|
|
118
|
+
}
|
|
119
|
+
const shellCommand = shellPayload.split(/\s+/)[0];
|
|
120
|
+
if (BLOCKED_COMMANDS.has(shellCommand)) {
|
|
121
|
+
throw new ReplicantError(ErrorCode.COMMAND_BLOCKED, `Shell command '${shellPayload}' is not allowed`, "Use safe commands only");
|
|
122
|
+
}
|
|
123
|
+
for (const pattern of BLOCKED_SHELL_PATTERNS) {
|
|
124
|
+
if (pattern.test(shellPayload)) {
|
|
125
|
+
throw new ReplicantError(ErrorCode.COMMAND_BLOCKED, `Shell command '${shellPayload}' is not allowed`, "Use safe commands only");
|
|
126
|
+
}
|
|
127
|
+
}
|
|
77
128
|
}
|
|
78
129
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface BaselineTestResult {
|
|
2
|
+
test: string;
|
|
3
|
+
status: "pass" | "fail" | "skip";
|
|
4
|
+
}
|
|
5
|
+
export interface TestBaseline {
|
|
6
|
+
savedAt: string;
|
|
7
|
+
task: string;
|
|
8
|
+
results: BaselineTestResult[];
|
|
9
|
+
}
|
|
10
|
+
export interface Regression {
|
|
11
|
+
test: string;
|
|
12
|
+
previousStatus: string;
|
|
13
|
+
currentStatus: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function saveBaseline(taskName: string, results: BaselineTestResult[]): void;
|
|
16
|
+
export declare function loadBaseline(taskName: string): TestBaseline | null;
|
|
17
|
+
export declare function clearBaseline(taskName: string): void;
|
|
18
|
+
export declare function compareResults(baseline: TestBaseline, current: BaselineTestResult[]): Regression[];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import * as path from "path";
|
|
3
|
+
import * as os from "os";
|
|
4
|
+
function getBaselineDir() {
|
|
5
|
+
const base = process.cwd() === "/" ? os.homedir() : process.cwd();
|
|
6
|
+
return path.join(base, ".replicant", "test-baselines");
|
|
7
|
+
}
|
|
8
|
+
function getBaselinePath(taskName) {
|
|
9
|
+
const safe = taskName.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
10
|
+
return path.join(getBaselineDir(), `${safe}.json`);
|
|
11
|
+
}
|
|
12
|
+
export function saveBaseline(taskName, results) {
|
|
13
|
+
const dir = getBaselineDir();
|
|
14
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
15
|
+
const baseline = {
|
|
16
|
+
savedAt: new Date().toISOString(),
|
|
17
|
+
task: taskName,
|
|
18
|
+
results,
|
|
19
|
+
};
|
|
20
|
+
fs.writeFileSync(getBaselinePath(taskName), JSON.stringify(baseline, null, 2));
|
|
21
|
+
}
|
|
22
|
+
export function loadBaseline(taskName) {
|
|
23
|
+
const filePath = getBaselinePath(taskName);
|
|
24
|
+
if (!fs.existsSync(filePath))
|
|
25
|
+
return null;
|
|
26
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
27
|
+
return JSON.parse(content);
|
|
28
|
+
}
|
|
29
|
+
export function clearBaseline(taskName) {
|
|
30
|
+
const filePath = getBaselinePath(taskName);
|
|
31
|
+
if (fs.existsSync(filePath)) {
|
|
32
|
+
fs.unlinkSync(filePath);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export function compareResults(baseline, current) {
|
|
36
|
+
const currentMap = new Map(current.map(r => [r.test, r.status]));
|
|
37
|
+
const regressions = [];
|
|
38
|
+
for (const prev of baseline.results) {
|
|
39
|
+
if (prev.status === "pass") {
|
|
40
|
+
const currentStatus = currentMap.get(prev.test);
|
|
41
|
+
if (currentStatus && currentStatus !== "pass") {
|
|
42
|
+
regressions.push({
|
|
43
|
+
test: prev.test,
|
|
44
|
+
previousStatus: prev.status,
|
|
45
|
+
currentStatus,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return regressions;
|
|
51
|
+
}
|
package/dist/tools/adb-app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { CACHE_TTLS } from "../types/index.js";
|
|
2
|
+
import { CACHE_TTLS, ReplicantError, ErrorCode } from "../types/index.js";
|
|
3
3
|
export const adbAppInputSchema = z.object({
|
|
4
4
|
operation: z.enum(["install", "uninstall", "launch", "stop", "clear-data", "list"]),
|
|
5
5
|
apkPath: z.string().optional(),
|
|
@@ -10,32 +10,37 @@ export const adbAppInputSchema = z.object({
|
|
|
10
10
|
offset: z.number().min(0).optional(),
|
|
11
11
|
});
|
|
12
12
|
async function handleInstall(input, deviceId, context) {
|
|
13
|
-
if (!input.apkPath)
|
|
14
|
-
throw new
|
|
13
|
+
if (!input.apkPath) {
|
|
14
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "apkPath is required for install operation", "Provide the path to the APK file to install");
|
|
15
|
+
}
|
|
15
16
|
await context.adb.install(deviceId, input.apkPath);
|
|
16
17
|
return { installed: input.apkPath, deviceId };
|
|
17
18
|
}
|
|
18
19
|
async function handleUninstall(input, deviceId, context) {
|
|
19
|
-
if (!input.packageName)
|
|
20
|
-
throw new
|
|
20
|
+
if (!input.packageName) {
|
|
21
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "packageName is required for uninstall operation", "Provide the package name to uninstall");
|
|
22
|
+
}
|
|
21
23
|
await context.adb.uninstall(deviceId, input.packageName);
|
|
22
24
|
return { uninstalled: input.packageName, deviceId };
|
|
23
25
|
}
|
|
24
26
|
async function handleLaunch(input, deviceId, context) {
|
|
25
|
-
if (!input.packageName)
|
|
26
|
-
throw new
|
|
27
|
+
if (!input.packageName) {
|
|
28
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "packageName is required for launch operation", "Provide the package name to launch");
|
|
29
|
+
}
|
|
27
30
|
await context.adb.launch(deviceId, input.packageName);
|
|
28
31
|
return { launched: input.packageName, deviceId };
|
|
29
32
|
}
|
|
30
33
|
async function handleStop(input, deviceId, context) {
|
|
31
|
-
if (!input.packageName)
|
|
32
|
-
throw new
|
|
34
|
+
if (!input.packageName) {
|
|
35
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "packageName is required for stop operation", "Provide the package name to stop");
|
|
36
|
+
}
|
|
33
37
|
await context.adb.stop(deviceId, input.packageName);
|
|
34
38
|
return { stopped: input.packageName, deviceId };
|
|
35
39
|
}
|
|
36
40
|
async function handleClearData(input, deviceId, context) {
|
|
37
|
-
if (!input.packageName)
|
|
38
|
-
throw new
|
|
41
|
+
if (!input.packageName) {
|
|
42
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "packageName is required for clear-data operation", "Provide the package name to clear data for");
|
|
43
|
+
}
|
|
39
44
|
await context.adb.clearData(deviceId, input.packageName);
|
|
40
45
|
return { cleared: input.packageName, deviceId };
|
|
41
46
|
}
|
|
@@ -73,8 +78,9 @@ const operations = {
|
|
|
73
78
|
export async function handleAdbAppTool(input, context) {
|
|
74
79
|
const device = await context.deviceState.ensureDevice(context.adb);
|
|
75
80
|
const handler = operations[input.operation];
|
|
76
|
-
if (!handler)
|
|
77
|
-
throw new
|
|
81
|
+
if (!handler) {
|
|
82
|
+
throw new ReplicantError(ErrorCode.INVALID_OPERATION, `Unknown operation: ${input.operation}`, "Valid operations: install, uninstall, launch, stop, clear-data, list");
|
|
83
|
+
}
|
|
78
84
|
return handler(input, device.id, context);
|
|
79
85
|
}
|
|
80
86
|
export const adbAppToolDefinition = {
|
package/dist/tools/adb-device.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { CACHE_TTLS } from "../types/index.js";
|
|
2
|
+
import { CACHE_TTLS, ReplicantError, ErrorCode } from "../types/index.js";
|
|
3
3
|
export const adbDeviceInputSchema = z.object({
|
|
4
4
|
operation: z.enum(["list", "select", "wait", "properties", "health-check"]),
|
|
5
5
|
deviceId: z.string().optional(),
|
|
@@ -16,12 +16,12 @@ async function handleList(input, context) {
|
|
|
16
16
|
}
|
|
17
17
|
async function handleSelect(input, context) {
|
|
18
18
|
if (!input.deviceId) {
|
|
19
|
-
throw new
|
|
19
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "deviceId is required for select operation", "Use adb-device list to see available devices, then provide a deviceId");
|
|
20
20
|
}
|
|
21
21
|
const devices = await context.adb.getDevices();
|
|
22
22
|
const device = devices.find((d) => d.id === input.deviceId);
|
|
23
23
|
if (!device) {
|
|
24
|
-
throw new
|
|
24
|
+
throw new ReplicantError(ErrorCode.DEVICE_NOT_FOUND, `Device ${input.deviceId} not found`, "Use adb-device list to see available devices");
|
|
25
25
|
}
|
|
26
26
|
context.deviceState.setCurrentDevice(device);
|
|
27
27
|
return { selected: device };
|
|
@@ -77,7 +77,7 @@ async function handleHealthCheck(input, context) {
|
|
|
77
77
|
warnings.push("No devices connected. Start an emulator or connect a USB device.");
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
-
catch
|
|
80
|
+
catch {
|
|
81
81
|
errors.push("adb server not responding. Run 'adb kill-server && adb start-server'");
|
|
82
82
|
}
|
|
83
83
|
}
|
|
@@ -103,8 +103,9 @@ const operations = {
|
|
|
103
103
|
};
|
|
104
104
|
export async function handleAdbDeviceTool(input, context) {
|
|
105
105
|
const handler = operations[input.operation];
|
|
106
|
-
if (!handler)
|
|
107
|
-
throw new
|
|
106
|
+
if (!handler) {
|
|
107
|
+
throw new ReplicantError(ErrorCode.INVALID_OPERATION, `Unknown operation: ${input.operation}`, "Valid operations: list, select, wait, properties, health-check");
|
|
108
|
+
}
|
|
108
109
|
return handler(input, context);
|
|
109
110
|
}
|
|
110
111
|
export const adbDeviceToolDefinition = {
|
|
@@ -5,11 +5,11 @@ export declare const adbLogcatInputSchema: z.ZodObject<{
|
|
|
5
5
|
package: z.ZodOptional<z.ZodString>;
|
|
6
6
|
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
7
7
|
level: z.ZodOptional<z.ZodEnum<{
|
|
8
|
-
|
|
8
|
+
error: "error";
|
|
9
|
+
warn: "warn";
|
|
9
10
|
info: "info";
|
|
11
|
+
debug: "debug";
|
|
10
12
|
verbose: "verbose";
|
|
11
|
-
warn: "warn";
|
|
12
|
-
error: "error";
|
|
13
13
|
}>>;
|
|
14
14
|
rawFilter: z.ZodOptional<z.ZodString>;
|
|
15
15
|
since: z.ZodOptional<z.ZodString>;
|
package/dist/tools/adb-logcat.js
CHANGED
|
@@ -35,10 +35,12 @@ export async function handleAdbLogcatTool(input, context) {
|
|
|
35
35
|
const output = await context.adb.logcat(deviceId, {
|
|
36
36
|
lines: input.lines,
|
|
37
37
|
filter: filter || undefined,
|
|
38
|
+
since: input.since,
|
|
39
|
+
package: input.package,
|
|
38
40
|
});
|
|
39
41
|
// Cache the full output and return a summary
|
|
40
42
|
const logId = context.cache.generateId("logcat");
|
|
41
|
-
context.cache.set(logId, { output, deviceId, filter }, "logcat", CACHE_TTLS.
|
|
43
|
+
context.cache.set(logId, { output, deviceId, filter }, "logcat", CACHE_TTLS.LOGCAT);
|
|
42
44
|
// Parse log lines
|
|
43
45
|
const lines = output.split("\n").filter(Boolean);
|
|
44
46
|
const errorCount = lines.filter((l) => l.includes(" E ")).length;
|
|
@@ -65,7 +67,7 @@ export const adbLogcatToolDefinition = {
|
|
|
65
67
|
tags: { type: "array", items: { type: "string" }, description: "Filter by log tags" },
|
|
66
68
|
level: { type: "string", enum: ["verbose", "debug", "info", "warn", "error"] },
|
|
67
69
|
rawFilter: { type: "string", description: "Raw logcat filter string" },
|
|
68
|
-
since: { type: "string", description: "Time filter (e.g., '
|
|
70
|
+
since: { type: "string", description: "Time filter in adb logcat -T format (e.g., '01-20 15:30:00.000')" },
|
|
69
71
|
},
|
|
70
72
|
},
|
|
71
73
|
};
|
|
@@ -3,6 +3,9 @@ import { ServerContext } from "../server.js";
|
|
|
3
3
|
export declare const adbShellInputSchema: z.ZodObject<{
|
|
4
4
|
command: z.ZodString;
|
|
5
5
|
timeout: z.ZodOptional<z.ZodNumber>;
|
|
6
|
+
maxChars: z.ZodOptional<z.ZodNumber>;
|
|
7
|
+
summaryOnly: z.ZodOptional<z.ZodBoolean>;
|
|
8
|
+
previewChars: z.ZodOptional<z.ZodNumber>;
|
|
6
9
|
}, z.core.$strip>;
|
|
7
10
|
export type AdbShellInput = z.infer<typeof adbShellInputSchema>;
|
|
8
11
|
export declare function handleAdbShellTool(input: AdbShellInput, context: ServerContext): Promise<Record<string, unknown>>;
|
|
@@ -20,6 +23,18 @@ export declare const adbShellToolDefinition: {
|
|
|
20
23
|
type: string;
|
|
21
24
|
description: string;
|
|
22
25
|
};
|
|
26
|
+
maxChars: {
|
|
27
|
+
type: string;
|
|
28
|
+
description: string;
|
|
29
|
+
};
|
|
30
|
+
summaryOnly: {
|
|
31
|
+
type: string;
|
|
32
|
+
description: string;
|
|
33
|
+
};
|
|
34
|
+
previewChars: {
|
|
35
|
+
type: string;
|
|
36
|
+
description: string;
|
|
37
|
+
};
|
|
23
38
|
};
|
|
24
39
|
required: string[];
|
|
25
40
|
};
|
package/dist/tools/adb-shell.js
CHANGED
|
@@ -2,17 +2,42 @@ import { z } from "zod";
|
|
|
2
2
|
export const adbShellInputSchema = z.object({
|
|
3
3
|
command: z.string(),
|
|
4
4
|
timeout: z.number().optional(),
|
|
5
|
+
maxChars: z.number().min(1).optional(),
|
|
6
|
+
summaryOnly: z.boolean().optional(),
|
|
7
|
+
previewChars: z.number().min(1).optional(),
|
|
5
8
|
});
|
|
6
9
|
export async function handleAdbShellTool(input, context) {
|
|
7
10
|
const device = await context.deviceState.ensureDevice(context.adb);
|
|
8
11
|
const deviceId = device.id;
|
|
9
12
|
const result = await context.adb.shell(deviceId, input.command, input.timeout);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
if (input.summaryOnly) {
|
|
14
|
+
const previewChars = input.previewChars ?? 200;
|
|
15
|
+
return {
|
|
16
|
+
exitCode: result.exitCode,
|
|
17
|
+
deviceId,
|
|
18
|
+
summarized: true,
|
|
19
|
+
stdoutPreview: result.stdout.slice(0, previewChars),
|
|
20
|
+
stderrPreview: result.stderr.slice(0, previewChars),
|
|
21
|
+
originalStdoutChars: result.stdout.length,
|
|
22
|
+
originalStderrChars: result.stderr.length,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const maxChars = input.maxChars;
|
|
26
|
+
const stdout = maxChars ? result.stdout.slice(0, maxChars) : result.stdout;
|
|
27
|
+
const stderr = maxChars ? result.stderr.slice(0, maxChars) : result.stderr;
|
|
28
|
+
const truncated = !!maxChars && (result.stdout.length > maxChars || result.stderr.length > maxChars);
|
|
29
|
+
const response = {
|
|
30
|
+
stdout,
|
|
31
|
+
stderr,
|
|
13
32
|
exitCode: result.exitCode,
|
|
14
33
|
deviceId,
|
|
34
|
+
truncated,
|
|
15
35
|
};
|
|
36
|
+
if (maxChars !== undefined) {
|
|
37
|
+
response.originalStdoutChars = result.stdout.length;
|
|
38
|
+
response.originalStderrChars = result.stderr.length;
|
|
39
|
+
}
|
|
40
|
+
return response;
|
|
16
41
|
}
|
|
17
42
|
export const adbShellToolDefinition = {
|
|
18
43
|
name: "adb-shell",
|
|
@@ -22,6 +47,9 @@ export const adbShellToolDefinition = {
|
|
|
22
47
|
properties: {
|
|
23
48
|
command: { type: "string", description: "Shell command to execute" },
|
|
24
49
|
timeout: { type: "number", description: "Timeout in ms (default: 30s, max: 120s)" },
|
|
50
|
+
maxChars: { type: "number", description: "Truncate stdout/stderr to at most this many characters" },
|
|
51
|
+
summaryOnly: { type: "boolean", description: "Return only compact previews and counts, omitting full stdout/stderr" },
|
|
52
|
+
previewChars: { type: "number", description: "For summaryOnly: preview length in characters (default: 200)" },
|
|
25
53
|
},
|
|
26
54
|
required: ["command"],
|
|
27
55
|
},
|
package/dist/tools/cache.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { ReplicantError, ErrorCode } from "../types/index.js";
|
|
2
3
|
export const cacheInputSchema = z.object({
|
|
3
4
|
operation: z.enum(["get-stats", "clear", "get-config", "set-config"]),
|
|
4
5
|
key: z.string().optional(),
|
|
@@ -29,7 +30,7 @@ export async function handleCacheTool(input, cache) {
|
|
|
29
30
|
}
|
|
30
31
|
return { config: cache.getConfig() };
|
|
31
32
|
default:
|
|
32
|
-
throw new
|
|
33
|
+
throw new ReplicantError(ErrorCode.INVALID_OPERATION, `Unknown operation: ${input.operation}`, "Valid operations: get-stats, clear, get-config, set-config");
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
export const cacheToolDefinition = {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { ReplicantError, ErrorCode } from "../types/index.js";
|
|
2
3
|
export const emulatorDeviceInputSchema = z.object({
|
|
3
4
|
operation: z.enum([
|
|
4
5
|
"list",
|
|
@@ -23,14 +24,14 @@ async function handleList(input, context) {
|
|
|
23
24
|
}
|
|
24
25
|
async function handleCreate(input, context) {
|
|
25
26
|
if (!input.avdName || !input.device || !input.systemImage) {
|
|
26
|
-
throw new
|
|
27
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "avdName, device, and systemImage are required for create", "Provide all three parameters: avdName, device, and systemImage");
|
|
27
28
|
}
|
|
28
29
|
await context.emulator.create(input.avdName, input.device, input.systemImage);
|
|
29
30
|
return { created: input.avdName };
|
|
30
31
|
}
|
|
31
32
|
async function handleStart(input, context) {
|
|
32
33
|
if (!input.avdName) {
|
|
33
|
-
throw new
|
|
34
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "avdName is required for start", "Provide the AVD name to start");
|
|
34
35
|
}
|
|
35
36
|
const emulatorId = await context.emulator.start(input.avdName);
|
|
36
37
|
const devices = await context.adb.getDevices();
|
|
@@ -43,7 +44,7 @@ async function handleStart(input, context) {
|
|
|
43
44
|
async function handleKill(input, context) {
|
|
44
45
|
const emulatorId = input.emulatorId || context.deviceState.getCurrentDevice()?.id;
|
|
45
46
|
if (!emulatorId) {
|
|
46
|
-
throw new
|
|
47
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "emulatorId is required or select an emulator first", "Use emulator-device list to see running emulators, or provide emulatorId");
|
|
47
48
|
}
|
|
48
49
|
await context.emulator.kill(emulatorId);
|
|
49
50
|
if (context.deviceState.getCurrentDevice()?.id === emulatorId) {
|
|
@@ -53,7 +54,7 @@ async function handleKill(input, context) {
|
|
|
53
54
|
}
|
|
54
55
|
async function handleWipe(input, context) {
|
|
55
56
|
if (!input.avdName) {
|
|
56
|
-
throw new
|
|
57
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "avdName is required for wipe", "Provide the AVD name to wipe");
|
|
57
58
|
}
|
|
58
59
|
await context.emulator.wipe(input.avdName);
|
|
59
60
|
return { wiped: input.avdName };
|
|
@@ -61,21 +62,23 @@ async function handleWipe(input, context) {
|
|
|
61
62
|
function resolveEmulatorId(input, context) {
|
|
62
63
|
const emulatorId = input.emulatorId || context.deviceState.getCurrentDevice()?.id;
|
|
63
64
|
if (!emulatorId) {
|
|
64
|
-
throw new
|
|
65
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, `emulatorId is required for ${input.operation}`, "Use emulator-device list to see running emulators, or provide emulatorId");
|
|
65
66
|
}
|
|
66
67
|
return emulatorId;
|
|
67
68
|
}
|
|
68
69
|
async function handleSnapshotSave(input, context) {
|
|
69
70
|
const emulatorId = resolveEmulatorId(input, context);
|
|
70
|
-
if (!input.snapshotName)
|
|
71
|
-
throw new
|
|
71
|
+
if (!input.snapshotName) {
|
|
72
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "snapshotName is required for snapshot-save", "Provide the snapshot name to save");
|
|
73
|
+
}
|
|
72
74
|
await context.emulator.snapshotSave(emulatorId, input.snapshotName);
|
|
73
75
|
return { saved: input.snapshotName, emulatorId };
|
|
74
76
|
}
|
|
75
77
|
async function handleSnapshotLoad(input, context) {
|
|
76
78
|
const emulatorId = resolveEmulatorId(input, context);
|
|
77
|
-
if (!input.snapshotName)
|
|
78
|
-
throw new
|
|
79
|
+
if (!input.snapshotName) {
|
|
80
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "snapshotName is required for snapshot-load", "Provide the snapshot name to load");
|
|
81
|
+
}
|
|
79
82
|
await context.emulator.snapshotLoad(emulatorId, input.snapshotName);
|
|
80
83
|
return { loaded: input.snapshotName, emulatorId };
|
|
81
84
|
}
|
|
@@ -86,8 +89,9 @@ async function handleSnapshotList(input, context) {
|
|
|
86
89
|
}
|
|
87
90
|
async function handleSnapshotDelete(input, context) {
|
|
88
91
|
const emulatorId = resolveEmulatorId(input, context);
|
|
89
|
-
if (!input.snapshotName)
|
|
90
|
-
throw new
|
|
92
|
+
if (!input.snapshotName) {
|
|
93
|
+
throw new ReplicantError(ErrorCode.INPUT_VALIDATION_FAILED, "snapshotName is required for snapshot-delete", "Provide the snapshot name to delete");
|
|
94
|
+
}
|
|
91
95
|
await context.emulator.snapshotDelete(emulatorId, input.snapshotName);
|
|
92
96
|
return { deleted: input.snapshotName, emulatorId };
|
|
93
97
|
}
|
|
@@ -104,8 +108,9 @@ const operations = {
|
|
|
104
108
|
};
|
|
105
109
|
export async function handleEmulatorDeviceTool(input, context) {
|
|
106
110
|
const handler = operations[input.operation];
|
|
107
|
-
if (!handler)
|
|
108
|
-
throw new
|
|
111
|
+
if (!handler) {
|
|
112
|
+
throw new ReplicantError(ErrorCode.INVALID_OPERATION, `Unknown operation: ${input.operation}`, "Valid operations: list, create, start, kill, wipe, snapshot-save, snapshot-load, snapshot-list, snapshot-delete");
|
|
113
|
+
}
|
|
109
114
|
return handler(input, context);
|
|
110
115
|
}
|
|
111
116
|
export const emulatorDeviceToolDefinition = {
|
|
@@ -8,6 +8,9 @@ export declare const gradleGetDetailsInputSchema: z.ZodObject<{
|
|
|
8
8
|
tasks: "tasks";
|
|
9
9
|
logs: "logs";
|
|
10
10
|
}>>>;
|
|
11
|
+
maxChars: z.ZodOptional<z.ZodNumber>;
|
|
12
|
+
summaryOnly: z.ZodOptional<z.ZodBoolean>;
|
|
13
|
+
previewChars: z.ZodOptional<z.ZodNumber>;
|
|
11
14
|
}, z.core.$strip>;
|
|
12
15
|
export type GradleGetDetailsInput = z.infer<typeof gradleGetDetailsInputSchema>;
|
|
13
16
|
export declare function handleGradleGetDetailsTool(input: GradleGetDetailsInput, context: ServerContext): Promise<Record<string, unknown>>;
|
|
@@ -26,6 +29,18 @@ export declare const gradleGetDetailsToolDefinition: {
|
|
|
26
29
|
enum: string[];
|
|
27
30
|
description: string;
|
|
28
31
|
};
|
|
32
|
+
maxChars: {
|
|
33
|
+
type: string;
|
|
34
|
+
description: string;
|
|
35
|
+
};
|
|
36
|
+
summaryOnly: {
|
|
37
|
+
type: string;
|
|
38
|
+
description: string;
|
|
39
|
+
};
|
|
40
|
+
previewChars: {
|
|
41
|
+
type: string;
|
|
42
|
+
description: string;
|
|
43
|
+
};
|
|
29
44
|
};
|
|
30
45
|
required: string[];
|
|
31
46
|
};
|