@11agents/cli 0.1.24 → 0.1.26

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 (71) hide show
  1. package/README.md +52 -0
  2. package/bin/11agents.js +12 -0
  3. package/mobile-runtime/README.md +19 -0
  4. package/mobile-runtime/configs/platforms/xiaohongshu_d01.json +73 -0
  5. package/mobile-runtime/configs/platforms/xiaohongshu_d02.json +70 -0
  6. package/mobile-runtime/configs/platforms/xiaohongshu_d03.json +73 -0
  7. package/mobile-runtime/configs/publish_policy.json +40 -0
  8. package/mobile-runtime/data-templates/README.md +4 -0
  9. package/mobile-runtime/data-templates/accounts.example.csv +6 -0
  10. package/mobile-runtime/data-templates/devices.example.csv +2 -0
  11. package/mobile-runtime/data-templates/publish_records.example.jsonl +2 -0
  12. package/mobile-runtime/data-templates/tasks.example.jsonl +5 -0
  13. package/mobile-runtime/data-templates/video_metrics.example.jsonl +1 -0
  14. package/mobile-runtime/python/pyproject.toml +34 -0
  15. package/mobile-runtime/python/src/device_control/__init__.py +5 -0
  16. package/mobile-runtime/python/src/device_control/adapters/__init__.py +31 -0
  17. package/mobile-runtime/python/src/device_control/adapters/base.py +43 -0
  18. package/mobile-runtime/python/src/device_control/adapters/facebook.py +30 -0
  19. package/mobile-runtime/python/src/device_control/adapters/instagram.py +25 -0
  20. package/mobile-runtime/python/src/device_control/adapters/reddit.py +29 -0
  21. package/mobile-runtime/python/src/device_control/adapters/tiktok.py +25 -0
  22. package/mobile-runtime/python/src/device_control/adapters/x.py +29 -0
  23. package/mobile-runtime/python/src/device_control/adapters/xiaohongshu.py +26 -0
  24. package/mobile-runtime/python/src/device_control/adb.py +161 -0
  25. package/mobile-runtime/python/src/device_control/appium_client.py +131 -0
  26. package/mobile-runtime/python/src/device_control/appium_manager.py +403 -0
  27. package/mobile-runtime/python/src/device_control/cli.py +1608 -0
  28. package/mobile-runtime/python/src/device_control/entrypoints.py +60 -0
  29. package/mobile-runtime/python/src/device_control/locks.py +162 -0
  30. package/mobile-runtime/python/src/device_control/metrics/__init__.py +33 -0
  31. package/mobile-runtime/python/src/device_control/metrics/collectors.py +320 -0
  32. package/mobile-runtime/python/src/device_control/metrics/tiktok_account_adb.py +367 -0
  33. package/mobile-runtime/python/src/device_control/metrics/tiktok_video_adb.py +714 -0
  34. package/mobile-runtime/python/src/device_control/models.py +439 -0
  35. package/mobile-runtime/python/src/device_control/publish_policy.py +173 -0
  36. package/mobile-runtime/python/src/device_control/publishers/__init__.py +24 -0
  37. package/mobile-runtime/python/src/device_control/publishers/facebook_adb.py +494 -0
  38. package/mobile-runtime/python/src/device_control/publishers/instagram_adb.py +663 -0
  39. package/mobile-runtime/python/src/device_control/publishers/reddit_adb.py +595 -0
  40. package/mobile-runtime/python/src/device_control/publishers/tiktok_adb.py +477 -0
  41. package/mobile-runtime/python/src/device_control/publishers/tiktok_appium.py +259 -0
  42. package/mobile-runtime/python/src/device_control/publishers/ui_helpers.py +372 -0
  43. package/mobile-runtime/python/src/device_control/publishers/x_adb.py +636 -0
  44. package/mobile-runtime/python/src/device_control/publishers/xiaohongshu_adb.py +1143 -0
  45. package/mobile-runtime/python/src/device_control/store.py +137 -0
  46. package/mobile-runtime/scripts/appium_smoke.py +71 -0
  47. package/mobile-runtime/skills/android-collect-tiktok-metrics/SKILL.md +60 -0
  48. package/mobile-runtime/skills/android-collect-tiktok-metrics/agents/openai.yaml +4 -0
  49. package/mobile-runtime/skills/android-group-control-cli/SKILL.md +76 -0
  50. package/mobile-runtime/skills/android-group-control-cli/agents/openai.yaml +4 -0
  51. package/mobile-runtime/skills/android-group-control-cli/references/command-reference.md +122 -0
  52. package/mobile-runtime/skills/android-publish-facebook/SKILL.md +41 -0
  53. package/mobile-runtime/skills/android-publish-facebook/agents/openai.yaml +4 -0
  54. package/mobile-runtime/skills/android-publish-instagram/SKILL.md +45 -0
  55. package/mobile-runtime/skills/android-publish-instagram/agents/openai.yaml +4 -0
  56. package/mobile-runtime/skills/android-publish-reddit/SKILL.md +41 -0
  57. package/mobile-runtime/skills/android-publish-reddit/agents/openai.yaml +4 -0
  58. package/mobile-runtime/skills/android-publish-tiktok/SKILL.md +43 -0
  59. package/mobile-runtime/skills/android-publish-tiktok/agents/openai.yaml +4 -0
  60. package/mobile-runtime/skills/android-publish-x/SKILL.md +40 -0
  61. package/mobile-runtime/skills/android-publish-x/agents/openai.yaml +4 -0
  62. package/mobile-runtime/skills/android-publish-xiaohongshu/SKILL.md +50 -0
  63. package/mobile-runtime/skills/android-publish-xiaohongshu/agents/openai.yaml +4 -0
  64. package/mobile-runtime/skills/mobile-publish-data-collection/SKILL.md +49 -0
  65. package/mobile-runtime/skills/mobile-publish-device-health/SKILL.md +47 -0
  66. package/mobile-runtime/skills/mobile-publish-execution/SKILL.md +57 -0
  67. package/mobile-runtime/skills/mobile-publish-records/SKILL.md +29 -0
  68. package/package.json +4 -1
  69. package/scripts/mobile-postinstall.js +26 -0
  70. package/src/commands/mobile.js +695 -0
  71. package/src/commands/runtime.js +21 -5
package/README.md CHANGED
@@ -10,6 +10,14 @@ npm install -g @11agents/cli@latest
10
10
 
11
11
  The CLI requires Node.js 22 or newer.
12
12
 
13
+ Android publishing machines can opt into mobile runtime setup during install:
14
+
15
+ ```bash
16
+ ELEVENAGENTS_INSTALL_MOBILE=1 npm install -g @11agents/cli@latest
17
+ ```
18
+
19
+ If setup must fail the npm install when Python/Appium prerequisites cannot be prepared, use `ELEVENAGENTS_REQUIRE_MOBILE=1`.
20
+
13
21
  ## Configure
14
22
 
15
23
  ```bash
@@ -125,6 +133,50 @@ export async function handleRuntimeTask(task) {
125
133
  }
126
134
  ```
127
135
 
136
+ ## Mobile Runtime
137
+
138
+ `@11agents/cli` bundles the Android device-pool runtime that used to live in `android-group-control-mvp`: Python `device_control`, platform configs, mobile publish skills, data templates, and the Appium smoke script. Runtime state is kept outside the npm package:
139
+
140
+ - Mobile home: `~/.11agents/mobile` or `ELEVENAGENTS_MOBILE_HOME`
141
+ - Python venv: `~/.11agents/mobile/.venv`
142
+ - Device/account data: `~/.11agents/mobile/data`
143
+ - Screenshots/UI dumps: `~/.11agents/mobile/artifacts`
144
+ - Task logs: `~/.11agents/mobile/runs/<task_id>/log`
145
+
146
+ Prepare a publishing machine:
147
+
148
+ ```bash
149
+ 11agents mobile setup
150
+ 11agents mobile doctor --json
151
+ ```
152
+
153
+ `setup` creates the Python venv, installs the bundled Python runtime, installs local Appium npm dependencies, installs UiAutomator2 into `APPIUM_HOME`, and seeds example data/config files without overwriting existing runtime data.
154
+
155
+ Core commands:
156
+
157
+ ```bash
158
+ 11agents mobile list-adb
159
+ 11agents mobile list-devices --health --json
160
+ 11agents mobile ensure-appium --device D03 --json --task-id TASK_123
161
+ 11agents mobile publish-tiktok --device D03 --video /path/video.mp4 --caption "..." --json --task-id TASK_123
162
+ 11agents mobile publish-instagram --device D03 --post-type reel --video /path/video.mp4 --caption "..." --json --task-id TASK_123
163
+ 11agents mobile publish-facebook --device D03 --post-type text --text "..." --json --task-id TASK_123
164
+ 11agents mobile publish-reddit --device D02 --post-type text --subreddit test --title "..." --body "..." --json --task-id TASK_123
165
+ 11agents mobile publish-x --device D03 --post-type text --body "..." --json --task-id TASK_123
166
+ 11agents mobile publish-xiaohongshu --device D03 --publish-package /path/publish_package.json --json --task-id TASK_123
167
+ ```
168
+
169
+ Post-publish data recovery:
170
+
171
+ ```bash
172
+ 11agents mobile list-publish-records --json --task-id TASK_123
173
+ 11agents mobile data collect --platform reddit --record-id pub_123 --json --task-id TASK_123
174
+ 11agents mobile data collect --platform tiktok --scope video --device D03 --record-id pub_123 --json --task-id TASK_123
175
+ 11agents mobile data collect --platform xiaohongshu --record-id pub_123 --copy-link --json --task-id TASK_123
176
+ ```
177
+
178
+ Unsupported collectors return `missing_access`; provide an API token, export, or manual values and append them with `11agents mobile add-metrics`.
179
+
128
180
  ## Hosted MCP
129
181
 
130
182
  Runtime agents and MCP clients should use the hosted project MCP endpoint:
package/bin/11agents.js CHANGED
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'
4
4
  import { parseArgs } from '../src/args.js'
5
5
  import { knowledgeStatus, syncKnowledge } from '../src/commands/knowledge.js'
6
6
  import { showDaemonLog, showTaskLog } from '../src/commands/logs.js'
7
+ import { runMobile } from '../src/commands/mobile.js'
7
8
  import { runNode } from '../src/commands/node.js'
8
9
  import { pushArtifact, pushBatch, pushObservation } from '../src/commands/push.js'
9
10
  import { registerRuntime, scanRuntime, startRuntimeDaemon } from '../src/commands/runtime.js'
@@ -33,6 +34,11 @@ Usage:
33
34
  11agents push observation --workspace <slug> --agent <key> --platform x --type post --external-id <id> --metric views=123
34
35
  11agents knowledge sync --project <slug> [--mode pull|push]
35
36
  11agents knowledge status --project <slug>
37
+ 11agents mobile setup
38
+ 11agents mobile doctor [--json]
39
+ 11agents mobile <device|publish|records|metrics command> [args...]
40
+ 11agents mobile data collect --platform <x|reddit|instagram|facebook|tiktok|xiaohongshu>
41
+ 11agents mobile ... --task-id <task-id> # logs under ~/.11agents/mobile/runs/<task-id>/log
36
42
  11agents node run --workspace <slug> --agent <key> --node <node-id> --handler ./collect-x.js [--once]
37
43
 
38
44
  Environment:
@@ -42,6 +48,7 @@ Environment:
42
48
  ELEVENAGENTS_MACHINE stable machine key, defaults to hostname
43
49
  GTM_SWARM_TOKEN project swarm token for push/node commands
44
50
  ~/.11agents/credentials project token map for hosted MCP and sync commands
51
+ ELEVENAGENTS_MOBILE_HOME optional mobile runtime state dir, defaults ~/.11agents/mobile
45
52
 
46
53
  Runtime task workspace:
47
54
  Daemon project headquarters live under ~/.11agents/<project>/.
@@ -135,6 +142,11 @@ async function main() {
135
142
  return
136
143
  }
137
144
 
145
+ if (command === 'mobile') {
146
+ await runMobile(argv.slice(1))
147
+ return
148
+ }
149
+
138
150
  if (command === 'validate') {
139
151
  const file = subcommand
140
152
  if (!file) throw new Error('validate requires a file')
@@ -0,0 +1,19 @@
1
+ # 11agents Mobile Runtime
2
+
3
+ This directory is bundled into `@11agents/cli` and contains the Android device-pool runtime:
4
+
5
+ - Python package: `python/src/device_control`
6
+ - Platform configs: `configs`
7
+ - Mobile skills: `skills`
8
+ - Data templates: `data-templates`
9
+ - Diagnostic scripts: `scripts`
10
+
11
+ Mutable state must not be written here. `11agents mobile setup` seeds state under `${ELEVENAGENTS_MOBILE_HOME:-~/.11agents/mobile}`.
12
+
13
+ Agent task logs are written to:
14
+
15
+ ```text
16
+ ~/.11agents/mobile/runs/<task_id>/log
17
+ ```
18
+
19
+ Use `11agents mobile skills list` and `11agents mobile skills show <skill-name>` to inspect bundled mobile skill guidance.
@@ -0,0 +1,73 @@
1
+ {
2
+ "package": "com.xingin.xhs",
3
+ "screen": {
4
+ "width": 1080,
5
+ "height": 2400
6
+ },
7
+ "coords": {
8
+ "unlock_swipe": [540, 1850, 540, 550, 350],
9
+ "home_plus": [540, 2230],
10
+ "album_drawer_first": [540, 1695],
11
+ "latest_video_select": [1025, 425],
12
+ "latest_image_select": [305, 425],
13
+ "multi_image_select": [
14
+ [305, 425],
15
+ [665, 425],
16
+ [1025, 425],
17
+ [305, 787],
18
+ [665, 787],
19
+ [1025, 787],
20
+ [305, 1150]
21
+ ],
22
+ "gallery_next": [800, 2240],
23
+ "editor_next": [885, 2220],
24
+ "first_publish": [945, 180],
25
+ "confirm_publish": [725, 2200],
26
+ "bottom_me": [972, 2230],
27
+ "profile_latest_note": [270, 1220],
28
+ "post_publish_prompt_close": [1020, 1200],
29
+ "note_share": [988, 186],
30
+ "copy_link": [137, 2142],
31
+ "comment_input": [421, 374]
32
+ },
33
+ "selectors": {
34
+ "title": "com.xingin.xhs:id/editTitle",
35
+ "caption": "com.xingin.xhs:id/postNoteEditContentView"
36
+ },
37
+ "waits": {
38
+ "after_wake": 1,
39
+ "after_unlock": 1,
40
+ "after_launch": 5,
41
+ "after_plus": 1,
42
+ "after_album_entry": 3,
43
+ "after_latest_video_select": 1,
44
+ "after_gallery_next": 4,
45
+ "after_editor_next": 4,
46
+ "after_title_click": 1,
47
+ "after_caption_click": 1,
48
+ "after_caption_input": 1,
49
+ "after_first_publish": 2,
50
+ "after_confirm_publish": 2,
51
+ "after_prompt_dismiss": 1,
52
+ "after_me_tab": 3,
53
+ "after_latest_note_tap": 3,
54
+ "after_share_tap": 1,
55
+ "after_copy_link": 1,
56
+ "after_comment_input_tap": 1,
57
+ "after_paste_probe": 1,
58
+ "detail_load_timeout": 12,
59
+ "publish_poll_interval": 2,
60
+ "publish_timeout": 45
61
+ },
62
+ "success_focus_keywords": [
63
+ "IndexActivityV2"
64
+ ],
65
+ "detail_focus_keywords": [
66
+ "NoteDetailActivity"
67
+ ],
68
+ "in_progress_focus_keywords": [
69
+ "CapaPostNotePlatformActivity",
70
+ "VideoEditActivity",
71
+ "CapaAlbumActivity"
72
+ ]
73
+ }
@@ -0,0 +1,70 @@
1
+ {
2
+ "package": "com.xingin.xhs",
3
+ "screen": {
4
+ "width": 1080,
5
+ "height": 2280
6
+ },
7
+ "coords": {
8
+ "unlock_swipe": [540, 1758, 540, 523, 350],
9
+ "home_plus": [540, 2119],
10
+ "album_drawer_first": [540, 1610],
11
+ "latest_video_select": [1025, 404],
12
+ "latest_image_select": [305, 404],
13
+ "multi_image_select": [
14
+ [305, 404],
15
+ [665, 404],
16
+ [1025, 404],
17
+ [305, 748],
18
+ [665, 748],
19
+ [1025, 748],
20
+ [305, 1092]
21
+ ],
22
+ "gallery_next": [800, 2128],
23
+ "editor_next": [885, 2109],
24
+ "first_publish": [945, 180],
25
+ "confirm_publish": [725, 2090],
26
+ "bottom_me": [972, 2119],
27
+ "profile_latest_note": [270, 1135],
28
+ "post_publish_prompt_close": [1020, 1140],
29
+ "note_share": [988, 186],
30
+ "copy_link": [137, 2035],
31
+ "comment_input": [421, 355]
32
+ },
33
+ "selectors": {
34
+ "title": "com.xingin.xhs:id/editTitle",
35
+ "caption": "com.xingin.xhs:id/postNoteEditContentView"
36
+ },
37
+ "waits": {
38
+ "after_wake": 1,
39
+ "after_unlock": 1,
40
+ "after_launch": 5,
41
+ "after_plus": 1,
42
+ "after_album_entry": 3,
43
+ "after_latest_video_select": 1,
44
+ "after_gallery_next": 4,
45
+ "after_editor_next": 4,
46
+ "after_title_click": 1,
47
+ "after_caption_click": 1,
48
+ "after_caption_input": 1,
49
+ "after_first_publish": 2,
50
+ "after_confirm_publish": 2,
51
+ "after_prompt_dismiss": 1,
52
+ "after_me_tab": 3,
53
+ "after_latest_note_tap": 3,
54
+ "after_share_tap": 1,
55
+ "after_copy_link": 1,
56
+ "after_comment_input_tap": 1,
57
+ "after_paste_probe": 1,
58
+ "detail_load_timeout": 12,
59
+ "publish_poll_interval": 2,
60
+ "publish_timeout": 45
61
+ },
62
+ "success_focus_keywords": ["IndexActivityV2"],
63
+ "detail_focus_keywords": ["NoteDetailActivity"],
64
+ "in_progress_focus_keywords": [
65
+ "CapaPostNotePlatformActivity",
66
+ "VideoEditActivity",
67
+ "ImageEditActivity",
68
+ "CapaAlbumActivity"
69
+ ]
70
+ }
@@ -0,0 +1,73 @@
1
+ {
2
+ "package": "com.xingin.xhs",
3
+ "screen": {
4
+ "width": 1080,
5
+ "height": 2400
6
+ },
7
+ "coords": {
8
+ "unlock_swipe": [540, 1850, 540, 550, 350],
9
+ "home_plus": [540, 2230],
10
+ "album_drawer_first": [540, 1695],
11
+ "latest_video_select": [1025, 425],
12
+ "latest_image_select": [1025, 425],
13
+ "multi_image_select": [
14
+ [1028, 424],
15
+ [665, 424],
16
+ [302, 424],
17
+ [1028, 787],
18
+ [665, 787],
19
+ [302, 787],
20
+ [1028, 1150]
21
+ ],
22
+ "gallery_next": [800, 2240],
23
+ "editor_next": [885, 2220],
24
+ "first_publish": [945, 180],
25
+ "confirm_publish": [725, 2200],
26
+ "bottom_me": [972, 2230],
27
+ "profile_latest_note": [270, 1220],
28
+ "post_publish_prompt_close": [1020, 1200],
29
+ "note_share": [988, 186],
30
+ "copy_link": [137, 2142],
31
+ "comment_input": [421, 374]
32
+ },
33
+ "selectors": {
34
+ "title": "com.xingin.xhs:id/editTitle",
35
+ "caption": "com.xingin.xhs:id/postNoteEditContentView"
36
+ },
37
+ "waits": {
38
+ "after_wake": 1,
39
+ "after_unlock": 1,
40
+ "after_launch": 5,
41
+ "after_plus": 1,
42
+ "after_album_entry": 3,
43
+ "after_latest_video_select": 1,
44
+ "after_gallery_next": 4,
45
+ "after_editor_next": 4,
46
+ "after_title_click": 1,
47
+ "after_caption_click": 1,
48
+ "after_caption_input": 1,
49
+ "after_first_publish": 2,
50
+ "after_confirm_publish": 2,
51
+ "after_prompt_dismiss": 1,
52
+ "after_me_tab": 3,
53
+ "after_latest_note_tap": 3,
54
+ "after_share_tap": 1,
55
+ "after_copy_link": 1,
56
+ "after_comment_input_tap": 1,
57
+ "after_paste_probe": 1,
58
+ "detail_load_timeout": 12,
59
+ "publish_poll_interval": 2,
60
+ "publish_timeout": 45
61
+ },
62
+ "success_focus_keywords": [
63
+ "IndexActivityV2"
64
+ ],
65
+ "detail_focus_keywords": [
66
+ "NoteDetailActivity"
67
+ ],
68
+ "in_progress_focus_keywords": [
69
+ "CapaPostNotePlatformActivity",
70
+ "VideoEditActivity",
71
+ "CapaAlbumActivity"
72
+ ]
73
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "global": {
3
+ "explicit_live_publish_auto_confirm": true
4
+ },
5
+ "instagram": {
6
+ "auto_confirm_reels_public": true
7
+ },
8
+ "xiaohongshu": {
9
+ "approval_bridge": {
10
+ "enabled": true,
11
+ "explicit_publish_states": [
12
+ "approved_for_publish",
13
+ "approved_to_publish",
14
+ "approved_to_publish_by_request",
15
+ "publish_requested_by_member_comment",
16
+ "publish_requested_for_series_by_member_comment",
17
+ "publish_requested_for_single_post_by_member_comment"
18
+ ]
19
+ },
20
+ "commercial_disclosure": {
21
+ "auto_confirm_no_disclosure_needed": true,
22
+ "default_decision": "no_disclosure_needed",
23
+ "trust_explicit_publish_request": true,
24
+ "strict_package_disclosure_decision": false,
25
+ "unresolved_decisions_follow_default": true,
26
+ "no_disclosure_decisions": [
27
+ "no_disclosure_needed",
28
+ "not_required",
29
+ "not_needed",
30
+ "none"
31
+ ],
32
+ "requires_disclosure_decisions": [
33
+ "disclosure_required",
34
+ "commercial_disclosure_required",
35
+ "content_declaration_required",
36
+ "required"
37
+ ]
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,4 @@
1
+ # 11agents Mobile Data Templates
2
+
3
+ These files seed `~/.11agents/mobile/data` during `11agents mobile setup`.
4
+ Replace example devices and accounts with company-owned, authorized Android devices and logged-in accounts before publishing.
@@ -0,0 +1,6 @@
1
+ account_id,platform,username,bound_device_id,login_status,status,notes
2
+ RD_001,reddit,company_reddit_001,D01,logged_in,active,example account
3
+ TT_001,tiktok,company_tiktok_001,D01,logged_in,active,example account
4
+ IG_001,instagram,company_instagram_001,D02,unknown,paused,example account
5
+ FB_001,facebook,company_facebook_001,D03,unknown,paused,example account
6
+ XHS_001,xiaohongshu,company_xiaohongshu_001,D04,unknown,paused,example account
@@ -0,0 +1,2 @@
1
+ device_id,adb_serial,ip_address,brand,model,android_version,status,notes
2
+ D01,REPLACE_WITH_ADB_SERIAL,,ExampleBrand,ExampleModel,13,active,replace with an authorized company-owned Android device
@@ -0,0 +1,2 @@
1
+ {"account_id":"acct_tiktok_001","caption":"demo caption","created_at":"2026-05-27T09:00:00+00:00","device_id":"D01","local_media_path":"/Users/edy/Desktop/demo.mp4","platform":"tiktok","platform_permalink":"https://www.tiktok.com/@example/video/1234567890","platform_post_id":"1234567890","post_type":"video","published_at":"2026-05-27T09:00:00+00:00","record_id":"pub_demo_tiktok_001","remote_media_path":"/sdcard/DCIM/Camera/demo.mp4","result_screenshot_path":"artifacts/screenshots/demo-after-post/D01.png","status":"published","task_id":"task_demo_001"}
2
+ {"account_id":"acct_reddit_001","caption":"","created_at":"2026-05-27T09:05:00+00:00","device_id":"D02","local_media_path":"","platform":"reddit","platform_permalink":"https://www.reddit.com/r/test/comments/abc123/demo/","platform_post_id":"abc123","post_type":"text","published_at":"2026-05-27T09:05:00+00:00","record_id":"pub_demo_reddit_001","remote_media_path":"","result_screenshot_path":"artifacts/screenshots/demo-reddit-after-post/D02.png","status":"published","task_id":"task_demo_002"}
@@ -0,0 +1,5 @@
1
+ {"task_id":"TASK_RD_001","action":"publish_content","platform":"reddit","post_type":"text","account_id":"RD_001","device_id":"D01","target":"r/test","title":"Dry-run test post","body":"This is a dry-run test post.","link_url":null,"media_path":null,"caption":null,"hashtags":[],"dry_run":true,"require_human_confirm":true}
2
+ {"task_id":"TASK_TT_001","action":"publish_content","platform":"tiktok","post_type":"video","account_id":"TT_001","device_id":"D01","target":null,"title":null,"body":null,"link_url":null,"media_path":"media/video-001.mp4","caption":"New product demo","hashtags":["demo","product"],"dry_run":true,"require_human_confirm":true}
3
+ {"task_id":"TASK_IG_001","action":"publish_content","platform":"instagram","post_type":"video","account_id":"IG_001","device_id":"D02","target":null,"title":null,"body":null,"link_url":null,"media_path":"media/video-001.mp4","caption":"New product demo","hashtags":["demo","product"],"dry_run":true,"require_human_confirm":true}
4
+ {"task_id":"TASK_FB_001","action":"publish_content","platform":"facebook","post_type":"text","account_id":"FB_001","device_id":"D03","target":null,"title":null,"body":"Dry-run Facebook post.","link_url":null,"media_path":null,"caption":null,"hashtags":[],"dry_run":true,"require_human_confirm":true}
5
+ {"task_id":"TASK_XHS_001","action":"publish_content","platform":"xiaohongshu","post_type":"image","account_id":"XHS_001","device_id":"D04","target":null,"title":"Dry-run Xiaohongshu note","body":null,"link_url":null,"media_path":"media/image-001.jpg","caption":"New product note","hashtags":["demo","product"],"dry_run":true,"require_human_confirm":true}
@@ -0,0 +1 @@
1
+ {"account_id":"acct_tiktok_001","comments":3,"fetched_at":"2026-05-27T10:00:00+00:00","likes":42,"platform":"tiktok","platform_permalink":"https://www.tiktok.com/@example/video/1234567890","platform_post_id":"1234567890","raw":{"view_count":1200},"record_id":"pub_demo_tiktok_001","saves":null,"score":null,"shares":5,"snapshot_id":"met_demo_tiktok_001","source":"tiktok_display_api","views":1200}
@@ -0,0 +1,34 @@
1
+ [project]
2
+ name = "elevenagents-mobile-runtime"
3
+ version = "0.1.0"
4
+ description = "11agents Android device-pool runtime for mobile publishing, records, and data collection."
5
+ requires-python = ">=3.10"
6
+ dependencies = [
7
+ "Pillow>=10",
8
+ ]
9
+
10
+ [project.optional-dependencies]
11
+ appium = [
12
+ "Appium-Python-Client>=4.0",
13
+ ]
14
+
15
+ [project.scripts]
16
+ groupctl = "device_control.cli:main"
17
+ publish-facebook = "device_control.entrypoints:publish_facebook"
18
+ publish-instagram = "device_control.entrypoints:publish_instagram"
19
+ publish-reddit = "device_control.entrypoints:publish_reddit"
20
+ publish-tiktok = "device_control.entrypoints:publish_tiktok"
21
+ publish-x = "device_control.entrypoints:publish_x"
22
+ publish-xiaohongshu = "device_control.entrypoints:publish_xiaohongshu"
23
+ copy-xiaohongshu-link = "device_control.entrypoints:copy_xiaohongshu_link"
24
+ ensure-appium = "device_control.entrypoints:ensure_appium"
25
+ stop-appium = "device_control.entrypoints:stop_appium"
26
+ collect-tiktok-account-metrics = "device_control.entrypoints:collect_tiktok_account_metrics"
27
+ collect-tiktok-video-metrics = "device_control.entrypoints:collect_tiktok_video_metrics"
28
+
29
+ [build-system]
30
+ requires = ["setuptools>=68"]
31
+ build-backend = "setuptools.build_meta"
32
+
33
+ [tool.setuptools.packages.find]
34
+ where = ["src"]
@@ -0,0 +1,5 @@
1
+ """Android group control MVP package."""
2
+
3
+ __all__ = ["__version__"]
4
+
5
+ __version__ = "0.1.0"
@@ -0,0 +1,31 @@
1
+ from .base import AdapterRegistry, PlatformAdapter
2
+ from .facebook import FacebookAdapter
3
+ from .instagram import InstagramAdapter
4
+ from .reddit import RedditAdapter
5
+ from .tiktok import TikTokAdapter
6
+ from .x import XAdapter
7
+ from .xiaohongshu import XiaohongshuAdapter
8
+
9
+
10
+ def default_registry() -> AdapterRegistry:
11
+ registry = AdapterRegistry()
12
+ registry.register(RedditAdapter())
13
+ registry.register(TikTokAdapter())
14
+ registry.register(InstagramAdapter())
15
+ registry.register(FacebookAdapter())
16
+ registry.register(XiaohongshuAdapter())
17
+ registry.register(XAdapter())
18
+ return registry
19
+
20
+
21
+ __all__ = [
22
+ "AdapterRegistry",
23
+ "PlatformAdapter",
24
+ "FacebookAdapter",
25
+ "RedditAdapter",
26
+ "TikTokAdapter",
27
+ "InstagramAdapter",
28
+ "XiaohongshuAdapter",
29
+ "XAdapter",
30
+ "default_registry",
31
+ ]
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+
5
+ from device_control.models import PublishTask
6
+
7
+
8
+ class PlatformAdapter(ABC):
9
+ platform: str
10
+ package_names: tuple[str, ...]
11
+ supported_post_types: tuple[str, ...]
12
+
13
+ def validate(self, task: PublishTask) -> list[str]:
14
+ errors: list[str] = []
15
+ if task.platform != self.platform:
16
+ errors.append(f"Task platform {task.platform!r} does not match adapter {self.platform!r}")
17
+ if task.post_type not in self.supported_post_types:
18
+ errors.append(
19
+ f"Post type {task.post_type!r} is not supported by {self.platform}; "
20
+ f"supported={self.supported_post_types}"
21
+ )
22
+ return errors
23
+
24
+ @abstractmethod
25
+ def plan(self, task: PublishTask) -> list[str]:
26
+ """Return human-readable execution steps for dry planning."""
27
+
28
+
29
+ class AdapterRegistry:
30
+ def __init__(self) -> None:
31
+ self._adapters: dict[str, PlatformAdapter] = {}
32
+
33
+ def register(self, adapter: PlatformAdapter) -> None:
34
+ self._adapters[adapter.platform] = adapter
35
+
36
+ def get(self, platform: str) -> PlatformAdapter:
37
+ key = platform.lower()
38
+ if key not in self._adapters:
39
+ raise KeyError(f"No adapter registered for platform {platform!r}")
40
+ return self._adapters[key]
41
+
42
+ def platforms(self) -> list[str]:
43
+ return sorted(self._adapters)
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from device_control.models import PublishTask
4
+
5
+ from .base import PlatformAdapter
6
+
7
+
8
+ class FacebookAdapter(PlatformAdapter):
9
+ platform = "facebook"
10
+ package_names = ("com.facebook.katana", "com.facebook.lite")
11
+ supported_post_types = ("text", "link", "image", "video")
12
+
13
+ def plan(self, task: PublishTask) -> list[str]:
14
+ steps = [
15
+ "wake device",
16
+ "open Facebook app",
17
+ f"choose content flow for post_type={task.post_type}",
18
+ ]
19
+ if task.media_path:
20
+ steps.append(f"push/select media: {task.media_path}")
21
+ if task.title:
22
+ steps.append(f"fill title: {task.title}")
23
+ if task.body:
24
+ steps.append("fill body")
25
+ if task.caption:
26
+ steps.append(f"fill caption: {task.caption}")
27
+ if task.link_url:
28
+ steps.append(f"fill link: {task.link_url}")
29
+ steps.extend(["capture pre-publish screenshot", "publish or stop at dry-run", "capture result screenshot"])
30
+ return steps
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from device_control.models import PublishTask
4
+
5
+ from .base import PlatformAdapter
6
+
7
+
8
+ class InstagramAdapter(PlatformAdapter):
9
+ platform = "instagram"
10
+ package_names = ("com.instagram.android",)
11
+ supported_post_types = ("image", "video")
12
+
13
+ def plan(self, task: PublishTask) -> list[str]:
14
+ return [
15
+ "wake device",
16
+ "push media to gallery directory",
17
+ "open Instagram app",
18
+ f"choose content flow for post_type={task.post_type}",
19
+ f"select media: {task.media_path}",
20
+ f"fill caption: {task.caption or ''}",
21
+ f"fill hashtags: {' '.join('#' + tag for tag in task.hashtags)}",
22
+ "capture pre-publish screenshot",
23
+ "publish or stop at dry-run",
24
+ "capture result screenshot",
25
+ ]
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ from device_control.models import PublishTask
4
+
5
+ from .base import PlatformAdapter
6
+
7
+
8
+ class RedditAdapter(PlatformAdapter):
9
+ platform = "reddit"
10
+ package_names = ("com.reddit.frontpage",)
11
+ supported_post_types = ("text", "link", "image", "video")
12
+
13
+ def plan(self, task: PublishTask) -> list[str]:
14
+ steps = [
15
+ "wake device",
16
+ "open Reddit app",
17
+ f"select target community: {task.target}",
18
+ f"choose post type: {task.post_type}",
19
+ ]
20
+ if task.media_path:
21
+ steps.append(f"push/select media: {task.media_path}")
22
+ if task.title:
23
+ steps.append(f"fill title: {task.title}")
24
+ if task.body:
25
+ steps.append("fill body")
26
+ if task.link_url:
27
+ steps.append(f"fill link: {task.link_url}")
28
+ steps.extend(["capture pre-publish screenshot", "publish or stop at dry-run", "capture result screenshot"])
29
+ return steps
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from device_control.models import PublishTask
4
+
5
+ from .base import PlatformAdapter
6
+
7
+
8
+ class TikTokAdapter(PlatformAdapter):
9
+ platform = "tiktok"
10
+ package_names = ("com.zhiliaoapp.musically", "com.ss.android.ugc.trill")
11
+ supported_post_types = ("video",)
12
+
13
+ def plan(self, task: PublishTask) -> list[str]:
14
+ return [
15
+ "wake device",
16
+ "push video to gallery directory",
17
+ "open TikTok app",
18
+ "enter create flow",
19
+ f"select video: {task.media_path}",
20
+ f"fill caption: {task.caption or ''}",
21
+ f"fill hashtags: {' '.join('#' + tag for tag in task.hashtags)}",
22
+ "capture pre-publish screenshot",
23
+ "publish or stop at dry-run",
24
+ "capture result screenshot",
25
+ ]