Use Maven Central
Snap-O publishes its Android libraries to
Maven Central.
Most Android projects already include
mavenCentral(); add it to your dependency
sources if yours does not.
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
Add the Android dependencies
Add the real Tweaks implementation to debug builds and the matching no-op implementation to release builds. Both expose the same Compose functions. In release, each function returns observable state containing its default without shipping the live registry or server. The no-op artifacts remain the recommended release setup. The overlay dependencies are optional. Add both only if you want an on-device floating panel. Their matching public APIs let the same app-root code compile in debug and release.
[versions]
snapo = "3.2.0"
[libraries]
snapo-tweaks = { module = "com.openai.snapo:tweaks", version.ref = "snapo" }
snapo-tweaks-noop = { module = "com.openai.snapo:tweaks-noop", version.ref = "snapo" }
# Optional: add both if you want the in-app overlay panel.
snapo-tweaks-overlay = { module = "com.openai.snapo:tweaks-overlay", version.ref = "snapo" }
snapo-tweaks-overlay-noop = { module = "com.openai.snapo:tweaks-overlay-noop", version.ref = "snapo" }
dependencies {
debugImplementation(libs.snapo.tweaks)
releaseImplementation(libs.snapo.tweaks.noop)
// Optional: add both if you want the in-app overlay panel.
debugImplementation(libs.snapo.tweaks.overlay)
releaseImplementation(libs.snapo.tweaks.overlay.noop)
}
dependencies {
debugImplementation("com.openai.snapo:tweaks:3.2.0")
releaseImplementation("com.openai.snapo:tweaks-noop:3.2.0")
// Optional: add both if you want the in-app overlay panel.
debugImplementation("com.openai.snapo:tweaks-overlay:3.2.0")
releaseImplementation("com.openai.snapo:tweaks-overlay-noop:3.2.0")
}
Add the Tweaks dependencies to each Android module that calls
the tweak(...) function. In a multi-module app, a shared
Gradle convention plugin can apply the debug and release pair
consistently. Only the module that installs the optional
overlay needs its additional overlay dependencies.
If a real Tweaks artifact is included in a nondebuggable app, tweaks still return their defaults, the server does not start, and the floating overlay stays hidden unless you explicitly enable Tweaks for that app.
Enable Tweaks in release builds
Only when you intentionally include the real Tweaks
artifacts in a release build, add this metadata directly
to your application's <application>
element. It enables the Tweaks server and, if installed
and enabled, the on-device overlay.
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<meta-data
android:name="snapo.tweaks.allow_release"
android:value="true" />
</application>
</manifest>
This setting applies only to Tweaks.
snapo.network.allow_release controls Network
Inspector separately; enabling either feature does not
enable the other. Prefer no-op release artifacts unless
you need live inspection in a release build.
Expose values from Compose
Replace a fixed UI value with a tweak at the place that consumes
it. Snap-O registers the control while that composable is in
composition and returns observable State<T>
that updates as you edit its value.
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import com.openai.snapo.tweaks.tweak
@Composable
fun TypographyPreview() {
Text(
text = tweak("Make it feel right.", name = "Typography/Preview text").value,
fontSize = tweak(36, "Typography/Font size", 16..72).value.sp,
fontWeight = FontWeight(tweak(600, "Typography/Font weight", 100..900, step = 100).value),
color = tweak(Color(0xFF18212F), "Colors/Text").value,
)
}
Read each tweak close to its consumer when practical. You do not need to hoist every value to the top of a screen.
Compose function reference
Import com.openai.snapo.tweaks.tweak and call
the overload matching your default value from composition.
Every overload returns State<T>. Delegate
the state with by, or read
tweak(...).value immediately. Numeric ranges and
increments are optional. For strings, name the
name argument to distinguish it from the
default value.
@Composable
fun tweak(
default: Int,
name: String,
range: IntRange? = null,
step: Int? = null,
): State<Int>
@Composable
fun tweak(
default: Float,
name: String,
range: ClosedFloatingPointRange<Float>? = null,
step: Float? = null,
): State<Float>
@Composable
fun tweak(
default: Color,
name: String,
): State<Color>
@Composable
fun tweak(
default: Boolean,
name: String,
): State<Boolean>
@Composable
fun tweak(
default: String,
name: String,
): State<String>
Keep the returned state unread until the phase that needs it. For a value used only during drawing or layout, read the delegated value inside the draw or layout callback to avoid recomposing the caller.
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.openai.snapo.tweaks.tweak
@Composable
fun MotionTrack(modifier: Modifier = Modifier) {
val thickness by tweak(
default = 2f,
name = "Motion/Track thickness",
range = 1f..8f,
step = 0.5f,
)
Box(
modifier = modifier.drawBehind {
drawLine(
color = Color.Black,
start = Offset(x = 0f, y = center.y),
end = Offset(x = size.width, y = center.y),
strokeWidth = thickness.dp.toPx(),
)
},
)
}
Group tweaks by section (optional)
Tweaks work without sections. When grouping would make a
screen easier to inspect, use a slash-separated name to place
a control in a visible inspector section. For example,
Typography/Font size appears under
Typography, and
Motion/Duration appears under
Motion.
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.openai.snapo.tweaks.tweak
@Composable
fun MotionSection(isExpanded: Boolean) {
if (!tweak(true, "Motion/Show").value) return
val useSpring by tweak(true, "Motion/Use spring")
val animationSpec = if (useSpring) {
spring<Float>(
dampingRatio = tweak(0.7f, "Motion/Spring damping", 0.1f..1f, step = 0.05f).value,
stiffness = tweak(280f, "Motion/Spring stiffness", 80f..800f, step = 20f).value,
)
} else {
tween<Float>(
durationMillis = tweak(400, "Motion/Duration", 100..1500, step = 50).value,
easing = FastOutSlowInEasing,
)
}
AnimatedVisibility(
visible = isExpanded,
enter = fadeIn(animationSpec),
exit = fadeOut(animationSpec),
) {
Box(modifier = Modifier.size(48.dp)) {
// Animated content.
}
}
}
One name, one tweak. Reusing the exact same name in multiple composed consumers shares one control and current value. Use matching defaults and bounds for each use.
Controls appear only while their composables are in
composition. In the example, turning off
Motion/Show removes the motion controls; changing
Motion/Use spring swaps the spring settings for
the duration control. When the same UI returns during the app
process, its controls register again with their last edited
values and original ordering.
Interact with tweaks
Once the enabled app exposes a tweak, choose the interaction that fits your workflow: inspect the app on your Mac, use an optional panel on the device, call the REST API, or let an agent work with the same live values.
Mac App Inspector
Snap-O’s App Inspector discovers the running app with Tweaks enabled and shows the controls registered by its current Compose screen.
- Install and launch the debug build, or an explicitly enabled release build, on a connected, authorized Android device or emulator.
- Open Snap-O on macOS and select the device.
- Choose Tools → Show App Inspector, or press ⌘⌥I.
- Select your app and its Tweaks entry in the inspector picker.
- Adjust a slider, color, switch, or text field and watch the running Compose UI update.
- Reset an individual control or use Reset all tweaks to restore defaults.
Navigate through your Android app to change which controls are visible. Only tweaks belonging to the currently composed UI appear in the inspector.
On-device panel
For a developer-facing UI on the Android device, the optional
SnapOTweakOverlay starts as a movable floating
control and expands into an editable panel. Install it at the
app root and expose Snap-O’s built-in overlay setting from
your developer settings. The panel automatically observes
the same currently composed tweaks as the Mac inspector. See
Add an on-device floating panel
for the module and root wrapper.
REST API
The Tweaks server exposes a small HTTP API for reading currently composed controls, updating their values, and streaming changes. Use it to build your own inspection tools, integrations, and custom panels.
See the Tweaks protocol reference for every endpoint, example requests and responses, live events, resets, and error handling.
Agents (e.g. Codex)
Install the official Snap-O Codex plugin to give an agent the dedicated Tweaks skill and shared command-line client. The plugin requires Python 3 and Android Platform Tools.
codex plugin marketplace add openai/snap-o --ref main
codex plugin add snap-o@snap-o
Migrate an existing sparse marketplace installation
If Snap-O was previously installed with sparse paths, remove and add its marketplace again so the shared CLI and both plugin skills are available:
codex plugin marketplace remove snap-o
codex plugin marketplace add openai/snap-o --ref main
codex plugin add snap-o@snap-o
Start a new Codex session after installation. Ask the agent to inspect the available controls, apply a requested design direction, or reset a value. For example: “Make this screen feel calmer; try the typography, color, and motion tweaks and tell me what changed.” The skill discovers the running app, reads typed descriptors, and changes or resets values only when requested.
Snap-O for macOS also bundles the same CLI. Use it directly for discovery, automation, live snapshots, and explicitly requested updates:
SNAPO_BIN="/Applications/Snap-O.app/Contents/MacOS/snapo"
"$SNAPO_BIN" tweaks apps --json
"$SNAPO_BIN" tweaks list -s emulator-5554 -n snapo_tweaks_12345 --json
"$SNAPO_BIN" tweaks set 'Typography/Font size' 42 -s emulator-5554 -n snapo_tweaks_12345 --json
"$SNAPO_BIN" tweaks reset 'Typography/Font size' -s emulator-5554 -n snapo_tweaks_12345 --json
"$SNAPO_BIN" tweaks watch -s emulator-5554 -n snapo_tweaks_12345 --once --json
The CLI manages Android socket discovery, forwarding, and cleanup. Device serials and socket names come from the discovery output and change when the app process restarts.
You can also ask an agent to create a small custom panel for a
specific workflow, such as typography comparisons, animation
tuning, or a curated set of design controls. The panel can
read GET /tweaks, send
PATCH /tweaks, and subscribe to
GET /tweaks/events. No separate Snap-O agent API
or automatic panel integration is required or implied.
Apply tweaks to your codebase
Once you are happy with the adjustments you made in your app, ask an AI agent, such as Codex, to apply them to your codebase:
“Apply all the tweak adjustments I made in the app to my codebase.”
The agent can read the latest tweaked values from your running app and update the corresponding values in your source code so your adjustments become part of the app.
Optional: add an on-device floating panel
The optional overlay provides SnapOTweakOverlay
for apps that also want to edit live tweaks on the device.
Wrap your root content once. Snap-O owns and persists the
developer setting, and the overlay automatically discovers
tweaks from the current composition. Its matching no-op
artifact renders your app content unchanged in release, so
this code belongs in your shared source set.
import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.unit.sp
import com.openai.snapo.tweaks.overlay.SnapOTweakOverlay
import com.openai.snapo.tweaks.overlay.SnapOTweakOverlaySettings
import com.openai.snapo.tweaks.tweak
@Composable
fun AppRoot() {
SnapOTweakOverlay {
ProfileScreen()
}
}
@Composable
fun OverlayDeveloperSetting() {
Switch(
checked = SnapOTweakOverlaySettings.isEnabled,
onCheckedChange = { SnapOTweakOverlaySettings.isEnabled = it },
)
}
@Composable
fun ProfileScreen() {
val fontSize by tweak(
default = 16,
name = "Typography/Font size",
range = 12..32,
)
Text(
text = "Profile",
fontSize = fontSize.sp,
)
}
The panel starts as a collapsed floating button that can be moved anywhere on the screen. Tap it to expand the panel and edit integer, floating-point, boolean, color, or text tweaks. Reset an individual tweak or restore every tweak to its default. Changes update the running app immediately and are available to the Mac inspector through its normal updates. Snap-O saves the button's horizontal and vertical position, restoring it when the button returns or the app restarts.
Visibility follows the current Compose screen.
The button appears only when the real overlay is installed,
the Tweaks runtime is enabled,
SnapOTweakOverlaySettings.isEnabled is
true and at least one tweak is in composition.
The setting defaults to off and survives app restarts. As
screens and sections appear or disappear, the panel updates
automatically. The no-op overlay never appears, and its
setting remains disabled. No manually supplied values,
update callback, or build-specific app wrapper is required.
Troubleshooting
- Confirm the installed app contains the live
:tweaksartifact, not:tweaks-noop. - For a nondebuggable app, set
snapo.tweaks.allow_releasetotruein its<application>metadata. - If no Tweaks server appears, check device authorization, app startup, the live dependency, and whether the current build allows the runtime.
- If the server appears but its tweak list is empty, open a screen that calls
tweak(...); controls exist only while their composables are in composition. - Use the exact same name, default, and bounds when one control is read in multiple places.
- Choose your app’s Tweaks entry inside App Inspector.
- After the Android app restarts, select its current running app or process if prompted.
- For the optional overlay, confirm its developer setting is enabled and the current Compose screen contains at least one tweak.