Skill: dbdoctor-das/dbdoctor-tools (version 1.0.5)

--- name: dbdoctor-tools description: > DBdoctor database performance diagnosis platform tools. Invoke when user needs to query database instances, slow SQL, inspection reports, performance metrics, or perform SQL audit/rewrite operations. license: Apache-2.0 compatibility: Requires Python 3.9+ with requests, pycryptodome, python-dotenv packages. Network access to DBdoctor API server required. allowed-tools: Bash Read requires: env: - DBDOCTOR_URL commands: - python - pip metadata: author: DBdoctor-DAS version: 1.1.0 tags: [database, performance, diagnosis, slow-sql, sql-audit, monitoring, dbdoctor] --- ## Quick Start Examples ### Scenario 1: Diagnose Instance Performance Issues (Recommended) ```bash # 1. Get tenant and project python scripts/get_current_user.py --extract # 2. Get instance list python scripts/get_instance.py --tenant [tenant] --project [project] # 3. Execute comprehensive performance diagnosis (last 1 hour) python scripts/performance_diagnosis.py --instance-id [instance_id] --start-time [start_ts] --end-time [end_ts] ``` ### Scenario 2: Execute Instance Inspection ```bash # 1. Get tenant and project of the instance python scripts/get_instance.py # 2. Execute inspection python scripts/do_inspect_instance.py --instance-id [instance_id] --tenant [tenant] --project [project] # 3. Get inspection report python scripts/get_recent_inspect_report.py --instance-id [instance_id] --start-time [start] --end-time [end] --tenant [tenant] --project [project] ``` ### Scenario 3: SQL Optimization ```bash # 1. Get slow SQL list python scripts/get_slow_sql.py --instance-id [instance_id] --start-time [start] --end-time [end] # 2. Audit slow SQL python scripts/sql_audit.py --instance-id [instance_id] --database [db] --schema [schema] --sql "[sql]" # 3. Use AI to rewrite SQL (optional) python scripts/ai_sql_rewrite.py --instance-id [instance_id] --database [db] --schema [schema] --sql "[sql]" ``` *** ## Security ### Credential Management This skill supports two authentication modes: - **Mode 1 - Password login** (企业版（免费试用）): Requires `DBDOCTOR_URL`, `DBDOCTOR_USER`, `DBDOCTOR_PASSWORD`. - **Mode 2 - Email verification code login** (免费版（永久免费）, Windows/Mac): Requires `DBDOCTOR_URL`, `DBDOCTOR_EMAIL`. When a verification code is needed, the user will be prompted interactively. If `DBDOCTOR_EMAIL` is configured, email mode takes precedence. Credentials are managed by the platform and injected as environment variables at runtime. **This skill does not write credentials to disk.** The `.token_cache` file (API session token only) is the only file persisted locally and is listed in `.gitignore`. ### Privileged Operations Two tools perform write operations that require operator care: - **execute\_sql**: Executes arbitrary SQL on the target database. Review all SQL statements before execution. The tool does not enforce read-only restrictions. - **manage\_instance**: Registers new database instances to the platform. Verify all connection parameters (IP, port, credentials) before execution. All other tools are read-only queries against the DBdoctor API. ### Authentication Mechanism The program supports two login methods: 1. **Password mode**: Reads username/password from environment variables, AES-encrypts the password, and calls `/nephele/login` to obtain a Token. 2. **Email mode**: Sends a verification code to the configured email via `/drapi/user/verificationCode`, prompts the user to enter the code, AES-encrypts it, and calls `/nephele/login` with `authType=authCode`. Token is cached in `.token_cache`. When the token expires, the system automatically re-authenticates (password mode is silent; email mode prompts for a new verification code). No manual auth management is required. *** ## Configuration Set the following environment variables based on your login mode: | Variable | Description | Required | | --- | --- | --- | | DBDOCTOR\_URL | DBdoctor API base URL (e.g. `http://host:port`) | Always | | DBDOCTOR\_USER | Login username (also used as UserId) | Password mode only | | DBDOCTOR\_PASSWORD | Login password (sensitive) | Password mode only | | DBDOCTOR\_EMAIL | Login email for verification code | Email mode only | > **Note**: If `DBDOCTOR_EMAIL` is set, email verification code mode is used. Otherwise, username/password mode is used. ### Mode 1: Password login (企业版（免费试用）) ```bash # CLI configuration (recommended) clawdbot skills config dbdoctor-tools DBDOCTOR_URL "http://[host]:[port]" clawdbot skills config dbdoctor-tools DBDOCTOR_USER "[username]" clawdbot skills config dbdoctor-tools DBDOCTOR_PASSWORD "[password]" ``` ### Mode 2: Email verification code login (免费版（永久免费）) ```bash # CLI configuration (recommended) clawdbot skills config dbdoctor-tools DBDOCTOR_URL "http://[host]:[port]" clawdbot skills config dbdoctor-tools DBDOCTOR_EMAIL "[email]" ``` ### Manual configuration Edit `~/.clawdbot/clawdbot.json`: ```json5 { skills: { entries: { "dbdoctor-tools": { env: { // Mode 1: Password login DBDOCTOR_URL: "http://[host]:[port]", DBDOCTOR_USER: "[username]", DBDOCTOR_PASSWORD: "[password]" // Mode 2: Email login (use this instead of USER/PASSWORD) // DBDOCTOR_URL: "http://[host]:[port]", // DBDOCTOR_EMAIL: "[email]" } } } } } ``` ### System environment variables ```bash # Linux / Mac - Password mode export DBDOCTOR_URL="http://[host]:[port]" export DBDOCTOR_USER="[username]" export DBDOCTOR_PASSWORD="[password]" # Linux / Mac - Email mode export DBDOCTOR_URL="http://[host]:[port]" export DBDOCTOR_EMAIL="[email]" # Windows PowerShell - Password mode $env:DBDOCTOR_URL="http://[host]:[port]" $env:DBDOCTOR_USER="[username]" $env:DBDOCTOR_PASSWORD="[password]" # Windows PowerShell - Email mode $env:DBDOCTOR_URL="http://[host]:[port]" $env:DBDOCTOR_EMAIL="[email]" ``` ### Install Dependencies ```bash pip install -r requirements.txt ``` Dependencies: requests, pycryptodome, python-dotenv *** ## Instance Information Retrieval Guidelines **Important**: When tenant and project information is needed, **it must be dynamically retrieved through tools**, and is prohibited from being extracted directly from user input. **Strictly prohibited from fabricating tenant and project information** **Method 1: Retrieve via get_current_user (Recommended)** ``` 1. Call get_current_user --extract to get all tenants and projects 2. Select target tenant and project 3. Call get_instance --tenant xxx --project yyy 4. Select target instance and execute other operations ``` **Method 2: Retrieve via get_instance (Recommended)** ``` 1. Call get_instance to query all instances (no parameters needed) 2. Find target instance from returned data 3. Extract tenant and project from instance data ``` ### API Usage Constraints **Strictly prohibited from calling interfaces not defined in this document** - Only use tools and interfaces listed in the API Reference - Prohibited from fabricating or inferring interface paths - Prohibited from calling interfaces of other systems or services *** ## Tool Combination Patterns ### Pattern 1: Performance Diagnosis Workflow (Most Common) ``` get_current_user --extract | get_instance --tenant xxx --project yyy | performance_diagnosis --instance-id xxx --start-time t1 --end-time t2 | [Based on diagnosis results] - Many slow SQLs -> sql_audit / ai_sql_rewrite - Resource bottleneck -> get_host_resource_info / get_basic_monitor_info - High active sessions -> get_aas_info / get_current_process ``` ### Pattern 2: Instance Inspection Workflow ``` get_instance -> do_inspect_instance -> get_recent_inspect_report ``` ### Pattern 3: SQL Optimization Workflow ``` get_slow_sql / get_related_sql_info -> sql_audit -> ai_sql_rewrite (if needed) ``` ### Pattern 4: New Instance Registration Workflow ``` get_current_user --extract -> manage_instance -> get_instance (confirm) ``` *** ## Information Collection Matrix | Task Type | Required Information | Collection Strategy | Notes | | --- | --- | --- | --- | | Query Instance | None | Call tool directly | Get instance list and tenant/project | | **Instance Inspection** | **Instance ID** | **Check -> Ask -> Call** | tenant/project via get_instance | | **Performance Diagnosis** | **Instance ID + Time Range** | **Check -> Ask -> Call** | tenant/project via get_instance | | **View Data** | **Instance ID** | **Check -> Ask -> Call** | tenant/project via get_instance | For detailed processing strategies, decision trees and rules: `reference/agent_guidelines.md` *** ## Tool API Reference For complete API documentation with parameters, examples, and related pages, see: [reference/api_reference.md](reference/api_reference.md) ### Quick Reference: Parameter Requirements Summary | Tool | Required Parameters | | --- | --- | | get\_instance | None (returns all instances) | | get\_current\_user | None (get current user tenant-project info) | | get\_instance\_abnormal | --instance-id | | get\_database\_by\_instance | --instance-id | | manage\_instance | --ip, --port, --engine, --db-user, --db-password, --db-version, --tenant, --project | | get\_slow\_sql | --instance-id, --start-time, --end-time | | get\_table\_ddl | --instance-id, --database, --schema, --table | | execute\_sql | --instance-id, --database, --schema, --sql, --engine, --tenant, --project | | sql\_audit | --instance-id, --database, --schema, --sql | | get\_sql\_audit\_rules | --engine (optional), --priority (optional) | | do\_inspect\_instance | --instance-id, --tenant (optional), --project (optional) | | get\_recent\_inspect\_report | --instance-id, --start-time, --end-time, --tenant, --project | | get\_inspect\_item | None | | get\_current\_process | --instance-id | | alert\_message | --status (optional), --priority (optional), --instance-ip (optional) | | performance\_diagnosis | --instance-id, --start-time, --end-time (comprehensive, recommended) | | get\_basic\_monitor\_info | --instance-id, --start-time, --end-time | | get\_host\_resource\_info | --instance-id, --start-time, --end-time | | get\_db\_parameter\_info | --instance-id | | get\_aas\_info | --instance-id, --start-time, --end-time | | get\_related\_sql\_info | --instance-id, --start-time, --end-time | | get\_instance\_info | --instance-id | | get\_slow\_sql\_by\_time | --instance-id, --start-time, --end-time | | ai\_sql\_rewrite | --instance-id, --database, --schema, --sql | | get\_sql\_rewrite\_result | --task-id | *** ## Notes 1. **Timestamps**: Time range parameters use Unix timestamps (seconds), not milliseconds 2. **Schema**: For MySQL, schema name equals database name 3. **Engine Types**: mysql, oracle, postgresql, dm (Dameng), sqlserver, oracle-rac 4. **SQL Parameter**: When --sql contains spaces or special characters, wrap in quotes 5. **tenant/project**: Must be obtained via `get_instance` — never fabricate or extract from user input 6. **Time Range Default**: Alert queries default to last 2 hours if not specified 7. **Performance Diagnosis**: Recommended time ranges: last 1h, 6h, or 24h ### Reference Document Index - `reference/api_reference.md` - Complete Tool API Reference (parameters, examples, related pages) - `reference/performance_diagnosis_guide.md` - Performance Diagnosis Knowledge Base - `reference/best_practices.md` - Best Practices Guide - `reference/audit_and_inspection_rules.md` - SQL Audit Rules and Inspection Rules - `reference/troubleshooting.md` - Common Issues and Solutions - `reference/agent_guidelines.md` - Agent Processing Strategies and Decision Guidelines

path: common/auth.py
content: """
auth.py - Authentication and Token management module

Responsibilities:
  - AES encrypt password (ECB/PKCS7 compatible with frontend CryptoJS)
  - Call login API to get authToken (password mode or email verification code mode)
  - Token file cache (.token_cache) and auto refresh
"""

import os
import sys
import base64

import requests
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

from common.config import config, LOGIN_MODE_EMAIL

# AES key (same as frontend)
AES_KEY = "loo2vrx79g87luhvj06lodb0c3lp3gqw"

# Token cache file path (in project directory)
_TOKEN_CACHE_FILE = os.path.join(os.path.dirname(os.path.dirname(__file__)), ".token_cache")

# Max retry attempts for verification code login
_MAX_CODE_ATTEMPTS = 3


class AuthError(Exception):
    """Authentication error exception"""
    pass


def encrypt_password(plain_password: str, disabled: bool = False) -> str:
    """
    Encrypt password using AES-ECB, return Base64 encoded string.
    Compatible with frontend CryptoJS.AES.encrypt (ECB mode, PKCS7 padding).
    
    Args:
        plain_password: Plaintext password
        disabled: Whether to disable encryption (return plaintext directly)
    """
    if disabled:
        return plain_password
    
    key = AES_KEY.encode("utf-8")
    cipher = AES.new(key, AES.MODE_ECB)
    padded_data = pad(plain_password.encode("utf-8"), AES.block_size)
    encrypted = cipher.encrypt(padded_data)
    return base64.b64encode(encrypted).decode("utf-8")


def send_verification_code(email: str) -> dict:
    """
    Send verification code to the given email address.

    POST {base_url}/drapi/user/verificationCode
    Payload: {"email": email}

    Args:
        email: Target email address
    Returns:
        Parsed JSON response dict
    Raises:
        AuthError: If the request fails or API returns failure
    """
    url = f"{config.base_url}/drapi/user/verificationCode"
    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json, text/plain, */*",
    }
    payload = {"email": email}

    try:
        resp = requests.post(url, json=payload, headers=headers, verify=False, timeout=30)
        resp.raise_for_status()
    except requests.RequestException as e:
        raise AuthError(f"Failed to send verification code: {e}")

    data = resp.json()
    if not data.get("Success"):
        raise AuthError(f"Failed to send verification code: {data.get('Message', data)}")

    return data


def _prompt_verification_code(email: str) -> str:
    """
    Prompt user to enter the verification code received via email.
    Prints prompt to stderr to avoid polluting stdout JSON output.

    Args:
        email: The email address where the code was sent
    Returns:
        The verification code string (stripped)
    Raises:
        AuthError: If input is empty or stdin is not available (EOFError)
    """
    print(f"\nVerification code has been sent to {email}", file=sys.stderr)
    print("Please check your email and enter the verification code.", file=sys.stderr)

    try:
        code = input("Verification code: ").strip()
    except EOFError:
        raise AuthError(
            "Cannot read verification code: no interactive input available. "
            "Email verification code login requires interactive terminal input."
        )

    if not code:
        raise AuthError("Verification code cannot be empty.")

    return code


def _login_with_email() -> str:
    """
    Login using email and verification code.

    Flow:
      1. Send verification code to config.email
      2. Prompt user to enter the code
      3. Encrypt the code using AES-ECB (same as password encryption)
      4. POST /nephele/login with authType=authCode

    Allows up to _MAX_CODE_ATTEMPTS retries for incorrect codes.
    Returns authToken string, raises AuthError on failure.
    """
    email = config.email

    # Step 1: Send verification code
    send_verification_code(email)

    # Step 2-4: Prompt code and attempt login (with retry)
    url = f"{config.base_url}/nephele/login"
    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json, text/plain, */*",
    }

    last_error = None
    for attempt in range(1, _MAX_CODE_ATTEMPTS + 1):
        code = _prompt_verification_code(email)
        encrypted_code = encrypt_password(code)

        payload = {
            "userName": email,
            "password": encrypted_code,
            "authType": "authCode",
        }

        try:
            resp = requests.post(url, json=payload, headers=headers, verify=False, timeout=30)
            resp.raise_for_status()
        except requests.RequestException as e:
            raise AuthError(f"Login request failed: {e}")

        data = resp.json()
        if data.get("success"):
            [REDACTED_SECRET]"data", {}).get("authToken")
            if not token:
                raise AuthError(f"authToken not found in login response: {data}")
            return token

        # Login failed - possibly wrong code
        last_error = data
        remaining = _MAX_CODE_ATTEMPTS - attempt
        if remaining > 0:
            print(
                f"Login failed (incorrect code or code expired). "
                f"{remaining} attempt(s) remaining.",
                file=sys.stderr,
            )

    raise AuthError(f"Login failed after {_MAX_CODE_ATTEMPTS} attempts: {last_error}")


def _login_with_password() -> str:
    """
    Login using username and password (one-click launch version).

    POST {base_url}/nephele/login
    Return authToken string, raise AuthError on failure.
    """
    url = f"{config.base_url}/nephele/login"
    headers = {
        "Content-Type": "application/json",
        "Accept": "application/json, text/plain, */*",
    }
    payload = {
        "userName": config.username,
        "password": encrypt_password(config.password),
    }

    try:
        resp = requests.post(url, json=payload, headers=headers, verify=False, timeout=30)
        resp.raise_for_status()
    except requests.RequestException as e:
        raise AuthError(f"Login request failed: {e}")

    data = resp.json()
    if not data.get("success"):
        raise AuthError(f"Login failed: {data}")

    [REDACTED_SECRET]"data", {}).get("authToken")
    if not token:
        raise AuthError(f"authToken not found in login response: {data}")

    return token


def _login() -> str:
    """
    Call login API to get authToken.
    Dispatches to password or email login based on config.login_mode.
    """
    if config.login_mode == LOGIN_MODE_EMAIL:
        return _login_with_email()
    return _login_with_password()


def _save_token(token: str) -> None:
    """Write token to cache file"""
    with open(_TOKEN_CACHE_FILE, "w", encoding="utf-8") as f:
        f.write(token)


def _load_token() -> str | None:
    """Read token from cache file, return None if not exists"""
    if not os.path.exists(_TOKEN_CACHE_FILE):
        return None
    try:
        with open(_TOKEN_CACHE_FILE, "r", encoding="utf-8") as f:
            [REDACTED_SECRET]).strip()
            return token if token else None
    except Exception:
        return None


def _clear_token() -> None:
    """Clear cached token file"""
    if os.path.exists(_TOKEN_CACHE_FILE):
        os.remove(_TOKEN_CACHE_FILE)


def get_token() -> str:
    """
    Get current valid authToken.
    Read from cache file first, auto login and cache if not exists.
    """
    [REDACTED_SECRET])
    if [REDACTED_SECRET] token

    [REDACTED_SECRET])
    _save_token(token)
    return token


def refresh_token() -> str:
    """
    Force refresh token: clear cache → re-login → cache new token.
    """
    _clear_token()
    [REDACTED_SECRET])
    _save_token(token)
    return token


path: common/client.py
content: """
client.py - HTTP request wrapper module

Responsibilities:
  - Wrap GET / POST requests
  - Auto inject auth_token header
  - Auto refresh and retry once when token expires
"""

import sys

import requests as _requests

from common.auth import get_token, refresh_token
from common.config import config


def _build_headers(token: str) -> dict:
    """Build common headers with auth_token"""
    return {
        "Content-Type": "application/json",
        "auth_token": token,
    }


def _is_auth_failure(resp: _requests.Response) -> bool:
    """
    Check if response is authentication failure.
    - HTTP 401
    - Or response JSON has success=false and contains token-related error
    """
    if resp.status_code == 401:
        return True
    try:
        data = resp.json()
        if not data.get("success"):
            msg = str(data.get("message", "")).lower()
            if "token" in msg or "auth" in msg or "login" in msg or "unauthorized" in msg:
                return True
    except Exception:
        pass
    return False


def get(path: str, params: dict | None = None) -> dict:
    """
    Send GET request.

    Args:
        path: Relative path, e.g., /drapi/ai/instanceMessage
        params: Query parameters dict
    Returns:
        Parsed JSON response dict
    """
    url = f"{config.base_url}{path}"
    [REDACTED_SECRET])
    resp = _requests.get(url, params=params, headers=_build_headers(token), verify=False, timeout=30)

    # Refresh and retry once when token expires
    if _is_auth_failure(resp):
        print("Token expired. Re-authentication required...", file=sys.stderr)
        [REDACTED_SECRET])
        resp = _requests.get(url, params=params, headers=_build_headers(token), verify=False, timeout=30)

    resp.raise_for_status()
    return resp.json()


def post(path: str, params: dict | None = None, json_body: dict | None = None) -> dict:
    """
    Send POST request.

    Args:
        path: Relative path, e.g., /drapi/sqlAudit/submit
        params: URL query parameters dict
        json_body: Request body dict
    Returns:
        Parsed JSON response dict
    """
    url = f"{config.base_url}{path}"
    [REDACTED_SECRET])
    resp = _requests.post(url, params=params, json=json_body, headers=_build_headers(token), verify=False, timeout=30)

    # Refresh and retry once when token expires
    if _is_auth_failure(resp):
        print("Token expired. Re-authentication required...", file=sys.stderr)
        [REDACTED_SECRET])
        resp = _requests.post(url, params=params, json=json_body, headers=_build_headers(token), verify=False, timeout=30)

    resp.raise_for_status()
    return resp.json()


path: common/config.py
content: """
config.py - Configuration Management Module

Reads DBDoctor configuration from environment variables:
  - DBDOCTOR_URL: API base URL (always required)
  - DBDOCTOR_USER: Login username (password mode)
  - DBDOCTOR_PASSWORD: Login password (password mode)
  - DBDOCTOR_EMAIL: Login email (email verification code mode)

Login mode detection:
  - If DBDOCTOR_EMAIL is set → email mode (verification code login)
  - Otherwise → password mode (username/password login)

Priority: System / platform-injected environment variables > .env file (legacy fallback)
"""

import os
import base64
from pathlib import Path

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# Login mode constants
LOGIN_MODE_PASSWORD = "password"
LOGIN_MODE_EMAIL = "email"

# Required env vars by mode
_REQUIRED_COMMON = ("DBDOCTOR_URL",)
_REQUIRED_PASSWORD_MODE = ("DBDOCTOR_USER", "DBDOCTOR_PASSWORD")

# Storage encryption key (for decrypting legacy ENC: prefixed values in .env)
_STORAGE_KEY = b"dbdoctor-tools!!dbdoctor-tools!!"  # 32 bytes for AES-256

# Prefix to identify encrypted values
_ENC_PREFIX = "ENC:"


def _decrypt_from_storage(value: str) -> str:
    """Decrypt an ENC: prefixed value from legacy .env file. Returns plaintext as-is."""
    if not value.startswith(_ENC_PREFIX):
        return value
    encrypted = base64.b64decode(value[len(_ENC_PREFIX):])
    cipher = AES.new(_STORAGE_KEY, AES.MODE_ECB)
    decrypted = unpad(cipher.decrypt(encrypted), AES.block_size)
    return decrypted.decode("utf-8")


class ConfigError(Exception):
    """Configuration error exception"""
    pass


def _try_load_dotenv():
    """Try to load .env file (legacy fallback)"""
    try:
        from dotenv import load_dotenv
        env_path = Path(__file__).parent.parent / '.env'
        if env_path.exists():
            load_dotenv(dotenv_path=env_path)
            return True
    except ImportError:
        pass
    return False


def _detect_login_mode() -> str:
    """Detect login mode based on environment variables.
    If DBDOCTOR_EMAIL is set (non-empty), use email mode; otherwise password mode.
    """
    email = os.environ.get("DBDOCTOR_EMAIL", "").strip()
    if email:
        return LOGIN_MODE_EMAIL
    return LOGIN_MODE_PASSWORD


def _check_env_vars_for_mode(mode: str):
    """Check if required environment variables are set for the given mode.
    Returns (complete: bool, missing: list[str]).
    """
    required = list(_REQUIRED_COMMON)
    if mode == LOGIN_MODE_PASSWORD:
        required.extend(_REQUIRED_PASSWORD_MODE)
    # Email mode only needs DBDOCTOR_URL (+ DBDOCTOR_EMAIL which is already verified)
    missing = [k for k in required if not os.environ.get(k)]
    return len(missing) == 0, missing


class Config:
    """DBDoctor Configuration Management"""

    def __init__(self):
        # Try to load legacy .env file first (may populate env vars)
        _try_load_dotenv()

        # Detect login mode
        self.login_mode = _detect_login_mode()

        # Validate required env vars for the detected mode
        env_complete, missing = _check_env_vars_for_mode(self.login_mode)

        if not env_complete:
            raise ConfigError(
                f"Missing required environment variables: {', '.join(missing)}\n\n"
                f"Please configure using one of the following methods:\n\n"
                f"Mode 1 - Password login (one-click launch version):\n"
                f"   clawdbot skills config dbdoctor-tools DBDOCTOR_URL \"http://[host]:[port]\"\n"
                f"   clawdbot skills config dbdoctor-tools DBDOCTOR_USER \"[username]\"\n"
                f"   clawdbot skills config dbdoctor-tools DBDOCTOR_PASSWORD \"[password]\"\n\n"
                f"Mode 2 - Email verification code login (free version):\n"
                f"   clawdbot skills config dbdoctor-tools DBDOCTOR_URL \"http://[host]:[port]\"\n"
                f"   clawdbot skills config dbdoctor-tools DBDOCTOR_EMAIL \"[email]\""
            )

        self.base_url = os.environ["DBDOCTOR_URL"]
        # Role hardcoded as dev
        self.role = "dev"

        if self.login_mode == LOGIN_MODE_PASSWORD:
            self.username = os.environ["DBDOCTOR_USER"]
            # Support legacy ENC: prefixed passwords from old .env files
            self.[REDACTED_SECRET]"DBDOCTOR_PASSWORD"])
            self.email = None
            # Username is used as UserId
            self.user_id = self.username
        else:
            self.username = None
            self.password = None
            self.email = os.environ["DBDOCTOR_EMAIL"].strip()
            self.user_id = self.email


# Global configuration singleton
config = Config()


path: common/__init__.py
content: # common - DBDoctor common module


path: reference/audit_and_inspection_rules.md
content: # SQL Audit Rules and Inspection Rules Description

## SQL Audit Rules Classification

### MySQL Engine Main Rules

**Performance**:
- Poor current index
- Full table scan exists
- SQL row scan limit

**Standard**:
- SELECT * not allowed
- Force SQL to use WHERE condition

**Security**:
- UPDATE primary key field prohibited
- Table deletion prohibited

### Oracle Engine Main Rules

**Performance**:
- Execution plan analysis (index skip scan, full table scan, cartesian product)

**Standard**:
- Table naming format restrictions
- Index naming conventions
- Field naming conventions

**Security**:
- Modify column type prohibited
- Delete field prohibited
- Partition table usage prohibited

## Rule Priority Description

| Priority | Description | Example |
|--------|------|------|
| **ERROR** | Critical issues, must fix | Full table scan, poor indexing, foreign keys prohibited |
| **WARNING** | Warning issues, recommended to fix | Avoid using SELECT *, limit on number of indexed fields |
| **DANGER** | Dangerous operations, use with caution | Prohibit dropping tables, prohibit TRUNCATE |

## Inspection Rules Classification

### Performance Inspection

- CPU/Memory/Disk IO usage
- QPS/TPS/Connections
- Buffer pool hit rate

### Resource Inspection

- Tablespace usage
- Disk free space
- Long transaction detection

### Configuration Inspection

- Database parameter check
- Archive configuration check
- Backup status check

### Status Inspection

- Database running status
- Master-slave synchronization status
- Deadlock detection

## Common Tools

### SQL Audit Rules Query

```bash
# Get all MySQL audit rules
python scripts/get_sql_audit_rules.py --engine mysql

# Get Oracle ERROR level rules
python scripts/get_sql_audit_rules.py --engine oracle --priority ERROR
```

### Inspection Related Tools

```bash
# Execute instance inspection
python scripts/do_inspect_instance.py --instance-id [instance_id] --tenant [tenant] --project [project]

# Get inspection items list
python scripts/get_inspect_item.py

# Get recent inspection report
python scripts/get_recent_inspect_report.py --instance-id [instance_id] --start-time [start] --end-time [end] --tenant [tenant] --project [project]
```


path: scripts/__init__.py
content: # tools - DBDoctor tools module


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: clean | SkillSpector: suspicious (CRITICAL)