1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
use clap::{Args, Parser, Subcommand};
use std::path::PathBuf;
/// palp: The fast, safe, and friendly mobile driver for AI coding assistants and developers.
#[derive(Parser, Debug)]
#[command(
name = "palp",
author = "Ralph Cajipe <ralphcajipe@gmail.com>",
version,
about = "A fast, safe, and friendly mobile screen driver for AI coding agents and developers",
long_about = "palp gives AI coding assistants (Google Antigravity, Claude Code, OpenAI Codex) \
and human developers sight and touch on real mobile phones and emulators.\n\n\
Eliminate repetitive manual screen tapping and empower autonomous AI testing."
)]
pub struct Cli {
/// Target a specific device serial or model name
#[arg(short, long, global = true, env = "PALP_DEVICE")]
pub device: Option<String>,
#[command(subcommand)]
pub command: Commands,
}
#[derive(Subcommand, Debug)]
pub enum Commands {
/// Run diagnostic check to verify ADB connection, device authorization, and display health
Doctor,
/// Measure device screen resolution, density, and system bar insets (status bar & gesture bar)
Probe(ProbeArgs),
/// Capture a clean in-app screenshot with status bar and navigation bars automatically removed
Snap(SnapArgs),
/// Tap a named shortcut (from .palprc.json), screen percentage (0.5,0.5), or physical pixels (540,1100)
Tap(TapArgs),
/// Find visible text, accessibility label, or button ID on screen and tap its center
TapText(TapTextArgs),
/// Wait until text appears on screen, or with --gone until it disappears
WaitText(WaitTextArgs),
/// Print the text and labels on screen with where to tap them (cheaper than a screenshot)
Texts(TextsArgs),
/// Tap the N-th text field from the top; fields rarely show their label to palp
TapField(TapFieldArgs),
/// Open an app by its package name, e.g. com.example.app
Launch(LaunchArgs),
/// Type text into the currently active input field
Type(TypeArgs),
/// Press hardware or navigation buttons (back, home, enter, tab, power)
Press(PressArgs),
/// Swipe across the screen: left, right, up, down, or coordinates (x1,y1 x2,y2 [ms])
Swipe(SwipeArgs),
/// Generate an AGENT_DEVICE.md instruction manual for AI coding assistants in this project
Init,
}
#[derive(Args, Debug)]
pub struct ProbeArgs {
/// Save probed screen resolution and insets into local .palprc.json
#[arg(long, short)]
pub save: bool,
}
#[derive(Args, Debug)]
pub struct SnapArgs {
/// Output file path for the clean screenshot (e.g., screenshot.png or preview)
#[arg(default_value = "screenshot.png")]
pub output: PathBuf,
/// Shrink the screenshot to this many pixels wide, keeping its proportions
#[arg(long, value_name = "PX", value_parser = clap::value_parser!(u32).range(1..))]
pub width: Option<u32>,
}
#[derive(Args, Debug)]
pub struct TapArgs {
/// Target to tap: named shortcut (e.g., btn:submit or tab:home), percentage (e.g., 0.5,0.5), or pixels (e.g., 540,1100)
pub target: String,
}
#[derive(Args, Debug)]
pub struct TapTextArgs {
/// Text, accessibility label, or testID to search for and tap
pub text: String,
/// Only whole-text matches count, not text that merely contains it
#[arg(long)]
pub exact: bool,
/// Tap the N-th match counted from the top of the screen, instead of letting palp choose
#[arg(long, value_name = "N")]
pub nth: Option<usize>,
/// Keep looking for this many seconds while the screen loads (0 = look once)
#[arg(long, value_name = "SECONDS", default_value_t = 5)]
pub timeout: u64,
}
#[derive(Args, Debug)]
pub struct TypeArgs {
/// Text to type into the active field
pub text: String,
}
#[derive(Args, Debug)]
pub struct PressArgs {
/// Key to press: back, home, enter, tab, power
pub key: String,
}
#[derive(Args, Debug)]
pub struct SwipeArgs {
/// Direction (left, right, up, down) or start coordinates (x1,y1)
pub direction_or_from: String,
/// End coordinates (x2,y2) if start coordinates were specified
pub to: Option<String>,
/// Duration of swipe in milliseconds
#[arg(default_value = "250")]
pub duration: u32,
}
#[derive(Args, Debug)]
pub struct WaitTextArgs {
/// Text or accessibility label to wait for
pub text: String,
/// Give up after this many seconds
#[arg(long, value_name = "SECONDS", default_value_t = 15)]
pub timeout: u64,
/// Wait for the text to disappear instead
#[arg(long)]
pub gone: bool,
/// Only whole-text matches count
#[arg(long)]
pub exact: bool,
}
#[derive(Args, Debug)]
pub struct TextsArgs {
/// Print JSON instead of lines
#[arg(long)]
pub json: bool,
}
#[derive(Args, Debug)]
pub struct TapFieldArgs {
/// Which field, counted from the top of the screen
#[arg(default_value_t = 1)]
pub n: usize,
/// Keep looking for this many seconds while the screen loads (0 = look once)
#[arg(long, value_name = "SECONDS", default_value_t = 5)]
pub timeout: u64,
}
#[derive(Args, Debug)]
pub struct LaunchArgs {
/// The app's package name, e.g. com.example.app
pub package: String,
}