feat(mcp): Add 5 developer enhancements (git, logs, clipboard watcher, ast skeleton, nvim ghost text)
This commit is contained in:
1 parent
ccc268d807
commit
0e9748f797
27 files changed
+1401
-174
No files matched your search
+50
-7
@@ -23,6 +23,7 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
|
||||
)
|
||||
.route("/ws", get(ws_handler))
|
||||
.route("/health", get(health_handler))
|
||||
.route("/ping", get(|| async move { (axum::http::StatusCode::OK, "pong") }))
|
||||
.route("/nvim/telemetry", post(nvim_telemetry_handler))
|
||||
.route("/api/terminal/history", get(crate::api::telemetry::get_terminal_history_handler))
|
||||
.route("/terminal/telemetry", post(crate::api::telemetry::terminal_telemetry_handler))
|
||||
@@ -50,13 +51,12 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
|
||||
return (axum::http::StatusCode::UNAUTHORIZED, "Unauthorized").into_response();
|
||||
}
|
||||
|
||||
std::thread::spawn(|| {
|
||||
tracing::info!(
|
||||
"Received shutdown request via /shutdown endpoint. Exiting process cleanly."
|
||||
);
|
||||
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||
std::process::exit(0);
|
||||
});
|
||||
tracing::info!(
|
||||
"Received shutdown request via /shutdown endpoint. Initiating graceful shutdown."
|
||||
);
|
||||
if let Some(tx) = state.shutdown_tx.lock().unwrap().take() {
|
||||
let _ = tx.send(());
|
||||
}
|
||||
(axum::http::StatusCode::OK, "Shutting down...").into_response()
|
||||
},
|
||||
),
|
||||
@@ -279,6 +279,48 @@ pub fn create_router(app_state: Arc<AppState>) -> Router {
|
||||
}
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/clipboard/capture",
|
||||
post({
|
||||
move || async move {
|
||||
let result = tokio::task::spawn_blocking(move || -> Result<String, String> {
|
||||
let mut clipboard = arboard::Clipboard::new()
|
||||
.map_err(|e| format!("Failed to access clipboard: {}", e))?;
|
||||
|
||||
let image_data = clipboard.get_image()
|
||||
.map_err(|e| format!("No image found in clipboard (or unsupported format): {}", e))?;
|
||||
|
||||
let img: image::RgbaImage = image::ImageBuffer::from_raw(
|
||||
image_data.width as u32,
|
||||
image_data.height as u32,
|
||||
image_data.bytes.into_owned(),
|
||||
)
|
||||
.ok_or("Failed to parse raw clipboard image data")?;
|
||||
|
||||
let mut dynamic_img = image::DynamicImage::ImageRgba8(img);
|
||||
let max_dim = 1024;
|
||||
if dynamic_img.width() > max_dim || dynamic_img.height() > max_dim {
|
||||
dynamic_img = dynamic_img.resize(max_dim, max_dim, image::imageops::FilterType::Lanczos3);
|
||||
}
|
||||
let rgb_img = dynamic_img.into_rgb8();
|
||||
|
||||
use base64::{Engine as _, engine::general_purpose::STANDARD};
|
||||
let mut cursor = std::io::Cursor::new(Vec::new());
|
||||
rgb_img.write_to(&mut cursor, image::ImageFormat::Jpeg).map_err(|e| e.to_string())?;
|
||||
let b64 = STANDARD.encode(cursor.into_inner());
|
||||
Ok(b64)
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task panic: {}", e))
|
||||
.and_then(|r| r);
|
||||
|
||||
match result {
|
||||
Ok(b64) => axum::Json(serde_json::json!({ "success": true, "image": format!("data:image/jpeg;base64,{}", b64) })),
|
||||
Err(e) => axum::Json(serde_json::json!({ "success": false, "error": e }))
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
.with_state(app_state)
|
||||
}
|
||||
|
||||
@@ -318,3 +360,4 @@ mod tests {
|
||||
assert_eq!(response.status(), 200);
|
||||
}
|
||||
}
|
||||
// Force recompile
|
||||
Reference in new issue
Block a user