@0xhayd3n/fling 0.6.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 (133) hide show
  1. package/dist/adb.d.ts +18 -0
  2. package/dist/adb.js +109 -0
  3. package/dist/adb.js.map +1 -0
  4. package/dist/apkFinder.d.ts +19 -0
  5. package/dist/apkFinder.js +113 -0
  6. package/dist/apkFinder.js.map +1 -0
  7. package/dist/apkResolver.d.ts +14 -0
  8. package/dist/apkResolver.js +55 -0
  9. package/dist/apkResolver.js.map +1 -0
  10. package/dist/config.d.ts +44 -0
  11. package/dist/config.js +113 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/devices.d.ts +24 -0
  14. package/dist/devices.js +143 -0
  15. package/dist/devices.js.map +1 -0
  16. package/dist/errors.d.ts +13 -0
  17. package/dist/errors.js +22 -0
  18. package/dist/errors.js.map +1 -0
  19. package/dist/featureFlags.d.ts +6 -0
  20. package/dist/featureFlags.js +11 -0
  21. package/dist/featureFlags.js.map +1 -0
  22. package/dist/gradle.d.ts +39 -0
  23. package/dist/gradle.js +129 -0
  24. package/dist/gradle.js.map +1 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.js +76 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/mdns.d.ts +19 -0
  29. package/dist/mdns.js +68 -0
  30. package/dist/mdns.js.map +1 -0
  31. package/dist/pairing.d.ts +52 -0
  32. package/dist/pairing.js +212 -0
  33. package/dist/pairing.js.map +1 -0
  34. package/dist/qrText.d.ts +5 -0
  35. package/dist/qrText.js +8 -0
  36. package/dist/qrText.js.map +1 -0
  37. package/dist/schemas.d.ts +31 -0
  38. package/dist/schemas.js +26 -0
  39. package/dist/schemas.js.map +1 -0
  40. package/dist/shellFraming.d.ts +28 -0
  41. package/dist/shellFraming.js +43 -0
  42. package/dist/shellFraming.js.map +1 -0
  43. package/dist/shellPool.d.ts +54 -0
  44. package/dist/shellPool.js +181 -0
  45. package/dist/shellPool.js.map +1 -0
  46. package/dist/toolResult.d.ts +10 -0
  47. package/dist/toolResult.js +11 -0
  48. package/dist/toolResult.js.map +1 -0
  49. package/dist/tools/build-app.d.ts +2 -0
  50. package/dist/tools/build-app.js +74 -0
  51. package/dist/tools/build-app.js.map +1 -0
  52. package/dist/tools/deploy-and-run.d.ts +2 -0
  53. package/dist/tools/deploy-and-run.js +195 -0
  54. package/dist/tools/deploy-and-run.js.map +1 -0
  55. package/dist/tools/device-state.d.ts +11 -0
  56. package/dist/tools/device-state.js +140 -0
  57. package/dist/tools/device-state.js.map +1 -0
  58. package/dist/tools/dismiss-dialog.d.ts +15 -0
  59. package/dist/tools/dismiss-dialog.js +89 -0
  60. package/dist/tools/dismiss-dialog.js.map +1 -0
  61. package/dist/tools/dump-ui.d.ts +4 -0
  62. package/dist/tools/dump-ui.js +93 -0
  63. package/dist/tools/dump-ui.js.map +1 -0
  64. package/dist/tools/find-on-screen.d.ts +27 -0
  65. package/dist/tools/find-on-screen.js +92 -0
  66. package/dist/tools/find-on-screen.js.map +1 -0
  67. package/dist/tools/install-app.d.ts +23 -0
  68. package/dist/tools/install-app.js +127 -0
  69. package/dist/tools/install-app.js.map +1 -0
  70. package/dist/tools/launch-and-wait.d.ts +27 -0
  71. package/dist/tools/launch-and-wait.js +103 -0
  72. package/dist/tools/launch-and-wait.js.map +1 -0
  73. package/dist/tools/launch-app.d.ts +20 -0
  74. package/dist/tools/launch-app.js +131 -0
  75. package/dist/tools/launch-app.js.map +1 -0
  76. package/dist/tools/launch-settings.d.ts +42 -0
  77. package/dist/tools/launch-settings.js +211 -0
  78. package/dist/tools/launch-settings.js.map +1 -0
  79. package/dist/tools/list-devices.d.ts +2 -0
  80. package/dist/tools/list-devices.js +35 -0
  81. package/dist/tools/list-devices.js.map +1 -0
  82. package/dist/tools/long-press-by-text.d.ts +3 -0
  83. package/dist/tools/long-press-by-text.js +99 -0
  84. package/dist/tools/long-press-by-text.js.map +1 -0
  85. package/dist/tools/open-setting.d.ts +55 -0
  86. package/dist/tools/open-setting.js +257 -0
  87. package/dist/tools/open-setting.js.map +1 -0
  88. package/dist/tools/read-logs.d.ts +2 -0
  89. package/dist/tools/read-logs.js +147 -0
  90. package/dist/tools/read-logs.js.map +1 -0
  91. package/dist/tools/screenshot-with-ui.d.ts +21 -0
  92. package/dist/tools/screenshot-with-ui.js +74 -0
  93. package/dist/tools/screenshot-with-ui.js.map +1 -0
  94. package/dist/tools/screenshot.d.ts +8 -0
  95. package/dist/tools/screenshot.js +97 -0
  96. package/dist/tools/screenshot.js.map +1 -0
  97. package/dist/tools/scroll-until-visible.d.ts +23 -0
  98. package/dist/tools/scroll-until-visible.js +138 -0
  99. package/dist/tools/scroll-until-visible.js.map +1 -0
  100. package/dist/tools/start-pair-qr.d.ts +11 -0
  101. package/dist/tools/start-pair-qr.js +62 -0
  102. package/dist/tools/start-pair-qr.js.map +1 -0
  103. package/dist/tools/stop-app.d.ts +2 -0
  104. package/dist/tools/stop-app.js +63 -0
  105. package/dist/tools/stop-app.js.map +1 -0
  106. package/dist/tools/tap-by-content-desc.d.ts +17 -0
  107. package/dist/tools/tap-by-content-desc.js +97 -0
  108. package/dist/tools/tap-by-content-desc.js.map +1 -0
  109. package/dist/tools/tap-by-resource-id.d.ts +17 -0
  110. package/dist/tools/tap-by-resource-id.js +89 -0
  111. package/dist/tools/tap-by-resource-id.js.map +1 -0
  112. package/dist/tools/tap-by-text.d.ts +40 -0
  113. package/dist/tools/tap-by-text.js +180 -0
  114. package/dist/tools/tap-by-text.js.map +1 -0
  115. package/dist/tools/tap-text-verified.d.ts +37 -0
  116. package/dist/tools/tap-text-verified.js +111 -0
  117. package/dist/tools/tap-text-verified.js.map +1 -0
  118. package/dist/tools/uninstall-app.d.ts +2 -0
  119. package/dist/tools/uninstall-app.js +96 -0
  120. package/dist/tools/uninstall-app.js.map +1 -0
  121. package/dist/tools/wait-for-pair.d.ts +29 -0
  122. package/dist/tools/wait-for-pair.js +132 -0
  123. package/dist/tools/wait-for-pair.js.map +1 -0
  124. package/dist/tools/wait-for.d.ts +26 -0
  125. package/dist/tools/wait-for.js +109 -0
  126. package/dist/tools/wait-for.js.map +1 -0
  127. package/dist/uiDump.d.ts +34 -0
  128. package/dist/uiDump.js +108 -0
  129. package/dist/uiDump.js.map +1 -0
  130. package/dist/uiSelector.d.ts +21 -0
  131. package/dist/uiSelector.js +62 -0
  132. package/dist/uiSelector.js.map +1 -0
  133. package/package.json +67 -0
package/dist/adb.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ export interface RunAdbOptions {
2
+ timeoutMs?: number;
3
+ maxBufferBytes?: number;
4
+ }
5
+ export interface AdbResult {
6
+ stdout: string;
7
+ stderr: string;
8
+ }
9
+ export interface AdbBinaryResult {
10
+ stdout: Buffer;
11
+ stderr: string;
12
+ }
13
+ export declare function runAdb(args: string[], options?: RunAdbOptions): Promise<AdbResult>;
14
+ /**
15
+ * Run adb and capture stdout as raw bytes (for screencap, file pulls, etc.).
16
+ * Uses spawn so the byte stream is never coerced through a text encoding.
17
+ */
18
+ export declare function runAdbBinary(args: string[], options?: RunAdbOptions): Promise<AdbBinaryResult>;
package/dist/adb.js ADDED
@@ -0,0 +1,109 @@
1
+ import { execFile, spawn } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { FlingError } from "./errors.js";
4
+ const execFileAsync = promisify(execFile);
5
+ const DEFAULT_TIMEOUT_MS = 15_000;
6
+ const DEFAULT_MAX_BUFFER = 10 * 1024 * 1024;
7
+ const ADB_INSTALL_HINT = "adb (Android Platform Tools) is required but was not found on PATH. " +
8
+ "Install it via `winget install Google.PlatformTools` (Windows), " +
9
+ "`brew install --cask android-platform-tools` (macOS), " +
10
+ "or your distro's package manager (Linux), then restart this MCP server.";
11
+ export async function runAdb(args, options = {}) {
12
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
13
+ const maxBuffer = options.maxBufferBytes ?? DEFAULT_MAX_BUFFER;
14
+ try {
15
+ const { stdout, stderr } = await execFileAsync("adb", args, {
16
+ timeout: timeoutMs,
17
+ maxBuffer,
18
+ windowsHide: true,
19
+ });
20
+ return { stdout, stderr };
21
+ }
22
+ catch (err) {
23
+ const e = err;
24
+ if (e.code === "ENOENT") {
25
+ throw new FlingError("ADB_NOT_FOUND", ADB_INSTALL_HINT);
26
+ }
27
+ if (e.killed) {
28
+ throw new FlingError("ADB_TIMEOUT", `adb ${args.join(" ")} timed out after ${timeoutMs}ms`, { stdout: e.stdout, stderr: e.stderr });
29
+ }
30
+ const exitCode = typeof e.code === "number" ? e.code : undefined;
31
+ throw new FlingError("ADB_FAILED", `adb ${args.join(" ")} failed${exitCode !== undefined ? ` (exit ${exitCode})` : ""}: ${(e.stderr || e.message || "unknown error").trim()}`, { stdout: e.stdout, stderr: e.stderr, exitCode });
32
+ }
33
+ }
34
+ /**
35
+ * Run adb and capture stdout as raw bytes (for screencap, file pulls, etc.).
36
+ * Uses spawn so the byte stream is never coerced through a text encoding.
37
+ */
38
+ export async function runAdbBinary(args, options = {}) {
39
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
40
+ const maxBuffer = options.maxBufferBytes ?? DEFAULT_MAX_BUFFER;
41
+ return new Promise((resolve, reject) => {
42
+ const child = spawn("adb", args, { windowsHide: true });
43
+ const stdoutChunks = [];
44
+ let stdoutBytes = 0;
45
+ let stderrText = "";
46
+ let timedOut = false;
47
+ let overBuffer = false;
48
+ // ENOENT fires both 'error' and 'close' on Node; without this guard we'd
49
+ // try to reject twice. The second reject is a no-op on the Promise but
50
+ // still constructs a stray FlingError that some monitors flag.
51
+ let settled = false;
52
+ const safeResolve = (v) => {
53
+ if (settled)
54
+ return;
55
+ settled = true;
56
+ resolve(v);
57
+ };
58
+ const safeReject = (e) => {
59
+ if (settled)
60
+ return;
61
+ settled = true;
62
+ reject(e);
63
+ };
64
+ const timer = setTimeout(() => {
65
+ timedOut = true;
66
+ child.kill();
67
+ }, timeoutMs);
68
+ child.stdout.on("data", (chunk) => {
69
+ stdoutBytes += chunk.length;
70
+ if (stdoutBytes > maxBuffer) {
71
+ overBuffer = true;
72
+ child.kill();
73
+ return;
74
+ }
75
+ stdoutChunks.push(chunk);
76
+ });
77
+ child.stderr.setEncoding("utf8");
78
+ child.stderr.on("data", (chunk) => {
79
+ stderrText += chunk;
80
+ });
81
+ child.on("error", (err) => {
82
+ clearTimeout(timer);
83
+ const e = err;
84
+ if (e.code === "ENOENT") {
85
+ safeReject(new FlingError("ADB_NOT_FOUND", ADB_INSTALL_HINT));
86
+ }
87
+ else {
88
+ safeReject(new FlingError("ADB_FAILED", `adb ${args.join(" ")} failed: ${err.message}`));
89
+ }
90
+ });
91
+ child.on("close", (code) => {
92
+ clearTimeout(timer);
93
+ if (timedOut) {
94
+ safeReject(new FlingError("ADB_TIMEOUT", `adb ${args.join(" ")} timed out after ${timeoutMs}ms`, { stderr: stderrText }));
95
+ return;
96
+ }
97
+ if (overBuffer) {
98
+ safeReject(new FlingError("ADB_FAILED", `adb ${args.join(" ")} exceeded the binary buffer cap of ${maxBuffer} bytes.`));
99
+ return;
100
+ }
101
+ if (code !== 0) {
102
+ safeReject(new FlingError("ADB_FAILED", `adb ${args.join(" ")} failed (exit ${code}): ${stderrText.trim() || "unknown error"}`, { stderr: stderrText, exitCode: code ?? undefined }));
103
+ return;
104
+ }
105
+ safeResolve({ stdout: Buffer.concat(stdoutChunks), stderr: stderrText });
106
+ });
107
+ });
108
+ }
109
+ //# sourceMappingURL=adb.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adb.js","sourceRoot":"","sources":["../src/adb.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export declare const DEFAULT_APK_GLOB = "**/outputs/apk/**/*.apk";
2
+ /**
3
+ * Convert a glob to a regex. Supports:
4
+ * `**` — any number of path segments (including zero, allowing top-level matches)
5
+ * `*` — any chars except `/`
6
+ * `?` — any single char except `/`
7
+ * All other regex metacharacters are escaped.
8
+ */
9
+ export declare function globToRegex(pattern: string): RegExp;
10
+ export declare function findFiles(rootDir: string, pattern: string): Promise<string[]>;
11
+ export interface FoundApk {
12
+ path: string;
13
+ mtimeMs: number;
14
+ }
15
+ /**
16
+ * Find the newest file matching the pattern under rootDir.
17
+ * Returns null when no match exists.
18
+ */
19
+ export declare function findNewestApk(rootDir: string, pattern?: string): Promise<FoundApk | null>;
@@ -0,0 +1,113 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ const SKIP_DIRS = new Set([
4
+ ".git",
5
+ ".gradle",
6
+ ".idea",
7
+ ".vscode",
8
+ "node_modules",
9
+ ]);
10
+ export const DEFAULT_APK_GLOB = "**/outputs/apk/**/*.apk";
11
+ /**
12
+ * Convert a glob to a regex. Supports:
13
+ * `**` — any number of path segments (including zero, allowing top-level matches)
14
+ * `*` — any chars except `/`
15
+ * `?` — any single char except `/`
16
+ * All other regex metacharacters are escaped.
17
+ */
18
+ export function globToRegex(pattern) {
19
+ let re = "";
20
+ let i = 0;
21
+ while (i < pattern.length) {
22
+ const c = pattern[i];
23
+ if (c === "*" && pattern[i + 1] === "*") {
24
+ // `**` semantics depend on what follows:
25
+ // - At end of pattern: match anything (including no slash) → `.*`
26
+ // - Followed by `/`: zero-or-more path segments → `(.*/)?`,
27
+ // so `**/*.apk` matches both `app.apk` and `nested/app.apk`.
28
+ // - Followed by non-slash text: literal "any chars" → `.*`
29
+ i += 2;
30
+ if (i >= pattern.length) {
31
+ re += ".*";
32
+ }
33
+ else if (pattern[i] === "/") {
34
+ re += "(.*/)?";
35
+ i++;
36
+ }
37
+ else {
38
+ re += ".*";
39
+ }
40
+ continue;
41
+ }
42
+ else if (c === "*") {
43
+ re += "[^/]*";
44
+ i++;
45
+ }
46
+ else if (c === "?") {
47
+ re += "[^/]";
48
+ i++;
49
+ }
50
+ else if (/[.+^$|(){}[\]\\]/.test(c)) {
51
+ re += "\\" + c;
52
+ i++;
53
+ }
54
+ else {
55
+ re += c;
56
+ i++;
57
+ }
58
+ }
59
+ return new RegExp(`^${re}$`);
60
+ }
61
+ export async function findFiles(rootDir, pattern) {
62
+ const regex = globToRegex(pattern);
63
+ const found = [];
64
+ async function walk(dir, relPath) {
65
+ let entries;
66
+ try {
67
+ entries = await readdir(dir, { withFileTypes: true });
68
+ }
69
+ catch {
70
+ return;
71
+ }
72
+ for (const entry of entries) {
73
+ const absChild = join(dir, entry.name);
74
+ const relChild = relPath ? `${relPath}/${entry.name}` : entry.name;
75
+ if (entry.isDirectory()) {
76
+ if (SKIP_DIRS.has(entry.name))
77
+ continue;
78
+ await walk(absChild, relChild);
79
+ }
80
+ else if (entry.isFile()) {
81
+ if (regex.test(relChild)) {
82
+ found.push(absChild);
83
+ }
84
+ }
85
+ }
86
+ }
87
+ await walk(rootDir, "");
88
+ return found;
89
+ }
90
+ /**
91
+ * Find the newest file matching the pattern under rootDir.
92
+ * Returns null when no match exists.
93
+ */
94
+ export async function findNewestApk(rootDir, pattern = DEFAULT_APK_GLOB) {
95
+ const matches = await findFiles(rootDir, pattern);
96
+ if (matches.length === 0)
97
+ return null;
98
+ const withMtime = await Promise.all(matches.map(async (p) => {
99
+ try {
100
+ const s = await stat(p);
101
+ return { path: p, mtimeMs: s.mtimeMs };
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ }));
107
+ const valid = withMtime.filter((x) => x !== null);
108
+ if (valid.length === 0)
109
+ return null;
110
+ valid.sort((a, b) => b.mtimeMs - a.mtimeMs);
111
+ return valid[0];
112
+ }
113
+ //# sourceMappingURL=apkFinder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apkFinder.js","sourceRoot":"","sources":["../src/apkFinder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { FlingConfig } from "./config.js";
2
+ export interface ResolvedApk {
3
+ path: string;
4
+ source: "explicit" | "config-path" | "config-glob" | "auto-discover";
5
+ }
6
+ /**
7
+ * Resolve which APK to install. Priority:
8
+ * 1. Explicit `explicitPath` argument (from the tool call)
9
+ * 2. config.apkPath
10
+ * 3. Glob search using config.apkGlob (or default) under buildCwd
11
+ *
12
+ * Throws FlingError("APK_NOT_FOUND") with the actual locations tried.
13
+ */
14
+ export declare function resolveApk(explicitPath: string | undefined, config: FlingConfig, buildCwd: string): Promise<ResolvedApk>;
@@ -0,0 +1,55 @@
1
+ import { stat } from "node:fs/promises";
2
+ import { isAbsolute, resolve as resolvePath } from "node:path";
3
+ import { DEFAULT_APK_GLOB, findNewestApk } from "./apkFinder.js";
4
+ import { FlingError } from "./errors.js";
5
+ async function fileExists(path) {
6
+ try {
7
+ return (await stat(path)).isFile();
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ /**
14
+ * Resolve which APK to install. Priority:
15
+ * 1. Explicit `explicitPath` argument (from the tool call)
16
+ * 2. config.apkPath
17
+ * 3. Glob search using config.apkGlob (or default) under buildCwd
18
+ *
19
+ * Throws FlingError("APK_NOT_FOUND") with the actual locations tried.
20
+ */
21
+ export async function resolveApk(explicitPath, config, buildCwd) {
22
+ if (explicitPath) {
23
+ // Relative paths resolve against buildCwd, matching how config.apkPath
24
+ // resolves. The previous behavior used process.cwd() which surprised
25
+ // users who passed relative paths from inside a project directory.
26
+ const abs = isAbsolute(explicitPath)
27
+ ? explicitPath
28
+ : resolvePath(buildCwd, explicitPath);
29
+ if (!(await fileExists(abs))) {
30
+ throw new FlingError("APK_NOT_FOUND", `APK not found at ${abs}.`);
31
+ }
32
+ return { path: abs, source: "explicit" };
33
+ }
34
+ if (config.apkPath) {
35
+ const abs = isAbsolute(config.apkPath)
36
+ ? config.apkPath
37
+ : resolvePath(buildCwd, config.apkPath);
38
+ if (!(await fileExists(abs))) {
39
+ throw new FlingError("APK_NOT_FOUND", `config.apkPath points to ${abs} but no file is there. ` +
40
+ "Build first, or update the apkPath in your fling.config.json.");
41
+ }
42
+ return { path: abs, source: "config-path" };
43
+ }
44
+ const pattern = config.apkGlob ?? DEFAULT_APK_GLOB;
45
+ const found = await findNewestApk(buildCwd, pattern);
46
+ if (!found) {
47
+ throw new FlingError("APK_NOT_FOUND", `No APK matched ${pattern} under ${buildCwd}. ` +
48
+ "Build the project first, or set apkPath in fling.config.json.");
49
+ }
50
+ return {
51
+ path: found.path,
52
+ source: config.apkGlob ? "config-glob" : "auto-discover",
53
+ };
54
+ }
55
+ //# sourceMappingURL=apkResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apkResolver.js","sourceRoot":"","sources":["../src/apkResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AAE/D,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAOzC,KAAK,UAAU,UAAU,CAAC,IAAY;IACpC,IAAI,CAAC;QACH,OAAO,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,YAAgC,EAChC,MAAmB,EACnB,QAAgB;IAEhB,IAAI,YAAY,EAAE,CAAC;QACjB,uEAAuE;QACvE,qEAAqE;QACrE,mEAAmE;QACnE,MAAM,GAAG,GAAG,UAAU,CAAC,YAAY,CAAC;YAClC,CAAC,CAAC,YAAY;YACd,CAAC,CAAC,WAAW,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;QACxC,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,UAAU,CAClB,eAAe,EACf,oBAAoB,GAAG,GAAG,CAC3B,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;IAC3C,CAAC;IAED,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC;YACpC,CAAC,CAAC,MAAM,CAAC,OAAO;YAChB,CAAC,CAAC,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1C,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YAC7B,MAAM,IAAI,UAAU,CAClB,eAAe,EACf,4BAA4B,GAAG,yBAAyB;gBACtD,+DAA+D,CAClE,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IAC9C,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,IAAI,gBAAgB,CAAC;IACnD,MAAM,KAAK,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IACrD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,UAAU,CAClB,eAAe,EACf,kBAAkB,OAAO,UAAU,QAAQ,IAAI;YAC7C,+DAA+D,CAClE,CAAC;IACJ,CAAC;IACD,OAAO;QACL,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,eAAe;KACzD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ export declare const flingConfigSchema: z.ZodObject<{
3
+ buildCommand: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodArray<z.ZodString, "many">]>>;
4
+ gradleTask: z.ZodOptional<z.ZodString>;
5
+ buildCwd: z.ZodOptional<z.ZodString>;
6
+ apkPath: z.ZodOptional<z.ZodString>;
7
+ apkGlob: z.ZodOptional<z.ZodString>;
8
+ packageName: z.ZodOptional<z.ZodString>;
9
+ mainActivity: z.ZodOptional<z.ZodString>;
10
+ }, "strip", z.ZodTypeAny, {
11
+ buildCommand?: string | string[] | undefined;
12
+ gradleTask?: string | undefined;
13
+ buildCwd?: string | undefined;
14
+ apkPath?: string | undefined;
15
+ apkGlob?: string | undefined;
16
+ packageName?: string | undefined;
17
+ mainActivity?: string | undefined;
18
+ }, {
19
+ buildCommand?: string | string[] | undefined;
20
+ gradleTask?: string | undefined;
21
+ buildCwd?: string | undefined;
22
+ apkPath?: string | undefined;
23
+ apkGlob?: string | undefined;
24
+ packageName?: string | undefined;
25
+ mainActivity?: string | undefined;
26
+ }>;
27
+ export type FlingConfig = z.infer<typeof flingConfigSchema>;
28
+ export interface LoadedConfig {
29
+ config: FlingConfig;
30
+ configPath: string | null;
31
+ projectRoot: string;
32
+ }
33
+ /**
34
+ * Walk up from `startDir` looking for fling.config.json or a "fling" key in
35
+ * package.json. The first hit wins. Returns the loaded (possibly empty) config
36
+ * and the project root (the directory of the config file, or `startDir` if
37
+ * none was found).
38
+ */
39
+ export declare function loadFlingConfig(startDir: string): Promise<LoadedConfig>;
40
+ /**
41
+ * Resolve the build working directory: explicit config.buildCwd (relative to
42
+ * the config file's directory) or the project root if not set.
43
+ */
44
+ export declare function resolveBuildCwd(loaded: LoadedConfig): string;
package/dist/config.js ADDED
@@ -0,0 +1,113 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { dirname, isAbsolute, resolve as resolvePath } from "node:path";
3
+ import { z } from "zod";
4
+ import { FlingError } from "./errors.js";
5
+ export const flingConfigSchema = z.object({
6
+ buildCommand: z
7
+ .union([z.string(), z.array(z.string())])
8
+ .optional()
9
+ .describe("Override for the build command. String form is split on whitespace. " +
10
+ "If omitted, the gradle wrapper at projectRoot/gradlew[.bat] is run with gradleTask."),
11
+ gradleTask: z
12
+ .string()
13
+ .optional()
14
+ .describe('Gradle task to run when buildCommand is not set. Default "assembleDebug".'),
15
+ buildCwd: z
16
+ .string()
17
+ .optional()
18
+ .describe("Directory to run the build from. Relative paths resolve against the config file's directory. Defaults to the config directory."),
19
+ apkPath: z
20
+ .string()
21
+ .optional()
22
+ .describe("Explicit path to the APK to install. Resolved relative to buildCwd. Takes precedence over apkGlob."),
23
+ apkGlob: z
24
+ .string()
25
+ .optional()
26
+ .describe('Glob (relative to buildCwd) for locating the build output. Default "**/outputs/apk/**/*.apk". Newest match by mtime wins.'),
27
+ packageName: z
28
+ .string()
29
+ .optional()
30
+ .describe("Default Android package for launch_app and install_app."),
31
+ mainActivity: z
32
+ .string()
33
+ .optional()
34
+ .describe("Optional default activity for launch_app. Use a leading dot for a package-relative class (e.g. .MainActivity)."),
35
+ });
36
+ const CONFIG_FILENAMES = ["fling.config.json"];
37
+ const PACKAGE_JSON = "package.json";
38
+ async function tryReadJson(path) {
39
+ try {
40
+ const content = await readFile(path, "utf8");
41
+ return JSON.parse(content);
42
+ }
43
+ catch (err) {
44
+ const e = err;
45
+ if (e.code === "ENOENT")
46
+ return null;
47
+ if (e instanceof SyntaxError) {
48
+ throw new FlingError("INVALID_INPUT", `Failed to parse ${path}: ${e.message}`);
49
+ }
50
+ throw err;
51
+ }
52
+ }
53
+ function validateConfig(raw, source) {
54
+ const parsed = flingConfigSchema.safeParse(raw);
55
+ if (!parsed.success) {
56
+ const issues = parsed.error.issues
57
+ .map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`)
58
+ .join("; ");
59
+ throw new FlingError("INVALID_INPUT", `Invalid Fling config at ${source}: ${issues}`);
60
+ }
61
+ return parsed.data;
62
+ }
63
+ /**
64
+ * Walk up from `startDir` looking for fling.config.json or a "fling" key in
65
+ * package.json. The first hit wins. Returns the loaded (possibly empty) config
66
+ * and the project root (the directory of the config file, or `startDir` if
67
+ * none was found).
68
+ */
69
+ export async function loadFlingConfig(startDir) {
70
+ let dir = resolvePath(startDir);
71
+ const seen = new Set();
72
+ while (!seen.has(dir)) {
73
+ seen.add(dir);
74
+ for (const name of CONFIG_FILENAMES) {
75
+ const candidate = resolvePath(dir, name);
76
+ const raw = await tryReadJson(candidate);
77
+ if (raw !== null) {
78
+ return {
79
+ config: validateConfig(raw, candidate),
80
+ configPath: candidate,
81
+ projectRoot: dir,
82
+ };
83
+ }
84
+ }
85
+ const pkgPath = resolvePath(dir, PACKAGE_JSON);
86
+ const pkgRaw = await tryReadJson(pkgPath);
87
+ if (pkgRaw && typeof pkgRaw === "object" && "fling" in pkgRaw) {
88
+ return {
89
+ config: validateConfig(pkgRaw.fling, pkgPath),
90
+ configPath: pkgPath,
91
+ projectRoot: dir,
92
+ };
93
+ }
94
+ const parent = dirname(dir);
95
+ if (parent === dir)
96
+ break;
97
+ dir = parent;
98
+ }
99
+ return { config: {}, configPath: null, projectRoot: resolvePath(startDir) };
100
+ }
101
+ /**
102
+ * Resolve the build working directory: explicit config.buildCwd (relative to
103
+ * the config file's directory) or the project root if not set.
104
+ */
105
+ export function resolveBuildCwd(loaded) {
106
+ const { config, projectRoot } = loaded;
107
+ if (!config.buildCwd)
108
+ return projectRoot;
109
+ if (isAbsolute(config.buildCwd))
110
+ return config.buildCwd;
111
+ return resolvePath(projectRoot, config.buildCwd);
112
+ }
113
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ export type DeviceState = "device" | "unauthorized" | "offline" | "no permissions" | "recovery" | "sideload" | "bootloader" | "unknown";
2
+ export interface Device {
3
+ serial: string;
4
+ state: DeviceState;
5
+ product?: string;
6
+ model?: string;
7
+ device?: string;
8
+ transportId?: string;
9
+ usb?: string;
10
+ raw: string;
11
+ }
12
+ export declare function parseDevicesOutput(stdout: string): Device[];
13
+ export declare function listDevices(): Promise<Device[]>;
14
+ export declare function formatDevicesSummary(devices: Device[]): string;
15
+ /**
16
+ * Resolve which `-s <id>` args to pass to adb.
17
+ *
18
+ * Priority: explicit deviceId → ANDROID_SERIAL env → auto-pick the single ready
19
+ * device. Throws FlingError when ambiguous or impossible.
20
+ */
21
+ export declare function resolveDeviceArgs(deviceId?: string): Promise<{
22
+ args: string[];
23
+ serial: string;
24
+ }>;