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.
Files changed (101) hide show
  1. {skillware-0.3.3/skillware.egg-info → skillware-0.3.5}/PKG-INFO +2 -2
  2. {skillware-0.3.3 → skillware-0.3.5}/README.md +1 -1
  3. {skillware-0.3.3 → skillware-0.3.5}/pyproject.toml +5 -1
  4. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/pii_masker/skill.py +16 -15
  5. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/tos_evaluator/skill.py +34 -13
  6. {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/synthetic_generator/skill.py +17 -30
  7. skillware-0.3.5/skills/defi/evm_tx_handler/abis.py +136 -0
  8. skillware-0.3.5/skills/defi/evm_tx_handler/card.json +20 -0
  9. skillware-0.3.5/skills/defi/evm_tx_handler/config.yaml.example +11 -0
  10. skillware-0.3.5/skills/defi/evm_tx_handler/data/addressbook.yaml +4 -0
  11. skillware-0.3.5/skills/defi/evm_tx_handler/data/chains.yaml +18 -0
  12. skillware-0.3.5/skills/defi/evm_tx_handler/data/tokens.yaml +25 -0
  13. skillware-0.3.5/skills/defi/evm_tx_handler/instructions.md +81 -0
  14. skillware-0.3.5/skills/defi/evm_tx_handler/manifest.yaml +63 -0
  15. skillware-0.3.5/skills/defi/evm_tx_handler/skill.py +1114 -0
  16. skillware-0.3.5/skills/defi/evm_tx_handler/test_skill.py +542 -0
  17. {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/workflow.py +16 -7
  18. {skillware-0.3.3 → skillware-0.3.5}/skills/optimization/prompt_rewriter/skill.py +26 -9
  19. skillware-0.3.5/skillware/__init__.py +0 -0
  20. skillware-0.3.5/skillware/core/__init__.py +0 -0
  21. {skillware-0.3.3 → skillware-0.3.5}/skillware/core/loader.py +5 -1
  22. {skillware-0.3.3 → skillware-0.3.5/skillware.egg-info}/PKG-INFO +2 -2
  23. {skillware-0.3.3 → skillware-0.3.5}/skillware.egg-info/SOURCES.txt +12 -0
  24. {skillware-0.3.3 → skillware-0.3.5}/tests/test_loader.py +27 -18
  25. {skillware-0.3.3 → skillware-0.3.5}/tests/test_skill_issuer.py +18 -14
  26. {skillware-0.3.3 → skillware-0.3.5}/LICENSE +0 -0
  27. {skillware-0.3.3 → skillware-0.3.5}/MANIFEST.in +0 -0
  28. {skillware-0.3.3 → skillware-0.3.5}/setup.cfg +0 -0
  29. {skillware-0.3.3 → skillware-0.3.5}/skills/__init__.py +0 -0
  30. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/__init__.py +0 -0
  31. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/mica_module/__init__.py +0 -0
  32. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/mica_module/card.json +0 -0
  33. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/mica_module/instructions.md +0 -0
  34. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/mica_module/manifest.yaml +0 -0
  35. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/mica_module/mica_corpus.json +0 -0
  36. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/mica_module/skill.py +0 -0
  37. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/pii_masker/__init__.py +0 -0
  38. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/pii_masker/card.json +0 -0
  39. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/pii_masker/instructions.md +0 -0
  40. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/pii_masker/manifest.yaml +0 -0
  41. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/tos_evaluator/__init__.py +0 -0
  42. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/tos_evaluator/card.json +0 -0
  43. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/tos_evaluator/instructions.md +0 -0
  44. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/tos_evaluator/manifest.yaml +0 -0
  45. {skillware-0.3.3 → skillware-0.3.5}/skills/compliance/tos_evaluator/test_skill.py +0 -0
  46. {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/__init__.py +0 -0
  47. {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/novelty_extractor/__init__.py +0 -0
  48. {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/novelty_extractor/card.json +0 -0
  49. {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/novelty_extractor/instructions.md +0 -0
  50. {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/novelty_extractor/manifest.yaml +0 -0
  51. {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/novelty_extractor/skill.py +0 -0
  52. {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/novelty_extractor/test_skill.py +0 -0
  53. {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/synthetic_generator/__init__.py +0 -0
  54. {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/synthetic_generator/card.json +0 -0
  55. {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/synthetic_generator/instructions.md +0 -0
  56. {skillware-0.3.3 → skillware-0.3.5}/skills/data_engineering/synthetic_generator/manifest.yaml +0 -0
  57. {skillware-0.3.3/skills/finance → skillware-0.3.5/skills/defi}/__init__.py +0 -0
  58. {skillware-0.3.3/skills/office → skillware-0.3.5/skills/defi/evm_tx_handler}/__init__.py +0 -0
  59. {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/__init__.py +0 -0
  60. {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/__init__.py +0 -0
  61. {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/card.json +0 -0
  62. {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/instructions.md +0 -0
  63. {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/manifest.yaml +0 -0
  64. {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/skill.py +0 -0
  65. {skillware-0.3.3 → skillware-0.3.5}/skills/dev_tools/issue_resolver/test_skill.py +0 -0
  66. {skillware-0.3.3/skills/office/pdf_form_filler → skillware-0.3.5/skills/finance}/__init__.py +0 -0
  67. {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/__init__.py +0 -0
  68. {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/card.json +0 -0
  69. {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/data/entities.ftm.json +0 -0
  70. {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/data/malicious_scs_2025.json +0 -0
  71. {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/data/normalized_fbi_lazarus_20250722_180019.json +0 -0
  72. {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/data/normalized_israel_nbctf_20250722_180019.json +0 -0
  73. {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/data/normalized_uniswap_trm.json +0 -0
  74. {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/instructions.md +0 -0
  75. {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/maintenance/normalization_tool.py +0 -0
  76. {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/maintenance/normalize_uniswap_trm.py +0 -0
  77. {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/manifest.yaml +0 -0
  78. {skillware-0.3.3 → skillware-0.3.5}/skills/finance/wallet_screening/skill.py +0 -0
  79. {skillware-0.3.3/skills/optimization → skillware-0.3.5/skills/office}/__init__.py +0 -0
  80. {skillware-0.3.3/skillware → skillware-0.3.5/skills/office/pdf_form_filler}/__init__.py +0 -0
  81. {skillware-0.3.3 → skillware-0.3.5}/skills/office/pdf_form_filler/card.json +0 -0
  82. {skillware-0.3.3 → skillware-0.3.5}/skills/office/pdf_form_filler/instructions.md +0 -0
  83. {skillware-0.3.3 → skillware-0.3.5}/skills/office/pdf_form_filler/manifest.yaml +0 -0
  84. {skillware-0.3.3 → skillware-0.3.5}/skills/office/pdf_form_filler/skill.py +0 -0
  85. {skillware-0.3.3 → skillware-0.3.5}/skills/office/pdf_form_filler/utils.py +0 -0
  86. {skillware-0.3.3/skillware/core → skillware-0.3.5/skills/optimization}/__init__.py +0 -0
  87. {skillware-0.3.3 → skillware-0.3.5}/skills/optimization/prompt_rewriter/__init__.py +0 -0
  88. {skillware-0.3.3 → skillware-0.3.5}/skills/optimization/prompt_rewriter/card.json +0 -0
  89. {skillware-0.3.3 → skillware-0.3.5}/skills/optimization/prompt_rewriter/instructions.md +0 -0
  90. {skillware-0.3.3 → skillware-0.3.5}/skills/optimization/prompt_rewriter/manifest.yaml +0 -0
  91. {skillware-0.3.3 → skillware-0.3.5}/skillware/cli.py +0 -0
  92. {skillware-0.3.3 → skillware-0.3.5}/skillware/core/base_skill.py +0 -0
  93. {skillware-0.3.3 → skillware-0.3.5}/skillware/core/env.py +0 -0
  94. {skillware-0.3.3 → skillware-0.3.5}/skillware/version_policy.py +0 -0
  95. {skillware-0.3.3 → skillware-0.3.5}/skillware.egg-info/dependency_links.txt +0 -0
  96. {skillware-0.3.3 → skillware-0.3.5}/skillware.egg-info/entry_points.txt +0 -0
  97. {skillware-0.3.3 → skillware-0.3.5}/skillware.egg-info/requires.txt +0 -0
  98. {skillware-0.3.3 → skillware-0.3.5}/skillware.egg-info/top_level.txt +0 -0
  99. {skillware-0.3.3 → skillware-0.3.5}/tests/test_cli.py +0 -0
  100. {skillware-0.3.3 → skillware-0.3.5}/tests/test_mica_module.py +0 -0
  101. {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
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 Anthropic's Skills repository in several ways:
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 Anthropic's Skills repository in several ways:
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.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'_[0-9]+$', '', e) for e in detected_entities]))
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(f"{endpoint}/api/generate", json={
43
- "model": "arpacorp/micro-f1-mask",
44
- "prompt": text,
45
- "stream": False
46
- }, timeout=30)
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'\[([A-Z_]+(?:_[0-9]+)?)\]', result_text)
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'\[[A-Z_]+(?:_[0-9]+)?\]'
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'\s+', ' ', text).strip()
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"] = f"robots.txt returned HTTP {response.status_code}."
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(candidates.values(), key=lambda item: item["score"], reverse=True)
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(self, page_url: str, origin: str) -> List[Dict[str, Any]]:
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 not response or "html" not in response.headers.get("Content-Type", "").lower():
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(tos_assessment.get("matched_clauses") or tos_assessment["status"] == "caution")
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 = "Review the discovered policy pages manually before proceeding."
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 = "Avoid the requested action or obtain explicit written permission."
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 = "Prefer an official API or documented integration path if one exists."
534
- elif tos_assessment["status"] == "allowed" and robots_assessment.get("can_fetch") is not False:
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": [item["url"] for item in policy_candidates.get("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
- api_key = (
61
- self.config.get("ANTHROPIC_API_KEY")
62
- or os.environ.get("ANTHROPIC_API_KEY")
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,4 @@
1
+ # Label (lowercase) -> checksummed address. Replace placeholders before mainnet use.
2
+ # Example only — do not send real funds to these addresses.
3
+ mom: "0x000000000000000000000000000000000000dEaD"
4
+ george: "0x000000000000000000000000000000000000dEaD"
@@ -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.