{"id":51541863,"url":"https://github.com/gabriele-mastrapasqua/linklore","last_synced_at":"2026-07-09T14:30:23.036Z","repository":{"id":354909559,"uuid":"1225938305","full_name":"gabriele-mastrapasqua/linklore","owner":"gabriele-mastrapasqua","description":"Bookmarks you actually own — local-first link manager with hybrid search (FTS5 + semantic), private RAG chat with citations, and an LLM-optional pipeline. One Go binary, one SQLite file. No accounts, no SaaS, no telemetry.","archived":false,"fork":false,"pushed_at":"2026-04-30T22:03:39.000Z","size":5355,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-30T22:17:08.935Z","etag":null,"topics":["bookmark-manager","bookmarks","fts5","golang","htmx","knowledge-management","litellm","llm","local-first","ollama","privacy","rag","read-later","self-hosted","single-binary","sqlite"],"latest_commit_sha":null,"homepage":"https://github.com/gabriele-mastrapasqua/linklore","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/gabriele-mastrapasqua.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-30T20:00:55.000Z","updated_at":"2026-04-30T21:57:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/gabriele-mastrapasqua/linklore","commit_stats":null,"previous_names":["gabriele-mastrapasqua/linklore"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/gabriele-mastrapasqua/linklore","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabriele-mastrapasqua%2Flinklore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabriele-mastrapasqua%2Flinklore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabriele-mastrapasqua%2Flinklore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabriele-mastrapasqua%2Flinklore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/gabriele-mastrapasqua","download_url":"https://codeload.github.com/gabriele-mastrapasqua/linklore/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/gabriele-mastrapasqua%2Flinklore/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35303290,"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-07-09T02:00:07.329Z","response_time":57,"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":["bookmark-manager","bookmarks","fts5","golang","htmx","knowledge-management","litellm","llm","local-first","ollama","privacy","rag","read-later","self-hosted","single-binary","sqlite"],"created_at":"2026-07-09T14:30:22.291Z","updated_at":"2026-07-09T14:30:23.035Z","avatar_url":"https://github.com/gabriele-mastrapasqua.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/linklore-github-1280x640.png\" alt=\"Linklore — bookmarks you actually own. Local-first · hybrid search · private RAG.\" width=\"640\"\u003e\n\u003c/p\u003e\n\n# Linklore\n\n[![test](https://github.com/gabriele-mastrapasqua/linklore/actions/workflows/test.yml/badge.svg)](https://github.com/gabriele-mastrapasqua/linklore/actions/workflows/test.yml)\n[![release](https://github.com/gabriele-mastrapasqua/linklore/actions/workflows/release.yml/badge.svg)](https://github.com/gabriele-mastrapasqua/linklore/actions/workflows/release.yml)\n[![coverage](https://img.shields.io/badge/coverage-75%25-brightgreen)](#tests)\n[![go report](https://goreportcard.com/badge/github.com/gabriele-mastrapasqua/linklore)](https://goreportcard.com/report/github.com/gabriele-mastrapasqua/linklore)\n[![go version](https://img.shields.io/badge/go-1.25%2B-00ADD8?logo=go)](go.mod)\n[![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n[![local-first](https://img.shields.io/badge/local--first-✓-success)](#local-first-privacy-first)\n\n\u003e **Bookmarks you actually own.** A local-first, single-binary library for\n\u003e the links you save — full-text + semantic search across everything,\n\u003e a private RAG chat grounded on your own content, four view modes,\n\u003e RSS/Atom subscriptions, dead-link checker, drag-and-drop. SQLite under\n\u003e the hood. No accounts. No SaaS. No telemetry.\n\u003e\n\u003e **One Go binary. One SQLite file.** No Node, no npm, no JS build\n\u003e chain, no Docker compose, no Postgres, no Redis. The whole thing\n\u003e is ~14 MB and starts in under a second.\n\n![Linklore — grid view](docs/screenshots/00-hero.png)\n\n\u003e Screenshots in this README are taken from a curated public demo\n\u003e library — generate yours with `make seed-demo`.\n\n---\n\n## Local-first, in one paragraph\n\nYour data is one SQLite file. Migration is `cp`. No accounts, no SaaS,\nno telemetry. The LLM is optional and points at any OpenAI-compatible\nserver you run yourself (vLLM, llama.cpp, LM Studio, or any other\n`/v1` endpoint) — or skip the LLM entirely and search falls back to\nBM25. Binds to `127.0.0.1` by default; you decide if it ever leaves\nthe machine.\n\n---\n\n## Why\n\nBrowser bookmarks rot. Read-later apps lock your data behind an account.\nSelf-hosted bookmark managers ship a Docker compose, a Postgres, a\nRedis, and a JS build chain.\n\nLinklore is one Go binary and one SQLite file. Open it, paste a URL,\nforget it. When you want to find something — type, or ask the AI in\nplain language. When you want to leave — copy the SQLite file. That's\nthe migration.\n\n---\n\n## Features\n\n- 🪝 **Smart capture** — paste a link or RSS/Atom feed; the same field\n  handles both. Bookmarklet + Netscape import/export.\n- 🖼️ **Four views per collection** — list / grid / headlines /\n  Pinterest-style moodboard, persisted server-side. Moodboard\n  card width is slider-driven so you can pack 6 columns or stretch\n  to 2.\n- 🔎 **Hybrid search** — FTS5 + semantic (cosine on stored embeddings),\n  with a clean BM25 fallback when no LLM is around.\n- 💬 **RAG chat** with inline `[src:N]` citations and a sources rail that\n  dims retrieved-but-unused chunks.\n- ✨ **Per-link AI** — TL;DR + auto-tags on ingest. Optional; off by\n  default.\n- 🎬 **YouTube transcripts** *(optional)* — set `yt-dlp` on PATH and\n  Linklore pulls captions (manual + auto-generated) so the LLM\n  summarises what the video actually says, not the watch-page\n  boilerplate. Off by default — zero extra deps until you opt in.\n- 📖 **Reader drawer** — in-place preview with size / width /\n  light-sepia-dark.\n- 🌐 **Web preview that actually loads** — a same-origin proxy strips\n  `X-Frame-Options` / `frame-ancestors`, injects a `\u003cbase\u003e` so CSS\n  + images resolve against the upstream, and (by default) neutralises\n  scripts so SPA routers can't bounce the page off its first paint.\n  Toggle JS back on per page if a site needs hydration.\n- 🗂️ **Raindrop-style sidebar** — Collections is the hero, capped at 50\n  vh with a themed scrollbar; Library + Tools + theme/preview toggles\n  live in the topbar overflow menu so the sidebar stays focused.\n- 🏷️ **Type filter** — article / video / image / audio / document / book\n  chips per collection.\n- ⌨️ **⌘K palette**, `j/k` keyboard nav, right-click menu, drag-and-drop\n  reorder, light/dark/auto theme.\n- 🧹 **Duplicates view** with URL canonicalisation (strips `www.`,\n  trailing slash, fragment, `utm_*`, 17 other tracker keys).\n\n---\n\n## Screenshots\n\nThe two views the project is built around: a Pinterest-style moodboard\nof your saved links (the hero above), and a RAG chat that answers\nquestions in the language of your library and cites the exact source\nchunk it used.\n\n\u003ctable\u003e\n\u003ctr\u003e\n\u003ctd colspan=\"2\"\u003e\u003cstrong\u003eChat with your library\u003c/strong\u003e — streaming answer with inline \u003ccode\u003e[src:N]\u003c/code\u003e citations. The sources rail on the right highlights the chunks the model actually used and dims the ones that were retrieved but irrelevant. Powered by hybrid FTS5 + embedding retrieval over your own SQLite.\u003cbr\u003e\u003cimg src=\"docs/screenshots/02-chat.png\" alt=\"RAG chat with inline citations\"\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"50%\"\u003e\u003cstrong\u003eCollection — list view\u003c/strong\u003e\u003cbr\u003eFavicon + LLM summary + tags + a single big hero image per row. Sidebar leads with All collections + Ask, Collections is the visual anchor with a primary \u003ccode\u003e+ Add\u003c/code\u003e pill.\u003cbr\u003e\u003cimg src=\"docs/screenshots/03-collection.png\" alt=\"Collection list view\"\u003e\u003c/td\u003e\n\u003ctd width=\"50%\"\u003e\u003cstrong\u003eReader drawer\u003c/strong\u003e\u003cbr\u003eSlide-in preview with size / width / theme controls + a TL;DR card.\u003cbr\u003e\u003cimg src=\"docs/screenshots/04-drawer.png\" alt=\"Reader drawer\"\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"50%\"\u003e\u003cstrong\u003eWeb preview — proxied\u003c/strong\u003e\u003cbr\u003eSame-origin proxy strips \u003ccode\u003eX-Frame-Options\u003c/code\u003e, injects a \u003ccode\u003e\u0026lt;base\u0026gt;\u003c/code\u003e, and disables JS by default so the SSR'd first paint stays put. Pages other readers can't embed render here, fully styled.\u003cbr\u003e\u003cimg src=\"docs/screenshots/08-web-proxy.png\" alt=\"Web preview proxy with full styling\"\u003e\u003c/td\u003e\n\u003ctd width=\"50%\"\u003e\u003cstrong\u003eTopbar overflow menu\u003c/strong\u003e\u003cbr\u003eTags, Duplicates, Broken links, Bookmarklet, Backup, Highlights export, Settings, plus theme + previews toggles — all behind one \u003ccode\u003e⋯\u003c/code\u003e so the sidebar stays focused on Collections.\u003cbr\u003e\u003cimg src=\"docs/screenshots/09-overflow-menu.png\" alt=\"Topbar overflow menu with Library, Tools, Preferences\"\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd width=\"50%\"\u003e\u003cstrong\u003eType filter\u003c/strong\u003e\u003cbr\u003ePer-collection chips: article / video / image / audio / document / book.\u003cbr\u003e\u003cimg src=\"docs/screenshots/07-types.png\" alt=\"Type filter chips\"\u003e\u003c/td\u003e\n\u003ctd width=\"50%\"\u003e\u003cstrong\u003e⌘K command palette\u003c/strong\u003e\u003cbr\u003eFuzzy-search every link, every collection, every page.\u003cbr\u003e\u003cimg src=\"docs/screenshots/05-palette.png\" alt=\"Command palette\"\u003e\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/table\u003e\n\n---\n\n## Install\n\n### Pre-built binaries (recommended)\n\nGrab a release archive from the\n[releases page](https://github.com/gabriele-mastrapasqua/linklore/releases/latest)\nfor your OS/arch — `linux-amd64`, `linux-arm64`, `darwin-amd64`,\n`darwin-arm64`, or `windows-amd64`. Each archive bundles the binary,\n`README.md`, `LICENSE`, and `configs/config.yaml`.\n\n```bash\n# example: macOS arm64\ncurl -LO https://github.com/gabriele-mastrapasqua/linklore/releases/latest/download/linklore-\u003cversion\u003e-darwin-arm64.tar.gz\ntar -xzf linklore-*-darwin-arm64.tar.gz\n./linklore-*-darwin-arm64 serve\n```\n\nOpen `http://127.0.0.1:8080`. That's the whole onboarding.\n\n### From source\n\nYou need **Go 1.25+** and `make`. An LLM is optional — Linklore boots\ncleanly without one.\n\n```bash\ngit clone git@github.com:gabriele-mastrapasqua/linklore.git\ncd linklore\nmake build           # ./bin/linklore (~14 MB)\nmake run             # serves on http://127.0.0.1:8080\n```\n\nWant a local LLM (RAG chat + per-link TL;DR)? Two extra steps:\n\n```bash\nmake env-template    # cp .env.example → .env (skips if .env exists)\n$EDITOR .env         # fill in OPENAI_BASE_URL, OPENAI_API_KEY, model names\n```\n\nSee **[Configuration](#configuration)** below for the env-var values.\n\n### Dev loop\n\n```bash\nmake dev             # reset DB + build + run (handy when iterating)\nmake test            # full suite, race-enabled, fts5 tag — what CI runs\nmake check           # fmt + vet + lint + test (run before commit)\nmake smoke           # spin up server, hit health + create paths, tear down\nmake seed-demo       # populate ./data/linklore-demo.db with curated public links\nmake help            # show every target\n```\n\n### CLI\n\n```bash\n./bin/linklore serve   [--config configs/config.yaml]\n./bin/linklore add     -c \u003cslug\u003e \u003curl\u003e\n./bin/linklore reindex\n```\n\n---\n\n## Configuration\n\nLinklore splits config across two files with **strict, non-overlapping\nresponsibilities** so a `git add .` can't ever leak a secret:\n\n| File | Holds | Tracked in git? |\n|---|---|---|\n| `configs/config.yaml` | Non-secret tunables. | ✓ committed |\n| `.env`                | Secrets + per-machine values: LLM endpoint, API key, model names. | gitignored |\n\nThe `/settings` page reads the live config and writes LLM changes back\nto `.env` only — yaml is **never** touched by the UI save path.\n\n### What's in `configs/config.yaml`\n\nEdit when you want to change a defaults; otherwise ignore it.\n\n```yaml\nserver.addr                 # default 127.0.0.1:8080\ndatabase.path               # default ./data/linklore.db\nworker.{concurrency, embed_batch_size, fetch_timeout_seconds}\nextract.{headless_fallback, archive_html, min_readable_chars, yt_dlp_path}\nchunking.{target_tokens, overlap_tokens, min_tokens}   # RAG knobs\ntags.{max_per_link, active_cap, reuse_distance}        # auto-tag caps\nui.{show_images_default, reader_font, reader_width}    # cosmetic defaults\n```\n\n### Pick an LLM backend (or skip)\n\n`make env-template` (or `cp .env.example .env`) then fill in one of\nthe blocks below. **Use `openai` for any local LLM** — point\n`OPENAI_BASE_URL` at any server that speaks the OpenAI-compatible\n`/v1/chat/completions` + `/v1/embeddings` shape (vLLM, llama.cpp,\nLM Studio, OpenAI itself, …). Switching servers is one URL.\n\n\u003e **`OPENAI_API_KEY` is optional.** Leave it empty (or omit it) for\n\u003e local servers — linklore won't send an `Authorization` header at\n\u003e all. Set a real `sk-...` key only when you talk to the actual\n\u003e OpenAI cloud (or to a local server you've protected with auth).\n\n**Local LLM (no key needed):**\n\n```ini\nLINKLORE_LLM_BACKEND=openai\nOPENAI_BASE_URL=http://localhost:8000/v1     # vLLM / llama.cpp / LM Studio / …\nLINKLORE_LLM_MODEL=qwen3:14b\nLINKLORE_LLM_EMBED_MODEL=nomic-embed-text\n# OPENAI_API_KEY=                              # leave empty for local\n```\n\n**Real OpenAI cloud** (key required, paid):\n\n```ini\nLINKLORE_LLM_BACKEND=openai\nOPENAI_BASE_URL=https://api.openai.com/v1\nOPENAI_API_KEY=sk-...your-real-key...\nLINKLORE_LLM_MODEL=gpt-4o-mini\nLINKLORE_LLM_EMBED_MODEL=text-embedding-3-small\n```\n\n**No LLM** (default — search degrades to BM25, chat shows a friendly\ndisabled banner, ingestion still fetches and extracts):\n\n```ini\nLINKLORE_LLM_BACKEND=none\n```\n\n\u003cdetails\u003e\n\u003csummary\u003eUsing Ollama? Two options.\u003c/summary\u003e\n\nThe recommended path is still **`openai`** — Ollama exposes an\nOpenAI-compatible endpoint at `/v1`, so you get the same code path as\nevery other server:\n\n```ini\nLINKLORE_LLM_BACKEND=openai\nOPENAI_BASE_URL=http://localhost:11434/v1\nOPENAI_API_KEY=ollama         # any non-empty string\nLINKLORE_LLM_MODEL=qwen3:14b\nLINKLORE_LLM_EMBED_MODEL=nomic-embed-text\n```\n\nA separate `LINKLORE_LLM_BACKEND=ollama` exists that hits Ollama's\nnative `/api/*` endpoints. It's only useful if you need a flag the\n`/v1` shim doesn't surface — otherwise prefer `openai`.\n\n\u003c/details\u003e\n\n### Optional: YouTube transcripts via `yt-dlp`\n\nYouTube watch pages on their own carry no usable text for the LLM —\njust the boilerplate `\u003cmeta\u003e` description. To pull the actual captions\n(manual or auto-generated), Linklore can shell out to\n[`yt-dlp`](https://github.com/yt-dlp/yt-dlp). It's **fully optional**:\nwhen the path is empty, YouTube links are saved like any other\nbookmark with no transcript and no extra dependency.\n\nWhen enabled, every newly-fetched YouTube link gets a\n`## Transcript` section appended to its body, and existing links\nexpose a **\"yt transcript\"** button next to *refetch* / *reindex* on\nthe link detail page.\n\n**Install yt-dlp** (pick one):\n\n```bash\nbrew install yt-dlp                # macOS\npipx install yt-dlp                # any OS, isolated venv\nsudo apt install yt-dlp            # Debian/Ubuntu (recent enough)\n```\n\n**Point Linklore at it.** Either edit `configs/config.yaml`:\n\n```yaml\nextract:\n  yt_dlp_path: \"/opt/homebrew/bin/yt-dlp\"   # or `which yt-dlp`\n```\n\n…or set it at runtime via env (overrides the yaml):\n\n```bash\nLINKLORE_YT_DLP_PATH=\"$(which yt-dlp)\" ./bin/linklore serve\n```\n\n**Re-extract an existing link**: open the link, click **yt transcript**.\nThe transcript is appended in place and the link goes back to\n`fetched` so the worker re-runs summary + embeddings against the new\nbody. Idempotent — clicking twice replaces the prior transcript\nsection, doesn't duplicate it.\n\n\u003e No API key, no quota — `yt-dlp` talks to public YouTube endpoints.\n\u003e If a video has captions disabled the call returns a `no transcript`\n\u003e error and the link is left untouched.\n\n### Precedence\n\n```\nprocess env  \u003e  .env  \u003e  configs/config.yaml  \u003e  built-in defaults\n```\n\nA one-shot override always wins:\n\n```bash\nLINKLORE_LLM_BACKEND=none ./bin/linklore serve\n```\n\n### All env vars\n\n```\nLINKLORE_LLM_BACKEND          none | openai   (ollama also accepted, see above)\nLINKLORE_LLM_MODEL            chat/summary model\nLINKLORE_LLM_EMBED_MODEL      embedding model\nOPENAI_BASE_URL               OpenAI-compatible base URL\nOPENAI_API_KEY                bearer token. OPTIONAL: leave empty for\n                              local servers; set a real sk-... only\n                              for the real OpenAI cloud\nLINKLORE_ADDR                 server bind address\nLINKLORE_DB_PATH              SQLite path\nLINKLORE_WORKER_CONCURRENCY   parallel ingest workers\nLINKLORE_YT_DLP_PATH          path to yt-dlp (enables YouTube transcripts)\n```\n\n---\n\n## Keyboard shortcuts\n\n| Key | Action |\n|---|---|\n| `⌘K` / `Ctrl+K` | Command palette |\n| `j` / `↓`        | Next card |\n| `k` / `↑`        | Previous card |\n| `↵`              | Open in preview drawer |\n| `x`              | Toggle bulk-selection on focused card |\n| `del`            | Delete focused card |\n| `/`              | Focus the search |\n| `?`              | Show shortcut overlay |\n| `esc`            | Dismiss overlay / drawer / clear selection |\n| Right-click row  | Context menu (Preview / Open / Copy URL / ✦ Ask / Delete) |\n\n---\n\n## Tests\n\n```bash\nmake test       # full suite, -race, fts5 tag, count=1 — what CI runs\nmake cover      # writes coverage.html + prints total\nmake cover-pkg  # per-package summary, no HTML\n```\n\nCurrent total: ~75% across 23 packages. The 0%-coverage areas are\nthe `runServe` boot orchestration (subprocess-only) and the LLM\nclient wrappers that are exercised end-to-end via `make smoke`.\n\n---\n\n## Docs\n\n| Topic | Where |\n|-------|-------|\n| Architecture, package map, stack | [`docs/architecture.md`](docs/architecture.md) |\n| The four view modes | [`docs/views.md`](docs/views.md) |\n| Search engine and popover | [`docs/search.md`](docs/search.md) |\n| Preview drawer + 5 tabs | [`docs/preview.md`](docs/preview.md) |\n| Responsive layout (breakpoints, off-canvas) | [`docs/responsive.md`](docs/responsive.md) |\n| Drag-and-drop chip + insertion bar | [`docs/dnd.md`](docs/dnd.md) |\n| Keyboard shortcuts | [`docs/keyboard.md`](docs/keyboard.md) |\n| LLM modes (`none` / `openai`) | [`docs/llm-modes.md`](docs/llm-modes.md) |\n\n---\n\n## Contributing\n\nPersonal project, but PRs that fit the spirit (local-first,\nsingle-user, no SaaS, no JS build) are welcome. Package layout and\nstack notes live in [`docs/architecture.md`](docs/architecture.md).\n\n1. `make check` — fmt + vet + lint + race test.\n2. New features go behind a config flag if their cost is non-trivial.\n3. New tests for new features. Bar: \"could a regression slip past the\n   existing suite?\" If yes, add the test.\n\n---\n\n## License\n\nMIT. See [`LICENSE`](LICENSE).\n\nLinklore is named after Linkjam, the proto-bookmark-manager you would\nhave built if you'd stayed up one Saturday in 2008.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabriele-mastrapasqua%2Flinklore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgabriele-mastrapasqua%2Flinklore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgabriele-mastrapasqua%2Flinklore/lists"}