Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/build-windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ jobs:
- name: Build frontend
run: npm run build

- name: Test Rust backend
run: cargo test --manifest-path src-tauri/Cargo.toml

- name: Build Tauri Windows app and installers
run: npx tauri build --bundles nsis,msi

Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ Realtime status needs the WorkBuddy plugin to be enabled. After enabling it, res
The bundled plugin is status-only:

- Includes `status-hook.mjs`
- Includes `status-hook.cmd` on Windows to locate WorkBuddy's bundled Node runtime
- Invokes `status-hook.mjs` through WorkBuddy's bundled `node` command on Windows
- Accepts WorkBuddy's directory-linked plugin installs even when its CLI does not create a cache registry entry
- Migrates obsolete local marketplace settings to WorkBuddy's current directory-source schema
- Retains `status-hook.cmd` as a manual fallback launcher
- Includes `status-runtime.mjs`
- Does not install `approval-hook.mjs`

Expand Down
4 changes: 2 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
</div>
</dl>

<div id="setup-row" class="setup-row" hidden>
<div id="setup-row" class="setup-row" data-status="disabled">
<button id="install-plugin" type="button">启用实时状态</button>
<span id="setup-message">需要安装 WorkBuddy 状态插件</span>
</div>
Expand All @@ -73,7 +73,7 @@
<span></span>
</div>
</section>
<div class="stand"></div>
<div id="stand" class="stand"></div>
</main>

<script type="module" src="/src/main.ts"></script>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "workbuddy-buddy",
"version": "0.1.0",
"version": "0.1.1",
"description": "Privacy-safe WorkBuddy lifecycle bridge and fail-open desktop-pet approval UI.",
"author": {
"name": "FlashFamily",
Expand Down
14 changes: 7 additions & 7 deletions src-tauri/resources/workbuddy-plugin/hooks.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"hooks": [
{
"type": "command",
"command": "cmd /d /s /c \"\"${CODEBUDDY_PLUGIN_ROOT}\\scripts\\status-hook.cmd\" SessionStart\"",
"command": "node \"${CODEBUDDY_PLUGIN_ROOT}/scripts/status-hook.mjs\" SessionStart",
"timeout": 10
}
]
Expand All @@ -17,7 +17,7 @@
"hooks": [
{
"type": "command",
"command": "cmd /d /s /c \"\"${CODEBUDDY_PLUGIN_ROOT}\\scripts\\status-hook.cmd\" UserPromptSubmit\"",
"command": "node \"${CODEBUDDY_PLUGIN_ROOT}/scripts/status-hook.mjs\" UserPromptSubmit",
"timeout": 10
}
]
Expand All @@ -29,7 +29,7 @@
"hooks": [
{
"type": "command",
"command": "cmd /d /s /c \"\"${CODEBUDDY_PLUGIN_ROOT}\\scripts\\status-hook.cmd\" PreToolUse\"",
"command": "node \"${CODEBUDDY_PLUGIN_ROOT}/scripts/status-hook.mjs\" PreToolUse",
"timeout": 10
}
]
Expand All @@ -41,7 +41,7 @@
"hooks": [
{
"type": "command",
"command": "cmd /d /s /c \"\"${CODEBUDDY_PLUGIN_ROOT}\\scripts\\status-hook.cmd\" PostToolUse\"",
"command": "node \"${CODEBUDDY_PLUGIN_ROOT}/scripts/status-hook.mjs\" PostToolUse",
"timeout": 10
}
]
Expand All @@ -52,7 +52,7 @@
"hooks": [
{
"type": "command",
"command": "cmd /d /s /c \"\"${CODEBUDDY_PLUGIN_ROOT}\\scripts\\status-hook.cmd\" PermissionRequest\"",
"command": "node \"${CODEBUDDY_PLUGIN_ROOT}/scripts/status-hook.mjs\" PermissionRequest",
"timeout": 10
}
]
Expand All @@ -63,7 +63,7 @@
"hooks": [
{
"type": "command",
"command": "cmd /d /s /c \"\"${CODEBUDDY_PLUGIN_ROOT}\\scripts\\status-hook.cmd\" Notification\"",
"command": "node \"${CODEBUDDY_PLUGIN_ROOT}/scripts/status-hook.mjs\" Notification",
"timeout": 10
}
]
Expand All @@ -74,7 +74,7 @@
"hooks": [
{
"type": "command",
"command": "cmd /d /s /c \"\"${CODEBUDDY_PLUGIN_ROOT}\\scripts\\status-hook.cmd\" Stop\"",
"command": "node \"${CODEBUDDY_PLUGIN_ROOT}/scripts/status-hook.mjs\" Stop",
"timeout": 10
}
]
Expand Down
125 changes: 103 additions & 22 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,33 @@ use std::time::Duration;

use tauri::menu::{Menu, MenuItem};
use tauri::tray::TrayIconBuilder;
use tauri::{Manager, PhysicalPosition};
use tauri::{Emitter, Manager, PhysicalPosition};

#[derive(Default)]
struct HitRect {
#[derive(Clone, Copy, Default)]
struct Rect {
x: f64,
y: f64,
w: f64,
h: f64,
}

impl Rect {
fn contains(self, x: f64, y: f64) -> bool {
self.w > 0.0
&& self.h > 0.0
&& x >= self.x
&& x <= self.x + self.w
&& y >= self.y
&& y <= self.y + self.h
}
}

#[derive(Clone, Copy, Default)]
struct HitRegions {
pet: Rect,
panel: Rect,
}

fn pos_file() -> Option<std::path::PathBuf> {
dirs::config_dir().map(|dir| dir.join("Agent Buddy").join("workbuddy-pos"))
}
Expand All @@ -35,10 +52,34 @@ fn write_pos(x: i32, y: i32) {
}

#[tauri::command]
fn set_hit_rect(app: tauri::AppHandle, x: f64, y: f64, w: f64, h: f64) {
if let Some(state) = app.try_state::<Mutex<HitRect>>() {
if let Ok(mut rect) = state.lock() {
*rect = HitRect { x, y, w, h };
#[allow(clippy::too_many_arguments)]
fn set_hit_regions(
app: tauri::AppHandle,
pet_x: f64,
pet_y: f64,
pet_w: f64,
pet_h: f64,
panel_x: f64,
panel_y: f64,
panel_w: f64,
panel_h: f64,
) {
if let Some(state) = app.try_state::<Mutex<HitRegions>>() {
if let Ok(mut regions) = state.lock() {
*regions = HitRegions {
pet: Rect {
x: pet_x,
y: pet_y,
w: pet_w,
h: pet_h,
},
panel: Rect {
x: panel_x,
y: panel_y,
w: panel_w,
h: panel_h,
},
};
}
}
}
Expand All @@ -63,9 +104,9 @@ pub fn run() {
let _ = win.set_focus();
}
}))
.manage(Mutex::new(HitRect::default()))
.manage(Mutex::new(HitRegions::default()))
.invoke_handler(tauri::generate_handler![
set_hit_rect,
set_hit_regions,
set_pet_visible,
workbuddy::workbuddy_snapshot,
workbuddy::install_workbuddy_status_plugin,
Expand Down Expand Up @@ -101,6 +142,7 @@ pub fn run() {
std::thread::spawn(move || {
let mut last_ignore: Option<bool> = None;
let mut last_saved = read_pos();
let mut interaction_active = false;
let mut tick: u32 = 0;
loop {
std::thread::sleep(Duration::from_millis(30));
Expand All @@ -110,18 +152,20 @@ pub fn run() {

match (handle.cursor_position(), win.outer_position()) {
(Ok(cursor), Ok(window_pos)) => {
let rect = handle
.try_state::<Mutex<HitRect>>()
.and_then(|state| state.lock().ok().map(|r| (r.x, r.y, r.w, r.h)));
let inside = match rect {
Some((x, y, w, h)) if w > 0.0 && h > 0.0 => {
let rx = cursor.x - window_pos.x as f64;
let ry = cursor.y - window_pos.y as f64;
rx >= x && rx <= x + w && ry >= y && ry <= y + h
let regions = handle
.try_state::<Mutex<HitRegions>>()
.and_then(|state| state.lock().ok().map(|regions| *regions));
let scale = win.scale_factor().unwrap_or(1.0).max(f64::EPSILON);
let rx = (cursor.x - window_pos.x as f64) / scale;
let ry = (cursor.y - window_pos.y as f64) / scale;
interaction_active = match regions {
Some(regions) if regions.pet.w > 0.0 => {
regions.pet.contains(rx, ry)
|| (interaction_active && regions.panel.contains(rx, ry))
}
_ => true,
};
let ignore = !inside;
let ignore = !interaction_active;
if Some(ignore) != last_ignore {
let _ = win.set_ignore_cursor_events(ignore);
last_ignore = Some(ignore);
Expand Down Expand Up @@ -149,19 +193,56 @@ pub fn run() {

let show = MenuItem::with_id(app, "show", "显示桌宠", true, None::<&str>)?;
let hide = MenuItem::with_id(app, "hide", "隐藏桌宠", true, None::<&str>)?;
let install =
MenuItem::with_id(app, "install-plugin", "启用 WorkBuddy 实时状态", true, None::<&str>)?;
let plugin_status = workbuddy::current_plugin_status();
let install_label = if plugin_status.is_ready() {
"✓ WorkBuddy 实时状态已启用"
} else {
"启用 WorkBuddy 实时状态"
};
let install = MenuItem::with_id(
app,
"install-plugin",
install_label,
!plugin_status.is_ready(),
None::<&str>,
)?;
let quit = MenuItem::with_id(app, "quit", "退出 Agent Buddy", true, None::<&str>)?;
let menu = Menu::with_items(app, &[&show, &hide, &install, &quit])?;
let install_for_event = install.clone();
let mut tray = TrayIconBuilder::new()
.tooltip("Agent Buddy")
.menu(&menu)
.show_menu_on_left_click(true)
.on_menu_event(|app, event| match event.id.as_ref() {
.on_menu_event(move |app, event| match event.id.as_ref() {
"show" => set_pet_visible(app.clone(), true),
"hide" => set_pet_visible(app.clone(), false),
"install-plugin" => {
let _ = workbuddy::install_workbuddy_status_plugin();
let _ = install_for_event.set_enabled(false);
let _ = install_for_event.set_text("正在启用 WorkBuddy 实时状态…");
set_pet_visible(app.clone(), true);

let app_handle = app.clone();
let install_item = install_for_event.clone();
std::thread::spawn(move || {
match workbuddy::install_workbuddy_status_plugin_blocking() {
Ok(status) => {
let _ = install_item.set_text("✓ WorkBuddy 实时状态已启用");
let _ = install_item.set_enabled(false);
let _ = app_handle.emit(
"workbuddy-plugin-status",
serde_json::json!({ "status": status }),
);
}
Err(error) => {
let _ = install_item.set_text("启用失败,点击重试");
let _ = install_item.set_enabled(true);
let _ = app_handle.emit(
"workbuddy-plugin-status",
serde_json::json!({ "error": error }),
);
}
}
});
}
"quit" => app.exit(0),
_ => {}
Expand Down
Loading
Loading