AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
// Screen control on 你的電腦 (computer use), like Grok Bot's Computer Use helper: the bot looks at
// the screen and uses the mouse and keyboard. Same tool names and arguments as the original's
// local daemon (`computer_use_screenshot`, `computer_use_click` {method, x, y, button, count,
// modifier_keys}, `computer_use_scroll`, `computer_use_mouse_move`, `computer_use_drag`,
// `computer_use_typing` {value}, `computer_use_press_key` {key, hold_duration_ms}).
//
// Screenshot sizing (local-exec-daemon main.cjs): `zct` fits the long edge to 1024 (Gct) keeping
// both edges at least 8 (Hs); images stay under 1 MiB (mg = 1024*1024); JPEG is tried at quality
// 85, 70, 55 ($3r), taking the first that fits, else the smallest.
use base64 ::Engine ;
use serde_json ::{ json , Value } ;
use std ::sync ::Mutex ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
pub const NAMES : [ & str ; 11 ] = [ " computer_use_screenshot " , " computer_use_click " , " computer_use_typing " , " computer_use_press_key " , " computer_use_scroll " , " computer_use_mouse_move " , " computer_use_drag " , " computer_use_mouse_button " , " computer_use_check_permissions " , " computer_use_read_app " , " computer_use_open_app " ] ;
/// Wait after an action before taking the picture that comes back with it (menus open, pages
/// start to change; estimated).
pub const SETTLE_MS : u64 = 500 ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
/// The person took control back from the bot (the floating bar): screen actions wait.
pub static HELD_BY_PERSON : std ::sync ::atomic ::AtomicBool = std ::sync ::atomic ::AtomicBool ::new ( false ) ;
/// When the bot last moved the mouse or typed on this Mac (for the floating bar), and who.
pub static LAST_ACTION : Mutex < Option < ( std ::time ::Instant , String ) > > = Mutex ::new ( None ) ;
2026-11-05 07:48:22 +00:00
/// Spawn a short-lived helper (`open`, a player, …) and reap it in the background:
/// spawn-without-wait left one zombie per call for the app's whole life.
pub fn spawn_reaped ( c : & mut std ::process ::Command ) -> std ::io ::Result < ( ) > {
let mut child = c . spawn ( ) ? ;
std ::thread ::spawn ( move | | {
let _ = child . wait ( ) ;
} ) ;
Ok ( ( ) )
}
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
/// Longest screenshot edge sent to the model (Gct).
pub const MAX_LONG_EDGE : u32 = 1024 ;
/// Shortest edge never goes below this (Hs).
pub const MIN_EDGE : u32 = 8 ;
/// Largest screenshot sent (mg = 1024*1024).
pub const MAX_IMAGE_BYTES : usize = 1048576 ;
/// JPEG qualities tried in order ($3r).
pub const JPEG_QUALITIES : [ u8 ; 3 ] = [ 85 , 70 , 55 ] ;
/// Screenshots kept in what's sent to the model; older ones become a note (estimated).
pub const KEEP_SCREENSHOTS : usize = 3 ;
/// Most UTF-16 units per typed chunk; characters are never split (CUTypedCharacters.utf16Chunks
/// @0x1000beb00: capacity 0x14, a new chunk when the next character would pass 20).
#[ cfg_attr(not(target_os = " macos " ), allow(dead_code)) ]
pub const TYPE_CHUNK_UNITS : usize = 20 ;
/// Virtual key the typed text rides on (postChunks @0x1000beebc: CGEventCreateKeyboardEvent(_, 0x80, _)).
#[ cfg_attr(not(target_os = " macos " ), allow(dead_code)) ]
const TYPE_KEY_CODE : u16 = 128 ;
/// Key down → key up for typed chunks and pressed keys (postChunks / handlePressKey: sleep 0.035 s).
pub const KEY_HOLD_SECS : f64 = 0.035 ;
/// Between typed chunks and between keys of one press (postChunks / handlePressKey: sleep 0.02 s).
pub const KEY_GAP_SECS : f64 = 0.02 ;
/// Mouse down → up when no hold is given (CUActuator.click @0x100010120: hold > 0 ? hold : 0.03).
pub const CLICK_HOLD_SECS : f64 = 0.03 ;
/// Between the clicks of a double/triple click (CUActuator.click: sleep 0.08 s).
pub const CLICK_REPEAT_GAP_SECS : f64 = 0.08 ;
/// Interpolated dragged events per path segment (handleDrag @0x10009dee4 passes steps: 6).
pub const DRAG_STEPS : usize = 6 ;
/// Hold after pressing, before moving (handleDrag @0x10009dee4 passes holdSeconds: 0.04).
pub const DRAG_HOLD_SECS : f64 = 0.04 ;
/// The dragged-event points: each segment split into `DRAG_STEPS` equal parts, ending on its end
/// point (CURemoteEventPoster.drag @0x10008c34c: start + delta * i / steps, i = 1…steps).
pub fn drag_points ( path : & [ ( f64 , f64 ) ] ) -> Vec < ( f64 , f64 ) > {
path . windows ( 2 )
. flat_map ( | w | {
let ( ( ax , ay ) , ( bx , by ) ) = ( w [ 0 ] , w [ 1 ] ) ;
( 1 ..= DRAG_STEPS ) . map ( move | i | {
let t = i as f64 / DRAG_STEPS as f64 ;
( ax + ( bx - ax ) * t , ay + ( by - ay ) * t )
} )
} )
. collect ( )
}
/// Scroll steps when none is given, and the allowed range (handleScroll @0x10009e200: default 3,
/// max(amount, 1), min(…, 20)).
pub const SCROLL_DEFAULT_AMOUNT : i64 = 3 ;
pub const SCROLL_MAX_AMOUNT : i64 = 20 ;
/// Pixels per scroll step (handleScroll @0x10009e200: (amount + amount) << 3).
pub const SCROLL_PIXELS_PER_STEP : i32 = 16 ;
pub fn scroll_amount ( amount : Option < i64 > ) -> i32 {
amount . unwrap_or ( SCROLL_DEFAULT_AMOUNT ) . clamp ( 1 , SCROLL_MAX_AMOUNT ) as i32
}
/// (vertical, horizontal) pixel deltas: up and left positive, down and right negative.
pub fn scroll_pixels ( direction : & str , amount : i32 ) -> Option < ( i32 , i32 ) > {
let px = amount * SCROLL_PIXELS_PER_STEP ;
match direction {
" up " = > Some ( ( px , 0 ) ) ,
" down " = > Some ( ( - px , 0 ) ) ,
" left " = > Some ( ( 0 , px ) ) ,
" right " = > Some ( ( 0 , - px ) ) ,
_ = > None ,
}
}
/// Pause between mouse move / drag steps (estimated).
#[ cfg_attr(not(target_os = " macos " ), allow(dead_code)) ]
const EVENT_GAP_MS : u64 = 8 ;
/// Longest key hold allowed (estimated).
const MAX_HOLD_MS : u64 = 5000 ;
/// utf16Chunks: split text into chunks of at most `TYPE_CHUNK_UNITS` UTF-16 units without
/// splitting a character (a character longer than that gets its own chunk).
#[ cfg_attr(not(target_os = " macos " ), allow(dead_code)) ]
pub fn utf16_chunks ( text : & str ) -> Vec < Vec < u16 > > {
let mut out : Vec < Vec < u16 > > = vec! [ ] ;
let mut cur : Vec < u16 > = Vec ::with_capacity ( TYPE_CHUNK_UNITS ) ;
for c in text . chars ( ) {
let mut buf = [ 0 u16 ; 2 ] ;
let units = c . encode_utf16 ( & mut buf ) ;
if ! cur . is_empty ( ) & & cur . len ( ) + units . len ( ) > TYPE_CHUNK_UNITS {
out . push ( std ::mem ::take ( & mut cur ) ) ;
}
cur . extend_from_slice ( units ) ;
}
if ! cur . is_empty ( ) {
out . push ( cur ) ;
}
out
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
/// One key of a press: a key with its modifiers, or a character typed (「@」「?」 have no key of
/// their own on every keyboard; refusing them sent the bot looking for workarounds).
#[ derive(Debug, PartialEq) ]
pub enum Key {
Combo ( u16 , u64 ) ,
Char ( String ) ,
}
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
/// "cmd+a delete" → each key of one press, in order (handlePressKey presses a list of keys).
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
pub fn key_plan ( keys : & str ) -> Result < Vec < Key > , String > {
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
let list : Vec < & str > = keys . split_whitespace ( ) . collect ( ) ;
if list . is_empty ( ) {
return Err ( " 請給要按的鍵 " . into ( ) ) ;
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
list . into_iter ( )
. map ( | k | match parse_combo ( k ) {
// A letter, digit or symbol without a modifier is a character: typed, so an input
// method (注音) can't turn it into a syllable. Named symbol keys mean their character.
Ok ( ( c , 0 ) ) if printable_of ( c ) . is_some ( ) = > Ok ( Key ::Char ( printable_of ( c ) . unwrap_or_default ( ) ) ) ,
Ok ( ( c , f ) ) = > Ok ( Key ::Combo ( c , f ) ) ,
Err ( _ ) if k . chars ( ) . count ( ) = = 1 = > Ok ( Key ::Char ( k . to_string ( ) ) ) ,
Err ( e ) = > Err ( e ) ,
} )
. collect ( )
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
/// The character a printing key gives without shift (US layout), or None for keys like return.
fn printable_of ( code : u16 ) -> Option < String > {
const LETTERS : [ u16 ; 26 ] = [ 0 , 11 , 8 , 2 , 14 , 3 , 5 , 4 , 34 , 38 , 40 , 37 , 46 , 45 , 31 , 35 , 12 , 15 , 1 , 17 , 32 , 9 , 13 , 7 , 16 , 6 ] ;
const DIGITS : [ u16 ; 10 ] = [ 29 , 18 , 19 , 20 , 21 , 23 , 22 , 26 , 28 , 25 ] ;
if let Some ( i ) = LETTERS . iter ( ) . position ( | c | * c = = code ) {
return Some ( ( ( b 'a' + i as u8 ) as char ) . to_string ( ) ) ;
}
if let Some ( i ) = DIGITS . iter ( ) . position ( | c | * c = = code ) {
return Some ( ( ( b '0' + i as u8 ) as char ) . to_string ( ) ) ;
}
let c = match code {
27 = > '-' ,
24 = > '=' ,
33 = > '[' ,
30 = > ']' ,
41 = > ';' ,
39 = > '\'' ,
43 = > ',' ,
47 = > '.' ,
44 = > '/' ,
42 = > '\\' ,
50 = > '`' ,
_ = > return None ,
} ;
Some ( c . to_string ( ) )
}
/// The picture the bot and the phone get of this screen: its size, always the same for the same
/// screen. Coordinates on it map to the screen without remembering any earlier screenshot (that
/// hidden state made clicks fail with "screenshot first" and land wrong for a viewer whose
/// picture was a different size).
pub fn shot_size ( ) -> ( u32 , u32 ) {
let ( px , py ) = mac ::screen_pixels ( ) ;
let ( w , h , _ ) = fit ( px . max ( 1 ) , py . max ( 1 ) ) ;
( w , h )
}
/// Screen points (the mouse's units) per picture pixel.
pub fn scale_here ( ) -> ( f64 , f64 ) {
let ( pw , ph ) = mac ::screen_points ( ) ;
let ( w , h ) = shot_size ( ) ;
( pw / w . max ( 1 ) as f64 , ph / h . max ( 1 ) as f64 )
}
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
pub fn is_computer_tool ( name : & str ) -> bool {
NAMES . contains ( & name )
}
pub fn label ( name : & str ) -> & 'static str {
match name {
" computer_use_screenshot " = > " 看你的螢幕 " ,
" computer_use_click " = > " 點一下 " ,
" computer_use_typing " = > " 打字 " ,
" computer_use_press_key " = > " 按鍵 " ,
" computer_use_scroll " = > " 捲動 " ,
" computer_use_mouse_move " = > " 移動滑鼠 " ,
" computer_use_drag " = > " 拖曳 " ,
" computer_use_mouse_button " = > " 按住或放開滑鼠 " ,
" computer_use_check_permissions " = > " 檢查螢幕權限 " ,
2026-11-04 09:19:59 +00:00
" computer_use_read_app " = > " 讀 App 畫面 " ,
" computer_use_open_app " = > " 打開 App " ,
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
_ = > " 操作畫面 " ,
}
}
fn args_of ( args : & str ) -> Value {
serde_json ::from_str ( if args . trim ( ) . is_empty ( ) { " {} " } else { args } ) . unwrap_or ( Value ::Null )
}
pub fn describe ( name : & str , args : & str ) -> String {
let v = args_of ( args ) ;
2026-11-05 10:25:46 +00:00
// By number from the app's list (e7), else where on the screen (it said "(0, 0)" for every
// click by number).
let xy = | | match v [ " ref " ] . as_str ( ) . map ( str ::trim ) . filter ( | r | ! r . is_empty ( ) ) {
Some ( r ) = > r . to_string ( ) ,
None = > format! ( " ( {} , {} ) " , v [ " x " ] . as_f64 ( ) . unwrap_or ( 0.0 ) , v [ " y " ] . as_f64 ( ) . unwrap_or ( 0.0 ) ) ,
} ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
match name {
" computer_use_screenshot " = > " 螢幕截圖 " . into ( ) ,
" computer_use_click " = > {
let b = v [ " button " ] . as_str ( ) . unwrap_or ( " left " ) ;
let n = v [ " count " ] . as_u64 ( ) . unwrap_or ( 1 ) ;
format! ( " {} {} {} {} " , if b = = " right " { " 右鍵 " } else { " " } , if n > = 2 { " 連點 " } else { " 點 " } , if n > = 2 { format! ( " {n} 下 " ) } else { String ::new ( ) } , xy ( ) )
}
" computer_use_typing " = > {
let t = v [ " value " ] . as_str ( ) . unwrap_or ( " " ) ;
let short : String = t . chars ( ) . take ( 60 ) . collect ( ) ;
2026-11-05 10:25:46 +00:00
let at = v [ " ref " ] . as_str ( ) . filter ( | r | ! r . trim ( ) . is_empty ( ) ) . map ( | r | format! ( " 在 {r} " ) ) . unwrap_or_default ( ) ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
format! ( " {at} 「 {short} {} 」 {} " , if t . chars ( ) . count ( ) > 60 { " … " } else { " " } , if v [ " submit " ] . as_bool ( ) . unwrap_or ( false ) { " 再按 return " } else { " " } )
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
" computer_use_press_key " = > v [ " key " ] . as_str ( ) . unwrap_or ( " " ) . to_string ( ) ,
" computer_use_scroll " = > format! ( " {} {} 格,在 {} " , v [ " direction " ] . as_str ( ) . unwrap_or ( " down " ) , v [ " amount " ] . as_i64 ( ) . unwrap_or ( 3 ) , xy ( ) ) ,
" computer_use_mouse_move " = > xy ( ) ,
" computer_use_drag " = > format! ( " {} 個點 " , v [ " path " ] . as_array ( ) . map ( | a | a . len ( ) ) . unwrap_or ( 0 ) ) ,
" computer_use_mouse_button " = > format! ( " {} {} " , if v [ " action " ] . as_str ( ) = = Some ( " release " ) { " 放開 " } else { " 按住 " } , if v [ " x " ] . is_null ( ) { String ::new ( ) } else { format! ( " {} " , xy ( ) ) } ) ,
" computer_use_check_permissions " = > " 螢幕錄製、輔助使用 " . into ( ) ,
2026-11-04 09:19:59 +00:00
" computer_use_read_app " | " computer_use_open_app " = > v [ " app " ] . as_str ( ) . filter ( | a | ! a . is_empty ( ) ) . unwrap_or ( " 最前面的 App " ) . to_string ( ) ,
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
_ = > String ::new ( ) ,
}
}
#[ allow(dead_code) ]
pub fn specs ( ) -> Vec < Value > {
2026-11-05 08:47:28 +00:00
specs_for ( & [ ] , " " )
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
/// The screen tools, each with a `computer` choice. One way to act (a real click or keystroke),
/// two ways to see: the picture, or an app's controls as numbered text.
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
pub fn specs_for ( computers : & [ String ] , default : & str ) -> Vec < Value > {
let mods = json! ( { " type " : " array " , " items " : { " type " : " string " , " enum " : [ " cmd " , " shift " , " option " , " control " ] } } ) ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
let rf = json! ( { " type " : " string " , " description " : " A control's number from computer_use_read_app (e.g. e12), instead of x, y " } ) ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
let mut v = vec! [
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
json! ( { " name " : " computer_use_screenshot " , " description " : " See a computer's screen. x, y in the other computer_use tools are pixels of this picture. Every action below already returns a fresh picture of the result, so call this only to start or to look again. " ,
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
" parameters " : { " type " : " object " , " properties " : { } } } ) ,
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
json! ( { " name " : " computer_use_click " , " description " : " Click at x, y (pixels of the latest picture), or on a control by its number (ref) from computer_use_read_app. Returns the screen after the click. " ,
" parameters " : { " type " : " object " , " properties " : { " x " : { " type " : " number " } , " y " : { " type " : " number " } , " ref " : rf , " button " : { " type " : " string " , " enum " : [ " left " , " right " , " middle " ] } , " count " : { " type " : " integer " , " description " : " 2 for a double click " } , " modifier_keys " : mods } } } ) ,
json! ( { " name " : " computer_use_typing " , " description " : " Type text (any language) where the cursor is, or into a field by its number (ref; it is clicked first). submit=true presses return after the text (send a message, run a command). Returns the screen after. " ,
" parameters " : { " type " : " object " , " properties " : { " value " : { " type " : " string " } , " ref " : rf , " submit " : { " type " : " boolean " } } , " required " : [ " value " ] } } ) ,
json! ( { " name " : " computer_use_press_key " , " description " : " Press a key or shortcut in the app in front: return, tab, escape, delete, up, slash, cmd+c, cmd+shift+t, cmd+space. Several in order: separate with spaces ( \" cmd+a delete \" ). On a Linux computer cmd means ctrl. Returns the screen after. " ,
" parameters " : { " type " : " object " , " properties " : { " key " : { " type " : " string " } , " hold_duration_ms " : { " type " : " integer " } } , " required " : [ " key " ] } } ) ,
json! ( { " name " : " computer_use_scroll " , " description " : " Scroll at x, y or on a control by number (ref); neither = where the pointer is. Returns the screen after. " ,
" parameters " : { " type " : " object " , " properties " : { " x " : { " type " : " number " } , " y " : { " type " : " number " } , " ref " : rf , " direction " : { " type " : " string " , " enum " : [ " up " , " down " , " left " , " right " ] } , " amount " : { " type " : " integer " , " description " : " Scroll steps of 16 pixels, 1-20, default 3 " } , " modifier_keys " : mods } , " required " : [ " direction " ] } } ) ,
json! ( { " name " : " computer_use_open_app " , " description " : " Open an app, or bring it to the front when it is already open (its name as shown, e.g. Ghostty, Calculator, 計算機, Chromium, xterm). Returns the screen after. " ,
" parameters " : { " type " : " object " , " properties " : { " app " : { " type " : " string " } } , " required " : [ " app " ] } } ) ,
json! ( { " name " : " computer_use_read_app " , " description " : " Read an app's window as text: buttons, fields, menus, list items and visible text, each control numbered (e1, e2…) for click/typing/scroll's ref. For when a picture can't be taken or isn't enough (exact text, long lists). app: its name; leave out for the app in front. Only on computers that offer it (not the bots' own Linux computer). " ,
" parameters " : { " type " : " object " , " properties " : { " app " : { " type " : " string " } } } } ) ,
json! ( { " name " : " computer_use_mouse_move " , " description " : " Move the mouse to x, y (e.g. to show a hover menu). Returns the screen after. " ,
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
" parameters " : { " type " : " object " , " properties " : { " x " : { " type " : " number " } , " y " : { " type " : " number " } } , " required " : [ " x " , " y " ] } } ) ,
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
json! ( { " name " : " computer_use_drag " , " description " : " Drag with the mouse along a path of points (first = press, last = release). Returns the screen after. " ,
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
" parameters " : { " type " : " object " , " properties " : { " path " : { " type " : " array " , " items " : { " type " : " object " , " properties " : { " x " : { " type " : " number " } , " y " : { " type " : " number " } } , " required " : [ " x " , " y " ] } } , " button " : { " type " : " string " , " enum " : [ " left " , " right " ] } , " modifier_keys " : mods } , " required " : [ " path " ] } } ) ,
json! ( { " name " : " computer_use_mouse_button " , " description " : " Press and hold, or release, a mouse button (optionally moving to x, y first). For long presses and custom drags. " ,
" parameters " : { " type " : " object " , " properties " : { " action " : { " type " : " string " , " enum " : [ " press " , " release " ] } , " button " : { " type " : " string " , " enum " : [ " left " , " right " , " middle " ] } , " x " : { " type " : " number " } , " y " : { " type " : " number " } } , " required " : [ " action " ] } } ) ,
json! ( { " name " : " computer_use_check_permissions " , " description " : " Check whether the screen can be seen and the mouse and keyboard used on that computer. " ,
" parameters " : { " type " : " object " , " properties " : { } } } ) ,
] ;
2026-11-05 08:47:28 +00:00
// Always named: each computer goes by its own name (the instructions say which is which),
// so the bot says whose screen it means and the person can tell where it worked.
if ! computers . is_empty ( ) {
let desc = format! ( " Whose screen, by the computer's name. Default: {default} (the one the person chose). Use another only when the person names it. " ) ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
for t in v . iter_mut ( ) {
2026-11-05 08:03:34 +00:00
t [ " parameters " ] [ " properties " ] [ " computer " ] = json! ( { " type " : " string " , " enum " : computers , " description " : desc } ) ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
}
v
}
/// zct: the size to send. Long edge down to `MAX_LONG_EDGE`, never an edge under `MIN_EDGE`.
pub fn fit ( w : u32 , h : u32 ) -> ( u32 , u32 , bool ) {
if w < 1 | | h < 1 {
return ( w , h , false ) ;
}
let long = w . max ( h ) ;
if long < = MAX_LONG_EDGE {
return ( w , h , false ) ;
}
let short = w . min ( h ) ;
if short < MIN_EDGE {
return ( w , h , false ) ;
}
let mut i = MAX_LONG_EDGE as f64 / long as f64 ;
let mut o = ( ( w as f64 * i ) . round ( ) as u32 ) . max ( 1 ) ;
let mut a = ( ( h as f64 * i ) . round ( ) as u32 ) . max ( 1 ) ;
if o < MIN_EDGE | | a < MIN_EDGE {
i = MIN_EDGE as f64 / short as f64 ;
o = ( ( w as f64 * i ) . round ( ) as u32 ) . max ( MIN_EDGE ) ;
a = ( ( h as f64 * i ) . round ( ) as u32 ) . max ( MIN_EDGE ) ;
}
( o , a , o ! = w | | a ! = h )
}
/// j3r: JPEG at 85, 70, 55: the first within the budget, else the smallest if it's under the cap.
pub fn encode_jpeg ( img : & image ::RgbImage ) -> Option < Vec < u8 > > {
let mut best : Option < Vec < u8 > > = None ;
for q in JPEG_QUALITIES {
let mut out = vec! [ ] ;
let enc = image ::codecs ::jpeg ::JpegEncoder ::new_with_quality ( & mut out , q ) ;
if img . write_with_encoder ( enc ) . is_err ( ) {
continue ;
}
if out . len ( ) < = MAX_IMAGE_BYTES {
return Some ( out ) ;
}
if best . as_ref ( ) . map_or ( true , | b | out . len ( ) < b . len ( ) ) {
best = Some ( out ) ;
}
}
best . filter ( | b | b . len ( ) < = MAX_IMAGE_BYTES )
}
/// Screenshot pixel → screen point.
pub fn to_screen ( x : f64 , y : f64 , scale : ( f64 , f64 ) ) -> ( f64 , f64 ) {
( x * scale . 0 , y * scale . 1 )
}
/// Key name → (key code, needs shift). US layout codes.
pub fn key_code ( name : & str ) -> Option < u16 > {
let n = name . trim ( ) . to_lowercase ( ) ;
let named = match n . as_str ( ) {
" return " | " enter " = > 36 ,
" tab " = > 48 ,
" space " = > 49 ,
" delete " | " backspace " = > 51 ,
" escape " | " esc " = > 53 ,
" forwarddelete " | " forward_delete " | " del " = > 117 ,
" home " = > 115 ,
" end " = > 119 ,
" pageup " | " page_up " = > 116 ,
" pagedown " | " page_down " = > 121 ,
" left " | " arrowleft " = > 123 ,
" right " | " arrowright " = > 124 ,
" down " | " arrowdown " = > 125 ,
" up " | " arrowup " = > 126 ,
" f1 " = > 122 ,
" f2 " = > 120 ,
" f3 " = > 99 ,
" f4 " = > 118 ,
" f5 " = > 96 ,
" f6 " = > 97 ,
" f7 " = > 98 ,
" f8 " = > 100 ,
" f9 " = > 101 ,
" f10 " = > 109 ,
" f11 " = > 103 ,
" f12 " = > 111 ,
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
// Symbol keys by name (a model writes "slash" as often as "/").
" slash " = > 44 ,
" backslash " = > 42 ,
" comma " = > 43 ,
" period " | " dot " = > 47 ,
" semicolon " = > 41 ,
" quote " | " apostrophe " = > 39 ,
" minus " | " hyphen " = > 27 ,
" equal " | " equals " = > 24 ,
" grave " | " backtick " | " backquote " = > 50 ,
" bracketleft " | " leftbracket " = > 33 ,
" bracketright " | " rightbracket " = > 30 ,
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
_ = > u16 ::MAX ,
} ;
if named ! = u16 ::MAX {
return Some ( named ) ;
}
let mut cs = n . chars ( ) ;
let ( Some ( c ) , None ) = ( cs . next ( ) , cs . next ( ) ) else { return None } ;
const LETTERS : [ u16 ; 26 ] = [ 0 , 11 , 8 , 2 , 14 , 3 , 5 , 4 , 34 , 38 , 40 , 37 , 46 , 45 , 31 , 35 , 12 , 15 , 1 , 17 , 32 , 9 , 13 , 7 , 16 , 6 ] ;
const DIGITS : [ u16 ; 10 ] = [ 29 , 18 , 19 , 20 , 21 , 23 , 22 , 26 , 28 , 25 ] ;
match c {
'a' ..= 'z' = > Some ( LETTERS [ ( c as u8 - b 'a' ) as usize ] ) ,
'0' ..= '9' = > Some ( DIGITS [ ( c as u8 - b '0' ) as usize ] ) ,
'-' = > Some ( 27 ) ,
'=' = > Some ( 24 ) ,
'[' = > Some ( 33 ) ,
']' = > Some ( 30 ) ,
';' = > Some ( 41 ) ,
'\'' = > Some ( 39 ) ,
',' = > Some ( 43 ) ,
'.' = > Some ( 47 ) ,
'/' = > Some ( 44 ) ,
'\\' = > Some ( 42 ) ,
'`' = > Some ( 50 ) ,
_ = > None ,
}
}
pub const FLAG_SHIFT : u64 = 1 < < 17 ;
pub const FLAG_CONTROL : u64 = 1 < < 18 ;
pub const FLAG_OPTION : u64 = 1 < < 19 ;
pub const FLAG_CMD : u64 = 1 < < 20 ;
pub fn modifier_flag ( m : & str ) -> Option < u64 > {
match m . trim ( ) . to_lowercase ( ) . as_str ( ) {
" cmd " | " command " | " meta " | " super " | " ⌘ " = > Some ( FLAG_CMD ) ,
" shift " = > Some ( FLAG_SHIFT ) ,
" option " | " alt " | " opt " = > Some ( FLAG_OPTION ) ,
" control " | " ctrl " = > Some ( FLAG_CONTROL ) ,
_ = > None ,
}
}
/// "cmd+shift+t" → (key code, flags).
pub fn parse_combo ( combo : & str ) -> Result < ( u16 , u64 ) , String > {
let parts : Vec < & str > = combo . split ( '+' ) . map ( str ::trim ) . filter ( | p | ! p . is_empty ( ) ) . collect ( ) ;
let Some ( ( key , mods ) ) = parts . split_last ( ) else { return Err ( " 請給要按的鍵 " . into ( ) ) } ;
let mut flags = 0 ;
for m in mods {
flags | = modifier_flag ( m ) . ok_or_else ( | | format! ( " 不認得「 {m} 」這個修飾鍵 " ) ) ? ;
}
let code = key_code ( key ) . ok_or_else ( | | format! ( " 不認得「 {key} 」這個鍵 " ) ) ? ;
Ok ( ( code , flags ) )
}
fn flags_of ( v : & Value ) -> u64 {
v [ " modifier_keys " ] . as_array ( ) . map ( | a | a . iter ( ) . filter_map ( | m | m . as_str ( ) . and_then ( modifier_flag ) ) . fold ( 0 , | f , x | f | x ) ) . unwrap_or ( 0 )
}
/// Run a computer_use tool. Returns (text, ok, screenshot as base64 JPEG).
#[ allow(dead_code) ]
pub fn run ( name : & str , args : & str ) -> ( String , bool , Option < String > ) {
run_by ( name , args , " " )
}
/// Same, remembering which bot acted (for the 「正在操作你的電腦」 bar).
pub fn run_by ( name : & str , args : & str , who : & str ) -> ( String , bool , Option < String > ) {
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
if moves_things ( name ) {
2026-11-05 07:48:22 +00:00
* LAST_ACTION . lock ( ) . unwrap_or_else ( | e | e . into_inner ( ) ) = Some ( ( std ::time ::Instant ::now ( ) , who . to_string ( ) ) ) ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
match run_inner ( name , & args_of ( args ) ) {
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
Ok ( ( t , img ) ) = > ( t , true , img ) ,
Err ( e ) = > ( e , false , None ) ,
}
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
/// Steps that use the mouse or keyboard (and come back with the screen after them).
pub fn moves_things ( name : & str ) -> bool {
matches! ( name , " computer_use_click " | " computer_use_typing " | " computer_use_press_key " | " computer_use_scroll " | " computer_use_mouse_move " | " computer_use_drag " | " computer_use_mouse_button " | " computer_use_open_app " )
2026-11-04 09:19:59 +00:00
}
2026-11-05 08:57:44 +00:00
/// Ask macOS for a permission (its own prompt, then 系統設定 at that pane) at most once every ten
/// minutes. Asking on every step meant a bot working through several steps — or a brain far away
/// working on this computer — covered the screen with prompts and Settings windows, and the
/// person could do nothing else.
pub fn ask_permission ( pane : & str ) {
static LAST : std ::sync ::Mutex < Vec < ( String , std ::time ::Instant ) > > = std ::sync ::Mutex ::new ( Vec ::new ( ) ) ;
{
let mut l = LAST . lock ( ) . unwrap_or_else ( | e | e . into_inner ( ) ) ;
if l . iter ( ) . any ( | ( p , t ) | p = = pane & & t . elapsed ( ) < std ::time ::Duration ::from_secs ( 600 ) ) {
return ;
}
l . retain ( | ( p , _ ) | p ! = pane ) ;
l . push ( ( pane . to_string ( ) , std ::time ::Instant ::now ( ) ) ) ;
}
if ! cfg! ( target_os = " macos " ) {
return ;
}
match pane {
" Privacy_Accessibility " = > {
let _ = crate ::axread ::trusted ( true ) ;
}
#[ cfg(target_os = " macos " ) ]
" Privacy_ScreenCapture " = > {
let _ = mac ::screen_capture_allowed ( ) ;
}
_ = > { }
}
let _ = spawn_reaped ( & mut std ::process ::Command ::new ( " open " ) . arg ( format! ( " x-apple.systempreferences:com.apple.preference.security? {pane} " ) ) ) ;
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
const NO_ACCESSIBILITY : & str = " macOS 還沒讓 AnyBot 使用「輔助使用」,所以不能讀 App, 也不能動滑鼠和鍵盤。這台電腦上已經跳出詢問並打開「系統設定 → 隱私權與安全性 → 輔助使用」:請使用者把 AnyBot 打開(只要一次),再叫我試一次。 " ;
const NO_SCREEN_RECORDING : & str = " macOS 還沒讓 AnyBot 錄製螢幕,所以看不到畫面。這台電腦上已經打開「系統設定 → 隱私權與安全性 → 螢幕與系統錄音」:請使用者把 AnyBot 打開(只要一次)。在那之前可以用 computer_use_read_app 讀 App 的文字。 " ;
/// Reading apps and using the mouse and keyboard need 輔助使用 on macOS (other systems: always).
fn controls_allowed ( ) -> bool {
! cfg! ( target_os = " macos " ) | | crate ::axread ::trusted ( false )
}
2026-11-05 10:50:08 +00:00
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
fn need_controls ( ) -> Result < ( ) , String > {
if controls_allowed ( ) {
return Ok ( ( ) ) ;
2026-11-05 10:50:08 +00:00
}
ask_permission ( " Privacy_Accessibility " ) ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
Err ( NO_ACCESSIBILITY . into ( ) )
2026-11-05 10:50:08 +00:00
}
2026-11-05 08:57:44 +00:00
/// The app a shown name stands for (「計算機」 → Calculator), from the apps' display names.
#[ cfg(target_os = " macos " ) ]
fn app_named ( name : & str ) -> Option < String > {
if name . is_empty ( ) | | name . contains ( [ '"' , '\\' , '\'' ] ) {
return None ;
}
let q = format! ( " kMDItemDisplayName == \" {name} \" cd && kMDItemContentType == \" com.apple.application-bundle \" " ) ;
let out = std ::process ::Command ::new ( " mdfind " ) . arg ( q ) . output ( ) . ok ( ) ? ;
let path = String ::from_utf8_lossy ( & out . stdout ) . lines ( ) . next ( ) ? . trim ( ) . to_string ( ) ;
std ::path ::Path ::new ( & path ) . file_stem ( ) . map ( | s | s . to_string_lossy ( ) . to_string ( ) )
}
#[ cfg(not(target_os = " macos " )) ]
fn app_named ( _name : & str ) -> Option < String > {
None
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
/// Open an app or bring it to the front. A name as the person sees it (「計算機」 on a Chinese
/// Mac) isn't one the system launches by; find the app it is and launch that.
fn open_app ( app : & str ) -> Result < String , String > {
let app = app . trim ( ) ;
if app . is_empty ( ) {
return Err ( " 請給要打開的 App 名稱 " . into ( ) ) ;
}
People watch and help on the bots' computer through VNC (noVNC), carried by the account server
The core: the bot works the computer; a person watches it like a colleague, and takes over
when it needs them. VNC is made for that (only what changed, every mouse move and key, touch on
phones), so the pictures sent one by one go, and with them their own input path.
- vnc.rs: a VNC proxy to the box's x11vnc through one docker exec (a Python bridge that ends
with either side). It signs in to the box with the box's own password (its VNC_PASSWORD) and
offers the viewer no password, so the password never leaves the brain. The person's clicks
and keys make the bot's steps wait until 8 s after they stop; text typed in the viewer's box
(any language) is pasted in the box.
- Account server: /screen (the viewer page) and /novnc.js (noVNC 1.6.0, MPL-2.0, bundled);
/relay/vnc/<host> takes the browser's WebSocket (account session, or a 10-minute ticket the
app asks for with POST /relay/screen-ticket; owners and admins) and carries it over that
AnyBot's lasting connection (vopen / vdata / vtext / vclose). Both ends of that connection
wait 2 ms instead of 40 ms to read while things move: a request and its answer took up to
160 ms more than needed.
- Phone page: 看 Bot 的電腦 and 接管 open the viewer page (also on the local network: /api/vnc on
the AnyBot's own phone server). This Mac's own screen moved to the ⋯ menu.
- Mac app: 看畫面 opens the viewer in the browser (a screen of a brain: the brain's computer by
ticket; else this computer's box, noVNC straight from it). The in-app picture viewer, the
live JPEG stream, the person's click/move/scroll/key endpoints and the input shell are gone.
- Opening a Mac app uses LaunchServices (`open -a`): agent-desktop's launch waited on the window
through accessibility and failed without 輔助使用 now and then.
Measured on this Mac (made-up account, local relay + headless brain + the real box): viewer
signed in in 0.4–0.7 s, a request and its answer through relay + brain + box 25–58 ms median;
headless Chrome shows the desktop live in /screen. remote_e2e 28/28 ×2, 149 unit tests, relay 11.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 15:05:44 +00:00
launch ( app ) . or_else ( | e | match app_named ( app ) {
Some ( real ) if real ! = app = > launch ( & real ) ,
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
_ = > Err ( e ) ,
} ) ? ;
Ok ( format! ( " 已打開「 {app} 」,放在最前面。 " ) )
2026-11-04 09:19:59 +00:00
}
People watch and help on the bots' computer through VNC (noVNC), carried by the account server
The core: the bot works the computer; a person watches it like a colleague, and takes over
when it needs them. VNC is made for that (only what changed, every mouse move and key, touch on
phones), so the pictures sent one by one go, and with them their own input path.
- vnc.rs: a VNC proxy to the box's x11vnc through one docker exec (a Python bridge that ends
with either side). It signs in to the box with the box's own password (its VNC_PASSWORD) and
offers the viewer no password, so the password never leaves the brain. The person's clicks
and keys make the bot's steps wait until 8 s after they stop; text typed in the viewer's box
(any language) is pasted in the box.
- Account server: /screen (the viewer page) and /novnc.js (noVNC 1.6.0, MPL-2.0, bundled);
/relay/vnc/<host> takes the browser's WebSocket (account session, or a 10-minute ticket the
app asks for with POST /relay/screen-ticket; owners and admins) and carries it over that
AnyBot's lasting connection (vopen / vdata / vtext / vclose). Both ends of that connection
wait 2 ms instead of 40 ms to read while things move: a request and its answer took up to
160 ms more than needed.
- Phone page: 看 Bot 的電腦 and 接管 open the viewer page (also on the local network: /api/vnc on
the AnyBot's own phone server). This Mac's own screen moved to the ⋯ menu.
- Mac app: 看畫面 opens the viewer in the browser (a screen of a brain: the brain's computer by
ticket; else this computer's box, noVNC straight from it). The in-app picture viewer, the
live JPEG stream, the person's click/move/scroll/key endpoints and the input shell are gone.
- Opening a Mac app uses LaunchServices (`open -a`): agent-desktop's launch waited on the window
through accessibility and failed without 輔助使用 now and then.
Measured on this Mac (made-up account, local relay + headless brain + the real box): viewer
signed in in 0.4–0.7 s, a request and its answer through relay + brain + box 25–58 ms median;
headless Chrome shows the desktop live in /screen. remote_e2e 28/28 ×2, 149 unit tests, relay 11.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 15:05:44 +00:00
/// Open an app or bring it forward. macOS: LaunchServices (`open -a`), which needs no
/// permission (agent-desktop's launch waited on the window through accessibility and failed
/// without 輔助使用 now and then); elsewhere agent-desktop's.
fn launch ( app : & str ) -> Result < ( ) , String > {
#[ cfg(target_os = " macos " ) ]
{
let out = std ::process ::Command ::new ( " open " ) . args ( [ " -a " , app ] ) . output ( ) . map_err ( | e | e . to_string ( ) ) ? ;
if out . status . success ( ) {
return Ok ( ( ) ) ;
}
Err ( format! ( " 找不到「 {app} 」這個 App " ) )
}
#[ cfg(not(target_os = " macos " )) ]
crate ::desktop ::launch ( app )
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
/// Bring the app a control belongs to in front, so a click at its spot lands in it (and not in
/// a window lying over it). Nothing to do when it is in front already.
fn bring_forward ( app : & str ) {
// macOS only: there "launch" brings a running app forward; on Linux it can start a second
// copy, and the names a window and its accessibility give differ.
if ! cfg! ( target_os = " macos " ) {
return ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
let front = mac ::front_app ( ) . map ( | f | f . 0 ) . unwrap_or_default ( ) ;
if app . is_empty ( ) | | front . eq_ignore_ascii_case ( app ) {
return ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
People watch and help on the bots' computer through VNC (noVNC), carried by the account server
The core: the bot works the computer; a person watches it like a colleague, and takes over
when it needs them. VNC is made for that (only what changed, every mouse move and key, touch on
phones), so the pictures sent one by one go, and with them their own input path.
- vnc.rs: a VNC proxy to the box's x11vnc through one docker exec (a Python bridge that ends
with either side). It signs in to the box with the box's own password (its VNC_PASSWORD) and
offers the viewer no password, so the password never leaves the brain. The person's clicks
and keys make the bot's steps wait until 8 s after they stop; text typed in the viewer's box
(any language) is pasted in the box.
- Account server: /screen (the viewer page) and /novnc.js (noVNC 1.6.0, MPL-2.0, bundled);
/relay/vnc/<host> takes the browser's WebSocket (account session, or a 10-minute ticket the
app asks for with POST /relay/screen-ticket; owners and admins) and carries it over that
AnyBot's lasting connection (vopen / vdata / vtext / vclose). Both ends of that connection
wait 2 ms instead of 40 ms to read while things move: a request and its answer took up to
160 ms more than needed.
- Phone page: 看 Bot 的電腦 and 接管 open the viewer page (also on the local network: /api/vnc on
the AnyBot's own phone server). This Mac's own screen moved to the ⋯ menu.
- Mac app: 看畫面 opens the viewer in the browser (a screen of a brain: the brain's computer by
ticket; else this computer's box, noVNC straight from it). The in-app picture viewer, the
live JPEG stream, the person's click/move/scroll/key endpoints and the input shell are gone.
- Opening a Mac app uses LaunchServices (`open -a`): agent-desktop's launch waited on the window
through accessibility and failed without 輔助使用 now and then.
Measured on this Mac (made-up account, local relay + headless brain + the real box): viewer
signed in in 0.4–0.7 s, a request and its answer through relay + brain + box 25–58 ms median;
headless Chrome shows the desktop live in /screen. remote_e2e 28/28 ×2, 149 unit tests, relay 11.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 15:05:44 +00:00
if launch ( app ) . is_ok ( ) {
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 300 ) ) ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
}
/// Where a step acts: a control's number (from computer_use_read_app), or x, y on the picture.
/// Returns the screen point and how to say it (「e7( 按鈕「7」) 」, 「(512, 300)」).
fn place_of ( v : & Value , scale : ( f64 , f64 ) ) -> Result < Option < ( f64 , f64 , String ) > , String > {
if let Some ( r ) = v [ " ref " ] . as_str ( ) . map ( str ::trim ) . filter ( | r | ! r . is_empty ( ) ) {
let s = crate ::desktop ::spot ( r ) ? ;
bring_forward ( & s . app ) ;
return Ok ( Some ( ( s . x , s . y , format! ( " {} ( {} ) " , crate ::desktop ::short_ref ( r ) ? , s . what ) ) ) ) ;
}
match ( v [ " x " ] . as_f64 ( ) , v [ " y " ] . as_f64 ( ) ) {
( Some ( x ) , Some ( y ) ) = > {
let ( sx , sy ) = to_screen ( x , y , scale ) ;
Ok ( Some ( ( sx , sy , format! ( " ( {x} , {y} ) " ) ) ) )
}
( None , None ) if v [ " x " ] . is_null ( ) & & v [ " y " ] . is_null ( ) = > Ok ( None ) ,
_ = > Err ( " x 和 y 要一起給,而且要是數字(截圖上的位置) " . into ( ) ) ,
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
}
/// Press each key of "cmd+a delete" in order.
fn press_keys ( keys : & str , hold : f64 ) -> Result < ( ) , String > {
for ( i , k ) in key_plan ( keys ) ? . iter ( ) . enumerate ( ) {
if i > 0 {
std ::thread ::sleep ( std ::time ::Duration ::from_secs_f64 ( KEY_GAP_SECS ) ) ;
}
match k {
Key ::Combo ( code , flags ) = > mac ::press ( * code , * flags , hold ) ,
Key ::Char ( ch ) = > mac ::type_text ( ch ) ,
}
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
Ok ( ( ) )
}
/// Do one mouse or keyboard step at the screen's own coordinates (`scale`: screen points per
/// picture pixel). The bot's tools and a person tapping on the phone's picture both come here:
/// one way of acting on a screen.
pub fn act ( name : & str , v : & Value , scale : ( f64 , f64 ) ) -> Result < String , String > {
let button = v [ " button " ] . as_str ( ) . unwrap_or ( " left " ) ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
match name {
" computer_use_click " = > {
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
let ( x , y , at ) = place_of ( v , scale ) ? . ok_or ( " 請給 x、y( 截圖上的位置) 或 ref( computer_use_read_app 的編號) " ) ? ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
let count = v [ " count " ] . as_u64 ( ) . unwrap_or ( 1 ) . clamp ( 1 , 3 ) as i64 ;
mac ::click ( x , y , button , count , flags_of ( v ) ) ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
let how = format! ( " {} {} " , if button = = " right " { " 右鍵 " } else { " " } , if count > = 2 { format! ( " 連點 {count} 下 " ) } else { " 點 " . into ( ) } ) ;
Ok ( format! ( " 已 {how} {at} 。 " ) )
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
" computer_use_mouse_move " = > {
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
let ( x , y , at ) = place_of ( v , scale ) ? . ok_or ( " 請給 x 和 y " ) ? ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
mac ::move_to ( x , y ) ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
Ok ( format! ( " 已把滑鼠移到 {at} 。 " ) )
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
" computer_use_scroll " = > {
// CURemoteToolService.handleScroll @0x10009e200 / closure @0x10009e998.
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
let at = place_of ( v , scale ) ? ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
if ! v [ " amount " ] . is_null ( ) & & v [ " amount " ] . as_i64 ( ) . is_none ( ) {
return Err ( " amount 要是整數 " . into ( ) ) ;
}
let amount = scroll_amount ( v [ " amount " ] . as_i64 ( ) ) ;
let dir = v [ " direction " ] . as_str ( ) . unwrap_or ( " " ) . trim ( ) . to_lowercase ( ) ;
let ( dy , dx ) = scroll_pixels ( & dir , amount ) . ok_or_else ( | | format! ( " 方向「 {dir} 」要是 up、down、left 或 right " ) ) ? ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
mac ::scroll ( at . as_ref ( ) . map ( | ( x , y , _ ) | ( * x , * y ) ) , dy , dx , flags_of ( v ) ) ;
Ok ( format! ( " 已捲動 {dir} × {amount} {} 。 " , at . map ( | ( _ , _ , a ) | format! ( " ,在 {a} " ) ) . unwrap_or_default ( ) ) )
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
" computer_use_drag " = > {
2026-11-05 07:48:22 +00:00
// Every point must be valid: points that failed to parse used to be dropped in
// silence, so the drag ran on fewer points and reported success at the wrong place.
let raw = v [ " path " ] . as_array ( ) . ok_or ( " 拖曳要給 path: 一個點陣, 每個點有數字 x 和 y( 用截圖上的座標) " ) ? ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
let pts : Vec < ( f64 , f64 ) > = raw . iter ( ) . map ( | p | match ( p [ " x " ] . as_f64 ( ) , p [ " y " ] . as_f64 ( ) ) {
( Some ( x ) , Some ( y ) ) = > Ok ( ( x , y ) ) ,
_ = > Err ( " 拖曳的每個點都要有數字 x 和 y( 用截圖上的座標) " . to_string ( ) ) ,
} ) . collect ::< Result < _ , _ > > ( ) ? ;
if pts . len ( ) < 2 {
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
return Err ( " 拖曳至少要兩個點(每個點要有數字 x 和 y, 用截圖上的座標) " . into ( ) ) ;
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
let path : Vec < ( f64 , f64 ) > = pts . iter ( ) . map ( | ( x , y ) | to_screen ( * x , * y , scale ) ) . collect ( ) ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
mac ::drag ( & path , button , flags_of ( v ) ) ;
// Like the original's "Dragged from (x, y) to (x, y) with left button."
let side = match button {
" right " = > " 右鍵 " ,
" middle " = > " 中鍵 " ,
_ = > " 左鍵 " ,
} ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
let ( a , b ) = ( pts [ 0 ] , pts [ pts . len ( ) - 1 ] ) ;
Ok ( format! ( " 已從 ( {:.0} , {:.0} ) 拖曳到 ( {:.0} , {:.0} )( {side} )。 " , a . 0 , a . 1 , b . 0 , b . 1 ) )
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
" computer_use_mouse_button " = > {
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
let at = place_of ( v , scale ) ? . map ( | ( x , y , _ ) | ( x , y ) ) ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
let release = v [ " action " ] . as_str ( ) = = Some ( " release " ) ;
mac ::mouse_button ( at , button , ! release ) ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
Ok ( ( if release { " 已放開滑鼠鍵。 " } else { " 已按住滑鼠鍵(記得之後放開)。 " } ) . into ( ) )
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
" computer_use_typing " = > {
let text = v [ " value " ] . as_str ( ) . unwrap_or ( " " ) ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
let submit = v [ " submit " ] . as_bool ( ) . unwrap_or ( false ) ;
if text . is_empty ( ) & & ! submit {
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
return Err ( " 請給要打的字 " . into ( ) ) ;
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
// Into a field by its number: click it first, like a person would.
let at = place_of ( & json! ( { " ref " : v [ " ref " ] } ) , scale ) ? ;
if let Some ( ( x , y , _ ) ) = & at {
mac ::click ( * x , * y , " left " , 1 , 0 ) ;
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 150 ) ) ;
}
if ! text . is_empty ( ) {
mac ::type_text ( text ) ;
}
if submit {
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 60 ) ) ;
mac ::press ( 36 , 0 , KEY_HOLD_SECS ) ;
}
Ok ( format! ( " 已打字( {} 個字) {} {} 。 " , text . chars ( ) . count ( ) , at . map ( | ( _ , _ , a ) | format! ( " 在 {a} " ) ) . unwrap_or_default ( ) , if submit { " ,再按了 return " } else { " " } ) )
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
" computer_use_press_key " = > {
let hold = v [ " hold_duration_ms " ] . as_u64 ( ) . map ( | ms | ms . min ( MAX_HOLD_MS ) as f64 / 1000.0 ) . filter ( | s | * s > 0.0 ) . unwrap_or ( KEY_HOLD_SECS ) ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
press_keys ( v [ " key " ] . as_str ( ) . unwrap_or ( " " ) , hold ) ? ;
Ok ( format! ( " 已按 {} 。 " , v [ " key " ] . as_str ( ) . unwrap_or ( " " ) ) )
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
" computer_use_open_app " = > open_app ( v [ " app " ] . as_str ( ) . unwrap_or ( " " ) ) ,
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
other = > Err ( format! ( " 沒有「 {other} 」這個工具 " ) ) ,
}
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
/// What the screen looks like now, after a step: the picture when this computer can take one,
/// else the app in front as numbered text. The bot sees the result of each step in the same
/// answer instead of spending another step (another trip to the model) on a screenshot.
fn observe ( note : String ) -> ( String , Option < String > ) {
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( SETTLE_MS ) ) ;
if mac ::screen_capture_preflight ( ) {
return match screenshot ( ) {
Ok ( ( t , img ) ) = > ( format! ( " {note} \n {t} " ) , Some ( img ) ) ,
Err ( e ) = > ( format! ( " {note} \n (看不到結果: {e} ) " ) , None ) ,
} ;
}
if controls_allowed ( ) {
if let Ok ( ( app , lines ) ) = crate ::desktop ::read_app ( " " ) {
let shown : Vec < & String > = lines . iter ( ) . take ( 120 ) . collect ( ) ;
return ( format! ( " {note} \n (這台現在拍不到畫面,改讀最前面的「 {app} 」; e 編號可以用在 ref) \n {} " , shown . iter ( ) . map ( | l | l . as_str ( ) ) . collect ::< Vec < _ > > ( ) . join ( " \n " ) ) , None ) ;
}
}
( format! ( " {note} (這台現在看不到畫面,無法確認結果) " ) , None )
}
fn run_inner ( name : & str , v : & Value ) -> Result < ( String , Option < String > ) , String > {
if ! cfg! ( any ( target_os = " macos " , target_os = " linux " ) ) {
return Err ( " 操作畫面只支援 macOS 和 Linux " . into ( ) ) ;
}
#[ cfg(target_os = " linux " ) ]
if ! crate ::desk ::available ( ) {
return Err ( crate ::desk ::describe ( ) . into ( ) ) ;
}
match name {
" computer_use_screenshot " = > return screenshot ( ) . map ( | ( t , img ) | ( t , Some ( img ) ) ) ,
" computer_use_check_permissions " = > {
#[ cfg(target_os = " linux " ) ]
return Ok ( ( crate ::desk ::describe ( ) . into ( ) , None ) ) ;
#[ allow(unreachable_code) ]
{
let see = mac ::screen_capture_preflight ( ) ;
let control = controls_allowed ( ) ;
return Ok ( ( format! ( " 螢幕錄製(看畫面): {} \n 輔助使用(滑鼠、鍵盤、讀 App) : {} {} " , if see { " 已允許 " } else { " 還沒允許 " } , if control { " 已允許 " } else { " 還沒允許 " } , if see & & control { " " } else { " \n 請使用者到「系統設定 → 隱私權與安全性」打開 AnyBot。 " } ) , None ) ) ;
}
}
" computer_use_read_app " = > {
need_controls ( ) ? ;
let ( app , lines ) = crate ::desktop ::read_app ( v [ " app " ] . as_str ( ) . unwrap_or ( " " ) ) . map_err ( plain_words ) ? ;
return Ok ( ( if lines . is_empty ( ) {
format! ( " 「 {app} 」的畫面讀不到內容(有些 App 不提供輔助使用資訊)。可以改用 computer_use_screenshot 看。 " )
} else {
format! ( " 「 {app} 」的畫面( e 編號可以用在 click / typing / scroll 的 ref) : \n {} " , lines . join ( " \n " ) )
} , None ) ) ;
}
_ = > { }
}
if ! moves_things ( name ) {
return Err ( format! ( " 沒有「 {name} 」這個工具 " ) ) ;
}
if HELD_BY_PERSON . load ( std ::sync ::atomic ::Ordering ::SeqCst ) {
return Err ( " 使用者正在自己操作這台電腦(已取得控制權)。請等他交還控制權後再操作;可以先截圖看看。 " . into ( ) ) ;
}
if name ! = " computer_use_open_app " {
need_controls ( ) ? ;
}
let note = act ( name , v , scale_here ( ) ) . map_err ( plain_words ) ? ;
// Holding a button down is the middle of something (a long press, a drag): no picture yet.
if name = = " computer_use_mouse_button " {
return Ok ( ( note , None ) ) ;
}
Ok ( observe ( note ) )
}
/// agent-desktop's error codes, as words the bot can pass on.
fn plain_words ( t : String ) -> String {
if t . contains ( " PERM_DENIED " ) {
ask_permission ( " Privacy_Accessibility " ) ;
return NO_ACCESSIBILITY . to_string ( ) ;
}
if t . contains ( " APP_NOT_FOUND " ) {
return format! ( " 找不到這個 App( 沒有開著, 或名字不對) : 先用 computer_use_open_app 打開它。( {t} ) " ) ;
}
t
}
/// The phone or another viewer acting on this screen as the person: their tap is on the picture
/// they were sent (`shot_size()`), so it maps exactly like the bot's coordinates. Not refused
/// while the person holds the screen (it is the person), and no picture back (the viewer
/// refreshes its own).
pub fn person_act ( name : & str , v : & Value ) -> Result < ( ) , String > {
need_controls ( ) ? ;
act ( name , v , scale_here ( ) ) . map ( | _ | ( ) )
}
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
/// The app a screen action will land in: the window under the point for clicks, else the
/// frontmost app. Used for the per-app permission (「AnyBot 可以看到 {app},並在其中點擊和輸入」).
pub fn target_app ( name : & str , args : & str ) -> Option < String > {
let v = args_of ( args ) ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
if let Some ( app ) = v [ " app " ] . as_str ( ) . filter ( | a | ! a . trim ( ) . is_empty ( ) & & matches! ( name , " computer_use_read_app " | " computer_use_open_app " ) ) {
2026-11-04 09:19:59 +00:00
return Some ( app . trim ( ) . to_string ( ) ) ;
}
if name = = " computer_use_read_app " {
return frontmost_app ( ) ;
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
if let Some ( r ) = v [ " ref " ] . as_str ( ) . filter ( | r | ! r . trim ( ) . is_empty ( ) ) {
return crate ::desktop ::spot ( r ) . ok ( ) . map ( | s | s . app ) . or_else ( crate ::desktop ::last_app ) ;
2026-11-04 09:19:59 +00:00
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
let s = scale_here ( ) ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
let point = match name {
" computer_use_click " | " computer_use_mouse_move " | " computer_use_scroll " | " computer_use_mouse_button " if ! v [ " x " ] . is_null ( ) = > Some ( to_screen ( v [ " x " ] . as_f64 ( ) ? , v [ " y " ] . as_f64 ( ) ? , s ) ) ,
" computer_use_drag " = > v [ " path " ] . as_array ( ) . and_then ( | a | a . first ( ) ) . and_then ( | p | Some ( to_screen ( p [ " x " ] . as_f64 ( ) ? , p [ " y " ] . as_f64 ( ) ? , s ) ) ) ,
_ = > None ,
} ;
match point {
Some ( ( x , y ) ) = > mac ::window_at ( x , y ) . map ( | w | w . 0 ) ,
None = > mac ::front_app ( ) . map ( | w | w . 0 ) ,
}
}
/// Screenshot pixels per screen point (for the demonstration recorder).
pub fn points_to_shot ( ) -> f64 {
let ( pw , _ ) = mac ::screen_points ( ) ;
let ( px , py ) = mac ::screen_pixels ( ) ;
let ( fw , _ , _ ) = fit ( px . max ( 1 ) , py . max ( 1 ) ) ;
if pw > 0.0 { fw as f64 / pw } else { 1.0 }
}
/// The person's own clicks and keys can be recorded (教它一項任務): macOS, Linux on X11.
/// Wayland doesn't let apps watch other apps' input.
pub fn demo_supported ( ) -> bool {
#[ cfg(target_os = " linux " ) ]
{
crate ::desk ::kind ( ) = = crate ::desk ::Kind ::X11
}
#[ cfg(not(target_os = " linux " )) ]
{
cfg! ( target_os = " macos " )
}
}
/// This system's screen can be seen and used (macOS; Linux on X11 or Wayland).
pub fn screen_supported ( ) -> bool {
#[ cfg(target_os = " linux " ) ]
{
crate ::desk ::available ( )
}
#[ cfg(not(target_os = " linux " )) ]
{
cfg! ( target_os = " macos " )
}
}
pub fn screen_capture_ok ( ) -> bool {
mac ::screen_capture_preflight ( )
}
#[ cfg_attr(not(target_os = " macos " ), allow(dead_code)) ]
pub fn frontmost_app ( ) -> Option < String > {
mac ::front_app ( ) . map ( | w | w . 0 )
}
#[ cfg_attr(not(target_os = " macos " ), allow(dead_code)) ]
pub fn app_at_point ( x : f64 , y : f64 ) -> Option < String > {
mac ::window_at ( x , y ) . map ( | w | w . 0 )
}
fn screenshot ( ) -> Result < ( String , String ) , String > {
2026-11-05 08:57:44 +00:00
if ! mac ::screen_capture_preflight ( ) {
ask_permission ( " Privacy_ScreenCapture " ) ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
return Err ( NO_SCREEN_RECORDING . into ( ) ) ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
let img = capture_screen ( ) ? ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
// Always the same size for this screen (shot_size), so coordinates map without state.
let ( fw , fh ) = shot_size ( ) ;
let img = if img . dimensions ( ) ! = ( fw , fh ) { image ::imageops ::resize ( & img , fw , fh , image ::imageops ::FilterType ::Triangle ) } else { img } ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
let jpeg = encode_jpeg ( & img ) . ok_or ( " 截圖太大了,沒辦法送出 " ) ? ;
let b64 = base64 ::engine ::general_purpose ::STANDARD . encode ( & jpeg ) ;
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
Ok ( ( format! ( " 螢幕截圖( {fw} × {fh} )。座標用這張圖上的像素。 " ) , b64 ) )
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
/// The whole screen as RGB (backing pixels).
pub fn capture_screen ( ) -> Result < image ::RgbImage , String > {
#[ cfg(target_os = " linux " ) ]
return crate ::desk ::capture ( ) ;
#[ allow(unreachable_code) ]
{
2026-11-05 07:48:22 +00:00
// A random tail: two bots screenshotting the same millisecond must not delete each
// other's file (pid+time alone collides).
let file = std ::env ::temp_dir ( ) . join ( format! ( " anybot-screen- {} - {} - {} .png " , std ::process ::id ( ) , crate ::model ::now_ms ( ) , crate ::httpd ::random_hex ( 4 ) ) ) ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
let st = std ::process ::Command ::new ( " screencapture " ) . args ( [ " -x " , " -m " , " -t " , " png " ] ) . arg ( & file ) . status ( ) . map_err ( | e | e . to_string ( ) ) ? ;
let bytes = std ::fs ::read ( & file ) ;
let _ = std ::fs ::remove_file ( & file ) ;
if ! st . success ( ) {
return Err ( " 截圖失敗 " . into ( ) ) ;
}
Ok ( image ::load_from_memory ( & bytes . map_err ( | e | format! ( " 截圖失敗: {e} " ) ) ? ) . map_err ( | e | format! ( " 截圖讀不出來: {e} " ) ) ? . to_rgb8 ( ) )
}
}
#[ cfg(target_os = " linux " ) ]
use crate ::desk as mac ;
#[ cfg(target_os = " macos " ) ]
mod mac {
use super ::* ;
use std ::ffi ::c_void ;
use std ::time ::Duration ;
#[ repr(C) ]
#[ derive(Clone, Copy) ]
struct CGPoint {
x : f64 ,
y : f64 ,
}
#[ repr(C) ]
#[ derive(Clone, Copy) ]
struct CGSize {
width : f64 ,
height : f64 ,
}
#[ repr(C) ]
#[ derive(Clone, Copy) ]
struct CGRect {
origin : CGPoint ,
size : CGSize ,
}
type Ev = * mut c_void ;
type Src = * mut c_void ;
#[ link(name = " CoreGraphics " , kind = " framework " ) ]
extern " C " {
fn CGMainDisplayID ( ) -> u32 ;
fn CGDisplayBounds ( d : u32 ) -> CGRect ;
fn CGPreflightScreenCaptureAccess ( ) -> bool ;
fn CGRequestScreenCaptureAccess ( ) -> bool ;
fn CGEventSourceCreate ( state : i32 ) -> Src ;
fn CGEventSourceSetLocalEventsSuppressionInterval ( src : Src , secs : f64 ) ;
fn CGEventCreateMouseEvent ( src : Src , kind : u32 , at : CGPoint , button : u32 ) -> Ev ;
fn CGEventCreateKeyboardEvent ( src : Src , key : u16 , down : bool ) -> Ev ;
fn CGEventKeyboardSetUnicodeString ( e : Ev , len : usize , s : * const u16 ) ;
fn CGEventSetFlags ( e : Ev , flags : u64 ) ;
fn CGEventSetIntegerValueField ( e : Ev , field : u32 , v : i64 ) ;
fn CGEventPost ( tap : u32 , e : Ev ) ;
fn CGEventCreateScrollWheelEvent ( src : Src , units : u32 , count : u32 , wheel1 : i32 , .. . ) -> Ev ;
fn CGDisplayPixelsWide ( d : u32 ) -> usize ;
fn CGDisplayPixelsHigh ( d : u32 ) -> usize ;
fn CGWindowListCopyWindowInfo ( option : u32 , relative_to : u32 ) -> * const c_void ;
fn CGRectMakeWithDictionaryRepresentation ( dict : * const c_void , rect : * mut CGRect ) -> bool ;
static kCGWindowOwnerName : * const c_void ;
static kCGWindowOwnerPID : * const c_void ;
static kCGWindowBounds : * const c_void ;
static kCGWindowLayer : * const c_void ;
}
#[ link(name = " CoreFoundation " , kind = " framework " ) ]
extern " C " {
fn CFArrayGetCount ( a : * const c_void ) -> isize ;
fn CFArrayGetValueAtIndex ( a : * const c_void , i : isize ) -> * const c_void ;
fn CFDictionaryGetValue ( d : * const c_void , k : * const c_void ) -> * const c_void ;
fn CFNumberGetValue ( n : * const c_void , kind : isize , out : * mut c_void ) -> bool ;
fn CFStringGetCString ( s : * const c_void , buf : * mut std ::ffi ::c_char , len : isize , enc : u32 ) -> bool ;
}
/// On-screen windows front to back: (owner app, pid, bounds), normal layer only.
fn windows ( ) -> Vec < ( String , i32 , CGRect ) > {
let mut out = vec! [ ] ;
unsafe {
// kCGWindowListOptionOnScreenOnly | kCGWindowListExcludeDesktopElements
let list = CGWindowListCopyWindowInfo ( 1 | 16 , 0 ) ;
if list . is_null ( ) {
return out ;
}
for i in 0 .. CFArrayGetCount ( list ) {
let d = CFArrayGetValueAtIndex ( list , i ) ;
let mut layer : i32 = - 1 ;
let l = CFDictionaryGetValue ( d , kCGWindowLayer ) ;
if l . is_null ( ) | | ! CFNumberGetValue ( l , 3 , & mut layer as * mut i32 as * mut c_void ) | | layer ! = 0 {
continue ;
}
let mut pid : i32 = 0 ;
let p = CFDictionaryGetValue ( d , kCGWindowOwnerPID ) ;
if ! p . is_null ( ) {
CFNumberGetValue ( p , 3 , & mut pid as * mut i32 as * mut c_void ) ;
}
let mut name = String ::new ( ) ;
let n = CFDictionaryGetValue ( d , kCGWindowOwnerName ) ;
if ! n . is_null ( ) {
let mut buf = [ 0 u8 ; 256 ] ;
if CFStringGetCString ( n , buf . as_mut_ptr ( ) as * mut std ::ffi ::c_char , buf . len ( ) as isize , 0x0800_0100 ) {
let end = buf . iter ( ) . position ( | b | * b = = 0 ) . unwrap_or ( buf . len ( ) ) ;
name = String ::from_utf8_lossy ( & buf [ .. end ] ) . to_string ( ) ;
}
}
let mut r = CGRect { origin : CGPoint { x : 0.0 , y : 0.0 } , size : CGSize { width : 0.0 , height : 0.0 } } ;
let b = CFDictionaryGetValue ( d , kCGWindowBounds ) ;
if ! b . is_null ( ) {
CGRectMakeWithDictionaryRepresentation ( b , & mut r ) ;
}
out . push ( ( name , pid , r ) ) ;
}
CFRelease ( list ) ;
}
out
}
/// The app whose window is under a screen point.
pub fn window_at ( x : f64 , y : f64 ) -> Option < ( String , i32 ) > {
windows ( ) . into_iter ( ) . find ( | ( _ , _ , r ) | x > = r . origin . x & & y > = r . origin . y & & x < r . origin . x + r . size . width & & y < r . origin . y + r . size . height ) . map ( | ( n , p , _ ) | ( n , p ) )
}
/// The frontmost app (its frontmost normal window).
pub fn front_app ( ) -> Option < ( String , i32 ) > {
windows ( ) . into_iter ( ) . find ( | ( n , _ , r ) | ! n . is_empty ( ) & & r . size . width > 1.0 ) . map ( | ( n , p , _ ) | ( n , p ) )
}
pub fn screen_capture_preflight ( ) -> bool {
unsafe { CGPreflightScreenCaptureAccess ( ) }
}
pub fn screen_pixels ( ) -> ( u32 , u32 ) {
let d = unsafe { CGMainDisplayID ( ) } ;
let ( pw , ph ) = screen_points ( ) ;
// CGDisplayPixelsWide reports points on Retina; screencapture gives backing pixels.
let ( w , h ) = unsafe { ( CGDisplayPixelsWide ( d ) as f64 , CGDisplayPixelsHigh ( d ) as f64 ) } ;
let scale = backing_scale ( ) ;
let _ = ( w , h ) ;
( ( pw * scale ) as u32 , ( ph * scale ) as u32 )
}
fn backing_scale ( ) -> f64 {
static S : std ::sync ::OnceLock < f64 > = std ::sync ::OnceLock ::new ( ) ;
* S . get_or_init ( | | {
let file = std ::env ::temp_dir ( ) . join ( format! ( " anybot-scale- {} .png " , std ::process ::id ( ) ) ) ;
let ok = std ::process ::Command ::new ( " screencapture " ) . args ( [ " -x " , " -m " , " -t " , " png " , " -R0,0,10,10 " ] ) . arg ( & file ) . status ( ) . map ( | s | s . success ( ) ) . unwrap_or ( false ) ;
let w = if ok { image ::image_dimensions ( & file ) . map ( | d | d . 0 ) . unwrap_or ( 10 ) } else { 10 } ;
let _ = std ::fs ::remove_file ( & file ) ;
( w as f64 / 10.0 ) . max ( 1.0 )
} )
}
/// Press or release a mouse button where the pointer is (or at a point).
pub fn mouse_button ( at : Option < ( f64 , f64 ) > , button : & str , press : bool ) {
let ( down , up , _ , b ) = kinds ( button ) ;
let p = match at {
Some ( ( x , y ) ) = > {
move_to ( x , y ) ;
gap ( ) ;
CGPoint { x , y }
}
None = > current_point ( ) ,
} ;
let src = Source ::new ( ) ;
let e = unsafe { CGEventCreateMouseEvent ( src . 0 , if press { down } else { up } , p , b ) } ;
if ! e . is_null ( ) {
unsafe { CGEventSetIntegerValueField ( e , CLICK_STATE , 1 ) } ;
}
post ( e , 0 ) ;
}
fn current_point ( ) -> CGPoint {
extern " C " {
fn CGEventCreate ( src : Src ) -> Ev ;
fn CGEventGetLocation ( e : Ev ) -> CGPoint ;
}
unsafe {
let e = CGEventCreate ( std ::ptr ::null_mut ( ) ) ;
if e . is_null ( ) {
return CGPoint { x : 0.0 , y : 0.0 } ;
}
let p = CGEventGetLocation ( e ) ;
CFRelease ( e ) ;
p
}
}
#[ link(name = " CoreFoundation " , kind = " framework " ) ]
extern " C " {
fn CFRelease ( x : * const c_void ) ;
}
const HID_TAP : u32 = 0 ;
/// kCGEventSourceStateHIDSystemState, as CUActuator uses (CGEventSourceCreate(1)).
const HID_STATE : i32 = 1 ;
const CLICK_STATE : u32 = 1 ;
const MOVED : u32 = 5 ;
/// kCGScrollEventUnitPixel, as CGEventCreateScrollWheelEvent2(_, 0, 2, …) in the original.
const SCROLL_PIXEL : u32 = 0 ;
/// An event source like the original's: HID system state, local-event suppression 0 s, so
/// the person's own mouse and keys aren't frozen after each synthetic event.
struct Source ( Src ) ;
impl Source {
fn new ( ) -> Self {
let s = unsafe { CGEventSourceCreate ( HID_STATE ) } ;
if ! s . is_null ( ) {
unsafe { CGEventSourceSetLocalEventsSuppressionInterval ( s , 0.0 ) } ;
}
Source ( s )
}
}
impl Drop for Source {
fn drop ( & mut self ) {
if ! self . 0. is_null ( ) {
unsafe { CFRelease ( self . 0 as * const c_void ) }
}
}
}
fn post ( e : Ev , flags : u64 ) {
if e . is_null ( ) {
return ;
}
unsafe {
CGEventSetFlags ( e , flags ) ;
CGEventPost ( HID_TAP , e ) ;
CFRelease ( e ) ;
}
}
fn gap ( ) {
std ::thread ::sleep ( Duration ::from_millis ( EVENT_GAP_MS ) ) ;
}
fn sleep_secs ( s : f64 ) {
std ::thread ::sleep ( Duration ::from_secs_f64 ( s ) ) ;
}
/// (down, up, dragged) event kinds and button number.
fn kinds ( button : & str ) -> ( u32 , u32 , u32 , u32 ) {
match button {
" right " = > ( 3 , 4 , 7 , 1 ) ,
" middle " = > ( 25 , 26 , 27 , 2 ) ,
_ = > ( 1 , 2 , 6 , 0 ) ,
}
}
pub fn screen_capture_allowed ( ) -> bool {
unsafe { CGPreflightScreenCaptureAccess ( ) | | CGRequestScreenCaptureAccess ( ) }
}
pub fn screen_points ( ) -> ( f64 , f64 ) {
let r = unsafe { CGDisplayBounds ( CGMainDisplayID ( ) ) } ;
( r . size . width , r . size . height )
}
pub fn move_to ( x : f64 , y : f64 ) {
let src = Source ::new ( ) ;
post ( unsafe { CGEventCreateMouseEvent ( src . 0 , MOVED , CGPoint { x , y } , 0 ) } , 0 ) ;
}
/// CUActuator.click: press, hold 0.03 s, release; 0.08 s between the clicks of a multi-click.
pub fn click ( x : f64 , y : f64 , button : & str , count : i64 , flags : u64 ) {
let ( down , up , _ , b ) = kinds ( button ) ;
let at = CGPoint { x , y } ;
move_to ( x , y ) ;
gap ( ) ;
let src = Source ::new ( ) ;
for n in 1 ..= count {
if n > 1 {
sleep_secs ( CLICK_REPEAT_GAP_SECS ) ;
}
for ( i , kind ) in [ down , up ] . into_iter ( ) . enumerate ( ) {
if i = = 1 {
sleep_secs ( CLICK_HOLD_SECS ) ;
}
let e = unsafe { CGEventCreateMouseEvent ( src . 0 , kind , at , b ) } ;
if ! e . is_null ( ) {
unsafe { CGEventSetIntegerValueField ( e , CLICK_STATE , n ) } ;
}
post ( e , flags ) ;
}
}
}
/// CURemoteEventPoster.drag(path:button:steps:holdSeconds:): press at the first point (click
/// state 1), hold, then walk each segment in `DRAG_STEPS` interpolated dragged events, then
/// release at the last point.
pub fn drag ( path : & [ ( f64 , f64 ) ] , button : & str , flags : u64 ) {
let ( down , up , dragged , b ) = kinds ( button ) ;
let ( x0 , y0 ) = path [ 0 ] ;
move_to ( x0 , y0 ) ;
gap ( ) ;
let src = Source ::new ( ) ;
let event = | kind : u32 , x : f64 , y : f64 | {
let e = unsafe { CGEventCreateMouseEvent ( src . 0 , kind , CGPoint { x , y } , b ) } ;
if ! e . is_null ( ) {
unsafe { CGEventSetIntegerValueField ( e , CLICK_STATE , 1 ) } ;
}
post ( e , flags ) ;
} ;
event ( down , x0 , y0 ) ;
sleep_secs ( DRAG_HOLD_SECS ) ;
for ( x , y ) in drag_points ( path ) {
gap ( ) ;
event ( dragged , x , y ) ;
}
let ( xn , yn ) = path [ path . len ( ) - 1 ] ;
event ( up , xn , yn ) ;
}
/// handleScroll closure @0x10009e998: a mouse-moved event at the spot (the pointer's own
/// position when no x/y), then one pixel-unit scroll event with 2 wheels at that spot.
pub fn scroll ( at : Option < ( f64 , f64 ) > , dy : i32 , dx : i32 , flags : u64 ) {
if let Some ( ( x , y ) ) = at {
move_to ( x , y ) ;
gap ( ) ;
}
let src = Source ::new ( ) ;
post ( unsafe { CGEventCreateScrollWheelEvent ( src . 0 , SCROLL_PIXEL , 2 , dy , dx ) } , flags ) ;
}
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
/// Type text so that any app takes it, whatever the keyboard layout or input method: paste
/// it (⌘V) and put the clipboard back. Text sent as key events (the original's
/// CUTypedCharacters, key 128 carrying the characters) went into the input method instead
/// when one was on — with 注音 every letter vanished while return still arrived, so a
/// command typed into a terminal ran empty. Falls back to those key events without a
/// clipboard.
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
pub fn type_text ( text : & str ) {
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
if ! paste ( text ) {
type_as_keys ( text ) ;
}
}
/// Wait for the app to read the clipboard before it is put back (Electron apps read it a
/// moment after ⌘V; estimated).
const PASTE_READ_MS : u64 = 350 ;
fn paste ( text : & str ) -> bool {
let Ok ( mut cb ) = arboard ::Clipboard ::new ( ) else { return false } ;
// What the person had there comes back afterwards (text or a picture).
let saved_text = cb . get_text ( ) . ok ( ) ;
let saved_image = if saved_text . is_none ( ) { cb . get_image ( ) . ok ( ) } else { None } ;
if cb . set_text ( text . to_string ( ) ) . is_err ( ) {
return false ;
}
std ::thread ::sleep ( Duration ::from_millis ( 40 ) ) ;
press ( 9 , FLAG_CMD , KEY_HOLD_SECS ) ;
std ::thread ::sleep ( Duration ::from_millis ( PASTE_READ_MS ) ) ;
match ( saved_text , saved_image ) {
( Some ( t ) , _ ) = > {
let _ = cb . set_text ( t ) ;
}
( None , Some ( i ) ) = > {
let _ = cb . set_image ( i ) ;
}
_ = > {
let _ = cb . clear ( ) ;
}
}
true
}
/// CUTypedCharacters.postChunks: each chunk on key 128, down, 0.035 s, up; 0.02 s between chunks.
fn type_as_keys ( text : & str ) {
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
let src = Source ::new ( ) ;
let chunks = utf16_chunks ( text ) ;
for ( i , chunk ) in chunks . iter ( ) . enumerate ( ) {
if i > 0 {
sleep_secs ( KEY_GAP_SECS ) ;
}
for down in [ true , false ] {
if ! down {
sleep_secs ( KEY_HOLD_SECS ) ;
}
let e = unsafe { CGEventCreateKeyboardEvent ( src . 0 , TYPE_KEY_CODE , down ) } ;
if ! e . is_null ( ) {
unsafe { CGEventKeyboardSetUnicodeString ( e , chunk . len ( ) , chunk . as_ptr ( ) ) } ;
}
post ( e , 0 ) ;
}
}
}
/// handlePressKey: key down, hold (0.035 s unless told), key up.
pub fn press ( code : u16 , flags : u64 , hold_secs : f64 ) {
let src = Source ::new ( ) ;
post ( unsafe { CGEventCreateKeyboardEvent ( src . 0 , code , true ) } , flags ) ;
sleep_secs ( hold_secs ) ;
post ( unsafe { CGEventCreateKeyboardEvent ( src . 0 , code , false ) } , flags ) ;
}
}
#[ cfg(not(any(target_os = " macos " , target_os = " linux " ))) ]
mod mac {
pub fn screen_capture_allowed ( ) -> bool {
false
}
pub fn screen_points ( ) -> ( f64 , f64 ) {
( 0.0 , 0.0 )
}
pub fn move_to ( _ : f64 , _ : f64 ) { }
pub fn click ( _ : f64 , _ : f64 , _ : & str , _ : i64 , _ : u64 ) { }
pub fn drag ( _ : & [ ( f64 , f64 ) ] , _ : & str , _ : u64 ) { }
pub fn scroll ( _ : Option < ( f64 , f64 ) > , _ : i32 , _ : i32 , _ : u64 ) { }
pub fn type_text ( _ : & str ) { }
pub fn press ( _ : u16 , _ : u64 , _ : f64 ) { }
pub fn window_at ( _ : f64 , _ : f64 ) -> Option < ( String , i32 ) > {
None
}
pub fn front_app ( ) -> Option < ( String , i32 ) > {
None
}
pub fn screen_capture_preflight ( ) -> bool {
false
}
pub fn screen_pixels ( ) -> ( u32 , u32 ) {
( 0 , 0 )
}
pub fn mouse_button ( _ : Option < ( f64 , f64 ) > , _ : & str , _ : bool ) { }
}
#[ cfg(test) ]
mod tests {
use super ::* ;
#[ test ]
fn screenshots_fit_like_the_original ( ) {
assert_eq! ( fit ( 2940 , 1912 ) , ( 1024 , 666 , true ) ) ;
assert_eq! ( fit ( 1024 , 640 ) , ( 1024 , 640 , false ) ) ;
assert_eq! ( fit ( 800 , 600 ) , ( 800 , 600 , false ) ) ;
assert_eq! ( fit ( 4000 , 7 ) , ( 4000 , 7 , false ) ) ;
// A very wide strip keeps its short edge at 8.
assert_eq! ( fit ( 4000 , 10 ) , ( 3200 , 8 , true ) ) ;
}
#[ test ]
fn jpeg_stays_under_the_cap ( ) {
let img = image ::RgbImage ::from_fn ( 1024 , 640 , | x , y | image ::Rgb ( [ ( x % 256 ) as u8 , ( y % 256 ) as u8 , ( ( x ^ y ) % 256 ) as u8 ] ) ) ;
let j = encode_jpeg ( & img ) . unwrap ( ) ;
assert! ( j . len ( ) < = MAX_IMAGE_BYTES & & j . starts_with ( & [ 0xFF , 0xD8 ] ) ) ;
}
#[ test ]
fn shortcuts_are_parsed ( ) {
assert_eq! ( parse_combo ( " cmd+c " ) . unwrap ( ) , ( 8 , FLAG_CMD ) ) ;
assert_eq! ( parse_combo ( " cmd+shift+t " ) . unwrap ( ) , ( 17 , FLAG_CMD | FLAG_SHIFT ) ) ;
assert_eq! ( parse_combo ( " Return " ) . unwrap ( ) , ( 36 , 0 ) ) ;
assert_eq! ( parse_combo ( " ctrl+option+space " ) . unwrap ( ) , ( 49 , FLAG_CONTROL | FLAG_OPTION ) ) ;
assert! ( parse_combo ( " hyper+x " ) . is_err ( ) ) ;
assert! ( parse_combo ( " " ) . is_err ( ) ) ;
}
#[ test ]
fn typed_text_is_chunked_like_the_original ( ) {
let c = utf16_chunks ( & " a " . repeat ( 45 ) ) ;
assert_eq! ( c . iter ( ) . map ( Vec ::len ) . collect ::< Vec < _ > > ( ) , vec! [ 20 , 20 , 5 ] ) ;
// An emoji (2 UTF-16 units) is never split across chunks.
let c = utf16_chunks ( & format! ( " {} 😀b " , " a " . repeat ( 19 ) ) ) ;
assert_eq! ( c . iter ( ) . map ( Vec ::len ) . collect ::< Vec < _ > > ( ) , vec! [ 19 , 3 ] ) ;
assert_eq! ( utf16_chunks ( " 你好 " ) . len ( ) , 1 ) ;
assert! ( utf16_chunks ( " " ) . is_empty ( ) ) ;
}
#[ test ]
fn scrolls_like_the_original ( ) {
assert_eq! ( scroll_amount ( None ) , 3 ) ;
assert_eq! ( scroll_amount ( Some ( 0 ) ) , 1 ) ;
assert_eq! ( scroll_amount ( Some ( 99 ) ) , 20 ) ;
assert_eq! ( scroll_pixels ( " down " , 3 ) , Some ( ( - 48 , 0 ) ) ) ;
assert_eq! ( scroll_pixels ( " left " , 2 ) , Some ( ( 0 , 32 ) ) ) ;
assert_eq! ( scroll_pixels ( " sideways " , 1 ) , None ) ;
}
#[ test ]
fn drags_walk_six_steps_per_segment ( ) {
let pts = drag_points ( & [ ( 0.0 , 0.0 ) , ( 60.0 , 0.0 ) , ( 60.0 , 30.0 ) ] ) ;
assert_eq! ( pts . len ( ) , 2 * DRAG_STEPS ) ;
assert_eq! ( pts [ 0 ] , ( 10.0 , 0.0 ) ) ;
assert_eq! ( pts [ DRAG_STEPS - 1 ] , ( 60.0 , 0.0 ) ) ;
assert_eq! ( * pts . last ( ) . unwrap ( ) , ( 60.0 , 30.0 ) ) ;
}
#[ test ]
fn a_press_can_be_several_keys ( ) {
One way to act on a screen: a real click or keystroke, wherever the bot looked
- Acting by an app's number (read_app's e7) is now a real click at that control's spot, the
same path as screenshot coordinates. agent-desktop only reads; its own actions (a second set
with its own key names and failures: no typing in terminals, "/" and "`" failing off the main
thread) are gone, with computer_use_select, background clicks and press_key's app.
- Every action comes back with the screen after it (or, with no picture allowed, the front
app's numbered controls), so the bot no longer spends a step on a screenshot to check.
- Coordinates map without remembering a screenshot: the picture's size depends only on the
screen. The phone's taps go straight to the screen as the person's, in the pixels of the
picture it was sent; they used to borrow the bot's last screenshot scale and landed 1.25x off
on the bots' computer, or failed with "screenshot first".
- Mac typing pastes (⌘V, clipboard put back): with 注音 on, text sent as key events vanished
while return arrived, so commands typed into a terminal ran empty. Bare printing keys
("slash", "r") are typed the same way; keys like "slash" and "grave" have names.
- The bots' Docker computer: one docker exec per step (action, settle, picture), never
`mousemove --sync` (it waits forever when the pointer is already there: a second click on
the same spot hung two minutes), open_app (chromium = the browser browser_* drive, xterm).
Tests: box_screen_is_exact (real Docker: bot and phone coordinates exact, same spot twice in
under 2 s, ASCII + Chinese + named key typed), Calculator by numbers and Terminal typing under
注音 through the signed app, remote_e2e 28/28, Linux build + 151 tests + x11_live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 13:51:19 +00:00
assert_eq! ( key_plan ( " cmd+a delete " ) . unwrap ( ) , vec! [ Key ::Combo ( 0 , FLAG_CMD ) , Key ::Combo ( 51 , 0 ) ] ) ;
assert! ( key_plan ( " " ) . is_err ( ) ) ;
// Keys by the names models use, and characters without a key typed instead.
assert_eq! ( key_plan ( " cmd+grave " ) . unwrap ( ) , vec! [ Key ::Combo ( 50 , FLAG_CMD ) ] ) ;
// A printing key alone is its character (typed, so 注音 can't swallow it).
assert_eq! ( key_plan ( " slash " ) . unwrap ( ) , vec! [ Key ::Char ( " / " . into ( ) ) ] ) ;
assert_eq! ( key_plan ( " / @ r return " ) . unwrap ( ) , vec! [ Key ::Char ( " / " . into ( ) ) , Key ::Char ( " @ " . into ( ) ) , Key ::Char ( " r " . into ( ) ) , Key ::Combo ( 36 , 0 ) ] ) ;
assert! ( key_plan ( " hyper+x " ) . is_err ( ) ) ;
}
#[ test ]
fn pictures_and_clicks_share_one_size ( ) {
// The picture's size depends only on the screen, so a tap or a click maps the same
// whoever saw which screenshot last (a 2940× 1912 Retina screen of 1470× 956 points).
let ( w , h , _ ) = fit ( 2940 , 1912 ) ;
let s = ( 1470.0 / w as f64 , 956.0 / h as f64 ) ;
let ( x , y ) = to_screen ( 512.0 , 333.0 , s ) ;
assert! ( ( x - 735.0 ) . abs ( ) < 0.01 & & ( y - 478.0 ) . abs ( ) < 1.0 ) ;
AnyBot: first commit of the working desktop app
Rust/egui rebuild of Grok Bot as of 2026-11-04: chat with streaming
providers (xAI, OpenAI, Claude, Gemini, OpenRouter, OpenCode Go, Ollama,
custom), tools and approvals, the bot's Docker computer, computer use on
macOS / Linux X11 / Wayland, AnyBot's browser with a full DevTools-based
toolset, the encrypted vault, pinned grid and sidebar sections, Markdown
with Mermaid diagrams, phone access through the relay, and the Linux
build and test scripts.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uirha3oEtWeHh58zA7ErHa
2026-11-04 08:51:51 +00:00
}
#[ test ]
fn screenshot_pixels_map_to_screen_points ( ) {
// A 1470-point-wide screen sent as a 1024-wide picture.
let s = ( 1470.0 / 1024.0 , 956.0 / 666.0 ) ;
let ( x , y ) = to_screen ( 512.0 , 333.0 , s ) ;
assert! ( ( x - 735.0 ) . abs ( ) < 0.01 & & ( y - 478.0 ) . abs ( ) < 0.01 ) ;
}
}
#[ cfg(test) ]
mod window_tests {
/// `cargo test --release -- --ignored finds_apps_on_screen --nocapture`
#[ test ]
#[ ignore ]
fn finds_apps_on_screen ( ) {
let front = super ::frontmost_app ( ) ;
println! ( " front: {front:?} " ) ;
println! ( " at (200,200): {:?} " , super ::app_at_point ( 200.0 , 200.0 ) ) ;
println! ( " points→shot: {} " , super ::points_to_shot ( ) ) ;
assert! ( front . is_some ( ) ) ;
}
}