Linkwise - Save, annotate, chat with everything you've read. | Product Hunt
Command line

Linkwise CLI

Save, search and read your library without leaving the terminal. Every command is a thin wrapper over one endpoint in the API reference, so anything the CLI can do, a script can do too.

Install

A single static binary with no runtime to install alongside it. Every release publishes to Homebrew and npm at the same time, from the same tag.

The shell installer and the npm package both verify the release checksum before they unpack anything. If it does not match, nothing is written.

Install
brew install linkwiseapp/tap/linkwise

Update

linkwise update reports the newest release and the one command that installs it. Each channel owns the file it put on disk, so the right command is not the same for everyone; update works it out from where the binary actually lives rather than asking you to remember.--run executes it for you.

You do not have to go looking. Once a day, a command run at a terminal checks in the background and prints a single line on stderr when a newer release is out. It never delays a command: the lookup runs alongside the work you asked for, and an unfinished one leaves the news for next time.

Set LINKWISE_NO_UPDATE_CHECK to turn the notice off. It is already off wherever it would be noise: piped output, --json, and any environment with CI set.

On 0.1.1 or earlier?

linkwise update arrived in 0.1.2, so an older binary answers that there is no such command. Update it once with the channel it came from, and linkwise update takes over from there. ls -l $(command -v linkwise) tells you which: a link into Caskroom is Homebrew, a link into node_modules is npm, and a plain file is the shell installer. An npm install that fails with EEXIST on /usr/local/bin/linkwise is npm finding a binary another channel put there, and refusing to overwrite it.

Installed with the shell script, switching to Homebrew?

Remove the old binary first. The installer writes a real file to /usr/local/bin/linkwise and the cask wants to put its own symlink there, so brew install stops with there is already a Binary at ... until you rm it. Going the other way, the installer refuses on its own rather than breaking your Homebrew install.

Terminal
linkwise update
# -> A new release is out: 0.1.0 -> 0.2.0
# -> Installed via Homebrew.
# ->
# ->   brew upgrade --cask linkwiseapp/tap/linkwise
# ->
# -> Run it, or pass --run to run it for you.

# Or let it run the right command for you
linkwise update --run

Authenticate

auth login opens the dashboard, takes the key you paste, and checks it against GET /v1/me before storing it, so a mistyped key fails at the prompt rather than on your next command. It is kept in the system keychain; on a machine without one the CLI writes it to a 0600 file and tells you it did.

In CI, skip the browser entirely and set LINKWISE_TOKEN. The CLI prefers the environment variable over anything stored, so a pipeline never picks up a developer's credentials by accident.

Terminal
linkwise auth login
# -> Create a key at https://linkwise.app/build/dashboard
# -> Paste key: ****************
# -> Signed in as [email protected]
# -> Key stored in the system keychain.

linkwise auth status
# Profile  default
# Account  [email protected]  (pro)
# Key      lw_pat_8fK...  (from the keychain)
# Scopes   links:read links:write collections:read search:read

The full-screen app

Run linkwise with nothing after it and the terminal becomes a reader for the whole library, laid out like the Library tab in the app. Browse everything, what is unread, unsorted or archived, one collection, what arrived by email, the posts from one RSS feed, or the links that have an AI summary, then read the extracted article in place without opening a browser. Past conversations with Linkwise AI open as transcripts.

Every row on the home screen says what it does. Press / and type to narrow the list to a feature; the Search row stays and offers to search the library for the same words, so enter always goes somewhere.

d switches between the light and dark palettes and remembers the choice in config.toml. NO_COLOR drops it to plain text, the same as everywhere else.

Wherever the interface cannot be drawn, the bare command prints help exactly as it did before: output piped somewhere, no terminal on stdin, --json, or not signed in. A script that runs linkwise with no arguments sees what it always saw.

Conversations need a key with the ai:chat scope, which a key made for the library does not have. Without it the screen says so and names the scope.

Keys

j / kMove down and up. Arrow keys work too.
enterOpen what is selected.
escBack to wherever you came from.
/On the home screen, narrow the menu to a feature or search the library. Anywhere else, search titles, descriptions and content.
?Every key, in one screen.
dSwitch between the light and dark palettes.
qQuit, leaving the scrollback as it was found.
oOpen the selected link in the browser.
yCopy the selected link to the clipboard.
eArchive it, or restore it if it is archived.
uToggle read and unread.
pPin it, or unpin it.
Terminal
 linkwise v0.2.0      q:quit | /:search | j/k:nav | d:light theme | enter:select

     ██     ██ ███   ██ ██   ██ ██      ██ ██  ██████ ██████
     ██     ██ ████  ██ ██  ██  ██      ██ ██ ██      ██
     ██     ██ ██ ██ ██ ████    ██  ██  ██ ██  █████  █████
     ██     ██ ██  ████ ████    ██ ████ ██ ██      ██ ██
     ██     ██ ██   ███ ██  ██  ████  ████ ██      ██ ██
     ██████ ██ ██    ██ ██   ██ ███    ███ ██ ██████  ██████
     Made for AI agents. Humans get the scenic route.

     › press / to find a feature or search your library

     — Library —
     > Browse All            Every link you have saved
       Browse Unread         Links you have not opened yet
       Browse Unsorted       Links not filed in any collection
       Browse Archived       Links you have finished and put away
       Browse Collections    23 collections, and the links in each
       Browse Emails         Links that arrived by email

     — Feeds & AI —
       RSS Feeds             Your subscriptions, and the posts from each
       Insights              Links with an AI summary
       Conversations         Your past chats with Linkwise AI

     — More —
       Search                Search titles, descriptions and content
       Me                    Your account, plan and credits
       Keyboard Shortcuts    Every key, and where it works
Terminal
 linkwise v0.2.0   esc:back | /:search | j/k:nav | d:light theme | enter:select

  Library: all

  > Postgres row-level security, explained   supabase.com    unread   Sep 04
    The Unreasonable Effectiveness of Plai…  danluu.com      read     Sep 02
    Designing Data-Intensive Applications    martinkleppma…  unread   Aug 29 *
    Research acceleration: the view inside…  simonwillison…  read     Aug 28
  Scroll down for more…

Configuration

Resolved in this order, first one wins: a command flag, then the environment, then the config file, then the default.

Environment

LINKWISE_TOKENPersonal access token. Overrides the keychain.
LINKWISE_APIBase URL. Useful for pointing at a staging deploy.
LINKWISE_PROFILEWhich profile to use, for multiple accounts.
NO_COLORAny value disables colour, per the no-color.org convention.
LINKWISE_NO_UPDATE_CHECKAny value silences the once-a-day update notice.
config.toml
default_profile = "default"

[profiles.default]
api = "https://jcbgrqawrvztwsvxawda.supabase.co/functions/v1/api"
# The key lives in the OS keychain, not here. On a machine with
# no keychain the CLI falls back to this file with 0600
# permissions and says so on login rather than doing it silently.

[output]
format = "table"   # table | json
color  = "auto"    # auto | always | never

[tui]
theme = "dark"     # dark | light. Written for you by the d key.

Output and piping

Human-readable tables on a terminal, machine-readable JSON when piped. The CLI detects this rather than making you remember a flag, and --json forces it either way.

Terminal
# A table, because stdout is a terminal
linkwise ls --limit 3

# NDJSON, because it is not
linkwise ls --limit 3 | jq -r '.url'

# Force it either way
linkwise ls --collection reading --json > backlog.json

Exit codes

Distinct per failure class, so a script can tell "your key expired" from "that link does not exist" without parsing stderr. They map onto the API's error codes.

Codes

0SuccessThe command did what it said.
1ErrorAnything unclassified, including a network failure.
2UsageBad flags or a missing argument. Nothing was sent.
3AuthNo token, or it is invalid, expired or revoked. Maps to unauthorized.
4ForbiddenThe token lacks the scope, or API access is disabled. Maps to forbidden.
5Not foundNo such link, collection or tag. Maps to not_found.
6Rate limitedOver the limit. The CLI already waited and retried once. Maps to rate_limited.
7Plan limitA quota is exhausted or the endpoint is Pro only.

Commands

Each one names the endpoint it calls, so you can drop to curl for anything the CLI does not cover.

Interactive

The command with nothing after it.

linkwiseGET /v1/links

Open the full-screen library: browse everything, unread, unsorted, archived, one collection, email, one RSS feed or the links with insights, read the extracted article in place, and read past conversations with Linkwise AI. Prints help instead wherever it cannot be drawn, so a script that runs the bare command sees what it always saw.

Saving

The command you will actually type all day.

linkwise save <url> [--title] [--description] [--collection] [--tag]POST /v1/links

Save a link. Re-running with the same URL returns the existing link rather than creating a duplicate.

linkwise open <id>GET /v1/links/{id}

Open a saved link in the browser.

linkwise read <id> [--raw]GET /v1/links/{id}/content

Print the extracted reader content to stdout, as Markdown by default.

Library

Listing and organising what you have saved.

linkwise ls [--collection] [--tag] [--archived] [--limit]GET /v1/links

List links, newest first. Paginates automatically when piped.

linkwise rm <id>DELETE /v1/links/{id}

Delete a link. Soft delete, recoverable in the app.

linkwise tag <id> <tag>...PUT /v1/links/{id}/tags

Replace the tags on a link. Tags that do not exist are created.

linkwise collections [ls|new|rm]/v1/collections

Manage collections.

linkwise tags [ls|new|rm]/v1/tags

Manage tags.

linkwise highlights [ls|add]/v1/highlights

Read and create highlights.

Finding

Two search modes, one flag.

linkwise search <query> [--mode fast|hybrid]GET /v1/search

Search the library. Fast is keyword only and returns everything at once. Hybrid blends keyword with embeddings, and is the default.

linkwise search --highlights <query>GET /v1/search/highlights

Search inside your highlights instead of your links.

linkwise discover [--save <id>]/v1/discover

Print the recommendation feed, and optionally save one item into the library.

Feeds

RSS in, OPML out.

linkwise feeds [ls|add <url>|rm <id>]/v1/feeds

Manage RSS subscriptions.

linkwise feeds export > linkwise.opmlGET /v1/feeds/export

Export every subscription as OPML, importable by any reader.

AI

Pro only, and streams by default.

linkwise ask <question> [--link <id>] [--collection <id>]POST /v1/chat

Ask a question over one link, one collection or the whole library. Streams to the terminal.

Not available to an API key yet. Needs a signed-in session. The chat and speech functions resolve the caller from a user JWT, so an API key cannot reach them yet.

linkwise say <id> [--voice] [--out file.mp3]POST /v1/tts

Synthesize a link to speech.

Not available to an API key yet. Needs a signed-in session. The chat and speech functions resolve the caller from a user JWT, so an API key cannot reach them yet.

Account

Who you are and what is left.

linkwise auth loginGET /v1/me

Opens the dashboard, takes the key you paste, checks it works and stores it in the system keychain.

linkwise auth statusGET /v1/me

Show the active profile, its scopes, and which source the key came from.

linkwise auth logout-

Remove the stored key from this machine. The key keeps working until you revoke it in the dashboard.

linkwise usageGET /v1/me/usage

Plan, credits and remaining quota.

linkwise update [--run]-

Report the newest release and the command that installs it. Which command that is depends on how the binary was installed, so update works it out from where the binary actually lives rather than guessing. --run executes it.

linkwise completion [bash|zsh|fish]-

Print a shell completion script. Source it from your shell profile.