Events and Binds¶
Beyond reading state with mq.TLO.*,
scripts can register slash commands, subscribe to game events, and drive the
client.
Commands¶
eq2.register_command(name, fn)¶
Registers /name and routes it to fn(args). Returns false if the name is
already taken.
eq2.register_command("greet", function(args)
eq2.print("hello, " .. (args ~= "" and args or "world"))
end)
while true do eq2.delay(1000) end -- the command dies with the script
args is the entire rest of the line as one string, unsplit. Parse it
yourself:
local sub, rest = args:match("^(%S*)%s*(.-)$")
The command is unregistered automatically when the script stops.
Callbacks stored in C++ must bind the main thread
This is handled for you by eq2.register_command and every eq2.on_*
below, but it's worth knowing why: a Lua callback held across the C++
boundary is bound as a main protected function. Binding the coroutine's
own state instead means the handler fires against a coroutine suspended
inside eq2.delay(), and fails.
eq2.cmd(line) / eq2.cmdf(fmt, ...)¶
Runs a slash command through the game's own input path — a real send for chat, casts, and everything else.
eq2.cmd("/say hello")
eq2.cmdf("/target_id %d", id)
eq2.cmd_available()¶
Is the command system ready to accept sends? false when the underlying hook
didn't resolve.
eq2.target_by_id(spawn_id)¶
Target a specific actor by spawn id. Resolves the actor's ghost handle and calls
the native target function. Returns true on success, false on failure.
if eq2.target_by_id(mob_id) then
eq2.print("targeted " .. mq.TLO.Target.Name())
end
Prefer this over /target <name> when you have the spawn id, since name-based
targeting can hit the wrong mob when duplicates exist.
Output¶
| Call | What |
|---|---|
eq2.print(...) |
To clockwork's log and the overlay console. Multiple args are tostring'd and space-joined, like Lua's print. |
eq2.write_chat(text) |
Local-only print into your own chat window. Sends nothing. |
eq2.write_chatf(fmt, ...) |
Same, with string.format. |
eq2.write_chat does not send. Use eq2.cmd("/say ...") to actually talk.
Chat timestamps¶
| Call | What |
|---|---|
eq2.chat_timestamps(enabled, fmt?) |
Enable or disable timestamp injection into the game's own chat window. Optional fmt is a strftime format string. |
eq2.chat_timestamps_enabled() |
Is timestamp injection currently on? |
eq2.chat_timestamp_channels(t?) |
Get or set the per-channel filter table. |
The /chattime script wraps the toggle.
Timing¶
| Call | What |
|---|---|
eq2.delay(ms) |
Yield; resume after ms. Required periodically. |
eq2.gametime() |
Millisecond timestamp, GetTickCount64()-style. |
Events¶
Every subscription follows the same shape:
local token = eq2.on_something(function(payload) ... end)
eq2.off_something(token) -- rarely needed; auto-swept on script stop
Handlers are dispatched on the game thread, from the pulse, off a snapshot — so a handler that unsubscribes itself mid-dispatch is safe.
Chat¶
eq2.on_chat(function(channel, text, local_echo)
if text:find("invite") then eq2.cmd("/accept") end
end)
| Arg | Type | Notes |
|---|---|---|
channel |
number | Channel id. lib.chat has named constants. |
text |
string | The line. |
local_echo |
bool | True if this is your own message echoing back. |
Check local_echo before reacting — a handler that replies to text it also
prints will loop.
Cast failures¶
eq2.on_cast_fail(function(code)
if code == 0x26 then -- out of line of sight
require("lib.nav").los_start()
end
end)
The payload is a single numeric reason code.
Command queue¶
eq2.on_cmdqueue(function(ev)
eq2.print(ev.cmd)
end)
Observes commands the client's own UI pushes onto its internal queue — the verbs its buttons emit, which are not the same set as typed slash commands.
| Field | Type |
|---|---|
cmd |
string |
caller |
number — RVA of the caller, for diagnostics |
self |
number |
seq |
number |
truncated |
bool |
| Call | What |
|---|---|
eq2.on_cmdqueue(fn) |
Subscribe. Returns a token. |
eq2.off_cmdqueue(token) |
Unsubscribe. |
eq2.cmdqueue_available() |
Is the cmdQueuePush observation hook resolved? Also gates whether on_cmdqueue will ever fire. |
The observe side above is read-only. To push onto the same queue yourself:
eq2.cmdqueue("some_internal_verb")
| Call | What |
|---|---|
eq2.cmdqueue(line) |
Push a raw string straight onto the game's own internal command queue via cmdQueuePush — a different channel from eq2.cmd/eq2.cmdf above, which go through RunInputLine's typed-command parser instead. This reaches internal-only verbs RunInputLine has no handler for. Sent verbatim, no formatting applied. Returns false if the hook hasn't resolved. |
Quest offers¶
eq2.on_quest_offer(function(q)
eq2.print("offered: " .. q.name)
eq2.quest.accept()
end)
| Field | Notes |
|---|---|
name |
Quest name. |
accept_cmd |
This client's own server-supplied accept command. Never ship it to a peer — it is not portable. |
crc |
Offer checksum. |
seq |
Monotonic sequence number. |
Poll-style equivalents, for policy that prefers not to use the event:
| Call | Returns |
|---|---|
eq2.quest.hook_ok() |
Is the offer-capture hook live? |
eq2.quest.pending() |
Is an offer captured and waiting? |
eq2.quest.offer() |
{name, accept_cmd, crc} or nil. |
eq2.quest.accept() |
Accept this client's captured offer. |
eq2.quest.accept_click() |
Accept via UI button click. |
eq2.quest.accept_all() |
Accept locally, then broadcast quest_accept so each peer accepts its own. |
eq2.quest.offer_window() |
The offer window UiHandle, or nil. |
eq2.quest.reward_window() |
The reward window UiHandle, or nil. |
eq2.quest.reward_click() |
Click the reward-accept button. |
Dialogs¶
eq2.on_dialog(function(d)
if d.event == "open" then
local st = eq2.dialog.status()
for _, opt in ipairs(st.options) do eq2.print(opt.index .. ": " .. opt.text) end
end
end)
| Field | Notes |
|---|---|
event |
"open", "page", or "close". |
ctx |
Opaque monotonic integer handle — not a pointer. |
conv_id |
Conversation id. |
option_count |
Number of replies offered. |
"page" fires only on the observe path — your own eq2.dialog.reply() calls
don't re-trigger it.
| Call | Returns |
|---|---|
eq2.dialog.ready() |
Health gate: is the advance path resolved? |
eq2.dialog.status() |
{open, conv_id, option_count, options} or nil. Each entry in options is {index, text}. |
eq2.dialog.reply(index) |
Advance by 0-based reply index. |
Matching an option by text rather than blind index is policy, not a binding —
lib.dialog's reply_by_text does it.
Travel¶
eq2.on_travel_list(function(t)
for _, d in ipairs(t.destinations) do eq2.print(d.text) end
end)
| Field | Notes |
|---|---|
portal_id |
The portal that produced the list. |
window |
Opaque monotonic generation counter — not a pointer. |
destinations |
An array; each entry is {key, diff, data, text}. |
| Call | What |
|---|---|
eq2.travel.list() |
Snapshot of the observed destination list. |
eq2.travel.map_list() |
Fast-travel map destinations. |
eq2.travel.map_select(label) |
Commit a map destination by display label. Commits by record, so it reaches every zone instance. |
eq2.travel.map_status() |
Map hook state. |
eq2.travel.status() |
Portal-list hook state and relay counters. |
eq2.travel.relay_last() |
Relay the last captured destination to peers. |
eq2.travel.relay_enabled() / .set_relay_enabled(b) |
Master auto-relay switch, shared with zone doors. |
eq2.travel.select() was removed
It fabricated a network message. Use eq2.travel.map_select(label).
Travel (continued):
| Call | What |
|---|---|
eq2.travel.portal_select(...) |
Select a portal destination (replaces the removed select()). |
Zone doors:
| Call | What |
|---|---|
eq2.zone_door.click{spawn_id=, name=, prompt=, flag=} |
Table arg; needs at least spawn_id or name. |
eq2.zone_door.interact(...) |
Interact with a door. |
eq2.zone_door.status() |
Hook state and relay counters. |
eq2.zone_door.relay_last(scope?) |
Relay the last captured click to peers. Optional scope filter. |
Quest objects:
| Call | What |
|---|---|
eq2.quest_object.status() |
Hook state for quest-clickable objects. |
eq2.quest_object.relay_last(scope?) |
Relay the last quest-object interaction to peers. |
Reply dialogs:
| Call | What |
|---|---|
eq2.reply_dialog.status() |
Current reply-dialog state (for examinable items that open a ReplyDialog). |
eq2.reply_dialog.select(index) |
Select a reply option by index. |
eq2.reply_dialog.window() |
The dialog window UiHandle, or nil. |
Network messages¶
eq2.on_net_msg(function(msg)
eq2.print(("type=0x%04X len=%d"):format(msg.type_id, msg.len))
end)
Subscribe to inbound network messages. Two forms:
eq2.on_net_msg(fn)— wildcard, fires for every message.eq2.on_net_msg(type_id, fn)— filtered to one message type.
| Call | What |
|---|---|
eq2.on_net_msg(fn) / eq2.on_net_msg(type_id, fn) |
Subscribe. Returns a token. |
eq2.off_net_msg(token) |
Unsubscribe. |
eq2.net_msg_available() |
Is the net-message hook resolved? |
eq2.net_stats() |
Per-type message histogram. |
eq2.net_stats_reset() |
Clear the histogram. |
Network send (outbound)¶
eq2.on_net_send(function(p)
if not p.self then
eq2.print(("vtable=%s flag=%d seq=%d"):format(
("0x%X"):format(p.vtable), p.flag, p.seq))
end
end)
Subscribe to outbound network messages. Two forms:
eq2.on_net_send(fn)— wildcard, fires for every outbound send.eq2.on_net_send(vtable, fn)— filtered to messages with a specific vtable pointer.
| Field | Type | Notes |
|---|---|---|
vtable |
number | First 8 bytes of the message object (message type discriminator). |
caller |
number | Return address as an EverQuest2.exe RVA; 0 when the caller is outside the game module. |
seq |
number | Monotonic send counter. |
flag |
number | The flag byte passed to netSend. |
self |
bool | True when clockwork itself sent this message. |
raw |
string | Bounded raw prefix of the message object (up to 256 bytes). |
| Call | What |
|---|---|
eq2.on_net_send(fn) / eq2.on_net_send(vtable, fn) |
Subscribe. Returns a token. |
eq2.off_net_send(token) |
Unsubscribe. |
eq2.net_send_available() |
Is the netSend hook resolved? |
Crafting¶
eq2.on_craft_round(function(ev)
eq2.print(("round: result=%d quality=%d progress=%.0f durability=%.0f")
:format(ev.result, ev.quality, ev.progress, ev.durability))
end)
Subscribe to crafting round-results (opcode 0xE5) — one event per round of a
tradeskill combine, whether the round was triggered by you or the client's own
auto-event logic.
| Field | Type | Notes |
|---|---|---|
result |
number | 1-4. |
durability |
number | |
progress |
number | |
quality |
number | 0-4. |
main_icon |
number | |
mod_a, mod_b |
number | |
message |
string |
| Call | What |
|---|---|
eq2.on_craft_round(fn) |
Subscribe. Returns a token. |
eq2.off_craft_round(token) |
Unsubscribe. |
lua/craft.lua and lua/autocraft.lua are the bundled scripts built on this —
see /craft and
/autocraft.
Loot¶
| Call | What |
|---|---|
eq2.loot.status() |
Loot window state. |
eq2.loot.items() |
Array of items in the current loot window. |
eq2.loot.window() |
The loot window UiHandle, or nil. |
eq2.loot.loot_item(a, b) |
Loot an item by index. |
Broker¶
| Call | What |
|---|---|
eq2.broker.consignments() |
Your broker consignment listings. |
eq2.broker.container() |
The broker container (lot reader). |
Native UI¶
Most native-UI scripts start by finding a window, then act on the UiHandle
they get back:
local w = eq2.ui.window("Choice")
if w and w.visible then
w.h:click()
end
| Call | What |
|---|---|
eq2.ui.windows() |
Array of {name, visible} for every top-level window clockwork's window walk can see. Best-effort — see the caveat below. |
eq2.ui.window(name) |
A top-level window by exact, case-insensitive name. A UiHandle, or nil. |
eq2.ui.find(name_or_path) |
Recursive search through the game's own child lookup, starting from the UI root. Unlike eq2.ui.window(), this can reach a control nested inside an already-open window without waiting for a fresh show event — the trade-off is it walks the game's own (occasionally mistyped) child-lookup path rather than clockwork's window enumeration. |
windows()/window() may not see everything
The underlying walk is a best-effort read of the client's own window tree,
not a guaranteed-complete enumeration. A window that exists but doesn't
show up in windows() is a known gap, not necessarily a bug in your
script — try eq2.ui.find() instead.
Once you have a UiHandle, see UiHandle
for the full set of things you can do with it — read text, click, walk
children, peek/poke raw offsets.
local token = eq2.on_ui_action(function(ev)
if ev.name == "Accept" and ev.event == "OnPress" then
ev.h:click()
end
end)
| Field | Notes |
|---|---|
control |
Opaque numeric handle id — never a pointer. |
h |
The same handle as a UiHandle, so you can act on the control you just observed. |
name |
Control name. |
event |
Event name. |
listeners |
Listener count. |
vtable, thunk, arg |
Diagnostic hex, for log correlation only. |
Arm the listener only while you need it
Every observed event mints a UI handle, and the client's handle table has
4096 slots. An always-on on_ui_action subscription across a long
session can saturate it, after which every handle request fails for the rest
of the session — which looks like a completely unrelated bug.
Subscribe when your window opens, unsubscribe when it closes.
Window show/hide¶
eq2.on_ui_showhide("Choice", function(e)
if e.visible then
-- suppress the popup by clearing the visible bit
local flags = e.h:peek_u32(0xC8)
if flags then e.h:poke_u32(0xC8, flags - (flags % 2)) end
end
end)
Subscribe to window show/hide events. Fires for every widget the client shows or hides — the same hook that drives upsell suppression and quest-dialog capture, now exposed to Lua. Two forms:
eq2.on_ui_showhide(fn)— wildcard, fires for every show/hide.eq2.on_ui_showhide(name, fn)— case-insensitive substring filter on the window name.
| Field | Type | Notes |
|---|---|---|
name |
string | The window/control name (read via the handle system). |
visible |
bool | true = show, false = hide. |
h |
UiHandle |
Adopted handle — call :poke_u32(), :peek_u32(), :name(), :find(), :text(), etc. |
| Call | What |
|---|---|
eq2.on_ui_showhide(fn) / eq2.on_ui_showhide(name, fn) |
Subscribe. Returns a token. |
eq2.off_ui_showhide(token) |
Unsubscribe. |
Unlike on_ui_action, this bus does not mint a handle per observed event
in the push path — the handle is adopted only in the drain, so it has no
handle-table saturation risk from high-frequency widget traffic. The
g_showhide_wanted atomic gate means zero cost when no Lua listener exists.
Reacting to a show, not preventing it
The callback fires after the game has already shown the window (the event is queued in the hook and drained on the next pump). To suppress a show, clear the visible bit in the callback as shown above. The window may flash for one frame.
Multibox¶
eq2.peer.* broadcasts to selected clients; each peer executes locally on its
own game thread.
| Call | What |
|---|---|
eq2.peer.run(script, args) |
/lua run <script> <args> on every selected client, including this one. |
eq2.peer.run_others(script, args) |
Same, excluding this one. |
eq2.peer.cmd(line) |
A raw command line on every selected client. |
eq2.peer.cmd_others(line) |
Same, excluding this one. |
eq2.peer.send(to, channel, data) |
Send a message to one peer. |
eq2.peer.broadcast(channel, data) |
Send a message on a named channel. |
eq2.peer.group_send(group, channel, data) |
Send to a group channel. |
eq2.peer.on(channel, fn) / eq2.peer.off(token) |
Subscribe. fn(from, data). |
eq2.peer.join(channel) / eq2.peer.leave(channel) |
Channel membership. |
eq2.peer.group_channel() |
The current group channel name. |
eq2.peer.name() |
This client's peer identity. |
eq2.peer.set_identity(name) |
Set this client's peer identity. |
eq2.peer.list() / eq2.peer.count() |
Who's out there. |
Don't wrap peer calls in /dga or /dge
eq2.peer.* already broadcasts. Sending a /dga-prefixed line as the
payload makes every receiver's own /dga handler re-broadcast it, so the
work runs twice on every peer.
For the same reason, /dga refuses to
broadcast a line whose first token is itself a broadcast command.
Same-box peers are not on loopback
Clients on one PC are discovered at the host's LAN address, never
127.0.0.1. A loopback-only check will refuse every peer on the same
machine.
Writing a peer-relay receiver¶
eq2.peer.cmd(line) / eq2.peer.cmd_others(line) deliver line to each
target peer's own game thread, where it is dispatched exactly like a typed
command: clockwork's own command registry first, then the
game's native RunInputLine if nothing claims it.
That second half is the trap. If no script on the receiving box has
registered the command name with eq2.register_command, the relayed line
falls straight through to the game client as literal input. EverQuest II has
no such command, so it's silently ignored — no error on the sender, no error
on the receiver, nothing in the log. eq2.peer.cmd_others reports success
the same way whether or not anything on the other end was listening.
In practice this means: for a relay to do anything, the matching receiver script must already be running on every peer that should react. Several clockwork scripts are built as relay/receiver pairs this way:
| Relay sends | Receiver script registers |
|---|---|
/travelto <text> |
travel |
/dialogreply <text> |
dialogreply |
/maptravel <destination> |
maptravel |
/resettimer <zone_id>, /resetalltimers |
resettimer |
/qj delete <name> |
qj |
/settarget <id>|<name> |
targetsync |
None of these start themselves on a peer — the leader-side script only sends
a command line, it never launches anything remotely (eq2.peer.run* exists
for that, but relaying a launch at an already-running resident script is a
no-op by design, since /lua run on a running script is refused). The
receiver has to already be resident before the first relay arrives.
Autoload the receiver on every peer
Add the receiver script's name (bare, one per line — no /lua run, no
args) to config/autoload.txt. clockwork launches every listed script
once per character login. A per-character list also exists at
config/<CharacterName>/autoload.txt for boxes that need a different
set; see the shipped autoload.txt.example template.
A relay script's own comments are usually explicit about which receiver it depends on — check there before assuming a relay "isn't working" when it may just be talking to an empty room.
lib.resident (Helper Libraries) is the shared
command-queue-plus-worker-loop shape most receiver scripts are built on.
ImGui¶
eq2.imgui.init("mypanel", function()
if ImGui.Begin("My Panel") then
ImGui.Text("hp: " .. mq.TLO.Me.PctHP() .. "%")
end
ImGui.End()
end)
while true do eq2.delay(100) end
| Call | What |
|---|---|
eq2.imgui.init(name, fn) |
Register a per-frame draw callback. name must be globally unique. |
eq2.imgui.destroy(name) |
Remove it. |
eq2.imgui.exists(name) |
Is a window with this name registered? |
The global ImGui table is a guard proxy: calling ImGui.* outside a draw
callback raises a Lua error rather than corrupting the frame. That guard exists
because out-of-frame calls were a live crash path.
ImGui.Image and texture ids
Passing a plain Lua number where a texture reference is expected is a guaranteed client crash — the DirectX 11 backend casts it straight to a device pointer. A binding existing and compiling is not evidence it is safe to call with an arbitrary number.
Movement¶
Vertical-axis and jump controls, added alongside the existing
eq2.move_drive /
eq2.turn_drive axes:
| Call | What |
|---|---|
eq2.jump(active) |
Start (true) or stop (false) the jump input. Event-driven — call once, not per-frame. |
eq2.move_up(active) |
Start/stop upward movement (e.g. swimming, flying). |
eq2.move_down(active) |
Start/stop downward movement. |
These complement eq2.move_drive() and eq2.turn_drive(), which handle
forward/strafe and heading. See
Navigation for the full movement surface.
Navmesh¶
Detour navmesh queries for offline-baked zone meshes:
| Call | What |
|---|---|
eq2.navmesh.available() |
Is the navmesh subsystem initialized? |
eq2.navmesh.zones() |
List of zone keys with baked meshes. |
eq2.navmesh.load(zone_key) |
Load a zone's navmesh. |
eq2.navmesh.load_current() |
Load the navmesh for the current zone. |
eq2.navmesh.loaded() |
Currently loaded zone key, or nil. |
eq2.navmesh.find_path(fx,fy,fz, gx,gy,gz) |
A* path from start to goal. Returns corners, nil or nil, reason. |
eq2.navmesh.path_length(fx,fy,fz, gx,gy,gz) |
Total 3D path length through the navmesh (same pipeline as find_path, no table allocation). Returns length, nil or -1, reason. |
eq2.navmesh.nearest_point(x,y,z) |
Snap to nearest point on the mesh. |
eq2.navmesh.raycast(fx,fy,fz, gx,gy,gz) |
Raycast along the mesh. |
eq2.navmesh.local_geometry(cx,cz,radius) |
Local mesh geometry around a point. |
lib.navpath wraps find_path and path_length for common use.
Textures¶
| Call | What |
|---|---|
eq2.texture.load(path) |
Load a texture from disk. Returns a TextureHandle with .width, .height, .valid. |
eq2.texture.release(handle) |
Release a loaded texture. |
Inventory¶
| Call | What |
|---|---|
eq2.inventory() |
Snapshot of all bag items as an array of item. |
eq2.equipped() |
Snapshot of equipped items. |
eq2.inventory_space() |
Free bag slot count (same as Me.FreeInventory()). |
eq2.item_def(item_def_id) |
Examine-level detail for one item template — see item_def. nil until primed. |
eq2.item_examine(item_def_id) |
Requests examine data for an item template from the server. Fire-and-forget — poll item_def() for the result. |
Process¶
| Call | What |
|---|---|
eq2.process_memory() |
Current process memory stats. |
eq2.trim_working_set() |
Release unused physical memory. |
Files and misc¶
| Call | What |
|---|---|
eq2.mkdir(path) |
Recursive mkdir -p. |
eq2.listdir(path) |
List a directory. |
eq2.luaDir |
Absolute path to the script directory. |
eq2.name |
This script's registered name. |
require("lsqlite3") |
SQLite, for script-side persistence. See below. |
require("lsqlite3")¶
local sqlite3 = require("lsqlite3")
local db = sqlite3.open(eq2.luaDir .. "/mydata.sqlite3")
db:exec("CREATE TABLE IF NOT EXISTS seen (id INTEGER PRIMARY KEY)")
local stmt = db:prepare("SELECT * FROM seen")
for row in stmt:nrows() do eq2.print(row.id) end
stmt:finalize()
db:close()
SqliteDb method |
What |
|---|---|
db:exec(sql) |
Run a statement with no result rows. |
db:prepare(sql) |
Compile a statement, returns a SqliteStmt. |
db:busy_timeout(ms) |
Set the lock-wait timeout. |
db:errmsg() |
Last error message. |
db:last_insert_rowid() |
Rowid of the last insert. |
db:update_hook(fn) |
Callback on insert/update/delete. |
db:close() |
Close the connection. |
SqliteStmt method |
What |
|---|---|
stmt:bind(...) |
Bind parameters. |
stmt:step() |
Advance one row. |
stmt:nrows() |
Iterator form — one table per row, for a for row in ... loop. |
stmt:reset() |
Rewind to re-execute. |
stmt:finalize() |
Release the statement. |
The module table also carries sqlite3.open(path) and the usual result/open
constants (OK, ROW, DONE, BUSY, INSERT, UPDATE, DELETE,
OPEN_READWRITE, OPEN_CREATE, OPEN_READONLY, OPEN_NOMUTEX,
OPEN_FULLMUTEX).
Advanced / internal surfaces¶
These exist and are reachable from any script, but they back clockwork's own tooling more than end-user automation — treat them as advanced/unstable rather than reaching for them by default.
| Surface | What |
|---|---|
eq2.mem.* (read_u8…u64/i8…i64/f32/f64/ptr/cstr/bytes, chain, hexdump, base, scan, scan_all, rip_target) |
Raw process-memory reads, keyed off the signature registry. What lua/inspector.lua's struct inspector is built on. |
eq2.sig.* (get, list, reresolve) |
Query the signature registry that resolves every hook/offset clockwork uses internally. |
eq2.quests(), .achievements(), .quests_at(), .quest_list(), .quest_tree(), .jw_read(), .jw_str(), .scene_peek(), .journal_peek(), .selected_quest_id() |
Raw quest-journal tree primitives, returning QuestEntry/QuestListEntry handles. Prefer lib.questjournal, which wraps the common list/find/delete/share operations over this surface. |
See also¶
- Helper Libraries — the pure-Lua wrappers over all of this.
- Top-Level Objects
- Slash Commands