From 41815545ebd7d1f4ede5f70154d69ba17555cb58 Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Sat, 25 Jul 2026 00:38:33 +0530 Subject: [PATCH 01/10] feat(bg_remover): harden skill to v0.2.0 Signed-off-by: AyushSrivastava1818 --- CHANGELOG.md | 4 ++ docs/skills/bg_remover.md | 2 +- skills/creative/bg_remover/instructions.md | 16 ++++- skills/creative/bg_remover/manifest.yaml | 2 +- skills/creative/bg_remover/skill.py | 72 +++++++++++++++++-- skills/creative/bg_remover/test_skill.py | 80 +++++++++++++++++++++- 6 files changed, 165 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 386e239..0af4e1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ Contributors add user-facing entries under `[Unreleased]` in the same PR. Mainta - **Citation:** Root `CITATION.cff` and README **Citing** section for formal software citation; first Zenodo-archived GitHub release (#269, #270). +### Changed + +- **`creative/bg_remover`**: Hardened the skill by reusing rembg sessions across executions, validating Base64 input and image files, rejecting empty, oversized (>25 MB), or invalid images, and automatically creating parent directories for `output_path`. Updated documentation and expanded bundle tests for the new validation behavior. + ## [0.4.6] - 2026-07-23 ### Added diff --git a/docs/skills/bg_remover.md b/docs/skills/bg_remover.md index 36823a3..d8e1960 100644 --- a/docs/skills/bg_remover.md +++ b/docs/skills/bg_remover.md @@ -123,7 +123,7 @@ Skill instructions: when to invoke, input/output conventions, URL and cloud pre- ### The Body (`skill.py`) -Lazy-imports `rembg` and Pillow, runs `new_session` + `remove`, and returns structured JSON with transparent PNG bytes. +Lazy-imports `rembg` and Pillow, reuses cached rembg sessions, runs `remove`, and returns structured JSON with transparent PNG bytes. ## Usage Examples diff --git a/skills/creative/bg_remover/instructions.md b/skills/creative/bg_remover/instructions.md index ee0e3c6..ac952c8 100644 --- a/skills/creative/bg_remover/instructions.md +++ b/skills/creative/bg_remover/instructions.md @@ -32,6 +32,8 @@ For S3, GCS, Azure Blob, or Cloudflare R2: download the object to a temp file On the **first run** in a fresh environment, processing may take a few minutes while rembg downloads the ONNX model (~176 MB for `isnet-general-use`). Later runs reuse the cache and are much faster. +Background removal sessions are also reused across executions for the same model, reducing repeated initialization overhead. + ## Input (one required: `image` OR `input_path`) | Scenario | Parameter | @@ -43,6 +45,14 @@ On the **first run** in a fresh environment, processing may take a few minutes w If **both** `image` and `input_path` are sent, **`image` wins** (do not double-submit). +### Input validation + +- Invalid Base64 payloads are rejected. +- `input_path` must reference an existing file (directories are rejected). +- Empty image files are rejected. +- Images larger than **25 MB** are rejected. +- Image integrity is validated before processing. + ### Example payloads Chat / attachment: @@ -91,6 +101,8 @@ Omit `model` for default `isnet-general-use`. Set `alpha_matting` only when edge | Chat or API only | Omit `output_path`; use `image_base64` from the result (always present on success). | | Save next to original | Same directory, new name (e.g. `1223_no_bg.png`). | +If the parent directory of `output_path` does not exist, it is created automatically before writing the PNG. + ## Interpreting a successful result When `success` is `true`: @@ -109,10 +121,12 @@ Runtime: `rembg`, `pillow`, `onnxruntime`. Install: `pip install "skillware[crea First `execute()` downloads the ONNX model to the rembg cache (`~/.u2net/` on Linux/macOS, `%USERPROFILE%\.u2net\` on Windows). +Subsequent executions reuse cached rembg sessions for the selected model, reducing repeated initialization overhead. + ## Errors | `error_code` | Response | | :--- | :--- | -| `INVALID_INPUT` | Ask for an upload, attachment, base64 payload, or local `input_path`. | +| `INVALID_INPUT` | Invalid Base64, missing input, directory path, empty image, oversized image (>25 MB), corrupt image, or unsupported input. | | `MISSING_DEPENDENCY` | Ask the user to run `pip install "skillware[creative_bg_remover]"` (or `pip install rembg pillow onnxruntime`), then retry. | | `PROCESSING_FAILED` | Surface the `error` string; input may be missing, corrupt, or unsupported. | diff --git a/skills/creative/bg_remover/manifest.yaml b/skills/creative/bg_remover/manifest.yaml index 3e59e81..0160641 100644 --- a/skills/creative/bg_remover/manifest.yaml +++ b/skills/creative/bg_remover/manifest.yaml @@ -1,5 +1,5 @@ name: "creative/bg_remover" -version: "0.1.0" +version: "0.2.0" description: "Remove image backgrounds locally using rembg." short_description: "Offline background removal using rembg with transparent PNG output." diff --git a/skills/creative/bg_remover/skill.py b/skills/creative/bg_remover/skill.py index ceac48e..ca59799 100644 --- a/skills/creative/bg_remover/skill.py +++ b/skills/creative/bg_remover/skill.py @@ -5,15 +5,26 @@ from skillware.core.base_skill import BaseSkill +MAX_IMAGE_BYTES = 25 * 1024 * 1024 # 25 MB class BackgroundRemover(BaseSkill): """Remove image backgrounds locally using rembg.""" + _sessions = {} + + @classmethod + def _get_session(cls, model: str): + """Load and reuse rembg sessions across executions.""" + if model not in cls._sessions: + from rembg import new_session + cls._sessions[model] = new_session(model) + + return cls._sessions[model] @property def manifest(self) -> Dict[str, Any]: return { "name": "creative/bg_remover", - "version": "0.1.0", + "version": "0.2.0", "description": ( "Remove image backgrounds locally using rembg " "and return a transparent PNG." @@ -26,7 +37,7 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: try: try: from PIL import Image - from rembg import new_session, remove + from rembg import remove except ImportError: return { "success": False, @@ -53,11 +64,58 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: # Read image bytes if image_b64: - image_bytes = base64.b64decode(image_b64) + try: + image_bytes = base64.b64decode(image_b64, validate=True) + except Exception: + return { + "success": False, + "error": "Invalid base64 image.", + "error_code": "INVALID_INPUT", + } else: - image_bytes = Path(input_path).read_bytes() + input_file = Path(input_path) + + if input_file.is_dir(): + return { + "success": False, + "error": "Input path must be a file, not a directory.", + "error_code": "INVALID_INPUT", + } + + if not input_file.exists(): + return { + "success": False, + "error": f"Input file '{input_path}' was not found.", + "error_code": "FILE_NOT_FOUND", + } + + image_bytes = input_file.read_bytes() + + if len(image_bytes) > MAX_IMAGE_BYTES: + return { + "success": False, + "error": f"Input image exceeds the maximum size of {MAX_IMAGE_BYTES // (1024 * 1024)} MB.", + "error_code": "INVALID_INPUT", + } + + if len(image_bytes) == 0: + return { + "success": False, + "error": "Input image is empty.", + "error_code": "INVALID_INPUT", + } + + try: + image = Image.open(io.BytesIO(image_bytes)) + image.verify() + except Exception: + return { + "success": False, + "error": "Input is not a valid image.", + "error_code": "INVALID_INPUT", + } - session = new_session(model) + session = self._get_session(model) output_bytes = remove( image_bytes, @@ -76,7 +134,9 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: # Optional save if output_path: - Path(output_path).write_bytes(buffer.getvalue()) + output_file = Path(output_path) + output_file.parent.mkdir(parents=True, exist_ok=True) + output_file.write_bytes(buffer.getvalue()) return { "success": True, diff --git a/skills/creative/bg_remover/test_skill.py b/skills/creative/bg_remover/test_skill.py index 92f50fb..b16ef34 100644 --- a/skills/creative/bg_remover/test_skill.py +++ b/skills/creative/bg_remover/test_skill.py @@ -117,7 +117,7 @@ def test_invalid_base64(skill): result = skill.execute({"image": "not_base64"}) assert result["success"] is False - assert result["error_code"] == "PROCESSING_FAILED" + assert result["error_code"] == "INVALID_INPUT" def test_missing_dependency(monkeypatch, skill): @@ -249,4 +249,80 @@ def test_missing_input_path(skill): result = skill.execute({"input_path": "/nonexistent/path/image.png"}) assert result["success"] is False - assert result["error_code"] == "PROCESSING_FAILED" + assert result["error_code"] == "FILE_NOT_FOUND" + + +def test_directory_input(skill, tmp_path): + result = skill.execute({"input_path": str(tmp_path)}) + + assert result["success"] is False + assert result["error_code"] == "INVALID_INPUT" + + +def test_empty_input_file(skill, tmp_path): + empty = tmp_path / "empty.png" + empty.write_bytes(b"") + + result = skill.execute({"input_path": str(empty)}) + + assert result["success"] is False + assert result["error_code"] == "INVALID_INPUT" + + +def test_invalid_image_file(skill, tmp_path): + bad = tmp_path / "bad.png" + bad.write_text("not an image") + + result = skill.execute({"input_path": str(bad)}) + + assert result["success"] is False + assert result["error_code"] == "INVALID_INPUT" + + +def test_output_directory_created(skill, tmp_path): + image = Image.new("RGB", (64, 64), "white") + + input_file = tmp_path / "input.png" + image.save(input_file) + + output_file = tmp_path / "nested" / "folder" / "output.png" + + result = skill.execute( + { + "input_path": str(input_file), + "output_path": str(output_file), + } + ) + + assert result["success"] is True + assert output_file.exists() + + +def test_session_reuse(skill, monkeypatch): + calls = {"count": 0} + + def fake_remove(image_bytes, *args, **kwargs): + img = Image.new("RGBA", (100, 100), (255, 0, 0, 0)) + buffer = io.BytesIO() + img.save(buffer, format="PNG") + return buffer.getvalue() + + def fake_new_session(model_name="u2net", *args, **kwargs): + calls["count"] += 1 + return object() + + fake_module = types.ModuleType("rembg") + fake_module.remove = fake_remove + fake_module.new_session = fake_new_session + fake_module.__spec__ = importlib.util.spec_from_loader("rembg", loader=None) + + monkeypatch.setitem(sys.modules, "rembg", fake_module) + + BackgroundRemover._sessions.clear() + + skill.execute({"image": create_image()}) + skill.execute({"image": create_image()}) + + assert calls["count"] == 1 + + BackgroundRemover._sessions.clear() \ No newline at end of file From 9bb812109f8c7a8485afc3ab1c3ef0d6d4bbfeec Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Sat, 25 Jul 2026 00:44:01 +0530 Subject: [PATCH 02/10] style(bg_remover): apply black formatting --- skills/creative/bg_remover/skill.py | 3 +++ skills/creative/bg_remover/test_skill.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/skills/creative/bg_remover/skill.py b/skills/creative/bg_remover/skill.py index ca59799..0aa237f 100644 --- a/skills/creative/bg_remover/skill.py +++ b/skills/creative/bg_remover/skill.py @@ -7,8 +7,10 @@ MAX_IMAGE_BYTES = 25 * 1024 * 1024 # 25 MB + class BackgroundRemover(BaseSkill): """Remove image backgrounds locally using rembg.""" + _sessions = {} @classmethod @@ -16,6 +18,7 @@ def _get_session(cls, model: str): """Load and reuse rembg sessions across executions.""" if model not in cls._sessions: from rembg import new_session + cls._sessions[model] = new_session(model) return cls._sessions[model] diff --git a/skills/creative/bg_remover/test_skill.py b/skills/creative/bg_remover/test_skill.py index b16ef34..16695d5 100644 --- a/skills/creative/bg_remover/test_skill.py +++ b/skills/creative/bg_remover/test_skill.py @@ -325,4 +325,4 @@ def fake_new_session(model_name="u2net", *args, **kwargs): assert calls["count"] == 1 - BackgroundRemover._sessions.clear() \ No newline at end of file + BackgroundRemover._sessions.clear() From 7c79ea882a910c7e10024e9b2c501c20b89c9e95 Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Wed, 29 Jul 2026 16:52:53 +0530 Subject: [PATCH 03/10] feat(bg_remover): address review feedback --- docs/usage/agent_loops.md | 2 +- examples/README.md | 146 ++++++++++----------- examples/bg_remover_demo.py | 42 ++++++ skills/creative/bg_remover/instructions.md | 1 + skills/creative/bg_remover/manifest.yaml | 2 +- skills/creative/bg_remover/skill.py | 22 +++- skills/creative/bg_remover/test_skill.py | 11 ++ 7 files changed, 144 insertions(+), 82 deletions(-) create mode 100644 examples/bg_remover_demo.py diff --git a/docs/usage/agent_loops.md b/docs/usage/agent_loops.md index b158130..ceba8b4 100644 --- a/docs/usage/agent_loops.md +++ b/docs/usage/agent_loops.md @@ -99,7 +99,7 @@ skills in one harness. | `compliance/mica_module` | - | `mica_rag_flow.py` | `mica_claude_flow.py` | (catalog page) | (catalog page) | `mica_ollama_flow.py` | | `compliance/pii_masker` | `pii_guardrail_flow.py` (local execute) | (catalog page) | (catalog page) | (catalog page) | (catalog page) | (catalog page) | | `security/prompt_injection_firewall` | `prompt_injection_firewall_demo.py` (local execute) | (catalog page) | (catalog page) | (catalog page) | (catalog page) | (catalog page) | -| `creative/bg_remover` | (catalog page) | (catalog page) | (catalog page) | (catalog page) | (catalog page) | (catalog page) | +| `creative/bg_remover` | `bg_remover_demo.py` (local execute) | (catalog page) | (catalog page) | (catalog page) | (catalog page) | (catalog page) | | `optimization/prompt_rewriter` | `prompt_compression_demo.py` (local execute) | (catalog page) | (catalog page) | (catalog page) | (catalog page) | `ollama_skills_test.py` (multi-skill) | | `data_engineering/synthetic_generator` | `build_dataset_demo.py` (local execute, Gemini backend) | (catalog page) | (catalog page) | (catalog page) | (catalog page) | (catalog page) | | `data_engineering/novelty_extractor` | `novelty_extractor_demo.py` (local execute) | `gemini_novelty_extractor.py` | (catalog page) | (catalog page) | (catalog page) | `ollama_novelty_extractor.py` | diff --git a/examples/README.md b/examples/README.md index b0dfd31..9f8f716 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,81 +1,69 @@ -# Skillware Examples Index - -> **These are usage examples, not tests.** Runnable provider demos live here; automated tests live in `skills/**/test_skill.py` (bundle) and `tests/` (framework and optional maintainer depth). See [TESTING.md](../docs/TESTING.md). - -Runnable examples in this directory show how to load Skillware skills, adapt -them for a provider, execute local skill logic, and return tool results to an -agent loop. After `pip install skillware`, run `skillware examples` or -`skillware list --examples` from any directory to browse the index in the -terminal; when no local `examples/README.md` is present, the index is fetched -from GitHub (network required); see -[CLI reference](../docs/usage/cli.md). Provider setup details live in the usage guides: - -- [API keys for skills](../docs/usage/api_keys.md) -- [Gemini](../docs/usage/gemini.md) -- [Claude](../docs/usage/claude.md) -- [OpenAI](../docs/usage/openai.md) +# Skillware Examples Index + +> **These are usage examples, not tests.** Runnable provider demos live here; automated tests live in `skills/**/test_skill.py` (bundle) and `tests/` (framework and optional maintainer depth). See [TESTING.md](../docs/TESTING.md). + +Runnable examples in this directory show how to load Skillware skills, adapt them for a provider, execute local skill logic, and return tool results to an agent loop. After `pip install skillware`, run `skillware examples` or `skillware list --examples` from any directory to browse the index in the terminal; when no local `examples/README.md` is present, the index is fetched from GitHub (network required); see [CLI reference](../docs/usage/cli.md). Provider setup details live in the usage guides: +- [API keys for skills](../docs/usage/api_keys.md) +- [Gemini](../docs/usage/gemini.md) +- [Claude](../docs/usage/claude.md) +- [OpenAI](../docs/usage/openai.md) - [OpenAI-compatible hosts](../docs/usage/openai_compatible.md) -- [DeepSeek](../docs/usage/deepseek.md) -- [Ollama](../docs/usage/ollama.md) -- [Install extras](../docs/usage/install_extras.md) -- [Agent loops](../docs/usage/agent_loops.md) - -Install the **skill extra** for each script (see [Install extras](../docs/usage/install_extras.md)), plus an **SDK extra** when the provider column is not local execute: - -```bash -pip install "skillware[office_pdf_form_filler]" "skillware[gemini]" -``` - -For local development with every skill and SDK: - -```bash -pip install -e ".[dev,all,agents]" -``` - -## Runnable Scripts - -| Script | Skill ID | Provider | Required extra | Required env vars | Description | -| :--- | :--- | :--- | :--- | :--- | :--- | -| `mental_coach_demo.py` | `wellness/mental_coach` | Local execute | `[wellness_mental_coach]` | None | Demonstrates coaching, crisis escalation, and blocked clinical paths locally. | -| `build_dataset_demo.py` | `data_engineering/synthetic_generator` | Local execute (Gemini backend) | `[data_engineering_synthetic_generator]`, `[gemini]` | `GOOGLE_API_KEY` | Generates a JSONL synthetic dataset with the synthetic generator skill. | -| `claude_pdf_form_filler.py` | `office/pdf_form_filler` | Claude | `[office_pdf_form_filler]`, `[claude]` | `ANTHROPIC_API_KEY` | Uses Claude with the PDF form filler skill to map instructions to fields. | -| `claude_tos_evaluator.py` | `compliance/tos_evaluator` | Claude | `[compliance_tos_evaluator]`, `[claude]` | `ANTHROPIC_API_KEY` | Runs a Claude tool loop for website automation policy review. | -| `claude_issue_resolver.py` | `dev_tools/issue_resolver` | Claude | `[dev_tools_issue_resolver]`, `[claude]` | `ANTHROPIC_API_KEY`; optional `GITHUB_TOKEN` | Claude loop for GitHub issue analysis; fetches issue data after `prepare` (sample: issue #123). | -| `claude_wallet_check.py` | `finance/wallet_screening` | Claude | `[finance_wallet_screening]`, `[claude]` | `ANTHROPIC_API_KEY`, `ETHERSCAN_API_KEY` | Screens an Ethereum wallet and returns the result through a Claude tool loop. | -| `deepseek_tos_evaluator.py` | `compliance/tos_evaluator` | DeepSeek | `[compliance_tos_evaluator]`, `[openai]` | `DEEPSEEK_API_KEY` | Uses the OpenAI-compatible DeepSeek API for terms-of-service evaluation. | -| `gemini_pdf_form_filler.py` | `office/pdf_form_filler` | Gemini | `[office_pdf_form_filler]`, `[gemini]` | `GOOGLE_API_KEY`, `ANTHROPIC_API_KEY` | Uses Gemini as the agent while the PDF skill calls Anthropic for form filling. | -| `gemini_tos_evaluator.py` | `compliance/tos_evaluator` | Gemini | `[compliance_tos_evaluator]`, `[gemini]` | `GOOGLE_API_KEY` | Runs the terms-of-service evaluator with a Gemini function-calling loop. | -| `gemini_wallet_check.py` | `finance/wallet_screening` | Gemini | `[finance_wallet_screening]`, `[gemini]` | `GOOGLE_API_KEY`, `ETHERSCAN_API_KEY` | Screens an Ethereum wallet with Gemini orchestration and Etherscan data. | -| `gemini_evm_tx_handler.py` | `defi/evm_tx_handler` | Gemini | `[defi_evm_tx_handler]`, `[gemini]` | `GOOGLE_API_KEY`; for live swaps also `AGENT_WALLET_PRIVATE_KEY`, `BASE_RPC_URL` or `ETHEREUM_RPC_URL`. Set `EVM_TX_HANDLER_EXAMPLE_DEMO=1` for mocked flow without keys. | Resolve → quote → preview → execute buy flow via Gemini tool loop (or demo mode). | -| `claude_evm_tx_handler.py` | `defi/evm_tx_handler` | Claude | `[defi_evm_tx_handler]`, `[claude]` | `ANTHROPIC_API_KEY`; for live swaps also `AGENT_WALLET_PRIVATE_KEY`, RPC URLs. Demo: `EVM_TX_HANDLER_EXAMPLE_DEMO=1`. | Claude tool loop for structured DeFi intent and optional execute after confirmation. | -| `mica_claude_flow.py` | `compliance/mica_module` | Claude | `[compliance_mica_module]`, `[claude]` | `ANTHROPIC_API_KEY` | Runs a MiCA compliance agent loop through Claude. | -| `mica_ollama_flow.py` | `compliance/mica_module` | Ollama | `[compliance_mica_module]`; install `ollama` separately | None | Runs a local Ollama MiCA flow with prompt-mode tool calling. | -| `mica_rag_flow.py` | `compliance/mica_module` | Gemini | `[compliance_mica_module]`, `[gemini]` | `GOOGLE_API_KEY` | Runs the MiCA RAG flow with Gemini. | -| `ollama_skills_test.py` | `finance/wallet_screening`, `office/pdf_form_filler`, `optimization/prompt_rewriter` | Ollama | `[finance_wallet_screening]`, `[office_pdf_form_filler]`, `[optimization_prompt_rewriter]`; install `ollama` separately | `ETHERSCAN_API_KEY`, `ANTHROPIC_API_KEY` | Loads multiple skills and tests prompt-mode tool calling with Ollama. | -| `ollama_tos_evaluator.py` | `compliance/tos_evaluator` | Ollama | `[compliance_tos_evaluator]`; install `ollama` separately | None | Runs the terms-of-service evaluator with local Ollama prompt-mode calls. | -| `openai_tos_evaluator.py` | `compliance/tos_evaluator` | OpenAI | `[compliance_tos_evaluator]`, `[openai]` | `OPENAI_API_KEY` | Runs the terms-of-service evaluator with OpenAI function calling. | +- [DeepSeek](../docs/usage/deepseek.md) +- [Ollama](../docs/usage/ollama.md) +- [Install extras](../docs/usage/install_extras.md) +- [Agent loops](../docs/usage/agent_loops.md) + +Install the **skill extra** for each script (see [Install extras](../docs/usage/install_extras.md)), plus an **SDK extra** when the provider column is not local execute: + +```bash +pip install "skillware[office_pdf_form_filler]" "skillware[gemini]" +``` + +For local development with every skill and SDK: +```bash +pip install -e ".[dev,all,agents]" +``` + +## Runnable Scripts +| Script | Skill ID | Provider | Required extra | Required env vars | Description | +| :--- | :--- | :--- | :--- | :--- | :--- | +| `mental_coach_demo.py` | `wellness/mental_coach` | Local execute | `[wellness_mental_coach]` | None | Demonstrates coaching, crisis escalation, and blocked clinical paths locally. | +| `build_dataset_demo.py` | `data_engineering/synthetic_generator` | Local execute (Gemini backend) | `[data_engineering_synthetic_generator]`, `[gemini]` | `GOOGLE_API_KEY` | Generates a JSONL synthetic dataset with the synthetic generator skill. | +| `claude_pdf_form_filler.py` | `office/pdf_form_filler` | Claude | `[office_pdf_form_filler]`, `[claude]` | `ANTHROPIC_API_KEY` | Uses Claude with the PDF form filler skill to map instructions to fields. | +| `claude_tos_evaluator.py` | `compliance/tos_evaluator` | Claude | `[compliance_tos_evaluator]`, `[claude]` | `ANTHROPIC_API_KEY` | Runs a Claude tool loop for website automation policy review. | +| `claude_issue_resolver.py` | `dev_tools/issue_resolver` | Claude | `[dev_tools_issue_resolver]`, `[claude]` | `ANTHROPIC_API_KEY`; optional `GITHUB_TOKEN` | Claude loop for GitHub issue analysis; fetches issue data after `prepare` (sample: issue #123). | +| `claude_wallet_check.py` | `finance/wallet_screening` | Claude | `[finance_wallet_screening]`, `[claude]` | `ANTHROPIC_API_KEY`, `ETHERSCAN_API_KEY` | Screens an Ethereum wallet and returns the result through a Claude tool loop. | +| `deepseek_tos_evaluator.py` | `compliance/tos_evaluator` | DeepSeek | `[compliance_tos_evaluator]`, `[openai]` | `DEEPSEEK_API_KEY` | Uses the OpenAI-compatible DeepSeek API for terms-of-service evaluation. | +| `gemini_pdf_form_filler.py` | `office/pdf_form_filler` | Gemini | `[office_pdf_form_filler]`, `[gemini]` | `GOOGLE_API_KEY`, `ANTHROPIC_API_KEY` | Uses Gemini as the agent while the PDF skill calls Anthropic for form filling. | +| `gemini_tos_evaluator.py` | `compliance/tos_evaluator` | Gemini | `[compliance_tos_evaluator]`, `[gemini]` | `GOOGLE_API_KEY` | Runs the terms-of-service evaluator with a Gemini function-calling loop. | +| `gemini_wallet_check.py` | `finance/wallet_screening` | Gemini | `[finance_wallet_screening]`, `[gemini]` | `GOOGLE_API_KEY`, `ETHERSCAN_API_KEY` | Screens an Ethereum wallet with Gemini orchestration and Etherscan data. | +| `gemini_evm_tx_handler.py` | `defi/evm_tx_handler` | Gemini | `[defi_evm_tx_handler]`, `[gemini]` | `GOOGLE_API_KEY`; for live swaps also `AGENT_WALLET_PRIVATE_KEY`, `BASE_RPC_URL` or `ETHEREUM_RPC_URL`. Set `EVM_TX_HANDLER_EXAMPLE_DEMO=1` for mocked flow without keys. | Resolve → quote → preview → execute buy flow via Gemini tool loop (or demo mode). | +| `claude_evm_tx_handler.py` | `defi/evm_tx_handler` | Claude | `[defi_evm_tx_handler]`, `[claude]` | `ANTHROPIC_API_KEY`; for live swaps also `AGENT_WALLET_PRIVATE_KEY`, RPC URLs. Demo: `EVM_TX_HANDLER_EXAMPLE_DEMO=1`. | Claude tool loop for structured DeFi intent and optional execute after confirmation. | +| `mica_claude_flow.py` | `compliance/mica_module` | Claude | `[compliance_mica_module]`, `[claude]` | `ANTHROPIC_API_KEY` | Runs a MiCA compliance agent loop through Claude. | +| `mica_ollama_flow.py` | `compliance/mica_module` | Ollama | `[compliance_mica_module]`; install `ollama` separately | None | Runs a local Ollama MiCA flow with prompt-mode tool calling. | +| `mica_rag_flow.py` | `compliance/mica_module` | Gemini | `[compliance_mica_module]`, `[gemini]` | `GOOGLE_API_KEY` | Runs the MiCA RAG flow with Gemini. | +| `ollama_skills_test.py` | `finance/wallet_screening`, `office/pdf_form_filler`, `optimization/prompt_rewriter` | Ollama | `[finance_wallet_screening]`, `[office_pdf_form_filler]`, `[optimization_prompt_rewriter]`; install `ollama` separately | `ETHERSCAN_API_KEY`, `ANTHROPIC_API_KEY` | Loads multiple skills and tests prompt-mode tool calling with Ollama. | +| `ollama_tos_evaluator.py` | `compliance/tos_evaluator` | Ollama | `[compliance_tos_evaluator]`; install `ollama` separately | None | Runs the terms-of-service evaluator with local Ollama prompt-mode calls. | +| `openai_tos_evaluator.py` | `compliance/tos_evaluator` | OpenAI | `[compliance_tos_evaluator]`, `[openai]` | `OPENAI_API_KEY` | Runs the terms-of-service evaluator with OpenAI function calling. | | `openai_compatible_host.py` | `compliance/tos_evaluator` | Groq (OpenAI-compatible) | `[compliance_tos_evaluator]`, `[openai]` | `GROQ_API_KEY`, `GROQ_MODEL` | Runs the terms-of-service evaluator through Groq's OpenAI-compatible API. | -| `pii_guardrail_flow.py` | `compliance/pii_masker` | Local execute | `[compliance_pii_masker]` | None | Demonstrates local PII masking before passing text to an external agent. | -| `prompt_injection_firewall_demo.py` | `security/prompt_injection_firewall` | Local execute | `[security_prompt_injection_firewall]` | None | Offline prompt-injection scan and sanitization with no API keys. | -| `prompt_compression_demo.py` | `optimization/prompt_rewriter` | Local execute | `[optimization_prompt_rewriter]` | None | Demonstrates prompt compression without a provider loop. | -| `novelty_extractor_demo.py` | `data_engineering/novelty_extractor` | Local execute | `[data_engineering_novelty_extractor]` | None | Demonstrates multi-turn corpus distillation using local embeddings with no API key. | -| `gemini_novelty_extractor.py` | `data_engineering/novelty_extractor` | Gemini | `[data_engineering_novelty_extractor]`, `[gemini]` | `GOOGLE_API_KEY` | Runs the novelty extractor with a Gemini function-calling loop. | -| `ollama_novelty_extractor.py` | `data_engineering/novelty_extractor` | Ollama | `[data_engineering_novelty_extractor]`; install `ollama` separately | None | Runs the novelty extractor with local Ollama prompt-mode calls. | -| `gemini_issue_resolver.py` | `dev_tools/issue_resolver` | Gemini | `[dev_tools_issue_resolver]`, `[gemini]` | `GOOGLE_API_KEY`; optional `GITHUB_TOKEN` | Gemini loop for GitHub issue analysis; fetches issue data after `prepare` (sample: issue #123). | -| `ollama_issue_resolver.py` | `dev_tools/issue_resolver` | Ollama | `[dev_tools_issue_resolver]`; install `ollama` separately | optional `GITHUB_TOKEN`; `OLLAMA_MODEL` (default `gemma4:e2b`) | Ollama prompt-mode loop for GitHub issue analysis (sample: issue #123). | -| `token_limiter_loop.py` | `monitoring/token_limiter` | Local execute | `[monitoring_token_limiter]` | None | Simulates a runaway task hitting a token ceiling with deterministic budget checks. | -| `gemini_token_limiter.py` | `monitoring/token_limiter` | Gemini | `[monitoring_token_limiter]`, `[gemini]` | Optional `GOOGLE_API_KEY` for Phase 2 live loop | Local budget simulation plus optional Gemini tool loop. | -| `claude_token_limiter.py` | `monitoring/token_limiter` | Claude | `[monitoring_token_limiter]`, `[claude]` | Optional `ANTHROPIC_API_KEY` for Phase 2 live loop | Local budget simulation plus optional Claude tool loop. | -| `gemini_uk_companies_house_handler.py` | `finance/uk_companies_house_handler` | Gemini | `[finance_uk_companies_house_handler]`, `[gemini]` | `GOOGLE_API_KEY`, `COMPANIES_HOUSE_API_KEY` | Resolve company, officers, filings via Gemini tool loop. | -| `uk_companies_house_handler_demo.py` | `finance/uk_companies_house_handler` | Local execute | `[finance_uk_companies_house_handler]` | None | Runs a scripted sequence (resolve, profile, officers, pscs, filings) with mocked HTTP responses (no API keys needed). | - -## Notes - -- **Skill extras** are listed per script above. Empty extras (`[]` in `pyproject.toml`) still install core + skillware; use them so docs stay stable when a skill gains new runtime deps. -- Agent-side model keys such as `GOOGLE_API_KEY`, `ANTHROPIC_API_KEY`, - `OPENAI_API_KEY`, and `DEEPSEEK_API_KEY` are documented in the provider - guides. -- Skill runtime keys such as `ETHERSCAN_API_KEY` are documented in each skill - manifest and on the skill catalog pages. -- Ollama examples require the Python `ollama` package, a local Ollama server, - and the model named in the script, but no cloud API key. +| `pii_guardrail_flow.py` | `compliance/pii_masker` | Local execute | `[compliance_pii_masker]` | None | Demonstrates local PII masking before passing text to an external agent. | +| `prompt_injection_firewall_demo.py` | `security/prompt_injection_firewall` | Local execute | `[security_prompt_injection_firewall]` | None | Offline prompt-injection scan and sanitization with no API keys. | +| `prompt_compression_demo.py` | `optimization/prompt_rewriter` | Local execute | `[optimization_prompt_rewriter]` | None | Demonstrates prompt compression without a provider loop. | +| `novelty_extractor_demo.py` | `data_engineering/novelty_extractor` | Local execute | `[data_engineering_novelty_extractor]` | None | Demonstrates multi-turn corpus distillation using local embeddings with no API key. | +| `gemini_novelty_extractor.py` | `data_engineering/novelty_extractor` | Gemini | `[data_engineering_novelty_extractor]`, `[gemini]` | `GOOGLE_API_KEY` | Runs the novelty extractor with a Gemini function-calling loop. | +| `ollama_novelty_extractor.py` | `data_engineering/novelty_extractor` | Ollama | `[data_engineering_novelty_extractor]`; install `ollama` separately | None | Runs the novelty extractor with local Ollama prompt-mode calls. | +| `gemini_issue_resolver.py` | `dev_tools/issue_resolver` | Gemini | `[dev_tools_issue_resolver]`, `[gemini]` | `GOOGLE_API_KEY`; optional `GITHUB_TOKEN` | Gemini loop for GitHub issue analysis; fetches issue data after `prepare` (sample: issue #123). | +| `ollama_issue_resolver.py` | `dev_tools/issue_resolver` | Ollama | `[dev_tools_issue_resolver]`; install `ollama` separately | optional `GITHUB_TOKEN`; `OLLAMA_MODEL` (default `gemma4:e2b`) | Ollama prompt-mode loop for GitHub issue analysis (sample: issue #123). | +| `token_limiter_loop.py` | `monitoring/token_limiter` | Local execute | `[monitoring_token_limiter]` | None | Simulates a runaway task hitting a token ceiling with deterministic budget checks. | +| `gemini_token_limiter.py` | `monitoring/token_limiter` | Gemini | `[monitoring_token_limiter]`, `[gemini]` | Optional `GOOGLE_API_KEY` for Phase 2 live loop | Local budget simulation plus optional Gemini tool loop. | +| `claude_token_limiter.py` | `monitoring/token_limiter` | Claude | `[monitoring_token_limiter]`, `[claude]` | Optional `ANTHROPIC_API_KEY` for Phase 2 live loop | Local budget simulation plus optional Claude tool loop. | +| `gemini_uk_companies_house_handler.py` | `finance/uk_companies_house_handler` | Gemini | `[finance_uk_companies_house_handler]`, `[gemini]` | `GOOGLE_API_KEY`, `COMPANIES_HOUSE_API_KEY` | Resolve company, officers, filings via Gemini tool loop. | +| `uk_companies_house_handler_demo.py` | `finance/uk_companies_house_handler` | Local execute | `[finance_uk_companies_house_handler]` | None | Runs a scripted sequence (resolve, profile, officers, pscs, filings) with mocked HTTP responses (no API keys needed). | +| `bg_remover_demo.py` | `creative/bg_remover` | Local execute | `[creative_bg_remover]` | None | Demonstrates offline background removal from a local image and optionally writes a transparent PNG. | + +## Notes + +- **Skill extras** are listed per script above. Empty extras (`[]` in `pyproject.toml`) still install core + skillware; use them so docs stay stable when a skill gains new runtime deps. +- Agent-side model keys such as `GOOGLE_API_KEY`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and `DEEPSEEK_API_KEY` are documented in the provider guides. +- Skill runtime keys such as `ETHERSCAN_API_KEY` are documented in each skill manifest and on the skill catalog pages. +- Ollama examples require the Python `ollama` package, a local Ollama server, and the model named in the script, but no cloud API key. diff --git a/examples/bg_remover_demo.py b/examples/bg_remover_demo.py new file mode 100644 index 0000000..5a58c4a --- /dev/null +++ b/examples/bg_remover_demo.py @@ -0,0 +1,42 @@ +import base64 +from pathlib import Path + +from skillware.core.loader import SkillLoader + + +def run_demo(): + print("Loading Background Remover...") + + skill_bundle = SkillLoader.load_skill("creative/bg_remover") + skill_instance = skill_bundle["class"]() + + input_path = "examples/sample_input.png" + output_path = "examples/sample_output_no_bg.png" + + if not Path(input_path).exists(): + print(f"Input image not found: {input_path}") + print("Place a sample PNG at the above path before running this demo.") + return + + print(f"Removing background from: {input_path}") + + result = skill_instance.execute( + { + "input_path": input_path, + "output_path": output_path, + } + ) + + if result["success"]: + print("Background removed successfully!") + print(f"Saved to: {result['output_path']}") + print(f"Output size: {result['width']} x {result['height']}") + print(f"Model: {result['model_used']}") + print(f"Base64 length: {len(result['image_base64'])}") + else: + print(f"Failed: {result['error']}") + print(f"Error code: {result['error_code']}") + + +if __name__ == "__main__": + run_demo() \ No newline at end of file diff --git a/skills/creative/bg_remover/instructions.md b/skills/creative/bg_remover/instructions.md index ac952c8..82ca5ea 100644 --- a/skills/creative/bg_remover/instructions.md +++ b/skills/creative/bg_remover/instructions.md @@ -52,6 +52,7 @@ If **both** `image` and `input_path` are sent, **`image` wins** (do not double-s - Empty image files are rejected. - Images larger than **25 MB** are rejected. - Image integrity is validated before processing. +- output_path must not contain path traversal components (for example `..`). ### Example payloads diff --git a/skills/creative/bg_remover/manifest.yaml b/skills/creative/bg_remover/manifest.yaml index 0160641..c4e61c9 100644 --- a/skills/creative/bg_remover/manifest.yaml +++ b/skills/creative/bg_remover/manifest.yaml @@ -77,7 +77,7 @@ outputs: error_code: type: string - description: "INVALID_INPUT, MISSING_DEPENDENCY, or PROCESSING_FAILED." + description: "INVALID_INPUT, FILE_NOT_FOUND, MISSING_DEPENDENCY, or PROCESSING_FAILED." requirements: - rembg>=2.0.0 diff --git a/skills/creative/bg_remover/skill.py b/skills/creative/bg_remover/skill.py index 0aa237f..953ca96 100644 --- a/skills/creative/bg_remover/skill.py +++ b/skills/creative/bg_remover/skill.py @@ -23,6 +23,15 @@ def _get_session(cls, model: str): return cls._sessions[model] + @staticmethod + def _validate_output_path(output_path: str) -> Path: + output = Path(output_path) + + if ".." in output.parts: + raise ValueError("Unsafe output_path contains path traversal.") + + return output + @property def manifest(self) -> Dict[str, Any]: return { @@ -111,6 +120,9 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: try: image = Image.open(io.BytesIO(image_bytes)) image.verify() + + # Reopen after verify() because verify() leaves the image unusable. + image = Image.open(io.BytesIO(image_bytes)) except Exception: return { "success": False, @@ -137,7 +149,15 @@ def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: # Optional save if output_path: - output_file = Path(output_path) + try: + output_file = self._validate_output_path(output_path) + except ValueError: + return { + "success": False, + "error": "Unsafe output_path.", + "error_code": "INVALID_INPUT", + } + output_file.parent.mkdir(parents=True, exist_ok=True) output_file.write_bytes(buffer.getvalue()) diff --git a/skills/creative/bg_remover/test_skill.py b/skills/creative/bg_remover/test_skill.py index 16695d5..90ebd0c 100644 --- a/skills/creative/bg_remover/test_skill.py +++ b/skills/creative/bg_remover/test_skill.py @@ -326,3 +326,14 @@ def fake_new_session(model_name="u2net", *args, **kwargs): assert calls["count"] == 1 BackgroundRemover._sessions.clear() + +def test_unsafe_output_path(skill): + result = skill.execute( + { + "image": create_image(), + "output_path": "../../../evil.png", + } + ) + + assert result["success"] is False + assert result["error_code"] == "INVALID_INPUT" From 6ffa76c6d7e02d47207e7389d2e336688db27044 Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Wed, 29 Jul 2026 16:55:48 +0530 Subject: [PATCH 04/10] style(bg_remover): apply black formatting --- examples/bg_remover_demo.py | 2 +- skills/creative/bg_remover/test_skill.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/bg_remover_demo.py b/examples/bg_remover_demo.py index 5a58c4a..202968a 100644 --- a/examples/bg_remover_demo.py +++ b/examples/bg_remover_demo.py @@ -39,4 +39,4 @@ def run_demo(): if __name__ == "__main__": - run_demo() \ No newline at end of file + run_demo() diff --git a/skills/creative/bg_remover/test_skill.py b/skills/creative/bg_remover/test_skill.py index 90ebd0c..95ee92a 100644 --- a/skills/creative/bg_remover/test_skill.py +++ b/skills/creative/bg_remover/test_skill.py @@ -327,6 +327,7 @@ def fake_new_session(model_name="u2net", *args, **kwargs): BackgroundRemover._sessions.clear() + def test_unsafe_output_path(skill): result = skill.execute( { From 25f0bd7d2b95a56f130ff5289dba4e49ac466988 Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Wed, 29 Jul 2026 16:59:37 +0530 Subject: [PATCH 05/10] fix(bg_remover): remove unused import --- examples/bg_remover_demo.py | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/bg_remover_demo.py b/examples/bg_remover_demo.py index 202968a..e2d9901 100644 --- a/examples/bg_remover_demo.py +++ b/examples/bg_remover_demo.py @@ -1,4 +1,3 @@ -import base64 from pathlib import Path from skillware.core.loader import SkillLoader From 6d7c8d6ad394a0576b4e31826217c953309d1ba8 Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Fri, 31 Jul 2026 11:58:49 +0530 Subject: [PATCH 06/10] fix(bg_remover): address review feedback --- CHANGELOG.md | 9 +++++---- docs/skills/bg_remover.md | 4 ++-- examples/README.md | 3 +++ skills/creative/bg_remover/instructions.md | 1 + skills/creative/bg_remover/test_skill.py | 12 +++++++++++- 5 files changed, 22 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0af4e1c..f70666e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,16 +13,17 @@ Contributors add user-facing entries under `[Unreleased]` in the same PR. Mainta - **Citation:** Zenodo concept DOI `10.5281/zenodo.21552745` in `CITATION.cff`, README badge/Citing section, and `pyproject.toml` project URL (#269). - **Skill:** `security/prompt_injection_firewall` — offline-only deterministic pre-flight scanner (no LLM path) with local `kb/` detectors for hidden text, Unicode/confusable evasion, nested encodings, instruction overrides, corroboration-based sensitivity, and sanitization output (#46). + +### Changed + +- **`creative/bg_remover`**: Hardened the skill by reusing rembg sessions across executions, validating Base64 input and image files, rejecting empty, oversized (>25 MB), or invalid images, and automatically creating parent directories for `output_path`. Updated documentation and expanded bundle tests for the new validation behavior. + ## [0.4.7] - 2026-07-25 ### Added - **Citation:** Root `CITATION.cff` and README **Citing** section for formal software citation; first Zenodo-archived GitHub release (#269, #270). -### Changed - -- **`creative/bg_remover`**: Hardened the skill by reusing rembg sessions across executions, validating Base64 input and image files, rejecting empty, oversized (>25 MB), or invalid images, and automatically creating parent directories for `output_path`. Updated documentation and expanded bundle tests for the new validation behavior. - ## [0.4.6] - 2026-07-23 ### Added diff --git a/docs/skills/bg_remover.md b/docs/skills/bg_remover.md index d8e1960..3aed6a4 100644 --- a/docs/skills/bg_remover.md +++ b/docs/skills/bg_remover.md @@ -74,7 +74,7 @@ Agent routing details live in `skills/creative/bg_remover/instructions.md`. | `height` | Output image height in pixels | | `model_used` | rembg model used for processing | | `error` | Human-readable error message when `success` is `false` | -| `error_code` | `INVALID_INPUT`, `MISSING_DEPENDENCY`, or `PROCESSING_FAILED` | +| `error_code` | `INVALID_INPUT`, `FILE_NOT_FOUND`, `MISSING_DEPENDENCY`, or `PROCESSING_FAILED` | ## Input scenarios @@ -133,7 +133,7 @@ Use `bundle["class"]()` in the snippets below; explicit `bundle["module"].ClassN ### Runnable examples -See [examples/README.md](../../examples/README.md) for the current runnable-script inventory. There is no dedicated runnable example for this skill yet; the Claude, OpenAI, and DeepSeek sections below are **catalog snippets only** (same pattern as [PDF Form Filler](pdf_form_filler.md)). +See [examples/README.md](../../examples/README.md) for the current runnable-script inventory. See `examples/bg_remover_demo.py` for a runnable local example. The Claude, OpenAI, and DeepSeek sections below remain catalog snippets showing provider integration patterns. Sample user request: diff --git a/examples/README.md b/examples/README.md index 9f8f716..4968157 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,7 +2,9 @@ > **These are usage examples, not tests.** Runnable provider demos live here; automated tests live in `skills/**/test_skill.py` (bundle) and `tests/` (framework and optional maintainer depth). See [TESTING.md](../docs/TESTING.md). + Runnable examples in this directory show how to load Skillware skills, adapt them for a provider, execute local skill logic, and return tool results to an agent loop. After `pip install skillware`, run `skillware examples` or `skillware list --examples` from any directory to browse the index in the terminal; when no local `examples/README.md` is present, the index is fetched from GitHub (network required); see [CLI reference](../docs/usage/cli.md). Provider setup details live in the usage guides: + - [API keys for skills](../docs/usage/api_keys.md) - [Gemini](../docs/usage/gemini.md) - [Claude](../docs/usage/claude.md) @@ -67,3 +69,4 @@ pip install -e ".[dev,all,agents]" - Agent-side model keys such as `GOOGLE_API_KEY`, `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, and `DEEPSEEK_API_KEY` are documented in the provider guides. - Skill runtime keys such as `ETHERSCAN_API_KEY` are documented in each skill manifest and on the skill catalog pages. - Ollama examples require the Python `ollama` package, a local Ollama server, and the model named in the script, but no cloud API key. + diff --git a/skills/creative/bg_remover/instructions.md b/skills/creative/bg_remover/instructions.md index 82ca5ea..564e495 100644 --- a/skills/creative/bg_remover/instructions.md +++ b/skills/creative/bg_remover/instructions.md @@ -131,3 +131,4 @@ Subsequent executions reuse cached rembg sessions for the selected model, reduci | `INVALID_INPUT` | Invalid Base64, missing input, directory path, empty image, oversized image (>25 MB), corrupt image, or unsupported input. | | `MISSING_DEPENDENCY` | Ask the user to run `pip install "skillware[creative_bg_remover]"` (or `pip install rembg pillow onnxruntime`), then retry. | | `PROCESSING_FAILED` | Surface the `error` string; input may be missing, corrupt, or unsupported. | +| `FILE_NOT_FOUND` | The supplied `input_path` does not exist. Ask the user to verify the path and try again. | \ No newline at end of file diff --git a/skills/creative/bg_remover/test_skill.py b/skills/creative/bg_remover/test_skill.py index 95ee92a..b0cb7f9 100644 --- a/skills/creative/bg_remover/test_skill.py +++ b/skills/creative/bg_remover/test_skill.py @@ -9,7 +9,7 @@ from skillware.core.loader import SkillLoader -from .skill import BackgroundRemover +from .skill import BackgroundRemover, MAX_IMAGE_BYTES import sys import types @@ -338,3 +338,13 @@ def test_unsafe_output_path(skill): assert result["success"] is False assert result["error_code"] == "INVALID_INPUT" + +def test_oversized_base64_image(skill): + oversized = base64.b64encode( + b"\0" * (MAX_IMAGE_BYTES + 1) + ).decode() + + result = skill.execute({"image": oversized}) + + assert result["success"] is False + assert result["error_code"] == "INVALID_INPUT" \ No newline at end of file From 1b1c8a249f38ef5434f8212dd235aa0218e7abcc Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Fri, 31 Jul 2026 12:02:47 +0530 Subject: [PATCH 07/10] fix(bg_remover): address review feedback --- skills/creative/bg_remover/test_skill.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/skills/creative/bg_remover/test_skill.py b/skills/creative/bg_remover/test_skill.py index b0cb7f9..9d141e2 100644 --- a/skills/creative/bg_remover/test_skill.py +++ b/skills/creative/bg_remover/test_skill.py @@ -339,12 +339,11 @@ def test_unsafe_output_path(skill): assert result["success"] is False assert result["error_code"] == "INVALID_INPUT" + def test_oversized_base64_image(skill): - oversized = base64.b64encode( - b"\0" * (MAX_IMAGE_BYTES + 1) - ).decode() + oversized = base64.b64encode(b"\0" * (MAX_IMAGE_BYTES + 1)).decode() result = skill.execute({"image": oversized}) assert result["success"] is False - assert result["error_code"] == "INVALID_INPUT" \ No newline at end of file + assert result["error_code"] == "INVALID_INPUT" From 0ba9948eac68cb9df80b8118c0059253a090a836 Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Fri, 31 Jul 2026 12:25:15 +0530 Subject: [PATCH 08/10] fix(bg_remover): address review feedback --- examples/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/README.md b/examples/README.md index 4968157..f46d28d 100644 --- a/examples/README.md +++ b/examples/README.md @@ -2,7 +2,6 @@ > **These are usage examples, not tests.** Runnable provider demos live here; automated tests live in `skills/**/test_skill.py` (bundle) and `tests/` (framework and optional maintainer depth). See [TESTING.md](../docs/TESTING.md). - Runnable examples in this directory show how to load Skillware skills, adapt them for a provider, execute local skill logic, and return tool results to an agent loop. After `pip install skillware`, run `skillware examples` or `skillware list --examples` from any directory to browse the index in the terminal; when no local `examples/README.md` is present, the index is fetched from GitHub (network required); see [CLI reference](../docs/usage/cli.md). Provider setup details live in the usage guides: - [API keys for skills](../docs/usage/api_keys.md) @@ -22,6 +21,7 @@ pip install "skillware[office_pdf_form_filler]" "skillware[gemini]" ``` For local development with every skill and SDK: + ```bash pip install -e ".[dev,all,agents]" ``` @@ -62,6 +62,7 @@ pip install -e ".[dev,all,agents]" | `gemini_uk_companies_house_handler.py` | `finance/uk_companies_house_handler` | Gemini | `[finance_uk_companies_house_handler]`, `[gemini]` | `GOOGLE_API_KEY`, `COMPANIES_HOUSE_API_KEY` | Resolve company, officers, filings via Gemini tool loop. | | `uk_companies_house_handler_demo.py` | `finance/uk_companies_house_handler` | Local execute | `[finance_uk_companies_house_handler]` | None | Runs a scripted sequence (resolve, profile, officers, pscs, filings) with mocked HTTP responses (no API keys needed). | | `bg_remover_demo.py` | `creative/bg_remover` | Local execute | `[creative_bg_remover]` | None | Demonstrates offline background removal from a local image and optionally writes a transparent PNG. | + (fix(bg_remover): address review feedback) ## Notes From 4f084df377de54c78c26c89bfccc23d003948d0c Mon Sep 17 00:00:00 2001 From: AyushSrivastava1818 Date: Fri, 31 Jul 2026 20:21:55 +0530 Subject: [PATCH 09/10] fix(bg_remover): address final review feedback --- docs/usage/agent_loops.md | 1 + skills/creative/bg_remover/instructions.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/usage/agent_loops.md b/docs/usage/agent_loops.md index ceba8b4..3a9004d 100644 --- a/docs/usage/agent_loops.md +++ b/docs/usage/agent_loops.md @@ -108,3 +108,4 @@ skills in one harness. | `defi/evm_tx_handler` | - | `gemini_evm_tx_handler.py` | `claude_evm_tx_handler.py` | - | - | - | | `monitoring/token_limiter` | `token_limiter_loop.py` (local execute) | `gemini_token_limiter.py` | `claude_token_limiter.py` | (catalog page) | (catalog page) | (catalog page) | | `finance/uk_companies_house_handler` | `uk_companies_house_handler_demo.py` | `gemini_uk_companies_house_handler.py` | (catalog page) | (catalog page) | (catalog page) | (catalog page) | + diff --git a/skills/creative/bg_remover/instructions.md b/skills/creative/bg_remover/instructions.md index 564e495..1846732 100644 --- a/skills/creative/bg_remover/instructions.md +++ b/skills/creative/bg_remover/instructions.md @@ -131,4 +131,4 @@ Subsequent executions reuse cached rembg sessions for the selected model, reduci | `INVALID_INPUT` | Invalid Base64, missing input, directory path, empty image, oversized image (>25 MB), corrupt image, or unsupported input. | | `MISSING_DEPENDENCY` | Ask the user to run `pip install "skillware[creative_bg_remover]"` (or `pip install rembg pillow onnxruntime`), then retry. | | `PROCESSING_FAILED` | Surface the `error` string; input may be missing, corrupt, or unsupported. | -| `FILE_NOT_FOUND` | The supplied `input_path` does not exist. Ask the user to verify the path and try again. | \ No newline at end of file +| `FILE_NOT_FOUND` | The supplied `input_path` does not exist. Ask the user to verify the path and try again. | From b211b9c7efbbb12df20073d31c609cb64f262705 Mon Sep 17 00:00:00 2001 From: rosspeili Date: Sun, 2 Aug 2026 13:03:53 +0300 Subject: [PATCH 10/10] chore(bg_remover): maintainer polish for #268 Remove accidental commit-message line from examples/README.md and load manifest metadata from manifest.yaml to avoid version drift. --- examples/README.md | 1 - skills/creative/bg_remover/skill.py | 16 ++++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/examples/README.md b/examples/README.md index f46d28d..97ec514 100644 --- a/examples/README.md +++ b/examples/README.md @@ -62,7 +62,6 @@ pip install -e ".[dev,all,agents]" | `gemini_uk_companies_house_handler.py` | `finance/uk_companies_house_handler` | Gemini | `[finance_uk_companies_house_handler]`, `[gemini]` | `GOOGLE_API_KEY`, `COMPANIES_HOUSE_API_KEY` | Resolve company, officers, filings via Gemini tool loop. | | `uk_companies_house_handler_demo.py` | `finance/uk_companies_house_handler` | Local execute | `[finance_uk_companies_house_handler]` | None | Runs a scripted sequence (resolve, profile, officers, pscs, filings) with mocked HTTP responses (no API keys needed). | | `bg_remover_demo.py` | `creative/bg_remover` | Local execute | `[creative_bg_remover]` | None | Demonstrates offline background removal from a local image and optionally writes a transparent PNG. | - (fix(bg_remover): address review feedback) ## Notes diff --git a/skills/creative/bg_remover/skill.py b/skills/creative/bg_remover/skill.py index 953ca96..920af3f 100644 --- a/skills/creative/bg_remover/skill.py +++ b/skills/creative/bg_remover/skill.py @@ -1,5 +1,6 @@ import base64 import io +import os from pathlib import Path from typing import Any, Dict @@ -34,14 +35,13 @@ def _validate_output_path(output_path: str) -> Path: @property def manifest(self) -> Dict[str, Any]: - return { - "name": "creative/bg_remover", - "version": "0.2.0", - "description": ( - "Remove image backgrounds locally using rembg " - "and return a transparent PNG." - ), - } + manifest_path = os.path.join(os.path.dirname(__file__), "manifest.yaml") + if os.path.exists(manifest_path): + import yaml + + with open(manifest_path, "r", encoding="utf-8") as f: + return yaml.safe_load(f) + return {} def execute(self, params: Dict[str, Any]) -> Dict[str, Any]: """Execute background removal."""