droidock 0.1.0__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,308 @@
1
+ Metadata-Version: 2.3
2
+ Name: droidock
3
+ Version: 0.1.0
4
+ Summary: Reusable Android connection management with an interactive CLI.
5
+ Keywords: android,adb,cli,tailscale,wireless-debugging
6
+ Classifier: Environment :: Console
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
9
+ Classifier: Topic :: System :: Networking
10
+ Requires-Dist: adbutils>=2.12,<3
11
+ Requires-Dist: filelock>=3.18,<4
12
+ Requires-Dist: platformdirs>=4,<5
13
+ Requires-Dist: questionary>=2.1,<3
14
+ Requires-Dist: rich>=14,<16
15
+ Requires-Dist: typer>=0.21,<1
16
+ Requires-Dist: zeroconf>=0.150,<1
17
+ Requires-Python: >=3.11
18
+ Project-URL: Repository, https://github.com/c0sogi/droidock
19
+ Project-URL: Issues, https://github.com/c0sogi/droidock/issues
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Droidock
23
+
24
+ A Python library and interactive CLI that remembers Android connection settings on your PC.
25
+ It groups USB and wireless connections by device serial number, discovers changing addresses,
26
+ and verifies the device identity before updating a saved profile.
27
+
28
+ ```powershell
29
+ uv tool install droidock
30
+ droidock
31
+ ```
32
+
33
+ To run without a persistent CLI installation, use `uvx droidock`. To use the Python API in another project,
34
+ run `uv add droidock`, then `from droidock import ConnectionManager`.
35
+
36
+ The package prefers the ADB executable included in `adbutils`, so a separate Android SDK or ADB installation
37
+ is not required on supported platforms. If no bundled executable is available for your OS, configure an ADB
38
+ path or provide ADB on `PATH`. The distribution has been verified on Windows x64.
39
+
40
+ ## First connection
41
+
42
+ Use the arrow keys and Enter to navigate. For a numbered menu, run `uv run droidock --plain`.
43
+
44
+ The main menu scans once on startup and keeps that snapshot while you browse menus or edit saved preferences.
45
+ Select **Scan again** to refresh it. Registration, connection changes, and ADB server settings refresh the view
46
+ when needed. **Back** and **Exit** do not start discovery. Use `uv run droidock --version` to check the
47
+ installed version; restart an already running CLI after updating it.
48
+
49
+ Nearby services can advertise both IPv4 and IPv6 addresses. The table and selection menu show one entry per
50
+ service name and purpose, with all its addresses retained. Selecting a wireless connection tries IPv4 first,
51
+ then another advertised address if the connection fails (up to three addresses). A discovery entry is not yet a
52
+ saved device: registration still requires a responding device with a verified serial number.
53
+
54
+ 1. Select **Add a device**.
55
+ 2. For USB, enable USB debugging on the device and authorize this PC before registering it.
56
+ 3. For wireless access, join the same network, enable **Wireless debugging**, and open
57
+ **Pair device with pairing code** on the device.
58
+ 4. Select the pairing address in the CLI and enter the six-digit code. The code is hidden and is not saved.
59
+ 5. After pairing, connect to the device's **current connection address**. The pairing port and connection port differ.
60
+ 6. Give the device a name. The first registered device becomes the default, with automatic connection enabled.
61
+
62
+ The interactive menu attempts to reconnect saved devices on subsequent launches. To keep checking connections
63
+ while the program runs:
64
+
65
+ ```powershell
66
+ uv run droidock watch
67
+ ```
68
+
69
+ `watch` periodically discovers and reconnects enabled devices until you stop it. It does not install an OS startup
70
+ entry or background service. Change per-device preferences under **Manage saved devices**, and startup behavior
71
+ under **Connection settings**. Disconnecting a saved device also disables its automatic connection preference.
72
+ These settings control this tool's connection attempts; other applications and a shared ADB server can manage
73
+ connections independently.
74
+
75
+ ## Tailscale devices
76
+
77
+ Open **Tailscale devices** in the main menu. This reads the installed Tailscale client's device list, with Android
78
+ devices first and both IP address families shown in one row. Tailscale must be installed, running, and signed in
79
+ on this PC; it is optional for the rest of Droidock. Set `DROIDOCK_TAILSCALE_PATH` if its executable cannot be found.
80
+ The device list is cached until you select **Refresh device list**. Opening a device menu or selecting **Back**
81
+ does not search ports.
82
+
83
+ 1. Select the device, then **Find ADB port and connect**.
84
+ 2. The search first checks ports from current and saved connections at the selected IP. If none answers as ADB,
85
+ it searches the other TCP ports on that one address, skipping the peer's advertised Tailscale API ports.
86
+ 3. A progress bar shows checked ports, percentage, elapsed time, and **ETA**. ETA estimates the time to check the
87
+ remaining range at the observed rate; it starts with “estimating” and can change with network conditions.
88
+ The search stops immediately when ADB responds, or at the five-minute time limit. A partial search keeps its
89
+ actual count instead of displaying 100%. Press **Ctrl+C** during the search to return to the device menu.
90
+ 4. Droidock connects through ADB, verifies the physical device serial, and lets you save or edit its name.
91
+ An existing profile for that serial keeps its ID and alias. Finding an ADB port does not grant authorization;
92
+ first-time wireless pairing still requires the device's pairing code.
93
+
94
+ IPv4 is selected first. Use **Select another device address** to try IPv6, or **Enter a known connection port**
95
+ to connect without searching. An online Tailscale status alone does not establish ADB access. Keep Wireless
96
+ debugging enabled, and ensure Tailscale access rules allow the connection.
97
+
98
+ Successful endpoints are saved on this PC and used by normal automatic connection attempts. Startup, **Scan again**,
99
+ and `watch` do not run broad Tailscale port searches. If the saved port stops working, reopen the Tailscale menu
100
+ to find its current port. Local mDNS remains part of ordinary discovery; Tailscale device enumeration supplies
101
+ peer addresses, not Android's changing debugging port.
102
+
103
+ The same functionality is available without terminal dependencies:
104
+
105
+ ```python
106
+ import asyncio
107
+
108
+ from droidock import AdbPortScanner, ConnectionManager, TailscaleClient, preferred_adb_ports
109
+
110
+ manager = ConnectionManager()
111
+ peers = TailscaleClient().peers() # List devices without probing their ports.
112
+ peer = next(p for p in peers if p.name == "Office XR") # The caller selects one device.
113
+ host = peer.addresses[0]
114
+ saved = [e for d in manager.store.read().devices for e in d.endpoints]
115
+ result = asyncio.run(
116
+ AdbPortScanner().scan(
117
+ host,
118
+ preferred_ports=preferred_adb_ports(host, saved),
119
+ excluded_ports=peer.peer_api_ports,
120
+ )
121
+ )
122
+ if result.endpoint:
123
+ device = manager.connect_endpoint(result.endpoint) # Verify serial before saving.
124
+ ```
125
+
126
+ Async applications can await `scan()` directly. Its optional `on_progress` callback receives `PortScanProgress`
127
+ values, including `eta_seconds`. Pass a `threading.Event` as `stop`, or cancel the async task, to close all active
128
+ probes. `PortScanStatus` distinguishes discovery, no response, timeout, and cancellation. Callers can set concurrency,
129
+ timeouts, or an explicit port range. Peer names and IP addresses are discovery hints, not persistent Android identities;
130
+ pass `expected=manager.device(saved_name)` when connecting on behalf of a specific saved device.
131
+
132
+ ## Saved connection settings
133
+
134
+ `droidock settings` shows the storage path. On Windows, the default is usually
135
+ `%LOCALAPPDATA%\droidock\state.json`.
136
+
137
+ An explicit `--data-dir` or `DeviceStore(directory)` takes priority over `DROIDOCK_HOME`, followed by the default
138
+ configuration directory. All stores use the same schema, so CLI and Python applications can share saved profiles
139
+ or select separate directories.
140
+
141
+ The store contains:
142
+
143
+ - A persistent local device ID, name, verified serial number, manufacturer, and model.
144
+ - Wireless identifiers read from the connected device, recent connection addresses, and the last-seen timestamp.
145
+ - The default device and each device's automatic connection preference.
146
+ - The ADB executable path, server port, discovery duration, command timeout, and connection attempt limit.
147
+
148
+ Saved IP addresses and ports are connection candidates. **A different device serial number never replaces the
149
+ saved identity.** Discovery combines ADB's results with independent mDNS discovery, which looks for device
150
+ advertisements on the local network. After connecting to a new address, the manager checks the command response
151
+ and serial number before updating the profile.
152
+
153
+ Writes use file locking and atomic replacement. An invalid or corrupted store is reported and preserved instead
154
+ of being silently reset. ADB manages its authorization keys in the PC user's `.android` directory. This tool does
155
+ not copy those keys or store pairing codes. Deleting a saved profile does not revoke pairing authorization on Android.
156
+
157
+ To keep separate records for another application, choose a different directory:
158
+
159
+ ```powershell
160
+ uv run droidock --data-dir C:\MyAppData\android devices
161
+ # Alternatively, set the DROIDOCK_HOME environment variable.
162
+ ```
163
+
164
+ Set `DROIDOCK_ADB_PATH` to override the bundled executable. The saved `adb_path` setting takes priority over this
165
+ environment variable. Set `DROIDOCK_TAILSCALE_PATH` to select a Tailscale executable, or pass its path directly to
166
+ `TailscaleClient(executable=...)`. The public base exception is `DroidockError`.
167
+
168
+ ## CLI commands
169
+
170
+ Interactive device details and diagnostics use labeled tables, readable statuses, and suggested next steps.
171
+ Result commands also display tables or short messages by default. For scripts, append `--json` to `devices`,
172
+ `register`, `connect`, `pair`, `auto-connect`, `diagnose`, `settings`, `profile`, or `disconnect`.
173
+ For example, `uv run droidock settings --json` preserves the structured field names and values.
174
+ JSON pairing requires an explicit address and `--code-stdin`, so prompts do not appear in the JSON output.
175
+
176
+ | Command | Behavior |
177
+ |---|---|
178
+ | `droidock` | Attempt startup connections and open the interactive menu. |
179
+ | `droidock --version` | Show the installed version without loading profiles or starting ADB. |
180
+ | `droidock devices --json` | List profiles, current connections, and wireless services without requesting reconnection. |
181
+ | `droidock register USB_SERIAL --name "Office XR"` | Register an already connected device. |
182
+ | `droidock pair IP:PAIRING_PORT` | Pair using a hidden code prompt. |
183
+ | `droidock connect --endpoint IP:CONNECT_PORT --name "Office XR"` | Connect, verify identity, and register the device. |
184
+ | `droidock connect "Office XR"` | Discover and reconnect a saved device. |
185
+ | `droidock connect "Office XR" --endpoint IP:PORT` | Connect to an explicit address and verify it matches the saved device. |
186
+ | `droidock auto-connect` | Try once for each device with automatic connection enabled. |
187
+ | `droidock watch --interval 5` | Maintain connections while running; stop with Ctrl+C. |
188
+ | `droidock profile "Office XR" --default` | Select the default device. |
189
+ | `droidock profile "Office XR" --no-auto-connect` | Disable automatic connection for this device. |
190
+ | `droidock disconnect "Office XR"` | Disconnect verified wireless connections and disable automatic connection. |
191
+ | `droidock diagnose` | Show ADB, server, and device diagnostics with connection guidance. |
192
+ | `droidock settings` | Show connection settings and the storage path. |
193
+ | `droidock settings adb_path auto` | Prefer bundled ADB. |
194
+ | `droidock forget "Office XR"` | Confirm and delete the saved profile from this PC. |
195
+
196
+ Prefix commands with `uv run` when working in the project. From another directory:
197
+
198
+ ```powershell
199
+ uv run --project C:\Projects\droidock droidock
200
+ ```
201
+
202
+ For scripted pairing, use `pair IP:PORT --code-stdin` and send the code through standard input. Successful pairing
203
+ confirms the trust exchange; a subsequent connection must still verify the device response. The default command
204
+ timeout is 5 seconds, pairing has a 30-second timeout, and reconnection allows up to 3 attempts by default.
205
+ Each attempt tries at most 3 addresses, preferring current discovery results over saved addresses.
206
+
207
+ ## Use the library in another project
208
+
209
+ Applications use the same `ConnectionManager` as the CLI. The core has no terminal prompts or output and does not
210
+ import Typer or Questionary. Importing the package does not start ADB or connect to devices.
211
+
212
+ During local development, add this package from a sibling project:
213
+
214
+ ```powershell
215
+ uv add ../droidock
216
+ ```
217
+
218
+ ```python
219
+ import adbutils
220
+
221
+ from droidock import ConnectionManager
222
+
223
+ # Share the profiles registered through the CLI.
224
+ manager = ConnectionManager()
225
+ transport = manager.resolve("Office XR")
226
+
227
+ # The returned address identifies a verified current ADB connection.
228
+ # The calling application owns installation, file transfer, and other device operations.
229
+ adb = adbutils.AdbClient(host="127.0.0.1", port=manager.settings.server_port)
230
+ device = adb.device(serial=transport.address)
231
+ print(device.shell("getprop ro.product.model"))
232
+ ```
233
+
234
+ Use a separate store when your application should manage its own profiles:
235
+
236
+ ```python
237
+ from droidock import ConnectionManager, DeviceStore
238
+
239
+ manager = ConnectionManager(DeviceStore("./my-app-data/android"))
240
+ ```
241
+
242
+ `twinverse-build` can obtain the current device address through this API and pass it to its APK deployment flow.
243
+ The package has no dependency on Twinverse, Unity project paths, a particular device model, or MQTT settings.
244
+ See [examples/integrate.py](https://github.com/c0sogi/droidock/blob/main/examples/integrate.py) for a runnable integration example.
245
+
246
+ Extension points:
247
+
248
+ - `ServiceKind.CONNECT` and `ServiceKind.PAIRING`: typed service purposes on `Service` and `ServiceGroup`.
249
+ Python callers should pass these enum members. Existing string inputs are normalized at runtime; unsupported
250
+ values raise `ValueError`. JSON continues to use `"connect"` and `"pairing"`, with unchanged field names.
251
+ - `Snapshot.service_groups` or `group_services(services)`: obtain one entry per advertised service with all its
252
+ addresses. `Snapshot.services` and `devices --json` retain the individual address records.
253
+ - `connect_endpoints(endpoints, name=...)`: connect through a selected service's addresses and save a verified
254
+ device. Pass `expected=record` to require an existing device identity; profile and identity errors stop fallback.
255
+ - `Backend`: replace ADB queries, connection, pairing, and identity inspection.
256
+ - `Discovery`: add discovery for a different network environment.
257
+ - `DeviceStore(directory)`: isolate storage; subclass it to replace the storage implementation.
258
+ - `on_event(ConnectionEvent)`: send discovery, connection, and failure events to your application's UI or logs.
259
+ - `watch(stop=threading.Event())`: let the calling application stop its reconnection loop.
260
+ - `DroidockError.code`: handle failures by error category.
261
+
262
+ Another connected Android device never satisfies a request for the selected device. Missing serial numbers and
263
+ conflicting model details for the same serial prevent automatic registration or merging. Serial matching is not a
264
+ cryptographic defense against cloned identifiers; authentication uses Android's ADB authorization and pairing.
265
+
266
+ ## Connection troubleshooting
267
+
268
+ A powered-on device may still be unavailable to ADB. Wireless debugging may be disabled, the network may block
269
+ discovery advertisements, or USB authorization or drivers may be missing. In those cases, update the device settings
270
+ or enter its current connection address manually. Windows USB connections may require device-specific drivers.
271
+
272
+ The tool reuses a compatible running ADB server and does not automatically stop the shared server. A protocol version
273
+ conflict produces guidance to select a compatible ADB executable or a separate local server port.
274
+
275
+ ## Development and validation
276
+
277
+ Use English for project documentation, CLI messages, comments, docstrings, and examples. User-provided device names
278
+ remain unchanged. Use `uv` to manage the Python environment and dependencies.
279
+
280
+ ```powershell
281
+ uv sync
282
+ uv run ruff check .
283
+ uv run ruff format --check .
284
+ uv run isort --check-only --diff .
285
+ uv run pyright
286
+ uv run pytest -q
287
+ uv lock --check
288
+ uv build --no-sources
289
+ ```
290
+
291
+ Ruff includes import-order checks (`I`), and isort is also installed as a development dependency for a separate
292
+ check. Both use a line length of 110; isort uses the `black` profile. Pyright covers `src`, `tests`, `scripts`,
293
+ and `examples`.
294
+
295
+ Tests cover identity persistence after address changes, reassigned addresses, USB/wireless duplicates, requested
296
+ device verification, connection preferences, pairing secrets, storage corruption and concurrent writes, and CLI
297
+ behavior. See [VALIDATION.md](https://github.com/c0sogi/droidock/blob/main/VALIDATION.md) for executed checks, isolated wheel installation, and physical-device
298
+ verification limits.
299
+
300
+ External API references:
301
+ [adbutils](https://github.com/openatx/adbutils),
302
+ [ADB commands](https://android.googlesource.com/platform/packages/modules/adb/+/HEAD/docs/user/adb.1.md),
303
+ [wireless ADB](https://android.googlesource.com/platform/packages/modules/adb/+/HEAD/docs/dev/adb_wifi.md),
304
+ [ADB protocol](https://android.googlesource.com/platform/packages/modules/adb/+/HEAD/docs/dev/protocol.md),
305
+ [Tailscale CLI](https://tailscale.com/docs/reference/tailscale-cli#status),
306
+ [Zeroconf](https://python-zeroconf.readthedocs.io/en/latest/api.html),
307
+ [isort configuration](https://isort.readthedocs.io/en/latest/configuration/black_compatibility.html),
308
+ [Ruff formatter](https://docs.astral.sh/ruff/formatter/).
@@ -0,0 +1,287 @@
1
+ # Droidock
2
+
3
+ A Python library and interactive CLI that remembers Android connection settings on your PC.
4
+ It groups USB and wireless connections by device serial number, discovers changing addresses,
5
+ and verifies the device identity before updating a saved profile.
6
+
7
+ ```powershell
8
+ uv tool install droidock
9
+ droidock
10
+ ```
11
+
12
+ To run without a persistent CLI installation, use `uvx droidock`. To use the Python API in another project,
13
+ run `uv add droidock`, then `from droidock import ConnectionManager`.
14
+
15
+ The package prefers the ADB executable included in `adbutils`, so a separate Android SDK or ADB installation
16
+ is not required on supported platforms. If no bundled executable is available for your OS, configure an ADB
17
+ path or provide ADB on `PATH`. The distribution has been verified on Windows x64.
18
+
19
+ ## First connection
20
+
21
+ Use the arrow keys and Enter to navigate. For a numbered menu, run `uv run droidock --plain`.
22
+
23
+ The main menu scans once on startup and keeps that snapshot while you browse menus or edit saved preferences.
24
+ Select **Scan again** to refresh it. Registration, connection changes, and ADB server settings refresh the view
25
+ when needed. **Back** and **Exit** do not start discovery. Use `uv run droidock --version` to check the
26
+ installed version; restart an already running CLI after updating it.
27
+
28
+ Nearby services can advertise both IPv4 and IPv6 addresses. The table and selection menu show one entry per
29
+ service name and purpose, with all its addresses retained. Selecting a wireless connection tries IPv4 first,
30
+ then another advertised address if the connection fails (up to three addresses). A discovery entry is not yet a
31
+ saved device: registration still requires a responding device with a verified serial number.
32
+
33
+ 1. Select **Add a device**.
34
+ 2. For USB, enable USB debugging on the device and authorize this PC before registering it.
35
+ 3. For wireless access, join the same network, enable **Wireless debugging**, and open
36
+ **Pair device with pairing code** on the device.
37
+ 4. Select the pairing address in the CLI and enter the six-digit code. The code is hidden and is not saved.
38
+ 5. After pairing, connect to the device's **current connection address**. The pairing port and connection port differ.
39
+ 6. Give the device a name. The first registered device becomes the default, with automatic connection enabled.
40
+
41
+ The interactive menu attempts to reconnect saved devices on subsequent launches. To keep checking connections
42
+ while the program runs:
43
+
44
+ ```powershell
45
+ uv run droidock watch
46
+ ```
47
+
48
+ `watch` periodically discovers and reconnects enabled devices until you stop it. It does not install an OS startup
49
+ entry or background service. Change per-device preferences under **Manage saved devices**, and startup behavior
50
+ under **Connection settings**. Disconnecting a saved device also disables its automatic connection preference.
51
+ These settings control this tool's connection attempts; other applications and a shared ADB server can manage
52
+ connections independently.
53
+
54
+ ## Tailscale devices
55
+
56
+ Open **Tailscale devices** in the main menu. This reads the installed Tailscale client's device list, with Android
57
+ devices first and both IP address families shown in one row. Tailscale must be installed, running, and signed in
58
+ on this PC; it is optional for the rest of Droidock. Set `DROIDOCK_TAILSCALE_PATH` if its executable cannot be found.
59
+ The device list is cached until you select **Refresh device list**. Opening a device menu or selecting **Back**
60
+ does not search ports.
61
+
62
+ 1. Select the device, then **Find ADB port and connect**.
63
+ 2. The search first checks ports from current and saved connections at the selected IP. If none answers as ADB,
64
+ it searches the other TCP ports on that one address, skipping the peer's advertised Tailscale API ports.
65
+ 3. A progress bar shows checked ports, percentage, elapsed time, and **ETA**. ETA estimates the time to check the
66
+ remaining range at the observed rate; it starts with “estimating” and can change with network conditions.
67
+ The search stops immediately when ADB responds, or at the five-minute time limit. A partial search keeps its
68
+ actual count instead of displaying 100%. Press **Ctrl+C** during the search to return to the device menu.
69
+ 4. Droidock connects through ADB, verifies the physical device serial, and lets you save or edit its name.
70
+ An existing profile for that serial keeps its ID and alias. Finding an ADB port does not grant authorization;
71
+ first-time wireless pairing still requires the device's pairing code.
72
+
73
+ IPv4 is selected first. Use **Select another device address** to try IPv6, or **Enter a known connection port**
74
+ to connect without searching. An online Tailscale status alone does not establish ADB access. Keep Wireless
75
+ debugging enabled, and ensure Tailscale access rules allow the connection.
76
+
77
+ Successful endpoints are saved on this PC and used by normal automatic connection attempts. Startup, **Scan again**,
78
+ and `watch` do not run broad Tailscale port searches. If the saved port stops working, reopen the Tailscale menu
79
+ to find its current port. Local mDNS remains part of ordinary discovery; Tailscale device enumeration supplies
80
+ peer addresses, not Android's changing debugging port.
81
+
82
+ The same functionality is available without terminal dependencies:
83
+
84
+ ```python
85
+ import asyncio
86
+
87
+ from droidock import AdbPortScanner, ConnectionManager, TailscaleClient, preferred_adb_ports
88
+
89
+ manager = ConnectionManager()
90
+ peers = TailscaleClient().peers() # List devices without probing their ports.
91
+ peer = next(p for p in peers if p.name == "Office XR") # The caller selects one device.
92
+ host = peer.addresses[0]
93
+ saved = [e for d in manager.store.read().devices for e in d.endpoints]
94
+ result = asyncio.run(
95
+ AdbPortScanner().scan(
96
+ host,
97
+ preferred_ports=preferred_adb_ports(host, saved),
98
+ excluded_ports=peer.peer_api_ports,
99
+ )
100
+ )
101
+ if result.endpoint:
102
+ device = manager.connect_endpoint(result.endpoint) # Verify serial before saving.
103
+ ```
104
+
105
+ Async applications can await `scan()` directly. Its optional `on_progress` callback receives `PortScanProgress`
106
+ values, including `eta_seconds`. Pass a `threading.Event` as `stop`, or cancel the async task, to close all active
107
+ probes. `PortScanStatus` distinguishes discovery, no response, timeout, and cancellation. Callers can set concurrency,
108
+ timeouts, or an explicit port range. Peer names and IP addresses are discovery hints, not persistent Android identities;
109
+ pass `expected=manager.device(saved_name)` when connecting on behalf of a specific saved device.
110
+
111
+ ## Saved connection settings
112
+
113
+ `droidock settings` shows the storage path. On Windows, the default is usually
114
+ `%LOCALAPPDATA%\droidock\state.json`.
115
+
116
+ An explicit `--data-dir` or `DeviceStore(directory)` takes priority over `DROIDOCK_HOME`, followed by the default
117
+ configuration directory. All stores use the same schema, so CLI and Python applications can share saved profiles
118
+ or select separate directories.
119
+
120
+ The store contains:
121
+
122
+ - A persistent local device ID, name, verified serial number, manufacturer, and model.
123
+ - Wireless identifiers read from the connected device, recent connection addresses, and the last-seen timestamp.
124
+ - The default device and each device's automatic connection preference.
125
+ - The ADB executable path, server port, discovery duration, command timeout, and connection attempt limit.
126
+
127
+ Saved IP addresses and ports are connection candidates. **A different device serial number never replaces the
128
+ saved identity.** Discovery combines ADB's results with independent mDNS discovery, which looks for device
129
+ advertisements on the local network. After connecting to a new address, the manager checks the command response
130
+ and serial number before updating the profile.
131
+
132
+ Writes use file locking and atomic replacement. An invalid or corrupted store is reported and preserved instead
133
+ of being silently reset. ADB manages its authorization keys in the PC user's `.android` directory. This tool does
134
+ not copy those keys or store pairing codes. Deleting a saved profile does not revoke pairing authorization on Android.
135
+
136
+ To keep separate records for another application, choose a different directory:
137
+
138
+ ```powershell
139
+ uv run droidock --data-dir C:\MyAppData\android devices
140
+ # Alternatively, set the DROIDOCK_HOME environment variable.
141
+ ```
142
+
143
+ Set `DROIDOCK_ADB_PATH` to override the bundled executable. The saved `adb_path` setting takes priority over this
144
+ environment variable. Set `DROIDOCK_TAILSCALE_PATH` to select a Tailscale executable, or pass its path directly to
145
+ `TailscaleClient(executable=...)`. The public base exception is `DroidockError`.
146
+
147
+ ## CLI commands
148
+
149
+ Interactive device details and diagnostics use labeled tables, readable statuses, and suggested next steps.
150
+ Result commands also display tables or short messages by default. For scripts, append `--json` to `devices`,
151
+ `register`, `connect`, `pair`, `auto-connect`, `diagnose`, `settings`, `profile`, or `disconnect`.
152
+ For example, `uv run droidock settings --json` preserves the structured field names and values.
153
+ JSON pairing requires an explicit address and `--code-stdin`, so prompts do not appear in the JSON output.
154
+
155
+ | Command | Behavior |
156
+ |---|---|
157
+ | `droidock` | Attempt startup connections and open the interactive menu. |
158
+ | `droidock --version` | Show the installed version without loading profiles or starting ADB. |
159
+ | `droidock devices --json` | List profiles, current connections, and wireless services without requesting reconnection. |
160
+ | `droidock register USB_SERIAL --name "Office XR"` | Register an already connected device. |
161
+ | `droidock pair IP:PAIRING_PORT` | Pair using a hidden code prompt. |
162
+ | `droidock connect --endpoint IP:CONNECT_PORT --name "Office XR"` | Connect, verify identity, and register the device. |
163
+ | `droidock connect "Office XR"` | Discover and reconnect a saved device. |
164
+ | `droidock connect "Office XR" --endpoint IP:PORT` | Connect to an explicit address and verify it matches the saved device. |
165
+ | `droidock auto-connect` | Try once for each device with automatic connection enabled. |
166
+ | `droidock watch --interval 5` | Maintain connections while running; stop with Ctrl+C. |
167
+ | `droidock profile "Office XR" --default` | Select the default device. |
168
+ | `droidock profile "Office XR" --no-auto-connect` | Disable automatic connection for this device. |
169
+ | `droidock disconnect "Office XR"` | Disconnect verified wireless connections and disable automatic connection. |
170
+ | `droidock diagnose` | Show ADB, server, and device diagnostics with connection guidance. |
171
+ | `droidock settings` | Show connection settings and the storage path. |
172
+ | `droidock settings adb_path auto` | Prefer bundled ADB. |
173
+ | `droidock forget "Office XR"` | Confirm and delete the saved profile from this PC. |
174
+
175
+ Prefix commands with `uv run` when working in the project. From another directory:
176
+
177
+ ```powershell
178
+ uv run --project C:\Projects\droidock droidock
179
+ ```
180
+
181
+ For scripted pairing, use `pair IP:PORT --code-stdin` and send the code through standard input. Successful pairing
182
+ confirms the trust exchange; a subsequent connection must still verify the device response. The default command
183
+ timeout is 5 seconds, pairing has a 30-second timeout, and reconnection allows up to 3 attempts by default.
184
+ Each attempt tries at most 3 addresses, preferring current discovery results over saved addresses.
185
+
186
+ ## Use the library in another project
187
+
188
+ Applications use the same `ConnectionManager` as the CLI. The core has no terminal prompts or output and does not
189
+ import Typer or Questionary. Importing the package does not start ADB or connect to devices.
190
+
191
+ During local development, add this package from a sibling project:
192
+
193
+ ```powershell
194
+ uv add ../droidock
195
+ ```
196
+
197
+ ```python
198
+ import adbutils
199
+
200
+ from droidock import ConnectionManager
201
+
202
+ # Share the profiles registered through the CLI.
203
+ manager = ConnectionManager()
204
+ transport = manager.resolve("Office XR")
205
+
206
+ # The returned address identifies a verified current ADB connection.
207
+ # The calling application owns installation, file transfer, and other device operations.
208
+ adb = adbutils.AdbClient(host="127.0.0.1", port=manager.settings.server_port)
209
+ device = adb.device(serial=transport.address)
210
+ print(device.shell("getprop ro.product.model"))
211
+ ```
212
+
213
+ Use a separate store when your application should manage its own profiles:
214
+
215
+ ```python
216
+ from droidock import ConnectionManager, DeviceStore
217
+
218
+ manager = ConnectionManager(DeviceStore("./my-app-data/android"))
219
+ ```
220
+
221
+ `twinverse-build` can obtain the current device address through this API and pass it to its APK deployment flow.
222
+ The package has no dependency on Twinverse, Unity project paths, a particular device model, or MQTT settings.
223
+ See [examples/integrate.py](https://github.com/c0sogi/droidock/blob/main/examples/integrate.py) for a runnable integration example.
224
+
225
+ Extension points:
226
+
227
+ - `ServiceKind.CONNECT` and `ServiceKind.PAIRING`: typed service purposes on `Service` and `ServiceGroup`.
228
+ Python callers should pass these enum members. Existing string inputs are normalized at runtime; unsupported
229
+ values raise `ValueError`. JSON continues to use `"connect"` and `"pairing"`, with unchanged field names.
230
+ - `Snapshot.service_groups` or `group_services(services)`: obtain one entry per advertised service with all its
231
+ addresses. `Snapshot.services` and `devices --json` retain the individual address records.
232
+ - `connect_endpoints(endpoints, name=...)`: connect through a selected service's addresses and save a verified
233
+ device. Pass `expected=record` to require an existing device identity; profile and identity errors stop fallback.
234
+ - `Backend`: replace ADB queries, connection, pairing, and identity inspection.
235
+ - `Discovery`: add discovery for a different network environment.
236
+ - `DeviceStore(directory)`: isolate storage; subclass it to replace the storage implementation.
237
+ - `on_event(ConnectionEvent)`: send discovery, connection, and failure events to your application's UI or logs.
238
+ - `watch(stop=threading.Event())`: let the calling application stop its reconnection loop.
239
+ - `DroidockError.code`: handle failures by error category.
240
+
241
+ Another connected Android device never satisfies a request for the selected device. Missing serial numbers and
242
+ conflicting model details for the same serial prevent automatic registration or merging. Serial matching is not a
243
+ cryptographic defense against cloned identifiers; authentication uses Android's ADB authorization and pairing.
244
+
245
+ ## Connection troubleshooting
246
+
247
+ A powered-on device may still be unavailable to ADB. Wireless debugging may be disabled, the network may block
248
+ discovery advertisements, or USB authorization or drivers may be missing. In those cases, update the device settings
249
+ or enter its current connection address manually. Windows USB connections may require device-specific drivers.
250
+
251
+ The tool reuses a compatible running ADB server and does not automatically stop the shared server. A protocol version
252
+ conflict produces guidance to select a compatible ADB executable or a separate local server port.
253
+
254
+ ## Development and validation
255
+
256
+ Use English for project documentation, CLI messages, comments, docstrings, and examples. User-provided device names
257
+ remain unchanged. Use `uv` to manage the Python environment and dependencies.
258
+
259
+ ```powershell
260
+ uv sync
261
+ uv run ruff check .
262
+ uv run ruff format --check .
263
+ uv run isort --check-only --diff .
264
+ uv run pyright
265
+ uv run pytest -q
266
+ uv lock --check
267
+ uv build --no-sources
268
+ ```
269
+
270
+ Ruff includes import-order checks (`I`), and isort is also installed as a development dependency for a separate
271
+ check. Both use a line length of 110; isort uses the `black` profile. Pyright covers `src`, `tests`, `scripts`,
272
+ and `examples`.
273
+
274
+ Tests cover identity persistence after address changes, reassigned addresses, USB/wireless duplicates, requested
275
+ device verification, connection preferences, pairing secrets, storage corruption and concurrent writes, and CLI
276
+ behavior. See [VALIDATION.md](https://github.com/c0sogi/droidock/blob/main/VALIDATION.md) for executed checks, isolated wheel installation, and physical-device
277
+ verification limits.
278
+
279
+ External API references:
280
+ [adbutils](https://github.com/openatx/adbutils),
281
+ [ADB commands](https://android.googlesource.com/platform/packages/modules/adb/+/HEAD/docs/user/adb.1.md),
282
+ [wireless ADB](https://android.googlesource.com/platform/packages/modules/adb/+/HEAD/docs/dev/adb_wifi.md),
283
+ [ADB protocol](https://android.googlesource.com/platform/packages/modules/adb/+/HEAD/docs/dev/protocol.md),
284
+ [Tailscale CLI](https://tailscale.com/docs/reference/tailscale-cli#status),
285
+ [Zeroconf](https://python-zeroconf.readthedocs.io/en/latest/api.html),
286
+ [isort configuration](https://isort.readthedocs.io/en/latest/configuration/black_compatibility.html),
287
+ [Ruff formatter](https://docs.astral.sh/ruff/formatter/).
@@ -0,0 +1,62 @@
1
+ [build-system]
2
+ requires = ["uv_build>=0.9.26,<0.12"]
3
+ build-backend = "uv_build"
4
+
5
+ [project]
6
+ name = "droidock"
7
+ version = "0.1.0"
8
+ description = "Reusable Android connection management with an interactive CLI."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ keywords = ["android", "adb", "cli", "tailscale", "wireless-debugging"]
12
+ classifiers = [
13
+ "Environment :: Console",
14
+ "Programming Language :: Python :: 3",
15
+ "Topic :: Software Development :: Libraries :: Python Modules",
16
+ "Topic :: System :: Networking",
17
+ ]
18
+ dependencies = [
19
+ "adbutils>=2.12,<3",
20
+ "filelock>=3.18,<4",
21
+ "platformdirs>=4,<5",
22
+ "questionary>=2.1,<3",
23
+ "rich>=14,<16",
24
+ "typer>=0.21,<1",
25
+ "zeroconf>=0.150,<1",
26
+ ]
27
+
28
+ [project.scripts]
29
+ droidock = "droidock.cli:main"
30
+
31
+ [project.urls]
32
+ Repository = "https://github.com/c0sogi/droidock"
33
+ Issues = "https://github.com/c0sogi/droidock/issues"
34
+
35
+ [dependency-groups]
36
+ dev = [
37
+ "pytest>=8,<10",
38
+ "ruff>=0.12",
39
+ "pyright>=1.1.408",
40
+ "isort>=6,<8",
41
+ ]
42
+
43
+ [tool.ruff]
44
+ line-length = 110
45
+
46
+ [tool.ruff.lint]
47
+ select = ["E", "F", "I", "UP", "B"]
48
+ ignore = ["E501"]
49
+
50
+ [tool.isort]
51
+ profile = "black"
52
+ line_length = 110
53
+ known_first_party = ["droidock"]
54
+ src_paths = ["src"]
55
+ skip_gitignore = true
56
+
57
+ [tool.pyright]
58
+ typeCheckingMode = "standard"
59
+ include = ["src", "tests", "scripts", "examples"]
60
+
61
+ [tool.pytest.ini_options]
62
+ testpaths = ["tests"]