Hand-Icon als App-/Tray-Icon, App heißt ai-control, Tests für Keychain/Key-Status
- Icons komplett aus hand.png regeneriert (tauri icon) - Tray-Icon explizit per include_bytes (cargo trackt die Datei) und als Template-Icon für helle/dunkle Menüleiste; tauri-Feature image-png - Cargo-Paket app -> ai-control, Fenstertitel ai-control - Tests: keychain_service-Suffix gegen verifizierten Referenzwert, hasCredentials-Status (Key vorhanden/leer/fehlt)
@@ -28,6 +28,28 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ai-control"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"getrandom 0.2.17",
|
||||||
|
"log",
|
||||||
|
"objc2",
|
||||||
|
"objc2-app-kit",
|
||||||
|
"objc2-foundation",
|
||||||
|
"portable-pty",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"sha2",
|
||||||
|
"tauri",
|
||||||
|
"tauri-build",
|
||||||
|
"tauri-plugin-autostart",
|
||||||
|
"tauri-plugin-dialog",
|
||||||
|
"tauri-plugin-log",
|
||||||
|
"ureq",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alloc-no-stdlib"
|
name = "alloc-no-stdlib"
|
||||||
version = "2.0.4"
|
version = "2.0.4"
|
||||||
@@ -75,28 +97,6 @@ version = "1.0.103"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "app"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"base64 0.22.1",
|
|
||||||
"getrandom 0.2.17",
|
|
||||||
"log",
|
|
||||||
"objc2",
|
|
||||||
"objc2-app-kit",
|
|
||||||
"objc2-foundation",
|
|
||||||
"portable-pty",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"sha2",
|
|
||||||
"tauri",
|
|
||||||
"tauri-build",
|
|
||||||
"tauri-plugin-autostart",
|
|
||||||
"tauri-plugin-dialog",
|
|
||||||
"tauri-plugin-log",
|
|
||||||
"ureq",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.8"
|
version = "0.7.8"
|
||||||
@@ -327,6 +327,12 @@ version = "1.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.12.0"
|
version = "1.12.0"
|
||||||
@@ -1648,6 +1654,19 @@ dependencies = [
|
|||||||
"icu_properties",
|
"icu_properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.25.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder-lite",
|
||||||
|
"moxcms",
|
||||||
|
"num-traits",
|
||||||
|
"png 0.18.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "1.9.3"
|
version = "1.9.3"
|
||||||
@@ -1944,6 +1963,16 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moxcms"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"pxfm",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "muda"
|
name = "muda"
|
||||||
version = "0.19.3"
|
version = "0.19.3"
|
||||||
@@ -2574,6 +2603,12 @@ dependencies = [
|
|||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pxfm"
|
||||||
|
version = "0.1.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.39.4"
|
version = "0.39.4"
|
||||||
@@ -3488,6 +3523,7 @@ dependencies = [
|
|||||||
"gtk",
|
"gtk",
|
||||||
"heck 0.5.0",
|
"heck 0.5.0",
|
||||||
"http",
|
"http",
|
||||||
|
"image",
|
||||||
"jni",
|
"jni",
|
||||||
"libc",
|
"libc",
|
||||||
"log",
|
"log",
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "app"
|
name = "ai-control"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "A Tauri App"
|
description = "Pool- und Session-Verwaltung für Claude Code"
|
||||||
authors = ["you"]
|
authors = ["marcus.hinz"]
|
||||||
license = ""
|
license = ""
|
||||||
repository = ""
|
repository = ""
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
@@ -21,7 +21,7 @@ tauri-build = { version = "2.6.3", features = [] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
tauri = { version = "2.11.3", features = ["tray-icon"] }
|
tauri = { version = "2.11.3", features = ["tray-icon", "image-png"] }
|
||||||
tauri-plugin-log = "2"
|
tauri-plugin-log = "2"
|
||||||
tauri-plugin-autostart = "2"
|
tauri-plugin-autostart = "2"
|
||||||
tauri-plugin-dialog = "2"
|
tauri-plugin-dialog = "2"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 1012 B |
|
After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 9.0 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 956 B |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||||
|
<background android:drawable="@color/ic_launcher_background"/>
|
||||||
|
</adaptive-icon>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 5.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 4.5 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="ic_launcher_background">#fff</color>
|
||||||
|
</resources>
|
||||||
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 867 B |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 5.0 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 6.3 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
@@ -837,7 +837,7 @@ fn main_builder() -> tauri::Builder<tauri::Wry> {
|
|||||||
// Fenster im Code statt in tauri.conf.json, damit der Terminal-Prozess
|
// Fenster im Code statt in tauri.conf.json, damit der Terminal-Prozess
|
||||||
// (gleiches Binary, gleiche Config) kein main-Fenster anlegt.
|
// (gleiches Binary, gleiche Config) kein main-Fenster anlegt.
|
||||||
tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::default())
|
tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::default())
|
||||||
.title("AI Control")
|
.title("ai-control")
|
||||||
.inner_size(800.0, 600.0)
|
.inner_size(800.0, 600.0)
|
||||||
.visible(false)
|
.visible(false)
|
||||||
.build()?;
|
.build()?;
|
||||||
@@ -846,7 +846,13 @@ fn main_builder() -> tauri::Builder<tauri::Wry> {
|
|||||||
let quit = tauri::menu::MenuItem::with_id(app, "quit", "Beenden", true, None::<&str>)?;
|
let quit = tauri::menu::MenuItem::with_id(app, "quit", "Beenden", true, None::<&str>)?;
|
||||||
let menu = tauri::menu::Menu::with_items(app, &[&open, &quit])?;
|
let menu = tauri::menu::Menu::with_items(app, &[&open, &quit])?;
|
||||||
tauri::tray::TrayIconBuilder::with_id("main")
|
tauri::tray::TrayIconBuilder::with_id("main")
|
||||||
.icon(app.default_window_icon().unwrap().clone())
|
// include_bytes! statt default_window_icon: cargo trackt die Datei,
|
||||||
|
// neu generierte Icons landen damit sicher im nächsten Build.
|
||||||
|
.icon(tauri::image::Image::from_bytes(include_bytes!(
|
||||||
|
"../icons/128x128.png"
|
||||||
|
))?)
|
||||||
|
// Template-Icon: macOS färbt es passend zur Menüleiste ein.
|
||||||
|
.icon_as_template(true)
|
||||||
.menu(&menu)
|
.menu(&menu)
|
||||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||||
"open" => {
|
"open" => {
|
||||||
@@ -1078,6 +1084,37 @@ mod tests {
|
|||||||
assert!(!p.pool_dir("kunde").exists());
|
assert!(!p.pool_dir("kunde").exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Referenzwert vom echten System: privateDefault → 096c4ef9
|
||||||
|
/// (verifiziert 2026-07-03 gegen den von claude angelegten Eintrag).
|
||||||
|
#[test]
|
||||||
|
fn keychain_service_suffix() {
|
||||||
|
let p = Paths { home: PathBuf::from("/Users/marcus.hinz") };
|
||||||
|
assert_eq!(
|
||||||
|
keychain_service(&p, "privateDefault"),
|
||||||
|
"Claude Code-credentials-096c4ef9"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pool_key_status() {
|
||||||
|
let p = tmp_paths();
|
||||||
|
create_apikey_pool_in(&p, "kunde", "sk-1").unwrap();
|
||||||
|
make_oauth_pool(&p, "privat");
|
||||||
|
|
||||||
|
let pools = list_pools_in(&p).unwrap();
|
||||||
|
assert!(pools.iter().find(|x| x.name == "kunde").unwrap().has_credentials);
|
||||||
|
// oauth: Keychain wird im Listing bewusst nicht geprüft → immer true.
|
||||||
|
assert!(pools.iter().find(|x| x.name == "privat").unwrap().has_credentials);
|
||||||
|
|
||||||
|
fs::write(p.pool_dir("kunde").join(APIKEY_FILE), "\n").unwrap();
|
||||||
|
let pools = list_pools_in(&p).unwrap();
|
||||||
|
assert!(!pools.iter().find(|x| x.name == "kunde").unwrap().has_credentials);
|
||||||
|
|
||||||
|
fs::remove_file(p.pool_dir("kunde").join(APIKEY_FILE)).unwrap();
|
||||||
|
let pools = list_pools_in(&p).unwrap();
|
||||||
|
assert!(!pools.iter().find(|x| x.name == "kunde").unwrap().has_credentials);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pool_loeschen_loest_zuordnungen() {
|
fn pool_loeschen_loest_zuordnungen() {
|
||||||
let p = tmp_paths();
|
let p = tmp_paths();
|
||||||
|
|||||||