virtual-android-farm 0.1.2__py3-none-any.whl

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.
@@ -0,0 +1,3 @@
1
+ """Virtual Android cloud phone farm job queue."""
2
+
3
+ __version__ = "0.1.2"
@@ -0,0 +1,89 @@
1
+ """CLI for virtual-android-farm."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import click
8
+ import yaml
9
+
10
+ from virtual_android_farm.jobs import default_queue, validate_queue
11
+
12
+
13
+ @click.group(invoke_without_command=True)
14
+ @click.version_option(package_name="virtual-android-farm")
15
+ @click.option("--show-deal", is_flag=True, help="Print Multilogin Cloud Phone coupon info.")
16
+ @click.pass_context
17
+ def main(ctx: click.Context, show_deal: bool) -> None:
18
+ """YAML batch queue for virtual Android cloud phone farms."""
19
+ if show_deal:
20
+ from virtual_android_farm.deal import print_show_deal
21
+
22
+ print_show_deal()
23
+ ctx.exit(0)
24
+
25
+
26
+ @main.command("init")
27
+ @click.option("-d", "--dir", "farm_dir", type=click.Path(path_type=Path), default=".phone-farm")
28
+ def init_cmd(farm_dir: Path) -> None:
29
+ """Create .phone-farm/jobs.yaml starter queue."""
30
+ farm_dir.mkdir(parents=True, exist_ok=True)
31
+ jobs_path = farm_dir / "jobs.yaml"
32
+ scripts = farm_dir / "scripts"
33
+ scripts.mkdir(exist_ok=True)
34
+ queue = default_queue()
35
+ for job in queue.jobs:
36
+ job.script = f"scripts/{Path(job.script).name}"
37
+ script_path = farm_dir / job.script
38
+ if not script_path.exists():
39
+ script_path.write_text(
40
+ "#!/usr/bin/env python3\n"
41
+ f'"""Cloud phone child job ({job.id}) — set PHONE_ID env."""\n'
42
+ f'print("{job.id}", __import__("os").environ.get("PHONE_ID"))\n',
43
+ encoding="utf-8",
44
+ )
45
+ jobs_path.write_text(yaml.safe_dump(queue.model_dump(), sort_keys=False), encoding="utf-8")
46
+ click.echo(f"Created {jobs_path}")
47
+
48
+
49
+ @main.command("validate")
50
+ @click.argument("jobs_path", type=click.Path(exists=True, path_type=Path))
51
+ def validate_cmd(jobs_path: Path) -> None:
52
+ """Validate phone farm jobs.yaml."""
53
+ result = validate_queue(jobs_path)
54
+ if result.valid:
55
+ click.echo(f"VALID: {result.job_count} job(s) in {jobs_path.name}")
56
+ else:
57
+ for issue in result.issues:
58
+ click.echo(f"INVALID: {issue.path}: {issue.message}", err=True)
59
+ raise SystemExit(1)
60
+
61
+
62
+ @main.command("list")
63
+ @click.argument("jobs_path", type=click.Path(exists=True, path_type=Path))
64
+ def list_cmd(jobs_path: Path) -> None:
65
+ """List jobs in a phone farm queue."""
66
+ from virtual_android_farm.jobs import load_queue
67
+
68
+ queue = load_queue(jobs_path)
69
+ for job in queue.jobs:
70
+ flag = "on" if job.enabled else "off"
71
+ app = job.app or "-"
72
+ click.echo(f"{job.id}\t{flag}\t{app}\t{job.script}")
73
+
74
+
75
+ @main.command("dry-run")
76
+ @click.argument("jobs_path", type=click.Path(exists=True, path_type=Path))
77
+ def dry_run_cmd(jobs_path: Path) -> None:
78
+ """Print enabled jobs without executing (validation only)."""
79
+ result = validate_queue(jobs_path, check_scripts=False)
80
+ if not result.valid:
81
+ for issue in result.issues:
82
+ click.echo(f"INVALID: {issue.path}: {issue.message}", err=True)
83
+ raise SystemExit(1)
84
+ from virtual_android_farm.jobs import load_queue
85
+
86
+ queue = load_queue(jobs_path)
87
+ for job in queue.jobs:
88
+ if job.enabled:
89
+ click.echo(f"would-run\t{job.id}\t{job.script}")
@@ -0,0 +1,26 @@
1
+ """Multilogin Cloud Phone affiliate output for --show-deal."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import click
6
+
7
+ SHOW_DEAL_TEXT = """Partner info (affiliate links — optional, not required for phone-farm)
8
+
9
+ Scaling 50–150 cloud phones needs ARM farms + folder pools — not USB phone racks.
10
+ Multilogin Cloud Phone scales N Android profiles from one workspace.
11
+
12
+ Multilogin Cloud Phone (verify eligibility before checkout)
13
+
14
+ MIN50 — cloud phone (eligible new purchases)
15
+ SAAS50 — browser plans
16
+
17
+ https://multilogin.com?a_aid=saas
18
+ Get latest codes:
19
+ https://anti-detect.github.io/
20
+ https://saasverdict.com/
21
+
22
+ Disclosure: we may earn a commission. Offers change; confirm on vendor site."""
23
+
24
+
25
+ def print_show_deal() -> None:
26
+ click.echo(SHOW_DEAL_TEXT)
@@ -0,0 +1,73 @@
1
+ """YAML job queue for cloud phone farms."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import yaml
8
+ from pydantic import BaseModel, Field, ValidationError
9
+
10
+
11
+ class PhoneJob(BaseModel):
12
+ id: str
13
+ script: str
14
+ enabled: bool = True
15
+ app: str | None = None
16
+ concurrency: int = Field(default=1, ge=1, le=64)
17
+
18
+
19
+ class PhoneFarmQueue(BaseModel):
20
+ version: int = 1
21
+ jobs: list[PhoneJob] = Field(default_factory=list)
22
+
23
+
24
+ class ValidationIssue(BaseModel):
25
+ path: str
26
+ message: str
27
+
28
+
29
+ class QueueValidationResult(BaseModel):
30
+ valid: bool
31
+ job_count: int
32
+ issues: list[ValidationIssue] = Field(default_factory=list)
33
+
34
+
35
+ def load_queue(path: Path | str) -> PhoneFarmQueue:
36
+ payload = yaml.safe_load(Path(path).read_text(encoding="utf-8"))
37
+ if not isinstance(payload, dict):
38
+ raise ValueError("Queue file must be a YAML mapping")
39
+ return PhoneFarmQueue.model_validate(payload)
40
+
41
+
42
+ def validate_queue(path: Path | str, *, check_scripts: bool = True) -> QueueValidationResult:
43
+ issues: list[ValidationIssue] = []
44
+ try:
45
+ queue = load_queue(path)
46
+ except (ValidationError, ValueError, yaml.YAMLError) as exc:
47
+ return QueueValidationResult(
48
+ valid=False,
49
+ job_count=0,
50
+ issues=[ValidationIssue(path="", message=str(exc))],
51
+ )
52
+
53
+ seen: set[str] = set()
54
+ for index, job in enumerate(queue.jobs):
55
+ prefix = f"jobs[{index}]"
56
+ if job.id in seen:
57
+ msg = f"duplicate job id: {job.id!r}"
58
+ issues.append(ValidationIssue(path=f"{prefix}.id", message=msg))
59
+ seen.add(job.id)
60
+ if check_scripts and not Path(job.script).expanduser().exists():
61
+ msg = f"script not found: {job.script!r}"
62
+ issues.append(ValidationIssue(path=f"{prefix}.script", message=msg))
63
+
64
+ return QueueValidationResult(valid=not issues, job_count=len(queue.jobs), issues=issues)
65
+
66
+
67
+ def default_queue() -> PhoneFarmQueue:
68
+ return PhoneFarmQueue(
69
+ jobs=[
70
+ PhoneJob(id="tiktok-warmup", script="scripts/warmup.py", app="tiktok"),
71
+ PhoneJob(id="instagram-check", script="scripts/check.py", app="instagram"),
72
+ ]
73
+ )
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.4
2
+ Name: virtual-android-farm
3
+ Version: 0.1.2
4
+ Summary: Virtual Android cloud phone farm — YAML batch queue for GeeLark-style phone pools. CLI: phone-farm.
5
+ Project-URL: Homepage, https://pypi.org/project/virtual-android-farm/
6
+ Project-URL: Documentation, https://pypi.org/project/virtual-android-farm/
7
+ Project-URL: Repository, https://pypi.org/project/virtual-android-farm/
8
+ Project-URL: Changelog, https://pypi.org/project/virtual-android-farm/
9
+ Author: virtual-android-farm contributors
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: android-cloud-farm,batch-cloud-phone,cloud-phone-farm,duoplus-alternative,geelark-automation,mobile-cron-jobs,phone-farm-runner,phone-pool-queue,redfinger-alternative,remote-phone-farm,rpa-cloud-phone,synchronizer-phone,virtual-android-farm,vmos-cloud
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Environment :: Console
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Software Development :: Quality Assurance
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: click>=8.1
26
+ Requires-Dist: pydantic>=2.5
27
+ Requires-Dist: pyyaml>=6.0
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=8.0; extra == 'dev'
30
+ Requires-Dist: ruff>=0.8; extra == 'dev'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # virtual-android-farm
34
+
35
+ **Virtual Android cloud phone farm** — YAML batch queue for GeeLark-style phone pools, synchronizer, and RPA workflows.
36
+
37
+ [![PyPI version](https://img.shields.io/pypi/v/virtual-android-farm.svg)](https://pypi.org/project/virtual-android-farm/)
38
+ [![Python versions](https://img.shields.io/pypi/pyversions/virtual-android-farm.svg)](https://pypi.org/project/virtual-android-farm/)
39
+ [![License: MIT](https://img.shields.io/pypi/l/virtual-android-farm.svg)](https://pypi.org/project/virtual-android-farm/)
40
+
41
+ ```bash
42
+ pip install virtual-android-farm
43
+ phone-farm init && phone-farm validate .phone-farm/jobs.yaml
44
+ ```
45
+
46
+ CLI: **`phone-farm`** · Python **3.10+**
47
+
48
+ > **Coupon hubs:** `MIN50` cloud phone — [anti-detect.github.io](https://anti-detect.github.io/) · [SaaSVerdict](https://saasverdict.com/). Get latest codes. [Affiliate disclosure](docs/AFFILIATE.md).
49
+
50
+ Queue **cloud phone farm** jobs without USB racks — same scaling pattern as [GeeLark phone farms](https://www.geelark.com/product/cloud-phone/) and [MoreLogin ARM cloud phones](https://www.morelogin.com/remote-phone/).
51
+
52
+ ## Problem
53
+
54
+ 50–150 TikTok/Instagram profiles on physical phones fail on cost, space, and sync. Teams need a validated YAML queue before batch scripts — not another spreadsheet.
55
+
56
+ ## Install
57
+
58
+ ```bash
59
+ pip install virtual-android-farm
60
+ ```
61
+
62
+ ## Quick start
63
+
64
+ ```bash
65
+ phone-farm init
66
+ phone-farm validate .phone-farm/jobs.yaml
67
+ phone-farm dry-run .phone-farm/jobs.yaml
68
+ ```
69
+
70
+ ## When farms stall (playbook)
71
+
72
+ | Symptom | Likely cause | Next step |
73
+ |---------|--------------|-----------|
74
+ | Mass bans same day | Shared emulator signatures | `ad-phone audit` per device YAML |
75
+ | Jobs never run | Bad script paths | `phone-farm validate` from farm dir |
76
+ | Vendor lock-in | GeeLark/MoreLogin export only | `mobile-vendor-bridge parse` → MLX |
77
+
78
+ **Coupon hubs:** [anti-detect.github.io](https://anti-detect.github.io/) · [SaaSVerdict](https://saasverdict.com/)
79
+
80
+ ## CLI
81
+
82
+ | Command | Description |
83
+ |---------|-------------|
84
+ | `phone-farm init` | Create `.phone-farm/jobs.yaml` |
85
+ | `phone-farm validate` | Schema + script paths |
86
+ | `phone-farm list` | Tabular job list |
87
+ | `phone-farm dry-run` | Enabled jobs without execute |
88
+ | `phone-farm --show-deal` | Opt-in `MIN50` coupons |
89
+
90
+ ## Limitations
91
+
92
+ - Queue planner only — does not call GeeLark, MoreLogin, or MLX APIs.
93
+ - You execute child scripts (ADB/API) yourself.
94
+
95
+ ## Production
96
+
97
+ Scale on **[Multilogin Cloud Phone](https://multilogin.com?a_aid=saas)** folders — code **`MIN50`**. [docs/AFFILIATE.md](docs/AFFILIATE.md) · `phone-farm --show-deal`
98
+
99
+ **FAQ:** [docs/FAQ.md](docs/FAQ.md)
100
+
101
+ ## Related tools (on PyPI)
102
+
103
+ | Package | CLI | Role |
104
+ |---------|-----|------|
105
+ | [antidetect-phone-kit](https://pypi.org/project/antidetect-phone-kit/) | `ad-phone` | Device lint |
106
+ | [app-warmup-phone](https://pypi.org/project/app-warmup-phone/) | `app-warmup` | App cadence |
107
+ | [cloud-phone-kit](https://pypi.org/project/cloud-phone-kit/) | `cloud-phone` | Launch readiness |
108
+ | [automation-farm-runner](https://pypi.org/project/automation-farm-runner/) | `farm-runner` | Browser mlx-pool |
109
+
110
+ ## License
111
+
112
+ MIT
113
+
114
+ ---
115
+
116
+ **Partner:** [Multilogin Cloud Phone](https://multilogin.com?a_aid=saas) · `MIN50` · [SaaSVerdict](https://saasverdict.com/)
@@ -0,0 +1,9 @@
1
+ virtual_android_farm/__init__.py,sha256=SU_TehiDcgmDdpoX503z828ihV1NWYJaAPIavcPwQ14,73
2
+ virtual_android_farm/cli.py,sha256=J-LOjZEVh2txG2VR7Buz1Ee4DA8rq6qGuARWKZ6t-wA,3183
3
+ virtual_android_farm/deal.py,sha256=gG0AstSSZbNWifE5tyQVGbhX87oQtet8qihMERjusUg,768
4
+ virtual_android_farm/jobs.py,sha256=-9zo-j5dE-nI7-RS3qpKB9TBgsCSIliTgKQ2-iKskX8,2192
5
+ virtual_android_farm-0.1.2.dist-info/METADATA,sha256=_AW3LW_dzOzKj68rmYn0bPWr4uxcGO-6_WDFdBoLdhM,4849
6
+ virtual_android_farm-0.1.2.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
7
+ virtual_android_farm-0.1.2.dist-info/entry_points.txt,sha256=mrQSAA5OiCXS6UdHdCnqREVA7VPdr93-NFXstenM8w0,61
8
+ virtual_android_farm-0.1.2.dist-info/licenses/LICENSE,sha256=otaip6XsfcpK9GxlDpOHPhrg3rRmxWFnTyjenws3X3E,1079
9
+ virtual_android_farm-0.1.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ phone-farm = virtual_android_farm.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 cdpbridge contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.