Documentation

Everything needed to install poim, sign in, and add your first server.

Install

The script detects your platform, verifies the SHA-256, and installs to /usr/local/bin when it can write there, otherwise to ~/.local/bin.

curl -sSL https://poim-5vk.pages.dev/install | bash
poim

The script enforces TLS 1.2 or newer and refuses a plain HTTP URL. On a checksum mismatch it aborts without installing anything.

Remove it with curl -sSL https://poim-5vk.pages.dev/uninstall | bash.

Authentication

The CLI has no password. It authenticates with an API token you create in the web panel. There are two ways to supply one.

Token

Create one under Settings, then API Tokens. Choose the write scope, copy the value, then run poim and pick Login with token. The value is shown exactly once.

The token is stored at ~/.config/poim/credentials.json, written atomically at mode 0600 inside a 0700 directory. The CLI refuses to read the file if its permissions are looser than that.

Device code

For terminals without a paste buffer, pick Login with device code. The CLI shows a code such as ABCD-1234 and a URL. Enter the code in the panel and approve it. The CLI polls every two seconds and signs itself in. The code expires after five minutes.

When a token stops working

A revoked or expired token is detected on the next call. The CLI discards it rather than retrying, and returns you to the login menu.

Keyboard and mouse

InputAction
Up / Ctrl+PMove up, wrapping at the top
Down / Ctrl+NMove down, wrapping at the bottom
Enter, RightActivate the highlighted row
Left, EscapeBack one level
Escape on the main menuOpen the quit confirmation
Tab, Shift+TabJump between sections
Home / EndFirst and last row
Page Up / Page DownScroll by ten rows
Any characterFuzzy filter
BackspaceDelete one filter character
Ctrl+UClear the filter
Ctrl+CQuit
Ctrl+LClear the screen
Mouse clickSelect the row under the cursor
Mouse wheelScroll three rows
Mouse hoverHighlight the row

Inside a live terminal, mouse reports are forwarded to the remote program, so htop and vim behave normally. Escape leaves the terminal and returns to the previous menu.

The agent

The agent is an 8 MB static Go binary. It makes outbound WebSocket connections only, so installing it opens no port on your server and nothing is added to your firewall.

In the CLI, open VPS Manager, then Add VPS, and run the command the panel prints:

curl -sSL https://poim-5vk.pages.dev/agent/install.sh | \
  POIM_PANEL=https://poim-5vk.pages.dev POIM_ENROLL_CODE=XXXX-XXXX bash

It installs a systemd unit. Review it before you trust it:

cat /etc/systemd/system/poim-agent.service
journalctl -u poim-agent -f

Remove it from the server with:

systemctl disable --now poim-agent
rm /etc/systemd/system/poim-agent.service /etc/poim/agent.conf

API tokens

A token has a name, a scope, an optional expiry and a status. The value is 64 characters, stored only as a SHA-256 digest, and returned exactly once at creation or rotation.

ScopeAllows
readRead-only. GET requests succeed, writes are refused with a permission error.
writeRead plus adding, editing and removing servers, terminals, file transfer.
adminWrite plus user, team and system management.

The current scope appears in the CLI status bar next to your username. Using a token outside its scope produces a clear permission error rather than a silent no-op.

Rotating a token issues a new value and invalidates the old one immediately. Revoking is permanent.

Architecture

Four layers. The rule that matters is in the middle.

1A  browser          1B  terminal (poim)
       \                    /
        \  HTTPS + WSS      /
         2  PANEL: relay and sync hub
            no shell, no PTY, no command execution
             |
             |  outbound WebSocket only
             v
         3  agent on your server
            real PTY, real metrics, argv to execve

The panel forwards opaque frames between a client and an agent. It does not parse them and cannot execute them. Compromising the panel yields token hashes and VPS metadata, not a shell on your servers. Compromising one server does not affect the panel or any other server.

The panel is a Cloudflare Worker with a Durable Object hub and D1 storage. The site is Cloudflare Pages. Both are free and both include TLS.

Security model

  • Tenant isolation. Every database query filters on user_id. There is no unscoped read path.
  • Token storage. SHA-256 digests only. The plaintext is never persisted and never logged.
  • No shell in the command path. argv goes to execve. Injection is structurally impossible, not filtered.
  • Bounded execution. Every remote command has a timeout. The agent caps concurrent sessions.
  • Agent hardening. The systemd unit sets NoNewPrivileges, ProtectSystem, ProtectHome and an explicit capability bounding set.
  • Supply chain. Release archives carry SHA-256 sums. Both installers abort on a mismatch rather than proceeding.
  • Read the full threat model, including six known limitations, in SECURITY.md.

Environment variables

VariableEffect
POIM_PANELPanel origin to talk to.
POIM_LANGForce a language. Overrides the stored preference.
POIM_THEMEForce a theme: dark, light, dracula, nord, solarized, monokai, gruvbox, tokyo-night.
POIM_CONFIG_DIRWhere credentials live.
POIM_DOWNLOAD_BASEInstaller: where release archives are fetched from.
POIM_INSTALL_DIRInstaller: where the binary is written.
NO_COLORDisable colour entirely. Respected deliberately.
COLORTERMSet to truecolor or 24bit for 24-bit output.
POIM_DEBUGPrint resolved configuration at startup.

Example:

POIM_LANG=vi POIM_THEME=nord POIM_DEBUG=1 poim

Troubleshooting

Menus are cut off

The terminal is below 80x24. The CLI shows a resize notice rather than a broken frame. Enlarge the window.

No colour in the menus

NO_COLOR is set, or TERM=dumb. Both are honoured. Use COLORTERM=truecolor.

A VPS stays pending

The agent has not connected. On the server run journalctl -u poim-agent -f. The usual cause is an outbound filter blocking WebSocket.

Permission denied on an action

Your token scope does not cover it. A read token cannot write. Create a token with the write or admin scope.

The web panel shows a VPS the CLI does not

Both hold a WebSocket to the panel and update on events. If one is stale the connection dropped. Reconnect by restarting that surface.

Version history

1.0.0 is the first release. See CHANGELOG.md in the repository for the full entry.

Browser support: current Chrome, Edge, Firefox and Safari. The site works with JavaScript disabled; the download page needs it for the QR code and copy buttons.