Skip to content

Quick commands & terminal

They arrive with the project

You rarely start from an empty list. XTop reads the project's own manifest files and offers what it finds as ready-made commands, so an imported project turns up with npm run dev already in its menu instead of a blank list you have to retype from memory.

Where they are read from

File in the projectWhat becomes a command
package.jsonevery entry under scripts, minus npm's own lifecycle hooks (postinstall, prepare, …)
composer.jsonevery entry under scripts, minus composer's pre- / post- hooks
artisanphp artisan serve and php artisan migrate
Makefileevery target
pyproject.tomlthe entry points under [project.scripts] or [tool.poetry.scripts] — or, if there are none, install and test
go.modgo run ., go build ./..., go test ./...
Cargo.tomlcargo run, cargo build, cargo test
docker-compose.yml (or compose.yaml)compose up, compose down, compose logs

The runner matches your lockfile. A package.json script becomes pnpm dev when there is a pnpm-lock.yaml, yarn dev for yarn.lock, bun run dev for a bun lockfile, and npm run dev otherwise. A packageManager field beats all of them, because corepack enforces it anyway. Python works the same way: uv run with a uv.lock, poetry run with poetry.

Every detected command is tagged with the file it came from, so a list holding both npm run test and make test still tells you which is which.

Where you are asked, and where you are not

  • Importing a folderAdd the commands found in each project is ticked by default, and the scan shows a count next to each project. Everything found is added.
  • Adding or editing a single project — the list appears in the dialog with everything ticked, and you untick what you don't want.
  • Any time laterDetect commands inside Manage commands… re-reads the project.

Detection never duplicates. A command whose line already exists on the project is skipped, so running it again on a project you imported months ago adds only the scripts that have appeared since.

Detected commands land in the private scope and run in the project's default location. Both are editable afterwards, like any other command.

Reading, not running

Detection only reads files — nothing in the project is executed. A package.json with a trailing comma, or a Makefile written for GNU extensions, simply contributes nothing and the other files are still read. At most 40 commands are taken from one project.

Three scopes

A command belongs to exactly one of three scopes, and the scope decides where it shows up:

ScopeOffered inStored on
Privateone projectthat project
Collectionevery project in that collectionthe collection
Globalevery projectonce, at the top level

Global commands are stored once rather than copied into every project, so editing npm install updates it everywhere at the same time.

A project's command menu: its own commands first, then the collection's, then the global ones — each tagged with its scope.
A project's command menu: its own commands first, then the collection's, then the global ones — each tagged with its scope.

Manage them per project with ⌘ → Manage commands…, or for a whole collection with the button in the collection header.

Each command has a label (what you see), the command line itself, and where to run it: the project's default, PowerShell, cmd, Git Bash, WSL, or an external terminal window.

Running one

Clicking a command opens the terminal window and starts a session.

One tab per session. Green dot = running, grey = exited.
One tab per session. Green dot = running, grey = exited.
  • A tab per session. Switching tabs does not lose anything — each session keeps its own scrollback.
  • The dot is the state. Green while the process is running, grey once it has exited.
  • ■ Stop kills the running command; the tab and its output stay.

The shell follows the project's environment: wsl.exe -d <distro> --cd <path> for WSL projects, PowerShell for Windows ones.

Real sessions

These are interactive terminals, not log windows: you can answer a prompt, hit Ctrl+C, run vim if you insist.

On the rare machine where the interactive backend cannot load, the app falls back to streaming output with no keyboard input — and the terminal window says so in a banner, rather than leaving you wondering why typing does nothing.

Keys inside the terminal

KeyAction
Ctrl+CCopies when text is selected; with no selection it goes through as SIGINT, which is what the key is mostly pressed for
Ctrl+Shift+CAlways copies
Ctrl+VPaste
Ctrl+click a URLOpen it in the browser. The modifier is deliberate — dragging a selection across a link should never launch a browser