mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-30 01:19:34 +00:00
* Add Google Calendar and Gmail WASM tools, and /add-tool skill Scaffold two new WASM tools that share a single Google OAuth token: - google-calendar: list/get/create/update/delete calendar events - gmail: list/search/get/send/draft/reply/trash emails Both tools use the sandboxed WIT interface with strict HTTP allowlists, credential injection, and rate limiting. OAuth config requests only the minimum scopes needed (calendar.events, gmail.modify, gmail.compose). Also adds the /add-tool skill for scaffolding future WASM or built-in tools with all boilerplate wired up. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Document WASM vs MCP server decision guide in CLAUDE.md Co-Authored-By: Claude Opus 4.6 <[email protected]> * Add Google Drive WASM tool with full file and sharing management Supports 12 actions: list/get/download/upload/update files, create folders, delete/trash, share/list/remove permissions, and list shared drives. Works with both personal and organizational drives via the corpora parameter. Uses shared google_oauth_token for auth. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Add Google Sheets, Docs, and Slides WASM tools Three new Google Workspace tools sharing google_oauth_token: - Sheets: create spreadsheets, read/write/append values, manage sheets, format cells - Docs: create/read/edit documents, text formatting, paragraphs, tables, lists - Slides: create/edit presentations, shapes, images, text formatting, thumbnails, templates Also adds tools-src/TOOLS.md tracking implementation status. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Add Telegram WASM tool with direct MTProto over HTTPS Replace TDLight Docker dependency with pure-Rust grammers crates for direct encrypted MTProto communication to Telegram's web transport endpoints. No middleware, no Docker needed. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Gitignore Cargo.lock files in WASM tools Library crates should not commit lock files. Consolidate per-tool .gitignore into a single one at wasm-tools/ level. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Flatten tools-src/wasm-tools/ into tools-src/ All tools are WASM, the extra nesting added no value. Moves all tool crates up one level, updates WIT paths and documentation references. Co-Authored-By: Claude Opus 4.6 <[email protected]> * Fix Slack tool: add OAuth auth, URL encoding, pin wit-bindgen - Add OAuth 2.0 auth section to Slack capabilities with proper scopes and manual fallback instructions - URL-encode query parameters in GET requests to prevent injection - Remove dead SlackApiError struct - Pin wit-bindgen to =0.36 across all WASM tools for Rust 1.86 compat - Update add-tool template with pinned version Co-Authored-By: Claude Opus 4.6 <[email protected]> --------- Co-authored-by: Claude Opus 4.6 <[email protected]>
424 lines
16 KiB
Rust
424 lines
16 KiB
Rust
//! Google Drive WASM Tool for IronClaw.
|
|
//!
|
|
//! Provides Google Drive integration for searching, accessing, uploading,
|
|
//! sharing, and organizing files and folders. Supports both personal and
|
|
//! shared (organizational) drives.
|
|
//!
|
|
//! # Capabilities Required
|
|
//!
|
|
//! - HTTP: `www.googleapis.com/drive/v3/*` and `www.googleapis.com/upload/drive/v3/*`
|
|
//! - Secrets: `google_oauth_token` (shared OAuth 2.0 token, injected automatically)
|
|
//!
|
|
//! # Supported Actions
|
|
//!
|
|
//! - `list_files`: Search/list files with Drive query syntax and corpora selection
|
|
//! - `get_file`: Get file metadata
|
|
//! - `download_file`: Download file content as text (exports Google Docs/Sheets)
|
|
//! - `upload_file`: Upload a text file (multipart)
|
|
//! - `update_file`: Rename, move, star, or update description
|
|
//! - `create_folder`: Create a new folder
|
|
//! - `delete_file`: Permanently delete a file
|
|
//! - `trash_file`: Move to trash
|
|
//! - `share_file`: Share with a user (reader, commenter, writer, organizer)
|
|
//! - `list_permissions`: See who has access
|
|
//! - `remove_permission`: Revoke access
|
|
//! - `list_shared_drives`: List organizational shared drives
|
|
//!
|
|
//! # Example Usage
|
|
//!
|
|
//! ```json
|
|
//! {"action": "list_files", "query": "name contains 'report' and mimeType = 'application/pdf'"}
|
|
//! {"action": "list_files", "corpora": "drive", "drive_id": "0ABcd...", "query": "trashed = false"}
|
|
//! {"action": "share_file", "file_id": "abc123", "email": "[email protected]", "role": "writer"}
|
|
//! ```
|
|
|
|
mod api;
|
|
mod types;
|
|
|
|
use types::GoogleDriveAction;
|
|
|
|
wit_bindgen::generate!({
|
|
world: "sandboxed-tool",
|
|
path: "../../wit/tool.wit",
|
|
});
|
|
|
|
struct GoogleDriveTool;
|
|
|
|
impl exports::near::agent::tool::Guest for GoogleDriveTool {
|
|
fn execute(req: exports::near::agent::tool::Request) -> exports::near::agent::tool::Response {
|
|
match execute_inner(&req.params) {
|
|
Ok(result) => exports::near::agent::tool::Response {
|
|
output: Some(result),
|
|
error: None,
|
|
},
|
|
Err(e) => exports::near::agent::tool::Response {
|
|
output: None,
|
|
error: Some(e),
|
|
},
|
|
}
|
|
}
|
|
|
|
fn schema() -> String {
|
|
r#"{
|
|
"type": "object",
|
|
"required": ["action"],
|
|
"oneOf": [
|
|
{
|
|
"properties": {
|
|
"action": { "const": "list_files" },
|
|
"query": {
|
|
"type": "string",
|
|
"description": "Drive search query. Examples: \"name contains 'report'\", \"mimeType = 'application/pdf'\", \"'folderId' in parents\", \"sharedWithMe = true\""
|
|
},
|
|
"page_size": {
|
|
"type": "integer",
|
|
"description": "Max results (default: 25, max: 1000)",
|
|
"default": 25
|
|
},
|
|
"order_by": {
|
|
"type": "string",
|
|
"description": "Sort order (e.g., 'modifiedTime desc', 'name')"
|
|
},
|
|
"corpora": {
|
|
"type": "string",
|
|
"enum": ["user", "drive", "domain", "allDrives"],
|
|
"description": "Search scope: 'user' (personal, default), 'drive' (specific shared drive), 'domain' (org-wide), 'allDrives' (everything)",
|
|
"default": "user"
|
|
},
|
|
"drive_id": {
|
|
"type": "string",
|
|
"description": "Shared drive ID (required when corpora is 'drive')"
|
|
},
|
|
"page_token": {
|
|
"type": "string",
|
|
"description": "Token for next page of results"
|
|
}
|
|
},
|
|
"required": ["action"]
|
|
},
|
|
{
|
|
"properties": {
|
|
"action": { "const": "get_file" },
|
|
"file_id": {
|
|
"type": "string",
|
|
"description": "The file ID"
|
|
}
|
|
},
|
|
"required": ["action", "file_id"]
|
|
},
|
|
{
|
|
"properties": {
|
|
"action": { "const": "download_file" },
|
|
"file_id": {
|
|
"type": "string",
|
|
"description": "The file ID to download"
|
|
},
|
|
"export_mime_type": {
|
|
"type": "string",
|
|
"description": "Export format for Google Workspace files (e.g., 'text/plain', 'text/csv', 'application/pdf')"
|
|
}
|
|
},
|
|
"required": ["action", "file_id"]
|
|
},
|
|
{
|
|
"properties": {
|
|
"action": { "const": "upload_file" },
|
|
"name": {
|
|
"type": "string",
|
|
"description": "File name"
|
|
},
|
|
"content": {
|
|
"type": "string",
|
|
"description": "File content (text)"
|
|
},
|
|
"mime_type": {
|
|
"type": "string",
|
|
"description": "MIME type (default: 'text/plain')",
|
|
"default": "text/plain"
|
|
},
|
|
"parent_id": {
|
|
"type": "string",
|
|
"description": "Parent folder ID (omit for root)"
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "File description"
|
|
}
|
|
},
|
|
"required": ["action", "name", "content"]
|
|
},
|
|
{
|
|
"properties": {
|
|
"action": { "const": "update_file" },
|
|
"file_id": {
|
|
"type": "string",
|
|
"description": "The file ID to update"
|
|
},
|
|
"name": {
|
|
"type": "string",
|
|
"description": "New file name"
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "New description"
|
|
},
|
|
"move_to_parent": {
|
|
"type": "string",
|
|
"description": "Move file to this folder ID"
|
|
},
|
|
"starred": {
|
|
"type": "boolean",
|
|
"description": "Star or unstar the file"
|
|
}
|
|
},
|
|
"required": ["action", "file_id"]
|
|
},
|
|
{
|
|
"properties": {
|
|
"action": { "const": "create_folder" },
|
|
"name": {
|
|
"type": "string",
|
|
"description": "Folder name"
|
|
},
|
|
"parent_id": {
|
|
"type": "string",
|
|
"description": "Parent folder ID (omit for root)"
|
|
},
|
|
"description": {
|
|
"type": "string",
|
|
"description": "Folder description"
|
|
}
|
|
},
|
|
"required": ["action", "name"]
|
|
},
|
|
{
|
|
"properties": {
|
|
"action": { "const": "delete_file" },
|
|
"file_id": {
|
|
"type": "string",
|
|
"description": "The file ID to permanently delete"
|
|
}
|
|
},
|
|
"required": ["action", "file_id"]
|
|
},
|
|
{
|
|
"properties": {
|
|
"action": { "const": "trash_file" },
|
|
"file_id": {
|
|
"type": "string",
|
|
"description": "The file ID to move to trash"
|
|
}
|
|
},
|
|
"required": ["action", "file_id"]
|
|
},
|
|
{
|
|
"properties": {
|
|
"action": { "const": "share_file" },
|
|
"file_id": {
|
|
"type": "string",
|
|
"description": "The file ID to share"
|
|
},
|
|
"email": {
|
|
"type": "string",
|
|
"description": "Recipient email address"
|
|
},
|
|
"role": {
|
|
"type": "string",
|
|
"enum": ["reader", "commenter", "writer", "organizer"],
|
|
"description": "Permission level (default: 'reader')",
|
|
"default": "reader"
|
|
},
|
|
"message": {
|
|
"type": "string",
|
|
"description": "Optional message in sharing notification"
|
|
}
|
|
},
|
|
"required": ["action", "file_id", "email"]
|
|
},
|
|
{
|
|
"properties": {
|
|
"action": { "const": "list_permissions" },
|
|
"file_id": {
|
|
"type": "string",
|
|
"description": "The file ID to check permissions for"
|
|
}
|
|
},
|
|
"required": ["action", "file_id"]
|
|
},
|
|
{
|
|
"properties": {
|
|
"action": { "const": "remove_permission" },
|
|
"file_id": {
|
|
"type": "string",
|
|
"description": "The file ID"
|
|
},
|
|
"permission_id": {
|
|
"type": "string",
|
|
"description": "The permission ID to remove (get from list_permissions)"
|
|
}
|
|
},
|
|
"required": ["action", "file_id", "permission_id"]
|
|
},
|
|
{
|
|
"properties": {
|
|
"action": { "const": "list_shared_drives" },
|
|
"page_size": {
|
|
"type": "integer",
|
|
"description": "Max results (default: 25)",
|
|
"default": 25
|
|
}
|
|
},
|
|
"required": ["action"]
|
|
}
|
|
]
|
|
}"#
|
|
.to_string()
|
|
}
|
|
|
|
fn description() -> String {
|
|
"Google Drive integration for searching, accessing, uploading, sharing, and organizing \
|
|
files and folders. Supports personal drives and shared (organizational) drives via the \
|
|
corpora parameter. Can search with Drive query syntax, download text files, upload new \
|
|
files, manage folder structure, and control sharing permissions. Requires a Google OAuth \
|
|
token with the drive scope."
|
|
.to_string()
|
|
}
|
|
}
|
|
|
|
fn execute_inner(params: &str) -> Result<String, String> {
|
|
if !crate::near::agent::host::secret_exists("google_oauth_token") {
|
|
return Err(
|
|
"Google OAuth token not configured. Run `ironclaw tool auth google-drive` to set up \
|
|
OAuth, or set the GOOGLE_OAUTH_TOKEN environment variable."
|
|
.to_string(),
|
|
);
|
|
}
|
|
|
|
let action: GoogleDriveAction =
|
|
serde_json::from_str(params).map_err(|e| format!("Invalid parameters: {}", e))?;
|
|
|
|
crate::near::agent::host::log(
|
|
crate::near::agent::host::LogLevel::Info,
|
|
&format!("Executing Google Drive action: {:?}", action),
|
|
);
|
|
|
|
let result = match action {
|
|
GoogleDriveAction::ListFiles {
|
|
query,
|
|
page_size,
|
|
order_by,
|
|
corpora,
|
|
drive_id,
|
|
page_token,
|
|
} => {
|
|
let result = api::list_files(
|
|
query.as_deref(),
|
|
page_size,
|
|
order_by.as_deref(),
|
|
&corpora,
|
|
drive_id.as_deref(),
|
|
page_token.as_deref(),
|
|
)?;
|
|
serde_json::to_string(&result).map_err(|e| e.to_string())?
|
|
}
|
|
|
|
GoogleDriveAction::GetFile { file_id } => {
|
|
let result = api::get_file(&file_id)?;
|
|
serde_json::to_string(&result).map_err(|e| e.to_string())?
|
|
}
|
|
|
|
GoogleDriveAction::DownloadFile {
|
|
file_id,
|
|
export_mime_type,
|
|
} => {
|
|
let result = api::download_file(&file_id, export_mime_type.as_deref())?;
|
|
serde_json::to_string(&result).map_err(|e| e.to_string())?
|
|
}
|
|
|
|
GoogleDriveAction::UploadFile {
|
|
name,
|
|
content,
|
|
mime_type,
|
|
parent_id,
|
|
description,
|
|
} => {
|
|
let result = api::upload_file(
|
|
&name,
|
|
&content,
|
|
&mime_type,
|
|
parent_id.as_deref(),
|
|
description.as_deref(),
|
|
)?;
|
|
serde_json::to_string(&result).map_err(|e| e.to_string())?
|
|
}
|
|
|
|
GoogleDriveAction::UpdateFile {
|
|
file_id,
|
|
name,
|
|
description,
|
|
move_to_parent,
|
|
starred,
|
|
} => {
|
|
let result = api::update_file(
|
|
&file_id,
|
|
name.as_deref(),
|
|
description.as_deref(),
|
|
move_to_parent.as_deref(),
|
|
starred,
|
|
)?;
|
|
serde_json::to_string(&result).map_err(|e| e.to_string())?
|
|
}
|
|
|
|
GoogleDriveAction::CreateFolder {
|
|
name,
|
|
parent_id,
|
|
description,
|
|
} => {
|
|
let result = api::create_folder(&name, parent_id.as_deref(), description.as_deref())?;
|
|
serde_json::to_string(&result).map_err(|e| e.to_string())?
|
|
}
|
|
|
|
GoogleDriveAction::DeleteFile { file_id } => {
|
|
let result = api::delete_file(&file_id)?;
|
|
serde_json::to_string(&result).map_err(|e| e.to_string())?
|
|
}
|
|
|
|
GoogleDriveAction::TrashFile { file_id } => {
|
|
let result = api::trash_file(&file_id)?;
|
|
serde_json::to_string(&result).map_err(|e| e.to_string())?
|
|
}
|
|
|
|
GoogleDriveAction::ShareFile {
|
|
file_id,
|
|
email,
|
|
role,
|
|
message,
|
|
} => {
|
|
let result = api::share_file(&file_id, &email, &role, message.as_deref())?;
|
|
serde_json::to_string(&result).map_err(|e| e.to_string())?
|
|
}
|
|
|
|
GoogleDriveAction::ListPermissions { file_id } => {
|
|
let result = api::list_permissions(&file_id)?;
|
|
serde_json::to_string(&result).map_err(|e| e.to_string())?
|
|
}
|
|
|
|
GoogleDriveAction::RemovePermission {
|
|
file_id,
|
|
permission_id,
|
|
} => {
|
|
let result = api::remove_permission(&file_id, &permission_id)?;
|
|
serde_json::to_string(&result).map_err(|e| e.to_string())?
|
|
}
|
|
|
|
GoogleDriveAction::ListSharedDrives { page_size } => {
|
|
let result = api::list_shared_drives(page_size)?;
|
|
serde_json::to_string(&result).map_err(|e| e.to_string())?
|
|
}
|
|
};
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
export!(GoogleDriveTool);
|