replicant-mcp 1.0.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.
Files changed (85) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +386 -0
  3. package/dist/adapters/adb.d.ts +21 -0
  4. package/dist/adapters/adb.js +75 -0
  5. package/dist/adapters/emulator.d.ts +19 -0
  6. package/dist/adapters/emulator.js +72 -0
  7. package/dist/adapters/gradle.d.ts +20 -0
  8. package/dist/adapters/gradle.js +80 -0
  9. package/dist/adapters/index.d.ts +4 -0
  10. package/dist/adapters/index.js +4 -0
  11. package/dist/adapters/ui-automator.d.ts +23 -0
  12. package/dist/adapters/ui-automator.js +53 -0
  13. package/dist/cli/adb.d.ts +2 -0
  14. package/dist/cli/adb.js +256 -0
  15. package/dist/cli/cache.d.ts +2 -0
  16. package/dist/cli/cache.js +115 -0
  17. package/dist/cli/emulator.d.ts +2 -0
  18. package/dist/cli/emulator.js +181 -0
  19. package/dist/cli/formatter.d.ts +52 -0
  20. package/dist/cli/formatter.js +68 -0
  21. package/dist/cli/gradle.d.ts +2 -0
  22. package/dist/cli/gradle.js +192 -0
  23. package/dist/cli/index.d.ts +6 -0
  24. package/dist/cli/index.js +6 -0
  25. package/dist/cli/ui.d.ts +2 -0
  26. package/dist/cli/ui.js +218 -0
  27. package/dist/cli.d.ts +2 -0
  28. package/dist/cli.js +14 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +6 -0
  31. package/dist/parsers/adb-output.d.ts +4 -0
  32. package/dist/parsers/adb-output.js +32 -0
  33. package/dist/parsers/emulator-output.d.ts +9 -0
  34. package/dist/parsers/emulator-output.js +33 -0
  35. package/dist/parsers/gradle-output.d.ts +30 -0
  36. package/dist/parsers/gradle-output.js +80 -0
  37. package/dist/parsers/index.d.ts +4 -0
  38. package/dist/parsers/index.js +4 -0
  39. package/dist/parsers/ui-dump.d.ts +27 -0
  40. package/dist/parsers/ui-dump.js +142 -0
  41. package/dist/server.d.ts +15 -0
  42. package/dist/server.js +113 -0
  43. package/dist/services/cache-manager.d.ts +22 -0
  44. package/dist/services/cache-manager.js +90 -0
  45. package/dist/services/device-state.d.ts +9 -0
  46. package/dist/services/device-state.js +26 -0
  47. package/dist/services/index.d.ts +3 -0
  48. package/dist/services/index.js +3 -0
  49. package/dist/services/process-runner.d.ts +15 -0
  50. package/dist/services/process-runner.js +62 -0
  51. package/dist/tools/adb-app.d.ts +38 -0
  52. package/dist/tools/adb-app.js +68 -0
  53. package/dist/tools/adb-device.d.ts +31 -0
  54. package/dist/tools/adb-device.js +71 -0
  55. package/dist/tools/adb-logcat.d.ts +54 -0
  56. package/dist/tools/adb-logcat.js +70 -0
  57. package/dist/tools/adb-shell.d.ts +26 -0
  58. package/dist/tools/adb-shell.js +27 -0
  59. package/dist/tools/cache.d.ts +50 -0
  60. package/dist/tools/cache.js +57 -0
  61. package/dist/tools/emulator-device.d.ts +56 -0
  62. package/dist/tools/emulator-device.js +132 -0
  63. package/dist/tools/gradle-build.d.ts +35 -0
  64. package/dist/tools/gradle-build.js +40 -0
  65. package/dist/tools/gradle-get-details.d.ts +32 -0
  66. package/dist/tools/gradle-get-details.js +72 -0
  67. package/dist/tools/gradle-list.d.ts +30 -0
  68. package/dist/tools/gradle-list.js +55 -0
  69. package/dist/tools/gradle-test.d.ts +34 -0
  70. package/dist/tools/gradle-test.js +40 -0
  71. package/dist/tools/index.d.ts +12 -0
  72. package/dist/tools/index.js +12 -0
  73. package/dist/tools/rtfm.d.ts +26 -0
  74. package/dist/tools/rtfm.js +70 -0
  75. package/dist/tools/ui.d.ts +77 -0
  76. package/dist/tools/ui.js +131 -0
  77. package/dist/types/cache.d.ts +24 -0
  78. package/dist/types/cache.js +14 -0
  79. package/dist/types/device.d.ts +11 -0
  80. package/dist/types/device.js +1 -0
  81. package/dist/types/errors.d.ts +31 -0
  82. package/dist/types/errors.js +43 -0
  83. package/dist/types/index.d.ts +3 -0
  84. package/dist/types/index.js +3 -0
  85. package/package.json +64 -0
@@ -0,0 +1,80 @@
1
+ import { ProcessRunner } from "../services/index.js";
2
+ import { ReplicantError, ErrorCode } from "../types/index.js";
3
+ import { parseBuildOutput, parseTestOutput, parseModuleList, parseVariantList, parseTaskList, } from "../parsers/gradle-output.js";
4
+ export class GradleAdapter {
5
+ runner;
6
+ projectPath;
7
+ constructor(runner = new ProcessRunner(), projectPath) {
8
+ this.runner = runner;
9
+ this.projectPath = projectPath;
10
+ }
11
+ async build(operation, module, flavor) {
12
+ const task = module ? `${module}:${operation}` : operation;
13
+ const args = [task];
14
+ if (flavor) {
15
+ args.push(`-Pflavor=${flavor}`);
16
+ }
17
+ const result = await this.gradle(args, 300000); // 5 min timeout for builds
18
+ const parsed = parseBuildOutput(result.stdout + result.stderr);
19
+ if (!parsed.success) {
20
+ throw new ReplicantError(ErrorCode.BUILD_FAILED, `Build failed: ${parsed.failedTask || "unknown error"}`, "Check gradle-get-details for full error output", { buildResult: parsed });
21
+ }
22
+ return { result: parsed, fullOutput: result.stdout + result.stderr };
23
+ }
24
+ async test(operation, module, filter) {
25
+ const taskName = operation === "unitTest" ? "testDebugUnitTest" : "connectedDebugAndroidTest";
26
+ const task = module ? `${module}:${taskName}` : taskName;
27
+ const args = [task];
28
+ if (filter) {
29
+ args.push("--tests", filter);
30
+ }
31
+ const result = await this.gradle(args, 600000); // 10 min timeout for tests
32
+ const parsed = parseTestOutput(result.stdout + result.stderr);
33
+ return { result: parsed, fullOutput: result.stdout + result.stderr };
34
+ }
35
+ async listModules() {
36
+ const result = await this.gradle(["projects"]);
37
+ return parseModuleList(result.stdout);
38
+ }
39
+ async listVariants(module) {
40
+ // Try to get variants - this varies by project setup
41
+ const task = module ? `${module}:printVariants` : "printVariants";
42
+ try {
43
+ const result = await this.gradle([task]);
44
+ return parseVariantList(result.stdout);
45
+ }
46
+ catch {
47
+ // Fallback: return common defaults
48
+ return [
49
+ { name: "debug", buildType: "debug", flavors: [] },
50
+ { name: "release", buildType: "release", flavors: [] },
51
+ ];
52
+ }
53
+ }
54
+ async listTasks(module) {
55
+ const args = module ? [`${module}:tasks`, "--all"] : ["tasks", "--all"];
56
+ const result = await this.gradle(args);
57
+ return parseTaskList(result.stdout);
58
+ }
59
+ async clean(stopDaemons = false) {
60
+ await this.gradle(["clean"]);
61
+ if (stopDaemons) {
62
+ await this.gradle(["--stop"]);
63
+ }
64
+ }
65
+ async gradle(args, timeoutMs = 120000) {
66
+ const gradleCmd = process.platform === "win32" ? "gradlew.bat" : "./gradlew";
67
+ try {
68
+ return await this.runner.run(gradleCmd, args, {
69
+ timeoutMs,
70
+ cwd: this.projectPath,
71
+ });
72
+ }
73
+ catch (error) {
74
+ if (error instanceof Error && error.message.includes("ENOENT")) {
75
+ throw new ReplicantError(ErrorCode.GRADLE_NOT_FOUND, "Gradle wrapper not found", "Ensure you are in an Android project directory with gradlew");
76
+ }
77
+ throw error;
78
+ }
79
+ }
80
+ }
@@ -0,0 +1,4 @@
1
+ export { AdbAdapter } from "./adb.js";
2
+ export { EmulatorAdapter, type EmulatorListResult } from "./emulator.js";
3
+ export { GradleAdapter } from "./gradle.js";
4
+ export { UiAutomatorAdapter } from "./ui-automator.js";
@@ -0,0 +1,4 @@
1
+ export { AdbAdapter } from "./adb.js";
2
+ export { EmulatorAdapter } from "./emulator.js";
3
+ export { GradleAdapter } from "./gradle.js";
4
+ export { UiAutomatorAdapter } from "./ui-automator.js";
@@ -0,0 +1,23 @@
1
+ import { AdbAdapter } from "./adb.js";
2
+ import { AccessibilityNode } from "../parsers/ui-dump.js";
3
+ export declare class UiAutomatorAdapter {
4
+ private adb;
5
+ constructor(adb?: AdbAdapter);
6
+ dump(deviceId: string): Promise<AccessibilityNode[]>;
7
+ find(deviceId: string, selector: {
8
+ resourceId?: string;
9
+ text?: string;
10
+ textContains?: string;
11
+ className?: string;
12
+ }): Promise<AccessibilityNode[]>;
13
+ tap(deviceId: string, x: number, y: number): Promise<void>;
14
+ tapElement(deviceId: string, element: AccessibilityNode): Promise<void>;
15
+ input(deviceId: string, text: string): Promise<void>;
16
+ screenshot(deviceId: string, localPath: string): Promise<void>;
17
+ accessibilityCheck(deviceId: string): Promise<{
18
+ hasAccessibleElements: boolean;
19
+ clickableCount: number;
20
+ textCount: number;
21
+ totalElements: number;
22
+ }>;
23
+ }
@@ -0,0 +1,53 @@
1
+ import { AdbAdapter } from "./adb.js";
2
+ import { parseUiDump, findElements, flattenTree } from "../parsers/ui-dump.js";
3
+ export class UiAutomatorAdapter {
4
+ adb;
5
+ constructor(adb = new AdbAdapter()) {
6
+ this.adb = adb;
7
+ }
8
+ async dump(deviceId) {
9
+ // Dump UI hierarchy to device
10
+ await this.adb.shell(deviceId, "uiautomator dump /sdcard/ui-dump.xml");
11
+ // Pull the dump
12
+ const result = await this.adb.shell(deviceId, "cat /sdcard/ui-dump.xml");
13
+ // Clean up
14
+ await this.adb.shell(deviceId, "rm /sdcard/ui-dump.xml");
15
+ return parseUiDump(result.stdout);
16
+ }
17
+ async find(deviceId, selector) {
18
+ const tree = await this.dump(deviceId);
19
+ return findElements(tree, selector);
20
+ }
21
+ async tap(deviceId, x, y) {
22
+ await this.adb.shell(deviceId, `input tap ${x} ${y}`);
23
+ }
24
+ async tapElement(deviceId, element) {
25
+ await this.tap(deviceId, element.centerX, element.centerY);
26
+ }
27
+ async input(deviceId, text) {
28
+ // Escape special characters for shell
29
+ const escaped = text.replace(/(['"\\$`])/g, "\\$1").replace(/ /g, "%s");
30
+ await this.adb.shell(deviceId, `input text "${escaped}"`);
31
+ }
32
+ async screenshot(deviceId, localPath) {
33
+ const remotePath = "/sdcard/screenshot.png";
34
+ await this.adb.shell(deviceId, `screencap -p ${remotePath}`);
35
+ // Pull to local (using adb pull via shell workaround)
36
+ // In real implementation, would use adb pull directly
37
+ const result = await this.adb.shell(deviceId, `base64 ${remotePath}`);
38
+ // For now, just verify it worked
39
+ await this.adb.shell(deviceId, `rm ${remotePath}`);
40
+ }
41
+ async accessibilityCheck(deviceId) {
42
+ const tree = await this.dump(deviceId);
43
+ const flat = flattenTree(tree);
44
+ const clickableCount = flat.filter((n) => n.clickable).length;
45
+ const textCount = flat.filter((n) => n.text || n.contentDesc).length;
46
+ return {
47
+ hasAccessibleElements: textCount > 0,
48
+ clickableCount,
49
+ textCount,
50
+ totalElements: flat.length,
51
+ };
52
+ }
53
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createAdbCommand(): Command;
@@ -0,0 +1,256 @@
1
+ import { Command } from "commander";
2
+ import { AdbAdapter } from "../adapters/index.js";
3
+ import { DeviceStateManager, CacheManager } from "../services/index.js";
4
+ import { formatDeviceList, formatLogcat } from "./formatter.js";
5
+ import { CACHE_TTLS } from "../types/index.js";
6
+ const adapter = new AdbAdapter();
7
+ const deviceState = new DeviceStateManager();
8
+ const cache = new CacheManager();
9
+ export function createAdbCommand() {
10
+ const adb = new Command("adb").description("ADB device and app management");
11
+ // Devices subcommand
12
+ adb
13
+ .command("devices")
14
+ .description("List connected devices")
15
+ .option("--json", "Output as JSON")
16
+ .action(async (options) => {
17
+ try {
18
+ const devices = await adapter.getDevices();
19
+ const currentDevice = deviceState.getCurrentDevice();
20
+ // Auto-select if single device
21
+ const autoSelected = deviceState.autoSelectIfSingle(devices);
22
+ const deviceInfos = devices.map((d) => ({
23
+ id: d.id,
24
+ name: d.name,
25
+ state: d.status,
26
+ selected: currentDevice?.id === d.id || (autoSelected && devices[0].id === d.id),
27
+ }));
28
+ if (options.json) {
29
+ console.log(JSON.stringify({ devices: deviceInfos }, null, 2));
30
+ }
31
+ else {
32
+ console.log(formatDeviceList({ devices: deviceInfos }));
33
+ if (autoSelected) {
34
+ console.log(`\nAuto-selected: ${devices[0].id}`);
35
+ }
36
+ }
37
+ }
38
+ catch (error) {
39
+ const errorMessage = error instanceof Error ? error.message : String(error);
40
+ console.error(`Error: ${errorMessage}`);
41
+ process.exit(1);
42
+ }
43
+ });
44
+ // Select subcommand
45
+ adb
46
+ .command("select <deviceId>")
47
+ .description("Set active device for subsequent commands")
48
+ .action(async (deviceId) => {
49
+ try {
50
+ const devices = await adapter.getDevices();
51
+ const device = devices.find((d) => d.id === deviceId);
52
+ if (!device) {
53
+ console.error(`Device not found: ${deviceId}`);
54
+ console.error("Available devices:");
55
+ devices.forEach((d) => console.error(` ${d.id} (${d.name})`));
56
+ process.exit(1);
57
+ }
58
+ deviceState.setCurrentDevice(device);
59
+ console.log(`Selected device: ${device.id} (${device.name})`);
60
+ }
61
+ catch (error) {
62
+ const errorMessage = error instanceof Error ? error.message : String(error);
63
+ console.error(`Error: ${errorMessage}`);
64
+ process.exit(1);
65
+ }
66
+ });
67
+ // Install subcommand
68
+ adb
69
+ .command("install <apkPath>")
70
+ .description("Install APK on the active device")
71
+ .option("-d, --device <deviceId>", "Target device (uses active device if not specified)")
72
+ .action(async (apkPath, options) => {
73
+ try {
74
+ const deviceId = options.device || getDeviceId();
75
+ await adapter.install(deviceId, apkPath);
76
+ console.log(`Installed: ${apkPath}`);
77
+ }
78
+ catch (error) {
79
+ const errorMessage = error instanceof Error ? error.message : String(error);
80
+ console.error(`Error: ${errorMessage}`);
81
+ process.exit(1);
82
+ }
83
+ });
84
+ // Launch subcommand
85
+ adb
86
+ .command("launch <package>")
87
+ .description("Launch an app on the active device")
88
+ .option("-d, --device <deviceId>", "Target device (uses active device if not specified)")
89
+ .action(async (packageName, options) => {
90
+ try {
91
+ const deviceId = options.device || getDeviceId();
92
+ await adapter.launch(deviceId, packageName);
93
+ console.log(`Launched: ${packageName}`);
94
+ }
95
+ catch (error) {
96
+ const errorMessage = error instanceof Error ? error.message : String(error);
97
+ console.error(`Error: ${errorMessage}`);
98
+ process.exit(1);
99
+ }
100
+ });
101
+ // Stop subcommand
102
+ adb
103
+ .command("stop <package>")
104
+ .description("Force stop an app on the active device")
105
+ .option("-d, --device <deviceId>", "Target device (uses active device if not specified)")
106
+ .action(async (packageName, options) => {
107
+ try {
108
+ const deviceId = options.device || getDeviceId();
109
+ await adapter.stop(deviceId, packageName);
110
+ console.log(`Stopped: ${packageName}`);
111
+ }
112
+ catch (error) {
113
+ const errorMessage = error instanceof Error ? error.message : String(error);
114
+ console.error(`Error: ${errorMessage}`);
115
+ process.exit(1);
116
+ }
117
+ });
118
+ // Uninstall subcommand
119
+ adb
120
+ .command("uninstall <package>")
121
+ .description("Uninstall an app from the active device")
122
+ .option("-d, --device <deviceId>", "Target device (uses active device if not specified)")
123
+ .action(async (packageName, options) => {
124
+ try {
125
+ const deviceId = options.device || getDeviceId();
126
+ await adapter.uninstall(deviceId, packageName);
127
+ console.log(`Uninstalled: ${packageName}`);
128
+ }
129
+ catch (error) {
130
+ const errorMessage = error instanceof Error ? error.message : String(error);
131
+ console.error(`Error: ${errorMessage}`);
132
+ process.exit(1);
133
+ }
134
+ });
135
+ // Clear subcommand
136
+ adb
137
+ .command("clear <package>")
138
+ .description("Clear app data on the active device")
139
+ .option("-d, --device <deviceId>", "Target device (uses active device if not specified)")
140
+ .action(async (packageName, options) => {
141
+ try {
142
+ const deviceId = options.device || getDeviceId();
143
+ await adapter.clearData(deviceId, packageName);
144
+ console.log(`Cleared data: ${packageName}`);
145
+ }
146
+ catch (error) {
147
+ const errorMessage = error instanceof Error ? error.message : String(error);
148
+ console.error(`Error: ${errorMessage}`);
149
+ process.exit(1);
150
+ }
151
+ });
152
+ // Logcat subcommand
153
+ adb
154
+ .command("logcat")
155
+ .description("Read device logs")
156
+ .option("-d, --device <deviceId>", "Target device (uses active device if not specified)")
157
+ .option("-l, --level <level>", "Log level filter (verbose, debug, info, warn, error)", "info")
158
+ .option("-n, --lines <count>", "Number of lines to retrieve", "50")
159
+ .option("-t, --tag <tag>", "Filter by tag")
160
+ .option("--json", "Output as JSON")
161
+ .action(async (options) => {
162
+ try {
163
+ const deviceId = options.device || getDeviceId();
164
+ // Build filter string
165
+ let filter = "";
166
+ if (options.tag) {
167
+ const levelChar = getLevelChar(options.level);
168
+ filter = `${options.tag}:${levelChar} *:S`;
169
+ }
170
+ else {
171
+ const levelChar = getLevelChar(options.level);
172
+ filter = `*:${levelChar}`;
173
+ }
174
+ const logs = await adapter.logcat(deviceId, {
175
+ lines: parseInt(options.lines, 10),
176
+ filter,
177
+ });
178
+ const lines = logs.split("\n").filter((line) => line.trim());
179
+ const cacheId = cache.generateId("logcat");
180
+ cache.set(cacheId, { logs, level: options.level }, "logcat", CACHE_TTLS.LOGCAT);
181
+ if (options.json) {
182
+ console.log(JSON.stringify({
183
+ level: options.level,
184
+ count: lines.length,
185
+ lines,
186
+ cacheId,
187
+ }, null, 2));
188
+ }
189
+ else {
190
+ console.log(formatLogcat({
191
+ level: options.level,
192
+ count: lines.length,
193
+ lines,
194
+ cacheId,
195
+ }));
196
+ }
197
+ }
198
+ catch (error) {
199
+ const errorMessage = error instanceof Error ? error.message : String(error);
200
+ console.error(`Error: ${errorMessage}`);
201
+ process.exit(1);
202
+ }
203
+ });
204
+ // Shell subcommand
205
+ adb
206
+ .command("shell <command...>")
207
+ .description("Run a shell command on the active device")
208
+ .option("-d, --device <deviceId>", "Target device (uses active device if not specified)")
209
+ .option("--json", "Output as JSON")
210
+ .action(async (commandParts, options) => {
211
+ try {
212
+ const deviceId = options.device || getDeviceId();
213
+ const command = commandParts.join(" ");
214
+ const result = await adapter.shell(deviceId, command);
215
+ if (options.json) {
216
+ console.log(JSON.stringify({
217
+ command,
218
+ exitCode: result.exitCode,
219
+ stdout: result.stdout,
220
+ stderr: result.stderr,
221
+ }, null, 2));
222
+ }
223
+ else {
224
+ if (result.stdout) {
225
+ console.log(result.stdout);
226
+ }
227
+ if (result.stderr) {
228
+ console.error(result.stderr);
229
+ }
230
+ }
231
+ if (result.exitCode !== 0) {
232
+ process.exit(result.exitCode);
233
+ }
234
+ }
235
+ catch (error) {
236
+ const errorMessage = error instanceof Error ? error.message : String(error);
237
+ console.error(`Error: ${errorMessage}`);
238
+ process.exit(1);
239
+ }
240
+ });
241
+ return adb;
242
+ }
243
+ function getDeviceId() {
244
+ const device = deviceState.requireCurrentDevice();
245
+ return device.id;
246
+ }
247
+ function getLevelChar(level) {
248
+ const levels = {
249
+ verbose: "V",
250
+ debug: "D",
251
+ info: "I",
252
+ warn: "W",
253
+ error: "E",
254
+ };
255
+ return levels[level.toLowerCase()] || "I";
256
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createCacheCommand(): Command;
@@ -0,0 +1,115 @@
1
+ import { Command } from "commander";
2
+ import { CacheManager } from "../services/index.js";
3
+ const cache = new CacheManager();
4
+ export function createCacheCommand() {
5
+ const cacheCmd = new Command("cache").description("Manage output cache");
6
+ // Stats subcommand
7
+ cacheCmd
8
+ .command("stats")
9
+ .description("Show cache statistics")
10
+ .option("--json", "Output as JSON")
11
+ .action((options) => {
12
+ try {
13
+ const stats = cache.getStats();
14
+ if (options.json) {
15
+ console.log(JSON.stringify(stats, null, 2));
16
+ }
17
+ else {
18
+ console.log("Cache Statistics");
19
+ console.log("================");
20
+ console.log(`Entries: ${stats.entryCount}`);
21
+ console.log(`Total size: ${formatBytes(stats.totalSizeBytes)}`);
22
+ console.log(`Max entries: ${stats.config.maxEntries}`);
23
+ console.log(`Default TTL: ${formatMs(stats.config.defaultTtlMs)}`);
24
+ if (Object.keys(stats.typeBreakdown).length > 0) {
25
+ console.log("\nBy type:");
26
+ for (const [type, count] of Object.entries(stats.typeBreakdown)) {
27
+ console.log(` ${type}: ${count}`);
28
+ }
29
+ }
30
+ }
31
+ }
32
+ catch (error) {
33
+ const errorMessage = error instanceof Error ? error.message : String(error);
34
+ console.error(`Error: ${errorMessage}`);
35
+ process.exit(1);
36
+ }
37
+ });
38
+ // Get subcommand
39
+ cacheCmd
40
+ .command("get <cacheId>")
41
+ .description("Get cached entry by ID")
42
+ .option("--json", "Output as JSON")
43
+ .action((cacheId, options) => {
44
+ try {
45
+ const entry = cache.get(cacheId);
46
+ if (!entry) {
47
+ console.error(`Cache entry not found: ${cacheId}`);
48
+ process.exit(1);
49
+ }
50
+ if (options.json) {
51
+ console.log(JSON.stringify({
52
+ id: cacheId,
53
+ data: entry.data,
54
+ metadata: entry.metadata,
55
+ expiresAt: entry.expiresAt,
56
+ }, null, 2));
57
+ }
58
+ else {
59
+ console.log(`Cache ID: ${cacheId}`);
60
+ console.log(`Type: ${entry.metadata.type}`);
61
+ console.log(`Created: ${new Date(entry.metadata.createdAt).toISOString()}`);
62
+ console.log(`Expires: ${new Date(entry.expiresAt).toISOString()}`);
63
+ console.log(`Size: ${formatBytes(entry.metadata.sizeBytes ?? 0)}`);
64
+ console.log("\nData:");
65
+ console.log(JSON.stringify(entry.data, null, 2));
66
+ }
67
+ }
68
+ catch (error) {
69
+ const errorMessage = error instanceof Error ? error.message : String(error);
70
+ console.error(`Error: ${errorMessage}`);
71
+ process.exit(1);
72
+ }
73
+ });
74
+ // Clear subcommand
75
+ cacheCmd
76
+ .command("clear")
77
+ .description("Clear all cached data")
78
+ .option("--json", "Output as JSON")
79
+ .action((options) => {
80
+ try {
81
+ const statsBefore = cache.getStats();
82
+ const entriesCleared = statsBefore.entryCount;
83
+ cache.clearAll();
84
+ if (options.json) {
85
+ console.log(JSON.stringify({
86
+ cleared: true,
87
+ entriesCleared,
88
+ }, null, 2));
89
+ }
90
+ else {
91
+ console.log(`Cleared ${entriesCleared} cache entries`);
92
+ }
93
+ }
94
+ catch (error) {
95
+ const errorMessage = error instanceof Error ? error.message : String(error);
96
+ console.error(`Error: ${errorMessage}`);
97
+ process.exit(1);
98
+ }
99
+ });
100
+ return cacheCmd;
101
+ }
102
+ function formatBytes(bytes) {
103
+ if (bytes === 0)
104
+ return "0 B";
105
+ const units = ["B", "KB", "MB", "GB"];
106
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
107
+ return `${(bytes / Math.pow(1024, i)).toFixed(i > 0 ? 1 : 0)} ${units[i]}`;
108
+ }
109
+ function formatMs(ms) {
110
+ if (ms < 1000)
111
+ return `${ms}ms`;
112
+ if (ms < 60000)
113
+ return `${(ms / 1000).toFixed(0)}s`;
114
+ return `${(ms / 60000).toFixed(0)}m`;
115
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function createEmulatorCommand(): Command;