skillware 0.3.1__tar.gz → 0.3.2__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.1/skillware.egg-info → skillware-0.3.2}/PKG-INFO +4 -2
- {skillware-0.3.1 → skillware-0.3.2}/README.md +3 -1
- {skillware-0.3.1 → skillware-0.3.2}/pyproject.toml +1 -1
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/mica_module/manifest.yaml +1 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/pii_masker/manifest.yaml +1 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/tos_evaluator/manifest.yaml +1 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/novelty_extractor/manifest.yaml +1 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/synthetic_generator/manifest.yaml +1 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/dev_tools/issue_resolver/manifest.yaml +1 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/manifest.yaml +1 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/skill.py +90 -44
- {skillware-0.3.1 → skillware-0.3.2}/skills/office/pdf_form_filler/manifest.yaml +1 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/optimization/prompt_rewriter/manifest.yaml +1 -0
- skillware-0.3.2/skillware/cli.py +299 -0
- {skillware-0.3.1 → skillware-0.3.2/skillware.egg-info}/PKG-INFO +4 -2
- {skillware-0.3.1 → skillware-0.3.2}/tests/test_cli.py +82 -1
- skillware-0.3.1/skillware/cli.py +0 -162
- {skillware-0.3.1 → skillware-0.3.2}/LICENSE +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/MANIFEST.in +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/setup.cfg +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/mica_module/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/mica_module/card.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/mica_module/instructions.md +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/mica_module/mica_corpus.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/mica_module/skill.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/pii_masker/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/pii_masker/card.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/pii_masker/instructions.md +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/pii_masker/skill.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/tos_evaluator/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/tos_evaluator/card.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/tos_evaluator/instructions.md +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/tos_evaluator/skill.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/compliance/tos_evaluator/test_skill.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/novelty_extractor/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/novelty_extractor/card.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/novelty_extractor/instructions.md +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/novelty_extractor/skill.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/novelty_extractor/test_skill.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/synthetic_generator/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/synthetic_generator/card.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/synthetic_generator/instructions.md +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/synthetic_generator/skill.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/dev_tools/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/dev_tools/issue_resolver/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/dev_tools/issue_resolver/card.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/dev_tools/issue_resolver/instructions.md +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/dev_tools/issue_resolver/skill.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/dev_tools/issue_resolver/test_skill.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/card.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/data/entities.ftm.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/data/malicious_scs_2025.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/data/normalized_fbi_lazarus_20250722_180019.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/data/normalized_israel_nbctf_20250722_180019.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/data/normalized_uniswap_trm.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/instructions.md +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/maintenance/normalization_tool.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/maintenance/normalize_uniswap_trm.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/office/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/office/pdf_form_filler/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/office/pdf_form_filler/card.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/office/pdf_form_filler/instructions.md +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/office/pdf_form_filler/skill.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/office/pdf_form_filler/utils.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/optimization/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/optimization/prompt_rewriter/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/optimization/prompt_rewriter/card.json +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/optimization/prompt_rewriter/instructions.md +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skills/optimization/prompt_rewriter/skill.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skillware/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skillware/core/__init__.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skillware/core/base_skill.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skillware/core/env.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skillware/core/loader.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skillware.egg-info/SOURCES.txt +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skillware.egg-info/dependency_links.txt +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skillware.egg-info/entry_points.txt +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skillware.egg-info/requires.txt +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/skillware.egg-info/top_level.txt +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/tests/test_loader.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/tests/test_mica_module.py +0 -0
- {skillware-0.3.1 → skillware-0.3.2}/tests/test_skill_issuer.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.2
|
|
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
|
|
@@ -81,6 +81,7 @@ Dynamic: license-file
|
|
|
81
81
|
<a href="#architecture">Architecture</a> •
|
|
82
82
|
<a href="#quick-start">Quick Start</a> •
|
|
83
83
|
<a href="#documentation">Documentation</a> •
|
|
84
|
+
<a href="CHANGELOG.md">Changelog</a> •
|
|
84
85
|
<a href="#contributing">Contributing</a> •
|
|
85
86
|
<a href="#comparison">Comparison</a> •
|
|
86
87
|
<a href="#contact">Contact</a>
|
|
@@ -163,7 +164,7 @@ pip install "skillware[cli]"
|
|
|
163
164
|
skillware list
|
|
164
165
|
```
|
|
165
166
|
|
|
166
|
-
This prints a table of all locally available skills and confirms the install and path resolution are working.
|
|
167
|
+
This prints a table of all locally available skills and confirms the install and path resolution are working. Running `skillware` with no arguments opens the interactive menu.
|
|
167
168
|
|
|
168
169
|
### 3. Configuration
|
|
169
170
|
|
|
@@ -240,6 +241,7 @@ For other providers and shared integration patterns, see the [usage guides index
|
|
|
240
241
|
| Topic | Link |
|
|
241
242
|
| :--- | :--- |
|
|
242
243
|
| Introduction | [docs/introduction.md](docs/introduction.md) |
|
|
244
|
+
| Changelog | [CHANGELOG.md](CHANGELOG.md) |
|
|
243
245
|
| Testing | [docs/TESTING.md](docs/TESTING.md) |
|
|
244
246
|
| Skill library | [docs/skills/README.md](docs/skills/README.md) |
|
|
245
247
|
| Usage guides | [index](docs/usage/README.md) — [Gemini](docs/usage/gemini.md) · [Claude](docs/usage/claude.md) · [OpenAI](docs/usage/openai.md) · [DeepSeek](docs/usage/deepseek.md) · [Ollama](docs/usage/ollama.md); [agent loops](docs/usage/agent_loops.md); [API keys](docs/usage/api_keys.md) |
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
<a href="#architecture">Architecture</a> •
|
|
20
20
|
<a href="#quick-start">Quick Start</a> •
|
|
21
21
|
<a href="#documentation">Documentation</a> •
|
|
22
|
+
<a href="CHANGELOG.md">Changelog</a> •
|
|
22
23
|
<a href="#contributing">Contributing</a> •
|
|
23
24
|
<a href="#comparison">Comparison</a> •
|
|
24
25
|
<a href="#contact">Contact</a>
|
|
@@ -101,7 +102,7 @@ pip install "skillware[cli]"
|
|
|
101
102
|
skillware list
|
|
102
103
|
```
|
|
103
104
|
|
|
104
|
-
This prints a table of all locally available skills and confirms the install and path resolution are working.
|
|
105
|
+
This prints a table of all locally available skills and confirms the install and path resolution are working. Running `skillware` with no arguments opens the interactive menu.
|
|
105
106
|
|
|
106
107
|
### 3. Configuration
|
|
107
108
|
|
|
@@ -178,6 +179,7 @@ For other providers and shared integration patterns, see the [usage guides index
|
|
|
178
179
|
| Topic | Link |
|
|
179
180
|
| :--- | :--- |
|
|
180
181
|
| Introduction | [docs/introduction.md](docs/introduction.md) |
|
|
182
|
+
| Changelog | [CHANGELOG.md](CHANGELOG.md) |
|
|
181
183
|
| Testing | [docs/TESTING.md](docs/TESTING.md) |
|
|
182
184
|
| Skill library | [docs/skills/README.md](docs/skills/README.md) |
|
|
183
185
|
| Usage guides | [index](docs/usage/README.md) — [Gemini](docs/usage/gemini.md) · [Claude](docs/usage/claude.md) · [OpenAI](docs/usage/openai.md) · [DeepSeek](docs/usage/deepseek.md) · [Ollama](docs/usage/ollama.md); [agent loops](docs/usage/agent_loops.md); [API keys](docs/usage/api_keys.md) |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
name: compliance/mica_module
|
|
2
2
|
version: 0.1.0
|
|
3
3
|
description: A highly specialized, localized RAG and policy enforcement engine for the Markets in Crypto-Assets (MiCA) regulation.
|
|
4
|
+
short_description: "Local RAG and policy enforcement engine for MiCA regulation."
|
|
4
5
|
issuer:
|
|
5
6
|
name: Ross Peili
|
|
6
7
|
email: vpeilivanidis@gmail.com
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
name: compliance/pii_masker
|
|
2
2
|
version: 0.1.0
|
|
3
3
|
description: High-precision, local PII detection and redaction using the micro-f1-mask model via Ollama.
|
|
4
|
+
short_description: "Detects and redacts PII locally using the micro-f1-mask model."
|
|
4
5
|
issuer:
|
|
5
6
|
name: Ross Peili
|
|
6
7
|
email: vpeilivanidis@gmail.com
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
name: "compliance/tos_evaluator"
|
|
2
2
|
version: "0.1.0"
|
|
3
3
|
description: "Evaluates whether an intended automated website action appears permissible by checking robots.txt, discovered legal pages, and optional low-cost LLM review for ambiguous policy language."
|
|
4
|
+
short_description: "Evaluates whether an automated web action is permitted by site policy."
|
|
4
5
|
issuer:
|
|
5
6
|
name: Ross Peili
|
|
6
7
|
email: vpeilivanidis@gmail.com
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
name: "data_engineering/novelty_extractor"
|
|
2
2
|
version: "0.1.0"
|
|
3
3
|
description: "Filters a text dataset by semantic novelty, retaining only chunks that carry new information above a configurable threshold."
|
|
4
|
+
short_description: "Filters text datasets by semantic novelty using local embeddings."
|
|
4
5
|
issuer:
|
|
5
6
|
name: Martin Rizzo Lozano
|
|
6
7
|
email: rizzolozanomartin@gmail.com
|
{skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/synthetic_generator/manifest.yaml
RENAMED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
name: "data_engineering/synthetic_generator"
|
|
2
2
|
version: "0.1.0"
|
|
3
3
|
description: "Generates high-entropy structured synthetic data for model fine-tuning to avoid mode collapse."
|
|
4
|
+
short_description: "Generates high-entropy synthetic JSONL data for model fine-tuning."
|
|
4
5
|
issuer:
|
|
5
6
|
name: Ross Peili
|
|
6
7
|
email: vpeilivanidis@gmail.com
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
name: "dev_tools/issue_resolver"
|
|
2
2
|
version: "0.1.0"
|
|
3
3
|
description: "Analyzes any GitHub issue against its target repository and produces a structured resolution plan with ranked implementation options, affected files, and caveats."
|
|
4
|
+
short_description: "Analyzes a GitHub issue and produces a ranked implementation plan."
|
|
4
5
|
issuer:
|
|
5
6
|
name: Ross Peili
|
|
6
7
|
email: vpeilivanidis@gmail.com
|
|
@@ -2,6 +2,7 @@ name: wallet_screening
|
|
|
2
2
|
version: 1.0.0
|
|
3
3
|
description: |
|
|
4
4
|
A comprehensive crypto wallet screening tool. Checks Ethereum addresses against sanctions lists (OFAC, FBI, etc.) and known malicious contracts (Mixers, Scams). analyze transaction history for risk.
|
|
5
|
+
short_description: "Screens Ethereum wallets against OFAC sanctions and mixer lists."
|
|
5
6
|
issuer:
|
|
6
7
|
name: Ross Peili
|
|
7
8
|
email: vpeilivanidis@gmail.com
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
|
+
import re
|
|
3
4
|
import requests
|
|
4
5
|
import glob
|
|
5
6
|
from typing import Any, Dict, List, Optional
|
|
6
7
|
from datetime import datetime
|
|
7
8
|
from skillware.core.base_skill import BaseSkill
|
|
8
9
|
|
|
10
|
+
_ETH_ADDRESS_RE = re.compile(r"^0x[a-fA-F0-9]{40}$")
|
|
11
|
+
_ZERO_WIDTH_CHARS = "\u200b\u200c\u200d\ufeff"
|
|
12
|
+
|
|
9
13
|
|
|
10
14
|
class WalletScreeningSkill(BaseSkill):
|
|
11
15
|
"""
|
|
@@ -31,13 +35,17 @@ class WalletScreeningSkill(BaseSkill):
|
|
|
31
35
|
# Load Additional Datasets dynamically (normalized files, etc.)
|
|
32
36
|
self.additional_datasets = self._load_additional_datasets()
|
|
33
37
|
|
|
38
|
+
# ETH address -> sanctions records (built once; O(1) lookup per screen)
|
|
39
|
+
self._sanctions_index: Dict[str, List[Dict]] = {}
|
|
40
|
+
self._build_sanctions_index()
|
|
41
|
+
|
|
34
42
|
@property
|
|
35
43
|
def manifest(self) -> Dict[str, Any]:
|
|
36
44
|
return {}
|
|
37
45
|
|
|
38
46
|
def execute(self, params: Dict[str, Any]) -> Dict[str, Any]:
|
|
39
|
-
address = params.get("address")
|
|
40
|
-
if not address
|
|
47
|
+
address = self.normalize_eth_address(params.get("address") or "")
|
|
48
|
+
if not address:
|
|
41
49
|
return {"error": "Invalid Ethereum address provided."}
|
|
42
50
|
|
|
43
51
|
if not self.etherscan_api_key:
|
|
@@ -49,9 +57,8 @@ class WalletScreeningSkill(BaseSkill):
|
|
|
49
57
|
eth_usd = self._get_price(self.coingecko_url_usd, "usd")
|
|
50
58
|
eth_eur = self._get_price(self.coingecko_url_eur, "eur")
|
|
51
59
|
|
|
52
|
-
# 2. Sanctions Check (
|
|
53
|
-
sanctions_hits = self.
|
|
54
|
-
sanctions_hits.extend(self._check_against_additional_sanctions(address))
|
|
60
|
+
# 2. Sanctions Check (FTM + additional lists via index)
|
|
61
|
+
sanctions_hits = self._lookup_sanctions_hits(address)
|
|
55
62
|
|
|
56
63
|
# 3. Analyze Transactions
|
|
57
64
|
analysis = self._analyze_transactions(txs, address)
|
|
@@ -126,10 +133,85 @@ class WalletScreeningSkill(BaseSkill):
|
|
|
126
133
|
|
|
127
134
|
# --- API Helpers ---
|
|
128
135
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
136
|
+
@staticmethod
|
|
137
|
+
def normalize_eth_address(address: str) -> Optional[str]:
|
|
138
|
+
"""Normalize and validate an Ethereum address (EIP-55 checksum not required)."""
|
|
139
|
+
if not isinstance(address, str):
|
|
140
|
+
return None
|
|
141
|
+
cleaned = address.strip().translate(
|
|
142
|
+
{ord(c): None for c in _ZERO_WIDTH_CHARS}
|
|
132
143
|
)
|
|
144
|
+
if not cleaned.lower().startswith("0x"):
|
|
145
|
+
return None
|
|
146
|
+
normalized = "0x" + cleaned[2:].lower()
|
|
147
|
+
if _ETH_ADDRESS_RE.match(normalized):
|
|
148
|
+
return normalized
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def _iter_property_values(properties: Dict, key: str):
|
|
153
|
+
raw = properties.get(key)
|
|
154
|
+
if raw is None:
|
|
155
|
+
return
|
|
156
|
+
if isinstance(raw, list):
|
|
157
|
+
for item in raw:
|
|
158
|
+
if item is not None:
|
|
159
|
+
yield str(item)
|
|
160
|
+
else:
|
|
161
|
+
yield str(raw)
|
|
162
|
+
|
|
163
|
+
def _eth_addresses_from_record(self, record: Dict) -> List[str]:
|
|
164
|
+
"""Collect normalized Ethereum addresses from a sanctions or FTM record."""
|
|
165
|
+
found: List[str] = []
|
|
166
|
+
if "addresses" in record and isinstance(record["addresses"], list):
|
|
167
|
+
for a in record["addresses"]:
|
|
168
|
+
norm = self.normalize_eth_address(str(a))
|
|
169
|
+
if norm:
|
|
170
|
+
found.append(norm)
|
|
171
|
+
|
|
172
|
+
properties = record.get("properties") or {}
|
|
173
|
+
if isinstance(properties, dict):
|
|
174
|
+
for key in ("address", "publicKey"):
|
|
175
|
+
for value in self._iter_property_values(properties, key):
|
|
176
|
+
for part in value.split(","):
|
|
177
|
+
norm = self.normalize_eth_address(part)
|
|
178
|
+
if norm:
|
|
179
|
+
found.append(norm)
|
|
180
|
+
|
|
181
|
+
if "address" in record and not isinstance(record["address"], list):
|
|
182
|
+
norm = self.normalize_eth_address(str(record["address"]))
|
|
183
|
+
if norm:
|
|
184
|
+
found.append(norm)
|
|
185
|
+
|
|
186
|
+
return list(dict.fromkeys(found))
|
|
187
|
+
|
|
188
|
+
def _build_sanctions_index(self) -> None:
|
|
189
|
+
"""Index normalized ETH addresses from FTM and additional datasets."""
|
|
190
|
+
index: Dict[str, List[Dict]] = {}
|
|
191
|
+
for entity in self.sanctions_entities:
|
|
192
|
+
addrs = self._eth_addresses_from_record(entity)
|
|
193
|
+
if not addrs:
|
|
194
|
+
continue
|
|
195
|
+
tagged = dict(entity)
|
|
196
|
+
tagged["__source_file__"] = "entities.ftm.json"
|
|
197
|
+
for addr in addrs:
|
|
198
|
+
index.setdefault(addr, []).append(tagged)
|
|
199
|
+
|
|
200
|
+
for entry in self.additional_datasets:
|
|
201
|
+
addrs = self._eth_addresses_from_record(entry)
|
|
202
|
+
if not addrs:
|
|
203
|
+
continue
|
|
204
|
+
tagged = dict(entry)
|
|
205
|
+
for addr in addrs:
|
|
206
|
+
index.setdefault(addr, []).append(tagged)
|
|
207
|
+
|
|
208
|
+
self._sanctions_index = index
|
|
209
|
+
|
|
210
|
+
def _lookup_sanctions_hits(self, address: str) -> List[Dict]:
|
|
211
|
+
normalized = self.normalize_eth_address(address)
|
|
212
|
+
if not normalized:
|
|
213
|
+
return []
|
|
214
|
+
return list(self._sanctions_index.get(normalized, []))
|
|
133
215
|
|
|
134
216
|
def _get_price(self, url: str, currency: str) -> float:
|
|
135
217
|
try:
|
|
@@ -179,42 +261,6 @@ class WalletScreeningSkill(BaseSkill):
|
|
|
179
261
|
|
|
180
262
|
# --- Logic Helpers ---
|
|
181
263
|
|
|
182
|
-
def _check_against_sanctions(self, address: str) -> List[Dict]:
|
|
183
|
-
hits = []
|
|
184
|
-
lower_addr = address.lower()
|
|
185
|
-
for entity in self.sanctions_entities:
|
|
186
|
-
matches = False
|
|
187
|
-
if "addresses" in entity:
|
|
188
|
-
if any(a.lower() == lower_addr for a in entity["addresses"]):
|
|
189
|
-
matches = True
|
|
190
|
-
elif "properties" in entity and "address" in entity["properties"]:
|
|
191
|
-
if entity["properties"]["address"].lower() == lower_addr:
|
|
192
|
-
matches = True
|
|
193
|
-
|
|
194
|
-
if matches:
|
|
195
|
-
entity["__source_file__"] = "entities.ftm.json"
|
|
196
|
-
hits.append(entity)
|
|
197
|
-
return hits
|
|
198
|
-
|
|
199
|
-
def _check_against_additional_sanctions(self, address: str) -> List[Dict]:
|
|
200
|
-
hits = []
|
|
201
|
-
lower_addr = address.lower()
|
|
202
|
-
for entry in self.additional_datasets:
|
|
203
|
-
addr = None
|
|
204
|
-
if "address" in entry:
|
|
205
|
-
addr = entry["address"]
|
|
206
|
-
elif "properties" in entry and "address" in entry["properties"]:
|
|
207
|
-
addr = entry["properties"]["address"]
|
|
208
|
-
elif "addresses" in entry and isinstance(entry["addresses"], list):
|
|
209
|
-
for a in entry["addresses"]:
|
|
210
|
-
if a.lower() == lower_addr:
|
|
211
|
-
addr = a
|
|
212
|
-
break
|
|
213
|
-
|
|
214
|
-
if addr and addr.lower() == lower_addr:
|
|
215
|
-
hits.append(entry)
|
|
216
|
-
return hits
|
|
217
|
-
|
|
218
264
|
def _analyze_transactions(
|
|
219
265
|
self, txs: List[Dict], wallet_addr: str
|
|
220
266
|
) -> Dict[str, Any]:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
name: "optimization/prompt_rewriter"
|
|
2
2
|
version: "0.1.0"
|
|
3
3
|
description: "A powerful middleware skill that aggressively compresses massive, bloated prompts into fewer tokens while retaining 100% of the semantic meaning and instructions."
|
|
4
|
+
short_description: "Compresses bloated prompts into fewer tokens without losing meaning."
|
|
4
5
|
issuer:
|
|
5
6
|
name: Ross Peili
|
|
6
7
|
email: vpeilivanidis@gmail.com
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import yaml
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Dict, Any, Optional
|
|
5
|
+
|
|
6
|
+
from skillware.core.loader import SkillLoader
|
|
7
|
+
|
|
8
|
+
TABLE_STYLE = "bold #C7CEEA" # lavender - headers
|
|
9
|
+
CATEGORY_STYLE = "bold #FFDAC1" # peach - category column
|
|
10
|
+
ID_STYLE = "#B5EAD7" # mint - skill ID column
|
|
11
|
+
BORDER_STYLE = "#C7CEEA" # lavender - table border
|
|
12
|
+
|
|
13
|
+
SPLASH_STYLE = "#C7CEEA" # lavender - skillware splash color
|
|
14
|
+
MENU_STYLE = "#FFDAC1" # peach - menu category
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _get_skill_roots(skills_root_override: Optional[Path] = None) -> List[Path]:
|
|
18
|
+
"""Return the list of roots to search for skills, mirrors SkillLoader resolution order."""
|
|
19
|
+
if skills_root_override is not None:
|
|
20
|
+
if skills_root_override.exists():
|
|
21
|
+
return [skills_root_override]
|
|
22
|
+
return []
|
|
23
|
+
|
|
24
|
+
roots = []
|
|
25
|
+
seen = set()
|
|
26
|
+
|
|
27
|
+
for root in (
|
|
28
|
+
SkillLoader._env_skill_roots()
|
|
29
|
+
+ SkillLoader._cwd_skill_roots()
|
|
30
|
+
+ [SkillLoader._bundled_skills_root()]
|
|
31
|
+
):
|
|
32
|
+
resolved = root.resolve()
|
|
33
|
+
if resolved not in seen and resolved.exists():
|
|
34
|
+
seen.add(resolved)
|
|
35
|
+
roots.append(resolved)
|
|
36
|
+
|
|
37
|
+
return roots
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _short_description(data: Dict[str, Any], max_len: int = 80) -> str:
|
|
41
|
+
"""Return short_description if present, else first sentence of description truncated."""
|
|
42
|
+
short = data.get("short_description", "").strip()
|
|
43
|
+
if short:
|
|
44
|
+
return short[:max_len] + ("…" if len(short) > max_len else "")
|
|
45
|
+
|
|
46
|
+
desc = data.get("description", "").strip()
|
|
47
|
+
|
|
48
|
+
seps = [".", "!", "?"]
|
|
49
|
+
|
|
50
|
+
for sep in seps:
|
|
51
|
+
idx = desc.find(sep)
|
|
52
|
+
if idx != -1:
|
|
53
|
+
desc = desc[: idx + 1]
|
|
54
|
+
break
|
|
55
|
+
|
|
56
|
+
return desc[:max_len] + ("…" if len(desc) > max_len else "")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _discover_skills(
|
|
60
|
+
skills_root_override: Optional[Path] = None,
|
|
61
|
+
) -> List[Dict[str, Any]]:
|
|
62
|
+
"""Walk all skill roots and return a list of dicts with each skill's metadata."""
|
|
63
|
+
roots = _get_skill_roots(skills_root_override)
|
|
64
|
+
|
|
65
|
+
skills = []
|
|
66
|
+
seen_ids = set()
|
|
67
|
+
|
|
68
|
+
for root in roots:
|
|
69
|
+
for manifest_path in root.glob("*/*/manifest.yaml"):
|
|
70
|
+
|
|
71
|
+
if not SkillLoader._is_skill_dir(manifest_path.parent):
|
|
72
|
+
continue
|
|
73
|
+
|
|
74
|
+
with open(manifest_path, encoding="utf-8") as f:
|
|
75
|
+
data = yaml.safe_load(f) or {}
|
|
76
|
+
|
|
77
|
+
skill_id = f"{manifest_path.parent.parent.name}/{manifest_path.parent.name}"
|
|
78
|
+
|
|
79
|
+
# skip duplicates found in multiple roots
|
|
80
|
+
if skill_id in seen_ids:
|
|
81
|
+
continue
|
|
82
|
+
seen_ids.add(skill_id)
|
|
83
|
+
|
|
84
|
+
issuer = data.get("issuer") or {}
|
|
85
|
+
|
|
86
|
+
skills.append(
|
|
87
|
+
{
|
|
88
|
+
"id": skill_id,
|
|
89
|
+
"category": manifest_path.parent.parent.name,
|
|
90
|
+
"name": manifest_path.parent.name,
|
|
91
|
+
"version": data.get("version", "?").strip(),
|
|
92
|
+
"description": _short_description(data),
|
|
93
|
+
"requirements": ", ".join(data.get("requirements") or []).strip(),
|
|
94
|
+
"issuer": issuer.get("github") or issuer.get("name") or "",
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return skills
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def cmd_list(
|
|
102
|
+
skills_root_override: Optional[Path] = None,
|
|
103
|
+
category_filter: Optional[str] = None,
|
|
104
|
+
issuer_filter: Optional[str] = None,
|
|
105
|
+
console=None,
|
|
106
|
+
) -> None:
|
|
107
|
+
"""Print a formatted table of all available skills."""
|
|
108
|
+
try:
|
|
109
|
+
from rich.table import Table
|
|
110
|
+
from rich.console import Console
|
|
111
|
+
from rich import box
|
|
112
|
+
except ImportError:
|
|
113
|
+
raise SystemExit(
|
|
114
|
+
"rich is required for the CLI. Install it with: pip install 'skillware[cli]'"
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
if console is None:
|
|
118
|
+
console = Console()
|
|
119
|
+
|
|
120
|
+
skills = _discover_skills(skills_root_override)
|
|
121
|
+
|
|
122
|
+
if category_filter:
|
|
123
|
+
skills = [s for s in skills if s["category"] == category_filter]
|
|
124
|
+
|
|
125
|
+
if issuer_filter:
|
|
126
|
+
skills = [s for s in skills if s["issuer"] == issuer_filter]
|
|
127
|
+
|
|
128
|
+
if not skills:
|
|
129
|
+
console.print("No skills found.")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
table = Table(
|
|
133
|
+
box=box.SIMPLE_HEAVY,
|
|
134
|
+
border_style=BORDER_STYLE,
|
|
135
|
+
header_style=TABLE_STYLE,
|
|
136
|
+
expand=True,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
table.add_column("ID", style=ID_STYLE, no_wrap=True, ratio=2)
|
|
140
|
+
table.add_column("VERSION", style="dim", no_wrap=True, ratio=1)
|
|
141
|
+
table.add_column("CATEGORY", style=CATEGORY_STYLE, no_wrap=True, ratio=1)
|
|
142
|
+
table.add_column("ISSUER", style="dim", no_wrap=True, ratio=1)
|
|
143
|
+
table.add_column("DESCRIPTION", ratio=3)
|
|
144
|
+
table.add_column("REQUIREMENTS", style="dim", ratio=2)
|
|
145
|
+
|
|
146
|
+
for skill in skills:
|
|
147
|
+
table.add_row(
|
|
148
|
+
skill["id"],
|
|
149
|
+
skill["version"],
|
|
150
|
+
skill["category"],
|
|
151
|
+
skill["issuer"],
|
|
152
|
+
skill["description"],
|
|
153
|
+
skill["requirements"],
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
console.print(table)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _print_menu(console, menu) -> None:
|
|
160
|
+
for num, name, desc in menu:
|
|
161
|
+
console.print(f" [{num}] {name:<20}— {desc}", style=MENU_STYLE)
|
|
162
|
+
|
|
163
|
+
console.print()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def cmd_interactive(console=None, parser=None) -> None:
|
|
167
|
+
"""Launch ASCII splash screen and interactive menu."""
|
|
168
|
+
try:
|
|
169
|
+
from rich.console import Console
|
|
170
|
+
from rich.text import Text
|
|
171
|
+
except ImportError:
|
|
172
|
+
raise SystemExit(
|
|
173
|
+
"rich is required for the CLI. Install it with: pip install 'skillware[cli]'"
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
import importlib.metadata
|
|
177
|
+
|
|
178
|
+
if console is None:
|
|
179
|
+
console = Console()
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
version = importlib.metadata.version("skillware")
|
|
183
|
+
except importlib.metadata.PackageNotFoundError:
|
|
184
|
+
version = "dev"
|
|
185
|
+
|
|
186
|
+
splash = r"""
|
|
187
|
+
███████╗██╗ ██╗██╗██╗ ██╗ ██╗ █████╗ ██████╗ ███████╗
|
|
188
|
+
██╔════╝██║ ██╔╝██║██║ ██║ ██║██╔══██╗██╔══██╗██╔════╝
|
|
189
|
+
███████╗█████╔╝ ██║██║ ██║ █╗ ██║███████║██████╔╝█████╗
|
|
190
|
+
╚════██║██╔═██╗ ██║██║ ██║███╗██║██╔══██║██╔══██╗██╔══╝
|
|
191
|
+
███████║██║ ██╗██║███████╗╚███╔███╔╝██║ ██║██║ ██║███████╗
|
|
192
|
+
╚══════╝╚═╝ ╚═╝╚═╝╚══════╝ ╚══╝╚══╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝"""
|
|
193
|
+
|
|
194
|
+
console.print(Text(splash, style=SPLASH_STYLE))
|
|
195
|
+
console.print(
|
|
196
|
+
Text(
|
|
197
|
+
f" Skill Management Framework — v{version}",
|
|
198
|
+
style=f"dim {SPLASH_STYLE}",
|
|
199
|
+
)
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
console.print(
|
|
203
|
+
Text(
|
|
204
|
+
" https://skillware.site · https://github.com/arpahls/skillware\n",
|
|
205
|
+
style=f"dim {SPLASH_STYLE}",
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
menu = [
|
|
210
|
+
("1", "list", "discover and display all locally installed skills"),
|
|
211
|
+
("2", "paths (soon, #81)", "show and repair skill directory resolution paths"),
|
|
212
|
+
("3", "test (soon, #83)", "run test_skill.py for one or all skills"),
|
|
213
|
+
("4", "help", "usage guide for any command"),
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
commands = {
|
|
217
|
+
"1": "list",
|
|
218
|
+
"list": "list",
|
|
219
|
+
"2": "paths",
|
|
220
|
+
"paths": "paths",
|
|
221
|
+
"3": "test",
|
|
222
|
+
"test": "test",
|
|
223
|
+
"4": "help",
|
|
224
|
+
"help": "help",
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_print_menu(console, menu)
|
|
228
|
+
|
|
229
|
+
while True:
|
|
230
|
+
try:
|
|
231
|
+
choice = input(" > ").strip().lower()
|
|
232
|
+
except (KeyboardInterrupt, EOFError):
|
|
233
|
+
console.print("\n Bye.", style="dim")
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
if choice == "q":
|
|
237
|
+
console.print(" Bye.", style="dim")
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
command = commands.get(choice)
|
|
241
|
+
|
|
242
|
+
if command == "list":
|
|
243
|
+
cmd_list(console=console)
|
|
244
|
+
elif command in ("paths", "test"):
|
|
245
|
+
console.print(
|
|
246
|
+
f" '{command}' is not yet implemented. Coming in a future release.",
|
|
247
|
+
style="dim",
|
|
248
|
+
)
|
|
249
|
+
elif command == "help":
|
|
250
|
+
if parser:
|
|
251
|
+
parser.print_help()
|
|
252
|
+
else:
|
|
253
|
+
console.print(
|
|
254
|
+
" Run 'skillware --help' for usage information.", style="dim"
|
|
255
|
+
)
|
|
256
|
+
else:
|
|
257
|
+
console.print(f" Unknown command: '{choice}'", style="dim #FF9AA2")
|
|
258
|
+
|
|
259
|
+
console.print()
|
|
260
|
+
_print_menu(console, menu)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def main() -> None:
|
|
264
|
+
"""CLI entry point."""
|
|
265
|
+
parser = argparse.ArgumentParser(prog="skillware")
|
|
266
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
267
|
+
|
|
268
|
+
list_parser = subparsers.add_parser("list", help="List all available skills.")
|
|
269
|
+
list_parser.add_argument(
|
|
270
|
+
"--skills-root",
|
|
271
|
+
type=Path,
|
|
272
|
+
default=None,
|
|
273
|
+
help="Override the skills directory path.",
|
|
274
|
+
)
|
|
275
|
+
list_parser.add_argument(
|
|
276
|
+
"--category",
|
|
277
|
+
default=None,
|
|
278
|
+
help="Filter skills by category.",
|
|
279
|
+
)
|
|
280
|
+
list_parser.add_argument(
|
|
281
|
+
"--issuer",
|
|
282
|
+
default=None,
|
|
283
|
+
help="Filter skills by issuer GitHub handle or name.",
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
args = parser.parse_args()
|
|
287
|
+
|
|
288
|
+
if args.command == "list":
|
|
289
|
+
cmd_list(
|
|
290
|
+
skills_root_override=args.skills_root,
|
|
291
|
+
category_filter=args.category,
|
|
292
|
+
issuer_filter=args.issuer,
|
|
293
|
+
)
|
|
294
|
+
else:
|
|
295
|
+
cmd_interactive(parser=parser)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
if __name__ == "__main__":
|
|
299
|
+
main()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: skillware
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
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
|
|
@@ -81,6 +81,7 @@ Dynamic: license-file
|
|
|
81
81
|
<a href="#architecture">Architecture</a> •
|
|
82
82
|
<a href="#quick-start">Quick Start</a> •
|
|
83
83
|
<a href="#documentation">Documentation</a> •
|
|
84
|
+
<a href="CHANGELOG.md">Changelog</a> •
|
|
84
85
|
<a href="#contributing">Contributing</a> •
|
|
85
86
|
<a href="#comparison">Comparison</a> •
|
|
86
87
|
<a href="#contact">Contact</a>
|
|
@@ -163,7 +164,7 @@ pip install "skillware[cli]"
|
|
|
163
164
|
skillware list
|
|
164
165
|
```
|
|
165
166
|
|
|
166
|
-
This prints a table of all locally available skills and confirms the install and path resolution are working.
|
|
167
|
+
This prints a table of all locally available skills and confirms the install and path resolution are working. Running `skillware` with no arguments opens the interactive menu.
|
|
167
168
|
|
|
168
169
|
### 3. Configuration
|
|
169
170
|
|
|
@@ -240,6 +241,7 @@ For other providers and shared integration patterns, see the [usage guides index
|
|
|
240
241
|
| Topic | Link |
|
|
241
242
|
| :--- | :--- |
|
|
242
243
|
| Introduction | [docs/introduction.md](docs/introduction.md) |
|
|
244
|
+
| Changelog | [CHANGELOG.md](CHANGELOG.md) |
|
|
243
245
|
| Testing | [docs/TESTING.md](docs/TESTING.md) |
|
|
244
246
|
| Skill library | [docs/skills/README.md](docs/skills/README.md) |
|
|
245
247
|
| Usage guides | [index](docs/usage/README.md) — [Gemini](docs/usage/gemini.md) · [Claude](docs/usage/claude.md) · [OpenAI](docs/usage/openai.md) · [DeepSeek](docs/usage/deepseek.md) · [Ollama](docs/usage/ollama.md); [agent loops](docs/usage/agent_loops.md); [API keys](docs/usage/api_keys.md) |
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
from skillware.cli import
|
|
1
|
+
from skillware.cli import (
|
|
2
|
+
_discover_skills,
|
|
3
|
+
cmd_list,
|
|
4
|
+
cmd_interactive,
|
|
5
|
+
_short_description,
|
|
6
|
+
)
|
|
2
7
|
|
|
3
8
|
|
|
4
9
|
def test_discover_skills_returns_skills(tmp_path):
|
|
@@ -138,3 +143,79 @@ def test_cmd_list_filter_by_category(tmp_path):
|
|
|
138
143
|
output = buf.getvalue()
|
|
139
144
|
assert "office" in output
|
|
140
145
|
assert "finance" not in output
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def test_short_description_uses_short_description_field():
|
|
149
|
+
"""short_description field takes priority over description."""
|
|
150
|
+
data = {
|
|
151
|
+
"short_description": "Short one.",
|
|
152
|
+
"description": "This is a much longer description that should not appear.",
|
|
153
|
+
}
|
|
154
|
+
assert _short_description(data) == "Short one."
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_short_description_truncates_at_80_chars():
|
|
158
|
+
"""short_description longer than 80 chars should be truncated with …"""
|
|
159
|
+
data = {"short_description": "A" * 90}
|
|
160
|
+
result = _short_description(data)
|
|
161
|
+
assert len(result) == 81 # 80 + "…"
|
|
162
|
+
assert result.endswith("…")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_short_description_falls_back_to_first_sentence():
|
|
166
|
+
"""Without short_description, use first sentence of description."""
|
|
167
|
+
data = {"description": "First sentence. Second sentence follows."}
|
|
168
|
+
assert _short_description(data) == "First sentence."
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_short_description_empty_manifest():
|
|
172
|
+
"""Empty manifest should return empty string."""
|
|
173
|
+
assert _short_description({}) == ""
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_cmd_interactive_exits_on_q(monkeypatch):
|
|
177
|
+
"""Entering q should exit cleanly."""
|
|
178
|
+
import io
|
|
179
|
+
from rich.console import Console
|
|
180
|
+
|
|
181
|
+
monkeypatch.setattr("builtins.input", lambda _: "q")
|
|
182
|
+
buf = io.StringIO()
|
|
183
|
+
cmd_interactive(console=Console(file=buf, force_terminal=False))
|
|
184
|
+
assert "Bye" in buf.getvalue()
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_cmd_interactive_unknown_command(monkeypatch):
|
|
188
|
+
"""Unknown command should print error then exit on q."""
|
|
189
|
+
import io
|
|
190
|
+
from rich.console import Console
|
|
191
|
+
|
|
192
|
+
responses = iter(["unknown_cmd", "q"])
|
|
193
|
+
monkeypatch.setattr("builtins.input", lambda _: next(responses))
|
|
194
|
+
buf = io.StringIO()
|
|
195
|
+
cmd_interactive(console=Console(file=buf, force_terminal=False))
|
|
196
|
+
assert "Unknown command" in buf.getvalue()
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_cmd_interactive_list_dispatch(tmp_path, monkeypatch):
|
|
200
|
+
"""Entering 1 or list should dispatch to cmd_list."""
|
|
201
|
+
import io
|
|
202
|
+
from rich.console import Console
|
|
203
|
+
|
|
204
|
+
skill_dir = tmp_path / "office" / "test_skill"
|
|
205
|
+
skill_dir.mkdir(parents=True)
|
|
206
|
+
(skill_dir / "skill.py").touch()
|
|
207
|
+
(skill_dir / "manifest.yaml").write_text(
|
|
208
|
+
"name: test_skill\nversion: 0.1.0\ndescription: Test.\n"
|
|
209
|
+
"short_description: Test skill.\n"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
responses = iter(["1", "q"])
|
|
213
|
+
monkeypatch.setattr("builtins.input", lambda _: next(responses))
|
|
214
|
+
monkeypatch.chdir(tmp_path)
|
|
215
|
+
|
|
216
|
+
buf = io.StringIO()
|
|
217
|
+
console = Console(file=buf, force_terminal=False)
|
|
218
|
+
cmd_interactive(console=console)
|
|
219
|
+
|
|
220
|
+
output = buf.getvalue()
|
|
221
|
+
assert "test_skill" in output
|
skillware-0.3.1/skillware/cli.py
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import argparse
|
|
2
|
-
import yaml
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import List, Dict, Any, Optional
|
|
5
|
-
|
|
6
|
-
from skillware.core.loader import SkillLoader
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def _get_skill_roots(skills_root_override: Optional[Path] = None) -> List[Path]:
|
|
10
|
-
"""Return the list of roots to search for skills, mirrors SkillLoader resolution order."""
|
|
11
|
-
if skills_root_override is not None:
|
|
12
|
-
if skills_root_override.exists():
|
|
13
|
-
return [skills_root_override]
|
|
14
|
-
return []
|
|
15
|
-
|
|
16
|
-
roots = []
|
|
17
|
-
seen = set()
|
|
18
|
-
|
|
19
|
-
for root in (
|
|
20
|
-
SkillLoader._env_skill_roots()
|
|
21
|
-
+ SkillLoader._cwd_skill_roots()
|
|
22
|
-
+ [SkillLoader._bundled_skills_root()]
|
|
23
|
-
):
|
|
24
|
-
resolved = root.resolve()
|
|
25
|
-
if resolved not in seen and resolved.exists():
|
|
26
|
-
seen.add(resolved)
|
|
27
|
-
roots.append(resolved)
|
|
28
|
-
|
|
29
|
-
return roots
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def _discover_skills(
|
|
33
|
-
skills_root_override: Optional[Path] = None,
|
|
34
|
-
) -> List[Dict[str, Any]]:
|
|
35
|
-
"""Walk all skill roots and return a list of dicts with each skill's metadata."""
|
|
36
|
-
roots = _get_skill_roots(skills_root_override)
|
|
37
|
-
|
|
38
|
-
skills = []
|
|
39
|
-
seen_ids = set()
|
|
40
|
-
|
|
41
|
-
for root in roots:
|
|
42
|
-
for manifest_path in root.glob("*/*/manifest.yaml"):
|
|
43
|
-
|
|
44
|
-
if not SkillLoader._is_skill_dir(manifest_path.parent):
|
|
45
|
-
continue
|
|
46
|
-
|
|
47
|
-
with open(manifest_path, encoding="utf-8") as f:
|
|
48
|
-
data = yaml.safe_load(f) or {}
|
|
49
|
-
|
|
50
|
-
skill_id = f"{manifest_path.parent.parent.name}/{manifest_path.parent.name}"
|
|
51
|
-
|
|
52
|
-
# skip duplicates found in multiple roots
|
|
53
|
-
if skill_id in seen_ids:
|
|
54
|
-
continue
|
|
55
|
-
seen_ids.add(skill_id)
|
|
56
|
-
|
|
57
|
-
issuer = data.get("issuer") or {}
|
|
58
|
-
|
|
59
|
-
skills.append(
|
|
60
|
-
{
|
|
61
|
-
"id": skill_id,
|
|
62
|
-
"category": manifest_path.parent.parent.name,
|
|
63
|
-
"name": manifest_path.parent.name,
|
|
64
|
-
"version": data.get("version", "?").strip(),
|
|
65
|
-
"description": data.get("description", "").strip(),
|
|
66
|
-
"requirements": ", ".join(data.get("requirements") or []).strip(),
|
|
67
|
-
"issuer": issuer.get("github") or issuer.get("name") or "",
|
|
68
|
-
}
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
return skills
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def cmd_list(
|
|
75
|
-
skills_root_override: Optional[Path] = None,
|
|
76
|
-
category_filter: Optional[str] = None,
|
|
77
|
-
issuer_filter: Optional[str] = None,
|
|
78
|
-
console=None,
|
|
79
|
-
) -> None:
|
|
80
|
-
"""Print a formatted table of all available skills."""
|
|
81
|
-
try:
|
|
82
|
-
from rich.table import Table
|
|
83
|
-
from rich.console import Console
|
|
84
|
-
except ImportError:
|
|
85
|
-
raise SystemExit(
|
|
86
|
-
"rich is required for the CLI. Install it with: pip install 'skillware[cli]'"
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
if console is None:
|
|
90
|
-
console = Console()
|
|
91
|
-
|
|
92
|
-
skills = _discover_skills(skills_root_override)
|
|
93
|
-
|
|
94
|
-
if category_filter:
|
|
95
|
-
skills = [s for s in skills if s["category"] == category_filter]
|
|
96
|
-
|
|
97
|
-
if issuer_filter:
|
|
98
|
-
skills = [s for s in skills if s["issuer"] == issuer_filter]
|
|
99
|
-
|
|
100
|
-
if not skills:
|
|
101
|
-
console.print("No skills found.")
|
|
102
|
-
return
|
|
103
|
-
|
|
104
|
-
table = Table()
|
|
105
|
-
|
|
106
|
-
table.add_column("ID")
|
|
107
|
-
table.add_column("VERSION")
|
|
108
|
-
table.add_column("CATEGORY")
|
|
109
|
-
table.add_column("ISSUER")
|
|
110
|
-
table.add_column("DESCRIPTION")
|
|
111
|
-
table.add_column("REQUIREMENTS")
|
|
112
|
-
|
|
113
|
-
for skill in skills:
|
|
114
|
-
table.add_row(
|
|
115
|
-
skill["id"],
|
|
116
|
-
skill["version"],
|
|
117
|
-
skill["category"],
|
|
118
|
-
skill["issuer"],
|
|
119
|
-
skill["description"],
|
|
120
|
-
skill["requirements"],
|
|
121
|
-
)
|
|
122
|
-
|
|
123
|
-
console.print(table)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def main() -> None:
|
|
127
|
-
"""CLI entry point."""
|
|
128
|
-
parser = argparse.ArgumentParser(prog="skillware")
|
|
129
|
-
subparsers = parser.add_subparsers(dest="command")
|
|
130
|
-
|
|
131
|
-
list_parser = subparsers.add_parser("list", help="List all available skills.")
|
|
132
|
-
list_parser.add_argument(
|
|
133
|
-
"--skills-root",
|
|
134
|
-
type=Path,
|
|
135
|
-
default=None,
|
|
136
|
-
help="Override the skills directory path.",
|
|
137
|
-
)
|
|
138
|
-
list_parser.add_argument(
|
|
139
|
-
"--category",
|
|
140
|
-
default=None,
|
|
141
|
-
help="Filter skills by category.",
|
|
142
|
-
)
|
|
143
|
-
list_parser.add_argument(
|
|
144
|
-
"--issuer",
|
|
145
|
-
default=None,
|
|
146
|
-
help="Filter skills by issuer GitHub handle or name.",
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
args = parser.parse_args()
|
|
150
|
-
|
|
151
|
-
if args.command == "list":
|
|
152
|
-
cmd_list(
|
|
153
|
-
skills_root_override=args.skills_root,
|
|
154
|
-
category_filter=args.category,
|
|
155
|
-
issuer_filter=args.issuer,
|
|
156
|
-
)
|
|
157
|
-
else:
|
|
158
|
-
parser.print_help()
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if __name__ == "__main__":
|
|
162
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/novelty_extractor/instructions.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{skillware-0.3.1 → skillware-0.3.2}/skills/data_engineering/synthetic_generator/instructions.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/data/malicious_scs_2025.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{skillware-0.3.1 → skillware-0.3.2}/skills/finance/wallet_screening/data/normalized_uniswap_trm.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|