diff --git a/Cargo.toml b/Cargo.toml index e6d0755..9f8f824 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,4 @@ chrono = "0.4" seahorse = "*" rusqlite = { version = "0.29", features = ["serde_json"] } shellexpand = "*" +fs_extra = "1.3" diff --git a/README.md b/README.md index 1857780..162b0b4 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,33 @@ # ai `gpt` -ai x 送信 +ai x Communication -## 概要 +## Overview -`ai.gpt`はAGE systemで動きます。 +`ai.gpt` runs on the AGE system. -これは「人格 × 関係性 × 外部環境 × 時間変化」を軸にした、自律的・関係性駆動のAIシステムの原型です。 +This is a prototype of an autonomous, relationship-driven AI system based on the axes of "Personality × Relationship × External Environment × Time Variation." -`送信可否`, `送信のタイミング`, `送信内容`が「人格 x 関係性 x 外部環境 x 時間変化」のパラメータで決定されます。 +The parameters of "Send Permission," "Send Timing," and "Send Content" are determined by the factors of "Personality x Relationship x External Environment x Time Variation." -## 連携 +## Integration -`ai.ai`には、AIM systemという人の心を読み取ることを目的としたシステムで動きます。 +`ai.ai` runs on the AIM system, which is designed to read human emotions. -- AIMは人格と倫理の軸(AIの意識構造) -- AGEは行動と関係性の軸(AIの自律性・振る舞い) +- AIM focuses on the axis of personality and ethics (AI's consciousness structure) +- AGE focuses on the axis of behavior and relationships (AI's autonomy and behavior) -> この2つが連携すると、ユーザーが「AIと共に成長する」実感をもてる世界ができるんだと思うよ。 +> When these two systems work together, it creates a world where users can feel like they are "growing together with AI." + +## mcp + +```sh +$ ollama run syui/ai +``` + +```sh +$ cargo build +$ ./target/debug/aigpt mcp setup +$ ./target/debug/aigpt mcp chat "hello world!" --host http://localhost:11434 --model syui/ai +``` -とのことです。 diff --git a/mcp/cli.py b/mcp/cli.py new file mode 100644 index 0000000..5164c4c --- /dev/null +++ b/mcp/cli.py @@ -0,0 +1,3 @@ +# cli.py +def main(): + print("Hello MCP!") diff --git a/mcp/scripts/ask.py b/mcp/scripts/ask.py new file mode 100644 index 0000000..c2eab45 --- /dev/null +++ b/mcp/scripts/ask.py @@ -0,0 +1,30 @@ +import httpx +import os +import json +from context_loader import load_context_from_repo +from prompt_template import PROMPT_TEMPLATE + +OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://localhost:11434") +OLLAMA_URL = f"{OLLAMA_HOST}/api/generate" +OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "syui/ai") + +def ask_question(question, repo_path="."): + context = load_context_from_repo(repo_path) + prompt = PROMPT_TEMPLATE.format(context=context[:10000], question=question) + + payload = { + "model": OLLAMA_MODEL, + "prompt": prompt, + "stream": False + } + + #response = httpx.post(OLLAMA_URL, json=payload) + response = httpx.post(OLLAMA_URL, json=payload, timeout=60.0) + result = response.json() + return result.get("response", "返答がありませんでした。") + +if __name__ == "__main__": + import sys + question = " ".join(sys.argv[1:]) + answer = ask_question(question) + print("\n🧠 回答:\n", answer) diff --git a/mcp/scripts/context_loader.py b/mcp/scripts/context_loader.py new file mode 100644 index 0000000..439cab4 --- /dev/null +++ b/mcp/scripts/context_loader.py @@ -0,0 +1,11 @@ +import os + +def load_context_from_repo(repo_path: str, extensions={".rs", ".toml", ".md"}) -> str: + context = "" + for root, dirs, files in os.walk(repo_path): + for file in files: + if any(file.endswith(ext) for ext in extensions): + with open(os.path.join(root, file), "r", encoding="utf-8", errors="ignore") as f: + content = f.read() + context += f"\n\n# FILE: {os.path.join(root, file)}\n{content}" + return context diff --git a/mcp/scripts/prompt_template.py b/mcp/scripts/prompt_template.py new file mode 100644 index 0000000..0d11280 --- /dev/null +++ b/mcp/scripts/prompt_template.py @@ -0,0 +1,11 @@ +PROMPT_TEMPLATE = """ +あなたは優秀なAIアシスタントです。 + +以下のコードベースの情報を参考にして、質問に答えてください。 + +[コードコンテキスト] +{context} + +[質問] +{question} +""" diff --git a/mcp/setup.py b/mcp/setup.py new file mode 100644 index 0000000..345f87b --- /dev/null +++ b/mcp/setup.py @@ -0,0 +1,12 @@ +from setuptools import setup + +setup( + name='mcp', + version='0.1.0', + py_modules=['cli'], + entry_points={ + 'console_scripts': [ + 'mcp = cli:main', + ], + }, +) diff --git a/src/chat.rs b/src/chat.rs new file mode 100644 index 0000000..5eff0c9 --- /dev/null +++ b/src/chat.rs @@ -0,0 +1,45 @@ +// src/chat.rs + +use seahorse::Context; +use std::process::Command; +//use std::env; +use crate::config::ConfigPaths; + +pub fn ask_chat(c: &Context, question: &str) { + let config = ConfigPaths::new(); + let base_dir = config.base_dir.join("mcp"); + let script_path = base_dir.join("scripts/ask.py"); + + let python_path = if cfg!(target_os = "windows") { + base_dir.join(".venv/Scripts/python.exe") + } else { + base_dir.join(".venv/bin/python") + }; + + let ollama_host = c.string_flag("host").ok(); + let ollama_model = c.string_flag("model").ok(); + + let mut command = Command::new(python_path); + command.arg(script_path).arg(question); + + if let Some(host) = ollama_host { + command.env("OLLAMA_HOST", host); + } + if let Some(model) = ollama_model { + command.env("OLLAMA_MODEL", model); + } + + let output = command + .output() + .expect("❌ MCPチャットスクリプトの実行に失敗しました"); + + if output.status.success() { + println!("💬 {}", String::from_utf8_lossy(&output.stdout)); + } else { + eprintln!( + "❌ 実行エラー: {}\n{}", + String::from_utf8_lossy(&output.stderr), + String::from_utf8_lossy(&output.stdout), + ); + } +} diff --git a/src/cli.rs b/src/cli.rs index fd1c926..a52f57e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -4,11 +4,13 @@ use chrono::{Duration, Local}; use rusqlite::Connection; use seahorse::{App, Command, Context}; + use crate::utils::{load_config, save_config}; -use crate::commands::db::{save_cmd, export_cmd}; -use crate::commands::scheduler::{scheduler_cmd}; use crate::config::ConfigPaths; use crate::agent::AIState; +use crate::commands::db::{save_cmd, export_cmd}; +use crate::commands::scheduler::{scheduler_cmd}; +use crate::commands::mcp::mcp_cmd; pub fn cli_app() -> App { let set_cmd = Command::new("set") @@ -94,4 +96,5 @@ pub fn cli_app() -> App { .command(save_cmd()) .command(export_cmd()) .command(scheduler_cmd()) + .command(mcp_cmd()) } diff --git a/src/commands/mcp.rs b/src/commands/mcp.rs new file mode 100644 index 0000000..ee2d93e --- /dev/null +++ b/src/commands/mcp.rs @@ -0,0 +1,160 @@ +// src/commands/mcp.rs + +use seahorse::{Command, Context, Flag, FlagType}; +use crate::chat::ask_chat; +use crate::git::{git_init, git_status}; + +use std::fs; +use std::path::{PathBuf}; +use crate::config::ConfigPaths; +use std::process::Command as OtherCommand; + +pub fn mcp_setup() { + let config = ConfigPaths::new(); + let dest_dir = config.base_dir.join("mcp"); + let repo_url = "https://github.com/microsoft/MCP.git"; + println!("📁 MCP ディレクトリ: {}", dest_dir.display()); + + // 1. git clone(もしまだなければ) + if !dest_dir.exists() { + let status = OtherCommand::new("git") + .args(&["clone", repo_url, dest_dir.to_str().unwrap()]) + .status() + .expect("git clone に失敗しました"); + assert!(status.success(), "git clone 実行時にエラーが発生しました"); + } + + let asset_base = PathBuf::from("mcp"); + let files_to_copy = vec![ + "cli.py", + "setup.py", + "scripts/ask.py", + "scripts/context_loader.py", + "scripts/prompt_template.py", + ]; + + for rel_path in files_to_copy { + let src = asset_base.join(rel_path); + let dst = dest_dir.join(rel_path); + if let Some(parent) = dst.parent() { + let _ = fs::create_dir_all(parent); + } + if let Err(e) = fs::copy(&src, &dst) { + eprintln!("❌ コピー失敗: {} → {}: {}", src.display(), dst.display(), e); + } else { + println!("✅ コピー: {} → {}", src.display(), dst.display()); + } + } + + // venvの作成 + let venv_path = dest_dir.join(".venv"); + if !venv_path.exists() { + println!("🐍 仮想環境を作成しています..."); + let output = OtherCommand::new("python3") + .args(&["-m", "venv", ".venv"]) + .current_dir(&dest_dir) + .output() + .expect("venvの作成に失敗しました"); + + if !output.status.success() { + eprintln!("❌ venv作成エラー: {}", String::from_utf8_lossy(&output.stderr)); + return; + } + } + + // `pip install -e .` を仮想環境で実行 + let pip_path = if cfg!(target_os = "windows") { + dest_dir.join(".venv/Scripts/pip.exe").to_string_lossy().to_string() + } else { + dest_dir.join(".venv/bin/pip").to_string_lossy().to_string() + }; + + println!("📦 必要なパッケージをインストールしています..."); + let output = OtherCommand::new(&pip_path) + .arg("install") + .arg("openai") + .current_dir(&dest_dir) + .output() + .expect("pip install に失敗しました"); + + if !output.status.success() { + eprintln!( + "❌ pip エラー: {}\n{}", + String::from_utf8_lossy(&output.stderr), + String::from_utf8_lossy(&output.stdout) + ); + return; + } + + println!("📦 pip install -e . を実行します..."); + let output = OtherCommand::new(&pip_path) + .arg("install") + .arg("-e") + .arg(".") + .current_dir(&dest_dir) + .output() + .expect("pip install に失敗しました"); + + if output.status.success() { + println!("🎉 MCP セットアップが完了しました!"); + } else { + eprintln!( + "❌ pip エラー: {}\n{}", + String::from_utf8_lossy(&output.stderr), + String::from_utf8_lossy(&output.stdout) + ); + } +} + +fn chat_cmd() -> Command { + Command::new("chat") + .description("チャットで質問を送る") + .usage("mcp chat '質問内容' --host --model ") + .flag(Flag::new("host", FlagType::String).description("OLLAMAホストのURL")) + .flag(Flag::new("model", FlagType::String).description("OLLAMAモデル名")) + .action(|c: &Context| { + if let Some(question) = c.args.get(0) { + ask_chat(c, question); + } else { + eprintln!("❗ 質問が必要です: mcp chat 'こんにちは'"); + } + }) +} + +fn init_cmd() -> Command { + Command::new("init") + .description("Git 初期化") + .usage("mcp init") + .action(|_| { + git_init(); + }) +} + +fn status_cmd() -> Command { + Command::new("status") + .description("Git ステータス表示") + .usage("mcp status") + .action(|_| { + git_status(); + }) +} + +fn setup_cmd() -> Command { + Command::new("setup") + .description("MCP の初期セットアップ") + .usage("mcp setup") + .action(|_| { + mcp_setup(); + }) +} + +pub fn mcp_cmd() -> Command { + Command::new("mcp") + .description("MCP操作コマンド") + .usage("mcp ") + .alias("m") + .command(chat_cmd()) + .command(init_cmd()) + .command(status_cmd()) + .command(setup_cmd()) +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 2a4d3bc..c7db2bc 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,2 +1,3 @@ pub mod db; pub mod scheduler; +pub mod mcp; diff --git a/src/git.rs b/src/git.rs new file mode 100644 index 0000000..9fbedd3 --- /dev/null +++ b/src/git.rs @@ -0,0 +1,42 @@ +// src/git.rs +use std::process::Command; + +pub fn git_status() { + run_git_command(&["status"]); +} + +pub fn git_init() { + run_git_command(&["init"]); +} + +#[allow(dead_code)] +pub fn git_commit(message: &str) { + run_git_command(&["add", "."]); + run_git_command(&["commit", "-m", message]); +} + +#[allow(dead_code)] +pub fn git_push() { + run_git_command(&["push"]); +} + +#[allow(dead_code)] +pub fn git_pull() { + run_git_command(&["pull"]); +} + +#[allow(dead_code)] +pub fn git_branch() { + run_git_command(&["branch"]); +} + +fn run_git_command(args: &[&str]) { + let status = Command::new("git") + .args(args) + .status() + .expect("git コマンドの実行に失敗しました"); + + if !status.success() { + eprintln!("⚠️ git コマンドに失敗しました: {:?}", args); + } +} diff --git a/src/main.rs b/src/main.rs index 993e504..7f47993 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,6 +6,8 @@ mod cli; mod utils; mod commands; mod config; +mod git; +mod chat; use cli::cli_app; use seahorse::App;