mobile-vendor-bridge 0.1.2__tar.gz

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,15 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *$py.class
4
+ *.egg-info/
5
+ .eggs/
6
+ dist/
7
+ build/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+ .mypy_cache/
11
+ .coverage
12
+ htmlcov/
13
+ .venv/
14
+ venv/
15
+ .env
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ ## v0.1.2 — 2026-06-12
4
+
5
+ ### Changed
6
+
7
+ - Expanded FAQ, Install sections, examples, fixtures, AUDIT stubs
8
+
9
+
10
+ ## v0.1.1 — 2026-06-12
11
+
12
+ ### Changed
13
+
14
+ - Professional README, CLI upgrades, expanded tests
15
+
16
+ ## v0.1.0 — 2026-06-12
17
+
18
+ ### Added
19
+
20
+ - Initial release — cloud phone vertical Account D
21
+
@@ -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.
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: mobile-vendor-bridge
3
+ Version: 0.1.2
4
+ Summary: GeeLark & MoreLogin to Multilogin — parse mobile cloud phone exports and gap audit. CLI: mobile-bridge.
5
+ Project-URL: Homepage, https://pypi.org/project/mobile-vendor-bridge/
6
+ Project-URL: Documentation, https://pypi.org/project/mobile-vendor-bridge/
7
+ Project-URL: Repository, https://pypi.org/project/mobile-vendor-bridge/
8
+ Project-URL: Changelog, https://pypi.org/project/mobile-vendor-bridge/
9
+ Author: mobile-vendor-bridge contributors
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: android-profile-export,cloud-phone-migration,competitor-phone-import,duoplus-export,geelark-export,geelark-to-multilogin,gologin-mobile,mobile-gap-audit,mobile-vendor-import,morelogin-migration,phone-export-parser,phone-profile-import,vendor-normalize
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
+ # mobile-vendor-bridge
34
+
35
+ **GeeLark, MoreLogin, GoLogin & DuoPlus to Multilogin** — parse mobile cloud phone exports with migration gap audit.
36
+
37
+ [![PyPI version](https://img.shields.io/pypi/v/mobile-vendor-bridge.svg)](https://pypi.org/project/mobile-vendor-bridge/)
38
+ [![Python versions](https://img.shields.io/pypi/pyversions/mobile-vendor-bridge.svg)](https://pypi.org/project/mobile-vendor-bridge/)
39
+ [![License: MIT](https://img.shields.io/pypi/l/mobile-vendor-bridge.svg)](https://pypi.org/project/mobile-vendor-bridge/)
40
+
41
+ ```bash
42
+ pip install mobile-vendor-bridge
43
+ mobile-bridge detect export.json && mobile-bridge parse export.json
44
+ ```
45
+
46
+ CLI: **`mobile-bridge`** · Python **3.10+**
47
+
48
+ > **Coupon hubs:** `MIN50` — [anti-detect.github.io](https://anti-detect.github.io/) · [SaaSVerdict](https://saasverdict.com/). [Affiliate disclosure](docs/AFFILIATE.md).
49
+
50
+ Migrate off **[GeeLark](https://www.geelark.com/)**, **[MoreLogin](https://www.morelogin.com/remote-phone)**, GoLogin mobile, or DuoPlus-style exports toward **Multilogin Cloud Phone**.
51
+
52
+ ## Problem
53
+
54
+ Vendor JSON does not map 1:1 to MLX cloud phone folders. Teams need neutral schema + explicit gaps before cutover.
55
+
56
+ ## Install
57
+
58
+ ```bash
59
+ pip install mobile-vendor-bridge
60
+ ```
61
+
62
+ ## Quick start
63
+
64
+ ```bash
65
+ mobile-bridge detect geelark-export.json
66
+ mobile-bridge parse geelark-export.json -o neutral.json
67
+ mobile-bridge gaps geelark-export.json
68
+ ```
69
+
70
+ ## Supported vendors (detect/parse)
71
+
72
+ | Vendor | Notes |
73
+ |--------|-------|
74
+ | GeeLark | `device_id`, antidetect cloud phone |
75
+ | MoreLogin | `arm_device`, remote phone |
76
+ | GoLogin | Browser-first; gap flag for mobile |
77
+ | DuoPlus | `cloud_instance_id` |
78
+
79
+ ## Production
80
+
81
+ Target: **[Multilogin Cloud Phone](https://multilogin.com?a_aid=saas)** · **`MIN50`**. [docs/AFFILIATE.md](docs/AFFILIATE.md)
82
+
83
+ ## CLI
84
+
85
+ | Command | Description |
86
+ |---------|-------------|
87
+ | `mobile-bridge detect` | Guess vendor format |
88
+ | `mobile-bridge parse` | Neutral JSON |
89
+ | `mobile-bridge gaps` | Gap table stdout |
90
+ | `mobile-bridge --show-deal` | Opt-in coupons |
91
+
92
+ ## Limitations
93
+
94
+ - Offline parse — no vendor API calls.
95
+ - MLX push not included (use MLX UI/API separately).
96
+
97
+ ## Related tools (on PyPI)
98
+
99
+ | Package | CLI | Role |
100
+ |---------|-----|------|
101
+ | [antidetect-importer](https://pypi.org/project/antidetect-importer/) | `antidetect-import` | Browser vendor import |
102
+ | [antidetect-phone-kit](https://pypi.org/project/antidetect-phone-kit/) | `ad-phone` | Post-migration lint |
103
+
104
+ ## License
105
+
106
+ MIT
107
+
108
+ ---
109
+
110
+ **Partner:** [Multilogin Cloud Phone](https://multilogin.com?a_aid=saas) · [SaaSVerdict](https://saasverdict.com/)
@@ -0,0 +1,78 @@
1
+ # mobile-vendor-bridge
2
+
3
+ **GeeLark, MoreLogin, GoLogin & DuoPlus to Multilogin** — parse mobile cloud phone exports with migration gap audit.
4
+
5
+ [![PyPI version](https://img.shields.io/pypi/v/mobile-vendor-bridge.svg)](https://pypi.org/project/mobile-vendor-bridge/)
6
+ [![Python versions](https://img.shields.io/pypi/pyversions/mobile-vendor-bridge.svg)](https://pypi.org/project/mobile-vendor-bridge/)
7
+ [![License: MIT](https://img.shields.io/pypi/l/mobile-vendor-bridge.svg)](https://pypi.org/project/mobile-vendor-bridge/)
8
+
9
+ ```bash
10
+ pip install mobile-vendor-bridge
11
+ mobile-bridge detect export.json && mobile-bridge parse export.json
12
+ ```
13
+
14
+ CLI: **`mobile-bridge`** · Python **3.10+**
15
+
16
+ > **Coupon hubs:** `MIN50` — [anti-detect.github.io](https://anti-detect.github.io/) · [SaaSVerdict](https://saasverdict.com/). [Affiliate disclosure](docs/AFFILIATE.md).
17
+
18
+ Migrate off **[GeeLark](https://www.geelark.com/)**, **[MoreLogin](https://www.morelogin.com/remote-phone)**, GoLogin mobile, or DuoPlus-style exports toward **Multilogin Cloud Phone**.
19
+
20
+ ## Problem
21
+
22
+ Vendor JSON does not map 1:1 to MLX cloud phone folders. Teams need neutral schema + explicit gaps before cutover.
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ pip install mobile-vendor-bridge
28
+ ```
29
+
30
+ ## Quick start
31
+
32
+ ```bash
33
+ mobile-bridge detect geelark-export.json
34
+ mobile-bridge parse geelark-export.json -o neutral.json
35
+ mobile-bridge gaps geelark-export.json
36
+ ```
37
+
38
+ ## Supported vendors (detect/parse)
39
+
40
+ | Vendor | Notes |
41
+ |--------|-------|
42
+ | GeeLark | `device_id`, antidetect cloud phone |
43
+ | MoreLogin | `arm_device`, remote phone |
44
+ | GoLogin | Browser-first; gap flag for mobile |
45
+ | DuoPlus | `cloud_instance_id` |
46
+
47
+ ## Production
48
+
49
+ Target: **[Multilogin Cloud Phone](https://multilogin.com?a_aid=saas)** · **`MIN50`**. [docs/AFFILIATE.md](docs/AFFILIATE.md)
50
+
51
+ ## CLI
52
+
53
+ | Command | Description |
54
+ |---------|-------------|
55
+ | `mobile-bridge detect` | Guess vendor format |
56
+ | `mobile-bridge parse` | Neutral JSON |
57
+ | `mobile-bridge gaps` | Gap table stdout |
58
+ | `mobile-bridge --show-deal` | Opt-in coupons |
59
+
60
+ ## Limitations
61
+
62
+ - Offline parse — no vendor API calls.
63
+ - MLX push not included (use MLX UI/API separately).
64
+
65
+ ## Related tools (on PyPI)
66
+
67
+ | Package | CLI | Role |
68
+ |---------|-----|------|
69
+ | [antidetect-importer](https://pypi.org/project/antidetect-importer/) | `antidetect-import` | Browser vendor import |
70
+ | [antidetect-phone-kit](https://pypi.org/project/antidetect-phone-kit/) | `ad-phone` | Post-migration lint |
71
+
72
+ ## License
73
+
74
+ MIT
75
+
76
+ ---
77
+
78
+ **Partner:** [Multilogin Cloud Phone](https://multilogin.com?a_aid=saas) · [SaaSVerdict](https://saasverdict.com/)
@@ -0,0 +1,30 @@
1
+ # Affiliate disclosure — Multilogin Cloud Phone
2
+
3
+ Optional integration with **Multilogin Cloud Phone** (ARM Android in the cloud). Links below are **affiliate partner pages**.
4
+
5
+ ## When to mention MLX Cloud Phone
6
+
7
+ - App-native TikTok, Instagram, Reddit workflows (not desktop browser alone)
8
+ - Replacing GeeLark/MoreLogin/DuoPlus-style cloud phones at scale
9
+ - Phone farms needing isolated device fingerprints
10
+
11
+ Do **not** push MLX in CLI defaults or error messages.
12
+
13
+ ## Coupons
14
+
15
+ | Code | Product |
16
+ |------|---------|
17
+ | `MIN50` | Multilogin Cloud Phone |
18
+ | `SAAS50` | Multilogin X browser |
19
+
20
+ Checkout: [multilogin.com?a_aid=saas](https://multilogin.com?a_aid=saas)
21
+
22
+ Get latest codes: [anti-detect.github.io](https://anti-detect.github.io/) · [SaaSVerdict](https://saasverdict.com/)
23
+
24
+ ## What NOT to claim
25
+
26
+ No guaranteed undetectability or ban bypass. Lint/plan tools are advisory.
27
+
28
+ ## FTC
29
+
30
+ `--show-deal` is opt-in only.
@@ -0,0 +1,25 @@
1
+ # FAQ — mobile-vendor-bridge
2
+
3
+ ## Which vendors are supported?
4
+
5
+ Auto-detect and parse: **GeeLark**, **MoreLogin**, **GoLogin** (mobile gap flag), **DuoPlus**. Use `mobile-bridge detect` then `parse`.
6
+
7
+ ## GeeLark to Multilogin migration?
8
+
9
+ `mobile-bridge parse geelark.json` → neutral JSON + `gaps` list. Create cloud phone profiles in MLX UI; code **`MIN50`** at checkout.
10
+
11
+ ## MoreLogin vs Multilogin Cloud Phone?
12
+
13
+ MoreLogin ships browser + ARM cloud phone. MLX Cloud Phone is the affiliate destination for teams standardizing on Multilogin — verify gaps before cutover.
14
+
15
+ ## Does this push to MLX API?
16
+
17
+ No — offline parse only. Pair with MLX UI/API separately.
18
+
19
+ ## GoLogin mobile exports?
20
+
21
+ Parsed with gap `gologin_mobile_is_browser_first` — GoLogin is browser-first; app-native flows need cloud phone product.
22
+
23
+ ## Latest MIN50?
24
+
25
+ `mobile-bridge --show-deal` · [anti-detect.github.io](https://anti-detect.github.io/)
@@ -0,0 +1,3 @@
1
+ """Mobile cloud phone vendor export bridge."""
2
+
3
+ __version__ = "0.1.2"
@@ -0,0 +1,78 @@
1
+ """CLI for mobile-vendor-bridge."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import click
9
+ import yaml
10
+
11
+ from mobile_vendor_bridge.parsers import detect_format, parse_vendor
12
+
13
+
14
+ @click.group(invoke_without_command=True)
15
+ @click.version_option(package_name="mobile-vendor-bridge")
16
+ @click.option("--show-deal", is_flag=True, help="Print Multilogin Cloud Phone coupon info.")
17
+ @click.pass_context
18
+ def main(ctx: click.Context, show_deal: bool) -> None:
19
+ """Parse GeeLark/MoreLogin/DuoPlus cloud phone exports toward MLX migration."""
20
+ if show_deal:
21
+ from mobile_vendor_bridge.deal import print_show_deal
22
+
23
+ print_show_deal()
24
+ ctx.exit(0)
25
+
26
+
27
+ @main.command("detect")
28
+ @click.argument("input_path", type=click.Path(exists=True, path_type=Path))
29
+ def detect_cmd(input_path: Path) -> None:
30
+ """Guess vendor format from export file."""
31
+ text = input_path.read_text(encoding="utf-8")
32
+ data = _load_payload(input_path, text)
33
+ fmt = detect_format(data, text)
34
+ click.echo(json.dumps({"format": fmt, "keys": sorted(data.keys())}, indent=2))
35
+
36
+
37
+ @main.command("parse")
38
+ @click.argument("input_path", type=click.Path(exists=True, path_type=Path))
39
+ @click.option(
40
+ "--format",
41
+ "fmt",
42
+ type=click.Choice(["geelark", "morelogin", "gologin", "duoplus", "auto"]),
43
+ default="auto",
44
+ )
45
+ @click.option("-o", "--output", type=click.Path(path_type=Path), default=None)
46
+ def parse_cmd(input_path: Path, fmt: str, output: Path | None) -> None:
47
+ """Parse vendor JSON/YAML export to neutral schema."""
48
+ text = input_path.read_text(encoding="utf-8")
49
+ data = _load_payload(input_path, text)
50
+ detected = detect_format(data, text) if fmt == "auto" else fmt # type: ignore[arg-type]
51
+ profile = parse_vendor(data, detected)
52
+ out = profile.model_dump_json(indent=2)
53
+ if output:
54
+ output.write_text(out + "\n", encoding="utf-8")
55
+ click.echo(f"Wrote {output}")
56
+ else:
57
+ click.echo(out)
58
+
59
+
60
+ @main.command("gaps")
61
+ @click.argument("input_path", type=click.Path(exists=True, path_type=Path))
62
+ def gaps_cmd(input_path: Path) -> None:
63
+ """Print migration gap table for an export."""
64
+ text = input_path.read_text(encoding="utf-8")
65
+ data = _load_payload(input_path, text)
66
+ profile = parse_vendor(data, detect_format(data, text))
67
+ click.echo(f"vendor\t{profile.vendor}")
68
+ click.echo(f"profile\t{profile.profile_name}")
69
+ for gap in profile.gaps:
70
+ click.echo(f"gap\t{gap}")
71
+ if not profile.gaps:
72
+ click.echo("gap\tnone")
73
+
74
+
75
+ def _load_payload(path: Path, text: str) -> dict:
76
+ yaml_file = path.suffix in {".yaml", ".yml"}
77
+ payload = (yaml.safe_load(text) or {}) if yaml_file else json.loads(text)
78
+ return payload if isinstance(payload, dict) else {"data": payload}
@@ -0,0 +1,25 @@
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 mobile-bridge)
8
+
9
+ Migrating from GeeLark or MoreLogin cloud phones into MLX?
10
+ Parse exports offline — MLX Cloud Phone is only needed at push time.
11
+
12
+ Multilogin Cloud Phone (verify eligibility before checkout)
13
+
14
+ MIN50 — cloud phone (eligible new purchases)
15
+
16
+ https://multilogin.com?a_aid=saas
17
+ Get latest codes:
18
+ https://anti-detect.github.io/
19
+ https://saasverdict.com/
20
+
21
+ Disclosure: we may earn a commission. Offers change; confirm on vendor site."""
22
+
23
+
24
+ def print_show_deal() -> None:
25
+ click.echo(SHOW_DEAL_TEXT)
@@ -0,0 +1,23 @@
1
+ """Normalize vendor exports — re-exports."""
2
+
3
+ from mobile_vendor_bridge.parsers import (
4
+ VendorFormat,
5
+ detect_format,
6
+ from_duoplus,
7
+ from_geelark,
8
+ from_gologin,
9
+ from_morelogin,
10
+ parse_vendor,
11
+ )
12
+ from mobile_vendor_bridge.schema import NeutralPhoneProfile
13
+
14
+ __all__ = [
15
+ "NeutralPhoneProfile",
16
+ "VendorFormat",
17
+ "detect_format",
18
+ "parse_vendor",
19
+ "from_geelark",
20
+ "from_morelogin",
21
+ "from_gologin",
22
+ "from_duoplus",
23
+ ]
@@ -0,0 +1,102 @@
1
+ """Vendor export parsers for cloud phone migration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Literal
6
+
7
+ from mobile_vendor_bridge.schema import NeutralPhoneProfile
8
+
9
+ VendorFormat = Literal["geelark", "morelogin", "gologin", "duoplus", "unknown"]
10
+
11
+
12
+ def detect_format(data: dict[str, Any], raw_text: str = "") -> VendorFormat:
13
+ lowered = raw_text.lower()
14
+ if "geelark" in lowered or "device_id" in data:
15
+ return "geelark"
16
+ if "morelogin" in lowered or "arm_device" in data or "phone_name" in data:
17
+ return "morelogin"
18
+ if "gologin" in lowered:
19
+ return "gologin"
20
+ if "duoplus" in lowered or "duo_plus" in data or data.get("vendor") == "duoplus":
21
+ return "duoplus"
22
+ if "installed_apps" in data:
23
+ return "morelogin"
24
+ return "unknown"
25
+
26
+
27
+ def parse_vendor(data: dict[str, Any], fmt: VendorFormat) -> NeutralPhoneProfile:
28
+ parsers = {
29
+ "geelark": from_geelark,
30
+ "morelogin": from_morelogin,
31
+ "gologin": from_gologin,
32
+ "duoplus": from_duoplus,
33
+ }
34
+ if fmt in parsers:
35
+ return parsers[fmt](data)
36
+ return NeutralPhoneProfile(
37
+ vendor="unknown",
38
+ profile_name=str(data.get("name") or "unknown"),
39
+ gaps=["unknown_vendor_format"],
40
+ )
41
+
42
+
43
+ def from_geelark(data: dict[str, Any]) -> NeutralPhoneProfile:
44
+ gaps: list[str] = []
45
+ if not data.get("proxy"):
46
+ gaps.append("proxy_missing")
47
+ if not data.get("device_id"):
48
+ gaps.append("device_id_not_mapped")
49
+ return NeutralPhoneProfile(
50
+ vendor="geelark",
51
+ profile_name=str(data.get("name") or data.get("profile_name") or "unknown"),
52
+ android_version=data.get("android_version"),
53
+ country=data.get("country") or data.get("region"),
54
+ proxy=data.get("proxy"),
55
+ apps=list(data.get("apps") or []),
56
+ gaps=gaps,
57
+ )
58
+
59
+
60
+ def from_morelogin(data: dict[str, Any]) -> NeutralPhoneProfile:
61
+ gaps: list[str] = []
62
+ if not data.get("arm_device"):
63
+ gaps.append("verify_arm_cloud_not_emulator")
64
+ return NeutralPhoneProfile(
65
+ vendor="morelogin",
66
+ profile_name=str(data.get("phone_name") or data.get("name") or "unknown"),
67
+ android_version=data.get("os_version"),
68
+ country=data.get("location"),
69
+ proxy=data.get("proxy_url"),
70
+ apps=list(data.get("installed_apps") or []),
71
+ gaps=gaps,
72
+ )
73
+
74
+
75
+ def from_gologin(data: dict[str, Any]) -> NeutralPhoneProfile:
76
+ gaps: list[str] = ["gologin_mobile_is_browser_first"]
77
+ if not data.get("proxy"):
78
+ gaps.append("proxy_missing")
79
+ return NeutralPhoneProfile(
80
+ vendor="gologin",
81
+ profile_name=str(data.get("name") or data.get("profile_name") or "unknown"),
82
+ android_version=str(data.get("androidVersion") or data.get("os") or "") or None,
83
+ country=data.get("timezone") or data.get("country"),
84
+ proxy=data.get("proxy"),
85
+ apps=list(data.get("apps") or []),
86
+ gaps=gaps,
87
+ )
88
+
89
+
90
+ def from_duoplus(data: dict[str, Any]) -> NeutralPhoneProfile:
91
+ gaps: list[str] = []
92
+ if not data.get("cloud_instance_id"):
93
+ gaps.append("instance_id_not_mapped")
94
+ return NeutralPhoneProfile(
95
+ vendor="duoplus",
96
+ profile_name=str(data.get("profile_name") or data.get("name") or "unknown"),
97
+ android_version=data.get("android"),
98
+ country=data.get("region"),
99
+ proxy=data.get("proxy"),
100
+ apps=list(data.get("apps") or []),
101
+ gaps=gaps,
102
+ )
@@ -0,0 +1,15 @@
1
+ """Neutral cloud phone profile schema."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pydantic import BaseModel, Field
6
+
7
+
8
+ class NeutralPhoneProfile(BaseModel):
9
+ vendor: str
10
+ profile_name: str
11
+ android_version: str | None = None
12
+ country: str | None = None
13
+ proxy: str | None = None
14
+ apps: list[str] = Field(default_factory=list)
15
+ gaps: list[str] = Field(default_factory=list)
@@ -0,0 +1,68 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "mobile-vendor-bridge"
7
+ version = "0.1.2"
8
+ description = "GeeLark & MoreLogin to Multilogin — parse mobile cloud phone exports and gap audit. CLI: mobile-bridge."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.10"
12
+ authors = [{ name = "mobile-vendor-bridge contributors" }]
13
+ keywords = [
14
+ "mobile-vendor-import",
15
+ "geelark-export",
16
+ "morelogin-migration",
17
+ "gologin-mobile",
18
+ "cloud-phone-migration",
19
+ "competitor-phone-import",
20
+ "phone-profile-import",
21
+ "android-profile-export",
22
+ "vendor-normalize",
23
+ "mobile-gap-audit",
24
+ "geelark-to-multilogin",
25
+ "phone-export-parser",
26
+ "duoplus-export",
27
+ ]
28
+ classifiers = [
29
+ "Development Status :: 4 - Beta",
30
+ "Environment :: Console",
31
+ "Intended Audience :: Developers",
32
+ "Topic :: Software Development :: Quality Assurance",
33
+ "License :: OSI Approved :: MIT License",
34
+ "Programming Language :: Python :: 3",
35
+ "Programming Language :: Python :: 3.10",
36
+ "Programming Language :: Python :: 3.11",
37
+ "Programming Language :: Python :: 3.12",
38
+ "Programming Language :: Python :: 3.13",
39
+ "Typing :: Typed",
40
+ ]
41
+ dependencies = [
42
+ "click>=8.1",
43
+ "pydantic>=2.5",
44
+ "pyyaml>=6.0",
45
+ ]
46
+
47
+ [project.optional-dependencies]
48
+ dev = ["pytest>=8.0", "ruff>=0.8"]
49
+
50
+ [project.scripts]
51
+ mobile-bridge = "mobile_vendor_bridge.cli:main"
52
+
53
+ [project.urls]
54
+ Homepage = "https://pypi.org/project/mobile-vendor-bridge/"
55
+ Documentation = "https://pypi.org/project/mobile-vendor-bridge/"
56
+ Repository = "https://pypi.org/project/mobile-vendor-bridge/"
57
+ Changelog = "https://pypi.org/project/mobile-vendor-bridge/"
58
+
59
+ [tool.pytest.ini_options]
60
+ testpaths = ["tests"]
61
+ addopts = "-q"
62
+
63
+ [tool.ruff]
64
+ target-version = "py310"
65
+ line-length = 100
66
+
67
+ [tool.ruff.lint]
68
+ select = ["E", "F", "I", "UP", "B", "SIM"]
@@ -0,0 +1,9 @@
1
+ {
2
+ "vendor": "duoplus",
3
+ "name": "tiktok-de-01",
4
+ "cloud_instance_id": "dp-9981",
5
+ "android": "12",
6
+ "region": "DE",
7
+ "proxy": "socks5://user:pass@5.6.7.8:1080",
8
+ "apps": ["tiktok"]
9
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "tiktok-us-01",
3
+ "device_id": "gl-123",
4
+ "android_version": "13",
5
+ "country": "US",
6
+ "proxy": "socks5://1.2.3.4:1080",
7
+ "apps": ["tiktok"]
8
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "phone_name": "ig-uk-02",
3
+ "arm_device": true,
4
+ "os_version": "13",
5
+ "location": "GB",
6
+ "proxy_url": "socks5://1.2.3.4:1080",
7
+ "installed_apps": ["instagram"]
8
+ }
@@ -0,0 +1,9 @@
1
+ from click.testing import CliRunner
2
+
3
+ from mobile_vendor_bridge.cli import main
4
+
5
+
6
+ def test_help_clean() -> None:
7
+ r = CliRunner().invoke(main, ["--help"])
8
+ assert r.exit_code == 0
9
+ assert "multilogin.com" not in r.output
@@ -0,0 +1,34 @@
1
+ import json
2
+ from pathlib import Path
3
+
4
+ from mobile_vendor_bridge.parsers import detect_format, parse_vendor
5
+
6
+ FIXTURES = Path(__file__).parent / "fixtures"
7
+
8
+
9
+ def _load(name: str) -> tuple[dict, str]:
10
+ path = FIXTURES / name
11
+ text = path.read_text(encoding="utf-8")
12
+ return json.loads(text), text
13
+
14
+
15
+ def test_geelark_fixture() -> None:
16
+ data, text = _load("geelark.json")
17
+ assert detect_format(data, text) == "geelark"
18
+ profile = parse_vendor(data, "geelark")
19
+ assert profile.gaps == []
20
+
21
+
22
+ def test_morelogin_fixture() -> None:
23
+ data, text = _load("morelogin.json")
24
+ assert detect_format(data, text) == "morelogin"
25
+ profile = parse_vendor(data, "morelogin")
26
+ assert profile.gaps == []
27
+
28
+
29
+ def test_duoplus_fixture() -> None:
30
+ data, text = _load("duoplus.json")
31
+ assert detect_format(data, text) == "duoplus"
32
+ profile = parse_vendor(data, "duoplus")
33
+ assert profile.vendor == "duoplus"
34
+ assert profile.gaps == []
@@ -0,0 +1,12 @@
1
+ from mobile_vendor_bridge.normalize import from_geelark, from_morelogin
2
+
3
+
4
+ def test_geelark_gap() -> None:
5
+ p = from_geelark({"name": "g1"})
6
+ assert p.vendor == "geelark"
7
+ assert "proxy_missing" in p.gaps
8
+
9
+
10
+ def test_morelogin() -> None:
11
+ p = from_morelogin({"phone_name": "m1", "arm_device": True})
12
+ assert p.vendor == "morelogin"
@@ -0,0 +1,21 @@
1
+ from mobile_vendor_bridge.parsers import detect_format, from_duoplus, from_gologin, parse_vendor
2
+
3
+
4
+ def test_detect_geelark() -> None:
5
+ assert detect_format({"device_id": "x"}, "") == "geelark"
6
+
7
+
8
+ def test_gologin_gap() -> None:
9
+ p = from_gologin({"name": "g1"})
10
+ assert "gologin_mobile_is_browser_first" in p.gaps
11
+
12
+
13
+ def test_duoplus() -> None:
14
+ p = from_duoplus({"name": "d1", "cloud_instance_id": "i1"})
15
+ assert p.vendor == "duoplus"
16
+ assert p.gaps == []
17
+
18
+
19
+ def test_parse_unknown() -> None:
20
+ p = parse_vendor({"name": "x"}, "unknown")
21
+ assert "unknown_vendor_format" in p.gaps