From a0b2b4eeb1db5db9727ebb191e9c249515163181 Mon Sep 17 00:00:00 2001 From: Sumit Datta Date: Mon, 19 Jan 2026 21:55:18 +0530 Subject: [PATCH 1/2] Update workspace and dependencies for nocodo-tools rename - Update Cargo.toml: manager-tools -> nocodo-tools - Update nocodo-agents and nocodo-api dependencies to nocodo-tools - Update all import statements from manager_tools to nocodo_tools --- Cargo.lock | 62 +++++++++---------- Cargo.toml | 12 ++-- nocodo-agents/Cargo.toml | 2 +- nocodo-agents/bin/codebase_analysis_runner.rs | 2 +- nocodo-agents/bin/sqlite_analysis_runner.rs | 2 +- nocodo-agents/bin/structured_json_runner.rs | 2 +- nocodo-agents/src/codebase_analysis/mod.rs | 4 +- nocodo-agents/src/codebase_analysis/tests.rs | 2 +- nocodo-agents/src/factory.rs | 2 +- nocodo-agents/src/lib.rs | 14 ++--- .../src/requirements_gathering/mod.rs | 6 +- .../src/requirements_gathering/tests.rs | 2 +- nocodo-agents/src/settings_management/mod.rs | 8 +-- nocodo-agents/src/sqlite_analysis/mod.rs | 8 +-- nocodo-agents/src/sqlite_analysis/tests.rs | 2 +- nocodo-agents/src/structured_json/mod.rs | 2 +- nocodo-agents/src/structured_json/tests.rs | 2 +- nocodo-agents/src/tesseract/mod.rs | 10 +-- nocodo-agents/src/tools/llm_schemas.rs | 4 +- nocodo-api/Cargo.toml | 2 +- .../codebase_analysis_agent.rs | 2 +- nocodo-api/src/helpers/agents.rs | 8 +-- 22 files changed, 80 insertions(+), 80 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 345fb165..bcb25cae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2776,35 +2776,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" -[[package]] -name = "manager-tools" -version = "0.1.12" -dependencies = [ - "anyhow", - "base64 0.22.1", - "clap", - "codex-apply-patch", - "codex-core", - "codex-process-hardening", - "glob", - "home", - "regex", - "reqwest 0.12.24", - "rusqlite", - "schemars 0.8.22", - "serde", - "serde_json", - "shared-types", - "sqlparser", - "tempfile", - "thiserror 1.0.69", - "tokio", - "tokio-test", - "tracing", - "tracing-subscriber", - "walkdir", -] - [[package]] name = "matchers" version = "0.2.0" @@ -2994,8 +2965,8 @@ dependencies = [ "chrono", "clap", "home", - "manager-tools", "nocodo-llm-sdk", + "nocodo-tools", "refinery", "regex", "rusqlite", @@ -3024,9 +2995,9 @@ dependencies = [ "clap", "config", "home", - "manager-tools", "nocodo-agents", "nocodo-llm-sdk", + "nocodo-tools", "rusqlite", "serde", "serde_json", @@ -3058,6 +3029,35 @@ dependencies = [ "tracing", ] +[[package]] +name = "nocodo-tools" +version = "0.1.12" +dependencies = [ + "anyhow", + "base64 0.22.1", + "clap", + "codex-apply-patch", + "codex-core", + "codex-process-hardening", + "glob", + "home", + "regex", + "reqwest 0.12.24", + "rusqlite", + "schemars 0.8.22", + "serde", + "serde_json", + "shared-types", + "sqlparser", + "tempfile", + "thiserror 1.0.69", + "tokio", + "tokio-test", + "tracing", + "tracing-subscriber", + "walkdir", +] + [[package]] name = "nom" version = "7.1.3" diff --git a/Cargo.toml b/Cargo.toml index d265dc0b..f0d57801 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,11 @@ [workspace] resolver = "2" -members = [ - "shared-types", - "manager-tools", - "nocodo-llm-sdk", "nocodo-agents", - "nocodo-api", -] + members = [ + "shared-types", + "nocodo-tools", + "nocodo-llm-sdk", "nocodo-agents", + "nocodo-api", + ] # nocodo-github-actions temporarily removed from workspace due to missing directory exclude = [ "manager-web" diff --git a/nocodo-agents/Cargo.toml b/nocodo-agents/Cargo.toml index b2bdb93c..3f52ee41 100644 --- a/nocodo-agents/Cargo.toml +++ b/nocodo-agents/Cargo.toml @@ -9,7 +9,7 @@ repository.workspace = true [dependencies] nocodo-llm-sdk = { path = "../nocodo-llm-sdk", features = ["ffi"] } shared-types = { path = "../shared-types" } -manager-tools = { path = "../manager-tools" } +nocodo-tools = { path = "../nocodo-tools" } anyhow = { workspace = true } async-trait = "0.1" tokio = { version = "1.0", features = ["full"] } diff --git a/nocodo-agents/bin/codebase_analysis_runner.rs b/nocodo-agents/bin/codebase_analysis_runner.rs index c53dd098..c66e4a4f 100644 --- a/nocodo-agents/bin/codebase_analysis_runner.rs +++ b/nocodo-agents/bin/codebase_analysis_runner.rs @@ -1,7 +1,7 @@ use clap::Parser; -use manager_tools::ToolExecutor; use nocodo_agents::{config, factory::create_codebase_analysis_agent, Agent}; use nocodo_llm_sdk::glm::zai::ZaiGlmClient; +use nocodo_tools::ToolExecutor; use std::path::PathBuf; use std::sync::Arc; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; diff --git a/nocodo-agents/bin/sqlite_analysis_runner.rs b/nocodo-agents/bin/sqlite_analysis_runner.rs index 27e50826..d07884a0 100644 --- a/nocodo-agents/bin/sqlite_analysis_runner.rs +++ b/nocodo-agents/bin/sqlite_analysis_runner.rs @@ -1,7 +1,7 @@ use clap::Parser; -use manager_tools::ToolExecutor; use nocodo_agents::{config, factory::create_sqlite_analysis_agent, Agent}; use nocodo_llm_sdk::glm::zai::ZaiGlmClient; +use nocodo_tools::ToolExecutor; use std::path::PathBuf; use std::sync::Arc; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter}; diff --git a/nocodo-agents/bin/structured_json_runner.rs b/nocodo-agents/bin/structured_json_runner.rs index 2c02b810..76e55c58 100644 --- a/nocodo-agents/bin/structured_json_runner.rs +++ b/nocodo-agents/bin/structured_json_runner.rs @@ -54,7 +54,7 @@ async fn main() -> anyhow::Result<()> { let database = Arc::new(nocodo_agents::database::Database::new(&db_path)?); let tool_executor = Arc::new( - manager_tools::ToolExecutor::new(std::env::current_dir()?) + nocodo_tools::ToolExecutor::new(std::env::current_dir()?) .with_max_file_size(10 * 1024 * 1024), ); diff --git a/nocodo-agents/src/codebase_analysis/mod.rs b/nocodo-agents/src/codebase_analysis/mod.rs index 9e64d415..dceeed3c 100644 --- a/nocodo-agents/src/codebase_analysis/mod.rs +++ b/nocodo-agents/src/codebase_analysis/mod.rs @@ -1,11 +1,11 @@ use crate::{database::Database, Agent, AgentTool}; use anyhow; use async_trait::async_trait; -use manager_tools::ToolExecutor; use nocodo_llm_sdk::client::LlmClient; use nocodo_llm_sdk::tools::ToolCall; use nocodo_llm_sdk::tools::ToolChoice; use nocodo_llm_sdk::types::{CompletionRequest, ContentBlock, Message, Role}; +use nocodo_tools::ToolExecutor; use std::sync::Arc; use std::time::Instant; @@ -173,7 +173,7 @@ impl CodebaseAnalysisAgent { // 3. Execute tool with typed request ✅ let start = Instant::now(); - let result: anyhow::Result = self + let result: anyhow::Result = self .tool_executor .execute(tool_request) // ✅ Typed execution .await; diff --git a/nocodo-agents/src/codebase_analysis/tests.rs b/nocodo-agents/src/codebase_analysis/tests.rs index 8ff69cda..5369e80b 100644 --- a/nocodo-agents/src/codebase_analysis/tests.rs +++ b/nocodo-agents/src/codebase_analysis/tests.rs @@ -35,7 +35,7 @@ impl LlmClient for MockLlmClient { fn create_test_agent() -> CodebaseAnalysisAgent { use crate::database::Database; - use manager_tools::ToolExecutor; + use nocodo_tools::ToolExecutor; use std::path::PathBuf; let client: Arc = Arc::new(MockLlmClient); diff --git a/nocodo-agents/src/factory.rs b/nocodo-agents/src/factory.rs index 30841edf..9c8f0091 100644 --- a/nocodo-agents/src/factory.rs +++ b/nocodo-agents/src/factory.rs @@ -6,8 +6,8 @@ use crate::sqlite_analysis::SqliteAnalysisAgent; use crate::structured_json::StructuredJsonAgent; use crate::tesseract::TesseractAgent; use crate::Agent; -use manager_tools::ToolExecutor; use nocodo_llm_sdk::client::LlmClient; +use nocodo_tools::ToolExecutor; use std::sync::Arc; /// Enum representing the available agent types diff --git a/nocodo-agents/src/lib.rs b/nocodo-agents/src/lib.rs index d69beffb..e17c15cc 100644 --- a/nocodo-agents/src/lib.rs +++ b/nocodo-agents/src/lib.rs @@ -10,8 +10,8 @@ pub mod tesseract; pub mod tools; use async_trait::async_trait; -use manager_tools::types::filesystem::*; -use manager_tools::types::{ToolRequest, ToolResponse}; +use nocodo_tools::types::filesystem::*; +use nocodo_tools::types::{ToolRequest, ToolResponse}; use serde::{Deserialize, Serialize}; use shared_types::user_interaction::*; @@ -68,7 +68,7 @@ impl AgentTool { ToolRequest::WriteFile(req) } "grep" => { - let req: manager_tools::types::GrepRequest = serde_json::from_value(arguments)?; + let req: nocodo_tools::types::GrepRequest = serde_json::from_value(arguments)?; ToolRequest::Grep(req) } "apply_patch" => { @@ -76,7 +76,7 @@ impl AgentTool { ToolRequest::ApplyPatch(req) } "bash" => { - let req: manager_tools::types::BashRequest = serde_json::from_value(arguments)?; + let req: nocodo_tools::types::BashRequest = serde_json::from_value(arguments)?; ToolRequest::Bash(req) } "ask_user" => { @@ -97,9 +97,9 @@ impl AgentTool { .and_then(|v| v.as_u64()) .map(|v| v as usize); - ToolRequest::Sqlite3Reader(manager_tools::types::Sqlite3ReaderRequest { + ToolRequest::Sqlite3Reader(nocodo_tools::types::Sqlite3ReaderRequest { db_path: String::new(), - mode: manager_tools::types::SqliteMode::Query { query }, + mode: nocodo_tools::types::SqliteMode::Query { query }, limit, }) } @@ -111,7 +111,7 @@ impl AgentTool { } /// Format ToolResponse for display to LLM -pub fn format_tool_response(response: &manager_tools::types::ToolResponse) -> String { +pub fn format_tool_response(response: &nocodo_tools::types::ToolResponse) -> String { match response { ToolResponse::ListFiles(r) => format!("Found {} files:\n{}", r.files.len(), r.files), ToolResponse::ReadFile(r) => { diff --git a/nocodo-agents/src/requirements_gathering/mod.rs b/nocodo-agents/src/requirements_gathering/mod.rs index e6d034a1..6e5a09dc 100644 --- a/nocodo-agents/src/requirements_gathering/mod.rs +++ b/nocodo-agents/src/requirements_gathering/mod.rs @@ -7,10 +7,10 @@ mod migrations_test; use crate::{database::Database, Agent, AgentTool}; use anyhow; use async_trait::async_trait; -use manager_tools::ToolExecutor; use nocodo_llm_sdk::client::LlmClient; use nocodo_llm_sdk::tools::{ToolCall, ToolChoice}; use nocodo_llm_sdk::types::{CompletionRequest, ContentBlock, Message, Role}; +use nocodo_tools::ToolExecutor; use std::sync::Arc; use std::time::Instant; @@ -93,7 +93,7 @@ that you need more information about what they want to automate."#.to_string() )?; let start = Instant::now(); - let result: anyhow::Result = + let result: anyhow::Result = self.tool_executor.execute(tool_request).await; let execution_time = start.elapsed().as_millis() as i64; @@ -331,7 +331,7 @@ pub fn create_user_clarification_agent( client: Arc, ) -> anyhow::Result<(UserClarificationAgent, Arc)> { let database = Arc::new(Database::new(&std::path::PathBuf::from(":memory:"))?); - let tool_executor = Arc::new(manager_tools::ToolExecutor::new(std::path::PathBuf::from( + let tool_executor = Arc::new(nocodo_tools::ToolExecutor::new(std::path::PathBuf::from( ".", ))); let agent = UserClarificationAgent::new(client, database.clone(), tool_executor); diff --git a/nocodo-agents/src/requirements_gathering/tests.rs b/nocodo-agents/src/requirements_gathering/tests.rs index ad937e89..64cf07fb 100644 --- a/nocodo-agents/src/requirements_gathering/tests.rs +++ b/nocodo-agents/src/requirements_gathering/tests.rs @@ -73,7 +73,7 @@ fn setup_test_agent( }); let database = Arc::new(Database::new(&PathBuf::from(":memory:"))?); - let tool_executor = Arc::new(manager_tools::ToolExecutor::new(PathBuf::from("."))); + let tool_executor = Arc::new(nocodo_tools::ToolExecutor::new(PathBuf::from("."))); let agent = UserClarificationAgent::new(client, database.clone(), tool_executor); Ok((agent, database)) diff --git a/nocodo-agents/src/settings_management/mod.rs b/nocodo-agents/src/settings_management/mod.rs index e98216f8..4659397d 100644 --- a/nocodo-agents/src/settings_management/mod.rs +++ b/nocodo-agents/src/settings_management/mod.rs @@ -4,10 +4,10 @@ pub mod models; use crate::{database::Database, Agent, AgentTool}; use anyhow; use async_trait::async_trait; -use manager_tools::ToolExecutor; use nocodo_llm_sdk::client::LlmClient; use nocodo_llm_sdk::tools::{ToolCall, ToolChoice}; use nocodo_llm_sdk::types::{CompletionRequest, ContentBlock, Message, Role}; +use nocodo_tools::ToolExecutor; use std::sync::Arc; use std::time::Instant; @@ -149,7 +149,7 @@ using the tool."#, )?; let start = Instant::now(); - let result: anyhow::Result = + let result: anyhow::Result = self.tool_executor.execute(tool_request).await; let execution_time = start.elapsed().as_millis() as i64; @@ -370,7 +370,7 @@ impl Agent for SettingsManagementAgent { let execution_time = start.elapsed().as_millis() as i64; // Extract responses from tool_response - if let manager_tools::types::ToolResponse::AskUser(ask_user_response) = + if let nocodo_tools::types::ToolResponse::AskUser(ask_user_response) = &tool_response { // Group responses by section name (parsed from question IDs like "section.key") @@ -457,7 +457,7 @@ pub fn create_settings_management_agent( agent_schemas: Vec, ) -> anyhow::Result<(SettingsManagementAgent, Arc)> { let database = Arc::new(Database::new(&std::path::PathBuf::from(":memory:"))?); - let tool_executor = Arc::new(manager_tools::ToolExecutor::new(std::path::PathBuf::from( + let tool_executor = Arc::new(nocodo_tools::ToolExecutor::new(std::path::PathBuf::from( ".", ))); let agent = SettingsManagementAgent::new( diff --git a/nocodo-agents/src/sqlite_analysis/mod.rs b/nocodo-agents/src/sqlite_analysis/mod.rs index 554d1959..77ce6459 100644 --- a/nocodo-agents/src/sqlite_analysis/mod.rs +++ b/nocodo-agents/src/sqlite_analysis/mod.rs @@ -1,10 +1,10 @@ use crate::{database::Database, Agent, AgentTool}; use anyhow::{self}; use async_trait::async_trait; -use manager_tools::ToolExecutor; use nocodo_llm_sdk::client::LlmClient; use nocodo_llm_sdk::tools::{ToolCall, ToolChoice}; use nocodo_llm_sdk::types::{CompletionRequest, ContentBlock, Message, Role}; +use nocodo_tools::ToolExecutor; use std::path::Path; use std::sync::Arc; use std::time::Instant; @@ -29,7 +29,7 @@ impl SqliteAnalysisAgent { ) -> anyhow::Result { validate_db_path(&db_path)?; - let table_names = manager_tools::sqlite_analysis::get_table_names(&db_path).await?; + let table_names = nocodo_tools::sqlite_analysis::get_table_names(&db_path).await?; let db_name = std::path::Path::new(&db_path) .file_stem() @@ -110,7 +110,7 @@ impl SqliteAnalysisAgent { let mut tool_request = AgentTool::parse_tool_call(tool_call.name(), tool_call.arguments().clone())?; - if let manager_tools::types::ToolRequest::Sqlite3Reader(ref mut req) = tool_request { + if let nocodo_tools::types::ToolRequest::Sqlite3Reader(ref mut req) = tool_request { req.db_path = self.db_path.clone(); tracing::debug!( db_path = %self.db_path, @@ -127,7 +127,7 @@ impl SqliteAnalysisAgent { )?; let start = Instant::now(); - let result: anyhow::Result = + let result: anyhow::Result = self.tool_executor.execute(tool_request).await; let execution_time = start.elapsed().as_millis() as i64; diff --git a/nocodo-agents/src/sqlite_analysis/tests.rs b/nocodo-agents/src/sqlite_analysis/tests.rs index 27ffd8d5..1142f26f 100644 --- a/nocodo-agents/src/sqlite_analysis/tests.rs +++ b/nocodo-agents/src/sqlite_analysis/tests.rs @@ -1,7 +1,7 @@ use super::*; use crate::database::Database; -use manager_tools::ToolExecutor; use nocodo_llm_sdk::claude::ClaudeClient; +use nocodo_tools::ToolExecutor; use rusqlite::Connection; use std::path::PathBuf; use tempfile::NamedTempFile; diff --git a/nocodo-agents/src/structured_json/mod.rs b/nocodo-agents/src/structured_json/mod.rs index 9b2f6b58..a2eaa2e3 100644 --- a/nocodo-agents/src/structured_json/mod.rs +++ b/nocodo-agents/src/structured_json/mod.rs @@ -1,9 +1,9 @@ use crate::{database::Database, Agent}; use anyhow; use async_trait::async_trait; -use manager_tools::ToolExecutor; use nocodo_llm_sdk::client::LlmClient; use nocodo_llm_sdk::types::{CompletionRequest, ContentBlock, Message, ResponseFormat, Role}; +use nocodo_tools::ToolExecutor; use std::sync::Arc; mod validator; diff --git a/nocodo-agents/src/structured_json/tests.rs b/nocodo-agents/src/structured_json/tests.rs index 1283e527..01ba630f 100644 --- a/nocodo-agents/src/structured_json/tests.rs +++ b/nocodo-agents/src/structured_json/tests.rs @@ -1,9 +1,9 @@ use super::*; use crate::database::Database; -use manager_tools::ToolExecutor; use nocodo_llm_sdk::client::LlmClient; use nocodo_llm_sdk::error::LlmError; use nocodo_llm_sdk::types::{CompletionRequest, CompletionResponse, ContentBlock, Role, Usage}; +use nocodo_tools::ToolExecutor; use std::path::PathBuf; use std::sync::Arc; diff --git a/nocodo-agents/src/tesseract/mod.rs b/nocodo-agents/src/tesseract/mod.rs index 2dadd9e8..55b4e29c 100644 --- a/nocodo-agents/src/tesseract/mod.rs +++ b/nocodo-agents/src/tesseract/mod.rs @@ -1,13 +1,13 @@ use crate::{database::Database, Agent, AgentTool}; use anyhow::{self, Context}; use async_trait::async_trait; -use manager_tools::{ - bash::{BashExecutor, BashPermissions}, - ToolExecutor, -}; use nocodo_llm_sdk::client::LlmClient; use nocodo_llm_sdk::tools::{ToolCall, ToolChoice}; use nocodo_llm_sdk::types::{CompletionRequest, ContentBlock, Message, Role}; +use nocodo_tools::{ + bash::{BashExecutor, BashPermissions}, + ToolExecutor, +}; use std::path::PathBuf; use std::sync::Arc; use std::time::Instant; @@ -141,7 +141,7 @@ impl TesseractAgent { // 3. Execute tool let start = Instant::now(); - let result: anyhow::Result = + let result: anyhow::Result = self.tool_executor.execute(tool_request).await; let execution_time = start.elapsed().as_millis() as i64; diff --git a/nocodo-agents/src/tools/llm_schemas.rs b/nocodo-agents/src/tools/llm_schemas.rs index 39a5486f..5b08ea4d 100644 --- a/nocodo-agents/src/tools/llm_schemas.rs +++ b/nocodo-agents/src/tools/llm_schemas.rs @@ -1,6 +1,6 @@ -use manager_tools::types::filesystem::*; -use manager_tools::types::{BashRequest, GrepRequest}; use nocodo_llm_sdk::tools::Tool; +use nocodo_tools::types::filesystem::*; +use nocodo_tools::types::{BashRequest, GrepRequest}; use shared_types::user_interaction::*; /// Create tool definitions for LLM using manager-models types diff --git a/nocodo-api/Cargo.toml b/nocodo-api/Cargo.toml index 40d344e0..f0bd83af 100644 --- a/nocodo-api/Cargo.toml +++ b/nocodo-api/Cargo.toml @@ -9,7 +9,7 @@ repository.workspace = true [dependencies] nocodo-llm-sdk = { path = "../nocodo-llm-sdk" } nocodo-agents = { path = "../nocodo-agents" } -manager-tools = { path = "../manager-tools" } +nocodo-tools = { path = "../nocodo-tools" } shared-types = { path = "../shared-types" } actix-web.workspace = true actix-cors = "0.7" diff --git a/nocodo-api/src/handlers/agent_execution/codebase_analysis_agent.rs b/nocodo-api/src/handlers/agent_execution/codebase_analysis_agent.rs index f5381928..b23b1da3 100644 --- a/nocodo-api/src/handlers/agent_execution/codebase_analysis_agent.rs +++ b/nocodo-api/src/handlers/agent_execution/codebase_analysis_agent.rs @@ -64,7 +64,7 @@ pub async fn execute_codebase_analysis_agent( tokio::spawn(async move { let tool_executor = Arc::new( - manager_tools::ToolExecutor::new(std::path::PathBuf::from(path.clone())) + nocodo_tools::ToolExecutor::new(std::path::PathBuf::from(path.clone())) .with_max_file_size(10 * 1024 * 1024), ); diff --git a/nocodo-api/src/helpers/agents.rs b/nocodo-api/src/helpers/agents.rs index 01a8587b..d4c3c733 100644 --- a/nocodo-api/src/helpers/agents.rs +++ b/nocodo-api/src/helpers/agents.rs @@ -71,7 +71,7 @@ pub async fn create_sqlite_agent( db_path: &str, ) -> anyhow::Result { let tool_executor = Arc::new( - manager_tools::ToolExecutor::new(std::env::current_dir()?) + nocodo_tools::ToolExecutor::new(std::env::current_dir()?) .with_max_file_size(10 * 1024 * 1024), ); @@ -130,7 +130,7 @@ pub fn create_structured_json_agent( domain_description: String, ) -> anyhow::Result { let tool_executor = Arc::new( - manager_tools::ToolExecutor::new(std::env::current_dir()?) + nocodo_tools::ToolExecutor::new(std::env::current_dir()?) .with_max_file_size(10 * 1024 * 1024), ); @@ -164,7 +164,7 @@ pub fn create_user_clarification_agent( database: &Arc, ) -> anyhow::Result { let tool_executor = Arc::new( - manager_tools::ToolExecutor::new(std::env::current_dir()?) + nocodo_tools::ToolExecutor::new(std::env::current_dir()?) .with_max_file_size(10 * 1024 * 1024), ); @@ -196,7 +196,7 @@ pub fn create_settings_management_agent( agent_schemas: Vec, ) -> anyhow::Result { let tool_executor = Arc::new( - manager_tools::ToolExecutor::new(std::env::current_dir()?) + nocodo_tools::ToolExecutor::new(std::env::current_dir()?) .with_max_file_size(10 * 1024 * 1024), ); From 9609f7747d8d76c89fbefd93e68f2a75a2af1a49 Mon Sep 17 00:00:00 2001 From: Sumit Datta Date: Mon, 19 Jan 2026 21:58:45 +0530 Subject: [PATCH 2/2] renamed manager-tools to nocodo-tools --- {manager-tools => nocodo-tools}/Cargo.toml | 4 +- {manager-tools => nocodo-tools}/README.md | 0 .../examples/custom_bash_permissions.rs | 2 +- .../src/bash/bash_executor.rs | 4 +- .../src/bash/bash_executor_tests.rs | 0 .../src/bash/bash_permissions.rs | 0 .../src/bash/bash_permissions_tests.rs | 0 .../src/bash/mod.rs | 0 .../src/bash/types.rs | 0 .../src/bin/hackernews_downloader.rs | 6 +- .../src/filesystem/apply_patch.rs | 0 .../src/filesystem/list_files.rs | 0 .../src/filesystem/mod.rs | 0 .../src/filesystem/path_utils.rs | 0 .../src/filesystem/read_file.rs | 0 .../src/filesystem/write_file.rs | 0 {manager-tools => nocodo-tools}/src/grep.rs | 0 .../src/hackernews/client.rs | 0 .../src/hackernews/fetcher.rs | 0 .../src/hackernews/mod.rs | 0 .../src/hackernews/schema.rs | 0 .../src/hackernews/storage.rs | 0 {manager-tools => nocodo-tools}/src/lib.rs | 0 .../src/sqlite_analysis/executor.rs | 0 .../src/sqlite_analysis/formatter.rs | 0 .../src/sqlite_analysis/mod.rs | 24 +- {manager-tools => nocodo-tools}/src/tests.rs | 0 .../src/tool_error.rs | 0 .../src/tool_executor.rs | 2 +- .../src/types/bash.rs | 0 .../src/types/core.rs | 0 .../src/types/filesystem.rs | 0 .../src/types/grep.rs | 0 .../src/types/hackernews.rs | 0 .../src/types/mod.rs | 0 .../src/types/sqlite_analysis.rs | 0 .../src/user_interaction/README.md | 0 .../src/user_interaction/ask_user.rs | 0 .../src/user_interaction/ask_user_tests.rs | 0 .../src/user_interaction/mod.rs | 0 .../add-command-restricted-bash-executor.md | 0 .../tasks/add-hackernews-download-manager.md | 0 .../tasks/add-hackernews-downloader-binary.md | 0 .../tasks/add-postgresql-reader-tool.md | 936 ++++++++++++++++++ .../tasks/add-sqlite3-reader-tool.md | 0 .../tasks/sqlite_dual_mode.md | 0 .../tests/custom_bash_permissions.rs | 2 +- 47 files changed, 958 insertions(+), 22 deletions(-) rename {manager-tools => nocodo-tools}/Cargo.toml (96%) rename {manager-tools => nocodo-tools}/README.md (100%) rename {manager-tools => nocodo-tools}/examples/custom_bash_permissions.rs (99%) rename {manager-tools => nocodo-tools}/src/bash/bash_executor.rs (99%) rename {manager-tools => nocodo-tools}/src/bash/bash_executor_tests.rs (100%) rename {manager-tools => nocodo-tools}/src/bash/bash_permissions.rs (100%) rename {manager-tools => nocodo-tools}/src/bash/bash_permissions_tests.rs (100%) rename {manager-tools => nocodo-tools}/src/bash/mod.rs (100%) rename {manager-tools => nocodo-tools}/src/bash/types.rs (100%) rename {manager-tools => nocodo-tools}/src/bin/hackernews_downloader.rs (95%) rename {manager-tools => nocodo-tools}/src/filesystem/apply_patch.rs (100%) rename {manager-tools => nocodo-tools}/src/filesystem/list_files.rs (100%) rename {manager-tools => nocodo-tools}/src/filesystem/mod.rs (100%) rename {manager-tools => nocodo-tools}/src/filesystem/path_utils.rs (100%) rename {manager-tools => nocodo-tools}/src/filesystem/read_file.rs (100%) rename {manager-tools => nocodo-tools}/src/filesystem/write_file.rs (100%) rename {manager-tools => nocodo-tools}/src/grep.rs (100%) rename {manager-tools => nocodo-tools}/src/hackernews/client.rs (100%) rename {manager-tools => nocodo-tools}/src/hackernews/fetcher.rs (100%) rename {manager-tools => nocodo-tools}/src/hackernews/mod.rs (100%) rename {manager-tools => nocodo-tools}/src/hackernews/schema.rs (100%) rename {manager-tools => nocodo-tools}/src/hackernews/storage.rs (100%) rename {manager-tools => nocodo-tools}/src/lib.rs (100%) rename {manager-tools => nocodo-tools}/src/sqlite_analysis/executor.rs (100%) rename {manager-tools => nocodo-tools}/src/sqlite_analysis/formatter.rs (100%) rename {manager-tools => nocodo-tools}/src/sqlite_analysis/mod.rs (97%) rename {manager-tools => nocodo-tools}/src/tests.rs (100%) rename {manager-tools => nocodo-tools}/src/tool_error.rs (100%) rename {manager-tools => nocodo-tools}/src/tool_executor.rs (98%) rename {manager-tools => nocodo-tools}/src/types/bash.rs (100%) rename {manager-tools => nocodo-tools}/src/types/core.rs (100%) rename {manager-tools => nocodo-tools}/src/types/filesystem.rs (100%) rename {manager-tools => nocodo-tools}/src/types/grep.rs (100%) rename {manager-tools => nocodo-tools}/src/types/hackernews.rs (100%) rename {manager-tools => nocodo-tools}/src/types/mod.rs (100%) rename {manager-tools => nocodo-tools}/src/types/sqlite_analysis.rs (100%) rename {manager-tools => nocodo-tools}/src/user_interaction/README.md (100%) rename {manager-tools => nocodo-tools}/src/user_interaction/ask_user.rs (100%) rename {manager-tools => nocodo-tools}/src/user_interaction/ask_user_tests.rs (100%) rename {manager-tools => nocodo-tools}/src/user_interaction/mod.rs (100%) rename {manager-tools => nocodo-tools}/tasks/add-command-restricted-bash-executor.md (100%) rename {manager-tools => nocodo-tools}/tasks/add-hackernews-download-manager.md (100%) rename {manager-tools => nocodo-tools}/tasks/add-hackernews-downloader-binary.md (100%) create mode 100644 nocodo-tools/tasks/add-postgresql-reader-tool.md rename {manager-tools => nocodo-tools}/tasks/add-sqlite3-reader-tool.md (100%) rename {manager-tools => nocodo-tools}/tasks/sqlite_dual_mode.md (100%) rename {manager-tools => nocodo-tools}/tests/custom_bash_permissions.rs (99%) diff --git a/manager-tools/Cargo.toml b/nocodo-tools/Cargo.toml similarity index 96% rename from manager-tools/Cargo.toml rename to nocodo-tools/Cargo.toml index c15f5932..ba8d3f5b 100644 --- a/manager-tools/Cargo.toml +++ b/nocodo-tools/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "manager-tools" + name = "nocodo-tools" version.workspace = true edition.workspace = true authors.workspace = true @@ -8,7 +8,7 @@ repository.workspace = true description = "Tool execution utilities for nocodo manager" [lib] -name = "manager_tools" + name = "nocodo_tools" path = "src/lib.rs" [[bin]] diff --git a/manager-tools/README.md b/nocodo-tools/README.md similarity index 100% rename from manager-tools/README.md rename to nocodo-tools/README.md diff --git a/manager-tools/examples/custom_bash_permissions.rs b/nocodo-tools/examples/custom_bash_permissions.rs similarity index 99% rename from manager-tools/examples/custom_bash_permissions.rs rename to nocodo-tools/examples/custom_bash_permissions.rs index 1d0ae580..5b212c36 100644 --- a/manager-tools/examples/custom_bash_permissions.rs +++ b/nocodo-tools/examples/custom_bash_permissions.rs @@ -1,6 +1,6 @@ //! Example showing how to create tool executors with custom bash permissions -use manager_tools::{ +use nocodo_tools::{ bash::{BashExecutor, BashPermissions}, types::{BashRequest, ToolRequest}, ToolExecutor, diff --git a/manager-tools/src/bash/bash_executor.rs b/nocodo-tools/src/bash/bash_executor.rs similarity index 99% rename from manager-tools/src/bash/bash_executor.rs rename to nocodo-tools/src/bash/bash_executor.rs index 7256c79d..e9eefccb 100644 --- a/manager-tools/src/bash/bash_executor.rs +++ b/nocodo-tools/src/bash/bash_executor.rs @@ -28,7 +28,7 @@ impl BashExecutor { /// /// ## Default permissions (backward compatible) /// ```rust - /// use manager_tools::bash::{BashExecutor, BashPermissions}; + /// use nocodo_tools::bash::{BashExecutor, BashPermissions}; /// /// let executor = BashExecutor::new( /// BashPermissions::default(), @@ -38,7 +38,7 @@ impl BashExecutor { /// /// ## Restricted to specific command /// ```rust - /// use manager_tools::bash::{BashExecutor, BashPermissions, PermissionRule}; + /// use nocodo_tools::bash::{BashExecutor, BashPermissions, PermissionRule}; /// /// let mut perms = BashPermissions::new(); /// perms.add_rule(PermissionRule::allow("tesseract*").unwrap()); diff --git a/manager-tools/src/bash/bash_executor_tests.rs b/nocodo-tools/src/bash/bash_executor_tests.rs similarity index 100% rename from manager-tools/src/bash/bash_executor_tests.rs rename to nocodo-tools/src/bash/bash_executor_tests.rs diff --git a/manager-tools/src/bash/bash_permissions.rs b/nocodo-tools/src/bash/bash_permissions.rs similarity index 100% rename from manager-tools/src/bash/bash_permissions.rs rename to nocodo-tools/src/bash/bash_permissions.rs diff --git a/manager-tools/src/bash/bash_permissions_tests.rs b/nocodo-tools/src/bash/bash_permissions_tests.rs similarity index 100% rename from manager-tools/src/bash/bash_permissions_tests.rs rename to nocodo-tools/src/bash/bash_permissions_tests.rs diff --git a/manager-tools/src/bash/mod.rs b/nocodo-tools/src/bash/mod.rs similarity index 100% rename from manager-tools/src/bash/mod.rs rename to nocodo-tools/src/bash/mod.rs diff --git a/manager-tools/src/bash/types.rs b/nocodo-tools/src/bash/types.rs similarity index 100% rename from manager-tools/src/bash/types.rs rename to nocodo-tools/src/bash/types.rs diff --git a/manager-tools/src/bin/hackernews_downloader.rs b/nocodo-tools/src/bin/hackernews_downloader.rs similarity index 95% rename from manager-tools/src/bin/hackernews_downloader.rs rename to nocodo-tools/src/bin/hackernews_downloader.rs index ad9ead9a..f17b575f 100644 --- a/manager-tools/src/bin/hackernews_downloader.rs +++ b/nocodo-tools/src/bin/hackernews_downloader.rs @@ -1,6 +1,6 @@ use clap::{Parser, ValueEnum}; -use manager_tools::hackernews::execute_hackernews_request; -use manager_tools::types::{FetchMode, HackerNewsRequest, StoryType}; +use nocodo_tools::hackernews::execute_hackernews_request; +use nocodo_tools::types::{FetchMode, HackerNewsRequest, StoryType}; use std::path::PathBuf; #[derive(Parser)] @@ -122,7 +122,7 @@ async fn main() -> Result<(), Box> { let response = execute_hackernews_request(request).await?; let duration = start.elapsed(); - if let manager_tools::ToolResponse::HackerNewsResponse(hn_response) = response { + if let nocodo_tools::ToolResponse::HackerNewsResponse(hn_response) = response { tracing::info!("\n{}", "=".repeat(60)); tracing::info!("Download Complete!"); tracing::info!("{}", "=".repeat(60)); diff --git a/manager-tools/src/filesystem/apply_patch.rs b/nocodo-tools/src/filesystem/apply_patch.rs similarity index 100% rename from manager-tools/src/filesystem/apply_patch.rs rename to nocodo-tools/src/filesystem/apply_patch.rs diff --git a/manager-tools/src/filesystem/list_files.rs b/nocodo-tools/src/filesystem/list_files.rs similarity index 100% rename from manager-tools/src/filesystem/list_files.rs rename to nocodo-tools/src/filesystem/list_files.rs diff --git a/manager-tools/src/filesystem/mod.rs b/nocodo-tools/src/filesystem/mod.rs similarity index 100% rename from manager-tools/src/filesystem/mod.rs rename to nocodo-tools/src/filesystem/mod.rs diff --git a/manager-tools/src/filesystem/path_utils.rs b/nocodo-tools/src/filesystem/path_utils.rs similarity index 100% rename from manager-tools/src/filesystem/path_utils.rs rename to nocodo-tools/src/filesystem/path_utils.rs diff --git a/manager-tools/src/filesystem/read_file.rs b/nocodo-tools/src/filesystem/read_file.rs similarity index 100% rename from manager-tools/src/filesystem/read_file.rs rename to nocodo-tools/src/filesystem/read_file.rs diff --git a/manager-tools/src/filesystem/write_file.rs b/nocodo-tools/src/filesystem/write_file.rs similarity index 100% rename from manager-tools/src/filesystem/write_file.rs rename to nocodo-tools/src/filesystem/write_file.rs diff --git a/manager-tools/src/grep.rs b/nocodo-tools/src/grep.rs similarity index 100% rename from manager-tools/src/grep.rs rename to nocodo-tools/src/grep.rs diff --git a/manager-tools/src/hackernews/client.rs b/nocodo-tools/src/hackernews/client.rs similarity index 100% rename from manager-tools/src/hackernews/client.rs rename to nocodo-tools/src/hackernews/client.rs diff --git a/manager-tools/src/hackernews/fetcher.rs b/nocodo-tools/src/hackernews/fetcher.rs similarity index 100% rename from manager-tools/src/hackernews/fetcher.rs rename to nocodo-tools/src/hackernews/fetcher.rs diff --git a/manager-tools/src/hackernews/mod.rs b/nocodo-tools/src/hackernews/mod.rs similarity index 100% rename from manager-tools/src/hackernews/mod.rs rename to nocodo-tools/src/hackernews/mod.rs diff --git a/manager-tools/src/hackernews/schema.rs b/nocodo-tools/src/hackernews/schema.rs similarity index 100% rename from manager-tools/src/hackernews/schema.rs rename to nocodo-tools/src/hackernews/schema.rs diff --git a/manager-tools/src/hackernews/storage.rs b/nocodo-tools/src/hackernews/storage.rs similarity index 100% rename from manager-tools/src/hackernews/storage.rs rename to nocodo-tools/src/hackernews/storage.rs diff --git a/manager-tools/src/lib.rs b/nocodo-tools/src/lib.rs similarity index 100% rename from manager-tools/src/lib.rs rename to nocodo-tools/src/lib.rs diff --git a/manager-tools/src/sqlite_analysis/executor.rs b/nocodo-tools/src/sqlite_analysis/executor.rs similarity index 100% rename from manager-tools/src/sqlite_analysis/executor.rs rename to nocodo-tools/src/sqlite_analysis/executor.rs diff --git a/manager-tools/src/sqlite_analysis/formatter.rs b/nocodo-tools/src/sqlite_analysis/formatter.rs similarity index 100% rename from manager-tools/src/sqlite_analysis/formatter.rs rename to nocodo-tools/src/sqlite_analysis/formatter.rs diff --git a/manager-tools/src/sqlite_analysis/mod.rs b/nocodo-tools/src/sqlite_analysis/mod.rs similarity index 97% rename from manager-tools/src/sqlite_analysis/mod.rs rename to nocodo-tools/src/sqlite_analysis/mod.rs index 04fcbc00..63ac8e00 100644 --- a/manager-tools/src/sqlite_analysis/mod.rs +++ b/nocodo-tools/src/sqlite_analysis/mod.rs @@ -70,8 +70,8 @@ //! ## Query Mode - Basic SELECT //! //! ```rust,no_run -//! use manager_tools::types::{Sqlite3ReaderRequest, SqliteMode}; -//! use manager_tools::sqlite_analysis::execute_sqlite3_reader; +//! use nocodo_tools::types::{Sqlite3ReaderRequest, SqliteMode}; +//! use nocodo_tools::sqlite_analysis::execute_sqlite3_reader; //! //! # async fn example() -> Result<(), Box> { //! let request = Sqlite3ReaderRequest { @@ -90,8 +90,8 @@ //! ## Query Mode - PRAGMA Statements //! //! ```rust,no_run -//! # use manager_tools::types::{Sqlite3ReaderRequest, SqliteMode}; -//! # use manager_tools::sqlite::execute_sqlite3_reader; +//! # use nocodo_tools::types::{Sqlite3ReaderRequest, SqliteMode}; +//! # use nocodo_tools::sqlite::execute_sqlite3_reader; //! # async fn example() -> Result<(), Box> { //! let request = Sqlite3ReaderRequest { //! db_path: "/path/to/database.db".to_string(), @@ -109,8 +109,8 @@ //! ## Reflect Mode - Discover Tables //! //! ```rust,no_run -//! # use manager_tools::types::{Sqlite3ReaderRequest, SqliteMode}; -//! # use manager_tools::sqlite::execute_sqlite3_reader; +//! # use nocodo_tools::types::{Sqlite3ReaderRequest, SqliteMode}; +//! # use nocodo_tools::sqlite::execute_sqlite3_reader; //! # async fn example() -> Result<(), Box> { //! let request = Sqlite3ReaderRequest { //! db_path: "/path/to/database.db".to_string(), @@ -129,8 +129,8 @@ //! ## Reflect Mode - Inspect Table Schema //! //! ```rust,no_run -//! # use manager_tools::types::{Sqlite3ReaderRequest, SqliteMode}; -//! # use manager_tools::sqlite::execute_sqlite3_reader; +//! # use nocodo_tools::types::{Sqlite3ReaderRequest, SqliteMode}; +//! # use nocodo_tools::sqlite::execute_sqlite3_reader; //! # async fn example() -> Result<(), Box> { //! let request = Sqlite3ReaderRequest { //! db_path: "/path/to/database.db".to_string(), @@ -149,8 +149,8 @@ //! ## Reflect Mode - Check Foreign Keys //! //! ```rust,no_run -//! # use manager_tools::types::{Sqlite3ReaderRequest, SqliteMode}; -//! # use manager_tools::sqlite::execute_sqlite3_reader; +//! # use nocodo_tools::types::{Sqlite3ReaderRequest, SqliteMode}; +//! # use nocodo_tools::sqlite::execute_sqlite3_reader; //! # async fn example() -> Result<(), Box> { //! let request = Sqlite3ReaderRequest { //! db_path: "/path/to/database.db".to_string(), @@ -169,8 +169,8 @@ //! ## Reflect Mode - Database Statistics //! //! ```rust,no_run -//! # use manager_tools::types::{Sqlite3ReaderRequest, SqliteMode}; -//! # use manager_tools::sqlite::execute_sqlite3_reader; +//! # use nocodo_tools::types::{Sqlite3ReaderRequest, SqliteMode}; +//! # use nocodo_tools::sqlite::execute_sqlite3_reader; //! # async fn example() -> Result<(), Box> { //! let request = Sqlite3ReaderRequest { //! db_path: "/path/to/database.db".to_string(), diff --git a/manager-tools/src/tests.rs b/nocodo-tools/src/tests.rs similarity index 100% rename from manager-tools/src/tests.rs rename to nocodo-tools/src/tests.rs diff --git a/manager-tools/src/tool_error.rs b/nocodo-tools/src/tool_error.rs similarity index 100% rename from manager-tools/src/tool_error.rs rename to nocodo-tools/src/tool_error.rs diff --git a/manager-tools/src/tool_executor.rs b/nocodo-tools/src/tool_executor.rs similarity index 98% rename from manager-tools/src/tool_executor.rs rename to nocodo-tools/src/tool_executor.rs index a7f61de9..94912e3a 100644 --- a/manager-tools/src/tool_executor.rs +++ b/nocodo-tools/src/tool_executor.rs @@ -185,7 +185,7 @@ impl ToolExecutorBuilder { /// /// ## With custom bash executor /// ```rust - /// use manager_tools::{ToolExecutor, bash::{BashExecutor, BashPermissions}}; + /// use nocodo_tools::{ToolExecutor, bash::{BashExecutor, BashPermissions}}; /// use std::path::PathBuf; /// /// let perms = BashPermissions::only_allow(vec!["tesseract*"]); diff --git a/manager-tools/src/types/bash.rs b/nocodo-tools/src/types/bash.rs similarity index 100% rename from manager-tools/src/types/bash.rs rename to nocodo-tools/src/types/bash.rs diff --git a/manager-tools/src/types/core.rs b/nocodo-tools/src/types/core.rs similarity index 100% rename from manager-tools/src/types/core.rs rename to nocodo-tools/src/types/core.rs diff --git a/manager-tools/src/types/filesystem.rs b/nocodo-tools/src/types/filesystem.rs similarity index 100% rename from manager-tools/src/types/filesystem.rs rename to nocodo-tools/src/types/filesystem.rs diff --git a/manager-tools/src/types/grep.rs b/nocodo-tools/src/types/grep.rs similarity index 100% rename from manager-tools/src/types/grep.rs rename to nocodo-tools/src/types/grep.rs diff --git a/manager-tools/src/types/hackernews.rs b/nocodo-tools/src/types/hackernews.rs similarity index 100% rename from manager-tools/src/types/hackernews.rs rename to nocodo-tools/src/types/hackernews.rs diff --git a/manager-tools/src/types/mod.rs b/nocodo-tools/src/types/mod.rs similarity index 100% rename from manager-tools/src/types/mod.rs rename to nocodo-tools/src/types/mod.rs diff --git a/manager-tools/src/types/sqlite_analysis.rs b/nocodo-tools/src/types/sqlite_analysis.rs similarity index 100% rename from manager-tools/src/types/sqlite_analysis.rs rename to nocodo-tools/src/types/sqlite_analysis.rs diff --git a/manager-tools/src/user_interaction/README.md b/nocodo-tools/src/user_interaction/README.md similarity index 100% rename from manager-tools/src/user_interaction/README.md rename to nocodo-tools/src/user_interaction/README.md diff --git a/manager-tools/src/user_interaction/ask_user.rs b/nocodo-tools/src/user_interaction/ask_user.rs similarity index 100% rename from manager-tools/src/user_interaction/ask_user.rs rename to nocodo-tools/src/user_interaction/ask_user.rs diff --git a/manager-tools/src/user_interaction/ask_user_tests.rs b/nocodo-tools/src/user_interaction/ask_user_tests.rs similarity index 100% rename from manager-tools/src/user_interaction/ask_user_tests.rs rename to nocodo-tools/src/user_interaction/ask_user_tests.rs diff --git a/manager-tools/src/user_interaction/mod.rs b/nocodo-tools/src/user_interaction/mod.rs similarity index 100% rename from manager-tools/src/user_interaction/mod.rs rename to nocodo-tools/src/user_interaction/mod.rs diff --git a/manager-tools/tasks/add-command-restricted-bash-executor.md b/nocodo-tools/tasks/add-command-restricted-bash-executor.md similarity index 100% rename from manager-tools/tasks/add-command-restricted-bash-executor.md rename to nocodo-tools/tasks/add-command-restricted-bash-executor.md diff --git a/manager-tools/tasks/add-hackernews-download-manager.md b/nocodo-tools/tasks/add-hackernews-download-manager.md similarity index 100% rename from manager-tools/tasks/add-hackernews-download-manager.md rename to nocodo-tools/tasks/add-hackernews-download-manager.md diff --git a/manager-tools/tasks/add-hackernews-downloader-binary.md b/nocodo-tools/tasks/add-hackernews-downloader-binary.md similarity index 100% rename from manager-tools/tasks/add-hackernews-downloader-binary.md rename to nocodo-tools/tasks/add-hackernews-downloader-binary.md diff --git a/nocodo-tools/tasks/add-postgresql-reader-tool.md b/nocodo-tools/tasks/add-postgresql-reader-tool.md new file mode 100644 index 00000000..60ed1458 --- /dev/null +++ b/nocodo-tools/tasks/add-postgresql-reader-tool.md @@ -0,0 +1,936 @@ +# Add PostgreSQL Reader Tool to manager-tools + +**Status**: 📋 Not Started +**Priority**: Medium +**Created**: 2026-01-19 + +## Summary + +Add a read-only PostgreSQL database query tool (`postgresql_reader`) to manager-tools, modeled after the proven `sqlite_analysis` implementation. This tool will enable AI agents to safely query PostgreSQL databases for analysis purposes with strict read-only guarantees. + +## Problem Statement + +AI agents need secure, read-only access to PostgreSQL databases for: +- Data analysis and exploration +- Schema introspection +- Report generation +- Data validation and quality checks + +Without a dedicated tool: +- **Security risks**: Ad-hoc database access may allow write operations +- **No standardization**: Each project implements its own database access +- **Inconsistent validation**: Different security approaches across projects +- **Code duplication**: Same logic reimplemented multiple times + +## Goals + +1. **Create reusable postgresql_reader tool**: Single implementation in manager-tools +2. **Read-only safety**: Strictly enforce SELECT and schema query operations only +3. **Connection-based access**: Support standard PostgreSQL connection parameters +4. **Schema introspection**: Support information_schema queries for schema discovery +5. **Security first**: Comprehensive SQL injection protection and validation +6. **Proven architecture**: Follow the battle-tested sqlite_analysis design + +## Architecture Overview + +### Design Decisions + +| Aspect | Decision | Rationale | +|--------|----------|-----------| +| **Connection parameters** | Standard PostgreSQL params (host, port, db, user, password) | Maximum flexibility, industry standard | +| **Write access** | Read-only (SELECT + information_schema) | Tool is for analysis only, not data modification | +| **Statement batching** | Single statement per call | Prevents SQL injection, keeps calls atomic | +| **Connection lifecycle** | One connection per request | Simpler implementation, stateless | +| **Schema discovery** | information_schema queries | Standard PostgreSQL approach, no custom metadata | +| **Library choice** | `tokio-postgres` + `sqlparser` | Async support, proven parser, excellent safety | +| **SSL/TLS** | Optional with configurable mode | Support secure and local connections | + +### Tool Interface + +```rust +// Request - supports two modes similar to sqlite_analysis +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct PostgresqlReaderRequest { + /// Connection parameters + pub connection: PostgresqlConnection, + /// Execution mode: either query or reflect + pub mode: PostgresqlMode, + /// Optional limit on rows returned (default: 100, max: 1000) + pub limit: Option, +} + +// Connection parameters +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct PostgresqlConnection { + pub host: String, + pub port: Option, // defaults to 5432 + pub database: String, + pub user: String, + pub password: String, + pub ssl_mode: Option, // disable, prefer, require +} + +// Mode enum similar to SqliteMode +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "mode")] +pub enum PostgresqlMode { + #[serde(rename = "query")] + Query { + #[schemars(description = "SQL query to execute. Only SELECT queries are allowed.")] + query: String, + }, + + #[serde(rename = "reflect")] + Reflect { + #[schemars(description = "Target of reflection: tables, schema, table_info, indexes, views, foreign_keys, stats")] + target: String, + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(description = "Optional: specific table name for table_info and foreign_keys modes")] + table_name: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(description = "Optional: specific schema name (defaults to 'public')")] + schema_name: Option, + }, +} + +// Response +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct PostgresqlReaderResponse { + /// Column names + pub columns: Vec, + /// Result rows (as JSON values) + pub rows: Vec>, + /// Number of rows returned + pub row_count: usize, + /// Whether results were truncated due to limit + pub truncated: bool, + /// Query execution time in milliseconds + pub execution_time_ms: u64, + /// Formatted output (table format for LLMs) + pub formatted_output: String, +} +``` + +## Implementation Plan + +### Phase 1: Create Core Components + +#### 1.1 Create postgresql_reader Module Structure + +Create new module in manager-tools: +``` +manager-tools/ + src/ + postgresql_reader/ + mod.rs # Public interface and executor wrapper + executor.rs # PostgresqlExecutor (similar to sqlite SqlExecutor) + formatter.rs # Result formatting for LLMs (reuse sqlite formatter) + types/ + postgresql_reader.rs # PostgresqlReaderRequest/Response types +``` + +#### 1.2 Implement PostgresqlExecutor + +**File**: `manager-tools/src/postgresql_reader/executor.rs` + +Core structure similar to `sqlite_analysis/executor.rs`: + +```rust +use tokio_postgres::{Client, NoTls}; +use serde_json::Value; +use sqlparser::ast::{SetExpr, Statement}; +use sqlparser::dialect::PostgreSqlDialect; +use sqlparser::parser::Parser; +use std::time::Instant; + +#[derive(Debug, Clone)] +pub struct QueryResult { + pub columns: Vec, + pub rows: Vec>, + pub row_count: usize, + pub truncated: bool, + pub execution_time_ms: u64, +} + +pub struct PostgresqlExecutor { + client: Client, + max_rows: usize, + timeout_ms: u64, +} + +impl PostgresqlExecutor { + /// Create new executor with connection parameters + pub async fn new( + connection: &PostgresqlConnection, + max_rows: usize, + timeout_ms: u64, + ) -> anyhow::Result { + // Build connection string + let conn_str = format!( + "host={} port={} dbname={} user={} password={}", + connection.host, + connection.port.unwrap_or(5432), + connection.database, + connection.user, + connection.password + ); + + // Configure SSL mode + let (client, connection) = match connection.ssl_mode.as_deref() { + Some("require") => { + // Use TLS for secure connections + tokio_postgres::connect(&conn_str, tokio_postgres::tls::MakeTlsConnector::new()?) + .await? + } + _ => { + // Use NoTls for local/dev connections + tokio_postgres::connect(&conn_str, NoTls).await? + } + }; + + // Spawn connection task + tokio::spawn(async move { + if let Err(e) = connection.await { + eprintln!("connection error: {}", e); + } + }); + + Ok(Self { + client, + max_rows, + timeout_ms, + }) + } + + /// Execute a validated query + pub async fn execute(&self, query: &str, limit: Option) -> anyhow::Result { + let start_time = Instant::now(); + + // Validate query for safety + self.validate_query(query)?; + + let effective_limit = limit.unwrap_or(self.max_rows).min(self.max_rows); + + // Apply LIMIT clause if not present + let final_query = self.apply_limit(query, effective_limit)?; + + // Execute query with timeout + let rows = tokio::time::timeout( + std::time::Duration::from_millis(self.timeout_ms), + self.client.query(&final_query, &[]) + ).await??; + + // Extract columns + let columns: Vec = if let Some(first_row) = rows.first() { + first_row + .columns() + .iter() + .map(|col| col.name().to_string()) + .collect() + } else { + Vec::new() + }; + + // Convert rows to JSON values + let mut result_rows = Vec::new(); + for row in rows.iter().take(effective_limit) { + let mut row_values = Vec::new(); + for (idx, column) in row.columns().iter().enumerate() { + let value = postgres_value_to_json(&row, idx, column.type_())?; + row_values.push(value); + } + result_rows.push(row_values); + } + + let row_count = result_rows.len(); + let truncated = row_count == effective_limit && rows.len() > effective_limit; + let execution_time_ms = start_time.elapsed().as_millis() as u64; + + Ok(QueryResult { + columns, + rows: result_rows, + row_count, + truncated, + execution_time_ms, + }) + } + + /// Validate query is read-only and safe + pub fn validate_query(&self, query: &str) -> anyhow::Result<()> { + // Check for multiple statements + if query.trim().ends_with(';') && query.trim().matches(';').count() > 1 { + return Err(anyhow::anyhow!("Multiple SQL statements are not allowed")); + } + + // Parse SQL using PostgreSQL dialect + let dialect = PostgreSqlDialect {}; + let statements = Parser::parse_sql(&dialect, query) + .map_err(|e| anyhow::anyhow!("Failed to parse SQL: {}", e))?; + + if statements.is_empty() { + return Err(anyhow::anyhow!("Empty SQL statement")); + } + + if statements.len() > 1 { + return Err(anyhow::anyhow!("Multiple SQL statements are not allowed")); + } + + // Only allow SELECT queries + match &statements[0] { + Statement::Query(query) => { + self.validate_query_body(&query.body)?; + } + _ => { + return Err(anyhow::anyhow!("Only SELECT queries are allowed")); + } + } + + // Block dangerous keywords (similar to sqlite) + let dangerous_keywords = [ + "INSERT", "UPDATE", "DELETE", "DROP", "CREATE", "ALTER", + "TRUNCATE", "EXEC", "EXECUTE", "MERGE", "CALL", "GRANT", + "REVOKE", "COPY", + ]; + + let query_upper = query.to_uppercase(); + for keyword in &dangerous_keywords { + if query_upper.contains(keyword) && !is_safe_context(query, keyword) { + return Err(anyhow::anyhow!( + "Use of '{}' is not allowed in queries", + keyword + )); + } + } + + Ok(()) + } + + // Helper methods similar to sqlite (validate_query_body, validate_table_factor, etc.) + // ... (implementation details similar to sqlite_analysis/executor.rs) +} + +/// Convert PostgreSQL value to JSON +fn postgres_value_to_json( + row: &tokio_postgres::Row, + idx: usize, + type_: &tokio_postgres::types::Type, +) -> anyhow::Result { + // Handle different PostgreSQL types + // Implementation will use tokio_postgres type system + // ... +} + +fn is_safe_context(query: &str, keyword: &str) -> bool { + // Similar to sqlite implementation + let query_upper = query.to_uppercase(); + + // UNION is allowed in SELECT queries + if keyword == "UNION" { + return query_upper.contains("SELECT"); + } + + let keyword_pattern = regex::Regex::new(&format!(r"\b{}\b", regex::escape(keyword))).unwrap(); + !keyword_pattern.is_match(&query_upper) +} +``` + +#### 1.3 Implement Reflection Queries + +**File**: `manager-tools/src/postgresql_reader/mod.rs` + +```rust +fn build_reflection_query( + target: &str, + table_name: Option<&str>, + schema_name: Option<&str>, +) -> Result { + let schema = schema_name.unwrap_or("public"); + + let query = match target.to_lowercase().as_str() { + "tables" => { + format!( + "SELECT table_name, table_type + FROM information_schema.tables + WHERE table_schema = '{}' + ORDER BY table_name", + schema + ) + } + "schema" => { + format!( + "SELECT table_name, column_name, data_type, is_nullable + FROM information_schema.columns + WHERE table_schema = '{}' + ORDER BY table_name, ordinal_position", + schema + ) + } + "table_info" => { + match table_name { + Some(name) => format!( + "SELECT column_name, data_type, is_nullable, column_default + FROM information_schema.columns + WHERE table_schema = '{}' AND table_name = '{}' + ORDER BY ordinal_position", + schema, name + ), + None => return Err(ToolError::InvalidInput( + "table_name is required for table_info reflection".to_string() + )), + } + } + "indexes" => { + format!( + "SELECT indexname, tablename, indexdef + FROM pg_indexes + WHERE schemaname = '{}' + ORDER BY tablename, indexname", + schema + ) + } + "views" => { + format!( + "SELECT table_name, view_definition + FROM information_schema.views + WHERE table_schema = '{}' + ORDER BY table_name", + schema + ) + } + "foreign_keys" => { + match table_name { + Some(name) => format!( + "SELECT + tc.constraint_name, + kcu.column_name, + ccu.table_name AS foreign_table_name, + ccu.column_name AS foreign_column_name + FROM information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu + ON tc.constraint_name = kcu.constraint_name + JOIN information_schema.constraint_column_usage AS ccu + ON ccu.constraint_name = tc.constraint_name + WHERE tc.constraint_type = 'FOREIGN KEY' + AND tc.table_schema = '{}' + AND tc.table_name = '{}'", + schema, name + ), + None => return Err(ToolError::InvalidInput( + "table_name is required for foreign_keys reflection".to_string() + )), + } + } + "stats" => { + format!( + "SELECT + schemaname, + COUNT(*) as table_count, + pg_size_pretty(SUM(pg_total_relation_size(schemaname||'.'||tablename))::bigint) as total_size + FROM pg_tables + WHERE schemaname = '{}' + GROUP BY schemaname", + schema + ) + } + _ => { + return Err(ToolError::InvalidInput( + format!("Unknown reflection target: {}. Valid targets: tables, schema, table_info, indexes, views, foreign_keys, stats", target) + )) + } + }; + + Ok(query) +} +``` + +#### 1.4 Reuse Formatter from SQLite + +The formatter can be shared between sqlite and postgresql: + +**File**: `manager-tools/src/postgresql_reader/formatter.rs` + +```rust +// Re-export formatter from sqlite_analysis +pub use crate::sqlite_analysis::formatter::*; +``` + +### Phase 2: Integrate with manager-tools Type System + +#### 2.1 Create Type Definitions + +**File**: `manager-tools/src/types/postgresql_reader.rs` + +```rust +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct PostgresqlConnection { + #[schemars(description = "PostgreSQL server hostname or IP address")] + pub host: String, + + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(description = "PostgreSQL server port (default: 5432)")] + pub port: Option, + + #[schemars(description = "Database name")] + pub database: String, + + #[schemars(description = "Database user")] + pub user: String, + + #[schemars(description = "Database password")] + pub password: String, + + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(description = "SSL mode: disable, prefer, require (default: prefer)")] + pub ssl_mode: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "mode")] +pub enum PostgresqlMode { + #[serde(rename = "query")] + Query { + #[schemars(description = "SQL query to execute. Only SELECT queries are allowed.")] + query: String, + }, + + #[serde(rename = "reflect")] + Reflect { + #[schemars(description = "Target of reflection: tables, schema, table_info, indexes, views, foreign_keys, stats")] + target: String, + + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(description = "Optional: specific table name for table_info and foreign_keys modes")] + table_name: Option, + + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(description = "Optional: schema name (defaults to 'public')")] + schema_name: Option, + }, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct PostgresqlReaderRequest { + #[schemars(description = "PostgreSQL connection parameters")] + pub connection: PostgresqlConnection, + + #[schemars(description = "Execution mode: either query or reflect")] + pub mode: PostgresqlMode, + + #[serde(skip_serializing_if = "Option::is_none")] + #[schemars(description = "Maximum number of rows to return. Defaults to 100, maximum 1000.")] + pub limit: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +pub struct PostgresqlReaderResponse { + pub columns: Vec, + pub rows: Vec>, + pub row_count: usize, + pub truncated: bool, + pub execution_time_ms: u64, + pub formatted_output: String, +} +``` + +#### 2.2 Update ToolRequest and ToolResponse Enums + +**File**: `manager-tools/src/types/core.rs` + +Add new variant to `ToolRequest`: +```rust +#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)] +#[serde(tag = "type")] +pub enum ToolRequest { + // ... existing variants + #[serde(rename = "postgresql_reader")] + PostgresqlReader(super::postgresql_reader::PostgresqlReaderRequest), +} +``` + +Add new variant to `ToolResponse`: +```rust +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(tag = "type")] +pub enum ToolResponse { + // ... existing variants + #[serde(rename = "postgresql_reader")] + PostgresqlReader(super::postgresql_reader::PostgresqlReaderResponse), +} +``` + +#### 2.3 Update Type Module Exports + +**File**: `manager-tools/src/types/mod.rs` + +Add: +```rust +pub mod postgresql_reader; +// ... existing modules + +pub use postgresql_reader::{ + PostgresqlConnection, PostgresqlMode, PostgresqlReaderRequest, PostgresqlReaderResponse +}; +``` + +### Phase 3: Implement Tool Executor Integration + +#### 3.1 Create Main PostgreSQL Tool Function + +**File**: `manager-tools/src/postgresql_reader/mod.rs` + +```rust +use crate::types::{PostgresqlReaderRequest, PostgresqlReaderResponse, ToolResponse}; +use crate::tool_error::ToolError; +use anyhow::Result; + +mod executor; +mod formatter; + +pub use executor::{QueryResult, PostgresqlExecutor}; + +pub async fn execute_postgresql_reader( + request: PostgresqlReaderRequest, +) -> Result { + const DEFAULT_LIMIT: usize = 100; + const MAX_LIMIT: usize = 1000; + const TIMEOUT_MS: u64 = 5000; + + let limit = request.limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT); + + let executor = PostgresqlExecutor::new(&request.connection, MAX_LIMIT, TIMEOUT_MS) + .await + .map_err(|e| ToolError::ExecutionError(format!("Failed to connect to database: {}", e)))?; + + let (query, target_label) = match request.mode { + PostgresqlMode::Query { query } => (query, None), + PostgresqlMode::Reflect { target, table_name, schema_name } => ( + build_reflection_query(&target, table_name.as_deref(), schema_name.as_deref())?, + Some(target), + ), + }; + + let result = executor + .execute(&query, Some(limit)) + .await + .map_err(|e| ToolError::ExecutionError(format!("Query execution failed: {}", e)))?; + + let formatted_output = match target_label { + Some(target) => { + format!( + "Schema Reflection ({}):\n{}", + target, + formatter::format_query_result(&result) + ) + } + None => formatter::format_query_result(&result), + }; + + Ok(ToolResponse::PostgresqlReader(PostgresqlReaderResponse { + columns: result.columns, + rows: result.rows, + row_count: result.row_count, + truncated: result.truncated, + execution_time_ms: result.execution_time_ms, + formatted_output, + })) +} +``` + +#### 3.2 Update ToolExecutor + +**File**: `manager-tools/src/tool_executor.rs` + +Add postgresql import: +```rust +use crate::postgresql_reader; +``` + +Add match arm in `execute()` method: +```rust +pub async fn execute(&self, request: ToolRequest) -> Result { + match request { + // ... existing match arms + ToolRequest::PostgresqlReader(req) => { + postgresql_reader::execute_postgresql_reader(req).await + } + } +} +``` + +### Phase 4: Add Dependencies + +#### 4.1 Update Cargo.toml + +**File**: `manager-tools/Cargo.toml` + +Add dependencies: +```toml +[dependencies] +# ... existing dependencies +tokio-postgres = "0.7" +postgres-types = "0.2" +``` + +Note: `sqlparser` is already a dependency from sqlite_analysis. + +### Phase 5: Security Considerations + +#### 5.1 Password Handling + +**Security measures:** +1. Passwords are passed in request but not logged +2. Connection strings built securely +3. Connections are short-lived (closed after query) +4. No connection pooling reduces attack surface + +#### 5.2 Query Validation + +**Multi-layer validation:** +1. AST parsing using `sqlparser` with PostgreSQL dialect +2. Only SELECT statements allowed +3. Dangerous keywords blocked +4. Multiple statements blocked +5. Row limits enforced +6. Query timeouts enforced + +#### 5.3 SSL/TLS Support + +**Connection security:** +- Support `disable` mode for local development +- Support `prefer` mode (attempt SSL, fallback to plain) +- Support `require` mode for production (SSL required) +- Future: Support certificate validation + +### Phase 6: Testing + +#### 6.1 Unit Tests + +**File**: `manager-tools/src/postgresql_reader/executor.rs` + +```rust +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_query_validation() { + // Test SELECT queries are allowed + // Test INSERT/UPDATE/DELETE are blocked + // Test multiple statements are blocked + // Test dangerous keywords are blocked + } + + // Additional tests similar to sqlite +} +``` + +#### 6.2 Integration Tests + +**File**: `manager-tools/src/postgresql_reader/mod.rs` + +```rust +#[cfg(test)] +mod tests { + use super::*; + + // Note: Integration tests require a running PostgreSQL instance + // Consider using testcontainers-rs for automated testing + + #[tokio::test] + async fn test_postgresql_reader_query_mode() { + // Setup test database + // Execute SELECT query + // Verify results + } + + #[tokio::test] + async fn test_postgresql_reader_reflect_mode() { + // Setup test database with tables + // Execute reflection queries + // Verify schema information + } +} +``` + +### Phase 7: Documentation + +#### 7.1 Module Documentation + +**File**: `manager-tools/src/postgresql_reader/mod.rs` + +Add comprehensive module documentation similar to sqlite_analysis: +- Security model +- Usage examples +- Supported reflection targets +- Error handling +- Connection parameters + +#### 7.2 Update manager-tools README + +**File**: `manager-tools/README.md` + +Add section documenting the new tool: +```markdown +### PostgreSQL Reader Tool + +Read-only PostgreSQL database query tool for AI agents. + +**Features:** +- Execute SELECT queries with full PostgreSQL SQL support +- Automatic query validation and SQL injection prevention +- Row limiting and query timeouts +- Schema introspection via information_schema +- SSL/TLS connection support +- Table-formatted output optimized for LLMs + +**Usage Example:** +\`\`\`rust +use manager_tools::{ToolExecutor, ToolRequest, PostgresqlReaderRequest, PostgresqlConnection, PostgresqlMode}; + +let request = ToolRequest::PostgresqlReader(PostgresqlReaderRequest { + connection: PostgresqlConnection { + host: "localhost".to_string(), + port: Some(5432), + database: "mydb".to_string(), + user: "readonly_user".to_string(), + password: "secure_password".to_string(), + ssl_mode: Some("prefer".to_string()), + }, + mode: PostgresqlMode::Query { + query: "SELECT * FROM users LIMIT 10".to_string(), + }, + limit: Some(10), +}); + +let response = executor.execute(request).await?; +\`\`\` + +**Security:** +- Only SELECT queries allowed +- Blocks INSERT, UPDATE, DELETE, DROP, etc. +- SQL injection protection via AST parsing +- SSL/TLS connection support +- Short-lived connections (no pooling) +``` + +## Files Changed + +### New Files +- `manager-tools/src/postgresql_reader/mod.rs` - Main module +- `manager-tools/src/postgresql_reader/executor.rs` - PostgresqlExecutor implementation +- `manager-tools/src/postgresql_reader/formatter.rs` - Result formatting (reuses sqlite) +- `manager-tools/src/types/postgresql_reader.rs` - Request/Response types +- `manager-tools/tasks/add-postgresql-reader-tool.md` - This task document + +### Modified Files +- `manager-tools/Cargo.toml` - Add tokio-postgres dependency +- `manager-tools/src/lib.rs` - Add postgresql_reader module +- `manager-tools/src/types/mod.rs` - Add postgresql_reader types export +- `manager-tools/src/types/core.rs` - Add PostgresqlReader variants +- `manager-tools/src/tool_executor.rs` - Add postgresql_reader execution +- `manager-tools/README.md` - Document new tool + +## Testing & Validation + +### Unit Tests +```bash +cd manager-tools +cargo test postgresql_reader +``` + +### Integration Tests (requires PostgreSQL) +```bash +# Start PostgreSQL container for testing +docker run -d --name postgres-test \ + -e POSTGRES_PASSWORD=test \ + -e POSTGRES_DB=testdb \ + -p 5432:5432 \ + postgres:16 + +# Run tests +cd manager-tools +cargo test postgresql_reader -- --ignored + +# Cleanup +docker stop postgres-test && docker rm postgres-test +``` + +### Full Build & Quality Checks +```bash +cd manager-tools +cargo fmt --check +cargo clippy --all-targets -- -D warnings +cargo test +cargo build +``` + +### Manual Testing Checklist +- [ ] Connection to PostgreSQL succeeds +- [ ] SELECT query returns correct results +- [ ] Reflection mode returns schema information +- [ ] INSERT/UPDATE/DELETE are blocked +- [ ] Multiple statements are blocked +- [ ] SQL injection attempts are blocked +- [ ] Row limits are enforced +- [ ] Query timeouts work +- [ ] SSL connections work +- [ ] Invalid credentials error gracefully +- [ ] Network errors handled gracefully +- [ ] Formatted output is readable + +## Success Criteria + +- [ ] postgresql_reader tool integrated into manager-tools +- [ ] All unit tests pass +- [ ] Integration tests pass (with PostgreSQL) +- [ ] No clippy warnings +- [ ] Code properly formatted +- [ ] Documentation complete +- [ ] SELECT queries execute successfully +- [ ] information_schema queries work for schema introspection +- [ ] Write operations strictly blocked +- [ ] SQL injection protection working +- [ ] SSL/TLS connections supported +- [ ] Ready for use in production + +## Comparison with SQLite Tool + +| Feature | SQLite | PostgreSQL | +|---------|--------|------------| +| Connection | File path | Host/port/credentials | +| Async | No (blocking) | Yes (tokio) | +| Schema queries | PRAGMA | information_schema | +| SSL/TLS | N/A | Supported | +| Parser dialect | GenericDialect | PostgreSqlDialect | +| Connection lifecycle | Per-request | Per-request (consistent) | +| Formatter | Custom | Reused from SQLite | + +## Future Enhancements + +1. **Connection pooling**: For high-frequency queries +2. **Certificate validation**: For production SSL connections +3. **Query caching**: For repeated queries +4. **Prepared statements**: For parameterized queries +5. **Transaction support**: For multi-query analysis (read-only) +6. **Connection string support**: Alternative to individual parameters +7. **Schema caching**: Cache information_schema results + +## References + +- **Similar implementation**: `manager-tools/src/sqlite_analysis/` +- **tokio-postgres docs**: https://docs.rs/tokio-postgres/ +- **PostgreSQL information_schema**: https://www.postgresql.org/docs/current/information-schema.html +- **sqlparser docs**: https://docs.rs/sqlparser/ + +## Notes + +- This is a pure addition - no breaking changes to existing tools +- The tool is designed for analysis, not data modification +- Follows proven architecture from sqlite_analysis +- SSL/TLS support enables production use +- information_schema provides standard schema introspection +- Password security: connections are short-lived, passwords not logged +- Consider using read-only database users in production diff --git a/manager-tools/tasks/add-sqlite3-reader-tool.md b/nocodo-tools/tasks/add-sqlite3-reader-tool.md similarity index 100% rename from manager-tools/tasks/add-sqlite3-reader-tool.md rename to nocodo-tools/tasks/add-sqlite3-reader-tool.md diff --git a/manager-tools/tasks/sqlite_dual_mode.md b/nocodo-tools/tasks/sqlite_dual_mode.md similarity index 100% rename from manager-tools/tasks/sqlite_dual_mode.md rename to nocodo-tools/tasks/sqlite_dual_mode.md diff --git a/manager-tools/tests/custom_bash_permissions.rs b/nocodo-tools/tests/custom_bash_permissions.rs similarity index 99% rename from manager-tools/tests/custom_bash_permissions.rs rename to nocodo-tools/tests/custom_bash_permissions.rs index f328f7e2..68d93c7e 100644 --- a/manager-tools/tests/custom_bash_permissions.rs +++ b/nocodo-tools/tests/custom_bash_permissions.rs @@ -1,4 +1,4 @@ -use manager_tools::{ +use nocodo_tools::{ bash::{BashExecutor, BashPermissions}, types::{BashRequest, ToolRequest, ToolResponse}, ToolExecutor,