droidsuite 1.0.0-beta.8 → 1.0.0-beta.9
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/COMMANDS.md +15 -12
- package/README.md +2 -2
- package/app/DroidSuite.apk +0 -0
- package/bin/droidsuite.mjs +40 -9
- package/install.ps1 +2 -4
- package/install.sh +2 -3
- package/package.json +1 -1
- package/skills/droidsuite/SKILL.md +3 -2
- package/src/capabilities.mjs +1 -1
- package/src/commands.mjs +11 -9
- package/src/debug.mjs +1 -1
- package/src/server.mjs +2 -0
- package/src/setup.mjs +38 -3
- package/src/skill.mjs +51 -17
- package/web/index.html +2 -2
package/COMMANDS.md
CHANGED
|
@@ -6,7 +6,7 @@ Run `droidsuite` with nothing after it for an interactive menu. Every command al
|
|
|
6
6
|
|
|
7
7
|
| Command | What it does |
|
|
8
8
|
|---|---|
|
|
9
|
-
| [`setup`](#setup) | First-run setup: get adb ready for Wi-Fi debugging,
|
|
9
|
+
| [`setup`](#setup) | First-run setup: add the DroidSuite icon, get adb ready for Wi-Fi debugging, offer the AI agent skill, show next steps |
|
|
10
10
|
| [`serve`](#serve) | Share APKs with your paired phones, push every new build to them, and swap files |
|
|
11
11
|
| [`open`](#open) | Open the DroidSuite page, starting the server if it isn't running (what the desktop icon does) |
|
|
12
12
|
| [`push`](#push) | Send any files to your paired phones (needs droidsuite serve running) |
|
|
@@ -22,21 +22,23 @@ Run `droidsuite` with nothing after it for an interactive menu. Every command al
|
|
|
22
22
|
| [`debug path`](#debug-path) | Add adb to your PATH so typing "adb" works in new terminals |
|
|
23
23
|
| [`adb`](#adb) | Run adb, even when it isn't on PATH |
|
|
24
24
|
| [`shortcut`](#shortcut) | Add a "DroidSuite" icon that starts the server and opens its page |
|
|
25
|
-
| [`skill`](#skill) | Teach AI coding agents (Claude Code and others) to use droidsuite, in every project |
|
|
25
|
+
| [`skill`](#skill) | Teach AI coding agents (Claude Code, Codex, Gemini CLI, Cursor and others) to use droidsuite, in every project |
|
|
26
26
|
| [`help`](#help) | Show all commands, or the details of one |
|
|
27
27
|
|
|
28
28
|
## setup
|
|
29
29
|
|
|
30
|
-
First-run setup: get adb ready for Wi-Fi debugging,
|
|
30
|
+
First-run setup: add the DroidSuite icon, get adb ready for Wi-Fi debugging, offer the AI agent skill, show next steps.
|
|
31
31
|
|
|
32
32
|
```
|
|
33
|
-
droidsuite setup [--yes] [--skip-adb]
|
|
33
|
+
droidsuite setup [--yes] [--skip-adb] [--no-shortcut]
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
| Option | Meaning |
|
|
37
37
|
|---|---|
|
|
38
38
|
| `-y, --yes` | Answer yes to every question (accepts the Platform-Tools license). |
|
|
39
39
|
| `--skip-adb` | Don't check or install adb (only needed for Debug over Wi-Fi). |
|
|
40
|
+
| `--no-shortcut` | Don't add the DroidSuite icon. |
|
|
41
|
+
| `--shortcut` | Add the icon even with no terminal to answer in. The install scripts use this. |
|
|
40
42
|
|
|
41
43
|
Examples:
|
|
42
44
|
|
|
@@ -44,7 +46,7 @@ Examples:
|
|
|
44
46
|
droidsuite setup
|
|
45
47
|
```
|
|
46
48
|
|
|
47
|
-
The install scripts run this for you. Asks before installing
|
|
49
|
+
The install scripts run this for you. Have Node.js and nothing installed yet? npx droidsuite setup installs DroidSuite too. Asks before installing adb, changing PATH or adding the skill; with no terminal to answer in, it skips.
|
|
48
50
|
|
|
49
51
|
## serve
|
|
50
52
|
|
|
@@ -337,7 +339,7 @@ No terminal window: the icon opens the page in your browser, and the page does e
|
|
|
337
339
|
|
|
338
340
|
## skill
|
|
339
341
|
|
|
340
|
-
Teach AI coding agents (Claude Code and others) to use droidsuite, in every project.
|
|
342
|
+
Teach AI coding agents (Claude Code, Codex, Gemini CLI, Cursor and others) to use droidsuite, in every project.
|
|
341
343
|
|
|
342
344
|
```
|
|
343
345
|
droidsuite skill [--project | --dir <folder>] [--print]
|
|
@@ -345,19 +347,20 @@ droidsuite skill [--project | --dir <folder>] [--print]
|
|
|
345
347
|
|
|
346
348
|
| Option | Meaning |
|
|
347
349
|
|---|---|
|
|
348
|
-
| `--project` | Install only into the current project
|
|
349
|
-
| `--global` | Every project on this computer
|
|
350
|
-
| `--dir <folder>` | Install into another skills folder, for
|
|
350
|
+
| `--project` | Install only into the current project instead of for every project on this computer. |
|
|
351
|
+
| `--global` | Every project on this computer. This is the default. |
|
|
352
|
+
| `--dir <folder>` | Install into another skills folder, for an agent that reads skills from somewhere else. |
|
|
351
353
|
| `--print` | Only print where the bundled skill is. |
|
|
352
354
|
|
|
353
355
|
Examples:
|
|
354
356
|
|
|
355
357
|
```bash
|
|
356
|
-
droidsuite skill # every project on this computer
|
|
357
|
-
droidsuite skill --project # only this project
|
|
358
|
+
droidsuite skill # every project on this computer, for every agent
|
|
359
|
+
droidsuite skill --project # only this project (commit it to share)
|
|
360
|
+
droidsuite skill --dir ~/.my-agent/skills
|
|
358
361
|
```
|
|
359
362
|
|
|
360
|
-
The skill shows an agent how to install builds on your phone, read logcat and screenshots over Wi-Fi, and move files, and which steps need you (pairing, Allow, PIN). Run it again after updating droidsuite.
|
|
363
|
+
The installer offers this when it finds an AI agent on your computer. Always installs into .agents/skills and .claude/skills, and into the folders of Gemini CLI, Antigravity, Cursor, GitHub Copilot, Windsurf, Qwen Code and Amp when they are on this computer. The skill shows an agent how to install builds on your phone, read logcat and screenshots over Wi-Fi, and move files, and which steps need you (pairing, Allow, PIN). Run it again after updating droidsuite or adding an agent.
|
|
361
364
|
|
|
362
365
|
## help
|
|
363
366
|
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ powershell -NoExit -ExecutionPolicy Bypass -Command "irm https://cdn.jsdelivr.ne
|
|
|
10
10
|
# macOS / Linux
|
|
11
11
|
curl -fsSL https://cdn.jsdelivr.net/npm/droidsuite/install.sh | sh
|
|
12
12
|
# or, with Node.js 20+ already installed
|
|
13
|
-
|
|
13
|
+
npx droidsuite setup
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
```bash
|
|
@@ -28,7 +28,7 @@ droidsuite debug install # install adb (Google platform-tools) if you don't h
|
|
|
28
28
|
droidsuite debug path # add adb to your PATH so typing adb works in new terminals
|
|
29
29
|
droidsuite adb devices # run adb even when it isn't on PATH
|
|
30
30
|
# http://localhost:8765 (while serve runs): see and use the phone's screen live
|
|
31
|
-
droidsuite skill # teach AI coding
|
|
31
|
+
droidsuite skill # teach your AI coding agent (Claude Code, Codex, Gemini CLI…) to use all of the above
|
|
32
32
|
```
|
|
33
33
|
|
|
34
34
|
Type `droidsuite` on its own for a menu. All commands and options: [COMMANDS.md](https://cdn.jsdelivr.net/npm/droidsuite/COMMANDS.md) or `droidsuite help <command>`.
|
package/app/DroidSuite.apk
CHANGED
|
Binary file
|
package/bin/droidsuite.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseArgs } from 'node:util';
|
|
3
|
-
import { spawn } from 'node:child_process';
|
|
3
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
4
4
|
import { existsSync, mkdirSync, openSync, readFileSync } from 'node:fs';
|
|
5
5
|
import { stat } from 'node:fs/promises';
|
|
6
6
|
import { dirname, join, resolve, relative } from 'node:path';
|
|
@@ -15,7 +15,7 @@ import { advertise, discoverPhones } from '../src/mdns.mjs';
|
|
|
15
15
|
import { findAdb } from '../src/adb.mjs';
|
|
16
16
|
import { autoConnectAdb, DebugService } from '../src/debugservice.mjs';
|
|
17
17
|
import { LICENSE_URL, toolsHome } from '../src/platformtools.mjs';
|
|
18
|
-
import { runSetup } from '../src/setup.mjs';
|
|
18
|
+
import { isTemporaryRun, runSetup } from '../src/setup.mjs';
|
|
19
19
|
import { CODE_HELP, connectAddress, newSetupSession, parseAddress, runSetup as runDebugSetup, SETUP_HELP, SETUP_STEPS, setupInput } from '../src/debug.mjs';
|
|
20
20
|
import { hello, helloPaired, normalizeTarget, sendFile } from '../src/send.mjs';
|
|
21
21
|
import { findServe, pushFile } from '../src/push.mjs';
|
|
@@ -25,7 +25,7 @@ import { commandHelp, findCommand, markdown, overview } from '../src/commands.mj
|
|
|
25
25
|
import { runMenu } from '../src/menu.mjs';
|
|
26
26
|
import { createShortcuts } from '../src/shortcut.mjs';
|
|
27
27
|
import { openUrl } from '../src/browser.mjs';
|
|
28
|
-
import { installSkill, SKILL_SOURCE } from '../src/skill.mjs';
|
|
28
|
+
import { hasAgents, installSkill, SKILL_SOURCE } from '../src/skill.mjs';
|
|
29
29
|
|
|
30
30
|
const { version: VERSION } = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
|
|
31
31
|
const CLI_PATH = fileURLToPath(import.meta.url);
|
|
@@ -442,10 +442,40 @@ async function adbPassthrough(argv) {
|
|
|
442
442
|
process.exitCode = await new Promise((ok) => child.on('close', (code) => ok(code ?? 1)));
|
|
443
443
|
}
|
|
444
444
|
/** `droidsuite setup`: first-run wizard (the install scripts run it). */
|
|
445
|
+
/**
|
|
446
|
+
* `npx droidsuite setup` runs from a temporary copy, and an icon made from it would break when npx cleans up.
|
|
447
|
+
* So it installs DroidSuite for good, then hands over to that copy. Returns false if the install failed.
|
|
448
|
+
*/
|
|
449
|
+
function installThenSetup(argv) {
|
|
450
|
+
console.log('Installing DroidSuite on this computer...');
|
|
451
|
+
const onWindows = process.platform === 'win32';
|
|
452
|
+
const npm = (...args) => spawnSync('npm', args, { shell: onWindows, encoding: 'utf8', stdio: ['inherit', 'pipe', 'inherit'] });
|
|
453
|
+
const installed = npm('install', '--global', process.env.DROIDSUITE_PACKAGE ?? `droidsuite@${VERSION}`, '--no-fund', '--no-audit', '--loglevel=error');
|
|
454
|
+
const root = installed.status === 0 ? npm('root', '--global').stdout?.trim() : null;
|
|
455
|
+
if (!root) {
|
|
456
|
+
console.error(`${c.red('✗')} Couldn't install DroidSuite for this computer (often a permissions issue). See: https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally`);
|
|
457
|
+
return false;
|
|
458
|
+
}
|
|
459
|
+
const run = spawnSync(process.execPath, [join(root, 'droidsuite', 'bin', 'droidsuite.mjs'), 'setup', ...argv], { stdio: 'inherit' });
|
|
460
|
+
process.exitCode = run.status ?? 1;
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
|
|
445
464
|
async function setup(argv) {
|
|
446
|
-
|
|
465
|
+
if (isTemporaryRun(CLI_PATH)) {
|
|
466
|
+
if (!installThenSetup(argv)) process.exitCode = 1;
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
const { values } = parseArgs({ args: argv, options: { yes: { type: 'boolean', short: 'y' }, 'skip-adb': { type: 'boolean' }, shortcut: { type: 'boolean' }, 'no-shortcut': { type: 'boolean' } } });
|
|
470
|
+
// With no terminal (CI, a script) nobody asked for an icon, unless --yes or --shortcut says so.
|
|
471
|
+
const wantsIcon = !values['no-shortcut'] && (values.shortcut || values.yes || process.stdin.isTTY);
|
|
447
472
|
const result = await runSetup({
|
|
448
473
|
service: debugService,
|
|
474
|
+
skill: { present: hasAgents, install: installSkill },
|
|
475
|
+
shortcut: wantsIcon ? {
|
|
476
|
+
create: () => createShortcuts({ nodePath: process.execPath, cliPath: CLI_PATH, desktop: true }),
|
|
477
|
+
where: { win32: 'on your desktop and in the Start menu', darwin: 'on your desktop' }[process.platform] ?? 'in your applications menu',
|
|
478
|
+
} : undefined,
|
|
449
479
|
say: (line) => console.log(line.startsWith('✓') ? c.green('✓') + line.slice(1) : line.startsWith('✗') ? c.red('✗') + line.slice(1) : line),
|
|
450
480
|
confirm: async (q, def) => {
|
|
451
481
|
// Nobody to ask (piped input, CI): never accept a license or change PATH on someone's behalf.
|
|
@@ -508,14 +538,15 @@ async function skill(argv) {
|
|
|
508
538
|
// --global is the default now; still accepted so older instructions keep working.
|
|
509
539
|
const { values } = parseArgs({ args: argv, options: { project: { type: 'boolean' }, global: { type: 'boolean' }, dir: { type: 'string' }, print: { type: 'boolean' } } });
|
|
510
540
|
if (values.print) return console.log(SKILL_SOURCE);
|
|
511
|
-
const {
|
|
512
|
-
console.log(`${c.green('✓')} ${updated ? 'Updated' : 'Added'} the droidsuite skill
|
|
513
|
-
|
|
541
|
+
const { targets, updated, alsoProject } = installSkill({ project: values.project, dir: values.dir });
|
|
542
|
+
console.log(`${c.green('✓')} ${updated ? 'Updated' : 'Added'} the droidsuite skill:`);
|
|
543
|
+
for (const t of targets) console.log(` ${t.path}${t.agents ? c.dim(` (${t.agents})`) : ''}`);
|
|
544
|
+
for (const p of alsoProject) console.log(`${c.green('✓')} Also updated this project's older copy: ${p}`);
|
|
514
545
|
console.log(c.dim(values.project
|
|
515
|
-
? ' Commit
|
|
546
|
+
? ' Commit these, and every AI agent working on this project knows how to use your phone.'
|
|
516
547
|
: values.dir
|
|
517
548
|
? ' Agents that read this folder can now install builds on the phone, read logcat, and move files.'
|
|
518
|
-
: '
|
|
549
|
+
: ' AI agents now know how to use your phone in every project on this computer. Added a new agent later? Run this again.\n Agent not listed? droidsuite skill --dir <its skills folder>'));
|
|
519
550
|
}
|
|
520
551
|
|
|
521
552
|
function help(argv) {
|
package/install.ps1
CHANGED
|
@@ -44,10 +44,8 @@
|
|
|
44
44
|
$prefix = (& npm prefix --global).Trim()
|
|
45
45
|
if (($env:Path -split ';') -notcontains $prefix) { $env:Path = "$env:Path;$prefix" }
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# One click from now on: the DroidSuite icon starts the server and opens its page.
|
|
50
|
-
& droidsuite shortcut --desktop
|
|
47
|
+
# Also adds the icon: one click from now on, it starts the server and opens its page.
|
|
48
|
+
& droidsuite setup --shortcut
|
|
51
49
|
|
|
52
50
|
# Open it now: the page comes up in the browser, the rest happens there.
|
|
53
51
|
& droidsuite open
|
package/install.sh
CHANGED
|
@@ -55,9 +55,8 @@ fi
|
|
|
55
55
|
|
|
56
56
|
PATH="$PATH:$(npm prefix --global)/bin"
|
|
57
57
|
if has_tty; then
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
droidsuite shortcut || true
|
|
58
|
+
# Also adds the icon. One click from now on: macOS gets "DroidSuite" on the desktop, Linux in the applications menu.
|
|
59
|
+
droidsuite setup --shortcut < /dev/tty
|
|
61
60
|
# Open it now: the page comes up in the browser, the rest happens there.
|
|
62
61
|
if droidsuite open; then
|
|
63
62
|
echo ""
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "droidsuite",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.9",
|
|
4
4
|
"description": "Your Android phone on your computer, no cable: see and control its screen, type on it, move files and clipboard both ways, install builds and debug over Wi-Fi.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"android",
|
|
@@ -156,7 +156,8 @@ node <this-skill>/scripts/droidsuite-status.mjs --wait-build 120 --match debug
|
|
|
156
156
|
in Receive mode, where the APK is kept). Sending the build again isn't needed.
|
|
157
157
|
- An app installed by adb (Path A) is still updated by Path B; only that first silent update may ask once.
|
|
158
158
|
- With Wireless debugging also connected (Path A), `serve` installs each build through adb itself: no first-install
|
|
159
|
-
tap, and
|
|
159
|
+
tap (except on Xiaomi, Redmi and POCO, which ask on every adb install), and it works where Android's developer
|
|
160
|
+
verification blocks apps from unregistered developers (adb is exempt).
|
|
160
161
|
The status output shows `"adb": "<serial>"` on the phone then. If installs stall on the phone in Brazil, Indonesia,
|
|
161
162
|
Singapore or Thailand (or anywhere from 2027), set up Path A and keep `serve` running.
|
|
162
163
|
- If adb refuses the install, `serve` falls back to the phone by itself and the person gets the install prompt:
|
|
@@ -197,7 +198,7 @@ node <this-skill>/scripts/droidsuite-status.mjs --wait-build 120 --match debug
|
|
|
197
198
|
| `no phone with Wireless debugging found` | Wireless debugging is off, or another network | Ask to switch it on (Path A); then `droidsuite debug doctor` |
|
|
198
199
|
| `Pairing didn't work: nothing answered at …` | Wrong IP address & Port typed for pairing | Ask for the line under the Wi-Fi pairing code |
|
|
199
200
|
| `Paired, but the phone can't be found` | Network blocks discovery (mDNS) | Ask for the IP address & Port at the top of Wireless debugging; `droidsuite debug connect` |
|
|
200
|
-
| `INSTALL_FAILED_USER_RESTRICTED` | The phone refused an adb install: locked, a prompt timed out, or Xiaomi's **Install via USB** is off |
|
|
201
|
+
| `INSTALL_FAILED_USER_RESTRICTED` | The phone refused an adb install: locked, a prompt timed out (Xiaomi gives about 10 seconds), or Xiaomi's **Install via USB** is off | Don't retry straight away. Say "Unlock the phone and be ready to tap **Install**", wait for their reply, then retry. On Xiaomi, Redmi and POCO, **Developer options → Install via USB** must be on, and each adb install still shows that prompt. If they'd rather not turn it on, or nobody is at the phone, use Path B: the phone app installs it, and the person confirms there |
|
|
201
202
|
| Phone: `Wrong code` when pairing by typing | Old or mistyped code | Read `serve.typing.code` again from the status output |
|
|
202
203
|
| Phone: `Needs Android X` on a build | Build's minSdk is above the phone's Android | Tell the person; it can't install there |
|
|
203
204
|
| Phone shows **Can't reach** / builds never appear | Different Wi-Fi, VPN, or firewall blocking port 8765 | Check `lanAddresses` in the status output is the Wi-Fi IP; ask about guest Wi-Fi/VPN |
|
package/src/capabilities.mjs
CHANGED
|
@@ -40,7 +40,7 @@ export function capabilities(sdk) {
|
|
|
40
40
|
? { id: 'files', label: 'Files', status: 'ok', note: 'Saved to Downloads/DroidSuite.', fix: null }
|
|
41
41
|
: { id: 'files', label: 'Files', status: 'limited', note: "Saved in DroidSuite's own folder.", fix: 'Open them from the DroidSuite app.' },
|
|
42
42
|
sdk >= NOTIFY_PERMISSION_SDK
|
|
43
|
-
? { id: 'notify', label: 'Notifications', status: 'ok', note: '
|
|
43
|
+
? { id: 'notify', label: 'Notifications', status: 'ok', note: 'Need your OK.', fix: 'Allow notifications when Android asks, or install prompts can\'t reach you.' }
|
|
44
44
|
: { id: 'notify', label: 'Notifications', status: 'ok', note: 'On.', fix: null },
|
|
45
45
|
];
|
|
46
46
|
}
|
package/src/commands.mjs
CHANGED
|
@@ -5,14 +5,16 @@ import { DEFAULT_PORT } from './config.mjs';
|
|
|
5
5
|
export const COMMANDS = [
|
|
6
6
|
{
|
|
7
7
|
name: 'setup',
|
|
8
|
-
summary: 'First-run setup: get adb ready for Wi-Fi debugging,
|
|
9
|
-
usage: 'droidsuite setup [--yes] [--skip-adb]',
|
|
8
|
+
summary: 'First-run setup: add the DroidSuite icon, get adb ready for Wi-Fi debugging, offer the AI agent skill, show next steps',
|
|
9
|
+
usage: 'droidsuite setup [--yes] [--skip-adb] [--no-shortcut]',
|
|
10
10
|
options: [
|
|
11
11
|
['-y, --yes', 'Answer yes to every question (accepts the Platform-Tools license).'],
|
|
12
12
|
['--skip-adb', "Don't check or install adb (only needed for Debug over Wi-Fi)."],
|
|
13
|
+
['--no-shortcut', "Don't add the DroidSuite icon."],
|
|
14
|
+
['--shortcut', 'Add the icon even with no terminal to answer in. The install scripts use this.'],
|
|
13
15
|
],
|
|
14
16
|
examples: ['droidsuite setup'],
|
|
15
|
-
notes: 'The install scripts run this for you. Asks before installing
|
|
17
|
+
notes: 'The install scripts run this for you. Have Node.js and nothing installed yet? npx droidsuite setup installs DroidSuite too. Asks before installing adb, changing PATH or adding the skill; with no terminal to answer in, it skips.',
|
|
16
18
|
},
|
|
17
19
|
{
|
|
18
20
|
name: 'serve',
|
|
@@ -159,16 +161,16 @@ export const COMMANDS = [
|
|
|
159
161
|
},
|
|
160
162
|
{
|
|
161
163
|
name: 'skill',
|
|
162
|
-
summary: 'Teach AI coding agents (Claude Code and others) to use droidsuite, in every project',
|
|
164
|
+
summary: 'Teach AI coding agents (Claude Code, Codex, Gemini CLI, Cursor and others) to use droidsuite, in every project',
|
|
163
165
|
usage: 'droidsuite skill [--project | --dir <folder>] [--print]',
|
|
164
166
|
options: [
|
|
165
|
-
['--project', 'Install only into the current project
|
|
166
|
-
['--global', 'Every project on this computer
|
|
167
|
-
['--dir <folder>', 'Install into another skills folder, for
|
|
167
|
+
['--project', 'Install only into the current project instead of for every project on this computer.'],
|
|
168
|
+
['--global', 'Every project on this computer. This is the default.'],
|
|
169
|
+
['--dir <folder>', 'Install into another skills folder, for an agent that reads skills from somewhere else.'],
|
|
168
170
|
['--print', 'Only print where the bundled skill is.'],
|
|
169
171
|
],
|
|
170
|
-
examples: ['droidsuite skill # every project on this computer
|
|
171
|
-
notes: 'The skill shows an agent how to install builds on your phone, read logcat and screenshots over Wi-Fi, and move files, and which steps need you (pairing, Allow, PIN). Run it again after updating droidsuite.',
|
|
172
|
+
examples: ['droidsuite skill # every project on this computer, for every agent', 'droidsuite skill --project # only this project (commit it to share)', 'droidsuite skill --dir ~/.my-agent/skills'],
|
|
173
|
+
notes: 'The installer offers this when it finds an AI agent on your computer. Always installs into .agents/skills and .claude/skills, and into the folders of Gemini CLI, Antigravity, Cursor, GitHub Copilot, Windsurf, Qwen Code and Amp when they are on this computer. The skill shows an agent how to install builds on your phone, read logcat and screenshots over Wi-Fi, and move files, and which steps need you (pairing, Allow, PIN). Run it again after updating droidsuite or adding an agent.',
|
|
172
174
|
},
|
|
173
175
|
{
|
|
174
176
|
name: 'help',
|
package/src/debug.mjs
CHANGED
|
@@ -414,7 +414,7 @@ export async function diagnose(deps, { phones = [], ownMdns = true, platform = p
|
|
|
414
414
|
];
|
|
415
415
|
const verdict = (text, next = null) => ({ checks, verdict: text, next });
|
|
416
416
|
|
|
417
|
-
if (wifi.some((d) => d.state === 'device')) return verdict('Connected. Builds install
|
|
417
|
+
if (wifi.some((d) => d.state === 'device')) return verdict('Connected. Builds install through adb. Xiaomi, Redmi and POCO phones show a prompt to tap each time.');
|
|
418
418
|
const old = live.find((p) => p.sdk && p.sdk < WIRELESS_DEBUG_SDK);
|
|
419
419
|
if (old && !seenIps.length && !live.some((p) => p.sdk >= WIRELESS_DEBUG_SDK)) {
|
|
420
420
|
return verdict(`${old.name} has ${androidName(old.sdk)}. Wireless debugging needs Android 11.`, 'Plug in a USB cable once (USB debugging on), then run: droidsuite debug usb');
|
package/src/server.mjs
CHANGED
|
@@ -265,6 +265,8 @@ export function createBridgeServer({
|
|
|
265
265
|
|
|
266
266
|
const heartbeat = setInterval(() => {
|
|
267
267
|
for (const res of [...phones.keys(), ...pages]) res.write(': ping\n\n');
|
|
268
|
+
// Notices Wireless debugging connected or dropped from outside (`droidsuite debug`, Android Studio): route and name follow.
|
|
269
|
+
for (const [res, phone] of phones) resolveAdb(res, phone).catch(() => {});
|
|
268
270
|
pruneCodeFailures();
|
|
269
271
|
if (Date.now() > typingExpiresAt) changeTypingCode('Typing code expired');
|
|
270
272
|
}, 20000).unref();
|
package/src/setup.mjs
CHANGED
|
@@ -3,12 +3,17 @@
|
|
|
3
3
|
import { dirname } from 'node:path';
|
|
4
4
|
import { LICENSE_URL, toolsHome } from './platformtools.mjs';
|
|
5
5
|
|
|
6
|
+
/** True when this copy of the program is a temporary one: `npx`, `pnpm dlx`. */
|
|
7
|
+
export const isTemporaryRun = (cliPath) => /[\\/](_npx|dlx-[^\\/]*)[\\/]/.test(cliPath);
|
|
8
|
+
|
|
6
9
|
/**
|
|
7
10
|
* @param {object} deps
|
|
8
11
|
* @param {import('./debugservice.mjs').DebugService} deps.service
|
|
9
12
|
* @param {(question: string, defaultYes: boolean) => Promise<boolean>} deps.confirm
|
|
10
13
|
* @param {(line: string) => void} deps.say
|
|
11
14
|
* @param {(ms: number) => Promise<void>} [deps.sleep]
|
|
15
|
+
* @param {{ present: () => boolean, install: () => { targets: object[], updated: boolean } }} [deps.skill]
|
|
16
|
+
* @param {{ create: () => Promise<unknown>, where: string }} [deps.shortcut] Left out when no icon is wanted.
|
|
12
17
|
* @param {{ yes?: boolean, skipAdb?: boolean }} [opts]
|
|
13
18
|
* Resolves to a summary: { adb: 'ready'|'installed'|'skipped'|'failed', path: 'ok'|'added'|'skipped'|'failed' }.
|
|
14
19
|
*/
|
|
@@ -16,7 +21,7 @@ export async function runSetup(deps, opts = {}) {
|
|
|
16
21
|
const { service, say } = deps;
|
|
17
22
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
18
23
|
const ask = (q, def) => (opts.yes ? Promise.resolve(true) : deps.confirm(q, def));
|
|
19
|
-
const result = { adb: 'skipped', path: 'skipped' };
|
|
24
|
+
const result = { adb: 'skipped', path: 'skipped', skill: 'skipped', shortcut: 'skipped' };
|
|
20
25
|
|
|
21
26
|
say('DroidSuite setup');
|
|
22
27
|
say('');
|
|
@@ -76,13 +81,43 @@ export async function runSetup(deps, opts = {}) {
|
|
|
76
81
|
}
|
|
77
82
|
}
|
|
78
83
|
|
|
84
|
+
// Only for people who have an AI coding agent: nobody else needs these folders.
|
|
85
|
+
if (deps.skill?.present()) {
|
|
86
|
+
say('');
|
|
87
|
+
if (await ask('Found an AI coding agent on this computer. Let it use your phone? (adds the droidsuite skill to its folder)', true)) {
|
|
88
|
+
try {
|
|
89
|
+
const { targets, updated } = deps.skill.install();
|
|
90
|
+
say(`✓ ${updated ? 'Updated' : 'Added'} the droidsuite skill for your AI agents (${targets.length} folders).`);
|
|
91
|
+
result.skill = 'installed';
|
|
92
|
+
} catch (err) {
|
|
93
|
+
say(`✗ Couldn't add the skill: ${err.message}. Try again later with: droidsuite skill`);
|
|
94
|
+
result.skill = 'failed';
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
say(' Skipped. Add it later with: droidsuite skill');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// One click from now on. It only adds an icon, so unlike adb it doesn't ask.
|
|
102
|
+
if (deps.shortcut) {
|
|
103
|
+
say('');
|
|
104
|
+
try {
|
|
105
|
+
await deps.shortcut.create();
|
|
106
|
+
say(`✓ Added the DroidSuite icon: ${deps.shortcut.where}.`);
|
|
107
|
+
result.shortcut = 'added';
|
|
108
|
+
} catch (err) {
|
|
109
|
+
say(`✗ Couldn't add the icon: ${err.message}. Try again later with: droidsuite shortcut --desktop`);
|
|
110
|
+
result.shortcut = 'failed';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
79
114
|
say('');
|
|
80
115
|
say('Next:');
|
|
81
|
-
say(
|
|
116
|
+
say(` 1. Open "DroidSuite" (${result.shortcut === 'added' ? 'the icon' : 'run: droidsuite open'}). In an Android project: droidsuite serve`);
|
|
82
117
|
say(' 2. No app on the phone yet? Open the address serve prints (http://<this computer>:8765/app)');
|
|
83
118
|
say(' on the phone, or scan "Get the phone app" at http://localhost:8765.');
|
|
84
119
|
say(' 3. In the app tap Pair and scan the pairing QR code. Turn on Live.');
|
|
85
120
|
say(' Debug over Wi-Fi: open http://localhost:8765 while serve runs, or run "droidsuite debug setup".');
|
|
86
|
-
say(' Use an AI coding agent such as Claude Code? Run "droidsuite skill" once, so it can install and check your app on the phone.');
|
|
121
|
+
if (result.skill !== 'installed') say(' Use an AI coding agent such as Claude Code, Codex or Gemini CLI? Run "droidsuite skill" once, so it can install and check your app on the phone.');
|
|
87
122
|
return result;
|
|
88
123
|
}
|
package/src/skill.mjs
CHANGED
|
@@ -8,15 +8,48 @@ export const SKILL_NAME = 'droidsuite';
|
|
|
8
8
|
const OLD_SKILL_NAME = 'apk-bridge';
|
|
9
9
|
export const SKILL_SOURCE = fileURLToPath(new URL(`../skills/${SKILL_NAME}`, import.meta.url));
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Every agent's skills folder, relative to the home folder (every project) or the project (--project).
|
|
13
|
+
* The first two always get a copy: most agents read one of them. Checked with the real programs:
|
|
14
|
+
* .agents/skills Codex (home and project), Antigravity and OpenCode (project)
|
|
15
|
+
* .claude/skills Claude Code, OpenCode
|
|
16
|
+
* .gemini/skills Antigravity (home)
|
|
17
|
+
* The rest come from each agent's documentation. They get a copy only when `when` already exists, so nobody ends up
|
|
18
|
+
* with folders for programs they don't have.
|
|
19
|
+
*/
|
|
20
|
+
const AGENT_FOLDERS = [
|
|
21
|
+
{ skills: ['.agents', 'skills'], agents: 'Codex, and agents that share this folder' },
|
|
22
|
+
{ skills: ['.claude', 'skills'], agents: 'Claude Code, OpenCode' },
|
|
23
|
+
{ when: ['.gemini'], skills: ['.gemini', 'skills'], agents: 'Gemini CLI, Antigravity' },
|
|
24
|
+
{ when: ['.cursor'], skills: ['.cursor', 'skills'], agents: 'Cursor' },
|
|
25
|
+
{ when: ['.copilot'], skills: ['.copilot', 'skills'], agents: 'GitHub Copilot' },
|
|
26
|
+
{ when: ['.codeium', 'windsurf'], skills: ['.codeium', 'windsurf', 'skills'], agents: 'Windsurf' },
|
|
27
|
+
{ when: ['.qwen'], skills: ['.qwen', 'skills'], agents: 'Qwen Code' },
|
|
28
|
+
{ when: ['.config', 'agents'], skills: ['.config', 'agents', 'skills'], agents: 'Amp' },
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
/** The folders to install into under [base], each with the agents that read it. */
|
|
32
|
+
function foldersUnder(base) {
|
|
33
|
+
return AGENT_FOLDERS
|
|
34
|
+
.filter((f) => !f.when || existsSync(join(base, ...f.when)))
|
|
35
|
+
.map((f) => ({ path: join(base, ...f.skills, SKILL_NAME), agents: f.agents }));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** True when any AI coding agent has left its folder in [home], so setup only asks people who use one. */
|
|
39
|
+
export function hasAgents({ home = homedir() } = {}) {
|
|
40
|
+
const others = [['.codex'], ['.config', 'opencode']]; // these read the shared folders, and have no folder of their own above
|
|
41
|
+
return [...AGENT_FOLDERS.map((f) => f.when ?? [f.skills[0]]), ...others].some((p) => existsSync(join(home, ...p)));
|
|
42
|
+
}
|
|
43
|
+
|
|
11
44
|
/**
|
|
12
45
|
* Where the skill goes:
|
|
13
|
-
* - default:
|
|
14
|
-
* - project:
|
|
46
|
+
* - default: every agent's folder in the home folder, so agents know DroidSuite in every project on this computer
|
|
47
|
+
* - project: the same folders in <cwd> (commit them so everyone working on the project has it)
|
|
15
48
|
* - dir: `<dir>/droidsuite`, for agents that read skills from somewhere else
|
|
16
49
|
*/
|
|
17
|
-
export function
|
|
18
|
-
if (dir) return join(resolve(cwd, dir), SKILL_NAME);
|
|
19
|
-
return
|
|
50
|
+
export function skillTargets({ project = false, dir = null, cwd = process.cwd(), home = homedir() } = {}) {
|
|
51
|
+
if (dir) return [{ path: join(resolve(cwd, dir), SKILL_NAME), agents: null }];
|
|
52
|
+
return foldersUnder(project ? cwd : home);
|
|
20
53
|
}
|
|
21
54
|
|
|
22
55
|
function copySkill(source, path) {
|
|
@@ -33,21 +66,22 @@ function removeOldName(path) {
|
|
|
33
66
|
}
|
|
34
67
|
|
|
35
68
|
/**
|
|
36
|
-
* Copies (or refreshes) the skill. Returns {
|
|
37
|
-
* alsoProject
|
|
38
|
-
*
|
|
69
|
+
* Copies (or refreshes) the skill. Returns { targets, updated, alsoProject }: updated when an older copy was replaced;
|
|
70
|
+
* alsoProject lists this project's own copies that a global install also refreshed (one left by an older
|
|
71
|
+
* `droidsuite skill` would otherwise shadow the new global one in this project).
|
|
39
72
|
*/
|
|
40
73
|
export function installSkill(options = {}, source = SKILL_SOURCE) {
|
|
41
|
-
const
|
|
42
|
-
const updated = existsSync(path);
|
|
43
|
-
copySkill(source, path);
|
|
44
|
-
|
|
74
|
+
const targets = skillTargets(options);
|
|
75
|
+
const updated = targets.some((t) => existsSync(t.path));
|
|
76
|
+
for (const t of targets) copySkill(source, t.path);
|
|
77
|
+
const alsoProject = [];
|
|
45
78
|
if (!options.project && !options.dir) {
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
79
|
+
const installed = new Set(targets.map((t) => t.path));
|
|
80
|
+
for (const t of skillTargets({ ...options, project: true })) {
|
|
81
|
+
if (installed.has(t.path) || !existsSync(t.path)) continue;
|
|
82
|
+
copySkill(source, t.path);
|
|
83
|
+
alsoProject.push(t.path);
|
|
50
84
|
}
|
|
51
85
|
}
|
|
52
|
-
return {
|
|
86
|
+
return { targets, updated, alsoProject };
|
|
53
87
|
}
|
package/web/index.html
CHANGED
|
@@ -473,7 +473,7 @@
|
|
|
473
473
|
<div id="old-phones"></div>
|
|
474
474
|
</div>
|
|
475
475
|
</div>
|
|
476
|
-
<div class="sub" id="agent-note" style="margin-top:18px">Use an AI coding agent such as Claude Code? Run <code class="mono">droidsuite skill</code> once in a terminal, so it can install and check your app on the phone.</div>
|
|
476
|
+
<div class="sub" id="agent-note" style="margin-top:18px">Use an AI coding agent such as Claude Code, Codex or Gemini CLI? Run <code class="mono">droidsuite skill</code> once in a terminal, so it can install and check your app on the phone.</div>
|
|
477
477
|
</section>
|
|
478
478
|
<p class="credit">DroidSuite · made by Mahmoud Helmy</p>
|
|
479
479
|
</div>
|
|
@@ -496,7 +496,7 @@
|
|
|
496
496
|
/** What this phone's Android version can't do (or needs), with the fix. */
|
|
497
497
|
const caps = (p) => {
|
|
498
498
|
const rows = (p.capabilities ?? []).filter((c) => c.status !== 'ok' || c.fix);
|
|
499
|
-
return rows.length ? `<div class="caps">${rows.map((c) => `<div>${c.status === '
|
|
499
|
+
return rows.length ? `<div class="caps">${rows.map((c) => `<div>${c.status === 'no' ? '✗' : '!'} <b>${esc(c.label)}:</b> ${esc(c.note)}${c.fix ? ` ${esc(c.fix)}` : ''}</div>`).join('')}</div>` : '';
|
|
500
500
|
};
|
|
501
501
|
/** "needs Android 12+" when a connected phone is older than the build's minSdk. */
|
|
502
502
|
const tooNewFor = (b, phones) => {
|