⏳ This skill is pending AI review.

Scores will appear once the review pipeline completes.

v1.0.0

api-endpoint-tester

@lethe044⭐ 48 stars

Runs a list of HTTP checks against a base URL from a JSON file and reports status codes, response time and body assertions. Use when the user asks whether an API is up, wants a smoke test after a deploy, or needs to verify that endpoints return what the docs promise.

Choose how to use this skill

You do not need every option. Choose the path your AI client supports. The stable page stays the same; versioned files are immutable.

1. Native installer

This listing has no registered native installer command. Use the complete package or source fallback below, depending on what your client supports.

Do not guess an installer command or replace an existing version without reviewing the diff.

2. Complete package recommended

Download the ZIP when available. It includes SKILL.md plus the references, security notes and version metadata.

No complete ProSkills package is published for this listing yet.

3. Prompt-only

Copy the prompt above when the agent can read the stable page or when you want to adopt the workflow without installing a skill.

Need only the instruction file?

Download SKILL.md only if your client requires a single file. The complete ZIP is safer for a full installation because it preserves the references and release context.

No path installs or executes anything by itself. Your agent still needs access to the project files. Before updating, compare the installed version and review the diff.

—/10

// RATINGS

⭐GitHub Stars
⭐⭐ 48 on GitHubGitHub ↗

Growing

🟢ProSkills Score
—
📍

Not yet listed on ClawHub or SkillsMP

// README

agentskill-kit is a command line toolkit for agent skills, the SKILL.md folders that coding agents such as Claude Code, Cursor, Codex, Gemini CLI and Hermes load when a task matches. It checks a skill before you trust it, generates new ones, and installs the same skill into every agent you use.

  • Lint a skill: name, description, structure. Works offline.
  • Scan it for prompt injection, hidden unicode, secrets and dangerous commands.
  • Test it: run the tests that ship with the skill in an isolated folder.
  • Forge a new skill from a task description with any model, including free and local ones.
  • Mine your shell history for routines that should be skills.
  • Export one skill to Claude Code, Cursor, Windsurf, Copilot, Codex and Gemini CLI.
  • Share skills through a static registry with checksums and a searchable website.

No runtime dependencies. No paid service required. Python 3.9 or newer on Linux, macOS and Windows.

Install

pip install agentskill-toolkit

The PyPI package is named agentskill-toolkit (the shorter name was not available). It installs the askit command and the longer alias agentskill. The repository, the Python module (agentskill_kit) and the commands keep the agentskill-kit name.

Quick start

Check a skill without any API key:

askit lint skills/my-skill
askit scan skills/my-skill
askit test skills/my-skill
askit score skills/my-skill

Create one with a free model (a local Ollama model, or a Groq or Gemini key on the free tier):

askit forge --task "turn meeting notes into action items with owners and due dates"

Use it in your agents:

askit export skills/meeting-notes-to-actions --target claude
askit export skills/meeting-notes-to-actions --target cursor
askit export skills/meeting-notes-to-actions --target all

Install something from the community registry:

askit search docker
askit install dockerfile-linter

Try everything offline with the built-in demo model: askit forge --task "anything" --provider mock.

Why this exists

Skills are plain text that an agent obeys, so they deserve the same care as code you run: a review, a linter, a test and a look at what they do. At the same time every agent wants a slightly different file layout, and writing good skills by hand is slow. agentskill-kit covers both: quality gates for skills, and tools that do the tedious parts.

Commands

CommandWhat it does
askit init NAMECreate a skill from a template, optionally with a script and tests
askit lint [PATH]Check SKILL.md structure (--strict, --format json|github)
askit scan [PATH]Security scan (--fail-on high|medium|low|never)
askit test [PATH]Run tests/cases.json and tests/test_*.py of each skill
askit score [PATH]Combine lint, scan and tests into one score and a pass or fail gate
askit forgeGenerate a skill with a model, verify it and refine it until it passes
askit eval PATHCheck how well a model follows a skill, with an optional baseline run
askit mineFind repeated workflows in shell history and draft skills
askit export PATHConvert and install a skill for another agent
askit search, install, uninstall, listWork with the registry, GitHub folders and local folders
askit pack, publishBundle a skill, run the quality gate, prepare a pull request
askit registry build|siteBuild a registry index or a static website
askit serveBrowse the skills on your machine in a local web page
askit providers, config, doctorModels, settings and environment checks

Every command has --help. Add --lang tr for a Turkish interface.

Free first, paid optional

ProviderCostHow
Ollama, LM Studiofree, localrun a local model, no key
Groqfree tierGROQ_API_KEY
Google Geminifree tierGEMINI_API_KEY
OpenRouterfree modelsOPENROUTER_API_KEY, pick a :free model
Any OpenAI compatible serveryoursASKIT_BASE_URL, ASKIT_MODEL
OpenAI, Anthropicpaid, optionalOPENAI_API_KEY, ANTHROPIC_API_KEY

askit providers shows what is ready. Details in docs/providers.md.

forge does not rely on tool calling. The loop is controlled by code, so small local models work. The model only writes files; lint, scan and tests decide whether they are good enough.

How forge decides

task or notes
      |
      v
  model drafts SKILL.md, scripts and tests/cases.json
      |
      v
  lint + scan + run tests in a clean folder  ->  score
      |                                           |
      |  below threshold: failures go back        |  at or above threshold,
      |  to the model (up to 3 rounds)            |  no high findings
      v                                           v
  best attempt kept                          written to skills/<name>/

The score is not the model's opinion. It comes from real checks. Nothing is written when the best attempt misses the threshold, unless you pass --force.

Mine your history

$ askit mine --file examples/shell-history-sample.txt
-- read 20 commands
Found 1 repeated workflow(s)

#1  git-status-add-commit  (4x)
     git status
     git add -A
     git commit -m <str>
     git push origin main

$ askit mine --file examples/shell-history-sample.txt --draft 1

Secrets are masked before anything is shown. See docs/mining.md.

Export targets

TargetResult
claude.claude/skills/<name>/ or ~/.claude/skills/ with --global
hermes.hermes/skills/<name>/ or ~/.hermes/skills/
cursor.cursor/rules/<name>.mdc
windsurf.windsurf/rules/<name>.md
copilot.github/instructions/<name>.instructions.md
codexmanaged block in AGENTS.md
geminimanaged block in GEMINI.md

Exports are repeatable: managed blocks are replaced, not duplicated. More in docs/export-targets.md.

Starter skills

The registry ships 16 skills that pass every check. Nine of them include scripts with tests, so you can read how a tested skill is built.

SkillWhat it does
log-analyzerseverity counts, time range and frequent errors of a log
python-code-smellsbare except, mutable defaults, missing docstrings and long functions
csv-profilercolumn types, missing values and ranges for a CSV
json-diffstructural diff of two JSON files
dockerfile-linterunpinned images, root user, ADD vs COPY, secrets in ENV
env-file-auditor.env against .env.example, without printing values
todo-extractorTODO, FIXME and HACK inventory of a source tree
changelog-generatorconventional commits to grouped release notes
api-endpoint-testersmoke test an API from a JSON file
commit-message-writerclear conventional commit messages
pr-description-writerpull request descriptions reviewers can

// HOW IT'S BUILT

// FILES

5 files shipped with this skill. Inline text is the catalog copy. The upstream link is the source repository.

  • skills/api-endpoint-tester/SKILL.md1.8 KBe00113fedcefupstream ↗
    ---
    name: api-endpoint-tester
    description: Runs a list of HTTP checks against a base URL from a JSON file and reports status codes, response time and body assertions. Use when the user asks whether an API is up, wants a smoke test after a deploy, or needs to verify that endpoints return what the docs promise.
    license: MIT
    metadata:
      version: 1.0.0
      author: Lethe044
      tags: [api, http, testing, smoke-test]
    ---
    
    # API endpoint tester
    
    A tiny smoke test runner with no dependencies. It sends real HTTP requests, so only point it at services
    the user owns or has permission to test.
    
    ## When to use
    
    - After a deploy, to confirm the main endpoints respond.
    - When the user suspects an endpoint changed shape or status code.
    - To turn a list of curl commands into a repeatable check.
    
    ## How to run
    
    ```bash
    python scripts/check_api.py checks.json --base-url https://staging.example.com
    ```
    
    The checks file is a JSON list:
    
    ```json
    [
      {"name": "health", "method": "GET", "path": "/health", "expect_status": 200, "expect_body_contains": "ok"},
      {"name": "create", "method": "POST", "path": "/items", "json": {"name": "demo"}, "expect_status": 201}
    ]
    ```
    
    Optional fields: `headers` (object), `json` (request body), `expect_status` (default 200),
    `expect_body_contains` (string) and `timeout` (seconds, default 10).
    
    ## Output
    
    JSON with `base_url`, `passed`, `failed` and `results`. Each result has `name`, `ok`, `status`,
    `elapsed_ms` and, when it failed, a `reason`. The exit code is 1 when any check fails.
    
    ## After running
    
    Report failures first with the reason and status code. Mention slow endpoints (over one second). Do not retry
    failing POST requests automatically, they may create duplicates.
    
    ## Errors
    
    A missing checks file exits with code 2. Invalid JSON or a malformed check exits with code 3.
    Connection errors are reported as failed checks, not crashes.
    
  • skills/api-endpoint-tester/scripts/check_api.py2.8 KB0345486871c7upstream ↗
    #!/usr/bin/env python3
    """Run HTTP checks from a JSON file and report the results."""
    import argparse
    import json
    import sys
    import time
    import urllib.error
    import urllib.request
    
    
    def run_check(base_url, check):
        method = str(check.get("method", "GET")).upper()
        url = base_url.rstrip("/") + "/" + str(check.get("path", "")).lstrip("/")
        headers = dict(check.get("headers") or {})
        data = None
        if "json" in check:
            data = json.dumps(check["json"]).encode("utf-8")
            headers.setdefault("Content-Type", "application/json")
        request = urllib.request.Request(url, data=data, headers=headers, method=method)
        started = time.time()
        status, body, reason = None, "", ""
        try:
            with urllib.request.urlopen(request, timeout=float(check.get("timeout", 10))) as resp:
                status = resp.status
                body = resp.read(200000).decode("utf-8", errors="replace")
        except urllib.error.HTTPError as exc:
            status = exc.code
            body = exc.read(200000).decode("utf-8", errors="replace")
        except (urllib.error.URLError, OSError) as exc:
            reason = "request failed: %s" % getattr(exc, "reason", exc)
        elapsed = int((time.time() - started) * 1000)
        if not reason:
            expected = int(check.get("expect_status", 200))
            if status != expected:
                reason = "expected status %d, got %d" % (expected, status)
            elif "expect_body_contains" in check and str(check["expect_body_contains"]) not in body:
                reason = "body does not contain %r" % check["expect_body_contains"]
        result = {"name": check.get("name") or "%s %s" % (method, check.get("path", "")), "ok": not reason,
                  "status": status, "elapsed_ms": elapsed}
        if reason:
            result["reason"] = reason
        return result
    
    
    def main(argv=None):
        parser = argparse.ArgumentParser(description=__doc__)
        parser.add_argument("checks", help="JSON file with a list of checks")
        parser.add_argument("--base-url", required=True)
        args = parser.parse_args(argv)
        try:
            with open(args.checks, encoding="utf-8") as fh:
                checks = json.load(fh)
        except OSError:
            print("file not found: %s" % args.checks, file=sys.stderr)
            return 2
        except ValueError:
            print("invalid JSON in %s" % args.checks, file=sys.stderr)
            return 3
        if not isinstance(checks, list) or not all(isinstance(c, dict) and "path" in c for c in checks):
            print("checks must be a list of objects that each have a path", file=sys.stderr)
            return 3
        results = [run_check(args.base_url, c) for c in checks]
        failed = sum(1 for r in results if not r["ok"])
        print(json.dumps({"base_url": args.base_url, "passed": len(results) - failed, "failed": failed,
                          "results": results}))
        return 1 if failed else 0
    
    
    if __name__ == "__main__":
        sys.exit(main())
    
  • skills/api-endpoint-tester/tests/cases.json423 B72e574414de9upstream ↗
    {
      "entry": "scripts/check_api.py",
      "cases": [
        {"name": "missing checks file", "kind": "error", "args": ["nope.json", "--base-url", "http://127.0.0.1:1"],
         "expect_exit": 2, "stderr_contains": "file not found"},
        {"name": "invalid json", "kind": "error", "args": ["bad.json", "--base-url", "http://127.0.0.1:1"],
         "files": {"bad.json": "{oops"}, "expect_exit": 3, "stderr_contains": "invalid JSON"}
      ]
    }
    
  • skills/api-endpoint-tester/tests/test_check_api.py3.1 KBb73e8ee6750aupstream ↗
    import json
    import os
    import subprocess
    import sys
    import tempfile
    import threading
    import unittest
    from http.server import BaseHTTPRequestHandler, HTTPServer
    
    SCRIPT = os.path.join(os.path.dirname(__file__), "..", "scripts", "check_api.py")
    
    
    class Handler(BaseHTTPRequestHandler):
        def log_message(self, *args):
            return
    
        def _reply(self, code, body):
            data = body.encode()
            self.send_response(code)
            self.send_header("Content-Length", str(len(data)))
            self.end_headers()
            self.wfile.write(data)
    
        def do_GET(self):
            if self.path == "/health":
                self._reply(200, "status ok")
            else:
                self._reply(404, "missing")
    
        def do_POST(self):
            length = int(self.headers.get("Content-Length", 0))
            payload = json.loads(self.rfile.read(length) or b"{}")
            self._reply(201, "created " + payload.get("name", ""))
    
    
    class CheckApiTests(unittest.TestCase):
        @classmethod
        def setUpClass(cls):
            cls.server = HTTPServer(("127.0.0.1", 0), Handler)
            cls.url = "http://127.0.0.1:%d" % cls.server.server_address[1]
            threading.Thread(target=cls.server.serve_forever, daemon=True).start()
    
        @classmethod
        def tearDownClass(cls):
            cls.server.shutdown()
            cls.server.server_close()
    
        def run_script(self, checks, base=None):
            with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fh:
                json.dump(checks, fh)
            try:
                proc = subprocess.run([sys.executable, SCRIPT, fh.name, "--base-url", base or self.url],
                                      capture_output=True, text=True, timeout=30)
            finally:
                os.unlink(fh.name)
            return proc
    
        def test_passing_checks(self):
            proc = self.run_script([
                {"name": "health", "path": "/health", "expect_body_contains": "ok"},
                {"name": "create", "method": "POST", "path": "/items", "json": {"name": "demo"}, "expect_status": 201,
                 "expect_body_contains": "demo"}])
            self.assertEqual(proc.returncode, 0, proc.stderr)
            data = json.loads(proc.stdout)
            self.assertEqual((data["passed"], data["failed"]), (2, 0))
    
        def test_wrong_status_fails(self):
            proc = self.run_script([{"name": "gone", "path": "/nope"}])
            self.assertEqual(proc.returncode, 1)
            result = json.loads(proc.stdout)["results"][0]
            self.assertFalse(result["ok"])
            self.assertIn("expected status 200, got 404", result["reason"])
    
        def test_body_assertion_fails(self):
            proc = self.run_script([{"path": "/health", "expect_body_contains": "degraded"}])
            self.assertEqual(proc.returncode, 1)
    
        def test_connection_error_is_a_failed_check(self):
            proc = self.run_script([{"path": "/health", "timeout": 2}], base="http://127.0.0.1:1")
            self.assertEqual(proc.returncode, 1)
            self.assertIn("request failed", json.loads(proc.stdout)["results"][0]["reason"])
    
        def test_bad_checks_file(self):
            proc = self.run_script({"not": "a list"})
            self.assertEqual(proc.returncode, 3)
    
    
    if __name__ == "__main__":
        unittest.main()
    
  • README.mdupstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

Plain-text copy, no login: /skill-bundles/lethe044-agentskill-kit-skills-api-endpoint-tester.md

// REPO STATS

48 stars