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)
This commit is contained in:
marcus.hinz
2026-07-04 13:45:02 +02:00
parent 9b008cbf51
commit 62ce802465
55 changed files with 110 additions and 28 deletions
+58 -22
View File
@@ -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",
+4 -4
View File
@@ -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"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 1012 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 956 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

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>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

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>
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 867 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

+39 -2
View File
@@ -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();