Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions skills/claude-api/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
name: claude-api
description: |-
Reference for the Claude API / Anthropic SDK — model ids, pricing, params, streaming, tool use, MCP, agents, caching, token counting, model migration.
TRIGGER — read BEFORE opening the target file; don't skip because it "looks like a one-liner" — whenever: the prompt names Claude/Anthropic in any form (Claude, Anthropic, Fable, Opus, Sonnet, Haiku, `anthropic`, `@anthropic-ai`, `claude-*`, `us.anthropic.*`, `[1m]`); the user asks about an LLM (pricing/model choice/limits/caching) — never answer from memory; OR the task is LLM-shaped with provider unstated (agent/MCP/tool-definition/multi-agent/RAG/LLM-judge/computer-use; generate/summarize/extract/classify/rewrite/converse over NL; debugging refusals/cutoffs/streaming/tool-calls/tokens).
SKIP only when another provider is being worked on (overrides all triggers): OpenAI/GPT/Gemini/Llama/Mistral/Cohere/Ollama named in the query; OR `grep -rE 'openai|langchain_openai|google.generativeai|genai|mistralai|cohere|ollama'` over the project hits (run this grep FIRST if no provider named — don't Read the file).
TRIGGER — read BEFORE opening target file: prompt names Claude/Anthropic in any form (Claude, Anthropic, Fable, Opus, Sonnet, Haiku, `anthropic`, `@anthropic-ai`, `claude-*`, `us.anthropic.*`, `[1m]`); user asks about an LLM (pricing/model choice/limits/caching) — never answer from memory; OR task is LLM-shaped with provider unstated (agent/MCP/tool-definition/multi-agent/RAG/LLM-judge/computer-use; generate/summarize/extract/classify/rewrite/converse over NL; debugging refusals/cutoffs/streaming/tool-calls/tokens).
SKIP only when another provider is being worked on: OpenAI/GPT/Gemini/Llama/Mistral/Cohere/Ollama named in query; OR `grep -rE 'openai|langchain_openai|google.generativeai|genai|mistralai|cohere|ollama'` over project hits (run grep FIRST if no provider named).
license: Complete terms in LICENSE.txt
---

Expand Down
7 changes: 4 additions & 3 deletions skills/docx/scripts/accept_changes.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
import logging
import shutil
import subprocess
import tempfile
from pathlib import Path

from office.soffice import get_soffice_env

logger = logging.getLogger(__name__)

LIBREOFFICE_PROFILE = "/tmp/libreoffice_docx_profile"
LIBREOFFICE_PROFILE = str(Path(tempfile.gettempdir()) / "libreoffice_docx_profile")
MACRO_DIR = f"{LIBREOFFICE_PROFILE}/user/basic/Standard"

ACCEPT_CHANGES_MACRO = """<?xml version="1.0" encoding="UTF-8"?>
Expand Down Expand Up @@ -58,7 +59,7 @@ def accept_changes(
cmd = [
"soffice",
"--headless",
f"-env:UserInstallation=file://{LIBREOFFICE_PROFILE}",
f"-env:UserInstallation={Path(LIBREOFFICE_PROFILE).as_uri()}",
"--norestore",
"vnd.sun.star.script:Standard.Module1.AcceptAllTrackedChanges?language=Basic&location=application",
str(output_path.absolute()),
Expand All @@ -76,7 +77,7 @@ def accept_changes(
except subprocess.TimeoutExpired:
return (
None,
f"Successfully accepted all tracked changes: {input_file} -> {output_file}",
f"Error: LibreOffice timed out while processing: {input_file}",
)

if result.returncode != 0:
Expand Down
5 changes: 4 additions & 1 deletion skills/docx/scripts/office/soffice.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import contextlib
import os
import platform
import socket
import subprocess
import tempfile
Expand Down Expand Up @@ -51,11 +52,13 @@ def run_soffice(args: Iterable[str], **kwargs) -> subprocess.CompletedProcess:


def _needs_shim() -> bool:
if platform.system() != "Linux":
return False
try:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.close()
return False
except OSError:
except (OSError, AttributeError):
return True


Expand Down
30 changes: 25 additions & 5 deletions skills/mcp-builder/scripts/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,25 @@ def extract_xml_content(text: str, tag: str) -> str | None:
return matches[-1].strip() if matches else None


def _serialize_tool_result(tool_result: Any) -> str:
"""Serialize MCP tool execution results into clean text for Claude."""
if hasattr(tool_result, "content"):
tool_result = getattr(tool_result, "content")
if isinstance(tool_result, list):
parts = []
for block in tool_result:
if hasattr(block, "text"):
parts.append(getattr(block, "text") or "")
elif isinstance(block, dict) and "text" in block:
parts.append(str(block.get("text") or ""))
else:
parts.append(str(block))
return "\n".join(parts)
elif isinstance(tool_result, dict):
return json.dumps(tool_result, indent=2)
return str(tool_result)


async def agent_loop(
client: Anthropic,
model: str,
Expand Down Expand Up @@ -114,7 +133,7 @@ async def agent_loop(
tool_start_ts = time.time()
try:
tool_result = await connection.call_tool(tool_name, tool_input)
tool_response = json.dumps(tool_result) if isinstance(tool_result, (dict, list)) else str(tool_result)
tool_response = _serialize_tool_result(tool_result)
except Exception as e:
tool_response = f"Error executing tool {tool_name}: {str(e)}\n"
tool_response += traceback.format_exc()
Expand Down Expand Up @@ -144,10 +163,11 @@ async def agent_loop(
)
messages.append({"role": "assistant", "content": response.content})

response_text = next(
(block.text for block in response.content if hasattr(block, "text")),
None,
)
text_blocks = [
block.text for block in response.content
if getattr(block, "type", None) == "text" or hasattr(block, "text")
]
response_text = "".join(text_blocks).strip() if text_blocks else None
return response_text, tool_metrics


Expand Down
1 change: 1 addition & 0 deletions skills/pdf/scripts/convert_pdf_to_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@


def convert(pdf_path, output_dir, max_dim=1000):
os.makedirs(output_dir, exist_ok=True)
images = convert_from_path(pdf_path, dpi=200)

for i, image in enumerate(images):
Expand Down
4 changes: 2 additions & 2 deletions skills/pdf/scripts/extract_form_field_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def make_field_dict(field, field_id):


def get_field_info(reader: PdfReader):
fields = reader.get_fields()
fields = reader.get_fields() or {}

field_info_by_id = {}
possible_radio_names = set()
Expand Down Expand Up @@ -110,7 +110,7 @@ def sort_key(f):
def write_field_info(pdf_path: str, json_output_path: str):
reader = PdfReader(pdf_path)
field_info = get_field_info(reader)
with open(json_output_path, "w") as f:
with open(json_output_path, "w", encoding="utf-8") as f:
json.dump(field_info, f, indent=2)
print(f"Wrote {len(field_info)} fields to {json_output_path}")

Expand Down
6 changes: 5 additions & 1 deletion skills/pptx/scripts/office/soffice.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,19 @@ def run_soffice(args: Iterable[str], **kwargs) -> subprocess.CompletedProcess:



import platform

_SHIM_SO = Path(tempfile.gettempdir()) / "lo_socket_shim.so"


def _needs_shim() -> bool:
if platform.system() != "Linux":
return False
try:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.close()
return False
except OSError:
except (OSError, AttributeError):
return True


Expand Down
3 changes: 2 additions & 1 deletion skills/skill-creator/eval-viewer/generate_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ def generate_html(
if benchmark:
embedded["benchmark"] = benchmark

data_json = json.dumps(embedded)
# Prevent </script in embedded data from prematurely closing inline script tag
data_json = json.dumps(embedded).replace("</", "<\\/")

return template.replace("/*__EMBEDDED_DATA__*/", f"const EMBEDDED_DATA = {data_json};")

Expand Down
20 changes: 12 additions & 8 deletions skills/skill-creator/eval-viewer/viewer.html
Original file line number Diff line number Diff line change
Expand Up @@ -1097,9 +1097,13 @@ <h2>Review Complete</h2>
}

function escapeHtml(text) {
const div = document.createElement("div");
div.textContent = text;
return div.innerHTML;
if (text == null) return "";
return String(text)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}

// ---- View switching ----
Expand Down Expand Up @@ -1129,9 +1133,9 @@ <h2>Review Complete</h2>
html += "<h2 style='font-family: Poppins, sans-serif; margin-bottom: 0.5rem;'>Benchmark Results</h2>";
html += "<p style='color: var(--text-muted); font-size: 0.875rem; margin-bottom: 1.25rem;'>";
if (metadata.skill_name) html += "<strong>" + escapeHtml(metadata.skill_name) + "</strong> &mdash; ";
if (metadata.timestamp) html += metadata.timestamp + " &mdash; ";
if (metadata.evals_run) html += "Evals: " + metadata.evals_run.join(", ") + " &mdash; ";
html += (metadata.runs_per_configuration || "?") + " runs per configuration";
if (metadata.timestamp) html += escapeHtml(metadata.timestamp) + " &mdash; ";
if (metadata.evals_run) html += "Evals: " + escapeHtml(metadata.evals_run.join(", ")) + " &mdash; ";
html += escapeHtml(String(metadata.runs_per_configuration || "?")) + " runs per configuration";
html += "</p>";

// Summary table
Expand Down Expand Up @@ -1225,7 +1229,7 @@ <h2>Review Complete</h2>
const r = run.result || {};
const prClass = r.pass_rate >= 0.8 ? "benchmark-delta-positive" : r.pass_rate < 0.5 ? "benchmark-delta-negative" : "";
html += '<tr class="' + rowClass + '">';
html += "<td>" + configLabel + "</td>";
html += "<td>" + escapeHtml(configLabel) + "</td>";
html += "<td>" + run.run_number + "</td>";
html += '<td class="' + prClass + '">' + ((r.pass_rate || 0) * 100).toFixed(0) + "% (" + (r.passed || 0) + "/" + (r.total || 0) + ")</td>";
if (hasTime) html += "<td>" + (r.time_seconds != null ? r.time_seconds.toFixed(1) : "—") + "</td>";
Expand All @@ -1238,7 +1242,7 @@ <h2>Review Complete</h2>
const avgRate = rates.reduce((a, b) => a + b, 0) / rates.length;
const avgPrClass = avgRate >= 0.8 ? "benchmark-delta-positive" : avgRate < 0.5 ? "benchmark-delta-negative" : "";
html += '<tr class="benchmark-row-avg ' + rowClass + '">';
html += "<td>" + configLabel + "</td>";
html += "<td>" + escapeHtml(configLabel) + "</td>";
html += "<td>Avg</td>";
html += '<td class="' + avgPrClass + '">' + (avgRate * 100).toFixed(0) + "%</td>";
if (hasTime) {
Expand Down
26 changes: 18 additions & 8 deletions skills/skill-creator/scripts/aggregate_benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,13 @@ def load_run_results(benchmark_dir: Path) -> dict:

for eval_idx, eval_dir in enumerate(sorted(search_dir.glob("eval-*"))):
metadata_path = eval_dir / "eval_metadata.json"
eval_name = None
if metadata_path.exists():
try:
with open(metadata_path) as mf:
eval_id = json.load(mf).get("eval_id", eval_idx)
with open(metadata_path, encoding="utf-8") as mf:
meta = json.load(mf)
eval_id = meta.get("eval_id", eval_idx)
eval_name = meta.get("eval_name")
except (json.JSONDecodeError, OSError):
eval_id = eval_idx
else:
Expand Down Expand Up @@ -117,7 +120,7 @@ def load_run_results(benchmark_dir: Path) -> dict:
continue

try:
with open(grading_file) as f:
with open(grading_file, encoding="utf-8") as f:
grading = json.load(f)
except json.JSONDecodeError as e:
print(f"Warning: Invalid JSON in {grading_file}: {e}")
Expand All @@ -126,6 +129,7 @@ def load_run_results(benchmark_dir: Path) -> dict:
# Extract metrics
result = {
"eval_id": eval_id,
"eval_name": eval_name or f"Eval {eval_id}",
"run_number": run_number,
"pass_rate": grading.get("summary", {}).get("pass_rate", 0.0),
"passed": grading.get("summary", {}).get("passed", 0),
Expand All @@ -139,7 +143,7 @@ def load_run_results(benchmark_dir: Path) -> dict:
timing_file = run_dir / "timing.json"
if result["time_seconds"] == 0.0 and timing_file.exists():
try:
with open(timing_file) as tf:
with open(timing_file, encoding="utf-8") as tf:
timing_data = json.load(tf)
result["time_seconds"] = timing_data.get("total_duration_seconds", 0.0)
result["tokens"] = timing_data.get("total_tokens", 0)
Expand Down Expand Up @@ -205,8 +209,13 @@ def aggregate_results(results: dict) -> dict:

# Calculate delta between the first two configs (if two exist)
if len(configs) >= 2:
primary = run_summary.get(configs[0], {})
baseline = run_summary.get(configs[1], {})
primary_keys = [c for c in configs if c in ("with_skill", "new_skill")]
baseline_keys = [c for c in configs if c in ("without_skill", "old_skill")]
primary_name = primary_keys[0] if primary_keys else configs[0]
remaining = [c for c in configs if c != primary_name]
baseline_name = baseline_keys[0] if baseline_keys and baseline_keys[0] != primary_name else (remaining[0] if remaining else configs[1])
primary = run_summary.get(primary_name, {})
baseline = run_summary.get(baseline_name, {})
else:
primary = run_summary.get(configs[0], {}) if configs else {}
baseline = {}
Expand Down Expand Up @@ -237,6 +246,7 @@ def generate_benchmark(benchmark_dir: Path, skill_name: str = "", skill_path: st
for result in results[config]:
runs.append({
"eval_id": result["eval_id"],
"eval_name": result.get("eval_name", f"Eval {result['eval_id']}"),
"configuration": config,
"run_number": result["run_number"],
"result": {
Expand Down Expand Up @@ -374,13 +384,13 @@ def main():
output_md = output_json.with_suffix(".md")

# Write benchmark.json
with open(output_json, "w") as f:
with open(output_json, "w", encoding="utf-8") as f:
json.dump(benchmark, f, indent=2)
print(f"Generated: {output_json}")

# Write benchmark.md
markdown = generate_markdown(benchmark)
with open(output_md, "w") as f:
with open(output_md, "w", encoding="utf-8") as f:
f.write(markdown)
print(f"Generated: {output_md}")

Expand Down
17 changes: 10 additions & 7 deletions skills/skill-creator/scripts/package_skill.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@
Skill Packager - Creates a distributable .skill file of a skill folder

Usage:
python utils/package_skill.py <path/to/skill-folder> [output-directory]
python -m scripts.package_skill <path/to/skill-folder> [output-directory]

Example:
python utils/package_skill.py skills/public/my-skill
python utils/package_skill.py skills/public/my-skill ./dist
python -m scripts.package_skill skills/public/my-skill
python -m scripts.package_skill skills/public/my-skill ./dist
"""

import fnmatch
import sys
import zipfile
from pathlib import Path
from scripts.quick_validate import validate_skill
try:
from scripts.quick_validate import validate_skill
except ImportError:
from quick_validate import validate_skill

# Patterns to exclude when packaging skills.
EXCLUDE_DIRS = {"__pycache__", "node_modules"}
Expand Down Expand Up @@ -110,10 +113,10 @@ def package_skill(skill_path, output_dir=None):

def main():
if len(sys.argv) < 2:
print("Usage: python utils/package_skill.py <path/to/skill-folder> [output-directory]")
print("Usage: python -m scripts.package_skill <path/to/skill-folder> [output-directory]")
print("\nExample:")
print(" python utils/package_skill.py skills/public/my-skill")
print(" python utils/package_skill.py skills/public/my-skill ./dist")
print(" python -m scripts.package_skill skills/public/my-skill")
print(" python -m scripts.package_skill skills/public/my-skill ./dist")
sys.exit(1)

skill_path = sys.argv[1]
Expand Down
Loading