Skip to content

Latest commit

 

History

8 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AI-powered Goodlinks Tagging

Auto-tag untagged bookmarks in GoodLinks using Claude AI. The tool fetches your untagged links from a local GoodLinks API, retrieves each page's content, asks Claude to suggest tags, and applies them back via the localhost API.

Tip

I auto-tagged ~1600 links that I had imported from other bookmarking systems for around $6 USD in Claude AI costs.

Prerequisites

Installation

Clone the repository and let uv handle the dependencies:

git clone <repo-url>
cd goodlinks-tagging
uv sync

uv sync reads pyproject.toml and installs the pinned dependencies from uv.lock. The only third-party dependency is anthropic.

Configuration

Environment Variables

Variable Required Description
GOODLINKS_TOKEN Yes Bearer token for the GoodLinks API. Obtain it from your GoodLinks instance (local server on port 9428).
ANTHROPIC_API_KEY Yes API key for Anthropic's Claude API. Create one at console.anthropic.com.

Export them before running the scripts:

export GOODLINKS_TOKEN="your-goodlinks-token"
export ANTHROPIC_API_KEY="your-anthropic-key"

Usage

You must run untagged.py first to fetch untagged links, run get-tags-list.py to get the list of existing tags, then run autotag.py next to process those untagged links. The three scripts must be run in this order because autotag.py reads the output files that untagged.py produces.

Step 1: Fetch Untagged Links

Run untagged.py to query the GoodLinks API and save all untagged links to untagged_links.json:

uv run untagged.py

This paginates through the API and writes the full list of untagged link objects to disk.

Step 2: Fetch Tags

Run get-tags-list.py to fetch your current tag list from GoodLinks and save it as tags.json:

uv run get-tags-list.py

This gives the script a list of existing tags to stick to. Occasionally it will create new tags (if the AI feels that those new tags are really warranted). You may or may not agree, and you can clean-up or delete those tags once the last script is done running.

Step 3: Auto-Tag

Once the other scripts have finished, run autotag.py to process each untagged link:

uv run autotag.py

The script reads untagged_links.json and tags.json, processes links concurrently, and writes checkpoint progress to progress.json. Already-processed links are skipped automatically, so you can safely re-run the command if it is interrupted.

It tries hard to do a good job and be idempotent. It will also respond to Claude rate limits and back off before triggering HTTP 429 responses.

How It Works

For each untagged link the pipeline is:

  1. Fetch page — download the URL and extract visible text (stripping scripts, styles, nav, and other non-content elements).
  2. Extract text — parse the HTML, pull the <title>, and truncate the body text to MAX_CONTENT_CHARS.
  3. Ask Claude — send the page text, link metadata, and the current tag list to Claude, which returns a JSON object with existing_tags and new_tags.
  4. Apply tags — write the chosen tags back to GoodLinks via the API. If the page could not be fetched, the link is tagged "__problem"; if Claude suggests nothing it is tagged "__notags".

Links are processed concurrently using a thread pool (MAX_WORKERS threads). Two sliding-window rate limiters keep requests and token usage within Anthropic's per-minute limits.

Tunable Constants

All tunable constants are defined at the top of autotag.py. Edit them directly to customise behaviour:

Constant Default Description
GOODLINKS_BASE "http://localhost:9428/api/v1" Base URL for the GoodLinks API.
UNTAGGED_FILE "./untagged_links.json" Path to the untagged links input file.
TAGS_FILE "./tags.json" Path to the cached tags file.
CHECKPOINT_FILE "./progress.json" Path to the checkpoint file.
CHECKPOINT_EVERY 5 Write a checkpoint every N processed links.
MAX_WORKERS 5 Number of concurrent threads.
MAX_TAGS 5 Maximum number of tags to apply per link.
FETCH_TIMEOUT 15 HTTP timeout in seconds for fetching web pages.
MAX_CONTENT_CHARS 4000 Maximum characters of page text sent to Claude.
LLM_MODEL "claude-haiku-4-5-20251001" Claude model identifier.
MAX_RETRIES 3 Maximum retry attempts for Claude API calls.

Rate limiter defaults (set just below the class definitions in autotag.py):

Instance Limit Description
_req_limiter 45 requests/min Sliding-window request rate limiter (hard API limit is 50).
_token_limiter 45 000 tokens/min Sliding-window input-token rate limiter (hard API limit is 50 000).

Output Files

(You can delete these when you're done.)

File Description
untagged_links.json List of untagged link objects produced by untagged.py.
tags.json Cached snapshot of all GoodLinks tags. Written by get-tags-list.py or refreshed by autotag.py.
progress.json Checkpoint file tracking which link IDs have been processed.

Checkpoint and Resume

autotag.py writes a checkpoint to progress.json every CHECKPOINT_EVERY processed links (default 5) and once more when the run finishes. The checkpoint stores the set of already-processed link IDs.

If the script is interrupted — network error, rate limit, Ctrl-C — simply re-run it:

uv run autotag.py

It loads progress.json on startup, skips links that are already done, and continues from where it left off. To start fresh, delete progress.json.

Support

None.

This is “itch-and-scratch-ware”. I had an itch, so I scratched it. The end. I may continue to improve this as I need to, but if you want any real changes, feel free to fork it and adapt it for your own needs.

About

AI-assisted tagging for Goodlinks. Leverages the Goodlinks local API and Anthropic Claude. Never any releases. Just run `main`.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Used by

Contributors

Languages