posts

A Second Cup of Bubble Tea: My pCloud in the Terminal

On this page

I’ll be honest about the order of events here, because it’s backwards.

Grifone was a problem that found a tool. I had been forgetting the games I played for years, I hated the OBS workflow, and at some point I sat down and wrote the thing that fixed it. Itch first, code second.

This one went the other way around. I finished grifone, closed the editor, and the feeling that stayed was not “problem solved”. It was “I want to write more of this”. Go and Bubble Tea, a terminal that does something useful, a static binary in ~/.local/bin. I liked building it more than I expected to, and when you like building something you start looking around the house for an excuse.

The excuse was sitting right there: my pCloud.

The Itch, Such As It Is

I keep a good chunk of my life on pCloud. Documents, photos, the archives I don’t want on the homeserver, the things that have to survive a dead disk. And every single time I needed one of those files, I did the same stupid dance: leave the terminal, open the browser, log in, click through folders, download, go find it in ~/Downloads, come back to the terminal.

There is an official Linux client, yes. It’s an Electron app that mounts a drive, and I already explained in the grifone post what I think about running Chromium to show me a screen. I don’t want a window. I’m in Omarchy, I live in the terminal, and everything else on this machine already respects that.

So: a real reason, plus a language and a framework I wanted more hours with. That’s how pcloud happened.


The Real Reason Is These Two

The Go gopher next to the Bubble Tea mascot

Gopher by Renée French, CC BY 3.0. The boba is Charm’s.

A rodent and a cup of tea with a face. Between them, they’re responsible for most of what I’ve written for fun in the last year.

Let me defend the rodent first. Go is boring on purpose, and after a decade of frameworks that reinvent themselves every eighteen months, boring is a feature I’m willing to pay for. The standard library speaks HTTP fluently, so the entire pCloud client here, authentication, listing, downloads, chunked uploads, is under 900 lines and imports nothing that isn’t in the box. Every dependency in my go.mod is there to draw the interface. Not one of them is there to reach the network. And it compiles to a single static file that I copy into ~/.local/bin, which will still run in five years because there is no runtime underneath it to rot. No interpreter, no version manager, no node_modules folder heavier than the operating system.

And then there’s the tea. Bubble Tea is Charm’s TUI framework, and it borrows the Elm architecture: a model, an update function, a view function, and that’s the whole contract. Your state is a struct. Every event, a keypress, a finished HTTP call, a tick of a progress bar, arrives as a message, and update returns a new state. View is a pure function from that state to a string.

The widgets and the colours are fine. What I actually came back for is that whole category of bug where the screen and the truth drift apart, the stale row, the spinner that never stops, the button that stays disabled after the thing finished. In that model those cannot happen. The screen has nothing to disagree with.

Why a Terminal, Though

I like TUIs. I like them the way some people like mechanical watches, which is to say more than the practical case strictly justifies. Anyone who read the six parts I wrote about programming the Commodore 64 already knows this about me: I find something genuinely beautiful in software that is honest about the machine it’s running on.

There is a practical case too. A TUI opens instantly, because there is no window to negotiate, no splash screen, no renderer warming up. It works over SSH without anyone having to invent a protocol for it, so the same binary that runs on my laptop runs on the homeserver through a phone if it has to. It’s keyboard-only, which is slow for ten minutes and then faster than a mouse forever. And it cannot be redesigned by somebody’s product team next quarter, because it’s mine and it lives in a text file.

There’s also a constraint I’ve come to love: everything has to fit in a grid of characters. You cannot solve a layout problem by adding a panel, so you’re forced to decide what matters. In pcloud that decision is visible in the code. The details panel only appears from 96 columns, then the modified column goes, then the size column, then the hint footer. The breadcrumb is the last thing to be trimmed, and it’s trimmed from the left, so the folder you’re standing in stays on screen no matter how small the window gets. A GUI would have let me avoid that conversation. The terminal made me have it.

Anyway. Two mascots, one very long justification.

The Stack (Short Version)

make install, and that’s the installation.

The rest of the sermon, the goroutines, the no-Electron rant, the reasoning about why a tool that lives in Omarchy has to live inside the terminal, is all in the grifone post, and nothing changed in a week.

What’s worth mentioning is the difference, and the difference is what’s missing. Grifone has SQLite, a background worker, a systemd service, a whole life cycle of states. pcloud has none of that. It has a token in ~/.config/pcloud/config.json at mode 0600, and everything else lives in memory while the program is open. There is nothing to persist, because the source of truth is pCloud’s servers and not my laptop. The smallest version that works is the right version.

What It Actually Does

You run pcloud and you get your drive.

The pcloud TUI showing the Backups folder with three items, the quota bar, and one item held in the paste buffer

That’s the whole interface. Breadcrumb on the left, how much of the plan I’ve burned through on the right, the folder in the middle, and a footer that reminds me of the keys I use least. Notice [moving 1] in the status line: that’s the paste buffer holding an item, waiting for me to walk somewhere and press p.

Folders sort before files, j and k move, Enter opens, h goes back. Vim keys, because of course.

From there it’s the file manager vocabulary everyone already knows. Space marks a row, a marks everything, / filters the current folder, s cycles the sorting between name, size and modified. y copies, m cuts, p pastes, and the paste buffer works on the marked set if there is one and on the highlighted row if there isn’t. c creates a folder, R renames, x deletes after asking. d downloads. u opens a local picker so I can choose what to send up, and the picker remembers where I left it for the next time.

S is the one I use more than I expected: it asks pCloud for a public link to whatever is highlighted and copies it to the clipboard (wl-copy on Wayland, xclip or xsel on X11). It also prints the link in the status line, always, because a machine without a clipboard tool shouldn’t leave you generating a second link to find the first one.

Transfers, Or: the Part That Was Hard

Browsing a remote folder is easy. Moving bytes is where the bugs live.

One transfer runs at a time and the rest queue behind it. Mark ten files, press d, and you get ten downloads in order while the interface stays responsive: you can keep browsing, filtering and navigating while things move. This is the one place where Go stopped being a preference and started being the reason the thing works. A transfer running in a goroutine and reporting back as messages is about fifteen lines, and the interface never learns that anything unusual is happening. The transfer block shows the current file, the position inside a recursive transfer, percentage, bytes, rate and ETA, and ctrl+x cancels the running one or clears the queue.

Interrupted transfers resume, in both directions. Downloads keep their .part file and continue with an HTTP range request. Uploads go through pCloud’s chunked upload API, with the upload id recorded on disk, so an upload picks up at the offset the server actually stored even if I killed the program and went to bed. And both directions skip what is already at the destination, which means an interrupted recursive transfer is fixed by running it again, with no flags and no resume subcommand.

Now the confession.

The upload progress bar used to hit 100% while the transfer had barely started. I was measuring the request body, which means I was proudly reporting how fast the kernel accepted a buffer I had already built in memory. A beautiful, fast, completely fictional number. It now counts only the bytes pCloud has acknowledged, one chunk at a time, which is slower to watch and true. Progress bars that lie are worse than no progress bar, because you plan around them.

The Theme Trick, Now Even Lazier

In grifone I was a bit too proud of the TUI detecting the active Omarchy theme and repainting itself to match. It works, and I still like it.

Here I did less and got the same result. Omarchy themes work by repainting the terminal’s ANSI 0-15 palette, so if the application never writes a hex colour and only ever asks for ANSI indexes, the theme retints the whole thing for free. No detection code, and no palette of my own to maintain. Change the theme and pcloud changes with it, because it never had opinions about colour in the first place.

The blues and greens in that screenshot are not mine. They’re whatever theme I had running that morning.

The Honest Part

It defaults to pCloud’s EU API, because that’s where my account is. There’s an environment variable for the other region, and that’s the extent of my testing, which is to say none.

It is not a mount. It doesn’t pretend to be a filesystem, ls in another terminal will not show you your cloud, and I have no plans to go down the FUSE road. It’s a browser and a transfer tool, and that covers everything I do with those files.

And it’s one person’s tool again. Built on my machine, for my terminal, for my account, for a workflow that has an audience of exactly one. If it works for you, great, the license is MIT and the repository is right there.

So, was it worth writing a file manager in 2026, when a browser tab already does the job?

For the files, honestly, it’s a small win. Ten seconds saved, a few times a week. The real result is the one I admitted in the second paragraph: I wanted more hours with Go and Bubble Tea, I wanted to find out whether the second TUI came out cleaner than the first, and it did. Fewer moving parts, fewer states, fewer colours, less code doing more. The file browser is the receipt.

Next time I’ll need a better excuse.

And that’s it.