Skip to main content

Commands

Every command available at the REPL, alphabetical. For the size/price grammar that fills the <size> / <price> slots below, see Sizing & prices. For chain operators (; / &), loops, and conditionals, see Workflow.

Web vs standalone — most commands run identically in the in-app web CLI and the standalone binary. Exceptions are flagged with standalone-only. See Web CLI subset for the full split.


Trading

buy / sell   (aliases b, s)

Place a market, limit, or best-priced order on the focused symbol, with optional reduce-only / post-only / bracket stop.

FormResult
buy $100Market buy $100 notional
buy 100$Same — suffix form
buy 50%bal50% of free balance
buy SYMBOL $100Symbol override (leading or trailing)
buy WIF,ENA $100Multi-symbol fan-out (comma list)
buy $100 at 50000Limit at absolute price
buy $100 at +100 / at 1%Limit at relative / percent offset
buy $100 at 50000 reduceReduce-only limit
buy $100 at 50000 poPost-only limit
buy $100 bestLimit at best bid (buy) / best ask (sell)
buy $100 stop 49500Bracketed entry with attached stop loss

Safety: Bare numbers ≥ 100 with no at are rejected — the parser asks "did you mean at <price>?" so a typo can't coerce to market silently. fatfinger checks fire per-order.

Source: packages/cli-core/src/commands/trading/BuyCommand.ts, SellCommand.ts.


stop

Reduce-only stop loss or take-profit. Decided by trigger price vs mark.

FormResult
stop at 18500Market stop for full focused position
stop at -1%Same, relative to entry
stop buy at +100Explicit side (when hedge-mode is ambiguous)
stop $500 at 1%Partial-size stop
stop sell 0.01 at -100 ETHUSDTSymbol override
stop at -1% trigger 18500Limit stop — trigger ≠ limit price

Source: StopCommand.ts.


close   (alias c)

FormResult
closeFull close, focused symbol
close SYM,SYM2Full close, named symbols
close long / shortHedge-side disambiguator
close longs / shortsAll long / short positions globally
close all / allposEvery open position
close 50% / close $100Partial close of focused
close longs 50%Partial close of every long globally

Global variants (longs, shorts, all, allpos) honor the whitelist. Source: CloseCommand.ts.


cancel   (alias x)

Filters compose: side × kind × reduce-only × position-selector × range × id.

FormResult
cancelAll orders, focused symbol
cancel ETHUSDTAll orders, named symbol
cancel buys / sellsSide filter
cancel limits / stopsKind filter
cancel ro / xroReduce-only / excluding reduce-only
cancel buys ro ETHUSDTCompose filters
cancel top 3 / bottom 5Sorted by price (top = highest)
cancel first 2 / last 1Sorted by last update
cancel from 49000 to 50000Price-range, limits only
cancel <id> / <id-prefix>Specific order (prefix ≥ 3 chars)
cancel <id1>,<id2>,…Comma list — what cancel @capturedVar becomes

Source: CancelCommand.ts.


bump

Shift open limit orders by a price delta. Stops are untouched.

FormResult
bump +10Shift every limit by +10, focused
bump ETHUSDT +10Symbol override
bump buy +10 / sell -5Side filter
bump top 3 +5Top-N price selector
bump 100 --buysLegacy flag form — still accepted

Source: BumpCommand.ts.


move   (alias mv)

Move a specific limit order by ID (full or unique prefix).

FormResult
move <orderId> 50000Absolute
move <orderId> +10 / -5Relative offset

Source: MoveCommand.ts.


scale

Ladder N limit orders between two prices, split evenly.

scale <buy|sell> <size> into <N> from <p1> to <p2> [reduce] [taker] [SYMBOL]
ModifierEffect
reduceReduce-only
takerDrop the default post-only TIF
cubic / icubicAccepted but distribution is still even (no-op today).

Examples:

scale buy 10 into 10 from 31000 to 32000
scale sell $1000 into 25 from 50000 to 48500 reduce
scale buy $1000 into 10 from -0.25% to -1% ETHUSDT

Source: ScaleCommand.ts.


swarm

Fire N concurrent market orders summing to <size>.

swarm <buy|sell> <size> into <N> [reduce] [irregular] [SYMBOL]
ModifierEffect
irregularRandomise slice weights in [0.5x..1.5x]
reduceReduce-only

Errors before any order is sent if a slice would round to zero. Source: SwarmCommand.ts.


chase

Passive limit at top of book that re-pegs to best bid / ask. Runs as a background task — list with chasers, kill with kill <id>.

FormResult
chase buy $100Chase on focused symbol
chase sell 50% reduceReduce-only chase
chase buy WIF,ENA $100Multi-symbol fan-out
chase sell %all% 20% reduceAll positions on the implied side
chase buy $100 to 1%Terminate-at distance (price-walk guard)
chase buy $100 to 49500 / to $50Same, abs / dollar form
chase cancelStop newest active chaser + cancel rest order
chase cancel keepStop chaser but leave the order in book

Source: ChaseCommand.ts.


chasers

FormResult
chasersList active + recent chasers
chasers cancel <id>Cancel by id-prefix
chasers cancel allCancel all chasers

Source: ChasersCommand.ts.


twap

Spread an order over time. Three forms accepted.

twap buy <size> [SYMBOL] into <N> over <minutes> [reduce] [chaser]
twap chase buy <size> into <N> over <M> [reduce] [timeout <pct> <action>] [fail <action>]
twap buy <size> duration=1h slices=10 # legacy key=value form

twap chase runs a chaser per slice. Defaults: timeout 50% market, fail market. Action ∈ {market, next}. <pct>(0, 100].

ManagementResult
twap listList all TWAPs
twap stop <id-prefix>Stop by id-prefix
twap stop allStop all TWAPs

Gotcha: reduce is parsed and gates fatfinger checks, but is not yet threaded through to slice placement. Treat twap … reduce as a fatfinger-bypass marker, not a hard guarantee per slice.

Source: TwapCommand.ts.


nuke

Cancel + close everything. Up to 3 retries internally.

FormResult
nuke / nuke allCancel all orders + close all pos
nuke positionsClose all positions only
nuke ordersCancel all orders only

Source: NukeCommand.ts.


leverage   (alias lev)

FormResult
leverageRead (only if a position is open)
lev 5Set 5x on focused symbol
leverage 10 ETHUSDTSet 10x on named symbol

Strings like -5, 5x, 2..5 are rejected loudly. Source: LeverageCommand.ts.


max

Largest possible order size given free balance × current leverage. Falls back to 1x with a note if no position is open. Source: MaxCommand.ts.


orders   (alias o)

FormResult
orders / oOpen + creating orders, focused symbol
orders --all / -aMulti-account view across all symbols

Source: OrdersCommand.ts.


positions   (alias ps)

FormResult
positions / psFocused symbol
positions long / shortSide filter
positions --all / -aMulti-account view, all symbols

Source: PositionsCommand.ts.


position   (aliases p, pos)

Card-style display for a single position (size, entry → mark, uPnL, liq, leverage). pos btc accepts fuzzy symbol match. show modifier is a no-op. Source: PositionCommand.ts.


margin   (aliases balance, bal)

FormResult
margin / balAccount margin / balance
bal --allMulti-account view

Source: MarginCommand.ts.


spot

Spot balances when the exchange exposes them. spot --all for multi-account. Source: SpotCommand.ts.


last-price / mark-price / index-price

Read live price from the focused or named ticker(s).

Aliases (in order)Reads
last-price, lp, llast trade
mark-price, mp, mmark
index-price, ip, iindex

All accept a trailing comma-separated symbol list. Source: PriceCommand.ts.


echo   (alias say)

Print args after variable substitution. echo @hi does ratchet a tracker on read; print @hi does not. Source: EchoCommand.ts.


chart   (standalone-only)

ASCII candlestick chart with position + open-order overlays.

FormResult
chart1h, 30 candles, focused
chart 15mTimeframe override
chart 4h 50Timeframe + bar count (capped at 100)
chart 1d ETHUSDTSymbol override

Source: ChartCommand.ts.


Session

symbol   (aliases sym, switch)

Change the focused symbol: switch BTCUSDT. Bare instrument typed at the prompt (BTCUSDT<enter>) is shorthand for this. Source: SymbolCommand.ts.

markets   (aliases mkt, m)

List available markets in a 4-column grid. Source: MarketsCommand.ts.

status   (alias st)

Account / exchange / symbol of the current session. Source: StatusCommand.ts.

connection   (aliases conn, ws)

FormResult
connWS health, current account
conn --allMulti-account
conn reconnect / ws reconnectForce a fresh WS connection

Source: ConnectionCommand.ts.

help   (aliases ?, h)

help for general index, help <cmd> for a specific command.

prompt

Configure the REPL prompt format. See Output → Prompt. Source: PromptCommand.ts.

clear   (alias cls)

Clear screen.

login, logout, auth   (standalone-only)

OAuth flow. whoami shows the current logged-in user. Listens on port 9876 for the OAuth callback.

logs / log / clearlogs   (standalone-only)

FormResult
logsPrint the log file path
logs clear / clearlogsWipe ~/.tealstreet/cli.log

audit   (standalone-only)

Durable, append-only log of write-ops (orders / cancels / closes / leverage). Off by default. When on, each write-op appends a JSON line to ~/.tealstreet/audit.log with timestamp, account, the command, and the result (ok + order IDs, or the error) — including failed attempts.

FormResult
auditShow status + log path
audit onEnable the write-op audit log
audit offDisable
audit tail [N]Print the last N entries (default 20)

Reads (positions / orders / watch / etc.) are never logged — only exposure-affecting writes. Script-driven writes are captured too: script:<name> internal write ops (orders, cancels, updates, closes, leverage) are logged (prefixed [script]) along with the named invocation. Every write from one typed line shares a runId (shown as run=<id> by audit tail), so all the orders from a repeat 10 or a single script run group together. Fills are attributed back to the order that produced them: when a tracked order fills, a [fill] entry is logged with the same runId and a ← <originating command> pointer (unattributable fills — placed before auditing was on, or in a prior process — are skipped). The log is append-only but size-capped: past ~5 MB the live audit.log rolls to audit.log.1 (one backup) so disk use stays bounded; audit tail reads from the end and dips into the backup when needed. Source: audit-log.ts, script-adapter.ts.

Controls the persistent bottom status footer — a pinned line at the bottom of the terminal, above which your prompt and output live, showing always-on live data (connection, account, symbol, balance, PnL, clock, …). It updates in place ~once a second and is a glanceable companion to the transient top alert bar. On by defaultfooter off opts out (persisted). The setting + layout live in ~/.tealstreet/config.json (footer block — see config files).

FormResult
footerShow on/off state + the current segment layout
footer on / footer offEnable / disable the footer (persisted)
footer listList the configured segments with their indices
footer resetReset the layout to the default set
footer add <type> [right]Append a segment, optionally to the right-aligned group
footer add script <name> [right]Append a custom-script segment (see below)
footer remove <index>Remove the segment at the given index

Built-in segment types: connection, account, symbol, balance, pnl (focused-symbol uPnL $ + %), totalpnl, position, mark, last, funding, clock, version (the CLI version — in the default layout; footer remove it if you don't want it), text. All read already-streamed state — no extra API calls. Each segment can set group (left / right) and a color override in the config file.

script segments show the return value of one of your custom scripts: footer add script <name> runs the named script ~once a second and renders the string it returns. These runs are hard read-only — capabilities are forced so the script cannot place or cancel orders or call runCommand (any such call fails); only reads work. They're also silent (the script's print() output is suppressed — only the return value is used) and time-boxed (a slow or throwing script shows a small error token like !myfooter and never blocks the footer).

Example footer script (~/.tealstreet/scripts/risk.ts):

export const meta = { name: 'risk', targets: ['core', 'cli'], capabilities: { accounts: [], writes: [] } };
export default function (_args, api) {
const p = api.getPositions()[0];
return p ? `risk ${p.notional.toFixed(0)}` : 'flat';
}

Source: footer.ts, footer-segments.ts, scroll-region.ts, repl.ts (runFooterScript).

update   (standalone-only)

update runs the binary updater. update --check / -c does a version check only.

exit / quit   (standalone-only)

Stops monitors, listener, exchanges, then process.exit(0).


Accounts

account   (aliases acc, use)

FormResult
account mybybitSwitch focused account by name
account 1 / account -# 1By 1-indexed number
account enable acc1 acc2Start streaming for named accounts
account enable --allStart streaming for every account
account disable acc1 / --allStop streaming

Source: AccountCommand.ts.

accounts   (alias config)

FormResult
accounts / accounts listList all configured accounts
accounts add -n <name> -e <ex> -k <key> -s <secret>Add — use --tealstreet-proxy [--proxy-region <region>] to match web "Use Tealstreet IPs"
accounts add -n <name> -e paradex -k <subkey-private-key> --paradex-address <addr> --subkey-address <addr>Add Paradex with the same credential fields as web
accounts import <json>Import from web export JSON
accounts remove <name> / --allRemove (tombstoned for 30 days)
accounts enable <name…> / disable <name…> / --allBulk streaming control
accounts enabled / streaming / activeList only streaming accounts
accounts helpFull reference

Manual account creation is validated by the shared safe-cex account contract used by web. Exchange-specific fields include --passphrase, --application-id, --api-key-index, --omni-key-seed, --paradex-address, --subkey-address, --subaccount, --grvt-signer-address, --grvt-trading-account-id, --vault, --tealstreet-tunnel, and --tealstreet-ws-route.

Source: AccountsCommand.ts.

exchanges   (alias ex)

List supported exchanges with required credential fields. Source: ExchangesCommand.ts.


Tasks (in-process)

tasks

Unified list of in-process chasers + TWAPs, insertion order.

kill   (aliases fuck, terminate)

FormResult
killMost-recent active task
kill lastSame
kill <id-prefix>By id-prefix
kill allEvery running task

Source: KillCommand.ts. fuck / terminate are ichibot-parity aliases.


History

history

FormResult
historyLast 50 commands
history 100Last N — capped at 200

Source: HistoryCommand.ts.

Persisted to ~/.tealstreet/cli_history (standalone) or a jotai atom (web).


Variables, aliases, workflow

Defined and detailed in their own pages — listed here for completeness:


Safety

Defined in Safety. Listed here:

  • simulation / sim / dry-run
  • fatfinger / unfatfinger
  • budget — cumulative notional cap (budget $5000 / budget off / budget reset)
  • whitelist / unwhitelist

Watch modes   (standalone-only)

AliasLong formsShows
wawatch account, watch accMargin + positions + orders combo
wpwatch positions, watch posLive positions table
wowatch orders, watch ordLive orders table
wmwatch margin, watch balance, watch balLive margin
watch chart   (shorthand watch c …)Live ASCII chart
watch chaseLive chaser status panel

All of wa/wp/wo/wm accept --all / -a for multi-account view. Exit: Escape or Ctrl+X. Refresh: 1000 ms.

See Output → Watch modes for terminal-recovery quirks.


Listener subsystem   (standalone-only)

Detailed in Operations → Listener and Hooks & webhooks. Listed here:

  • hook / hooks / hook create / hook revoke / hook rotate / hook show
  • clients / clients list / clients revoke

Cross-trade   (standalone-only)

Detailed in Operations → Cross-trade. Top-level binary surface:

FormResult
tealstreet cross-trade listList configs in ~/.tealstreet/cross-trade.json.
tealstreet cross-trade add --master <a> --copier <b> [--symbol] [--mode] [--enabled]Add a single-copier config. --mode ∈ LimitSlippage | Market | Chase (Chase skipped on CLI).
tealstreet cross-trade enable <id> / disable <id> / remove <id>Mutate by id prefix (8 chars matches one row).
tealstreet cross-trade runForeground daemon. Boots accounts, runs the engine, blocks until SIGINT.
tealstreet cross-trade statusRead the heartbeat and print running/not + uptime + active configs.

Recording   (standalone-only)

Detailed in Operations → Recording. REPL surface:

FormResult
record <exchange>Start orderbook recording
record start <exchange>Same — explicit
record stop <exchange>Stop recording
record listList configured recordings
record stats [exchange]DB stats

Alias: rec.


Import / export

FormResult
exportWrite vars + aliases to ~/.tealstreet/cli-export.json
importRead same — replaces current vars + aliases

Distinct from tealstreet account export/import, which dumps credentials.


Scripts   (standalone-only)

Scripts live in ~/.tealstreet/scripts/ — one .ts file per script. Open the directory in your editor for full IntelliSense via the seeded tsconfig.json + types.d.ts. Full layout in Operations → Config files → Scripts.

FormResult
script:<name>   (e.g. script:hello)Run the loaded script. The script: prefix is required — bare <name> is never resolved to a script. Args after the name are parsed and handed to the script as args. Composes with retry N, @<x> = , set ec, chain operators, and webhooks.
script reload   (or scripts reload)Re-scan ~/.tealstreet/scripts/ and rebuild the in-memory registry. Edits to script files don't auto-reload — run this after saving.

Grammar: script:<name> with no space after the colon. script: foo is a parse error. script: with no name is a parse error. Invoking a name the loader didn't find errors loudly — it does NOT fall through to the regular command dispatcher.

Why a prefix? Eliminates every name-collision case. Filenames like ~/.tealstreet/scripts/buy.ts are legal; the builtin buy keeps working. Future CLI upgrades adding new builtins never silently shadow existing user scripts.


The tealstreet binary

The CLI is also a standard command-line tool. With no args, it starts the REPL. With a subcommand, it runs one-shot.

Top-level flags

FlagEffect
-v / --versionPrint version and exit
--updateTrigger updater
--check-updateVersion-check only
--disable-allStart REPL with all exchanges disabled

Subcommands

SubcommandAliasesPurpose
interactive (default)iLaunch REPL. -a <name>, --simple, --disable-all accepted
exec <command…>One-shot. -a <account>, -s <symbol> for context scoping
buy / sell / close / cancel / scale / chase / nuke / balance / stopShorthand wrappers around exec <verb> …. All accept -a / -s
account (group)accountslist / add / remove / default / symbol / exchanges / import / export
tasks list / tasks clearPersisted tasks from ~/.tealstreet/tasks.json (see warning below)
record (group)start / stop / list / stats
update-c / --check for check-only
logslog-c / --clear, -n <N> lines
mcpRun as an MCP server over stdio — see MCP server below

tealstreet tasks is different from REPL tasks. Top-level reads persisted task records from ~/.tealstreet/tasks.json. REPL tasks lists live chasers + TWAPs from memory. Don't conflate them.

One-shot exec commands that start TWAPs keep the process alive until those TWAPs finish, then disconnect the exchange session.

Source: apps/cli/src/index.ts.


MCP server   (standalone-only)

tealstreet mcp runs the CLI as an MCP server over stdio, so an AI assistant (Claude Code, Claude Desktop, …) can read, write, and validate your scripts, inspect local account state, and read Tealstreet data tools.

Register it with your MCP client — for Claude Code:

claude mcp add tealstreet-scripts -- tealstreet mcp

You must be logged in. Start the CLI (tealstreet) and run login at the prompt — login is a REPL command, there is no top-level tealstreet login. The MCP server exits immediately with not logged in on stderr rather than starting and letting each tool fail separately, since an assistant would otherwise keep retrying instead of telling you to log in. If your MCP client reports the server as failed, check your login first — tokens expire.

Tools

ToolPurpose
list_scriptsEvery script on disk, including ones that currently fail to load
read_scriptOne script's source — works even when the script is broken
get_api_typesThe exact TypeScript surface available to scripts (core / cli / web / mobile / all)
validate_scriptTypecheck + capability check without writing. Fast, local, free
write_scriptCreate or replace a script. Validates first and refuses to write invalid source
delete_scriptDelete a script
export_scriptsExport your own scripts as a portable JSON bundle (backup / moving machines)
list_accountsConfigured accounts — names and exchanges only, never credentials
get_positions / get_orders / get_balance / get_tickerLive reads for an account
run_scriptDry run only — see below
list_market_data_sourcesPublic Tealstreet data sources and publicdata exchange health
get_public_tickers / get_public_tickerPublic ticker snapshots from Tealstreet publicdata
get_top_movers / get_top_volume / get_top_fundingPublic market top lists from Tealstreet publicdata
get_exchange_proxy_candlesHistorical candles through exchange.tealstreet.io's public candle service
get_recent_newsPublic normalized TealNews items through news.tealstreet.io's recent-news endpoint
get_safe_cex_markets / get_safe_cex_marketSafe-cex market metadata through exchange.tealstreet.io's public candle service
get_mcp_context_dsl_referenceSelf-contained mcp-context/v1 DSL, scope, refresh, callback and dashboard reference
list_mcp_context_planes / plan_mcp_context_queryPlan composable mcp-context/v1 dashboard queries into bounded MCP tool steps
get_polymarket_events / search_polymarket_eventsPublic Polymarket prediction-market events and search
get_polymarket_midpoints / get_polymarket_order_book / get_polymarket_price_historyPublic Polymarket CLOB data, with bounded book depth
get_hyperliquid_leaderboardCrawled Hyperliquid leaderboard rows, when analytics DB env vars are configured
get_hyperliquid_address_positions / get_hyperliquid_address_fills / get_hyperliquid_address_value_curveCrawled public Hyperliquid address analytics (take a public address), when analytics DB env vars are configured

What it will refuse

write_script runs the same checks as validate_script and will not write a file that fails any of them:

  • Value imports. import { OrderSide } from '@tealstreet/script-api-core' typechecks but fails at load, because scripts run from a directory with no node_modules. Use import type for types and api.constants.* for values.
  • Undeclared capabilities. Every api.exchange.* method the body calls must appear in meta.capabilities.writes. closePosition additionally requires placeOrder.
  • Top-level calls. Code outside the default-exported function runs when the CLI loads the scripts directory, not when the script is invoked. Declarations (types, helper functions, const config) are fine.

This is stricter than editing a file by hand on purpose: a script an assistant wrote is code you have not read.

run_script is dry-run only

run_script executes a script against live market data but every write — placeOrder, cancelOrder, runCommand, everything — is recorded and returned as an intent rather than executed. Nothing this server can do places a real order.

You get back the full list of what the script would have done, which is the useful part; a mechanism that simply refused at the first write would tell you about one action and nothing else.

To actually run a script, run script:<name> yourself in the REPL.

Two caveats worth knowing:

  • Dry run bounds a script's api.* calls. It is not a sandbox — scripts execute with full runtime globals, so a script that reaches outside the API entirely is unaffected. run_script therefore refuses to run source that doesn't validate.
  • The server connects accounts on demand and never writes active back to your config, so it cannot disturb a REPL running alongside it.

There is no import tool, on purpose

export_scripts exists; a matching import does not. Exporting your own scripts is a backup. Installing scripts written by someone else is a different proposition entirely: scripts currently run on the same boundary as code you wrote yourself, reaching fetch, localStorage and your session. Third-party scripts wait for the worker sandbox.

If you want to move a bundle to another machine, paste each script through write_script — it validates first, and you see what it declares before it lands.

Notes

  • Local only. stdio transport, no network listener. It does not expose your accounts to anything but the MCP client you launched it from.
  • Data reads may call Tealstreet services. Public market tools make outbound HTTPS calls to Tealstreet publicdata. Hyperliquid analytics tools read a Postgres analytics database only when you set HYPERLIQUID_ANALYTICS_DATABASE_URL or HYPERLIQUID_ANALYTICS_POSTGRES_*; otherwise they return a configuration error.
  • Context-query tools plan, they do not execute. Start with get_mcp_context_dsl_reference when an MCP client needs the self-contained DSL, scope semantics, callback contract, refresh metadata and dashboard piping instructions. plan_mcp_context_query compiles the allowlisted mcp-context/v1 DSL into MCP tool steps plus dashboard widget metadata (queryHash, refresh policy and required scopes) so an agent can pipe results into an MCP dashboard or refresh it on a polling interval. The stdio runtime rejects hosted-only private planes.
  • Credentials are never exposed. list_accounts returns an explicit allowlist (name, exchange, testnet, connected) — never the stored keys.
  • Script writes, deletions, and dry runs are recorded in the audit log tagged [mcp], so agent activity is distinguishable from your own after the fact. A dry-run entry carries the number of write intents the script attempted — that count is what makes a run worth reviewing later.
  • Scripts are not executed by this server — write them here, run them from the REPL with script:<name>.

Source: apps/cli/src/mcp/.


Multi-symbol fan-out

Most trading commands accept a leading comma-separated symbol list:

buy WIFUSDT,ENAUSDT $100
chase sell %all% 20% reduce
last-price BTC,ETH,SOL

%all% (in chase … reduce only) expands to every position on the implied side. See Recipes → Fan out across symbols.


Notes on accepted-but-no-op surface

A few keywords are accepted by the parser but don't yet have runtime effect. Treat them as "doesn't break the command":

  • scale … cubic / … icubic — accepted, distribution still even.
  • twap … reduce — accepted; gates fatfinger but not slice placement.
  • import payload's binds field — read but discarded (reserved for hotkeys).