kagi #
Web search and page-content extraction using the Kagi API (v1). Lightweight — no browser, no npm dependencies, just Node.js 18+ (uses global fetch).
Use #
Run from this directory, or prefix with the skill path:
scripts/search.mjs "query" # Basic search (10 results)
scripts/search.mjs "query" -n 5 # Fewer results
scripts/search.mjs "query" --time week # Results from the last week
scripts/extract.mjs https://example.com/article # Markdown to stdout
scripts/extract.mjs https://a.com https://b.com # Up to 10 URLs at once
scripts/extract.mjs https://a.com --out article.md # Write to file
scripts/extract.mjs https://a.com --out article.md --force # Overwrite existing
search.mjs takes -n <num> (1–1024, default: 10), --time day|week|month, and -h/--help. extract.mjs takes up to 10 HTTPS URLs, --out <file> (refuses to overwrite unless --force is given), and -h/--help. Both request format: "markdown" and pass the response through to stdout.
Setup #
The scripts read the Kagi API key from the OS keyring at call time. Nothing is read from the environment, so there is no key sitting in the environment of every shell and every process.
Lookup coordinates are identical on both platforms:
| Field | Value |
|---|---|
| service | kagi |
| account | api_key |
Store the key #
The key comes from Kagi API Keys — here shown being read back out of 1Password.
Linux — libsecret (GNOME Keyring, KWallet) #
op read 'op://Private/Pi/kagi' | secret-tool store --label='Kagi API key' service kagi key api_key
Verify it round-trips (expect a non-zero count):
secret-tool lookup service kagi key api_key | wc -c
secret-tool ships in libsecret-tools (Debian/Ubuntu), libsecret (Fedora, Arch).
macOS — login Keychain #
security add-generic-password -s kagi -a api_key -U -w "$(op read 'op://Private/Pi/kagi')"
Verify it round-trips:
security find-generic-password -s kagi -a api_key -w | wc -c
Two caveats:
-w <value>puts the key in the process arguments briefly, wherepscan see it. To avoid that, drop-wand letsecurityprompt — but it cannot read from a pipe, so you type or paste the key twice.- The first read from a new caller may raise a Keychain access prompt. Approve it once with Always Allow; otherwise script reads hang until the 10 second timeout.
Rotate or remove #
Rotate by re-running the store command above: on Linux the identical attributes overwrite the existing entry, on macOS -U updates it in place. The scripts pick up the new value on the next call.
Remove:
secret-tool clear service kagi key api_key # Linux
security delete-generic-password -s kagi -a api_key # macOS
Troubleshooting #
no Kagi API key in the OS keyring — the entry is missing. Run the store command for your platform above.
could not reach the OS keyring session bus — expected over SSH, in a container, or without a desktop session. There is no way to unlock a running keyring from another session; your options are:
-
Linux, with a graphical session for the same user running — reuse its session bus while it stays logged in:
export DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/$(id -u)/bus" secret-tool lookup service kagi key api_keyThis works only while the login keyring is unlocked, which it normally is for the whole graphical session.
-
macOS — unlock the login keychain explicitly:
security unlock-keychain ~/Library/Keychains/login.keychain-dbIt prompts on the tty and stays unlocked until sleep or lock; tune that with
security set-keychain-settings -t <seconds>.
timed out ... — the keyring is locked and an unlock prompt is waiting somewhere you cannot see, or no prompt is reachable at all. Unlock it in a graphical session (or per the macOS command above), then retry.
How it works #
scripts/ holds the implementation: search.mjs and extract.mjs are the two CLIs, lib.mjs owns the shared POST helper, and keyring.mjs owns the platform dispatch and is read-only. Setup is deliberately not something the scripts do for you.
Credits #
Inspired by badlogic/pi-skills brave-search/search.js (MIT, Copyright (c) 2024 Mario Zechner) — same single-file CLI shape, rewritten against the Kagi Universal Search API (v1) with OS-keyring key lookup instead of an environment variable.