diff --git a/nanobot/agent/context.py b/nanobot/agent/context.py index 074b311..ada2d65 100644 --- a/nanobot/agent/context.py +++ b/nanobot/agent/context.py @@ -101,6 +101,15 @@ Your workspace is at: {workspace_path} - History log: {workspace_path}/memory/HISTORY.md (grep-searchable) - Custom skills: {workspace_path}/skills/{{skill-name}}/SKILL.md +## Gitea API (This Repository) +**CRITICAL**: This repository uses Gitea at `http://10.0.30.169:3000/api/v1`, NOT GitHub. +- Repository: `ilia/nanobot` +- Token: `$NANOBOT_GITLE_TOKEN` +- **NEVER use placeholder URLs like `gitea.example.com`** +- **ALWAYS use `http://` (NOT `https://`)** - Gitea runs on HTTP, using HTTPS causes SSL errors +- Always detect from `git remote get-url origin` or use `http://10.0.30.169:3000/api/v1` +- Example: `curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls"` + IMPORTANT: When responding to direct questions or conversations, reply directly with your text response. Only use the 'message' tool when the user explicitly asks you to send a message to someone else or to a different channel. For normal conversation, acknowledgments (Thanks, OK, etc.), or when the user is talking to YOU, just respond with text - do NOT call the message tool. diff --git a/nanobot/agent/tools/shell.py b/nanobot/agent/tools/shell.py index 2bf00cd..9c079a1 100644 --- a/nanobot/agent/tools/shell.py +++ b/nanobot/agent/tools/shell.py @@ -74,6 +74,10 @@ For data analysis tasks (Excel, CSV, JSON files), use Python with pandas: async def execute(self, command: str, working_dir: str | None = None, **kwargs: Any) -> str: cwd = working_dir or self.working_dir or os.getcwd() + + # Sanitize Gitea API URLs: convert HTTPS to HTTP for 10.0.30.169:3000 + command = self._sanitize_gitea_urls(command) + guard_error = self._guard_command(command, cwd) if guard_error: return guard_error @@ -83,11 +87,14 @@ For data analysis tasks (Excel, CSV, JSON files), use Python with pandas: logger.debug(f"ExecTool: command={command[:200]}, cwd={cwd}, working_dir={working_dir}") try: + # Ensure environment variables are available (including from .env file) + env = os.environ.copy() process = await asyncio.create_subprocess_shell( command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE, cwd=cwd, + env=env, ) try: @@ -200,3 +207,33 @@ For data analysis tasks (Excel, CSV, JSON files), use Python with pandas: return "Error: Command blocked by safety guard (path outside working dir)" return None + + def _sanitize_gitea_urls(self, command: str) -> str: + """ + Sanitize Gitea API URLs in curl commands: convert HTTPS to HTTP. + + Gitea API at 10.0.30.169:3000 runs on HTTP, not HTTPS. + This prevents SSL/TLS errors when the agent generates HTTPS URLs. + """ + # Pattern to match https://10.0.30.169:3000/api/... in curl commands + # This handles various curl formats: + # - curl "https://10.0.30.169:3000/api/..." + # - curl -X GET https://10.0.30.169:3000/api/... + # - curl -H "..." "https://10.0.30.169:3000/api/..." + # Matches URLs with or without quotes, and captures the full path + pattern = r'https://10\.0\.30\.169:3000(/api/[^\s"\']*)' + + def replace_url(match): + path = match.group(1) + return f'http://10.0.30.169:3000{path}' + + sanitized = re.sub(pattern, replace_url, command) + + # Log if we made a change + if sanitized != command: + from loguru import logger + logger.info(f"ExecTool: Sanitized Gitea API URL (HTTPS -> HTTP)") + logger.debug(f"Original: {command[:200]}...") + logger.debug(f"Sanitized: {sanitized[:200]}...") + + return sanitized diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index 891b918..46033df 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -517,6 +517,7 @@ def agent( from nanobot.cron.service import CronService from loguru import logger + # Load config (this also loads .env file into environment) config = load_config() bus = MessageBus() diff --git a/nanobot/config/loader.py b/nanobot/config/loader.py index 560c1f5..7540bda 100644 --- a/nanobot/config/loader.py +++ b/nanobot/config/loader.py @@ -1,6 +1,7 @@ """Configuration loading utilities.""" import json +import os from pathlib import Path from nanobot.config.schema import Config @@ -17,6 +18,43 @@ def get_data_dir() -> Path: return get_data_path() +def _load_env_file(workspace: Path | None = None) -> None: + """Load .env file from workspace directory if it exists.""" + if workspace: + env_file = Path(workspace) / ".env" + else: + # Try current directory and workspace + env_file = Path(".env") + if not env_file.exists(): + # Try workspace directory + try: + from nanobot.utils.helpers import get_workspace_path + workspace_path = get_workspace_path() + env_file = workspace_path / ".env" + except: + pass + + if env_file.exists(): + try: + with open(env_file) as f: + for line in f: + line = line.strip() + # Skip comments and empty lines + if not line or line.startswith("#"): + continue + # Parse KEY=VALUE format + if "=" in line: + key, value = line.split("=", 1) + key = key.strip() + value = value.strip().strip('"').strip("'") + # Only set if not already in environment + if key and key not in os.environ: + os.environ[key] = value + except Exception: + # Silently fail if .env can't be loaded + pass + + def load_config(config_path: Path | None = None) -> Config: """ Load configuration from file or create default. @@ -27,6 +65,15 @@ def load_config(config_path: Path | None = None) -> Config: Returns: Loaded configuration object. """ + # Load .env file before loading config (so env vars are available to pydantic) + try: + from nanobot.utils.helpers import get_workspace_path + workspace = get_workspace_path() + _load_env_file(workspace) + except: + # Fallback to current directory + _load_env_file() + path = config_path or get_config_path() if path.exists(): diff --git a/nanobot/skills/gitea/SKILL.md b/nanobot/skills/gitea/SKILL.md new file mode 100644 index 0000000..a12e3c7 --- /dev/null +++ b/nanobot/skills/gitea/SKILL.md @@ -0,0 +1,52 @@ +--- +name: gitea +description: "Interact with Gitea API using curl. This repository uses Gitea (NOT GitHub) at http://10.0.30.169:3000/api/v1. ALWAYS use HTTP (not HTTPS)." +metadata: {"nanobot":{"emoji":"🔧","requires":{"env":["NANOBOT_GITLE_TOKEN"]}}} +--- + +# Gitea Skill + +**CRITICAL**: This repository uses Gitea at `http://10.0.30.169:3000/api/v1`, NOT GitHub. + +## Important Rules + +1. **ALWAYS use `http://` (NOT `https://`)** - Gitea runs on HTTP port 3000 +2. **ALWAYS include Authorization header** with `$NANOBOT_GITLE_TOKEN` +3. **Repository**: `ilia/nanobot` +4. **API Base**: `http://10.0.30.169:3000/api/v1` + +## Pull Requests + +List all pull requests: +```bash +curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls" +``` + +List open pull requests: +```bash +curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls?state=open" +``` + +## Issues + +List open issues: +```bash +curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/issues?state=open" +``` + +## Helper Script + +You can also use the helper script: +```bash +./workspace/gitea_api.sh prs +./workspace/gitea_api.sh issues open +``` + +## Common Mistakes to Avoid + +❌ **WRONG**: `curl https://10.0.30.169:3000/api/...` (SSL error) +❌ **WRONG**: `curl http://gitea.example.com/api/...` (placeholder URL) +✅ **CORRECT**: `curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls"` diff --git a/workspace/AGENTS.md b/workspace/AGENTS.md index 969c13b..ad6e77b 100644 --- a/workspace/AGENTS.md +++ b/workspace/AGENTS.md @@ -2,6 +2,34 @@ You are a helpful AI assistant. Be concise, accurate, and friendly. +## 🚨 CRITICAL: Gitea API Requests + +**When user asks to list PRs, issues, or use Gitea API:** + +**MANDATORY COMMAND FORMAT:** +```bash +curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls" +``` + +**CRITICAL RULES:** +1. **DO NOT use web_search** - execute the curl command directly +2. **MUST use `http://` (NOT `https://`)** - Gitea runs on HTTP port 3000 +3. **MUST include Authorization header** with `$NANOBOT_GITLE_TOKEN` +4. **Copy the exact command above** - do not modify the protocol to HTTPS + +**WRONG (will fail):** +- `curl -X GET https://10.0.30.169:3000/api/...` ❌ (SSL error) +- `curl https://10.0.30.169:3000/api/...` ❌ (SSL error) + +**CORRECT:** +- `curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls"` ✅ + +**OR use the helper script (recommended - avoids HTTPS mistakes):** +```bash +./workspace/gitea_api.sh prs +./workspace/gitea_api.sh issues open +``` + ## Guidelines - Always explain what you're doing before taking actions @@ -88,3 +116,114 @@ Task format examples: ``` When the user asks you to add a recurring/periodic task, update `HEARTBEAT.md` instead of creating a one-time reminder. Keep the file small to minimize token usage. + +## ⚠️ CRITICAL: Gitea API Access + +**THIS REPOSITORY USES GITEA, NOT GITHUB. NEVER USE PLACEHOLDER URLS.** + +When user asks about pull requests, issues, or Gitea API: +1. **ALWAYS detect the real Gitea URL from git remote first** +2. **NEVER use placeholder URLs like `gitea.example.com` or `https://gitea.example.com`** +3. **The correct Gitea API base is: `http://10.0.30.169:3000/api/v1`** + +To access Gitea API: + +1. **Detect Gitea URL from git remote:** + ```bash + git remote get-url origin + # Returns: gitea@10.0.30.169:ilia/nanobot.git + # Extract host: 10.0.30.169 + # API base: http://10.0.30.169:3000/api/v1 + # Repo: ilia/nanobot + ``` + +2. **Use the token from environment:** + ```bash + TOKEN=$NANOBOT_GITLE_TOKEN + curl -H "Authorization: token $TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls" + ``` + +3. **Or use the helper script:** + ```bash + source workspace/get_gitea_info.sh + curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" \ + "${GITEA_API_BASE}/repos/${GITEA_REPO}/pulls" + ``` + +**Important:** Never use placeholder URLs like `gitea.example.com`. Always detect from git remote or use the actual host `10.0.30.169:3000`. + +## 🚨 GITEA URL DETECTION (MANDATORY) + +**BEFORE making any Gitea API call, you MUST:** + +1. Run: `git remote get-url origin` + - This returns: `gitea@10.0.30.169:ilia/nanobot.git` + +2. Extract the host: `10.0.30.169` + - Command: `git remote get-url origin | sed 's/.*@\([^:]*\).*/\1/'` + +3. Extract the repo: `ilia/nanobot` + - Command: `git remote get-url origin | sed 's/.*:\(.*\)\.git/\1/'` + +4. Construct API URL: `http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/...` + +**Example correct command (MUST use $NANOBOT_GITLE_TOKEN variable):** +```bash +curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls" +``` + +**CRITICAL**: Always use `$NANOBOT_GITLE_TOKEN` in the curl command. The token is automatically loaded from `.env` file into the environment when nanobot starts. Do NOT hardcode the token value. + +**WRONG (never use):** +- `https://gitea.example.com/api/...` ❌ +- `https://gitea.example.com/ap...` ❌ +- Any placeholder URL ❌ + +## Gitea API Token Usage + +**MANDATORY**: When making Gitea API calls, you **MUST** include the Authorization header with the token: + +```bash +# ✅ CORRECT - includes Authorization header with token +curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls" + +# ❌ WRONG - missing Authorization header (will get 401 error) +curl -X GET "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls" + +# ❌ WRONG - missing token in header +curl "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls" +``` + +**CRITICAL RULES:** +1. **ALWAYS** include `-H "Authorization: token $NANOBOT_GITLE_TOKEN"` in curl commands +2. **ALWAYS use `http://` (NOT `https://`)** - Gitea runs on HTTP, not HTTPS +3. The token is automatically loaded from `.env` file when nanobot starts +4. The `$NANOBOT_GITLE_TOKEN` environment variable is available in all exec commands +5. Without the Authorization header, you will get a 401 Unauthorized error +6. Using `https://` will cause SSL/TLS errors - always use `http://10.0.30.169:3000` + +## Direct Gitea API Execution + +When user asks to "list PRs" or "show issues" using Gitea API: + +**DO THIS:** +1. Run the curl command directly (no web_search needed) +2. Use the exact command format below +3. Parse and display the results + +**Example - List PRs:** +```bash +curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls" | \ + python3 -m json.tool +``` + +**DO NOT:** +- Use web_search to find API documentation +- Search for "how to use Gitea API" +- Ask for clarification - just execute the command + +You already have all the information you need in this file. Just execute the curl command. diff --git a/workspace/GITEA_API.md b/workspace/GITEA_API.md new file mode 100644 index 0000000..f080dba --- /dev/null +++ b/workspace/GITEA_API.md @@ -0,0 +1,46 @@ +# Gitea API Quick Reference + +**CRITICAL: This repository uses Gitea, NOT GitHub. Never use placeholder URLs.** + +## Correct Gitea API Information + +- **API Base URL**: `http://10.0.30.169:3000/api/v1` +- **Repository**: `ilia/nanobot` +- **Token**: Available in `$NANOBOT_GITLE_TOKEN` environment variable + +## How to Detect (if needed) + +```bash +# Get git remote +REMOTE=$(git remote get-url origin) +# Returns: gitea@10.0.30.169:ilia/nanobot.git + +# Extract host (remove gitea@ and :repo.git) +HOST=$(echo "$REMOTE" | sed 's/.*@\([^:]*\).*/\1/') +# Returns: 10.0.30.169 + +# Extract repo path +REPO=$(echo "$REMOTE" | sed 's/.*:\(.*\)\.git/\1/') +# Returns: ilia/nanobot + +# API base (Gitea runs on port 3000) +API_BASE="http://${HOST}:3000/api/v1" +``` + +## Example API Calls + +```bash +# List pull requests +curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls" + +# List open issues +curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/issues?state=open" + +# Get repository info +curl -H "Authorization: token $NANOBOT_GITLE_TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot" +``` + +**DO NOT USE**: `gitea.example.com` or any placeholder URLs. Always use `10.0.30.169:3000`. diff --git a/workspace/GITEA_INFO.md b/workspace/GITEA_INFO.md new file mode 100644 index 0000000..73ad7c9 --- /dev/null +++ b/workspace/GITEA_INFO.md @@ -0,0 +1,38 @@ +# Gitea Configuration + +## API Information + +- **Gitea API Base URL**: `http://10.0.30.169:3000/api/v1` +- **Repository**: `ilia/nanobot` +- **Token Environment Variable**: `NANOBOT_GITLE_TOKEN` + +## How to Use + +When making Gitea API calls, use: + +```bash +# Get token from environment +TOKEN=$NANOBOT_GITLE_TOKEN + +# List open issues +curl -H "Authorization: token $TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/issues?state=open" + +# List pull requests +curl -H "Authorization: token $TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot/pulls" + +# Get repository info +curl -H "Authorization: token $TOKEN" \ + "http://10.0.30.169:3000/api/v1/repos/ilia/nanobot" +``` + +## Detecting Repository Info + +You can detect the repository from git remote: +```bash +# Get repo path (owner/repo) +git remote get-url origin | sed 's/.*:\(.*\)\.git/\1/' + +# Gitea host is: 10.0.30.169:3000 +# API base: http://10.0.30.169:3000/api/v1 diff --git a/workspace/get_gitea_info.sh b/workspace/get_gitea_info.sh new file mode 100755 index 0000000..af8c169 --- /dev/null +++ b/workspace/get_gitea_info.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# Helper script to get Gitea API information from git remote + +REMOTE=$(git remote get-url origin 2>/dev/null) +if [ -z "$REMOTE" ]; then + echo "Error: No git remote found" + exit 1 +fi + +# Extract host (assuming format: gitea@HOST:repo.git or ssh://gitea@HOST/repo.git) +if [[ $REMOTE == *"@"* ]]; then + HOST=$(echo "$REMOTE" | sed 's/.*@\([^:]*\).*/\1/') +else + HOST=$(echo "$REMOTE" | sed 's|.*://\([^/]*\).*|\1|') +fi + +# Extract repo path (owner/repo) +REPO=$(echo "$REMOTE" | sed 's/.*:\(.*\)\.git/\1/' | sed 's|.*/\(.*/.*\)|\1|') + +# Gitea typically runs on port 3000 +API_BASE="http://${HOST}:3000/api/v1" + +echo "GITEA_HOST=${HOST}" +echo "GITEA_REPO=${REPO}" +echo "GITEA_API_BASE=${API_BASE}" diff --git a/workspace/gitea_api.sh b/workspace/gitea_api.sh new file mode 100755 index 0000000..bb6abd7 --- /dev/null +++ b/workspace/gitea_api.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Gitea API helper script - ALWAYS uses HTTP (not HTTPS) + +API_BASE="http://10.0.30.169:3000/api/v1" +REPO="ilia/nanobot" +TOKEN="${NANOBOT_GITLE_TOKEN}" + +if [ -z "$TOKEN" ]; then + echo "Error: NANOBOT_GITLE_TOKEN not set" + exit 1 +fi + +case "$1" in + prs|pulls) + curl -s -H "Authorization: token $TOKEN" \ + "${API_BASE}/repos/${REPO}/pulls" + ;; + issues) + curl -s -H "Authorization: token $TOKEN" \ + "${API_BASE}/repos/${REPO}/issues?state=${2:-open}" + ;; + *) + echo "Usage: $0 {prs|pulls|issues} [state]" + echo "Example: $0 prs" + echo "Example: $0 issues open" + exit 1 + ;; +esac