skillware 0.3.3__tar.gz → 0.3.5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {skillware-0.3.3/skillware.egg-info → skillware-0.3.5}/PKG-INFO +2 -2
- {skillware-0.3.3 → skillware-0.3.5}/README.md +1 -1
- {skillware-0.3.3 → skillware-0.3.5}/pyproject.toml +5 -1
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/pii_masker/skill.py +16 -15
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/tos_evaluator/skill.py +34 -13
- {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/synthetic_generator/skill.py +17 -30
- skillware-0.3.5/skills/defi/evm_tx_handler/abis.py +136 -0
- skillware-0.3.5/skills/defi/evm_tx_handler/card.json +20 -0
- skillware-0.3.5/skills/defi/evm_tx_handler/config.yaml.example +11 -0
- skillware-0.3.5/skills/defi/evm_tx_handler/data/addressbook.yaml +4 -0
- skillware-0.3.5/skills/defi/evm_tx_handler/data/chains.yaml +18 -0
- skillware-0.3.5/skills/defi/evm_tx_handler/data/tokens.yaml +25 -0
- skillware-0.3.5/skills/defi/evm_tx_handler/instructions.md +81 -0
- skillware-0.3.5/skills/defi/evm_tx_handler/manifest.yaml +63 -0
- skillware-0.3.5/skills/defi/evm_tx_handler/skill.py +1114 -0
- skillware-0.3.5/skills/defi/evm_tx_handler/test_skill.py +542 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/workflow.py +16 -7
- {skillware-0.3.3 → skillware-0.3.5}/skills/optimization/prompt_rewriter/skill.py +26 -9
- skillware-0.3.5/skillware/__init__.py +0 -0
- skillware-0.3.5/skillware/core/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skillware/core/loader.py +5 -1
- {skillware-0.3.3 → skillware-0.3.5/skillware.egg-info}/PKG-INFO +2 -2
- {skillware-0.3.3 → skillware-0.3.5}/skillware.egg-info/SOURCES.txt +12 -0
- {skillware-0.3.3 → skillware-0.3.5}/tests/test_loader.py +27 -18
- {skillware-0.3.3 → skillware-0.3.5}/tests/test_skill_issuer.py +18 -14
- {skillware-0.3.3 → skillware-0.3.5}/LICENSE +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/MANIFEST.in +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/setup.cfg +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/mica_module/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/mica_module/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/mica_module/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/mica_module/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/mica_module/mica_corpus.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/mica_module/skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/pii_masker/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/pii_masker/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/pii_masker/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/pii_masker/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/tos_evaluator/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/tos_evaluator/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/tos_evaluator/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/tos_evaluator/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/tos_evaluator/test_skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/novelty_extractor/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/novelty_extractor/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/novelty_extractor/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/novelty_extractor/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/novelty_extractor/skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/novelty_extractor/test_skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/synthetic_generator/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/synthetic_generator/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/synthetic_generator/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/synthetic_generator/manifest.yaml +0 -0
- {skillware-0.3.3/skills/finance → skillware-0.3.5/skills/defi}/__init__.py +0 -0
- {skillware-0.3.3/skills/office → skillware-0.3.5/skills/defi/evm_tx_handler}/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/test_skill.py +0 -0
- {skillware-0.3.3/skills/office/pdf_form_filler → skillware-0.3.5/skills/finance}/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/data/entities.ftm.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/data/malicious_scs_2025.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/data/normalized_fbi_lazarus_20250722_180019.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/data/normalized_israel_nbctf_20250722_180019.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/data/normalized_uniswap_trm.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/maintenance/normalization_tool.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/maintenance/normalize_uniswap_trm.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/skill.py +0 -0
- {skillware-0.3.3/skills/optimization → skillware-0.3.5/skills/office}/__init__.py +0 -0
- {skillware-0.3.3/skillware → skillware-0.3.5/skills/office/pdf_form_filler}/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/office/pdf_form_filler/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/office/pdf_form_filler/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/office/pdf_form_filler/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/office/pdf_form_filler/skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/office/pdf_form_filler/utils.py +0 -0
- {skillware-0.3.3/skillware/core → skillware-0.3.5/skills/optimization}/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/optimization/prompt_rewriter/__init__.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/optimization/prompt_rewriter/card.json +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/optimization/prompt_rewriter/instructions.md +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skills/optimization/prompt_rewriter/manifest.yaml +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skillware/cli.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skillware/core/base_skill.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skillware/core/env.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skillware/version_policy.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skillware.egg-info/dependency_links.txt +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skillware.egg-info/entry_points.txt +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skillware.egg-info/requires.txt +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/skillware.egg-info/top_level.txt +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/tests/test_cli.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/tests/test_mica_module.py +0 -0
- {skillware-0.3.3 → skillware-0.3.5}/tests/test_version_policy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skillware
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
4
4
|
Summary: A framework for modular, self-contained AI skills.
|
|
5
5
|
Author-email: ARPA Hellenic Logical Systems <skillware-os@arpacorp.net>
|
|
6
6
|
License: MIT License
|
|
@@ -255,7 +255,7 @@ Also read the [Agent Code of Conduct](CODE_OF_CONDUCT.md). Open PRs with the [pu
|
|
|
255
255
|
|
|
256
256
|
## Comparison
|
|
257
257
|
|
|
258
|
-
Skillware differs from the Model Context Protocol (MCP), and
|
|
258
|
+
Skillware differs from the Model Context Protocol (MCP), and Agent Skills (SKILL.md) in several ways:
|
|
259
259
|
|
|
260
260
|
* **Model Agnostic**: Native adapters for Gemini, Claude, Ollama, and OpenAI.
|
|
261
261
|
* **Code-First**: Skills are executable Python packages, not just server specs.
|
|
@@ -192,7 +192,7 @@ Also read the [Agent Code of Conduct](CODE_OF_CONDUCT.md). Open PRs with the [pu
|
|
|
192
192
|
|
|
193
193
|
## Comparison
|
|
194
194
|
|
|
195
|
-
Skillware differs from the Model Context Protocol (MCP), and
|
|
195
|
+
Skillware differs from the Model Context Protocol (MCP), and Agent Skills (SKILL.md) in several ways:
|
|
196
196
|
|
|
197
197
|
* **Model Agnostic**: Native adapters for Gemini, Claude, Ollama, and OpenAI.
|
|
198
198
|
* **Code-First**: Skills are executable Python packages, not just server specs.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "skillware"
|
|
7
|
-
version = "0.3.
|
|
7
|
+
version = "0.3.5"
|
|
8
8
|
description = "A framework for modular, self-contained AI skills."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -69,3 +69,7 @@ include = ["skillware*", "skills*"]
|
|
|
69
69
|
# and this wildcard. No per-skill entries needed when adding registry skills.
|
|
70
70
|
[tool.setuptools.package-data]
|
|
71
71
|
skills = ["**/*"]
|
|
72
|
+
|
|
73
|
+
[tool.black]
|
|
74
|
+
line-length = 88
|
|
75
|
+
target-version = ["py310"]
|
|
@@ -11,10 +11,7 @@ class PIIMaskerSkill(BaseSkill):
|
|
|
11
11
|
|
|
12
12
|
@property
|
|
13
13
|
def manifest(self) -> Dict[str, Any]:
|
|
14
|
-
return {
|
|
15
|
-
"name": "compliance/pii_masker",
|
|
16
|
-
"version": "0.1.0"
|
|
17
|
-
}
|
|
14
|
+
return {"name": "compliance/pii_masker", "version": "0.1.0"}
|
|
18
15
|
|
|
19
16
|
def execute(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
20
17
|
text = params.get("text", "")
|
|
@@ -25,7 +22,7 @@ class PIIMaskerSkill(BaseSkill):
|
|
|
25
22
|
sanitized_text = self._apply_mode(sanitized_text, mode)
|
|
26
23
|
|
|
27
24
|
# Build unique entity types list
|
|
28
|
-
entities = list(set([re.sub(r
|
|
25
|
+
entities = list(set([re.sub(r"_[0-9]+$", "", e) for e in detected_entities]))
|
|
29
26
|
|
|
30
27
|
return {
|
|
31
28
|
"sanitized_text": sanitized_text,
|
|
@@ -33,17 +30,21 @@ class PIIMaskerSkill(BaseSkill):
|
|
|
33
30
|
"detected_entities": entities,
|
|
34
31
|
"entity_count": len(detected_entities),
|
|
35
32
|
"security_level": "local-only",
|
|
36
|
-
"model": "arpacorp/micro-f1-mask"
|
|
37
|
-
}
|
|
33
|
+
"model": "arpacorp/micro-f1-mask",
|
|
34
|
+
},
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
def _call_ollama(self, text: str, endpoint: str) -> Tuple[str, List[str]]:
|
|
41
38
|
try:
|
|
42
|
-
response = requests.post(
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
39
|
+
response = requests.post(
|
|
40
|
+
f"{endpoint}/api/generate",
|
|
41
|
+
json={
|
|
42
|
+
"model": "arpacorp/micro-f1-mask",
|
|
43
|
+
"prompt": text,
|
|
44
|
+
"stream": False,
|
|
45
|
+
},
|
|
46
|
+
timeout=30,
|
|
47
|
+
)
|
|
47
48
|
if response.status_code == 200:
|
|
48
49
|
result_text = response.json().get("response", text)
|
|
49
50
|
else:
|
|
@@ -56,7 +57,7 @@ class PIIMaskerSkill(BaseSkill):
|
|
|
56
57
|
result_text = text
|
|
57
58
|
|
|
58
59
|
# Detect entities in the response
|
|
59
|
-
detected = re.findall(r
|
|
60
|
+
detected = re.findall(r"\[([A-Z_]+(?:_[0-9]+)?)\]", result_text)
|
|
60
61
|
return result_text, detected
|
|
61
62
|
|
|
62
63
|
def _apply_mode(self, text: str, mode: str) -> str:
|
|
@@ -64,14 +65,14 @@ class PIIMaskerSkill(BaseSkill):
|
|
|
64
65
|
return text
|
|
65
66
|
|
|
66
67
|
# Pattern to catch [DOCUMENT], [PERSON_1], etc.
|
|
67
|
-
pattern = r
|
|
68
|
+
pattern = r"\[[A-Z_]+(?:_[0-9]+)?\]"
|
|
68
69
|
if mode == "redact":
|
|
69
70
|
return re.sub(pattern, "XXXX", text)
|
|
70
71
|
elif mode == "remove":
|
|
71
72
|
# Replace token and any immediate preceding/following spaces safely
|
|
72
73
|
# A simple sub is sufficient. Cleaning up double spaces.
|
|
73
74
|
text = re.sub(pattern, "", text)
|
|
74
|
-
text = re.sub(r
|
|
75
|
+
text = re.sub(r"\s+", " ", text).strip()
|
|
75
76
|
return text
|
|
76
77
|
|
|
77
78
|
return text
|
|
@@ -213,7 +213,9 @@ class TOSEvaluatorSkill(BaseSkill):
|
|
|
213
213
|
try:
|
|
214
214
|
response = self.session.get(robots_url, timeout=10)
|
|
215
215
|
if response.status_code >= 400:
|
|
216
|
-
assessment["reason"] =
|
|
216
|
+
assessment["reason"] = (
|
|
217
|
+
f"robots.txt returned HTTP {response.status_code}."
|
|
218
|
+
)
|
|
217
219
|
return assessment
|
|
218
220
|
|
|
219
221
|
parser = RobotFileParser()
|
|
@@ -266,13 +268,20 @@ class TOSEvaluatorSkill(BaseSkill):
|
|
|
266
268
|
else:
|
|
267
269
|
candidates[item["url"]] = item
|
|
268
270
|
|
|
269
|
-
ordered = sorted(
|
|
271
|
+
ordered = sorted(
|
|
272
|
+
candidates.values(), key=lambda item: item["score"], reverse=True
|
|
273
|
+
)
|
|
270
274
|
return {"candidates": ordered[:max_terms_pages]}
|
|
271
275
|
|
|
272
|
-
def _extract_candidate_links(
|
|
276
|
+
def _extract_candidate_links(
|
|
277
|
+
self, page_url: str, origin: str
|
|
278
|
+
) -> List[Dict[str, Any]]:
|
|
273
279
|
links: List[Dict[str, Any]] = []
|
|
274
280
|
response = self._safe_get(page_url, timeout=10)
|
|
275
|
-
if
|
|
281
|
+
if (
|
|
282
|
+
not response
|
|
283
|
+
or "html" not in response.headers.get("Content-Type", "").lower()
|
|
284
|
+
):
|
|
276
285
|
return links
|
|
277
286
|
|
|
278
287
|
soup = BeautifulSoup(response.text[:300000], "html.parser")
|
|
@@ -449,7 +458,10 @@ class TOSEvaluatorSkill(BaseSkill):
|
|
|
449
458
|
return False
|
|
450
459
|
if robots_assessment.get("can_fetch") is False:
|
|
451
460
|
return False
|
|
452
|
-
return bool(
|
|
461
|
+
return bool(
|
|
462
|
+
tos_assessment.get("matched_clauses")
|
|
463
|
+
or tos_assessment["status"] == "caution"
|
|
464
|
+
)
|
|
453
465
|
|
|
454
466
|
def _run_llm_evaluator(
|
|
455
467
|
self, normalized: Dict[str, Any], tos_assessment: Dict[str, Any]
|
|
@@ -512,26 +524,33 @@ class TOSEvaluatorSkill(BaseSkill):
|
|
|
512
524
|
verdict = "INSUFFICIENT_EVIDENCE"
|
|
513
525
|
confidence_score = 0.35
|
|
514
526
|
reason = "Insufficient policy evidence to safely approve the requested action."
|
|
515
|
-
recommended_next_step =
|
|
527
|
+
recommended_next_step = (
|
|
528
|
+
"Review the discovered policy pages manually before proceeding."
|
|
529
|
+
)
|
|
516
530
|
|
|
517
531
|
if robots_assessment.get("can_fetch") is False:
|
|
518
532
|
verdict = "UNSAFE"
|
|
519
533
|
confidence_score = 0.98
|
|
520
534
|
reason = robots_assessment["reason"]
|
|
521
|
-
recommended_next_step =
|
|
522
|
-
"Do not automate access to this path unless you have explicit permission."
|
|
523
|
-
)
|
|
535
|
+
recommended_next_step = "Do not automate access to this path unless you have explicit permission."
|
|
524
536
|
elif tos_assessment["status"] == "blocked":
|
|
525
537
|
verdict = "UNSAFE"
|
|
526
538
|
confidence_score = 0.9
|
|
527
539
|
reason = tos_assessment["summary"]
|
|
528
|
-
recommended_next_step =
|
|
540
|
+
recommended_next_step = (
|
|
541
|
+
"Avoid the requested action or obtain explicit written permission."
|
|
542
|
+
)
|
|
529
543
|
elif tos_assessment["status"] == "caution":
|
|
530
544
|
verdict = "CAUTION"
|
|
531
545
|
confidence_score = 0.65
|
|
532
546
|
reason = tos_assessment["summary"]
|
|
533
|
-
recommended_next_step =
|
|
534
|
-
|
|
547
|
+
recommended_next_step = (
|
|
548
|
+
"Prefer an official API or documented integration path if one exists."
|
|
549
|
+
)
|
|
550
|
+
elif (
|
|
551
|
+
tos_assessment["status"] == "allowed"
|
|
552
|
+
and robots_assessment.get("can_fetch") is not False
|
|
553
|
+
):
|
|
535
554
|
verdict = "SAFE"
|
|
536
555
|
confidence_score = 0.72
|
|
537
556
|
reason = tos_assessment["summary"]
|
|
@@ -575,7 +594,9 @@ class TOSEvaluatorSkill(BaseSkill):
|
|
|
575
594
|
"tos_assessment": tos_assessment,
|
|
576
595
|
"llm_assessment": llm_assessment or {"status": "not_used"},
|
|
577
596
|
"discovered_policy_urls": {
|
|
578
|
-
"candidates": [
|
|
597
|
+
"candidates": [
|
|
598
|
+
item["url"] for item in policy_candidates.get("candidates", [])
|
|
599
|
+
]
|
|
579
600
|
},
|
|
580
601
|
"evidence": evidence,
|
|
581
602
|
}
|
|
@@ -18,6 +18,7 @@ class SyntheticGeneratorSkill(BaseSkill):
|
|
|
18
18
|
manifest_path = os.path.join(os.path.dirname(__file__), "manifest.yaml")
|
|
19
19
|
if os.path.exists(manifest_path):
|
|
20
20
|
import yaml
|
|
21
|
+
|
|
21
22
|
with open(manifest_path, "r", encoding="utf-8") as f:
|
|
22
23
|
return yaml.safe_load(f)
|
|
23
24
|
return {}
|
|
@@ -35,16 +36,11 @@ class SyntheticGeneratorSkill(BaseSkill):
|
|
|
35
36
|
# Scaled for readability
|
|
36
37
|
return round(min(ratio * 1.5, 1.0), 3)
|
|
37
38
|
|
|
38
|
-
def _call_gemini(
|
|
39
|
-
self, prompt: str, temperature: float, model_name: str
|
|
40
|
-
) -> str:
|
|
39
|
+
def _call_gemini(self, prompt: str, temperature: float, model_name: str) -> str:
|
|
41
40
|
import google.genai as genai
|
|
42
41
|
from google.genai import types
|
|
43
42
|
|
|
44
|
-
api_key = (
|
|
45
|
-
self.config.get("GOOGLE_API_KEY")
|
|
46
|
-
or os.environ.get("GOOGLE_API_KEY")
|
|
47
|
-
)
|
|
43
|
+
api_key = self.config.get("GOOGLE_API_KEY") or os.environ.get("GOOGLE_API_KEY")
|
|
48
44
|
client = genai.Client(api_key=api_key)
|
|
49
45
|
response = client.models.generate_content(
|
|
50
46
|
model=model_name,
|
|
@@ -53,31 +49,28 @@ class SyntheticGeneratorSkill(BaseSkill):
|
|
|
53
49
|
)
|
|
54
50
|
return response.text
|
|
55
51
|
|
|
56
|
-
def _call_anthropic(
|
|
57
|
-
self, prompt: str, temperature: float, model_name: str
|
|
58
|
-
) -> str:
|
|
52
|
+
def _call_anthropic(self, prompt: str, temperature: float, model_name: str) -> str:
|
|
59
53
|
import anthropic
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
54
|
+
|
|
55
|
+
api_key = self.config.get("ANTHROPIC_API_KEY") or os.environ.get(
|
|
56
|
+
"ANTHROPIC_API_KEY"
|
|
63
57
|
)
|
|
64
58
|
client = anthropic.Anthropic(api_key=api_key)
|
|
65
59
|
message = client.messages.create(
|
|
66
60
|
model=model_name,
|
|
67
61
|
max_tokens=4096,
|
|
68
62
|
temperature=temperature,
|
|
69
|
-
messages=[{"role": "user", "content": prompt}]
|
|
63
|
+
messages=[{"role": "user", "content": prompt}],
|
|
70
64
|
)
|
|
71
65
|
return message.content[0].text
|
|
72
66
|
|
|
73
|
-
def _call_ollama(
|
|
74
|
-
self, prompt: str, temperature: float, model_name: str
|
|
75
|
-
) -> str:
|
|
67
|
+
def _call_ollama(self, prompt: str, temperature: float, model_name: str) -> str:
|
|
76
68
|
import ollama
|
|
69
|
+
|
|
77
70
|
response = ollama.chat(
|
|
78
71
|
model=model_name,
|
|
79
72
|
messages=[{"role": "user", "content": prompt}],
|
|
80
|
-
options={"temperature": temperature}
|
|
73
|
+
options={"temperature": temperature},
|
|
81
74
|
)
|
|
82
75
|
return response.get("message", {}).get("content", "")
|
|
83
76
|
|
|
@@ -107,21 +100,15 @@ class SyntheticGeneratorSkill(BaseSkill):
|
|
|
107
100
|
|
|
108
101
|
try:
|
|
109
102
|
if provider == "gemini":
|
|
110
|
-
raw_text = self._call_gemini(
|
|
111
|
-
system_prompt, temperature, model_name
|
|
112
|
-
)
|
|
103
|
+
raw_text = self._call_gemini(system_prompt, temperature, model_name)
|
|
113
104
|
elif provider == "anthropic":
|
|
114
|
-
raw_text = self._call_anthropic(
|
|
115
|
-
system_prompt, temperature, model_name
|
|
116
|
-
)
|
|
105
|
+
raw_text = self._call_anthropic(system_prompt, temperature, model_name)
|
|
117
106
|
else:
|
|
118
|
-
raw_text = self._call_ollama(
|
|
119
|
-
system_prompt, temperature, model_name
|
|
120
|
-
)
|
|
107
|
+
raw_text = self._call_ollama(system_prompt, temperature, model_name)
|
|
121
108
|
except Exception as e:
|
|
122
109
|
return {
|
|
123
110
|
"status": "error",
|
|
124
|
-
"message": f"LLM Call Failed via {provider}: {str(e)}"
|
|
111
|
+
"message": f"LLM Call Failed via {provider}: {str(e)}",
|
|
125
112
|
}
|
|
126
113
|
|
|
127
114
|
samples = []
|
|
@@ -141,7 +128,7 @@ class SyntheticGeneratorSkill(BaseSkill):
|
|
|
141
128
|
return {
|
|
142
129
|
"status": "error",
|
|
143
130
|
"message": f"Parsing failed: {e}",
|
|
144
|
-
"raw_output": raw_text
|
|
131
|
+
"raw_output": raw_text,
|
|
145
132
|
}
|
|
146
133
|
|
|
147
134
|
all_text = " ".join([str(s) for s in samples])
|
|
@@ -152,5 +139,5 @@ class SyntheticGeneratorSkill(BaseSkill):
|
|
|
152
139
|
"entropy_score": score,
|
|
153
140
|
"status": "success",
|
|
154
141
|
"provider_used": provider,
|
|
155
|
-
"samples_generated": len(samples)
|
|
142
|
+
"samples_generated": len(samples),
|
|
156
143
|
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Minimal contract ABIs for Uniswap V2 router and ERC20."""
|
|
2
|
+
|
|
3
|
+
ROUTER_V2_ABI = [
|
|
4
|
+
{
|
|
5
|
+
"name": "getAmountsOut",
|
|
6
|
+
"type": "function",
|
|
7
|
+
"stateMutability": "view",
|
|
8
|
+
"inputs": [
|
|
9
|
+
{"name": "amountIn", "type": "uint256"},
|
|
10
|
+
{"name": "path", "type": "address[]"},
|
|
11
|
+
],
|
|
12
|
+
"outputs": [{"name": "amounts", "type": "uint256[]"}],
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "getAmountsIn",
|
|
16
|
+
"type": "function",
|
|
17
|
+
"stateMutability": "view",
|
|
18
|
+
"inputs": [
|
|
19
|
+
{"name": "amountOut", "type": "uint256"},
|
|
20
|
+
{"name": "path", "type": "address[]"},
|
|
21
|
+
],
|
|
22
|
+
"outputs": [{"name": "amounts", "type": "uint256[]"}],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"name": "swapExactTokensForTokens",
|
|
26
|
+
"type": "function",
|
|
27
|
+
"stateMutability": "nonpayable",
|
|
28
|
+
"inputs": [
|
|
29
|
+
{"name": "amountIn", "type": "uint256"},
|
|
30
|
+
{"name": "amountOutMin", "type": "uint256"},
|
|
31
|
+
{"name": "path", "type": "address[]"},
|
|
32
|
+
{"name": "to", "type": "address"},
|
|
33
|
+
{"name": "deadline", "type": "uint256"},
|
|
34
|
+
],
|
|
35
|
+
"outputs": [{"name": "amounts", "type": "uint256[]"}],
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"name": "swapTokensForExactTokens",
|
|
39
|
+
"type": "function",
|
|
40
|
+
"stateMutability": "nonpayable",
|
|
41
|
+
"inputs": [
|
|
42
|
+
{"name": "amountOut", "type": "uint256"},
|
|
43
|
+
{"name": "amountInMax", "type": "uint256"},
|
|
44
|
+
{"name": "path", "type": "address[]"},
|
|
45
|
+
{"name": "to", "type": "address"},
|
|
46
|
+
{"name": "deadline", "type": "uint256"},
|
|
47
|
+
],
|
|
48
|
+
"outputs": [{"name": "amounts", "type": "uint256[]"}],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"name": "swapExactETHForTokens",
|
|
52
|
+
"type": "function",
|
|
53
|
+
"stateMutability": "payable",
|
|
54
|
+
"inputs": [
|
|
55
|
+
{"name": "amountOutMin", "type": "uint256"},
|
|
56
|
+
{"name": "path", "type": "address[]"},
|
|
57
|
+
{"name": "to", "type": "address"},
|
|
58
|
+
{"name": "deadline", "type": "uint256"},
|
|
59
|
+
],
|
|
60
|
+
"outputs": [{"name": "amounts", "type": "uint256[]"}],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "swapETHForExactTokens",
|
|
64
|
+
"type": "function",
|
|
65
|
+
"stateMutability": "payable",
|
|
66
|
+
"inputs": [
|
|
67
|
+
{"name": "amountOut", "type": "uint256"},
|
|
68
|
+
{"name": "path", "type": "address[]"},
|
|
69
|
+
{"name": "to", "type": "address"},
|
|
70
|
+
{"name": "deadline", "type": "uint256"},
|
|
71
|
+
],
|
|
72
|
+
"outputs": [{"name": "amounts", "type": "uint256[]"}],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "swapExactTokensForETH",
|
|
76
|
+
"type": "function",
|
|
77
|
+
"stateMutability": "nonpayable",
|
|
78
|
+
"inputs": [
|
|
79
|
+
{"name": "amountIn", "type": "uint256"},
|
|
80
|
+
{"name": "amountOutMin", "type": "uint256"},
|
|
81
|
+
{"name": "path", "type": "address[]"},
|
|
82
|
+
{"name": "to", "type": "address"},
|
|
83
|
+
{"name": "deadline", "type": "uint256"},
|
|
84
|
+
],
|
|
85
|
+
"outputs": [{"name": "amounts", "type": "uint256[]"}],
|
|
86
|
+
},
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
ERC20_ABI = [
|
|
90
|
+
{
|
|
91
|
+
"name": "balanceOf",
|
|
92
|
+
"type": "function",
|
|
93
|
+
"stateMutability": "view",
|
|
94
|
+
"inputs": [{"name": "account", "type": "address"}],
|
|
95
|
+
"outputs": [{"name": "", "type": "uint256"}],
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"name": "decimals",
|
|
99
|
+
"type": "function",
|
|
100
|
+
"stateMutability": "view",
|
|
101
|
+
"inputs": [],
|
|
102
|
+
"outputs": [{"name": "", "type": "uint8"}],
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"name": "allowance",
|
|
106
|
+
"type": "function",
|
|
107
|
+
"stateMutability": "view",
|
|
108
|
+
"inputs": [
|
|
109
|
+
{"name": "owner", "type": "address"},
|
|
110
|
+
{"name": "spender", "type": "address"},
|
|
111
|
+
],
|
|
112
|
+
"outputs": [{"name": "", "type": "uint256"}],
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"name": "approve",
|
|
116
|
+
"type": "function",
|
|
117
|
+
"stateMutability": "nonpayable",
|
|
118
|
+
"inputs": [
|
|
119
|
+
{"name": "spender", "type": "address"},
|
|
120
|
+
{"name": "amount", "type": "uint256"},
|
|
121
|
+
],
|
|
122
|
+
"outputs": [{"name": "", "type": "bool"}],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"name": "transfer",
|
|
126
|
+
"type": "function",
|
|
127
|
+
"stateMutability": "nonpayable",
|
|
128
|
+
"inputs": [
|
|
129
|
+
{"name": "to", "type": "address"},
|
|
130
|
+
{"name": "amount", "type": "uint256"},
|
|
131
|
+
],
|
|
132
|
+
"outputs": [{"name": "", "type": "bool"}],
|
|
133
|
+
},
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
MAX_UINT256 = 2**256 - 1
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "EVM Tx Handler",
|
|
3
|
+
"description": "Quote and transfer from a dedicated agent wallet on Ethereum and Base.",
|
|
4
|
+
"issuer": {
|
|
5
|
+
"name": "Hendobox",
|
|
6
|
+
"email": "henryonyebuchi53@gmail.com",
|
|
7
|
+
"github": "Hendobox",
|
|
8
|
+
"org": "ARPAHLS"
|
|
9
|
+
},
|
|
10
|
+
"icon": "wallet",
|
|
11
|
+
"color": "emerald",
|
|
12
|
+
"ui_schema": {
|
|
13
|
+
"type": "card",
|
|
14
|
+
"fields": [
|
|
15
|
+
{"key": "preview.you_pay", "label": "You pay"},
|
|
16
|
+
{"key": "preview.you_receive", "label": "You receive"},
|
|
17
|
+
{"key": "tx_hash", "label": "Transaction", "type": "link"}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Long-term preferences (copy to config.yaml beside this file). No secrets.
|
|
2
|
+
default_chain: ethereum
|
|
3
|
+
default_spend_asset: usdc
|
|
4
|
+
gas_policy: normal
|
|
5
|
+
confirm_before_send: true
|
|
6
|
+
slippage_bps: 50
|
|
7
|
+
max_trade_usd: 500
|
|
8
|
+
private_key_env: AGENT_WALLET_PRIVATE_KEY
|
|
9
|
+
# Optional guardrails (omit to allow all configured chains/tokens)
|
|
10
|
+
# allowed_chains: [ethereum, base]
|
|
11
|
+
# allowed_tokens: [usdc, weth, eth]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Verified Uniswap V2 Router02 deployments (re-check before mainnet use).
|
|
2
|
+
ethereum:
|
|
3
|
+
chain_id: 1
|
|
4
|
+
rpc_env: ETHEREUM_RPC_URL
|
|
5
|
+
native_symbol: eth
|
|
6
|
+
native_decimals: 18
|
|
7
|
+
explorer_tx_url: "https://etherscan.io/tx/{tx_hash}"
|
|
8
|
+
router_v2: "0x7a250d5630B4cF539739dF2C5dA4bF9C15291ECE"
|
|
9
|
+
weth: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
10
|
+
|
|
11
|
+
base:
|
|
12
|
+
chain_id: 8453
|
|
13
|
+
rpc_env: BASE_RPC_URL
|
|
14
|
+
native_symbol: eth
|
|
15
|
+
native_decimals: 18
|
|
16
|
+
explorer_tx_url: "https://basescan.org/tx/{tx_hash}"
|
|
17
|
+
router_v2: "0x4752ba5DBC23f44d87826276bf6fd6b1c372aD24"
|
|
18
|
+
weth: "0x4200000000000000000000000000000000000006"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Friendly symbol (lowercase) -> contract per chain. Extend via PR; do not edit skill.py.
|
|
2
|
+
ethereum:
|
|
3
|
+
eth:
|
|
4
|
+
native: true
|
|
5
|
+
decimals: 18
|
|
6
|
+
weth:
|
|
7
|
+
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
|
|
8
|
+
decimals: 18
|
|
9
|
+
usdc:
|
|
10
|
+
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
|
|
11
|
+
decimals: 6
|
|
12
|
+
|
|
13
|
+
base:
|
|
14
|
+
eth:
|
|
15
|
+
native: true
|
|
16
|
+
decimals: 18
|
|
17
|
+
weth:
|
|
18
|
+
address: "0x4200000000000000000000000000000000000006"
|
|
19
|
+
decimals: 18
|
|
20
|
+
usdc:
|
|
21
|
+
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
22
|
+
decimals: 6
|
|
23
|
+
degen:
|
|
24
|
+
address: "0x4ed4e862860bed51a9570b96d89af5e1b0efefed"
|
|
25
|
+
decimals: 18
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# EVM Transaction Handler
|
|
2
|
+
|
|
3
|
+
You are equipped with **`evm_tx_handler`**: a deterministic EVM tool for a **dedicated agent wallet** on Ethereum and Base.
|
|
4
|
+
|
|
5
|
+
## Your job vs the skill's job
|
|
6
|
+
|
|
7
|
+
| You (agent) | Skill |
|
|
8
|
+
|-------------|--------|
|
|
9
|
+
| Parse natural language into partial `intent` JSON | Merge intent with `config.yaml` and YAML registries |
|
|
10
|
+
| Ask for missing fields in plain language | Return `missing_fields` and `suggested_defaults` |
|
|
11
|
+
| Show `preview` to the user and obtain approval | Build on-chain quotes; sign swaps and transfers when confirmed |
|
|
12
|
+
| Pass `confirmed: true` after approval | Approve (if needed), swap, or transfer; return tx hash + receipt |
|
|
13
|
+
|
|
14
|
+
**Do not** expect the skill to parse free text. **Do not** pass private keys in tool arguments.
|
|
15
|
+
|
|
16
|
+
## Quote vs execute (important)
|
|
17
|
+
|
|
18
|
+
- **`quote` / `preview`** use live router math at call time.
|
|
19
|
+
- **`execute` re-quotes on-chain at broadcast time** using a fresh quote built from the same intent.
|
|
20
|
+
- Preview amounts, rates, and USD hints **may drift** between preview and execute.
|
|
21
|
+
- **Always call `quote` or `preview` immediately before** asking the user to confirm and before `execute`.
|
|
22
|
+
|
|
23
|
+
## ERC20 swaps: approve then swap
|
|
24
|
+
|
|
25
|
+
When the spend asset is an ERC20, the router may need allowance:
|
|
26
|
+
|
|
27
|
+
1. Optional **`needs_confirmation`** when `confirm_before_send` is true (human-in-the-loop).
|
|
28
|
+
2. On **`execute`**, the skill may broadcast an **`approve`** transaction first, wait for confirmation, then broadcast the **swap**.
|
|
29
|
+
3. If the response includes `approve_tx_hash`, tell the user this was a **two-step** flow (approve, then swap).
|
|
30
|
+
|
|
31
|
+
Native ETH spends skip ERC20 approve but still consume ETH for gas.
|
|
32
|
+
|
|
33
|
+
## Actions
|
|
34
|
+
|
|
35
|
+
| Action | Use when |
|
|
36
|
+
|--------|----------|
|
|
37
|
+
| `resolve` | Intent is incomplete — check `missing_fields` |
|
|
38
|
+
| `quote` | Buy/sell intent is complete — get amounts, path, optional USD |
|
|
39
|
+
| `preview` | Same as quote but preview-focused response |
|
|
40
|
+
| `execute` | User confirmed — broadcast Uni V2 swap (fresh on-chain quote) |
|
|
41
|
+
| `transfer` | Send native ETH or ERC20 to `0x…` or addressbook label |
|
|
42
|
+
| `balances` | List wallet balances on a chain |
|
|
43
|
+
| `wallet_info` | Agent address, chains, preferences (no secrets) |
|
|
44
|
+
| `update_preferences` | User explicitly asks to change defaults in `config.yaml` |
|
|
45
|
+
|
|
46
|
+
## Typical buy flow
|
|
47
|
+
|
|
48
|
+
1. User: “Buy 10 Degen on Base with USDC.”
|
|
49
|
+
2. `resolve` with `{ "side": "buy", "chain": "base", "target_asset": "degen", "amount": 10, "amount_kind": "target_out" }`.
|
|
50
|
+
3. If `spend_asset` missing, ask user; suggest `suggested_defaults.spend_asset`.
|
|
51
|
+
4. `quote` then show `preview` (you pay / you receive, rate, gas, warnings, optional `usd`).
|
|
52
|
+
5. After explicit user approval, `execute` with the same intent and **`confirmed: true`** (run quote/preview again if more than a few seconds passed).
|
|
53
|
+
|
|
54
|
+
## Transfer flow
|
|
55
|
+
|
|
56
|
+
1. Build intent: `chain`, `target_asset`, `amount`, `recipient` (label or address).
|
|
57
|
+
2. `transfer` without `confirmed` first if `confirm_before_send` — skill returns `needs_confirmation`.
|
|
58
|
+
3. After user approves, `transfer` with `confirmed: true`.
|
|
59
|
+
|
|
60
|
+
## Pre-flight balances
|
|
61
|
+
|
|
62
|
+
Before swap or transfer broadcast, the skill checks wallet balance (and native ETH for gas on ERC20 operations). On failure you get `status: insufficient_balance` with `agent_hint` — surface this clearly and do not retry execute until funded.
|
|
63
|
+
|
|
64
|
+
## Wallet key missing
|
|
65
|
+
|
|
66
|
+
If you see `status: missing_config`, the dedicated agent wallet key is not in `.env`. Follow `setup` in the JSON (env var name, docs links). Never ask the user to paste a private key into chat or tool args.
|
|
67
|
+
|
|
68
|
+
## Intent fields
|
|
69
|
+
|
|
70
|
+
- `side`: `buy` | `sell` (transfers use action `transfer`)
|
|
71
|
+
- `chain`: `ethereum` | `base` (default from config)
|
|
72
|
+
- `target_asset` / `spend_asset`: symbols from `data/tokens.yaml`
|
|
73
|
+
- `amount` + `amount_kind`: `target_out` for “buy 10 DEGEN”; `spend_in` for “sell 100 DEGEN”
|
|
74
|
+
- `recipient`: addressbook label or `0x` address
|
|
75
|
+
- `gas_policy`: `low` | `normal` | `high` | `aggressive` (per-tx only; does not persist)
|
|
76
|
+
|
|
77
|
+
## Safety
|
|
78
|
+
|
|
79
|
+
- Always preview before `execute` or `transfer`.
|
|
80
|
+
- `max_trade_usd` in config blocks quotes/executes when USD price is unavailable (fail closed).
|
|
81
|
+
- Recommend `finance/wallet_screening` for unknown recipient addresses before large sends.
|