Skip to content

CLI Reference

The Framedash CLI provides terminal access to telemetry data, analytics, and project management. It integrates into CI/CD pipelines for automated map uploads and content syncing.

Requires Node.js and npm. Install it globally, or run it on demand with npx without installing.

Terminal window
# Install globally
npm install -g @framedash/cli
# Or run without installing
npx @framedash/cli --help

Provide the API key via an environment variable, or read it from a file:

Terminal window
# Environment variable
export FRAMEDASH_API_KEY=fd_your_api_key_here
# Or from a file (use - to read from stdin)
framedash status --api-key-file ./read.key

Verify your key is valid and see the bound project:

Terminal window
framedash auth

The CLI can also sign in interactively with framedash login, which stores an OAuth session so you do not need to pass a key for subsequent non-SQL analytics reads.

Most commands accept these options:

OptionDescription
--api-key <key>API key (or FRAMEDASH_API_KEY env)
--api-key-file <path>Read the API key from a file (- for stdin)
--project-id <uuid>Project ID (or FRAMEDASH_PROJECT_ID env)
--base-url <url>API host URL (default: https://app.framedash.dev)
--format <fmt>Output format: json, table, csv (default: json)
-h, --helpShow help

This page covers the commands you reach for most often. Run framedash --help (or framedash <command> --help) for the authoritative, version-specific list.

Verify your API key and show the bound project.

Terminal window
framedash auth

Options: --api-key, --base-url, --format

With --format json, stdout contains only the JSON document, so it is safe to pipe to tools such as jq. Validation and credential-source status remain visible on stderr.

framedash login signs you in interactively with the OAuth 2.1 authorization code flow and PKCE (S256). It opens your system browser to {base-url}/oauth/authorize with a loopback redirect and waits up to 5 minutes for you to approve.

Terminal window
framedash login
OptionDescription
--scopes <list>Space-delimited scopes to request (default analytics:read)
--no-browserPrint the authorization URL instead of opening a browser
--base-url <url>Authorization server (default https://app.framedash.dev)

Tokens are stored in ~/.config/framedash/credentials.json (honoring XDG_CONFIG_HOME; the same path convention applies on Windows), keyed by server origin, and refreshed automatically. Token values are never printed.

framedash logout revokes the token server-side (best effort) and deletes the local credentials for the resolved base URL. Pass --all to clear every origin. API keys are unaffected.

Terminal window
framedash logout
framedash logout --all

An explicit --api-key, --api-key-file, or FRAMEDASH_API_KEY always takes precedence over a stored login, so CI should authenticate with FRAMEDASH_API_KEY rather than framedash login. Grants made through login can be reviewed and revoked in the dashboard under Settings -> Connected apps.

List the projects you can access (id, name, createdAt). Works with API keys that carry the analytics:read scope, or with OAuth logins, and needs no --project-id. An Ingest key with only events:write cannot list projects. Available from @framedash/cli 0.1.4.

Terminal window
framedash projects list --format table

Set the output with --format json|table|csv.

Show project health status.

Terminal window
framedash status

kpis.fetchedAt is the Unix timestamp in milliseconds when the KPI snapshot was queried. Status normally uses the server cache; pass --fresh to bypass it when CI or troubleshooting needs a newly queried snapshot. This option is not included in @framedash/cli 0.1.7; install a later release and confirm that it appears in framedash status --help before using it.

Show dashboard KPIs (DAU, MAU, sessions, events).

Terminal window
framedash dashboard --days 30
OptionValuesDefault
--days7, 14, 30, 9030

Show player retention cohorts (D1, D7, D30).

Terminal window
framedash retention --days 14
OptionValuesDefault
--days7, 14, 30, 9030

Analyze event funnels to measure player conversion between steps.

Terminal window
framedash funnel --steps "player_spawn,player_death,player_respawn"
OptionDescriptionDefault
--stepsComma-separated event names (required, 2-8 steps)
--windowTime window in seconds (3600, 21600, 86400, 604800)86400
--daysTime period (7, 14, 30, 90)30

List build IDs seen for the project, newest first. Use this to pick build IDs for framedash perf-diff.

Terminal window
framedash builds --days 30
OptionValuesDefault
--days7, 14, 30, 9030

Compare two builds’ P50/P95 performance (frame time, memory, GPU time). With --fail-on-regression, the command exits 1 when the candidate regressed beyond the threshold.

Terminal window
framedash perf-diff --baseline "$BASE_SHA" --candidate "$GITHUB_SHA" \
--threshold 5 --fail-on-regression
OptionDescription
--baseline <id>Known-good build_id (required)
--candidate <id>New build_id under test (required)
--metric <name>Restrict the gate to one metric: frame_time, memory, gpu_time, io.read_bytes, io.read_time_ms, io.read_ops, load_time_ms, or mem.vram (default: all). load_time_ms needs SDK map-load timing; mem.vram needs SDK VRAM sampling
--threshold <pct>Allowed regression percentage. Default is 0
--fail-on-regressionExit non-zero when a regression is detected
--days <n>Time period: 7, 14, 30, 90 (default 30)
--map <id>Restrict the comparison to one map
--platform <name>Restrict the comparison to one platform

--baseline and --candidate must be different build IDs. The CLI rejects identical IDs so a misconfigured CI job fails fast instead of comparing a build against itself.

Run a profiling build end-to-end for CI: export the FRAMEDASH_* automated-session variables, launch your game/profiling command, wait for its telemetry to ingest, then run the perf-diff gate against a baseline. The turnkey companion to builds and perf-diff.

Terminal window
framedash run-profile-test \
--command "./Build/Game.exe -nullrhi -ExecCmds='Automation RunTest Perf'" \
--scenario nightly --api-key-file ci-read.key \
--baseline "$BASE_SHA" --threshold 5 --fail-on-regression
OptionDescription
--command <cmd>Game/profiling command to launch via the shell (required)
--build-id <id>Candidate build_id (default: --commit, else git HEAD)
--branch <name>Default: git rev-parse --abbrev-ref HEAD
--commit <sha>Default: git rev-parse HEAD
--scenario <name>Test scenario label
--ingest-timeout <s>Max seconds to wait for fresh telemetry (default 180)
--poll-interval <s>Seconds between ingest polls (default 5)
--skip-waitSkip the ingest wait
--baseline <id>Known-good build_id to gate the candidate against

The --baseline, --metric, --threshold, --fail-on-regression, --days, --map, and --platform flags behave exactly as in framedash perf-diff.

The launched command inherits FRAMEDASH_BUILD_ID / FRAMEDASH_GIT_BRANCH / FRAMEDASH_GIT_COMMIT / FRAMEDASH_TEST_SCENARIO. Once an SDK calls BeginAutomatedSessionFromEnvironment(), every event is tagged automatically, with no per-event tagging code. See CI Profiling for the full setup.

Execute a SQL query against telemetry data.

Terminal window
# Inline SQL
framedash query "SELECT event_name, count() FROM events GROUP BY event_name"
# From a file
framedash query --file ./queries/daily-active.sql
OptionDescription
--file <path>Read SQL from a file instead of inline argument
--limit <n>Maximum rows to return

Manage performance alert rules.

Terminal window
# List all alert rules
framedash alerts list
# Create a new alert rule
framedash alerts create --name "FPS Alert" --map-id <uuid> \
--threshold-profile-id <uuid> --metric fps --threshold-level warn \
--fail-percentage 20 --evaluation-days 7 --cell-size 25 \
--cooldown-minutes 60
# Update an alert rule
framedash alerts update <alert-id> --name "Updated Alert"
# Deactivate an alert rule
framedash alerts delete <alert-id>

Manage the performance threshold profiles that alert rules reference (the warn/good bands per metric).

Newly created projects start with one automatically created profile named Default (all-wildcard device filters and built-in default thresholds), so alert rules can reference a profile right away; customize it or add more later.

Terminal window
# List threshold profiles
framedash threshold-profiles list
# Create a threshold profile
framedash threshold-profiles create --name "Console 60fps" \
--fps-good 60 --fps-warn 30 --platform windows
# Delete a threshold profile
framedash threshold-profiles delete <profileId>

framedash threshold-profiles create (@framedash/cli 0.1.7 and later) adds a profile. --name is required (max 100 characters). Threshold pairs are optional and fall back to the built-in defaults when omitted: --fps-good / --fps-warn (FPS), --frame-time-good / --frame-time-warn (ms), --memory-good / --memory-warn (MB), and --gpu-time-good / --gpu-time-warn (ms). FPS is higher-is-better (good above warn); the others are lower-is-better (good below warn). Optional device filters (--platform, --resolution such as 1920x1080, --build-config, --gpu, --storage) scope the profile, and an omitted filter acts as a wildcard. Creation needs an API key with the resources:write scope, or an OAuth session from a framedash login run with --scopes including resources:write (the default login requests analytics:read only); the server rejects a duplicate name or an identical device-filter combination (all five filters equal) with 409, while a partially overlapping combination succeeds and the command reports the overlapping profile names. On success the CLI prints Threshold profile created and the new profile.

framedash threshold-profiles delete <profileId> (@framedash/cli 0.1.8 and later) removes a profile. The command needs an API key with the resources:write scope, or an OAuth session from a framedash login run with --scopes including resources:write (the default login requests analytics:read only). A profile that is still referenced by an alert rule (as a primary threshold profile or as a bundle member) is rejected with 409; deactivating a rule does not unblock the deletion, so re-point the referencing alert rules at another profile with framedash alerts update <alert-id> --threshold-profile-ids <...> first.

Manage game maps.

Terminal window
# List maps
framedash maps list
# Delete a map by map identifier
framedash maps delete <map-id>

The identifier here is the mapId slug you assigned when capturing the map, not the internal UUID primary key.

Upload captured map images. This command has its own option parser and does not use the shared global options.

Terminal window
# Preview what would be uploaded (dry run)
framedash map-capture --input-dir ./captures --upload --dry-run
# Upload map captures
framedash map-capture --input-dir ./captures --upload \
--api-key fd_xxx --project-id <uuid>
OptionDescription
--input-dir <path>Directory containing captured map images (required)
--uploadActually upload the captures (required to perform upload)
--api-key <key>API key for upload
--project-id <uuid>Target project
--base-url <url>API base URL
--dry-runPreview upload without sending
--metadata-pattern <glob>Only read matching JSON sidecars (for example *.capture.json)

--input-dir is scanned non-recursively. By default every *.json file is treated as a capture metadata sidecar. If the directory also contains unrelated JSON, use --metadata-pattern '*.capture.json' and name sidecars accordingly. The --metadata-pattern option is not included in @framedash/cli 0.1.7; install a later release and confirm that it appears in framedash map-capture --help before using it. The command reads each selected JSON file, validates it, and uploads the image it references. A sidecar points at its image through image_path, resolved relative to the input directory. Absolute paths and paths that escape the directory are rejected; subdirectories inside the input directory are allowed. Supported image formats are .png, .jpg, .jpeg, and .webp.

Required fields:

FieldTypeNotes
versionstringMust be the literal "1.0"
map_idstring1-128 characters
image_pathstringPath to the image, relative to --input-dir
image_dimensionsobject{ "width", "height" }, positive integers
world_boundsobject{ "min": {x,y,z}, "max": {x,y,z} }, finite numbers. max.x must exceed min.x and max.y must exceed min.y; z is unconstrained. Values are engine world units (for example, centimeters in Unreal Engine)

Optional fields:

FieldTypeNotes
projectionstringFree text
capture_axisstringFree text
coordinate_systemstringFree text
enginestringFree text
build_idstringUp to 255 characters
captured_atstringISO 8601 datetime

Worked example. Place a sidecar arena.json next to its image arena.png (a 1024x1024 top-down capture) in ./captures:

{
"version": "1.0",
"map_id": "arena_dust",
"image_path": "arena.png",
"image_dimensions": { "width": 1024, "height": 1024 },
"world_bounds": {
"min": { "x": -8192, "y": -8192, "z": 0 },
"max": { "x": 8192, "y": 8192, "z": 2048 }
},
"engine": "UE5",
"captured_at": "2026-07-16T09:30:00Z"
}

Validate first with --dry-run. It needs no credentials and uploads nothing:

Terminal window
framedash map-capture --input-dir ./captures --dry-run

The dry run prints one line per file (map_id=..., image=..., bounds=[minX,minY]->[maxX,maxY], size=WxH), then a Done: N succeeded, M failed summary, and exits 1 if any file failed validation. Once it reports no failures, upload for real. The upload is a multipart POST to /api/v1/maps/upload and needs an API key with the resources:write scope:

Terminal window
framedash map-capture --input-dir ./captures --upload \
--api-key fd_xxx --project-id <uuid>

Manage the content registry (items, weapons, event types, etc.).

Terminal window
# List content entries
framedash content list
# Import content from a JSON file
framedash content import ./game-content.json
# Delete by UUID
framedash content delete <uuid>
# Delete by type and content ID
framedash content delete --type weapon --content-id ak47

Import JSON may be an array or { "entries": [...] }. Every entry requires non-empty string fields contentType, contentId, and displayName. Optional description and category are strings or null; metadata is an object or null. Validation errors identify the 1-based entry number before any request is sent.

The hourly API rate limit is enforced per account (tenant) and shared across every project and API key the account owns. The free plan allows 100 requests per hour; higher plans allow more. Because the budget is shared, parallel CLI invocations and separate keys all draw from the same pool.

On a 429, honor the Retry-After header on every response rather than scheduling around the X-RateLimit-Reset timestamp. Both values are derived from a sliding window, so they can understate the real wait immediately after the advertised reset, and retrying exactly at the reset time may hit another 429.

GitHub Actions example:

- name: Upload maps and import content
env:
FRAMEDASH_API_KEY: ${{ secrets.FRAMEDASH_API_KEY }}
FRAMEDASH_PROJECT_ID: ${{ vars.PROJECT_ID }}
run: |
framedash map-capture --input-dir ./map-captures --upload
framedash content import ./game-content.json

For Jenkins and TeamCity examples, see the CI/CD Integration Guide.