██╗ ██╗███████╗███████╗████████╗
╚██╗██╔╝██╔════╝██╔════╝╚══██╔══╝
╚███╔╝ █████╗ █████╗ ██║
██╔██╗ ██╔══╝ ██╔══╝ ██║
██╔╝ ██╗███████╗███████╗ ██║
╚═╝ ╚═╝╚══════╝╚══════╝ ╚═╝
/\_/\
( o.o )
> ^ < ready when you are ✦
post to x from your terminal. no api keys, no browser tab. browse your timeline with inline images, get reply notifications, answer in place, like, done.
Warning
xeet uses the same internal endpoints the x website uses, with your browser's session cookies. that probably violates x's terms of service. it's unofficial, not affiliated with x corp, and could break at any time.
╭────────────────────────────────────────╮
│ what are you thinking? │
╰────────────────────────────────────────╯
12/280 • enter to xeet
works on macos and linux. windows isn't supported, open an issue if you want it.
with nix:
nix profile install github:melqtx/xeet # install
nix run github:melqtx/xeet # or just try itwith go (1.26.5+, for the patched tls stack):
go install github.com/melqtx/xeet@latestfrom source:
git clone https://github.com/melqtx/xeet.git
cd xeet
make install # builds and installs to /usr/local/bin/hacking on it? nix-shell (or nix develop) gets you go, gopls, and
staticcheck; make dev runs fmt, vet, build, and tests.
or grab a release: download the archive for your platform from
releases, check it against
checksums.txt, drop the binary on your PATH:
tar -xzf xeet_*_$(uname -s | tr A-Z a-z)_$(uname -m | sed 's/x86_64/amd64/').tar.gz
sudo install -m 0755 xeet /usr/local/bin/log into x.com in your browser (chrome, helium, firefox, brave, or zen), then:
xeet auththe picker marks which browsers it can already see an x.com session in, so you don't have to guess:
where are you signed in to x.com?
› Chrome session found
Helium not detected
Firefox session found
xeet borrows that browser session. it never asks for your password. macos may prompt for keychain access; linux may ask you to unlock gnome keyring / secret service / kde wallet. snap and flatpak browser installs are detected, and multiple profiles work. whatever you pick is verified with x before it replaces a session that already works, and a failure lets you try another browser without starting over.
scripting it? xeet auth --browser firefox skips the picker entirely.
then just:
xeet # browse your timeline, images and all
xeet --following # start on the following feed
xeet --bookmarks # start on your bookmarks
xeet search "go tui" # search posts and browse results
xeet --barebones # text-only feed
xeet --compose # skip the feed, open the composer
xeet post "hello from my shell" # one-shot post
echo "piped in" | xeet post # reads stdin
xeet post "photos" -i one.png -i two.jpg
xeet post --image meme.png # image-only, no text
xeet post "a reply" --reply 1234567890if your session feels off:
xeet whoami # which account is connected
xeet doctor # session metadata + one authenticated read
xeet doctor --offline # local metadata only, no network
xeet logout # delete xeet's copy of the sessiondiagnostics print a short fingerprint and the browser/profile, never the cookie values.
xeet --help groups the commands by when you need them and shows a worked
example for each; xeet <command> --help goes deeper on any one of them.
composer
| key | does |
|---|---|
enter |
post |
alt+enter / ctrl+j |
line break |
ctrl+v |
paste image or text from clipboard |
ctrl+o |
attach an image path (drag a file in) |
tab |
switch between editor and attachments |
← → |
select an attached image |
delete / ctrl+x |
remove selected image |
B |
after a restricted/unclear result: open the text in x |
F1 |
help |
ctrl+c / esc |
quit (drafts ask first) |
up to four png/jpeg/gif/webp images per post, or one mp4/mov video (up to 512 MiB, uploaded in chunks straight from disk with live progress). the composer shows real format, dimensions, and size before anything uploads. unfinished drafts autosave (including clipboard images) and come back next time.
timeline
| key | does |
|---|---|
j / k / arrows |
move (ctrl+d/ctrl+u jumps five) |
tab / shift+tab |
cycle forward / backward through For You, Following, and Bookmarks |
f |
switch between the for you and following feeds |
b |
switch between bookmarks and the for you feed |
/ |
search posts |
enter |
open the post's replies |
e / space |
read a truncated post in full |
i |
zoom the post's image to the whole terminal |
A |
read image descriptions (works with previews off) |
l |
like / unlike |
r |
reply in place |
o |
open in browser |
y |
copy link |
P |
write a new post |
R |
refresh in place, new posts stack on top, you keep your spot |
n |
open replies and mentions |
N |
reply directly to the active notification popup |
? |
key guide + which image renderer is active and why |
more posts load automatically near the bottom; search results are just
another feed, so like, reply, and thread all work there too. inside a
conversation, j/k moves through replies, r replies to the selected
item, R reloads, and esc drops you back exactly where you were in the
timeline.
while the TUI is open, xeet checks for new replies and mentions every one to
five minutes, slowing down while idle. a small in-terminal card lets you press N to answer immediately,
n to open the notification panel, or x to hide the card. the panel keeps
unread state across restarts and supports the same reply, conversation,
browser, copy, like, and media actions as the timeline. this is active-session
polling—not a desktop notification service—and does not run after xeet exits.
themes
colors are configurable; the layout never changes. pick from tokyonight (default), catppuccin, gruvbox, nord, rosepine, or mono:
xeet theme # browse the palettes with a live preview
xeet theme nord # save a default in ~/.xeet.yaml
xeet theme --list # just the names, current one marked
xeet --theme mono # try one for a single runxeet theme on its own opens a picker that recolors itself as you move:
swatches, a couple of sample posts, and the composer footer, all in the
palette under the cursor. enter saves it, esc keeps what you had.
details: how images render
images are prefetched around your position so scrolling lands on
already-loaded previews. videos and gifs show their poster frame with a ▶
chip.
- direct ghostty and kitty sessions use kitty unicode-placeholder graphics
- iterm2 and wezterm use the iterm2 inline-image protocol
- tmux 3.3+ gets native kitty graphics too when
allow-passthroughis on and the terminal running tmux is kitty or ghostty; otherwise (and in zellij) the portable ansi renderer takes over
in auto mode xeet verifies kitty graphics before trusting them: apps that
merely embed libghostty (cmux, for one) inherit a ghostty TERM but don't
actually render placeholder graphics, so on macos xeet checks the host
app's bundle id and falls back to ansi inside embedders. a startup probe
also asks the terminal itself to confirm the protocol, so anything that
advertises graphics it can't load falls back instead of leaving blank gaps.
if a frame ever glitches, ctrl+l redraws.
pick a renderer explicitly when needed:
xeet timeline --images auto # native when the terminal confirms it; else ansi
xeet timeline --images native # trust the terminal, skip the probe
xeet timeline --images ansi # portable block preview
xeet timeline --images off # no previewson wayland, install wl-clipboard for clipboard paste and link copying.
x11/xwayland use the x11 clipboard. over ssh, clipboard degrades gracefully
and ctrl+o file attachment still works.
xeet reuses the x.com session already in your browser and speaks the same
unsupported internal graphql endpoints the website does. the imported
auth_token and ct0 cookies grant account-level access, so treat them
like a password. they live in the macos keychain or linux secret service, never
in the yaml config file. xeet logout deletes xeet's copy (your browser
stays logged in).
details: query ids and retries
x rotates its internal graphql operation ids periodically; xeet discovers and caches current ids for posting, timelines, likes, and unlikes. if CreateTweet discovery fails, xeet explains how to set the id manually from browser devtools.
CreateTweet mutations are never retried after a transient or ambiguous
outcome. (a request explicitly rejected as an unknown persisted-query id is
refreshed and sent once with the current id.) if x returns an unclear
result, xeet performs one read-only timeline check; if it still can't prove
the post landed, it preserves your draft and asks you to check your profile
before retrying. the details line contains only a response-shape
fingerprint, status, type names, and rate-limit metadata, never the draft
or session cookies.
to compare a successful browser post with xeet's request without exposing
secrets, export a har from devtools and run xeet inspect-har file.har.
it prints names and structural keys only, never values, post text, or
response bodies. keep the har local; it contains session cookies.
no scheduling, no bulk posting, no scraping, no automated engagement, no mass-posting. out of scope, will not be added.
MIT · not affiliated with x corp · bugs go here · security goes here
