Elyra
Elyra The coding agent e The native code editor Elyra Grove Native local development environment Askr The real server for Laravel & PHP Elyra Framework Rust + Svelte 5 framework for desktop apps Elyra Conductor Local project conductor Elyra SQL Server MySQL-compatible SQL server in Rust Elyra Félagi Agents as teammates on one board Elyra SQL Client Native desktop SQL workbench Elyra SQL Anywhere Replication-ready SQL engine Elyra Sjá SEO & GEO workspace for macOS Elyra DataGrid Server-driven data grid for Laravel
Release notes
Changelog
Elyra
CLI reference

CLI reference

askr <command> [options]

Commands: serve, test, replay, doctor, tune, cache-report, config-check, upgrade. Run askr <command> --help for the built-in help.

Global: -V/--version, -h/--help. Logging verbosity is RUST_LOG (e.g. RUST_LOG=askr=debug); default askr=info.


askr serve

Serve a PHP application over HTTP(S).

askr serve --root ./public --worker-script examples/laravel-worker.php \
  --workers 8 --tls-self-signed --admin 127.0.0.1:9000

Pass options as flags, or put everything in a config file and run askr serve --config askr.toml (the file is then the single source of truth and the other flags are ignored). See Configuration.

Core

Flag Default Meaning
--config <FILE> Load all settings from askr.toml (other flags ignored).
--root <DIR> ./public Document root.
--front <FILE> index.php Front controller, relative to root.
--listen <ADDR> 127.0.0.1:8000 Address to bind.
--https off Mark requests as HTTPS in $_SERVER (behind a TLS terminator).
--ini <LINES> Extra php.ini lines (e.g. opcache). Overrides $ASKR_PHP_INI.
--max-body-size <SIZE> 16M Reject larger bodies with 413 (K/M/G).
--force-https off Redirect plain HTTP to HTTPS (308). Host redirects (www→apex) go in [[redirect]] in askr.toml.
--tls-handshake-timeout <SECS> 10 Max time a client may take to finish the TLS handshake (slowloris guard).
--header-read-timeout <SECS> 15 Max time a client may take to send request headers (slowloris guard).

Workers & scaling

Flag Default Meaning
--workers <N> CPU cores (cgroup-aware) Worker processes (one interpreter each).
--worker-script <FILE> Boot the app once, serve many (Octane model). Omit for per-request.
--max-requests <N> 0 Recycle each worker after N requests (0 = never).
--max-rss <MB> 0 Recycle a worker gracefully once its RSS exceeds N MB (0 = never). Leak-aware: drains before PHP hits memory_limit and OOMs. Linux only.
--shadow-to <URL> Mirror sampled safe (GET/HEAD, cookie-less) requests to this upstream (e.g. a staging deploy of the next version) and report divergence on /metrics. Never affects the client.
--shadow-sample <PCT> 100 Percent of eligible requests to mirror to --shadow-to.
--workers-min <N> --workers CoW autoscaling floor.
--workers-max <N> --workers CoW autoscaling ceiling (> min enables autoscaling).
--cow off CoW template: boot once, fork warm workers (~ms respawn). Needs --worker-script.
--paranoid off Dev: detect state bleed between requests (worker mode; expensive).

TLS

Flag Default Meaning
--tls-cert <PEM> / --tls-key <PEM> Serve HTTPS from a cert + key (ALPN h2/http1.1).
--tls-self-signed off Generate a self-signed cert on startup (dev).
--http3 off Serve HTTP/3 (QUIC) on the TLS port alongside h1/h2, advertised via Alt-Svc. Requires --tls-cert/--tls-key; build with --features http3 (in the -full image).
--acme off Auto-TLS via ACME/Let's Encrypt (HTTP-01). See AUTOTLS.
--acme-domain <D> Domain(s) (repeatable). Required with --acme.
--acme-email <E> ACME account contact.
--acme-dir <DIR> /var/lib/askr/acme Account + cert cache.
--acme-staging off Let's Encrypt staging.
--acme-http <ADDR> 0.0.0.0:80 Where to answer HTTP-01 challenges.
--acme-directory-url <URL> / --acme-ca-root <PEM> Custom ACME directory / CA (Pebble, private CA).

Sidecars (same process tree)

Flag Default Meaning
--queue <N> + --queue-script <FILE> 0 Supervised queue-worker processes (floor when autoscaling).
--queue-max <N> = --queue Autoscaling ceiling: scale the queue-worker pool between --queue and N on backlog (Horizon balance=auto, no extra daemon).
--scheduler-script <FILE> Run the built-in scheduler (cron).
--sidecar "<cmd>" Supervise an arbitrary command (repeatable), e.g. Inertia SSR.

In-binary services (no Redis/Reverb)

Flag Default Meaning
--cache-slots <N> 0 Shared kv cache (askr_cache_*; ~4.3 KB/slot).
--cache-large-slots <N> 0 Large-value region (64 KB/slot) — sessions, fragments.
--response-cache <N> 0 Full-response cache + tag invalidation (~140 KB/slot). See CACHE.
--queue-slots <N> 0 Shared-memory job queue (askr_queue_*; 32 KB/slot).
--broadcast off askr_broadcast() + SSE at /askr/events. See BROADCAST.
--pusher off Pusher-compatible WebSocket + trigger (drop-in Reverb; auto-enables broadcast).
--pusher-secret <S> $ASKR_PUSHER_SECRET Verify private/presence subscription auth.

Operations & hardening

Flag Default Meaning
--admin <ADDR> off Admin dashboard/API + Prometheus /metrics. Bind to localhost. See ADMIN.
--access-log <PATH|-> off JSON access log per request (- = stdout).
--canary off Canary reload: roll one worker + health-check before the rest.
--record-errors <DIR> off Persist 5xx requests for askr replay. Sensitive.
--sandbox off Linux hardening: seccomp no-exec. See SANDBOX.
--sandbox-write <DIR> Landlock: writes allowed only here (repeatable).

Signals: SIGHUP = graceful rolling reload; SIGTERM/SIGINT = graceful drain + shutdown.


askr test

Run tests by forking a fresh, warm process per file (boot once, perfect isolation).

askr test --root . --runner examples/askr-test.php tests/
Flag Meaning
[paths…] Test files/dirs (dirs scanned for *Test.php); default ./tests.
--root <DIR> App base ($ASKR_APP_BASE).
--runner <FILE> Runner invoked per file (PHPUnit/Pest); omit to run files directly.
--parallel <N> Concurrent files (default CPU cores).
--ini <LINES> Extra php.ini.

askr replay

Replay a recorded failing request (see serve --record-errors).

askr replay /var/lib/askr/errors/<id>.json

askr doctor

Pre-flight checks: PHP build, extensions (required + recommended), platform (io_uring probe on Linux). --ini <LINES> to load opcache. Exit non-zero on critical failure.

--app <PATH>

Also check an application against the environment it is about to run in. Point it at the app root (the directory with composer.json), ideally from inside the container that will serve it, so it sees the same environment:

docker compose exec askr /opt/askr/askr doctor --app /var/www/example.com
• SESSION_DRIVER=askr — needs --cache-large-slots (sessions exceed 4 KB)
• QUEUE_CONNECTION=askr — needs --queue-slots *and* a worker
  queue names found in app/: imports, mail, webhooks
  this worker would poll:    default
✗ jobs dispatched to imports, mail, webhooks would never be processed —
  set ASKR_QUEUE=imports,mail,webhooks,default
✓ MAIL_MAILER=resend with an API key present

Configuration is resolved the way the application will resolve it: a real environment variable beats .env, because Laravel's Dotenv skips variables that already exist. Each run names the source it used, so running it from the host against a containerised server — where it cannot see the container's environment — is visible rather than silently wrong. An empty real variable counts as unset, since that is what the application sees.

is an observation, a verified check, a failure that exits non-zero — so this can gate a deploy. The distinction matters: doctor cannot see the flags a later serve will get, and a tick that means "noted" teaches you to skim ticks.

What it looks for, all of it drawn from failures that cost real time:

  • Queue names. It greps app/ for onQueue('x') and $queue = 'x' and compares them with ASKR_QUEUE. A job dispatched to a queue no worker polls sits in the ring indefinitely with no error anywhere — the failure that made a site's password-reset and invitation mail silently stop.
  • Shared-memory drivers that need slots. SESSION_DRIVER=askr without --cache-large-slots loses sessions quietly, which surfaces as 419 on every form.
  • Mail that looks configured. MAIL_MAILER=resend with neither RESEND_KEY (Laravel's name) nor RESEND_API_KEY (Resend's own documentation) falls back to writing mail into the log.
  • Scheduled ->command() tasks, which shell out to a php binary. Askr compiles PHP in and ships no CLI, so in the Docker image every such task fails with exit code 127.

The grep is a grep: a queue name built at runtime won't be found, which is why the output says "found" rather than "all".

askr cache-report

Measure what caching would buy before caching anything — and whether it would be safe:

askr serve --config askr.toml --traffic-log /tmp/traffic.jsonl   # run for an hour
askr cache-report /tmp/traffic.jsonl
  pattern                        ttl    hit PHP saved  safety
  ----------------------------------------------------------------------------------
  /products/*                    60s    94%    1.48 s/m  ✓ identical for every visitor
  /dashboard                     94%   ...    0.11 s/m  ✗ unsafe: 15 responses differed
  /login                         88%   ...    0.06 s/m  ✗ unsafe: 8 responses set a cookie
  /                              88%   ...    0.05 s/m  ✓ identical for every visitor

  Safe rules alone would have removed 73% of the PHP time above.

  Suggested askr.toml:
    [[cache.rule]]
    path = "/products/*"
    ttl = 60

The reason full-page caching is rare isn't performance, it's uncertainty: nobody knows how much a rule would win, or whether it would serve one visitor's page to everyone.

--traffic-log writes one JSON line per request that ran PHP — so it describes the work still being done, not what the cache already absorbed — including a hash of the response body. cache-report then groups URLs into patterns (/products/1421/products/*), simulates each TTL, and for every candidate checks the question a hit-rate estimate can't answer:

did the same URL ever return different bytes inside the TTL window?

If it did, the page is personalised and caching it would be a bug — so it's marked unsafe and left out of the suggested config. Set-Cookie in the response is likewise disqualifying. Pages whose requests carried cookies get a warning rather than a refusal, because those cookies may be analytics only (see ignore_cookies).

The report states what it doesn't know: a sample shorter than a minute is flagged as too short to extrapolate, and "identical for every visitor" means during the sample — not forever.

--traffic-log is a diagnostic, not a permanent setting. It costs one write per PHP-served request and the file grows with traffic; turn it on, learn something, turn it off.

askr tune

Measure the app and print an askr.toml you can paste, with one line of reasoning per number:

askr tune --root public --requests 30
  PHP boot              182.4 ms
  Request (mean)        24.9 ms wall, 0.1 ms CPU
  RSS after warm-up     94 MB

  Suggested askr.toml:

    [server]
    workers = 64          # only 1% CPU-bound (waits on I/O) ⇒ more workers than cores
    max_rss_mb = 220      # 2× observed peak (110 MB); memory grew 0.31 MB/request

It runs your front controller in-process a number of times and measures boot time, wall vs CPU time per request (that ratio is what decides whether more workers than cores will help), memory growth, and response size.

There is deliberately no HTTP load generator: Askr's own benchmarks show PHP is ~99.5 % of request time, so the interpreter is the thing worth measuring. The output also prints what it didn't cover — one route, no cookies, no concurrency — because a wrong recommendation is worse than none. Run it against a copy of production data, not an empty database.

askr config-check

askr config-check askr.toml — validate a config file and print the resolved settings without starting the server.

askr upgrade

Self-update the release install in place. Downloads the matching Linux tarball from GitHub, verifies its sha256, and swaps the whole prefix (binary + bundled libphp) atomically — the previous version is kept at <prefix>/../askr.old for rollback. Does not restart the running server unless you pass --restart.

askr upgrade --check              # is a newer release available?
sudo askr upgrade                 # install the latest (then restart manually)
sudo askr upgrade --restart       # install + systemctl restart askr
sudo askr upgrade --version 0.8.0 # pin a version (also to roll back)
Flag Meaning
--check Report whether a newer release exists; install nothing.
--version <X.Y.Z> Install a specific version instead of the latest (rollback).
--restart Run systemctl restart askr after a successful swap.

Needs write access to the install directory (run with sudo for /opt/askr). Refuses to run inside a container — upgrade those by pulling a new ghcr.io/kwhorne/askr tag. Linux release only; elsewhere build from source.