Initial Arka plugin registry: Official plugins mirror + Arka-signed index
11 plugins from github.com/librefang/librefang-registry plugins/. index.json / index.json.sig are signed with Arka's Ed25519 key (not upstream stats.librefang.ai). Private key is not in this repo.
This commit is contained in:
commit
8dec8f6038
62 files changed
+5189
No files matched your search
@@ -0,0 +1,14 @@
|
||||
# keyword-memory hooks
|
||||
|
||||
Python hook scripts for the keyword-memory plugin. Each script reads a JSON request from stdin and writes a JSON response to stdout.
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Hook | Description |
|
||||
|--------|------|-------------|
|
||||
| `ingest.py` | ingest | Receives `{"message": "..."}`, extracts keywords and named entities, returns them as memory fragments |
|
||||
|
||||
## Protocol
|
||||
|
||||
- **Input**: JSON object on stdin (fields vary by hook type)
|
||||
- **Output**: JSON object on stdout (`ingest_result` with memories)
|
||||
@@ -0,0 +1,148 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Keyword memory ingest hook.
|
||||
|
||||
Extracts keywords and named entities from user messages and returns
|
||||
them as contextual memories so agents have topic awareness.
|
||||
|
||||
Receives via stdin:
|
||||
{"type": "ingest", "agent_id": "...", "message": "user message text"}
|
||||
|
||||
Prints to stdout:
|
||||
{"type": "ingest_result", "memories": [{"content": "..."}]}
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
|
||||
# Compact English stopword set (~50 common words)
|
||||
STOPWORDS = frozenset({
|
||||
"a", "an", "the", "and", "or", "but", "in", "on", "at", "to", "for",
|
||||
"of", "with", "by", "from", "is", "are", "was", "were", "be", "been",
|
||||
"being", "have", "has", "had", "do", "does", "did", "will", "would",
|
||||
"could", "should", "may", "might", "shall", "can", "need", "must",
|
||||
"it", "its", "i", "me", "my", "you", "your", "he", "she", "we",
|
||||
"they", "them", "their", "this", "that", "these", "those", "what",
|
||||
"which", "who", "how", "when", "where", "why", "if", "then", "so",
|
||||
"not", "no", "just", "also", "very", "too", "about", "up", "out",
|
||||
"all", "some", "any", "each", "every", "into", "over", "after",
|
||||
})
|
||||
|
||||
# Minimum word length for plain keyword extraction
|
||||
MIN_WORD_LEN = 3
|
||||
|
||||
# Maximum keywords to return
|
||||
MAX_KEYWORDS = 10
|
||||
|
||||
# Pattern: email addresses
|
||||
RE_EMAIL = re.compile(r"[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}")
|
||||
|
||||
# Pattern: URLs (http/https/ftp) — excludes trailing punctuation
|
||||
RE_URL = re.compile(r"https?://[^\s,)>]+(?<=[a-zA-Z0-9/])|ftp://[^\s,)>]+(?<=[a-zA-Z0-9/])")
|
||||
|
||||
# Pattern: numbers with units (e.g. 500ms, 10GB, 3.5GHz, 200k)
|
||||
RE_NUMBER_UNIT = re.compile(r"\b\d+(?:\.\d+)?(?:ms|s|min|hr|h|kb|mb|gb|tb|ghz|mhz|hz|k|m|px|em|rem|%)\b", re.IGNORECASE)
|
||||
|
||||
# Pattern: dates (YYYY-MM-DD, MM/DD/YYYY, DD.MM.YYYY)
|
||||
RE_DATE = re.compile(
|
||||
r"\b\d{4}-\d{2}-\d{2}\b"
|
||||
r"|\b\d{1,2}/\d{1,2}/\d{2,4}\b"
|
||||
r"|\b\d{1,2}\.\d{1,2}\.\d{2,4}\b"
|
||||
)
|
||||
|
||||
# Pattern: camelCase or PascalCase identifiers
|
||||
RE_CAMEL = re.compile(r"\b[a-z]+(?:[A-Z][a-z0-9]+)+\b|\b(?:[A-Z][a-z0-9]+){2,}\b")
|
||||
|
||||
# Pattern: snake_case identifiers (at least one underscore)
|
||||
RE_SNAKE = re.compile(r"\b[a-zA-Z][a-zA-Z0-9]*(?:_[a-zA-Z0-9]+)+\b")
|
||||
|
||||
# Pattern: dotted technical terms (e.g. api.endpoint, os.path)
|
||||
RE_DOTTED = re.compile(r"\b[a-zA-Z][a-zA-Z0-9]*(?:\.[a-zA-Z][a-zA-Z0-9]*)+\b")
|
||||
|
||||
|
||||
def extract_patterns(text):
|
||||
"""Extract structured patterns: emails, URLs, numbers+units, dates, tech terms."""
|
||||
found = []
|
||||
for pattern in (RE_EMAIL, RE_URL, RE_NUMBER_UNIT, RE_DATE, RE_CAMEL, RE_SNAKE, RE_DOTTED):
|
||||
found.extend(pattern.findall(text))
|
||||
return found
|
||||
|
||||
|
||||
def extract_capitalized_phrases(text):
|
||||
"""Detect consecutive capitalized words (likely proper nouns / named entities).
|
||||
|
||||
Skips single capitalized words at sentence boundaries by requiring
|
||||
either multi-word phrases or mid-sentence capitalized words.
|
||||
"""
|
||||
phrases = []
|
||||
# Find sequences of 2+ capitalized words
|
||||
for match in re.finditer(r"\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)+)\b", text):
|
||||
phrases.append(match.group(0))
|
||||
|
||||
# Find single capitalized words that are NOT at sentence start
|
||||
# (preceded by a lowercase letter, comma, or mid-sentence punctuation)
|
||||
for match in re.finditer(r"(?<=[a-z,;]\s)([A-Z][a-zA-Z0-9]+)\b", text):
|
||||
word = match.group(1)
|
||||
if word.lower() not in STOPWORDS and len(word) >= MIN_WORD_LEN:
|
||||
phrases.append(word)
|
||||
|
||||
return phrases
|
||||
|
||||
|
||||
def extract_plain_keywords(text):
|
||||
"""Split text into words and filter out stopwords and short tokens."""
|
||||
# Remove URLs and emails first so they don't pollute word splitting
|
||||
cleaned = RE_URL.sub(" ", text)
|
||||
cleaned = RE_EMAIL.sub(" ", cleaned)
|
||||
# Split on non-alphanumeric (keep hyphens inside words)
|
||||
words = re.findall(r"[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9]|[a-zA-Z]", cleaned)
|
||||
keywords = []
|
||||
for w in words:
|
||||
lower = w.lower()
|
||||
if lower not in STOPWORDS and len(lower) >= MIN_WORD_LEN:
|
||||
keywords.append(lower)
|
||||
return keywords
|
||||
|
||||
|
||||
def deduplicate_keywords(items):
|
||||
"""Deduplicate while preserving insertion order. Case-insensitive for plain words."""
|
||||
seen = set()
|
||||
result = []
|
||||
for item in items:
|
||||
key = item.lower()
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
result.append(item)
|
||||
return result
|
||||
|
||||
|
||||
def main():
|
||||
request = json.loads(sys.stdin.read())
|
||||
message = request.get("message", "")
|
||||
|
||||
if not message.strip():
|
||||
print(json.dumps({"type": "ingest_result", "memories": []}))
|
||||
return
|
||||
|
||||
# Collect keywords from all extraction methods (patterns first for priority)
|
||||
all_keywords = []
|
||||
all_keywords.extend(extract_patterns(message))
|
||||
all_keywords.extend(extract_capitalized_phrases(message))
|
||||
all_keywords.extend(extract_plain_keywords(message))
|
||||
|
||||
# Deduplicate and cap at MAX_KEYWORDS
|
||||
keywords = deduplicate_keywords(all_keywords)[:MAX_KEYWORDS]
|
||||
|
||||
if not keywords:
|
||||
print(json.dumps({"type": "ingest_result", "memories": []}))
|
||||
return
|
||||
|
||||
topic_str = ", ".join(keywords)
|
||||
memories = [
|
||||
{"content": f"[keyword-memory] Key topics: {topic_str}"}
|
||||
]
|
||||
|
||||
print(json.dumps({"type": "ingest_result", "memories": memories}))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user