# Desktop app session (`desktop-session` feature)
[← README](README.md)
If the user is signed in to the Grok Bot desktop app, this module can reuse that login, so
they don't have to paste a token.
```toml
[dependencies]
grokbot_api = { version = "1", features = ["desktop-session"] }
```
> **Privacy:** this reads and decrypts the user's saved credentials. Only call it when the
> user explicitly asks, for example from a "sign in with the desktop app" command. Never log
> or display the decrypted values. `Debug` on `DesktopCredential` and `DesktopConfig` redacts
> them.
## Loading a credential
```rust,no_run
use grokbot_api::desktop::{DesktopConfig, DesktopCredential, Platform};
use grokbot_api::{Client, ClientOptions, Policy};
async fn from_desktop() -> grokbot_api::Result<Client> {
let platform = Platform::current().expect("Linux, macOS or Windows");
let cfg = DesktopConfig::new(platform);
let mut opts = ClientOptions::default();
if let Some(v) = cfg.detect_app_version() {
opts.client_version = v; // send the same version as the installed app
}
match cfg.load_credential(None, &Policy::default())? {
DesktopCredential::Session(session) => Client::new(session, opts),
DesktopCredential::CursorToken(token) => Client::connect_cursor(&token, opts).await,
}
}
```
`load_credential(key, policy)` tries the gateway session first, then the Cursor token:
1. **`gateway-descriptor.json`**, a saved gateway session, if it exists. The result is
`DesktopCredential::Session`.
- v1 descriptors have one entry (`"default"`); v2 descriptors can have several.
- Pass `Some(name)` to choose one; with several and `None` you get `AMBIGUOUS_ENTRIES`.
- `entries()` lists their names without decrypting anything.
- The decrypted `{baseUrl, token, headers?}` must pass `policy.check_gateway`.
2. **Otherwise `sand-secrets.json`**, the app's Cursor accounts. The result is
`DesktopCredential::CursorToken`; exchange it with `connect_cursor` or `ensure_sandbox`.
- `sand_accounts()` lists accounts from plaintext metadata only. Each has a `key`, an
`active` flag and a label like `1) 3f9a2c1b…`.
- `key` may be a 1-based number, a full account key or a unique prefix of 4+ characters.
- Only the chosen account's token is decrypted.
- `sand_key_for(choice)` gives you the stable account key to save, so a later re-read
picks the same account.
## Where the files are
| Linux | `$XDG_CONFIG_HOME/Grok Bot`, else `~/.config/Grok Bot` |
| macOS | `~/Library/Application Support/Grok Bot` |
| Windows | `%APPDATA%\Grok Bot` |
`descriptor_path` and `sand_secrets_path` (free functions and `DesktopConfig` methods) return
the full paths. `present()` and `sand_present()` check that they exist.
## Encryption
Both files use Chromium's "Safe Storage" (OSCrypt) format, the same as other Electron apps:
| Linux `v11` | Secret Service password via `secret-tool lookup xdg:schema chrome_libsecret_os_crypt_password_v2 application "Grok Bot"`; PBKDF2-HMAC-SHA1, salt `saltysalt`, 1 iteration, AES-128-CBC, IV of 16 spaces |
| Linux `v10` | Keyring-less "basic text" backend: fixed password `peanuts`, otherwise as `v11` (no keychain access needed) |
| macOS `v10` | Login Keychain item `Grok Bot Safe Storage` via `/usr/bin/security find-generic-password -w -s "Grok Bot Safe Storage"`; 1003 iterations |
| Windows `v10` | AES-256-GCM with the DPAPI-protected key from `Local State` (Windows builds only) |
The lower-level functions are public for tools and tests:
- `parse_entries`
- `needs_keychain`
- `decrypt_safe_storage`
- `decrypt_windows`
- `session_from_clear`
- `DesktopConfig::local_state_key` (Windows only)
## Testing and customising
Every `DesktopConfig` field is public, so tests can point it at a temporary directory:
```rust
use grokbot_api::desktop::{DesktopConfig, Platform};
let mut cfg = DesktopConfig::new(Platform::Linux);
cfg.home = std::env::temp_dir().join("fake-home");