@skrillex1224/android-toolkit 0.1.9 → 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.
package/README.md CHANGED
@@ -1,87 +1,74 @@
1
1
  # Android Toolkit
2
2
 
3
- `android-toolkit` 是原生 Android androider 的最小公共底座,入口格式对齐
4
- `playwright-toolkit`:业务 actor 只通过 `useAndroidToolKit()` 获取模块,不直接引用内部文件。
3
+ `@skrillex1224/android-toolkit` is the native Android counterpart to
4
+ `@skrillex1224/playwright-toolkit`.
5
5
 
6
- ## 快速开始
6
+ Actor code should import only `useAndroidToolKit()` and then use the returned modules.
7
+ The public API is intentionally small and destructive from v1 onward.
7
8
 
8
9
  ```js
9
- import { useAndroidToolKit } from '../android-toolkit/index.js';
10
+ import { useAndroidToolKit } from '@skrillex1224/android-toolkit';
10
11
 
11
12
  const { Launch } = useAndroidToolKit();
12
13
 
13
- await Launch.run(async ({ input, ctx, kit }) => {
14
- const result = await kit.ApifyKit.runStep('执行业务流程', null, async () => {
15
- await kit.Device.forceStopApp(ctx, ctx.packageName);
16
- await kit.Device.click(ctx, { x: 360, y: 640 });
17
- return { answer: `echo: ${input.query}`, sources: [] };
14
+ await Launch.run(async ({ ctx, kit }) => {
15
+ const { ApifyKit, DeviceInput, Mutation, Share } = kit;
16
+ await ApifyKit.runStep('输入并发送', ctx, async () => {
17
+ await DeviceInput.click(ctx, { id: 'input_text' });
18
+ await DeviceInput.fill(ctx, { id: 'input_text' }, ctx.query);
19
+ await DeviceInput.pressEnter(ctx);
18
20
  });
19
-
20
- await kit.ApifyKit.pushSuccess(result);
21
- }, {
22
- inputPath,
23
- outputPath
21
+ await Mutation.waitForStable(ctx, { id: 'message_list' });
22
+ const screenshotBase64 = await Share.captureScreen(ctx);
23
+ await ApifyKit.pushArtifact({ screenshotBase64 });
24
24
  });
25
25
  ```
26
26
 
27
- `ctx.packageName` 来自后端注入的 `input.runtime.device.packageName`。Android Toolkit
28
- 不把包名放进 `Constants.ActorInfo`,也不在业务包里维护包名真源。
29
-
30
- ## 保留模块
27
+ ## Public Modules
31
28
 
32
- | 模块 | 说明 |
29
+ | Module | Responsibility |
33
30
  | --- | --- |
34
- | `Launch` | 接受显式 `inputPath` / `outputPath`,创建 `ctx`,调用业务 handler |
35
- | `ApifyKit` | 提供 `runStep`、`runStepLoose`、`pushSuccess`、`pushFailed`,并统一补齐 `code/status/timestamp/data` |
36
- | `Device` | ADB 操作层:启动、点击、滑动、输入中文、截图、Activity 检查 |
37
- | `Frida` | Frida attach、短脚本执行、SQLite 查询、WebView event recorder |
38
- | `Share` | 对齐 `playwright-toolkit` 的分享能力:高视口截图、分享链接捕获、截图压缩 |
39
- | `Context` | 从显式 input/defaults 生成 Android 运行上下文 |
40
- | `Errors` | `CrawlerError` 和错误序列化;失败码由业务显式抛出的 `CrawlerError` 决定 |
41
-
42
- ## Share
43
-
44
- `Share.captureScreen(ctx, options)` 用于采集 Android 页面长图。实现策略是先用
45
- Frida 扫描当前 Activity 的 View 树,读取所有可滚动 View 的
46
- `computeVerticalScrollRange()`,取最大所需高度后临时执行 `wm size WxH`,再用
47
- `screencap` 一次性截图。它不裁剪状态栏和导航栏;如果 View 树无法读取,会直接报错,
48
- 不会退回普通视口截图。默认压缩阈值与 `playwright-toolkit` 一致:base64 最大
49
- 5MiB,JPEG,质量 `0.72`,最低质量 `0.38`,最低缩放 `0.25`。
31
+ | `Launch` | Native actor entrypoint with explicit input/output paths and lifecycle hooks. |
32
+ | `ApifyKit` | `runStep`, `runStepLoose`, `pushSuccess`, `pushFailed`, `pushArtifact`. |
33
+ | `Device` | Low-level ADB primitives only. |
34
+ | `DeviceView` | Native View-tree snapshot and selector lookup over `uiautomator dump`. |
35
+ | `DeviceInput` | View-tree-backed click/fill/press/scroll APIs using ADB input. |
36
+ | `Mutation` | Playwright-like stable wait semantics over matched ViewNode subtrees. |
37
+ | `Share` | Sprite screenshot and clipboard share-link polling. |
38
+ | `Frida` | Public `querySQLite()` and `health()` only. |
39
+ | `Constants` | Native-useful actor metadata and codes. |
40
+ | `Errors` | `CrawlerError` and error serialization. |
50
41
 
51
- ```js
52
- const screenshotBase64 = await Share.captureScreen(ctx, {
53
- Frida,
54
- actorInfo: Constants.ActorInfo['doubao.android'].key,
55
- maxHeight: 8000
56
- });
57
- ```
42
+ ## Runtime Contract
58
43
 
59
- `Share.captureLink(ctx, options)` 负责按 `Constants.ActorInfo[actor].share.prefix`
60
- 校验分享链接。Android 当前稳定模式是 `clipboard`:业务层执行点击分享、复制链接,
61
- toolkit 通过 `Frida.runScript` 轮询系统剪贴板候选内容并提取匹配 prefix 的 URL。
44
+ The toolkit does not install or discover runtime tools.
62
45
 
63
- ```js
64
- const result = await Share.captureLink(ctx, {
65
- Frida,
66
- actorInfo: Constants.ActorInfo['doubao.android'].key,
67
- performActions: async () => clickShareAndCopyLink()
68
- });
69
- ```
46
+ Required environment:
47
+
48
+ - `ANDROID_TOOLKIT_ADB_PATH`
49
+ - `ANDROID_TOOLKIT_FRIDA_PATH`
50
+
51
+ Device initialization, Appium Settings, UnicodeIME, and frida-server provisioning belong
52
+ to manager/cluster runtime. Runtime checks fail clearly when prerequisites are missing.
53
+
54
+ ## Boundaries
70
55
 
71
- ## 当前边界
56
+ Included:
72
57
 
73
- 放进 toolkit:
58
+ - ADB command execution, app launch, key input, screenshot, activity checks.
59
+ - View-tree parsing with a real XML parser.
60
+ - View selector operations: `id`, `text`, `textContains`, `contentDesc`.
61
+ - Internal Frida script execution only where the toolkit owns the behavior, such as
62
+ SQLite queries or clipboard probing inside `Share`.
74
63
 
75
- - ADB 执行、云手机 TCP ADB 离线重连、点击、滑动、截图、中文输入。
76
- - Frida attach 目标 App pid,并启动 WebView JS 注入事件 recorder。
77
- - App 进程内 SQLite 查询:业务方提供 db 路径规则、SQL 和参数,toolkit 只返回 cursor rows。
78
- - `runStep` / `runStepLoose` / `pushSuccess` / `pushFailed`。
79
- - 通用错误码和失败 dataset 兜底结构。
64
+ Excluded:
80
65
 
81
- 不放进 toolkit:
66
+ - Public generic Frida script execution.
67
+ - WebView event recorder.
68
+ - Frida UI traversal/click helpers.
69
+ - Package name and activity truth. Backend/runtime input owns those values.
70
+ - Actor-specific DB schemas, message parsing, product parsing, and success criteria.
82
71
 
83
- - 任意平台的包名、Activity、WebView class 名称。
84
- - 任意平台的事件名、正文边界、引用来源解析规则。
85
- - 任意平台的成功标准和业务错误码。
72
+ ## Browser Entry
86
73
 
87
- 这些都留在具体 `*-androider` 内,例如 `wechat-androider/src/utils.js`。
74
+ `@skrillex1224/android-toolkit/browser` exports only `Constants` and `Errors`.
package/browser.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ export namespace Constants {
2
+ export const Code: Record<string, number>;
3
+ export const Status: Record<string, string>;
4
+ export const ActorInfo: Record<string, {
5
+ key: string;
6
+ name: string;
7
+ icon: string;
8
+ share: {
9
+ mode: 'clipboard';
10
+ prefix: string;
11
+ xurl: unknown[];
12
+ };
13
+ }>;
14
+ }
15
+
16
+ export namespace Errors {
17
+ export class CrawlerError extends Error {
18
+ code: number;
19
+ context: Record<string, unknown>;
20
+ constructor(input?: string | {
21
+ message?: string;
22
+ code?: number;
23
+ context?: Record<string, unknown>;
24
+ });
25
+ static isCrawlerError(error: unknown): boolean;
26
+ static from(error: unknown, fallback?: {
27
+ code?: number;
28
+ context?: Record<string, unknown>;
29
+ }): CrawlerError;
30
+ }
31
+ }
@@ -0,0 +1,107 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/constants.js
8
+ var constants_exports = {};
9
+ __export(constants_exports, {
10
+ ActorInfo: () => ActorInfo,
11
+ Code: () => Code,
12
+ Status: () => Status,
13
+ UnicodeIme: () => UnicodeIme
14
+ });
15
+ var Code = Object.freeze({
16
+ Success: 0,
17
+ UnknownError: -1,
18
+ NotLogin: 30000001,
19
+ Timeout: 30000003,
20
+ InitialTimeout: 30000004,
21
+ OverallTimeout: 30000005,
22
+ InvalidRequest: 30010001,
23
+ AdbUnavailable: 30010002,
24
+ ContentUnavailable: 30010003,
25
+ SourceExtractionFailed: 30010004,
26
+ AutomationFailed: 30010005,
27
+ FridaUnavailable: 30010008,
28
+ AppNotInstalled: 30010009
29
+ });
30
+ var Status = Object.freeze({
31
+ Success: "SUCCESS",
32
+ Failed: "FAILED"
33
+ });
34
+ var UnicodeIme = Object.freeze({
35
+ packageName: "io.appium.settings",
36
+ component: "io.appium.settings/.UnicodeIME",
37
+ inputTextAction: "ADB_INPUT_TEXT"
38
+ });
39
+ var normalizeShare = (share) => {
40
+ const source = share && typeof share === "object" ? share : {};
41
+ return {
42
+ mode: "clipboard",
43
+ prefix: String(source.prefix || "").trim(),
44
+ xurl: Array.isArray(source.xurl) ? source.xurl : []
45
+ };
46
+ };
47
+ var createActorInfo = (info) => {
48
+ const key = String(info.key || "").trim();
49
+ return Object.freeze({
50
+ key,
51
+ name: String(info.name || key),
52
+ icon: String(info.icon || `https://static.heartbitai.com/general/actors/${key}.png`),
53
+ share: Object.freeze(normalizeShare(info.share))
54
+ });
55
+ };
56
+ var ActorInfo = Object.freeze({
57
+ "doubao.android": createActorInfo({
58
+ key: "doubao.android",
59
+ name: "\u8C46\u5305 Android",
60
+ share: {
61
+ prefix: "https://www.doubao.com/thread/",
62
+ xurl: []
63
+ }
64
+ })
65
+ });
66
+
67
+ // src/errors.js
68
+ var errors_exports = {};
69
+ __export(errors_exports, {
70
+ CrawlerError: () => CrawlerError,
71
+ serializeError: () => serializeError
72
+ });
73
+ var CrawlerError = class _CrawlerError extends Error {
74
+ constructor(input = {}, options = {}) {
75
+ const payload = typeof input === "string" ? { message: input } : input;
76
+ super(payload.message || "Android crawler error", options);
77
+ this.name = "CrawlerError";
78
+ this.code = payload.code || Code.UnknownError;
79
+ this.context = payload.context || {};
80
+ }
81
+ static isCrawlerError(error) {
82
+ return error instanceof _CrawlerError || (error == null ? void 0 : error.name) === "CrawlerError";
83
+ }
84
+ static from(error, fallback = {}) {
85
+ if (_CrawlerError.isCrawlerError(error)) return error;
86
+ return new _CrawlerError({
87
+ message: (error == null ? void 0 : error.message) || String(error),
88
+ code: fallback.code || Code.UnknownError,
89
+ context: fallback.context || {}
90
+ });
91
+ }
92
+ };
93
+ function serializeError(error) {
94
+ if (!error) return { message: "" };
95
+ return {
96
+ name: error.name || "Error",
97
+ message: error.message || String(error),
98
+ stack: error.stack || "",
99
+ code: error.code || "",
100
+ context: error.context || void 0
101
+ };
102
+ }
103
+ export {
104
+ constants_exports as Constants,
105
+ errors_exports as Errors
106
+ };
107
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/constants.js", "../src/errors.js"],
4
+ "sourcesContent": ["export const Code = Object.freeze({\n Success: 0,\n UnknownError: -1,\n NotLogin: 30000001,\n Timeout: 30000003,\n InitialTimeout: 30000004,\n OverallTimeout: 30000005,\n InvalidRequest: 30010001,\n AdbUnavailable: 30010002,\n ContentUnavailable: 30010003,\n SourceExtractionFailed: 30010004,\n AutomationFailed: 30010005,\n FridaUnavailable: 30010008,\n AppNotInstalled: 30010009,\n});\n\nexport const Status = Object.freeze({\n Success: 'SUCCESS',\n Failed: 'FAILED',\n});\n\nexport const UnicodeIme = Object.freeze({\n packageName: 'io.appium.settings',\n component: 'io.appium.settings/.UnicodeIME',\n inputTextAction: 'ADB_INPUT_TEXT',\n});\n\nconst normalizeShare = (share) => {\n const source = share && typeof share === 'object' ? share : {};\n return {\n mode: 'clipboard',\n prefix: String(source.prefix || '').trim(),\n xurl: Array.isArray(source.xurl) ? source.xurl : [],\n };\n};\n\nconst createActorInfo = (info) => {\n const key = String(info.key || '').trim();\n return Object.freeze({\n key,\n name: String(info.name || key),\n icon: String(info.icon || `https://static.heartbitai.com/general/actors/${key}.png`),\n share: Object.freeze(normalizeShare(info.share)),\n });\n};\n\nexport const ActorInfo = Object.freeze({\n 'doubao.android': createActorInfo({\n key: 'doubao.android',\n name: '\u8C46\u5305 Android',\n share: {\n prefix: 'https://www.doubao.com/thread/',\n xurl: [],\n },\n }),\n});\n", "import { Code } from './constants.js';\n\nexport class CrawlerError extends Error {\n constructor(input = {}, options = {}) {\n const payload = typeof input === 'string' ? { message: input } : input;\n super(payload.message || 'Android crawler error', options);\n this.name = 'CrawlerError';\n this.code = payload.code || Code.UnknownError;\n this.context = payload.context || {};\n }\n\n static isCrawlerError(error) {\n return error instanceof CrawlerError || error?.name === 'CrawlerError';\n }\n\n static from(error, fallback = {}) {\n if (CrawlerError.isCrawlerError(error)) return error;\n return new CrawlerError({\n message: error?.message || String(error),\n code: fallback.code || Code.UnknownError,\n context: fallback.context || {},\n });\n }\n}\n\nexport function serializeError(error) {\n if (!error) return { message: '' };\n return {\n name: error.name || 'Error',\n message: error.message || String(error),\n stack: error.stack || '',\n code: error.code || '',\n context: error.context || undefined,\n };\n}\n"],
5
+ "mappings": ";;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAO,IAAM,OAAO,OAAO,OAAO;AAAA,EAC9B,SAAS;AAAA,EACT,cAAc;AAAA,EACd,UAAU;AAAA,EACV,SAAS;AAAA,EACT,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,gBAAgB;AAAA,EAChB,oBAAoB;AAAA,EACpB,wBAAwB;AAAA,EACxB,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,iBAAiB;AACrB,CAAC;AAEM,IAAM,SAAS,OAAO,OAAO;AAAA,EAChC,SAAS;AAAA,EACT,QAAQ;AACZ,CAAC;AAEM,IAAM,aAAa,OAAO,OAAO;AAAA,EACpC,aAAa;AAAA,EACb,WAAW;AAAA,EACX,iBAAiB;AACrB,CAAC;AAED,IAAM,iBAAiB,CAAC,UAAU;AAC9B,QAAM,SAAS,SAAS,OAAO,UAAU,WAAW,QAAQ,CAAC;AAC7D,SAAO;AAAA,IACH,MAAM;AAAA,IACN,QAAQ,OAAO,OAAO,UAAU,EAAE,EAAE,KAAK;AAAA,IACzC,MAAM,MAAM,QAAQ,OAAO,IAAI,IAAI,OAAO,OAAO,CAAC;AAAA,EACtD;AACJ;AAEA,IAAM,kBAAkB,CAAC,SAAS;AAC9B,QAAM,MAAM,OAAO,KAAK,OAAO,EAAE,EAAE,KAAK;AACxC,SAAO,OAAO,OAAO;AAAA,IACjB;AAAA,IACA,MAAM,OAAO,KAAK,QAAQ,GAAG;AAAA,IAC7B,MAAM,OAAO,KAAK,QAAQ,gDAAgD,GAAG,MAAM;AAAA,IACnF,OAAO,OAAO,OAAO,eAAe,KAAK,KAAK,CAAC;AAAA,EACnD,CAAC;AACL;AAEO,IAAM,YAAY,OAAO,OAAO;AAAA,EACnC,kBAAkB,gBAAgB;AAAA,IAC9B,KAAK;AAAA,IACL,MAAM;AAAA,IACN,OAAO;AAAA,MACH,QAAQ;AAAA,MACR,MAAM,CAAC;AAAA,IACX;AAAA,EACJ,CAAC;AACL,CAAC;;;ACvDD;AAAA;AAAA;AAAA;AAAA;AAEO,IAAM,eAAN,MAAM,sBAAqB,MAAM;AAAA,EACpC,YAAY,QAAQ,CAAC,GAAG,UAAU,CAAC,GAAG;AAClC,UAAM,UAAU,OAAO,UAAU,WAAW,EAAE,SAAS,MAAM,IAAI;AACjE,UAAM,QAAQ,WAAW,yBAAyB,OAAO;AACzD,SAAK,OAAO;AACZ,SAAK,OAAO,QAAQ,QAAQ,KAAK;AACjC,SAAK,UAAU,QAAQ,WAAW,CAAC;AAAA,EACvC;AAAA,EAEA,OAAO,eAAe,OAAO;AACzB,WAAO,iBAAiB,kBAAgB,+BAAO,UAAS;AAAA,EAC5D;AAAA,EAEA,OAAO,KAAK,OAAO,WAAW,CAAC,GAAG;AAC9B,QAAI,cAAa,eAAe,KAAK,EAAG,QAAO;AAC/C,WAAO,IAAI,cAAa;AAAA,MACpB,UAAS,+BAAO,YAAW,OAAO,KAAK;AAAA,MACvC,MAAM,SAAS,QAAQ,KAAK;AAAA,MAC5B,SAAS,SAAS,WAAW,CAAC;AAAA,IAClC,CAAC;AAAA,EACL;AACJ;AAEO,SAAS,eAAe,OAAO;AAClC,MAAI,CAAC,MAAO,QAAO,EAAE,SAAS,GAAG;AACjC,SAAO;AAAA,IACH,MAAM,MAAM,QAAQ;AAAA,IACpB,SAAS,MAAM,WAAW,OAAO,KAAK;AAAA,IACtC,OAAO,MAAM,SAAS;AAAA,IACtB,MAAM,MAAM,QAAQ;AAAA,IACpB,SAAS,MAAM,WAAW;AAAA,EAC9B;AACJ;",
6
+ "names": []
7
+ }