Skip to content

queenctl

The complete command tree: every registered command and subcommand, every flag with its default, the config file and environment variables, exit codes, and the login flows.

Updated View as Markdown

queenctl is a single static Go binary built on the Go client. It exposes the broker’s data plane (push, pop, ack, transaction, lease) and its management plane (queues, partitions, consumer groups, DLQ, traces, maintenance, metrics, analytics). Version 1.3.0, aligned with the broker’s 1.3 line.

Every command below is registered in clients/client-cli/cmd. Nothing else is: if a name is not in this page, the binary does not have it.

Install

make -C clients/client-cli build

Puts the binary in clients/client-cli/bin/queenctl. make install runs go install into $GOBIN (or $HOME/go/bin). Both pass -ldflags that stamp the version, commit and build date; a build without them reports dev, none and unknown.

Global flags

Bound to the root command, so they work on every subcommand.

Flag Default Effect
--context <name> none Named context from the config file. Overrides current-context.
--server <url> none Broker URL. Overrides the context; same as $QUEEN_SERVER.
--token <token> none Bearer token. Overrides the context; same as $QUEEN_TOKEN.
-o, --output <fmt> table on a TTY, json on a pipe See the formats table.
--no-color false Disable ANSI colour.
--color false Force ANSI colour.
--insecure false Skip TLS verification.
-q, --quiet false Suppress non-essential output.
--no-headers false Omit table headers.
--config <path> ~/.queen/config.yaml Config file path.

Output formats

-o value Notes
table Column view. Default when stdout is a TTY.
wide Table with the extra columns a command declares.
json Pretty JSON. Default when stdout is not a TTY, so queenctl tail | queenctl push composes without extra plumbing.
ndjson, jsonl One JSON object per line.
yaml
raw The body as returned.
jsonpath=<expr>, json-path=<expr> Extract one value, e.g. -o jsonpath=.messages[0].transactionId.

pop and tail always emit NDJSON regardless of -o: they are designed to be piped.

Exit codes

Code Meaning
0 Success.
1 User error: bad flag, bad input, missing argument. Also the code for a route the proxy refuses to a tenant, since no retry helps.
2 Server error, or the server was unreachable. The default for an unclassified error.
3 Authentication or authorization failure.
4 Successful no-op: an empty pop, no DLQ rows, nothing applied.

Code 4 is the only one whose message is not printed to stderr. It is not a failure, so scripts can treat it as “nothing to do”.

Configuration

Resolution order

  1. Context name: --context, then $QUEEN_CONTEXT, then current-context from the file.
  2. Server: the context’s server, then $QUEEN_SERVER, then --server.
  3. Token: the context’s token-ref resolved, then $QUEEN_TOKEN, then --token.
  4. Insecure: the context’s insecure, then --insecure, but only when the flag was actually passed, so an absent flag never overrides the file with false.

With no server resolved, every command that talks to a broker fails with no server configured: set --server, QUEEN_SERVER, or run 'queenctl config set-context'.

Environment variables

Variable Effect
QUEEN_CONFIG Config file path. Beaten by --config.
QUEEN_CONTEXT Context name.
QUEEN_SERVER Broker URL.
QUEEN_TOKEN Bearer token, and the value read by token-ref: env.

Config file

~/.queen/config.yaml, kubectl-shaped. A missing file is not an error: first-run users get the environment-only experience.

current-context: prod
contexts:
  - name: prod
    server: https://queen.internal:6632
    token-ref: keychain://prod
  - name: local
    server: http://localhost:6632
    insecure: true

token-ref is one of:

Form Meaning
absent or "" No token.
env Read $QUEEN_TOKEN at runtime.
keychain://<id> Read from the OS keychain under service queenctl.
file:///abs/path Read from a file.
literal:<token> The token itself, in the config file. For CI; not recommended.

Writes are atomic (a .tmp file renamed over the target), with the file at 0600 and the parent directory at 0700.

Authentication

queenctl login

Flag Default Effect
--method <m> inferred token, password, google or github.
-u, --user <email> none Email for the password flow.
--password <pw> none Password. Prefer the interactive prompt.
--token <token> none JWT or qk_ API key to store, skipping the prompt.
--context <name> active context Which context to bind the credential to.
--no-browser false Do not auto-open the browser for the OAuth flows.

With no --method: --token implies token, --user implies password, otherwise token. The context must already exist: run config set-context first. An unknown --method exits 1.

The captured credential goes into the OS keychain and the config file keeps only keychain://<context>. If the keychain is unavailable (a CI container, a headless box), the CLI prints a warning to stderr and falls back to literal:<token> in the config file.

Method What happens
token Prompts for a JWT or a qk_ cluster API key (hidden input on a TTY, a stdin line otherwise) and stores it.
password Form-POSTs /auth/login on the proxy and reads the session JWT out of the cookie jar. Falls back to the legacy POST /api/login when the response cannot have come from that endpoint. Bad credentials exit 3.
google, github Probes GET /auth/<provider> for availability, opens the browser at /auth/<provider>?next=/auth/session-token, and asks you to paste the token the browser lands on.

queenctl logout deletes the keychain entry, clears token-ref on the context and saves the file. It takes --context <name>.

Data plane

queenctl push <queue>

Reads NDJSON from stdin by default, one JSON object per line, and pushes in batches.

Flag Default Effect
--partition <name> none Static partition for every message.
--partition-key <path> none JSON path inside each payload to use as the partition, e.g. user_id, order.id. One ordered lane per distinct key.
--batch <n> 100 Messages per HTTP request.
--data <json> none A single inline payload; skips stdin.
-f, --file <path> none Read NDJSON from a file instead of stdin.
--trace-id <uuid> none Attach this UUID as the trace id.
--transaction-id <id> none Explicit transactionId for the first item.
--wrapped false Input lines are full SDK items: {transactionId?, traceId?, partition?, data}.
--dry-run false Parse and group the input, send nothing.
cat events.ndjson | queenctl push events --batch 500 --partition-key user_id

--wrapped is what makes replication between brokers possible: it lets each line carry its own partition and transaction id, so dedup stays meaningful on the receiving side.

queenctl pop [queue]

One-shot pop, printed as NDJSON. The queue argument is optional: omit it and use --namespace / --task.

Flag Default Effect
--cg <name> none Consumer group.
--partition <name> none A specific partition.
-n, --limit <n> 1 Maximum messages printed.
--batch <n> --limit Batch size sent to the broker.
--max-partitions <n> 1 Claim up to N partitions in one call.
--auto-ack false Ack server-side, inside the pop transaction.
--wait true Long-poll until messages arrive or the timeout fires.
--timeout <dur> 10s Long-poll timeout.
--namespace <name> none Filter the queue-less pop by namespace.
--task <name> none Filter the queue-less pop by task.
--from-mode <mode> none Subscription mode: all, new, new-only.
--since <t> none Subscription start: now, RFC3339, 5m ago.
--conflation false Last-value delivery: one message per partition, the newest. Requires --cg, refuses --auto-ack, and needs a broker >= 1.1.0.

Each emitted line carries queue, partition, transactionId, partitionId, createdAt, retryCount and data, plus leaseId and producerSub when present. An empty pop exits 4.

queenctl tail <queue>

Live-tail, always NDJSON on stdout regardless of -o.

Flag Default Effect
--cg <name> queenctl-tail Consumer group. Without it an ephemeral group name is used.
--partition <name> any Single partition to tail.
-f, --follow false Keep long-polling after the queue drains.
-n, --limit <n> 0 (no limit) Stop after N messages.
--batch <n> 0 Messages per long-poll round-trip.
--max-partitions <n> 1 Claim up to N partitions per pop.
--auto-ack false Ack server-side as messages are emitted.
--from <mode> none Subscription mode: all, new, new-only.
--since <t> none Subscription start.
--conflation false Last-value delivery on every round trip. It is the GROUP’s policy, not the invocation’s, so pointing it at a shared --cg changes that group for everyone.
--idle-millis <n> 0 Stop after N ms with no messages.
--concurrency <n> 0 Parallel handlers.
--timeout <dur> 0 → 30 s, or --idle-millis when smaller Per-pop long-poll timeout.
queenctl tail src --auto-ack | queenctl push dst --batch 100

queenctl ack [transactionId]

Flag Default Effect
--partition-id <uuid> none Internal partition UUID, as printed by pop / tail.
--lease-id <uuid> none Lease UUID returned at pop time.
--cg <name> none Consumer group.
--error <msg> none Attach an error message; use with --failed.
--failed false Mark as failed instead of completed.
--batch <file> none Ack many messages from an NDJSON file; - for stdin.

Each line of a --batch file must be a JSON object with at least transactionId, partitionId and leaseId, exactly the shape pop and tail emit.

queenctl tail q --cg c -n 100 | queenctl ack --batch -

queenctl tx -f <bundle.json>

Posts a bundle to /api/v1/transaction: pushes and acks in one PostgreSQL transaction.

Flag Default
-f, --file <path> required; - for stdin
--dry-run false (parse and count, do not commit)

The file shape maps directly to the route:

{
  "operations": [
    {"type": "ack", "transactionId": "…", "partitionId": "…", "status": "completed"},
    {"type": "push", "items": [{"queue": "orders", "payload": {"id": 1}}]}
  ],
  "requiredLeases": ["lease-uuid"]
}

Only ack and push operation types are accepted; anything else exits 1. Push items are grouped by queue and partition before being handed to the builder, preserving first-seen order.

queenctl lease extend <leaseId>

Renews one lease, POST /api/v1/lease/:leaseId/extend. No flags of its own.

Queues and partitions

queenctl queue (aliases queues, q)

Subcommand Aliases Arguments
list ls none
describe <queue> get, show exactly one
depth <queue> none exactly one
configure <queue> none exactly one
delete <queue> rm exactly one
stats none none

queue list flags: --namespace, --task, --limit (0), --offset (0). queue stats flags: --namespace, --task. queue depth flags: --group (none). It is the cheap sibling of queue describe: one GET /api/v1/resources/queues/:queue/depth, watermark arithmetic only, no segment scan. The table view is a four-column summary (pending, partitions, partitionsPending, effective) while -o json prints the full per-partition array. partitionsPending comes from the broker on 1.1.0 and is derived client-side against older ones. effective appears only for a conflating group, where pending is log depth and effective is the handler runs that remain; on any other group the two are equal by construction and printing both would only raise the question of which one is real. queue delete requires --yes.

queue configure flags:

Flag Default Wire key
--namespace <name> none namespace
--task <name> none task
--lease-time <s> 0 leaseTime
--retry-limit <n> 0 retryLimit
--delayed-processing <s> 0 delayedProcessing
--window-buffer <s> 0 windowBuffer
--retention <s> 0 retentionSeconds
--completed-retention <s> 0 completedRetentionSeconds
--encrypt false encryptionEnabled
--dlq true deadLetterQueue and dlqAfterMaxRetries

A positive --retention or --completed-retention sets retentionEnabled: true automatically, because the broker’s retention service only sweeps queues with that flag set.

configure merges: only the flags you actually typed go on the wire, and every option the command does not send keeps the value the queue already has. That is what makes the flag-driven shortcut safe to reach for, and it is the difference from queenctl apply -f, which sends mode: replace because a manifest is the whole configuration.

queenctl queue configure orders --lease-time 60     # the lease, nothing else
queenctl queue configure orders --dlq=false         # stop dead-lettering
queenctl queue configure orders --dlq               # start again

--dlq and --encrypt are sent only when you type them, and they are sent as literal values, so --dlq=false really does disable dead-lettering. That was not true before 1.6.0: the two flags were populated on every call from --dlq’s default of true, so nudging an unrelated option silently switched dead-lettering back on for a queue somebody had deliberately set to drop.

With -o json or -o yaml, configure renders the full response including the echoed options block, which is what to assert against in tests. Without -o, it prints configured "<queue>".

queenctl partition (aliases partitions, p)

Subcommand Aliases Arguments Flags
list <queue> ls exactly one none
describe <queue> <partition> get, show exactly two none
seek <queue> <partition> none exactly two --cg (required), --to (default end)

partition seek only supports seek-to-end: the stored procedure behind POST /api/v1/consumer-groups/:cg/queues/:q/partitions/:p/seek takes no timestamp. --to accepts end, now, latest or an empty value; anything else exits 1. For a timestamp, use queenctl cg seek, which is queue-wide.

queenctl namespace (aliases namespaces, ns)

namespace list (alias ls) lists namespaces with their queues. No flags.

queenctl task (alias tasks)

task list (alias ls) lists tasks with their queues. No flags.

Consumer groups

queenctl cg (aliases consumer-group, consumer-groups)

Subcommand Aliases Arguments Flags
list ls none none
describe <name> get, show exactly one none
lag none none --min-seconds (0)
seek <cg> <queue> none exactly two --to, --dry-run
delete <cg> rm exactly one --queue, --metadata, --yes
refresh-stats none none none

cg delete without --queue deletes the group everywhere; with it, only for that queue. --metadata also removes the subscription metadata rows. --yes is required.

queenctl lag

--min-seconds <n> (default 0). At 0 it lists every group from /api/v1/consumer-groups; above 0 it queries /api/v1/consumer-groups/lagging. queenctl cg lag is the same command.

queenctl replay [queue]

Moves a consumer group’s cursor so the next pop returns messages from --to onward.

Flag Default Effect
--cg <name> required Consumer group to seek.
--to <t> required Target time.
--dry-run false Print the action, send nothing.

--to accepts:

Form Result
now, end, latest {toEnd: true}. Jumps past the latest message.
beginning, earliest, start Timestamp 1970-01-01T00:00:00Z. The route takes toEnd or a timestamp, so “beginning” is expressed as the epoch.
5m ago, 2h ago, 1d ago, 1w ago Relative to now. Bare forms like 30s are also read as “ago”.
RFC3339, YYYY-MM-DD, YYYY-MM-DD HH:MM:SS, unix seconds Absolute. Date-only is UTC midnight.

queenctl cg seek <cg> <queue> is the same operation with the arguments the other way round.

Messages, DLQ and traces

queenctl messages (aliases msg, msgs)

Subcommand Aliases Arguments
list ls none
get <partitionId> <transactionId> describe, show exactly two
delete <partitionId> <transactionId> rm exactly two
traces <partitionId> <transactionId> none exactly two

messages list flags: --queue, --partition, --status (pending, processing, completed, failed), --cg, --limit (100), --offset (0). messages delete requires --yes.

queenctl dlq

Subcommand Aliases Arguments
list ls none
describe <partitionId> <transactionId> get exactly two
retry <partitionId> <transactionId> requeue, replay exactly two
drain none none

list and drain share the filters: --queue, --cg, --partition, --from, --to, --limit (0), --offset (0). drain and retry both add --dry-run and --yes, and refuse to run without one of them.

drain pages through the filtered set in --limit-sized pages, defaulting to 200 when --limit is 0, and deletes every row it matches. It is the bulk delete you run after deciding not to replay.

retry is the replay, one address at a time. It wraps POST /api/v1/messages/:partitionId/:transactionId/retry, which since 1.6.0 moves the newest dead-letter snapshot at that address: claim the row under a lock, push the frame to its own queue and partition, delete the row, one transaction.

It still requires --yes, and the reason changed. It is now idempotent by row: the replayed frame carries the derived transaction id dlq:<row id>, and a second run answers 404 because the row went with the first move. What --yes guards is that the command appends a message and removes a record, both of which someone has to mean.

Three results are worth recognising at the prompt:

  • dlqRowRemoved: false on a success is the duplicate verdict: the destination already carries that transaction id, so nothing was written and nothing was removed. The command says so and names the offset to read. Replay elsewhere, or purge the row.
  • A 500 whose dlqRowRemoved is null means the broker never learned whether the move committed. The command prints a warning telling you to re-read the address with dlq list first: if the row is gone, the move happened.
  • A 503 is push maintenance. A move cannot be spooled, so it is refused and the row stays.

The replay lands at the tail of the partition, out of order for its key, and its age clock restarts at the destination. queenctl sends the request exactly once and never resends it on a 5xx, so the verdict you read is the one that happened.

queenctl traces

Subcommand Aliases Arguments Flags
names list, ls none --limit (100), --offset (0)
by-name <name> none exactly one --limit (100), --offset (0)
by-message <partitionId> <transactionId> none exactly two none

traces by-message is the same as messages traces.

Status and observability

queenctl status [queue]

No argument prints the cluster view: queues with depth, completed, failed and lag. With a queue name it prints the detailed view.

Flag Default Effect
--namespace <name> none Filter.
--task <name> none Filter.
--partitions false Include per-partition detail.

queenctl ping

Calls /health. Exits 0 when the broker reports healthy with the database connected, 2 otherwise. Prints status, database, version and measured latency.

Flag Default
--timeout <dur> 5s

queenctl version

Prints the CLI’s build metadata, then probes the server for its version, status and database state, and echoes the active context. --short prints only the CLI version and contacts nothing.

queenctl metrics

Reads /metrics (a JSON snapshot) by default. --prometheus reads /metrics/prometheus and prints the raw text/plain exposition.

queenctl metrics --prometheus | grep queue_pop_messages

queenctl analytics (aliases a, an)

Every subcommand takes --from and --to (both empty by default), which accept the same time forms as replay --to.

Subcommand Route
overview GET /api/v1/status/analytics (composite dashboard data)
queue-lag GET /api/v1/analytics/queue-lag
queue-ops GET /api/v1/analytics/queue-ops
queue-parked GET /api/v1/analytics/queue-parked-replicas
retention GET /api/v1/analytics/retention
system GET /api/v1/analytics/system-metrics
worker GET /api/v1/analytics/worker-metrics
postgres GET /api/v1/analytics/postgres-stats

system, worker and postgres are operator-only through a proxy.

Operations

queenctl apply -f <file>

Applies YAML or JSON manifests. Multiple documents separated by ---; -f - reads stdin. Operations are idempotent and tolerant of resources that already exist.

Flag Default
-f, --file <path> required; - for stdin
--dry-run false (parse and validate only)

Two kinds, flat, with no apiVersion dance. kind defaults to Queue; a document with no name is skipped, and an unknown kind exits 1.

kind: Queue
name: orders
namespace: billing
task: ingest
config:
  leaseTime: 60
  retryLimit: 5
  deadLetterQueue: false
---
kind: ConsumerGroup
name: analyzer
queue: orders
seek-to: beginning

Queue accepts name, namespace, task and a config block whose keys are the Go QueueConfig field names in camelCase. ConsumerGroup accepts name, queue (required) and seek-to, which takes the same forms as replay --to; a ConsumerGroup with no seek-to has nothing to apply and is reported as skipped. Applying nothing exits 4.

apply sends mode: replace, and that is the point of a manifest: the document is the whole desired configuration of the queue it names, so an option it does not mention goes back to its default even if a previous apply or a queue configure had set it. Applying the same file twice, or applying it after someone nudged an option by hand, converges on the file. Reach for queenctl queue configure instead when you mean to nudge one option and leave the rest alone.

queenctl maintenance (alias maint)

Subcommand Flags
get none
on --yes (required)
off none

--pop is a persistent flag on the parent, so it applies to all three: it scopes the operation to pop-only maintenance (/api/v1/system/maintenance/pop) instead of push maintenance (/api/v1/system/maintenance).

queenctl bench

An end-to-end smoke benchmark: push --total messages across --partitions partitions in --batch-sized batches from --producers goroutines, then drain with --consumers goroutines.

Flag Default
--queue <name> queenctl-bench
--total <n> 10000
--batch <n> 100
--partitions <n> 4
--producers <n> 4
--consumers <n> 4
--payload-size <bytes> 64
--skip-pop false
--auto-create true
--auto-delete false

It reports the push wall time, the pop wall time, and per-message residency percentiles.

Utility

queenctl config

Subcommand Aliases Arguments
view none none
current-context current none
get-contexts list, ls none
set-context <name> none exactly one
use-context <name> none exactly one
delete-context <name> rm exactly one

set-context flags:

Flag Default Effect
--server <url> none Broker URL.
--token <token> none Bearer token, stored in the keychain by default.
--token-ref <ref> none Explicit reference: env, keychain://<id>, file:///abs, literal:<value>.
--insecure false Skip TLS verification.
--no-keychain false Store --token literally in the config file. Less secure.

view prints the resolved configuration as YAML. delete-context also clears current-context when it pointed at the removed context.

queenctl completion [bash|zsh|fish|powershell]

Writes a completion script to stdout. Exactly one argument, from that list.

queenctl docs

Prints a curated list of documentation links.

Flag Default Effect
--open false Launch the target in the default browser instead of printing it.
--topic <name> none Deep-link one page. A short name such as quickstart or http-api, or any site path such as reference/http/pop.
--llms false Print https://queenmq.com/llms-brief.txt, the self-contained summary written for AI agents, and https://queenmq.com/llms.txt, the page index.

--topic and --llms cannot be combined. The short names the previous flat site used (concepts, architecture, server, sizing and the rest) still resolve, onto whichever page that content moved to. With -q, --llms prints the two URLs alone, which is the form to pipe into a fetch.

No kv and no timers commands

There is no queenctl kv and no queenctl timers, and their absence is a decision rather than an oversight.

The CLI’s value for those two surfaces is inspection: peek a timer, list a queue’s pending timers, read a key. That is exactly the part of them that a curl against the kv routes and the timer routes already covers, and the writing part, the part a CLI would make convenient, is the part that belongs in an application’s own transaction rather than at a prompt. A queenctl kv put next to a queenctl timers schedule would also make both surfaces look like operator tools, which they are not: they are application state, and the operator’s controls over them are the grants, the quotas and the runtime kill switches described under Self-hosting.

Those controls do have a place at a prompt, and they already have one: they are POST /api/v1/system/kv-timers and rows in queen.kv_quota, not a kv put.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close