@11agents/cli 0.1.28 → 0.1.30
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/mobile-runtime/python/src/device_control/cli.py +10 -2
- package/mobile-runtime/python/src/device_control/publishers/xiaohongshu_adb.py +69 -6
- package/mobile-runtime/skills/android-group-control-cli/references/command-reference.md +1 -1
- package/mobile-runtime/skills/android-publish-xiaohongshu/SKILL.md +3 -3
- package/package.json +1 -1
- package/src/commands/runtime.js +6 -7
|
@@ -983,7 +983,7 @@ def main() -> None:
|
|
|
983
983
|
topics=args.topics,
|
|
984
984
|
dry_run=args.dry_run,
|
|
985
985
|
confirm_no_disclosure_needed=args.confirm_no_disclosure_needed,
|
|
986
|
-
copy_link_after_publish=args.copy_link_after_publish,
|
|
986
|
+
copy_link_after_publish=args.copy_link_after_publish and not args.dry_run,
|
|
987
987
|
resume_post_form=args.resume_post_form,
|
|
988
988
|
)
|
|
989
989
|
|
|
@@ -1174,8 +1174,16 @@ def _add_publish_xiaohongshu_args(parser: argparse.ArgumentParser) -> None:
|
|
|
1174
1174
|
)
|
|
1175
1175
|
parser.add_argument(
|
|
1176
1176
|
"--copy-link-after-publish",
|
|
1177
|
+
dest="copy_link_after_publish",
|
|
1177
1178
|
action="store_true",
|
|
1178
|
-
|
|
1179
|
+
default=True,
|
|
1180
|
+
help="After a successful publish, open the share sheet, tap copy link, and try to read the copied URL. Enabled by default for live publish.",
|
|
1181
|
+
)
|
|
1182
|
+
parser.add_argument(
|
|
1183
|
+
"--no-copy-link-after-publish",
|
|
1184
|
+
dest="copy_link_after_publish",
|
|
1185
|
+
action="store_false",
|
|
1186
|
+
help="Disable automatic Xiaohongshu link recovery after publish.",
|
|
1179
1187
|
)
|
|
1180
1188
|
parser.add_argument("--parallel", action="store_true", help="Run target devices concurrently. Hard-capped at 5.")
|
|
1181
1189
|
parser.add_argument("--max-concurrency", type=int, default=MAX_PARALLEL_DEVICES, help="Max concurrent devices, 1-5.")
|
|
@@ -319,7 +319,13 @@ class XiaohongshuAdbPublisher:
|
|
|
319
319
|
)
|
|
320
320
|
status = "published"
|
|
321
321
|
if copy_link_after_publish:
|
|
322
|
-
link_result = self.copy_current_note_link(
|
|
322
|
+
link_result = self.copy_current_note_link(
|
|
323
|
+
device,
|
|
324
|
+
prepare=False,
|
|
325
|
+
run_dir=run_dir / "link",
|
|
326
|
+
expected_title=title,
|
|
327
|
+
expected_caption=composed_caption,
|
|
328
|
+
)
|
|
323
329
|
|
|
324
330
|
ended_epoch = int(time.time())
|
|
325
331
|
if status == "published":
|
|
@@ -757,6 +763,7 @@ class XiaohongshuAdbPublisher:
|
|
|
757
763
|
expected_caption: str,
|
|
758
764
|
) -> Path:
|
|
759
765
|
self._wait_for_publish_exit_after_confirm(device, run_dir)
|
|
766
|
+
self._restart_xiaohongshu_for_link_recovery(device, run_dir)
|
|
760
767
|
return self._open_latest_own_note_for_link(
|
|
761
768
|
device,
|
|
762
769
|
run_dir,
|
|
@@ -797,7 +804,13 @@ class XiaohongshuAdbPublisher:
|
|
|
797
804
|
|
|
798
805
|
self._open_me_tab(device, run_dir)
|
|
799
806
|
self._dismiss_link_recovery_prompts(device, run_dir)
|
|
800
|
-
self._tap_latest_profile_note(
|
|
807
|
+
self._tap_latest_profile_note(
|
|
808
|
+
device,
|
|
809
|
+
run_dir,
|
|
810
|
+
expected_title=expected_title,
|
|
811
|
+
require_expected_match=bool(expected_title or expected_caption),
|
|
812
|
+
allow_first_card_fallback=bool(expected_title or expected_caption),
|
|
813
|
+
)
|
|
801
814
|
self._wait_for_detail_screen(device, run_dir)
|
|
802
815
|
return self._verify_current_note_detail(
|
|
803
816
|
device,
|
|
@@ -807,6 +820,13 @@ class XiaohongshuAdbPublisher:
|
|
|
807
820
|
prefix="published-note-detail",
|
|
808
821
|
)
|
|
809
822
|
|
|
823
|
+
def _restart_xiaohongshu_for_link_recovery(self, device: Device, run_dir: Path) -> None:
|
|
824
|
+
self._close_xiaohongshu(device)
|
|
825
|
+
time.sleep(self._wait("after_prompt_dismiss"))
|
|
826
|
+
self.adb.screenshot(device.adb_serial, run_dir / "after-publish-app-closed.png")
|
|
827
|
+
self._launch_xiaohongshu(device)
|
|
828
|
+
self.adb.screenshot(device.adb_serial, run_dir / "after-publish-relaunch.png")
|
|
829
|
+
|
|
810
830
|
def _dismiss_link_recovery_prompts(self, device: Device, run_dir: Path) -> None:
|
|
811
831
|
for idx in range(2):
|
|
812
832
|
try:
|
|
@@ -844,7 +864,22 @@ class XiaohongshuAdbPublisher:
|
|
|
844
864
|
self._tap_coord_or_default(device, "bottom_me", self._relative_coord(0.9, 0.93), self._wait("after_me_tab"))
|
|
845
865
|
self.adb.screenshot(device.adb_serial, run_dir / "profile.png")
|
|
846
866
|
|
|
847
|
-
def _tap_latest_profile_note(
|
|
867
|
+
def _tap_latest_profile_note(
|
|
868
|
+
self,
|
|
869
|
+
device: Device,
|
|
870
|
+
run_dir: Path,
|
|
871
|
+
*,
|
|
872
|
+
expected_title: str = "",
|
|
873
|
+
require_expected_match: bool = False,
|
|
874
|
+
allow_first_card_fallback: bool = False,
|
|
875
|
+
) -> None:
|
|
876
|
+
if require_expected_match and not expected_title:
|
|
877
|
+
not_identifiable_shot = run_dir / "expected-profile-note-not-identifiable.png"
|
|
878
|
+
self.adb.screenshot(device.adb_serial, not_identifiable_shot)
|
|
879
|
+
raise XiaohongshuAutomationError(
|
|
880
|
+
"expected Xiaohongshu profile note cannot be selected safely without a title",
|
|
881
|
+
not_identifiable_shot,
|
|
882
|
+
)
|
|
848
883
|
attempts = 8 if expected_title else 3
|
|
849
884
|
for attempt in range(attempts):
|
|
850
885
|
root = ui.dump_ui(self.adb, device, run_dir / f"profile-before-latest-note-{attempt + 1}.xml")
|
|
@@ -857,7 +892,14 @@ class XiaohongshuAdbPublisher:
|
|
|
857
892
|
self._scroll_profile_notes_down_half_screen(device)
|
|
858
893
|
self.adb.screenshot(device.adb_serial, run_dir / f"profile-after-scroll-{attempt + 1}.png")
|
|
859
894
|
time.sleep(2 if expected_title else 1)
|
|
860
|
-
if
|
|
895
|
+
if allow_first_card_fallback:
|
|
896
|
+
root = ui.dump_ui(self.adb, device, run_dir / "profile-before-first-card-fallback.xml")
|
|
897
|
+
node = self._find_latest_profile_note_node(root, expected_title="")
|
|
898
|
+
if node is not None:
|
|
899
|
+
ui.tap_node(self.adb, device, node, sleep_s=self._wait("after_latest_note_tap"))
|
|
900
|
+
self.adb.screenshot(device.adb_serial, run_dir / "first-card-fallback-opened.png")
|
|
901
|
+
return
|
|
902
|
+
if expected_title or require_expected_match:
|
|
861
903
|
not_found_shot = run_dir / "expected-profile-note-not-found.png"
|
|
862
904
|
self.adb.screenshot(device.adb_serial, not_found_shot)
|
|
863
905
|
raise XiaohongshuAutomationError(
|
|
@@ -966,7 +1008,8 @@ class XiaohongshuAdbPublisher:
|
|
|
966
1008
|
continue
|
|
967
1009
|
note_text = f"{text}\n{desc}"
|
|
968
1010
|
if expected:
|
|
969
|
-
|
|
1011
|
+
note_match = _normalize_match_text(note_text)
|
|
1012
|
+
if not _looks_like_profile_note_card(node) or not _profile_card_matches_expected_title(note_match, expected):
|
|
970
1013
|
continue
|
|
971
1014
|
return node
|
|
972
1015
|
item = (top, left, node)
|
|
@@ -1462,7 +1505,7 @@ def _note_text_matches_expected(visible_text: str, expected_title: str, expected
|
|
|
1462
1505
|
title = _normalize_match_text(expected_title)
|
|
1463
1506
|
caption_fragment = _verification_fragment(expected_caption)
|
|
1464
1507
|
|
|
1465
|
-
if title and title not in haystack:
|
|
1508
|
+
if title and title not in haystack and not _visible_text_matches_truncated_title(haystack, title):
|
|
1466
1509
|
return False, f"title not visible: {expected_title!r}"
|
|
1467
1510
|
if caption_fragment and caption_fragment not in haystack:
|
|
1468
1511
|
return False, f"caption fragment not visible: {caption_fragment!r}"
|
|
@@ -1471,6 +1514,13 @@ def _note_text_matches_expected(visible_text: str, expected_title: str, expected
|
|
|
1471
1514
|
return True, ""
|
|
1472
1515
|
|
|
1473
1516
|
|
|
1517
|
+
def _visible_text_matches_truncated_title(visible_text: str, expected_title: str) -> bool:
|
|
1518
|
+
if not visible_text or not expected_title:
|
|
1519
|
+
return False
|
|
1520
|
+
min_prefix = min(18, len(expected_title))
|
|
1521
|
+
return len(expected_title) >= min_prefix and expected_title[:min_prefix] in visible_text
|
|
1522
|
+
|
|
1523
|
+
|
|
1474
1524
|
def _looks_like_profile_note_card(node) -> bool:
|
|
1475
1525
|
text = node.attrib.get("text", "")
|
|
1476
1526
|
desc = node.attrib.get("content-desc", "")
|
|
@@ -1570,6 +1620,19 @@ def _verification_fragment(text: str) -> str:
|
|
|
1570
1620
|
return normalized[:18]
|
|
1571
1621
|
|
|
1572
1622
|
|
|
1623
|
+
def _profile_card_matches_expected_title(card_text: str, expected_title: str) -> bool:
|
|
1624
|
+
if not card_text or not expected_title:
|
|
1625
|
+
return False
|
|
1626
|
+
if expected_title in card_text:
|
|
1627
|
+
return True
|
|
1628
|
+
min_prefix = min(18, len(expected_title))
|
|
1629
|
+
if len(card_text) >= min_prefix and card_text in expected_title:
|
|
1630
|
+
return True
|
|
1631
|
+
if len(card_text) >= min_prefix and expected_title.startswith(card_text):
|
|
1632
|
+
return True
|
|
1633
|
+
return expected_title[:min_prefix] in card_text
|
|
1634
|
+
|
|
1635
|
+
|
|
1573
1636
|
def _strip_hashtags(text: str) -> str:
|
|
1574
1637
|
return re.sub(r"#[^\s#]+", "", text or "").strip()
|
|
1575
1638
|
|
|
@@ -84,7 +84,7 @@ X uses one `body` field, not a title/body split.
|
|
|
84
84
|
11agents mobile copy-xiaohongshu-link --device D03 --record-id pub_123 --json --task-id TASK_123
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
Use `--confirm-no-disclosure-needed` only when the approved package and policy explicitly allow it.
|
|
87
|
+
Live Xiaohongshu publish recovers the note link by default after publish. Use `--no-copy-link-after-publish` only when automatic link recovery is explicitly not wanted. Use `--confirm-no-disclosure-needed` only when the approved package and policy explicitly allow it.
|
|
88
88
|
|
|
89
89
|
## Records And Data Recovery
|
|
90
90
|
|
|
@@ -23,8 +23,8 @@ If approval, account lifecycle, media/package path, text fields, or device readi
|
|
|
23
23
|
Image/video note:
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
|
-
11agents mobile publish-xiaohongshu --device D03 --post-type image --image "/path/image.jpg" --title "title" --caption "body" --
|
|
27
|
-
11agents mobile publish-xiaohongshu --device D03 --post-type video --video "/path/video.mp4" --title "title" --caption "body" --
|
|
26
|
+
11agents mobile publish-xiaohongshu --device D03 --post-type image --image "/path/image.jpg" --title "title" --caption "body" --json --task-id TASK_123
|
|
27
|
+
11agents mobile publish-xiaohongshu --device D03 --post-type video --video "/path/video.mp4" --title "title" --caption "body" --json --task-id TASK_123
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
Structured package:
|
|
@@ -33,7 +33,7 @@ Structured package:
|
|
|
33
33
|
11agents mobile publish-xiaohongshu --device D03 --publish-package "/path/publish_package.json" --json --task-id TASK_123
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Successful live publish is only confirmed after the CLI taps the final publish button, waits without tapping while Xiaohongshu shows upload/progress state, then opens the "我" page, opens the note whose title matches the expected title, and verifies the detail page against the expected title/body. If that evidence does not match, treat the result as `failed`; do not claim publish success from the publish button tap alone.
|
|
36
|
+
Successful live publish is only confirmed after the CLI taps the final publish button, waits without tapping while Xiaohongshu shows upload/progress state, closes and relaunches Xiaohongshu, then opens the "我" page, opens the note whose title matches the expected title or the first visible profile note as a verified fallback, and verifies the detail page against the expected title/body. The CLI recovers the note link by default for live publish; use `--no-copy-link-after-publish` only when link recovery is explicitly not wanted. If that evidence does not match, treat the result as `failed`; do not claim publish success from the publish button tap alone.
|
|
37
37
|
|
|
38
38
|
Link copy after publish or during the recovery skill:
|
|
39
39
|
|
package/package.json
CHANGED
package/src/commands/runtime.js
CHANGED
|
@@ -1083,13 +1083,12 @@ function buildCodexPrompt(task) {
|
|
|
1083
1083
|
'- image_size alternatives: "1K", "2K". For a specific output size, pass aspect and image_size; pass size only when the MCP tool/provider explicitly supports the requested exact dimensions.',
|
|
1084
1084
|
'- Use model="gpt" with model_id only when the task or current project notes confirm that GPT image generation is available for the hosted MCP key.',
|
|
1085
1085
|
'- MCP authentication (agent_planning, database_sync, knowledge_sync, image_generate, etc.):',
|
|
1086
|
-
'
|
|
1087
|
-
'
|
|
1088
|
-
'
|
|
1089
|
-
'
|
|
1090
|
-
'
|
|
1091
|
-
'
|
|
1092
|
-
' Get the token from the 11agents platform → open your project → Tokens menu.',
|
|
1086
|
+
' - The GTM_SWARM_TOKEN env var is already set to the correct project token for this workspace.',
|
|
1087
|
+
' - Configure the 11agents hosted MCP server using that env var:',
|
|
1088
|
+
' {"mcpServers":{"11agents-project-sync":{"url":"https://app.11agents.ai/mcp","transport":"streamable-http","headers":{"Authorization":"Bearer $GTM_SWARM_TOKEN"}}}}',
|
|
1089
|
+
' - NEVER hardcode the token value — always reference $GTM_SWARM_TOKEN so the config stays correct across workspaces.',
|
|
1090
|
+
' - If GTM_SWARM_TOKEN is missing, get the token from ~/.11agents/credentials.yaml under tokens.<project_slug>.',
|
|
1091
|
+
' If that file is empty, go to the 11agents platform → your project → Tokens menu.',
|
|
1093
1092
|
'',
|
|
1094
1093
|
'Final task comment requirements:',
|
|
1095
1094
|
'> [!IMPORTANT]',
|