feat(platform-macos): port PermissionsGate CLI flow for first-launch UX (#1527) - #1529
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughWalkthroughThis PR brings the Swift ChangesmacOS First-Launch Permissions Gate for cua-driver-rs
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/content/docs/cua-driver/reference/cli-reference.mdx`:
- Line 163: Update the description for `--no-permissions-gate` to list all
accepted env-var disable values for CUA_DRIVER_RS_PERMISSIONS_GATE (e.g., 0,
false, no, off — case-insensitive) instead of only `=0`, so the text clearly
states that the gate can also be disabled via environment variable values like
`CUA_DRIVER_RS_PERMISSIONS_GATE=0|false|no|off` (or any case variant).
In `@libs/cua-driver-rs/crates/platform-macos/src/permissions/gate.rs`:
- Around line 310-311: print_banner currently always prints "Opening System
Settings → Privacy & Security now." which is misleading when run_if_needed is
called with open_settings=false; update print_banner (or its caller) to accept
an open_settings boolean (or otherwise check the open_settings flag) and only
print the "Opening System Settings…" line when open_settings is true, otherwise
print a message indicating that the user should manually open Settings or omit
that line; locate the banner logic in print_banner and the call site in
run_if_needed to pass the flag or conditionally call the variant that includes
the open-settings text.
- Around line 351-389: Tests mutate the global CUA_DRIVER_RS_PERMISSIONS_GATE
env var and can race when cargo runs tests in parallel; wrap all env-var
reads/sets in a global test mutex to serialize them. Add a static
OnceLock<Mutex<()>> (or Lazy/OnceCell) and acquire its MutexGuard at the start
of each test (env_var_disables_gate, flag_disables_gate,
neither_flag_nor_env_does_not_opt_out, env_var_truthy_values_do_not_opt_out)
before calling GateOpts::from_env_and_flag or mutating std::env so the
set_var/remove_var and assertions are executed under the lock, then drop the
guard before returning. Ensure the mutex is initialized once and used across
tests to prevent race conditions on the CUA_DRIVER_RS_PERMISSIONS_GATE
environment variable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6ea7b1d0-8d98-48bc-bb29-39db8d4fb293
⛔ Files ignored due to path filters (1)
libs/cua-driver-rs/Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (10)
docs/content/docs/cua-driver/guide/getting-started/installation.mdxdocs/content/docs/cua-driver/reference/cli-reference.mdxlibs/cua-driver-rs/PARITY.mdlibs/cua-driver-rs/crates/cua-driver/src/cli.rslibs/cua-driver-rs/crates/cua-driver/src/main.rslibs/cua-driver-rs/crates/platform-macos/src/lib.rslibs/cua-driver-rs/crates/platform-macos/src/permissions/gate.rslibs/cua-driver-rs/crates/platform-macos/src/permissions/mod.rslibs/cua-driver-rs/crates/platform-macos/src/permissions/status.rslibs/cua-driver-rs/crates/platform-macos/src/tools/check_permissions.rs
| | Name | Description | | ||
| | ---- | ----------- | | ||
| | `--no-relaunch` | Stay in the current process instead of re-execing via `open -n -g -a CuaDriver`. | | ||
| | `--no-permissions-gate` | Skip the macOS TCC permissions gate at startup. Use for CI / headless runners where blocking on user input would deadlock the process. Also toggleable via `CUA_DRIVER_RS_PERMISSIONS_GATE=0`. | |
There was a problem hiding this comment.
Document all accepted env-var disable values, not just =0.
Line 163 currently implies only CUA_DRIVER_RS_PERMISSIONS_GATE=0 disables the gate, while other docs also allow false, no, and off. Please align this line to avoid conflicting operator guidance.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/content/docs/cua-driver/reference/cli-reference.mdx` at line 163, Update
the description for `--no-permissions-gate` to list all accepted env-var disable
values for CUA_DRIVER_RS_PERMISSIONS_GATE (e.g., 0, false, no, off —
case-insensitive) instead of only `=0`, so the text clearly states that the gate
can also be disabled via environment variable values like
`CUA_DRIVER_RS_PERMISSIONS_GATE=0|false|no|off` (or any case variant).
| println!(" Opening System Settings → Privacy & Security now."); | ||
| println!(" Grant each item, then this prompt will auto-continue."); |
There was a problem hiding this comment.
Banner text is inaccurate when open_settings is disabled.
print_banner always says Settings is being opened, but run_if_needed can be configured with open_settings=false. Make this line conditional (or parameterize banner rendering) to avoid misleading output.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@libs/cua-driver-rs/crates/platform-macos/src/permissions/gate.rs` around
lines 310 - 311, print_banner currently always prints "Opening System Settings →
Privacy & Security now." which is misleading when run_if_needed is called with
open_settings=false; update print_banner (or its caller) to accept an
open_settings boolean (or otherwise check the open_settings flag) and only print
the "Opening System Settings…" line when open_settings is true, otherwise print
a message indicating that the user should manually open Settings or omit that
line; locate the banner logic in print_banner and the call site in run_if_needed
to pass the flag or conditionally call the variant that includes the
open-settings text.
| fn env_var_disables_gate() { | ||
| // Mutating the env in a test is generally suspect (parallel tests | ||
| // racing) but cargo runs unit tests in this crate sequentially and | ||
| // the var name is unique enough to avoid collisions. | ||
| std::env::set_var("CUA_DRIVER_RS_PERMISSIONS_GATE", "0"); | ||
| let opts = GateOpts::from_env_and_flag(false); | ||
| assert!(opts.opt_out, "env=0 must opt out"); | ||
| std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn flag_disables_gate() { | ||
| std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE"); | ||
| let opts = GateOpts::from_env_and_flag(true); | ||
| assert!(opts.opt_out, "--no-permissions-gate must opt out"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn neither_flag_nor_env_does_not_opt_out() { | ||
| std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE"); | ||
| let opts = GateOpts::from_env_and_flag(false); | ||
| assert!(!opts.opt_out); | ||
| } | ||
|
|
||
| #[test] | ||
| fn env_var_truthy_values_do_not_opt_out() { | ||
| // Only the explicit "off" sentinels disable the gate. Anything | ||
| // else (including empty string or unknown garbage) leaves the gate | ||
| // active — fail-safe default for first-launch UX. | ||
| for v in &["1", "true", "yes", "on", "garbage", ""] { | ||
| std::env::set_var("CUA_DRIVER_RS_PERMISSIONS_GATE", v); | ||
| let opts = GateOpts::from_env_and_flag(false); | ||
| assert!( | ||
| !opts.opt_out, | ||
| "env={v:?} must not opt out (only 0/false/no/off do)" | ||
| ); | ||
| } | ||
| std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE"); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
FILE="libs/cua-driver-rs/crates/platform-macos/src/permissions/gate.rs"
echo "== Env-var mutation sites in tests =="
rg -n --type rust 'std::env::set_var|std::env::remove_var' "$FILE"
echo
echo "== Existing serialization primitives in test module (if any) =="
rg -n --type rust 'Mutex|OnceLock|serial_test' "$FILE"
echo
echo "== Relevant test block context =="
nl -ba "$FILE" | sed -n '345,395p'Repository: trycua/cua
Length of output: 571
🏁 Script executed:
# Check for test parallelism configuration in the crate's Cargo.toml
fd -n Cargo.toml | xargs grep -l "cua-driver-rs-platform-macos" 2>/dev/null | head -5Repository: trycua/cua
Length of output: 285
🏁 Script executed:
# Check the actual Cargo.toml for the platform-macos crate
find . -path "*platform-macos/Cargo.toml" -type fRepository: trycua/cua
Length of output: 108
🏁 Script executed:
# Search for any other usages of the CUA_DRIVER_RS_PERMISSIONS_GATE env var in the entire codebase
rg -i "CUA_DRIVER_RS_PERMISSIONS_GATE" --type rustRepository: trycua/cua
Length of output: 1631
🏁 Script executed:
# Check if there are any test configuration attributes (like #[test] ordering) in the test module
rg -B5 -A15 "fn env_var_disables_gate" libs/cua-driver-rs/crates/platform-macos/src/permissions/gate.rs | head -30Repository: trycua/cua
Length of output: 856
🏁 Script executed:
cat -n ./libs/cua-driver-rs/crates/platform-macos/Cargo.tomlRepository: trycua/cua
Length of output: 1822
🏁 Script executed:
# Check if there's a global test configuration or workspace setting
rg -n "test-threads|profile.test" --type toml libs/cua-driver-rs/Repository: trycua/cua
Length of output: 36
🏁 Script executed:
# Check parent workspace Cargo.toml
find . -name "Cargo.toml" -path "*/cua-driver-rs/*" | head -3 | xargs catRepository: trycua/cua
Length of output: 2584
🏁 Script executed:
# Verify the suggested lock pattern is correct Rust syntax
cat > /tmp/test_lock.rs << 'EOF'
use std::sync::{Mutex, OnceLock};
fn env_lock() -> std::sync::MutexGuard<'static, ()> {
static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
LOCK.get_or_init(|| Mutex::new(())).lock().expect("env lock poisoned")
}
#[test]
fn test_lock() {
let _guard = env_lock();
println!("lock acquired");
}
EOF
# Just check if it's syntactically valid by trying to parse it
rustfmt --check /tmp/test_lock.rs 2>&1 | head -20 || echo "Syntax check completed"Repository: trycua/cua
Length of output: 101
🏁 Script executed:
# Double-check: Look at the exact test functions to see if there are any other synchronization mechanisms
rg -A20 "^mod.*test" libs/cua-driver-rs/crates/platform-macos/src/permissions/gate.rs | tail -50Repository: trycua/cua
Length of output: 835
Add test synchronization to prevent race conditions on env-var mutations.
The four test functions in this module mutate the global CUA_DRIVER_RS_PERMISSIONS_GATE environment variable without synchronization. Although the existing comment claims tests run sequentially in this crate, there's no Cargo.toml configuration enforcing this—Cargo runs tests in parallel by default. These tests will race and produce nondeterministic failures when run concurrently.
Use a static Mutex + OnceLock to serialize access across all four test functions:
Suggested implementation
+ use std::sync::{Mutex, OnceLock};
+
+ fn env_lock() -> std::sync::MutexGuard<'static, ()> {
+ static LOCK: OnceLock<Mutex<()>> = OnceLock::new();
+ LOCK.get_or_init(|| Mutex::new(())).lock().expect("env lock poisoned")
+ }
+
#[test]
fn env_var_disables_gate() {
+ let _guard = env_lock();
std::env::set_var("CUA_DRIVER_RS_PERMISSIONS_GATE", "0");
let opts = GateOpts::from_env_and_flag(false);
assert!(opts.opt_out, "env=0 must opt out");
std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE");
}
#[test]
fn flag_disables_gate() {
+ let _guard = env_lock();
std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE");
let opts = GateOpts::from_env_and_flag(true);
assert!(opts.opt_out, "--no-permissions-gate must opt out");
}
#[test]
fn neither_flag_nor_env_does_not_opt_out() {
+ let _guard = env_lock();
std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE");
let opts = GateOpts::from_env_and_flag(false);
assert!(!opts.opt_out);
}
#[test]
fn env_var_truthy_values_do_not_opt_out() {
+ let _guard = env_lock();
for v in &["1", "true", "yes", "on", "garbage", ""] {
std::env::set_var("CUA_DRIVER_RS_PERMISSIONS_GATE", v);
let opts = GateOpts::from_env_and_flag(false);
assert!(
!opts.opt_out,
"env={v:?} must not opt out (only 0/false/no/off do)"
);
}
std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE");
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| fn env_var_disables_gate() { | |
| // Mutating the env in a test is generally suspect (parallel tests | |
| // racing) but cargo runs unit tests in this crate sequentially and | |
| // the var name is unique enough to avoid collisions. | |
| std::env::set_var("CUA_DRIVER_RS_PERMISSIONS_GATE", "0"); | |
| let opts = GateOpts::from_env_and_flag(false); | |
| assert!(opts.opt_out, "env=0 must opt out"); | |
| std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE"); | |
| } | |
| #[test] | |
| fn flag_disables_gate() { | |
| std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE"); | |
| let opts = GateOpts::from_env_and_flag(true); | |
| assert!(opts.opt_out, "--no-permissions-gate must opt out"); | |
| } | |
| #[test] | |
| fn neither_flag_nor_env_does_not_opt_out() { | |
| std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE"); | |
| let opts = GateOpts::from_env_and_flag(false); | |
| assert!(!opts.opt_out); | |
| } | |
| #[test] | |
| fn env_var_truthy_values_do_not_opt_out() { | |
| // Only the explicit "off" sentinels disable the gate. Anything | |
| // else (including empty string or unknown garbage) leaves the gate | |
| // active — fail-safe default for first-launch UX. | |
| for v in &["1", "true", "yes", "on", "garbage", ""] { | |
| std::env::set_var("CUA_DRIVER_RS_PERMISSIONS_GATE", v); | |
| let opts = GateOpts::from_env_and_flag(false); | |
| assert!( | |
| !opts.opt_out, | |
| "env={v:?} must not opt out (only 0/false/no/off do)" | |
| ); | |
| } | |
| std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE"); | |
| } | |
| use std::sync::{Mutex, OnceLock}; | |
| fn env_lock() -> std::sync::MutexGuard<'static, ()> { | |
| static LOCK: OnceLock<Mutex<()>> = OnceLock::new(); | |
| LOCK.get_or_init(|| Mutex::new(())).lock().expect("env lock poisoned") | |
| } | |
| #[test] | |
| fn env_var_disables_gate() { | |
| let _guard = env_lock(); | |
| // Mutating the env in a test is generally suspect (parallel tests | |
| // racing) but cargo runs unit tests in this crate sequentially and | |
| // the var name is unique enough to avoid collisions. | |
| std::env::set_var("CUA_DRIVER_RS_PERMISSIONS_GATE", "0"); | |
| let opts = GateOpts::from_env_and_flag(false); | |
| assert!(opts.opt_out, "env=0 must opt out"); | |
| std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE"); | |
| } | |
| #[test] | |
| fn flag_disables_gate() { | |
| let _guard = env_lock(); | |
| std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE"); | |
| let opts = GateOpts::from_env_and_flag(true); | |
| assert!(opts.opt_out, "--no-permissions-gate must opt out"); | |
| } | |
| #[test] | |
| fn neither_flag_nor_env_does_not_opt_out() { | |
| let _guard = env_lock(); | |
| std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE"); | |
| let opts = GateOpts::from_env_and_flag(false); | |
| assert!(!opts.opt_out); | |
| } | |
| #[test] | |
| fn env_var_truthy_values_do_not_opt_out() { | |
| let _guard = env_lock(); | |
| // Only the explicit "off" sentinels disable the gate. Anything | |
| // else (including empty string or unknown garbage) leaves the gate | |
| // active — fail-safe default for first-launch UX. | |
| for v in &["1", "true", "yes", "on", "garbage", ""] { | |
| std::env::set_var("CUA_DRIVER_RS_PERMISSIONS_GATE", v); | |
| let opts = GateOpts::from_env_and_flag(false); | |
| assert!( | |
| !opts.opt_out, | |
| "env={v:?} must not opt out (only 0/false/no/off do)" | |
| ); | |
| } | |
| std::env::remove_var("CUA_DRIVER_RS_PERMISSIONS_GATE"); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@libs/cua-driver-rs/crates/platform-macos/src/permissions/gate.rs` around
lines 351 - 389, Tests mutate the global CUA_DRIVER_RS_PERMISSIONS_GATE env var
and can race when cargo runs tests in parallel; wrap all env-var reads/sets in a
global test mutex to serialize them. Add a static OnceLock<Mutex<()>> (or
Lazy/OnceCell) and acquire its MutexGuard at the start of each test
(env_var_disables_gate, flag_disables_gate,
neither_flag_nor_env_does_not_opt_out, env_var_truthy_values_do_not_opt_out)
before calling GateOpts::from_env_and_flag or mutating std::env so the
set_var/remove_var and assertions are executed under the lock, then drop the
guard before returning. Ensure the mutex is initialized once and used across
tests to prevent race conditions on the CUA_DRIVER_RS_PERMISSIONS_GATE
environment variable.
Three findings from the review on the permissions-gate-cli branch: 1. `print_banner` no longer claims "Opening System Settings…" when the caller passed `open_settings: false`. The flag is threaded through and the false branch prints the equivalent `open "x-apple.systempreferences:…"` command(s) instead, so users still know how to grant manually. 2. The 7 unit tests that mutate `CUA_DRIVER_RS_PERMISSIONS_GATE` now serialize through a static `OnceLock<Mutex<()>>`. Cargo runs unit tests in parallel by default, so without the lock the env-var tests race on a process-global. Standard pattern; recovers from mutex poisoning so a panicking test doesn't wedge the rest of the suite. 3. `CUA_DRIVER_RS_PERMISSIONS_GATE` matching is now case-insensitive (`0` / `false` / `no` / `off` in any case), and the CLI reference + installation docs + module/banner text all describe the full off-sentinel set instead of only mentioning `=0`. New test `env_var_off_sentinels_are_case_insensitive` covers the matrix. Verified: 8/8 platform-macos::permissions tests pass across 5 repeated runs; `cargo build --release` stays clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Addressed the 3 CodeRabbit findings in commit
Verification: @coderabbitai please re-review. |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: Length of output: 86960
Fix 1 —
The call site Fix 2 — Env-var test serialization via The Fix 3 — Case-insensitive off-sentinels in code and docs ✅
Everything is internally consistent across the implementation, tests, and docs. [approve] 🐇 ✓ |
Port Swift's PermissionsGate first-launch UX to cua-driver-rs as a
terminal-driven flow. When `serve` starts and TCC permissions
(Accessibility / Screen Recording) are missing the gate now:
- prints a clear banner explaining which grant is missing and why
cua-driver needs it
- raises the system TCC prompts (no-op when already granted)
- auto-opens the matching `System Settings → Privacy & Security`
pane via the `x-apple.systempreferences:` URL scheme
- polls TCC state every 1s and emits a "still waiting on X" line
every 5s
- returns to normal startup once everything flips green
No SwiftUI / GUI dependency is introduced — the Rust port intentionally
keeps the gate terminal-only so it works headlessly under CI when paired
with the opt-out (added in the next commit).
`check_permissions.rs` now delegates its TCC probes to the new
`permissions::status` module so both the MCP tool and the gate share
the same code paths.
Refs #1527
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Call `platform_macos::permissions::run_if_needed()` at the top of `cua-driver serve` on macOS, before the tool registry is built or the Unix socket is bound. When permissions are already granted the gate is a transparent no-op — the existing happy path is unchanged. Adds a `--no-permissions-gate` bare flag that short-circuits the gate for CI / headless automation. `CUA_DRIVER_RS_PERMISSIONS_GATE=0` (checked inside the gate) is the env-var alternative; either signal is sufficient to opt out. If the gate returns an error (deadline elapsed) the daemon continues to start so that affected tool calls fail with their existing TCC error messages, mirroring Swift's "user closed the panel" fallback. Refs #1527 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- PARITY.md: new "Startup flow: permissions gate (`serve`)" section documenting the macOS port, the intentional UX divergence from Swift's SwiftUI panel, and the full opt-out signal table. - installation.mdx: call out the gate in a Callout under "Grant TCC permissions", with the CI / headless opt-out recipe (flag + env-var). - cli-reference.mdx: add a `serve` paragraph describing the gate and document `--no-permissions-gate` in the flags table. Refs #1527 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three findings from the review on the permissions-gate-cli branch: 1. `print_banner` no longer claims "Opening System Settings…" when the caller passed `open_settings: false`. The flag is threaded through and the false branch prints the equivalent `open "x-apple.systempreferences:…"` command(s) instead, so users still know how to grant manually. 2. The 7 unit tests that mutate `CUA_DRIVER_RS_PERMISSIONS_GATE` now serialize through a static `OnceLock<Mutex<()>>`. Cargo runs unit tests in parallel by default, so without the lock the env-var tests race on a process-global. Standard pattern; recovers from mutex poisoning so a panicking test doesn't wedge the rest of the suite. 3. `CUA_DRIVER_RS_PERMISSIONS_GATE` matching is now case-insensitive (`0` / `false` / `no` / `off` in any case), and the CLI reference + installation docs + module/banner text all describe the full off-sentinel set instead of only mentioning `=0`. New test `env_var_off_sentinels_are_case_insensitive` covers the matrix. Verified: 8/8 platform-macos::permissions tests pass across 5 repeated runs; `cargo build --release` stays clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
9661390 to
03c8496
Compare
Summary
Ports Swift
PermissionsGateto cua-driver-rs as a terminal-driven first-launch flow. Closes #1527.When
cua-driver servestarts on macOS and TCC grants for Accessibility or Screen Recording are missing, the gate now:System Settings → Privacy & Securitypane(s) viax-apple.systempreferences:URLs (the same URLs Swift'sPermissionsGateuses)Already-granted users see no change — the gate is a transparent no-op when both grants are active.
Why CLI and not SwiftUI
cua-driver servefrom a shell (Claude Code, Cursor, Codex) — terminal is already attached.--no-permissions-gateis the straight-line approach.A native
NSAlertvia objc2 is tracked as a possible follow-up. CLI is the MVP.Opt-out
cua-driver serve --no-permissions-gateCUA_DRIVER_RS_PERMISSIONS_GATE=0env-varCommits
feat(platform-macos): permissions gate (CLI flow)— newpermissions/{mod,status,gate}.rs, 7 unit tests,check_permissionstool refactored to share the newstatusmodule.feat(cli): wire permissions gate into serve startup—--no-permissions-gateflag +Servearm call.docs: permissions gate (PARITY.md + installation + CLI reference)— PARITY.md gets a new section undercheck_permissions;installation.mdxandcli-reference.mdxget the CI / headless opt-out recipe.Test plan
cargo build --releaseclean (warnings unchanged from baseline).cargo test -p platform-macos --lib permissions::— all 7 unit tests pass (opt-out logic, env-var parsing, missing-permission ordering, settings URLs match Swift verbatim).cua-driver serveon a fresh macOS box with grants revoked → banner appears, Settings opens, gate auto-continues on grant.cua-driver servewith both grants already active → no banner, no delay, normal startup.cua-driver serve --no-permissions-gateon a fresh box → no banner, daemon starts, tool calls fail with normal TCC errors.CUA_DRIVER_RS_PERMISSIONS_GATE=0 cua-driver serve→ same behaviour as the flag.🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
cua-driver servestarts.--no-permissions-gateflag andCUA_DRIVER_RS_PERMISSIONS_GATEenvironment variable to bypass the permissions prompt for CI/headless environments.Documentation