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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "alloc-no-stdlib"
|
||||
version = "2.0.4"
|
||||
@@ -75,28 +97,6 @@ version = "1.0.103"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.8"
|
||||
@@ -327,6 +327,12 @@ version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.12.0"
|
||||
@@ -1648,6 +1654,19 @@ dependencies = [
|
||||
"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]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
@@ -1944,6 +1963,16 @@ dependencies = [
|
||||
"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]]
|
||||
name = "muda"
|
||||
version = "0.19.3"
|
||||
@@ -2574,6 +2603,12 @@ dependencies = [
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.39.4"
|
||||
@@ -3488,6 +3523,7 @@ dependencies = [
|
||||
"gtk",
|
||||
"heck 0.5.0",
|
||||
"http",
|
||||
"image",
|
||||
"jni",
|
||||
"libc",
|
||||
"log",
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
[package]
|
||||
name = "app"
|
||||
name = "ai-control"
|
||||
version = "0.1.0"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
description = "Pool- und Session-Verwaltung für Claude Code"
|
||||
authors = ["marcus.hinz"]
|
||||
license = ""
|
||||
repository = ""
|
||||
edition = "2021"
|
||||
@@ -21,7 +21,7 @@ tauri-build = { version = "2.6.3", features = [] }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
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-autostart = "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
|
||||
// (gleiches Binary, gleiche Config) kein main-Fenster anlegt.
|
||||
tauri::WebviewWindowBuilder::new(app, "main", tauri::WebviewUrl::default())
|
||||
.title("AI Control")
|
||||
.title("ai-control")
|
||||
.inner_size(800.0, 600.0)
|
||||
.visible(false)
|
||||
.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 menu = tauri::menu::Menu::with_items(app, &[&open, &quit])?;
|
||||
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)
|
||||
.on_menu_event(|app, event| match event.id.as_ref() {
|
||||
"open" => {
|
||||
@@ -1078,6 +1084,37 @@ mod tests {
|
||||
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]
|
||||
fn pool_loeschen_loest_zuordnungen() {
|
||||
let p = tmp_paths();
|
||||
|
||||