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:
ixoblakp committed 2026-09-01 10:22:07 +03:00
commit 8dec8f6038
62 files changed
+5189

No files matched your search

+44
View File
@@ -0,0 +1,44 @@
# sentiment-tracker
Analyzes user message sentiment using keyword-based scoring and injects emotional context so agents can respond with appropriate tone. No external ML libraries required (stdlib only).
## Scoring Method
- **Positive words** (~30): great, love, excellent, awesome, helpful, appreciate, etc. (+1 each)
- **Negative words** (~30): bad, terrible, frustrated, broken, bug, error, crash, etc. (-1 each)
- **Intensifiers**: very, extremely, really, absolutely, totally (multiply next sentiment word by 1.5x)
- **Negators**: not, no, never, don't, doesn't, isn't, can't, won't (flip next word's polarity)
The raw score is normalized by message length and clamped to [-1.0, 1.0].
## Classification
| Score Range | Label | Action |
|-------------|-------|--------|
| > 0.3 | positive | Inject positive context memory |
| < -0.3 | negative | Inject frustration-aware memory |
| -0.3 to 0.3 | neutral | No memory injected (avoid context clutter) |
## Hooks
| Hook | Script | Description |
|------|--------|-------------|
| ingest | `hooks/ingest.py` | Analyzes message sentiment and returns emotional context as a memory fragment |
## Example Output
Negative sentiment:
```json
{"type": "ingest_result", "memories": [{"content": "[sentiment] User appears frustrated (score: -0.6). Consider acknowledging the issue."}]}
```
Positive sentiment:
```json
{"type": "ingest_result", "memories": [{"content": "[sentiment] User seems satisfied (score: 0.7). Positive interaction."}]}
```
Neutral sentiment returns an empty memories list.
## Usage
Installed automatically when enabled in agent configuration. No external dependencies required (stdlib only).
+14
View File
@@ -0,0 +1,14 @@
# sentiment-tracker hooks
Python hook scripts for the sentiment-tracker 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": "..."}`, analyzes sentiment, returns emotional context as a memory fragment |
## Protocol
- **Input**: JSON object on stdin (fields vary by hook type)
- **Output**: JSON object on stdout (`ingest_result` with memories, empty for neutral sentiment)
+155
View File
@@ -0,0 +1,155 @@
#!/usr/bin/env python3
"""Sentiment tracker ingest hook.
Analyzes user message sentiment using keyword-based scoring and injects
emotional context so agents can respond with appropriate tone.
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
# Positive sentiment words with base score of +1
POSITIVE_WORDS = frozenset({
"great", "love", "excellent", "happy", "thanks", "awesome", "perfect",
"wonderful", "good", "nice", "amazing", "fantastic", "helpful", "pleased",
"appreciate", "brilliant", "outstanding", "superb", "delightful", "glad",
"impressive", "beautiful", "enjoy", "excited", "grateful", "incredible",
"marvelous", "terrific", "thank", "cool",
})
# Negative sentiment words with base score of -1
NEGATIVE_WORDS = frozenset({
"bad", "terrible", "hate", "angry", "frustrated", "disappointed", "broken",
"wrong", "awful", "horrible", "annoying", "useless", "fail", "worst",
"problem", "issue", "bug", "error", "crash", "slow", "stuck", "confused",
"difficult", "painful", "ugly", "ridiculous", "poor", "sucks", "garbage",
"missing",
})
# Intensifiers multiply the next sentiment word's score by this factor
INTENSIFIERS = frozenset({
"very", "extremely", "really", "absolutely", "totally", "incredibly",
"completely", "utterly", "highly", "so",
})
INTENSIFIER_MULTIPLIER = 1.5
# Negators flip the polarity of the next sentiment word
NEGATORS = frozenset({
"not", "no", "never", "don't", "doesn't", "isn't", "can't", "won't",
"didn't", "wasn't", "weren't", "couldn't", "shouldn't", "wouldn't",
"hardly", "barely", "neither",
})
# Sentiment thresholds
POSITIVE_THRESHOLD = 0.3
NEGATIVE_THRESHOLD = -0.3
def tokenize(text):
"""Split text into lowercase tokens, preserving contractions."""
return re.findall(r"[a-zA-Z][a-zA-Z']*", text.lower())
def compute_sentiment(tokens):
"""Compute sentiment score from -1.0 to 1.0.
Walks through tokens tracking negator and intensifier state,
then applies them to sentiment-bearing words.
"""
if not tokens:
return 0.0
raw_score = 0.0
negate_next = False
intensify_next = False
for token in tokens:
if token in NEGATORS:
negate_next = True
continue
if token in INTENSIFIERS:
intensify_next = True
continue
score = 0.0
if token in POSITIVE_WORDS:
score = 1.0
elif token in NEGATIVE_WORDS:
score = -1.0
if score != 0.0:
if intensify_next:
score *= INTENSIFIER_MULTIPLIER
intensify_next = False
if negate_next:
score *= -1.0
negate_next = False
raw_score += score
else:
# Reset modifiers if the next word is not a sentiment word
# (modifiers only apply to the immediately following sentiment word)
negate_next = False
intensify_next = False
# Normalize to -1.0 .. 1.0 using tanh-like scaling
# This keeps small scores proportional while bounding large ones
word_count = len(tokens)
if word_count == 0:
return 0.0
# Scale by number of tokens to normalize for message length
normalized = raw_score / max(word_count ** 0.5, 1.0)
# Clamp to [-1.0, 1.0]
return max(-1.0, min(1.0, normalized))
def classify(score):
"""Classify sentiment score into a label."""
if score > POSITIVE_THRESHOLD:
return "positive"
elif score < NEGATIVE_THRESHOLD:
return "negative"
else:
return "neutral"
def main():
request = json.loads(sys.stdin.read())
message = request.get("message", "")
if not message.strip():
print(json.dumps({"type": "ingest_result", "memories": []}))
return
tokens = tokenize(message)
score = compute_sentiment(tokens)
label = classify(score)
# Only inject memory for clearly non-neutral sentiment
if label == "neutral":
print(json.dumps({"type": "ingest_result", "memories": []}))
return
score_str = f"{score:.1f}"
if label == "negative":
content = f"[sentiment] User appears frustrated (score: {score_str}). Consider acknowledging the issue."
else:
content = f"[sentiment] User seems satisfied (score: {score_str}). Positive interaction."
memories = [{"content": content}]
print(json.dumps({"type": "ingest_result", "memories": memories}))
if __name__ == "__main__":
main()
+39
View File
@@ -0,0 +1,39 @@
name = "sentiment-tracker"
version = "0.1.0"
description = "Analyzes user message sentiment and injects emotional context so agents can respond with appropriate tone"
author = "librefang"
[hooks]
ingest = "hooks/ingest.py"
[i18n.zh]
name = "情绪追踪"
description = "分析用户消息的情绪,并把情感上下文注入 Agent,让回复更贴合情绪。"
[i18n.zh-TW]
name = "情緒追蹤"
description = "分析使用者訊息的情緒,並將情感上下文注入 Agent,讓回覆更貼合情緒。"
[i18n.ja]
name = "感情トラッカー"
description = "ユーザーメッセージの感情を分析し、感情コンテキストを注入して適切なトーンで応答できるようにする。"
[i18n.ko]
name = "감정 추적기"
description = "사용자 메시지의 감정을 분석하여 Agent 컨텍스트에 감정 정보를 주입하고 적절한 어조로 응답하게 함."
[i18n.de]
name = "Sentiment-Tracker"
description = "Analysiert die Stimmung von Nutzernachrichten und fügt emotionalen Kontext ein, damit Antworten den passenden Ton treffen."
[i18n.es]
name = "Rastreador de sentimiento"
description = "Analiza el sentimiento de los mensajes del usuario e inyecta contexto emocional para que el agente responda con el tono adecuado."
[i18n.fr]
name = "Suivi de sentiment"
description = "Analyse le sentiment des messages utilisateur et injecte un contexte émotionnel pour que l'agent réponde avec le bon ton."
[integrity]
"hooks/ingest.py" = "1ce4a89e6b3de86d236d54d60b96b6fffb8306616666aef4802ba690ca8ae225"