Skill: ian-at/memori-extension (version 1.0.2)

--- name: memori-extension description: Memory augmentation and LLM call interception using the Memori Python library with optional Zhipu API integration. metadata: { "openclaw": { "emoji": "🧠", "user-invocable": false, "disable-model-invocation": false, "requires": { "pip": ["memori"], "anyBins": ["python3"], "env": { "optional": ["MEMORI_TECH_TERMS", "MEMORI_TECH_TERMS_FILE", "ZHIPUAI_API_KEY", "ZHIPUAI_MODEL", "MEMORI_DB_PATH"] } } } } --- # Memori Extension Skill Memory augmentation and LLM call interception using the Memori Python library with optional Zhipu AI API integration. ## Overview This skill provides memory augmentation and LLM call interception capabilities using the Memori Python library. It enables agents to retrieve relevant knowledge from a memory database and inject it into conversations. ## Security & Privacy Notice ⚠️ **Important**: This skill performs the following operations: ### File Operations (Always Enabled) - **Read/Write**: Local SQLite database (default: `./memori.db`) - **Read/Write**: Optional tech terms configuration file (default: `./config/tech_terms.txt`) - These operations are entirely local and do not transmit data externally. ### External API Calls (Optional - Requires Explicit Configuration) - **Condition**: Only if `ZHIPUAI_API_KEY` environment variable is set - **Data Transmitted**: Conversation text (user messages, system prompts, and assistant responses) is sent to Zhipu AI's servers for analysis and augmentation - **Purpose**: To enhance conversation understanding using Zhipu AI's language models - **Control**: If `ZHIPUAI_API_KEY` is NOT set, the skill operates 100% locally with NO external network calls ### Recommendations 1. **Start Local-Only**: Test the skill without `ZHIPUAI_API_KEY` first to verify local functionality 2. **Explicit Consent**: Only set `ZHIPUAI_API_KEY` if you explicitly consent to sending conversation data to external services 3. **Review Data**: Consider what conversation data you're comfortable transmitting before enabling external API features 4. **Sandbox Testing**: If providing API keys, test in a sandbox environment first to understand the data flow ## Dependencies This skill requires the following **Python packages**: ```bash pip install memori ``` The **memori** library is licensed under **Apache License Version 2.0**. ### Optional Dependencies For Zhipu API augmentation (optional): ```bash pip install zhipuai ``` **Warning**: Installing `zhipuai` and providing `ZHIPUAI_API_KEY` enables conversation content to be sent to external servers. ## Environment Variables ### All Supported Environment Variables | Variable | Description | Required | Default | Privacy Note | |----------|-------------|-----------|---------|---------------| | `ZHIPUAI_API_KEY` | Zhipu AI API key for conversation augmentation | **No** | - | ⚠️ **Enables external API calls** - conversation text will be sent to Zhipu AI servers | | `ZHIPUAI_MODEL` | Zhipu AI model name | **No** | `glm-4.7` | Only used if `ZHIPUAI_API_KEY` is set | | `MEMORI_TECH_TERMS` | Comma-separated technical terms for LLM interception | **No** | - | Local only | | `MEMORI_TECH_TERMS_FILE` | Path to file containing technical terms (one per line) | **No** | `./config/tech_terms.txt` | Local only - read/write | | `MEMORI_DB_PATH` | Path to Memori database | **No** | `./memori.db` | Local only - read/write | **Privacy & Data Flow Notes**: - ⚠️ **External API**: Only `ZHIPUAI_API_KEY` enables external network calls. All other variables control local file operations. - ✅ **Local-Only Mode**: If you omit `ZHIPUAI_API_KEY`, the skill operates 100% locally with no external data transmission. - 📁 **File Operations**: The skill reads/writes the database (`memori.db`) and optionally the tech terms file (`tech_terms.txt`). These are stored on your local filesystem. - 🔒 **Best Practice**: Start without `ZHIPUAI_API_KEY` to test local functionality. Only enable external API if you need enhanced features and consent to the data transmission. ### Configuration Examples **System environment:** ```bash # Optional: Enable Zhipu API augmentation export ZHIPUAI_API_KEY="your-api-key" export ZHIPUAI_MODEL="glm-4.7" # Optional: Customize technical terms export MEMORI_TECH_TERMS="FFI,Rust,Linux,kernel,spinlock" # Optional: Use custom database path export MEMORI_DB_PATH="/path/to/memori.db" ``` **OpenClaw configuration (`openclaw.json`):** ```json { "skills": { "entries": { "memori-extension": { "enabled": true, "env": { "ZHIPUAI_API_KEY": "your-api-key", "ZHIPUAI_MODEL": "glm-4.7", "MEMORI_TECH_TERMS": "FFI,Rust,Linux,kernel", "MEMORI_DB_PATH": "./memori.db" } } } } } ``` **Technical terms file** (optional): ```bash # Create config directory mkdir -p config # Create terms file cat > config/tech_terms.txt << EOF FFI Rust Linux kernel spinlock mutex unsafe EOF # Set environment variable export MEMORI_TECH_TERMS_FILE="config/tech_terms.txt" ``` ## Quick Start ### Method 1: Direct Memori Library (Recommended) ```python from memori import Memori memori = Memori( db_path="memori.db", entity_id="knowledge-base" ) # Search memories memories = memori.search("query", limit=5) # Augment query context = memori.augment("How to handle spinlock conflicts?", limit=3) # Store memory memory_id = memori.store("New content") # Get stats stats = memori.get_stats() # Close memori.close() ``` ### Method 2: Skill Convenience API ```python from skills.memori_extension import search, augment, intercept_llm # Search memories = search("FFI bindings", limit=5) # Augment enhanced = augment("How to handle spinlock conflicts?") if enhanced: print(enhanced) # Intercept LLM messages = [{"role": "user", "content": "FFI question"}] enhanced = intercept_llm(messages) ``` ## API Reference ### Memori Class #### `__init__(db_path, entity_id)` Initialize Memori instance. **Parameters:** - `db_path` (str | Path, optional): Database path - `entity_id` (str, optional): Entity ID, default `"default"` #### `search(query, limit, entity_id)` Search for relevant memories. **Returns:** `List[Memory]` #### `augment(query, limit, entity_id)` Augment query with retrieved memories. **Returns:** `AugmentedContext` #### `store(content, entity_id, metadata)` Store a new memory. **Returns:** `int` - Memory ID #### `get_stats(entity_id)` Get statistics. **Returns:** `dict` #### `close()` Close database connection. ### Memory Class Memory object with attributes: - `id` (int): Memory ID - `entity_id` (str): Entity ID - `content` (str): Memory content - `created_at` (str): Creation timestamp - `metadata` (dict, optional): Metadata ### AugmentedContext Class Augmented context with: - `original_query` (str): Original query - `retrieved_memories` (List[Memory]): Retrieved memories - `enhanced_prompt` (str): Augmented prompt - `has_memories` (bool): Whether memories were retrieved - `memories_count` (int): Number of retrieved memories ## Configuration ### Database Default database path: `./memori.db` The skill will: - ✅ **Read** from the database to retrieve memories - ✅ **Write** to the database when storing new memories - ✅ **Create** the database file if it doesn't exist ### Technical Terms The skill uses configurable technical terms for LLM call interception. You can customize these via: **1. Environment variable** (comma-separated): ```bash export MEMORI_TECH_TERMS="FFI,Rust,Linux,kernel,spinlock,mutex,unsafe" ``` **2. Configuration file** (one term per line): ```bash # Create config file mkdir -p config cat > config/tech_terms.txt << EOF FFI Rust Linux kernel spinlock mutex unsafe EOF # Set environment variable export MEMORI_TECH_TERMS_FILE="config/tech_terms.txt" ``` **Note**: If neither is set, the skill will still work but may not intercept technical queries as effectively. ## Security Considerations ### File Operations This skill performs the following file operations: | Operation | File | Description | |-----------|------|-------------| | Read | `./memori.db` (default) | Retrieve stored memories | | Write | `./memori.db` (default) | Store new memories | | Read | `MEMORI_TECH_TERMS_FILE` | Load technical terms | | Write | `MEMORI_TECH_TERMS_FILE` | Persist terms (if enabled) | ### External API Calls ⚠️ **Important**: If `ZHIPUAI_API_KEY` is set, this skill may send conversation text to Zhipu AI's servers for augmentation. **To disable external API calls**: - Simply don't set `ZHIPUAI_API_KEY` - The skill will work normally using local memory retrieval only ### Recommendations 1. **Review the code** before enabling external API features 2. **Test in a sandbox** first if providing API keys 3. **Use file permissions** to protect database and config files 4. **Only enable features** you explicitly need ## File Structure ``` skills/memori_extension/ ├── __init__.py # Skill entry ├── memori_extension.py # Skill implementation ├── SKILL.md # This file └── README.md # Quick start guide ``` ## License This skill is licensed under **Apache License Version 2.0**. This skill uses the **memori** Python library, which is also licensed under **Apache License Version 2.0**. ## Attribution This skill incorporates the Memori Python library, which is licensed under the **Apache License 2.0**. See the [LICENSE](http://www.apache.org/licenses/LICENSE-2.0) file for the complete license text. Memori Library: - Copyright 2025 Memori Team - License: Apache License 2.0 - Repository: <https://github.com/MemoriLabs/Memori>

path: __init__.py
content: """
Memori + Zhipu API - OpenClaw Skill Integration

Provides simplified API for OpenClaw integration using the Memori Python library.
"""
from .memori_extension import (
    get_memori,
    get_interceptor,
    get_zhipu_client,
    search,
    augment,
    intercept_llm,
    enhance_with_memori,
    intercept_llm_call,
    MemoriWrapper,
    LLMInterceptor,
    ZhipuClient,
    Message
)

# Import core classes from Memori library
from memori import Memori, Memory, AugmentedContext


__all__ = [
    # From Memori library
    "Memori",
    "Memory",
    "AugmentedContext",
    # Skill wrapper classes
    "MemoriWrapper",
    "LLMInterceptor",
    "ZhipuClient",
    "Message",
    # Global instances
    "get_memori",
    "get_interceptor",
    "get_zhipu_client",
    # Convenience API
    "search",
    "augment",
    "intercept_llm",
    # Compatibility API
    "enhance_with_memori",
    "intercept_llm_call"
]


path: README.md
content: # Memori Extension Skill

Memory augmentation and LLM call interception using the Memori Python library with optional Zhipu API integration.

## ⚠️ Security & Privacy Notice

**Important**: This skill performs the following operations:

1. **File Operations**: Reads and writes to a local SQLite database (default: `./memori.db`)
2. **Optional External API**: If `ZHIPUAI_API_KEY` is provided, conversation text may be sent to Zhipu AI's servers for analysis and augmentation
3. **Configurable Terms**: Loads technical terms from environment variables or config files

**Privacy Implications:**
- ⚠️ **If you set `ZHIPUAI_API_KEY`**: Conversation text (user messages, system prompts, and assistant responses) will be sent to Zhipu AI's external servers. Only set this if you explicitly consent to this data transmission.
- ✅ **If you do NOT set `ZHIPUAI_API_KEY`**: The skill operates entirely locally with no external network calls (except for fetching the Memori library itself during installation).
- 📁 **File Operations**: The skill creates and modifies a local SQLite database and optional configuration files. These are stored on your local machine.

**Recommendation**: Start without `ZHIPUAI_API_KEY` to test local-only functionality first. Only enable external API calls if you need the enhanced augmentation features and consent to the data transmission.

## License

This skill is licensed under the **Apache License Version 2.0**.

Copyright 2025

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

## Dependencies

This skill requires the **Memori Python library**:

```bash
pip install memori
```

The Memori library is licensed under the Apache License Version 2.0.

### Optional: Zhipu API

⚠️ **Warning**: For enhanced conversation augmentation, you can optionally install the Zhipu AI SDK:

```bash
pip install zhipuai
```

**Privacy Notice**: If `ZHIPUAI_API_KEY` is set, conversation content may be sent to Zhipu AI servers. Only set this if you explicitly consent.

## Environment Variables

### All Supported Environment Variables

| Variable | Description | Required | Default | Privacy Note |
|----------|-------------|-----------|---------|---------------|
| `ZHIPUAI_API_KEY` | Zhipu AI API key for conversation augmentation | **No** | - | ⚠️ **Enables external API calls** - conversation text will be sent to Zhipu AI servers |
| `ZHIPUAI_MODEL` | Zhipu AI model name | **No** | `glm-4.7` | Only used if `ZHIPUAI_API_KEY` is set |
| `MEMORI_TECH_TERMS` | Comma-separated technical terms for LLM interception | **No** | - | Local only |
| `MEMORI_TECH_TERMS_FILE` | Path to file containing technical terms (one per line) | **No** | `./config/tech_terms.txt` | Local only - read/write |
| `MEMORI_DB_PATH` | Path to Memori database | **No** | `./memori.db` | Local only - read/write |

**Privacy & Data Flow Notes**:
- ⚠️ **External API**: Only `ZHIPUAI_API_KEY` enables external network calls. All other variables control local file operations.
- ✅ **Local-Only Mode**: If you omit `ZHIPUAI_API_KEY`, the skill operates 100% locally with no external data transmission.
- 📁 **File Operations**: The skill reads/writes the database (`memori.db`) and optionally the tech terms file (`tech_terms.txt`). These are stored on your local filesystem.
- 🔒 **Best Practice**: Start without `ZHIPUAI_API_KEY` to test local functionality. Only enable external API if you need enhanced features and consent to the data transmission.

### Configuration Examples

**System environment:**
```bash
# Optional: Enable Zhipu API augmentation
export ZHIPUAI_API_KEY="your-api-key"
export ZHIPUAI_MODEL="glm-4.7"

# Optional: Customize technical terms
export MEMORI_TECH_TERMS="FFI,Rust,Linux,kernel,spinlock"

# Optional: Use custom database path
export MEMORI_DB_PATH="/path/to/memori.db"
```

**OpenClaw configuration (`openclaw.json`):**
```json
{
  "skills": {
    "entries": {
      "memori-extension": {
        "enabled": true,
        "env": {
          "ZHIPUAI_API_KEY": "your-api-key",
          "ZHIPUAI_MODEL": "glm-4.7"
        }
      }
    }
  }
}
```

**Technical terms file** (optional):
```bash
# Create config directory
mkdir -p config

# Create terms file
cat > config/tech_terms.txt << EOF
FFI
Rust
Linux
kernel
spinlock
mutex
unsafe
EOF

# Set environment variable
export MEMORI_TECH_TERMS_FILE="config/tech_terms.txt"
```

## Quick Start

### Installation

```bash
# Install Memori library (required)
pip install memori

# Install Zhipu SDK (optional, only if needed)
pip install zhipuai
```

### Basic Usage

```python
from memori import Memori

memori = Memori(
    db_path="memori.db",
    entity_id="knowledge-base"
)

# Search memories
memories = memori.search("query", limit=5)

# Augment query
context = memori.augment("How to handle spinlock conflicts?", limit=3)

# Store memory
memory_id = memori.store("New content")

# Close
memori.close()
```

### Using the Skill API

```python
from skills.memori_extension import search, augment, intercept_llm

# Search memories
memories = search("query", limit=5)

# Augment query
enhanced = augment("query")
if enhanced:
    print(enhanced)

# Intercept LLM calls
messages = [{"role": "user", "content": "question"}]
enhanced_messages = intercept_llm(messages)
```

## Features

- ✅ **Memory retrieval** - Search knowledge database by keywords
- ✅ **Query augmentation** - Inject retrieved memories into conversation
- ✅ **LLM call interception** - Automatically enhance LLM calls
- ✅ **Configurable terms** - Customize technical keywords for interception
- ✅ **Optional Zhipu API** - Enhanced conversation analysis (requires API key)
- ⚠️ **External API calls** - Only if ZHIPUAI_API_KEY is set

## Security Considerations

### File Operations

This skill performs the following file operations:

| Operation | File | Description |
|-----------|------|-------------|
| Read | `./memori.db` (default) | Retrieve stored memories |
| Write | `./memori.db` (default) | Store new memories |
| Read | `MEMORI_TECH_TERMS_FILE` | Load technical terms |
| Write | `MEMORI_TECH_TERMS_FILE` | Persist terms (if enabled) |

### External API Calls

⚠️ **Important**: If `ZHIPUAI_API_KEY` is set, this skill may send conversation text to Zhipu AI's servers for augmentation.

**To disable external API calls**:
- Simply don't set `ZHIPUAI_API_KEY`
- The skill will work normally using local memory retrieval only

### Recommendations

1. **Review the code** before enabling external API features
2. **Test in a sandbox** first if providing API keys
3. **Use file permissions** to protect database and config files
4. **Only enable features** you explicitly need
5. **Monitor usage** if enabling external API calls

## Attribution

This skill incorporates the Memori Python library, which is licensed under the Apache License Version 2.0.

**Memori Library:**
- Copyright 2025 Memori Team
- License: Apache License 2.0
- Repository: https://github.com/MemoriLabs/Memori

**Full license text:** http://www.apache.org/licenses/LICENSE-2.0


path: memori_extension.py
content: """
Memori + Zhipu API - OpenClaw Skill

Memory augmentation and LLM call interception using the Memori Python library.
"""
import os
from typing import List, Dict, Any, Optional, Set
from dataclasses import dataclass
import re
import pathlib

# Import the real Memori library
from memori import Memori, Memory, AugmentedContext

# Zhipu API config (optional - the skill works without these)
# NOTE: ZHIPUAI_API_KEY is OPTIONAL. If not set, the skill operates
# entirely locally with no external API calls. Only set this if you
# explicitly consent to sending conversation data to Zhipu AI servers.
ZHIPUAI_API_KEY = os.getenv("ZHIPUAI_API_KEY", "")
ZHIPUAI_MODEL = os.getenv("ZHIPUAI_MODEL", "glm-4.7")

# Feature flags
AUGMENTATION_ENABLED = True
RETRIEVAL_ENABLED = True
INTERCEPTION_ENABLED = True

# Try to import Zhipu SDK (optional)
# The zhipuai package is not required for core functionality.
# It's only needed if you want to enable Zhipu AI augmentation.
try:
    from zhipuai import ZhipuAI
    ZHIPUAI_AVAILABLE = True
except ImportError:
    ZHIPUAI_AVAILABLE = False


# ============================================
# Configurable tech terms (not hard-coded)
# ============================================

# Default fallback terms.
# Intentionally left empty so deployers provide their own terms via
# the MEMORI_TECH_TERMS env var or MEMORI_TECH_TERMS_FILE.
# You can set a small non-sensitive fallback here if desired.
DEFAULT_TECH_TERMS: List[str] = []

# Cached loaded terms
_TECH_TERMS_CACHE: Optional[List[str]] = None


def load_tech_terms() -> List[str]:
    """Load tech terms from environment, file, or fall back to defaults.

    Order of sources:
      1. MEMORI_TECH_TERMS environment variable (comma-separated)
      2. MEMORI_TECH_TERMS_FILE (one term per line)
      3. DEFAULT_TECH_TERMS fallback
    Results are cached for the process lifetime.
    """
    global _TECH_TERMS_CACHE
    if _TECH_TERMS_CACHE is not None:
        return _TECH_TERMS_CACHE

    terms: List[str] = []

    # 1) Env var
    env = os.getenv("MEMORI_TECH_TERMS", "")
    if env:
        terms.extend([t.strip() for t in env.split(",") if t.strip()])

    # 2) File
    file_path = os.getenv("MEMORI_TECH_TERMS_FILE", "./config/tech_terms.txt")
    p = pathlib.Path(file_path)
    if p.exists():
        try:
            lines = p.read_text(encoding="utf-8").splitlines()
            terms.extend([ln.strip() for ln in lines if ln.strip() and not ln.strip().startswith("#")])
        except Exception:
            # ignore file read errors and fall back
            pass

    # 3) fallback: only use DEFAULT_TECH_TERMS if it's non-empty.
    # If DEFAULT_TECH_TERMS is empty, we return an empty term list to avoid
    # embedding hard-coded organization-specific keywords in the source.
    if not terms:
        if DEFAULT_TECH_TERMS:
            terms = DEFAULT_TECH_TERMS.copy()
        else:
            terms = []

    # dedupe while preserving order
    seen: Set[str] = set()
    normalized: List[str] = []
    for t in terms:
        if t not in seen:
            seen.add(t)
            normalized.append(t)

    _TECH_TERMS_CACHE = normalized
    return _TECH_TERMS_CACHE


def add_tech_term(term: str, persist: bool = False) -> None:
    """Add a tech term at runtime. If persist=True and a file path is configured,
    append it to the file path to make it persistent across restarts."""
    global _TECH_TERMS_CACHE
    terms = load_tech_terms()
    if term not in terms:
        terms.append(term)
        _TECH_TERMS_CACHE = terms
        if persist:
            file_path = os.getenv("MEMORI_TECH_TERMS_FILE", "./config/tech_terms.txt")
            try:
                p = pathlib.Path(file_path)
                p.parent.mkdir(parents=True, exist_ok=True)
                with p.open("a", encoding="utf-8") as f:
                    f.write(term + "\n")
            except Exception:
                pass


# ============================================
# Data Models
# ============================================

@dataclass
class Message:
    """Message object."""
    role: str
    content: str
    timestamp: str
    metadata: Optional[Dict[str, Any]] = None

    def to_dict(self) -> Dict[str, Any]:
        return {
            "role": self.role,
            "content": self.content,
            "timestamp": self.timestamp,
            "metadata": self.metadata
        }


# ============================================
# Memori Wrapper
# ============================================

class MemoriWrapper:
    """
    Wrapper for Memori library with convenience features.
    """

    def __init__(
        self,
        db_path: str = "./memori.db",
        entity_id: str = "default"
    ):
        """
        Initialize Memori wrapper.

        Args:
            db_path: Database path
            entity_id: Entity ID
        """
        self.memori = Memori(db_path=db_path, entity_id=entity_id)
        self.entity_id = entity_id

    # Delegate to Memori library
    def search(self, query: str, limit: int = 5) -> List[Memory]:
        """Search memories."""
        if not RETRIEVAL_ENABLED:
            return []
        return self.memori.search(query, limit=limit)

    def augment(self, query: str, limit: int = 3) -> AugmentedContext:
        """Augment query."""
        return self.memori.augment(query, limit=limit)

    def store(self, content: str) -> int:
        """Store memory."""
        return self.memori.store(content)

    def get_stats(self) -> Dict[str, Any]:
        """Get statistics."""
        return self.memori.get_stats()

    def close(self):
        """Close connection."""
        self.memori.close()

    def __enter__(self):
        """Context manager support."""
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """Context manager support."""
        self.close()


# ============================================
# Zhipu AI Client (Optional)
# ============================================

class ZhipuClient:
    """
    Zhipu AI client for optional conversation augmentation.

    NOTE: This is an OPTIONAL feature. The skill works perfectly without it.
    Only use this if you:
    1. Have installed the zhipuai package (pip install zhipuai)
    2. Have set ZHIPUAI_API_KEY environment variable
    3. Explicitly consent to sending conversation data to external API servers

    If ZHIPUAI_API_KEY is not set, this client gracefully disables itself
    and the skill operates in local-only mode.
    """

    def __init__(self):
        self.client = None

        # Only initialize if Zhipu SDK is available AND API key is provided
        if ZHIPUAI_AVAILABLE and ZHIPUAI_API_KEY:
            try:
                self.client = ZhipuAI([REDACTED_SECRET])
            except Exception as e:
                # Fail gracefully - the skill will continue to work in local mode
                print(f"Warning: Zhipu client initialization failed: {e}")
                print("The skill will continue to operate in local-only mode.")

    def is_available(self) -> bool:
        """Check if Zhipu API is available and configured."""
        return self.client is not None

    def augment_conversation(
        self,
        messages: List[Message]
    ) -> Optional[Dict[str, Any]]:
        """
        Augment conversation using Zhipu API.

        Args:
            messages: Conversation messages

        Returns:
            Augmentation result if API is available, None otherwise

        NOTE: This sends conversation text to Zhipu AI's external servers.
        Only use if you have explicitly consented to this data transmission.
        """
        if not self.is_available():
            return None

        prompt = """Analyze the following conversation and extract:
1. Entities: Technical terms, concepts
2. Relations: Causal relationships, dependencies
3. Summary: Brief summary (100 words max)

Conversation:
""" + "\n".join([f"{m.role}: {m.content}" for m in messages])

        try:
            response = self.client.chat.completions.create(
                model=ZHIPUAI_MODEL,
                messages=[
                    {"role": "system", "content": "You are a technical analysis assistant."},
                    {"role": "user", "content": prompt}
                ]
            )

            return {
                "entities": self._extract_entities(response.choices[0].message.content),
                "summary": response.choices[0].message.content[:500],
                "timestamp": __import__('datetime').datetime.now().isoformat()
            }

        except Exception as e:
            # Fail gracefully - don't break the skill if API call fails
            print(f"Warning: Zhipu augmentation failed: {e}")
            return None

    def _extract_entities(self, text: str) -> List[str]:
        """Extract entities from text using configurable tech terms.

        Sources for tech terms (in order):
        1. Environment variable MEMORI_TECH_TERMS (comma-separated)
        2. File specified by MEMORI_TECH_TERMS_FILE (one term per line)
        3. Default fallback list
        """
        found: List[str] = []
        terms = load_tech_terms()
        for term in terms:
            # match as whole word where possible, case-insensitive
            pattern = r"\b" + re.escape(term) + r"\b"
            if re.search(pattern, text, flags=re.IGNORECASE):
                found.append(term)
        return found


# ============================================
# LLM Call Interceptor
# ============================================

class LLMInterceptor:
    """
    Intercepts LLM calls and injects memory context.
    """
    # keywords are loaded at runtime from configuration; keep field on instance
    def __init__(self, memori_wrapper: MemoriWrapper):
        self.memori = memori_wrapper
        self.intercept_count = 0
        # load configured keywords (cached)
        self.tech_keywords = load_tech_terms()

    def should_intercept(self, messages: List[Dict[str, str]]) -> bool:
        """Check if should intercept."""
        if not INTERCEPTION_ENABLED:
            return False

        for msg in reversed(messages):
            if msg.get("role") == "user":
                content = msg.get("content", "")
                # perform case-insensitive whole-word-ish matching against configured terms
                for kw in self.tech_keywords:
                    # simple contains check is fast and covers many cases; for word boundaries use regex
                    if re.search(r"\b" + re.escape(kw) + r"\b", content, flags=re.IGNORECASE):
                        return True
                return False

        return False

    def intercept(
        self,
        messages: List[Dict[str, str]]
    ) -> List[Dict[str, str]]:
        """
        Intercept and augment messages.

        Args:
            messages: Original messages

        Returns:
            Augmented messages
        """
        # Extract user query
        user_query = ""
        for msg in reversed(messages):
            if msg.get("role") == "user":
                user_query = msg.get("content", "")
                break

        if not user_query:
            return messages

        # Augment with Memori
        context = self.memori.augment(user_query, limit=3)

        if not context.has_memories:
            return messages

        # Build augmented messages
        enhanced_messages = [
            {
                "role": "system",
                "content": f"""You are a professional technical assistant. Please answer the user's question based on the following professional knowledge:

{context.enhanced_prompt}
"""
            }
        ]

        # Add original messages (except system)
        for msg in messages:
            if msg.get("role") != "system":
                enhanced_messages.append(msg)

        self.intercept_count += 1
        return enhanced_messages


# ============================================
# Global Instances
# ============================================

_memori_wrapper_instance = None
_interceptor_instance = None
_zhipu_client = None


def get_memori() -> MemoriWrapper:
    """Get global Memori instance."""
    global _memori_wrapper_instance
    if _memori_wrapper_instance is None:
        _memori_wrapper_instance = MemoriWrapper()
    return _memori_wrapper_instance


def get_interceptor() -> LLMInterceptor:
    """Get global interceptor instance."""
    global _interceptor_instance
    if _interceptor_instance is None:
        _interceptor_instance = LLMInterceptor(get_memori())
    return _interceptor_instance


def get_zhipu_client() -> ZhipuClient:
    """Get Zhipu client instance."""
    global _zhipu_client
    if _zhipu_client is None:
        _zhipu_client = ZhipuClient()
    return _zhipu_client


# ============================================
# Convenience API
# ============================================

def search(query: str, limit: int = 5) -> List[Memory]:
    """Search memories."""
    return get_memori().search(query, limit=limit)


def augment(query: str, limit: int = 3) -> str:
    """
    Augment query.

    Returns:
        Augmented prompt text if memories found
        None if no memories found
    """
    context = get_memori().augment(query, limit=limit)

    if context.has_memories:
        return context.enhanced_prompt
    else:
        return None


def intercept_llm(messages: List[Dict[str, str]]) -> List[Dict[str, str]]:
    """Intercept LLM call."""
    interceptor = get_interceptor()

    if interceptor.should_intercept(messages):
        return interceptor.intercept(messages)
    else:
        return messages


# ============================================
# Compatibility API
# ============================================

def enhance_with_memori(query: str) -> str:
    """Augment query (compatibility function)."""
    return augment(query)


def intercept_llm_call(messages: List[Dict[str, str]]) -> List[Dict[str, str]]:
    """Intercept LLM call (compatibility function)."""
    return intercept_llm(messages)


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