{"id":50612305,"url":"https://github.com/tgpski/leather","last_synced_at":"2026-06-06T05:01:11.933Z","repository":{"id":361819838,"uuid":"1245164823","full_name":"TGPSKI/leather","owner":"TGPSKI","description":"Local agent infrastructure in one stdlib-only Go binary","archived":false,"fork":false,"pushed_at":"2026-06-01T11:27:42.000Z","size":4281,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-06-01T12:27:49.862Z","etag":null,"topics":["agents","ai-agents","automation","cli","developer-tools","go","golang","homelab","llm","local-first","local-llm","mcp","model-context-protocol","ollama","scheduler","self-hosted","stdlib-only","webhook","workflow-engine","zero-dependencies"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/tgpski/leather","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TGPSKI.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-21T01:17:02.000Z","updated_at":"2026-06-01T11:33:03.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/TGPSKI/leather","commit_stats":null,"previous_names":["tgpski/leather"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/TGPSKI/leather","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TGPSKI%2Fleather","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TGPSKI%2Fleather/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TGPSKI%2Fleather/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TGPSKI%2Fleather/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TGPSKI","download_url":"https://codeload.github.com/TGPSKI/leather/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TGPSKI%2Fleather/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33969883,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-06T02:00:07.033Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["agents","ai-agents","automation","cli","developer-tools","go","golang","homelab","llm","local-first","local-llm","mcp","model-context-protocol","ollama","scheduler","self-hosted","stdlib-only","webhook","workflow-engine","zero-dependencies"],"created_at":"2026-06-06T05:01:11.288Z","updated_at":"2026-06-06T05:01:11.924Z","avatar_url":"https://github.com/TGPSKI.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# leather\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/tgpski/leather.svg)](https://pkg.go.dev/github.com/tgpski/leather)\n[![Go Version](https://img.shields.io/github/go-mod/go-version/TGPSKI/leather?v=2)](https://go.dev/)\n[![License](https://img.shields.io/github/license/TGPSKI/leather?v=2)](LICENSE)\n[![GitHub Release](https://img.shields.io/github/release/TGPSKI/leather.svg?style=flat)](https://github.com/TGPSKI/leather/releases)\n[![CI](https://github.com/TGPSKI/leather/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/TGPSKI/leather/actions/workflows/ci.yml)\n\n  \n\n**Local agent infrastructure in one stdlib-only Go binary.**\n\nLeather runs declarative agents on your workstation, server, or Raspberry Pi:\nscheduled jobs, one-shot runs, webhook-driven workflows, tool calling, and\nauditable outputs.\n\nNo Python stack. No hosted control plane. No broker, telemetry, or dependency\npile.\n\n```bash\nleather run ~/.leather/agents/summarizer.agent.md\nleather serve --pretty --stats\nleather ingest pr.json --kind github.pr --curing pr-review\n```\n\n## Why leather\n\n| Instead of… | leather gives you |\n|---|---|\n| A Python stack, a virtualenv, and a `requirements.txt` you re-pin every quarter | One Go binary. Stdlib only. `go.mod` has zero `require` entries. |\n| A hosted control plane or always-on SaaS | A single process you run on your workstation, server, or Raspberry Pi. No phone-home. No telemetry. Loopback API by default. |\n| A heavy broker (Redis, RabbitMQ, Temporal) just to fan webhooks into agents | Built-in queues with backpressure, retries, and dead-letter routing. HMAC-validated webhook intake. Same process. |\n| Writing a Python plugin every time an agent needs a tool | Skills, toolsets, MCP servers, and a shell-manifest companion binary — all stdlib Go. |\n| \"Context window exceeded\" halfway through a multi-megabyte PR | Oversized inputs are paged into bounded cuts; the model only ever sees what fits. |\n| Grepping yesterday's logs to reconstruct what an agent actually did | JSONL run history, deterministic replay, lineage on every artifact (agent, curing, input hide, timestamps). |\n| Picking between a cron tool *and* a workflow engine | Scheduled agents and webhook-driven multi-agent pipelines, in one binary, side by side. |\n\n\n## Examples\n\nEvery example is a single `make` target. Copy the env template, point it at\nyour local model, and go:\n\n\u003cdetails open\u003e\u003csummary\u003e\u003cstrong\u003eExample 02: scheduled agent\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\n```bash\ncp examples/env.example examples/.env\n$EDITOR examples/.env\nmake example-02\n```\n\n\u003cbr/\u003e\n\u003cimg src=\"docs/media/02.gif\" alt=\"Animated GIF of a terminal running 'make example-02' to start a scheduled agent, then showing the agent's output in the terminal\"/\u003e\n\u003c/details\u003e\n\u003cbr/\u003e\n\u003cdetails\u003e\u003csummary\u003e\u003cstrong\u003eExample 06: multi-agent curing + devtools UI\u003c/strong\u003e\u003c/summary\u003e\n\u003cbr/\u003e\n\n```bash\nmake example-06\n```\n\n\u003cbr/\u003e\n\u003cimg src=\"docs/media/06.gif\" alt=\"Animated GIF of a terminal running 'make example-06' to start a multi-agent curing\"/\u003e\n\u003cbr/\u003e\n\u003cimg src=\"docs/media/06-devtools-1.png\" alt=\"Screenshot of the devtools UI showing a session timeline with prompt and tool events\"/\u003e\n\u003cbr/\u003e\n\u003cimg src=\"docs/media/06-devtools-2.png\" alt=\"Screenshot of the devtools UI showing a session timeline with prompt and tool events\"/\u003e\n\u003cbr/\u003e\n\u003cimg src=\"docs/media/06-devtools-3.png\" alt=\"Screenshot of the devtools UI showing a session timeline with prompt and tool events\"/\u003e\n\n\u003c/details\u003e\n\u003cbr/\u003e\n\n\nThe full catalog lives in [examples/](examples/) — twelve runnable demos from\n`hello-mock` to a high-volume CI gate.\n\n\n## Capabilities\n\n### Local agent runtime\nAgents are Markdown + YAML. They run against any OpenAI-compatible endpoint.\nToken budgets are tracked per request; context is summarized or truncated\nbefore the model's limit is hit.\n\n### Tools, skills, MCP, and shell\n- **Skills** (`*.skill.yaml`) define tools plus optional prompt/parameter metadata.\n- **Toolsets** (`*.toolset.yaml`) bundle named tool collections.\n- **MCP servers** (`mcp-servers.yaml`) plug in any stdio‑transport MCP server.\n- **`shell-mcp`** is a companion binary that turns a JSON manifest into a fast local tool surface — `git`, `gh`, anything you'd put behind a shell command.\n\n### Curings — multi-stage workflows\nA `*.curing.yaml` binds one agent to one input queue. Compose pipelines by\nwriting one curing's output into the next curing's input queue. Runs under\nplain `leather serve`; no tannery required.\n\n- **Queues** — per‑curing FIFO with bounded depth, backpressure, configurable concurrency, exponential‑backoff retry, and a **dead‑letter queue** for items that exhaust their retry budget. Inspectable via `/queues` and `/queues/{name}`.\n- **CLI ingest** — `leather ingest path/to/file --kind \u003chide-kind\u003e --curing \u003cname\u003e` drops a hide directly onto a curing queue, no HTTP needed.\n- **Process lock** — non‑blocking `\u003cstate-dir\u003e/leather.lock`; a second `leather serve` against the same state directory exits with code 2 and a clear stderr message.\n\n### HTTP poll workers\n`*.worker.yaml` files under `--worker-dir` run background HTTP pollers that\npush results into named queues — RSS feeds, status pages, JSON APIs — with\nretry/backoff and the same dead-letter routing as curings.\n\n### Tannery — HTTP intake, hides, and artifacts\nAdd a `tannery.yaml` next to your config and `leather serve` also stands up:\n\n- **Intake** (`POST /intake`) and **webhooks** (`POST /webhooks/{name}`) — HMAC‑validated, per‑route body‑size caps, source/event matching dispatches into the right curing queue.\n- **Hides** — raw inputs (PR threads, API responses, logs, files) stored content‑addressed under `hide_dir`. Agents only ever see a bounded **cut** through a paged `HideBuffer`, so multi‑megabyte inputs can't blow the context window. Browseable via `/hides` and `/hides/{id}`.\n- **Artifacts** — promoted outputs stored content‑addressed under `artifact_dir` with lineage (which curing, which input hide(s), which agent, when). Queryable via `/artifacts` and `/artifacts/{id}`.\n\n### Replay, snapshots, and run history\n- `--persist-runs` writes every turn to JSONL with rotation\n- `--replay` and `--replay-live` reconstruct past sessions deterministically\n- `/snapshot` captures live state; `/replay/control` drives playback when the API is enabled\n\n### Browser DevTools UI\nWith `--api`, `leather serve` exposes a single-page UI at `/ui/devtools.html`:\nsession timeline, prompt/tool event inspector, curing flow diagram, queue and\nworker status, and live SSE updates. No build step, no JS dependencies —\nserved straight from the binary.\n\n### HTTP API\nLoopback-bound by default (`127.0.0.1:7749`):\n\n- **Runtime:** `/healthz`, `/status`, `/metrics`, `/config`, `/history`\n- **Scheduler \u0026 queues:** `/jobs`, `/jobs/{id}`, `/queues`, `/queues/{name}`, `/workers`\n- **Cache:** `/cache/stats`\n- **Tannery (when enabled):** `/intake`, `/webhooks/*`, `/hides`, `/hides/{id}`, `/artifacts`, `/artifacts/{id}`, `/curings`\n- **Replay:** `/snapshot`, `/replay/control`\n- **DevTools:** `/api/devtools/snapshot`, `/api/devtools/inspect/...`, `/api/devtools/trace/...`\n\n### Notifications\nFinished agent runs can be delivered to messaging sinks via per-agent output\nroutes. Built-in **Telegram** and **Signal** backends ship in\n`internal/notify`; curing outputs can additionally land in queues or be\npromoted to artifacts.\n\n\n\n## Build your own\n\n### 1. Write an agent\n\n```markdown\n---\nname: summarizer\n---\nYou are a concise planning assistant. Output bullet points only.\n```\n\nThat's a complete `*.agent.md` file. Front matter declares identity, the body\nis the system prompt.\n\n### 2. Give it a schedule (optional)\n\n```yaml\nagent: summarizer\nschedule: \"0 9 * * *\"\nmodel: llama3\nprompt: Summarize the three most important things to do today.\n```\n\n`*.lifecycle.yaml` files sit next to agents and carry the *when* and *how*.\n\n### 3. Run it\n\n```bash\nleather validate                                  # check everything parses\nleather run ~/.leather/agents/summarizer.agent.md # run once\nleather serve --pretty --stats                    # run on schedule\nleather chat --model llama3                       # talk to the model interactively\n```\n\n### 4. Add a workflow (when one agent isn't enough)\n\nTwo agents passing a hide between them, dispatched by a webhook:\n\n```yaml\n# tannery.yaml\nhide_dir: ./.tannery/hides\nartifact_dir: ./.tannery/artifacts\ncuring_dir: ./curings\nwebhooks:\n  - name: github\n    path: /webhooks/github\n    source: github\n    secret: \"{{env:GITHUB_WEBHOOK_SECRET}}\"\nroutes:\n  - name: pr-review\n    match:\n      source: github\n      event_type: pull_request\n    hide_kind: github.pull_request\n    curing: triage\n    queue: triage-in\n```\n\nEach curing binds one agent to one queue. Chain them by writing the\noutput of the first into the input queue of the second:\n\n```yaml\n# curings/triage.curing.yaml — first stage\nname: triage\nagent: triage      # classifies the PR, tags it\nhide_types: [github.pull_request]\nqueue: triage-in\noutput:\n  queue: review-in # hand off to the reviewer\n```\n\n```yaml\n# curings/review.curing.yaml — second stage\nname: review\nagent: reviewer    # reads the diff in cuts, writes the verdict\nhide_types: [github.pull_request]\nqueue: review-in\noutput:\n  artifact: true\n```\n\nSee [examples/06-multi-agent-curing](examples/06-multi-agent-curing/) for a\nworking two-curing chain and [examples/10-ci-gate](examples/10-ci-gate/) for\na webhook-driven fan-out.\n\n```bash\nleather serve --config tannery/config.yaml\n# any POST to /webhooks/github with a valid HMAC now enqueues a curing run\n```\n\nYou've just built a two-stage agent pipeline triggered by a GitHub webhook,\nrunning in a single local process.\n\n---\n\n## The vocabulary\n\nleather uses a deliberate metaphor borrowed from leatherworking. Sixty\nseconds here makes the rest of the docs read smoothly:\n\n```text\nLeather    the CLI/runtime/binary\nTanning    your local working area — configs, agents, curings, tools (a folder)\nTannery    the long-running workspace service composed from queue+worker+scheduler+session\nHide       raw input material — a PR thread, an API response, a log\nHideBuffer in-memory paged view of one hide\nCut        the bounded slice an agent actually sees in its context window\nCuring     a named N-agent workflow that transforms hides into artifacts\nOperation  one agent working on one or more cuts in a single turn\nArtifact   stabilized output with lineage (which curing, which hide, when)\nIntake     how hides get created (webhook, HTTP poll, CLI ingest, file)\n```\n\nFull reference: [docs/GLOSSARY.md](docs/GLOSSARY.md).\n\n---\n\n## Install\n\nFrom source:\n\n```bash\ngit clone https://github.com/tgpski/leather\ncd leather\nmake build \u0026\u0026 make build-shell-mcp\n```\n\nWith `go install`:\n\n```bash\ngo install github.com/tgpski/leather/cmd/leather@latest\ngo install github.com/tgpski/leather/cmd/shell-mcp@latest\n```\n\n**Verify the install** — no LLM endpoint required:\n\n```bash\nleather --version    # prints version\nmake example-01      # runs a mock-LLM example end-to-end\n```\n\n\n\n---\n\n## Commands\n\n| Command | Purpose |\n|---|---|\n| `leather init` | Scaffold `~/.leather` with `.env`, `config.yaml`, an example agent, and a `Makefile`. |\n| `leather doctor` | Print every effective config value with source attribution; redacts secrets. |\n| `leather serve` | Run scheduler, queue workers, and (when enabled) HTTP API, tannery, or replay. |\n| `leather run`   | Execute one agent definition once and exit. |\n| `leather chat`  | Interactive chat session with token‑budget management. |\n| `leather ingest`| Create a hide from a file or stdin and (optionally) enqueue a curing. |\n| `leather validate` | Validate config, agents, lifecycles, skills, workers, and MCP servers. |\n| `leather test-agent` | Run an agent against `MockLLM` and print the transcript. |\n| `leather status` | Print scheduler state and current token‑budget settings. |\n| `leather snapshot` | Save or restore a point-in-time `tar.gz` archive of runtime state. |\n| `leather attach` | Join a running `serve` instance and stream pretty-printed runtime events. |\n| `leather replay` | Replay a snapshot or live session. |\n| `leather version` / `leather help` | The obvious. |\n\n---\n\n## Configuration\n\n`config.Load` seeds defaults from built‑ins and `LEATHER_*` env vars, overlays\n`config.yaml`, then applies explicitly‑set CLI flags. In order of precedence:\n\n1. Explicit flag\n2. YAML config\n3. Environment variable\n4. Built‑in default\n\nEvery flag has a matching env var: `--flag-name` → `LEATHER_FLAG_NAME`.\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003cstrong\u003eShared flags (click to expand)\u003c/strong\u003e\u003c/summary\u003e\n\n| Flag | Env var | Default | Notes |\n|---|---|---|---|\n| `--config` | `LEATHER_CONFIG` | `~/.leather/config.yaml` | Config file path. |\n| `--agent-dir` | `LEATHER_AGENT_DIR` | `~/.leather/agents` | Agent and lifecycle directory. |\n| `--model` | `LEATHER_MODEL` | empty | Global default model name. |\n| `--temperature` | `LEATHER_TEMPERATURE` | `0.7` | Global default sampling temperature. |\n| `--log-level` | `LEATHER_LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error`. |\n| `--log-format` | `LEATHER_LOG_FORMAT` | `text` | `text` or `json`. |\n| `--max-tokens` | `LEATHER_MAX_TOKENS` | `8192` | Default max context window. |\n| `--completion-reserve` | `LEATHER_COMPLETION_RESERVE` | `1024` | Tokens reserved for completion. |\n| `--summarize-threshold` | `LEATHER_SUMMARIZE_THRESHOLD` | `0.85` | Summarization trigger ratio. |\n| `--llm-endpoint` | `LEATHER_LLM_ENDPOINT` | `http://localhost:11434` | OpenAI‑compatible base URL. |\n| `--llm-api-key` | `LEATHER_LLM_API_KEY` | empty | Bearer token for cloud OpenAI‑compatible endpoints. Empty = no auth. |\n| `--llm-timeout` | `LEATHER_LLM_TIMEOUT` | `60s` | Request timeout. |\n| `--scheduler-tick` | `LEATHER_SCHEDULER_TICK` | `1m` | Scheduler wake interval. |\n| `--max-concurrent-jobs` | `LEATHER_MAX_CONCURRENT_JOBS` | `4` | Scheduler concurrency cap. |\n| `--run-duration` | `LEATHER_RUN_DURATION` | `0` | Serve exits after this duration; `0` means unlimited. |\n| `--max-jobs` | `LEATHER_MAX_JOBS` | `0` | Serve exits after this many completed jobs; `0` means unlimited. |\n| `--state-dir` | `LEATHER_STATE_DIR` | `~/.leather/.state` | Scheduler state root. |\n| `--api` | `LEATHER_API` | `false` | Enable serve HTTP API. |\n| `--api-addr` | `LEATHER_API_ADDR` | `127.0.0.1:7749` | HTTP API bind address. Loopback recommended; see security note below. |\n| `--log-file` | `LEATHER_LOG_FILE` | empty | Tee structured logs to file, or file‑only in pretty mode. |\n| `--pretty` | `LEATHER_PRETTY` | `false` | Human‑readable console rendering. |\n| `--pretty-mode` | `LEATHER_PRETTY_MODE` | `all` | `messages` or `all`. |\n| `--stats` | `LEATHER_STATS` | `false` | Print token stats and summaries. |\n| `--tokens-per-turn` | `LEATHER_TOKENS_PER_TURN` | `false` | Print per‑turn token usage in pretty mode. |\n| `--persist-runs` | `LEATHER_PERSIST_RUNS` | `false` | Persist JSONL run history. |\n| `--run-history-dir` | `LEATHER_RUN_HISTORY_DIR` | empty | Defaults to `\u003cstate-dir\u003e/runs` when persistence is enabled. |\n| `--run-max-bytes` | `LEATHER_RUN_MAX_BYTES` | `10485760` | Rotate run logs at this size. |\n| `--replay` | `LEATHER_REPLAY` | empty | Snapshot replay file. |\n| `--replay-live` | `LEATHER_REPLAY_LIVE` | empty | Live replay directory. |\n| `--replay-speed` | `LEATHER_REPLAY_SPEED` | `1.0` | Replay speed multiplier. |\n| `--tool-dir` | `LEATHER_TOOL_DIR` | empty | Directory containing `*.skill.yaml` and `*.toolset.yaml`. |\n| `--default-toolsets` | `LEATHER_DEFAULT_TOOLSETS` | empty | Comma‑separated toolsets applied to every agent. |\n| `--max-tool-rounds` | `LEATHER_MAX_TOOL_ROUNDS` | `5` | Default tool‑call round cap. |\n| `--worker-dir` | `LEATHER_WORKER_DIR` | empty | Directory containing `*.worker.yaml`. |\n| `--cache-dir` | `LEATHER_CACHE_DIR` | empty | Defaults to `\u003cstate-dir\u003e/cache` in serve mode. |\n| `--mcp-servers-file` | `LEATHER_MCP_SERVERS_FILE` | empty | `run`, `serve`, and `validate` fall back to `~/.leather/mcp-servers.yaml`. |\n| `--loop` | `LEATHER_LOOP` | `1` | Repeat `leather run` this many times. |\n\n\u003c/details\u003e\n\n\n### Cloud LLM endpoints\n\n`leather` speaks the OpenAI Chat Completions API, so any cloud or\nself‑hosted endpoint that implements the same spec works without a Go\nrecompile — only `llm_endpoint`, `model`, and an API key change.\n\nProvide the bearer token in whichever form fits your deployment:\n\n```bash\n# Inline (good for one‑offs)\nleather run --agent hello \\\n  --llm-endpoint https://api.openai.com \\\n  --model gpt-4o-mini \\\n  --llm-api-key \"sk-...\"\n\n# Environment variable\nexport LEATHER_LLM_API_KEY=\"sk-...\"\nleather serve --llm-endpoint https://api.openai.com --model gpt-4o-mini\n```\n\nFor long‑running servers the recommended form is the Unix `pass`\nstore, with an env var as fallback. In `config.yaml`:\n\n```yaml\nllm_endpoint: https://api.openai.com\nmodel: gpt-4o-mini\nllm_api_key:\n  pass: openai/api-key       # `pass show openai/api-key`\n  env:  OPENAI_API_KEY       # fallback when pass is empty or unavailable\n```\n\n\n---\n\n## Go deeper\n\n- **Runnable examples** → [examples/](examples) — end-to-end demos, each one a single `make` target away\n- **Architecture \u0026 data flow** → [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)\n- **Glossary** → [docs/GLOSSARY.md](docs/GLOSSARY.md)\n- **Per‑package docs** → [docs/modules/](docs/modules)\n- **AI agent contributor guide** → [AGENTS.md](AGENTS.md)\n- **Domain‑specific subagent guides** → [.subagents/](.subagents)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftgpski%2Fleather","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftgpski%2Fleather","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftgpski%2Fleather/lists"}