@cognelis/langfuse-cli
v2.4.0
Published
Interact with the Langfuse API from the command line, with keyring-backed profiles
Maintainers
Readme
langfuse-cli
langfuse-cli is a command-line client for repeatable Langfuse work in terminals, CI jobs, and JSON pipelines. It keeps the secret key in the operating-system credential store, so a working setup never leaves a key in a dotfile, in an environment variable on disk, or in your shell history.
The repository also ships one Agent Skill and plugin manifests, so Codex and Claude Code can use the CLI without inventing commands or handling credentials.
Install
Node.js 20+ or Bun. The CLI has zero runtime dependencies.
npm i -g @cognelis/langfuse-cli
langfuse-cli --versionOr from a checkout:
bun install
bun run build
npm i -g .The binary is named langfuse-cli. The package is scoped because the bare
langfuse-cli name belongs to upstream.
Single-file executable
An npm i -g install ties the CLI to the Node version it was installed under.
Every release also carries
a standalone executable with no Node dependency and all six API contracts
embedded, for macOS, Linux and Windows on both x86-64 and arm64:
# pick the asset for your platform: langfuse-cli-{darwin,linux}-{arm64,amd64}
# or langfuse-cli-windows-{arm64,amd64}.exe
curl -fsSL -o langfuse-cli \
https://github.com/cognelis/langfuse-cli/releases/latest/download/langfuse-cli-darwin-arm64
chmod +x langfuse-cli && ./langfuse-cli --versionEach asset is built and started on a runner of its own platform before the
release is published, and checksums.txt in the same release verifies a
download:
shasum -a 256 --ignore-missing -c checksums.txtOr build it from a checkout:
bun run compile # writes dist/langfuse-cli
bun run compile ~/bin/langfuse-cli
bun run compile ./langfuse-cli --target bun-linux-arm64 # cross-compileThe contracts have to be embedded rather than read from disk: a compiled binary
has no dist/contracts/ beside it, and Bun's bundler cannot see a runtime
readFile(new URL(...)). They are carried as unparsed strings and parsed on
demand, so startup does not pay for six snapshots. The result is ~60 MB (Bun
bundles its runtime) and starts in about 10 ms once the page cache is warm.
Authenticate
For a workstation, start the guided setup. It creates or repairs a profile, masks the secret key while typing, verifies the connection before saving, stores the key in the operating-system credential store, activates the profile, and offers to install the bundled Skill.
langfuse-cli initThe setup is deliberately terminal-only and never starts implicitly. If input or output is redirected it exits without changing anything and prints the equivalent non-interactive commands.
For CI and short-lived shells, use environment variables. Keep the secret in the CI provider's secret store and disable shell tracing.
set +x
export LANGFUSE_HOST="https://cloud.langfuse.com"
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="...from a secret store..."
langfuse-cli doctor --jsonFor scripts that intentionally persist a profile, profiles store only the host and the public key. The secret key is verified before it is stored in the keyring.
langfuse-cli profile add production --host https://langfuse.example.com --public-key pk-lf-... --description "Production traces"
langfuse-cli profile use production
printf '%s' "$LANGFUSE_SECRET_KEY" | langfuse-cli auth login --secret-key-stdin
langfuse-cli doctor --jsonRunning langfuse-cli auth login without --secret-key-stdin opens a masked prompt. Use --no-verify only for an intentional offline setup; the result records a warning. There is intentionally no --secret-key flag, so the secret never enters shell history or process listings. config show and profile show report only whether a key exists, never its value.
Keep test and production instances in separate profiles. Select one for a single command with --profile, or change the default with profile use:
langfuse-cli --profile test init
langfuse-cli --profile prod init
langfuse-cli profile use test
langfuse-cli --profile prod profile show
langfuse-cli --profile prod api projects listConnections resolve in this order: --host, then --profile, then LANGFUSE_HOST (or LANGFUSE_BASE_URL), then the default profile. LANGFUSE_SECRET_KEY overrides the selected profile's stored key; unset it to use that profile's credential. Because --host and LANGFUSE_HOST name an address directly, they take their credentials from the environment — a stray profile can never silently supply a key for an address you typed by hand.
Changing an instance address with profile update <name> --host <url> clears the saved secret key before saving the new address, and tells you to authenticate again. An equivalent normalized URL preserves the key. If the credential store cannot clear the old key, the address is left unchanged.
profile add and profile update accept --description to record what an environment is for; profile list and profile show return it. A description is trimmed and must be one line of at most 200 characters, --description "" clears it, and changing only the description keeps the saved secret key. The bundled skill chooses a profile by its name and description: it uses a profile the user names, uses a production profile only when the request names production, matches the request against profile list, asks the user when several profiles or none could match one project, answers a question about a whole environment from each of its profiles, and otherwise keeps the environment already in use or the default profile.
profile add and profile update also accept --api-version to save the API contract a profile's calls use: an exact or major version, latest, or auto to detect it from the server, which suits a self-hosted v3 deployment (profile update selfhosted --api-version auto). The version resolves from --api-version, then LANGFUSE_API_VERSION, then the saved version of the profile the connection comes from, then the newest bundled contract. --api-version "" clears it, and a version that no bundled contract matches is rejected when saved.
Where the secret actually goes
| Platform | Backend | Command used |
|---|---|---|
| macOS | login keychain | security |
| Linux | Freedesktop secret service | secret-tool |
| Windows | DPAPI, per-user encryption | powershell |
| fallback | credentials.json, mode 0600 | — |
Every backend receives the secret on stdin; it is never passed as an argument, so it stays out of the process list. The fallback is only chosen when no system store is reachable, and every command that relies on it prints a warning. Force it with LANGFUSE_CREDENTIAL_STORE=file.
Diagnose a connection
Run the read-only diagnostic before remote automation or when a connection fails:
langfuse-cli doctor --jsonIt checks the CLI version, connection configuration, credential availability, API connectivity, and authentication in that order — nothing downstream is meaningful until the step before it passes. A completed run always writes its ordered pass/fail/skipped report to stdout, then exits with the code matching the first failure, so scripts should capture stdout before testing the status.
A last contract check compares the server version from that same health probe with the API contract in use. When --api-version auto would pick a different contract — a self-hosted Langfuse 3.162.0 against the bundled 4.16.0, for example — its message names both versions and the fix: langfuse-cli profile update <profile> --api-version auto, or --api-version auto / LANGFUSE_API_VERSION=auto without a profile. It is advice, so the check passes and never changes healthy or the exit code:
contract pass API contract 4.16.0 does not fit Langfuse 3.162.0 (auto picks 3.150.0): run `langfuse-cli profile update selfhosted --api-version auto`Query Langfuse
# Discover resources
langfuse-cli api help
langfuse-cli api prompts help # or: api prompts --help
langfuse-cli api prompts create help # or: api prompts create --help
langfuse-cli api schema --json # machine-readable discovery
# Prompts
langfuse-cli api prompts list
langfuse-cli api prompts get my-prompt
langfuse-cli api prompts create --type text --name my-prompt --prompt 'Hello {{name}}'
langfuse-cli api prompts create --type chat --name support \
--prompt '{"role":"system","content":"be nice"}' \
--prompt '{"role":"user","content":"{{question}}"}'
# Observations, traces, datasets, scores
langfuse-cli api observations list --limit 10
langfuse-cli api observations list --trace-id <trace-id>
langfuse-cli api observations list --is-root-observation # booleans: --flag, --flag=false or --no-flag
langfuse-cli api datasets list
langfuse-cli api dataset-items list --dataset-name my-dataset
langfuse-cli api scores list --limit 20
# Fetch every page of a paginated list (page- and cursor-based), max 1000 items
langfuse-cli api observations list --all
langfuse-cli api observations list --limit 100 --all --max-items 5000
# Preview the request without sending it
langfuse-cli api observations list --limit 5 --curl
# Pin an API snapshot for an older self-hosted deployment
langfuse-cli --api-version 3 api traces list
langfuse-cli --api-version 3.150.0 api traces list
langfuse-cli --api-version auto api prompts list # detect via /api/public/health
langfuse-cli profile update selfhosted --api-version auto # or save it with the profile
# Bundled snapshots, the one in use, and what auto picks for the server
langfuse-cli api versions list
langfuse-cli api versions current --json # data: {"version":"4.16.0","requested":"latest"}
langfuse-cli api versions detect # 3.162.0 -> 3.150.0Action help prints every option's full description, wrapped to the terminal width (100 columns when stdout is not a terminal) with continuation lines under the description column; long ones such as --filter document their columns and examples in full.
api versions list|current|detect print plain text — one version per line, the resolved version, <server> -> <contract> — even in a pipe, so $(langfuse-cli api versions current) keeps working. With --json or --raw they emit {"versions": [...]}, {"version", "requested"} (plus "detected" under auto) and {"detected", "version"} in the usual envelope or as the bare payload.
OpenAPI tags and explicit route versions remain accepted aliases, for example scores-v3 list for the canonical scores list. Verbose OpenAPI operationId values remain available in api schema --json but are never required as CLI commands.
--body-json and --body-file provide a lossless input path for nested objects, arrays, unions, and free-form JSON. Simple request-body fields also get generated kebab-case flags (for example --object-id for the objectId field), consistent with query-parameter flags; wire names are never affected.
Union bodies with a discriminator (for example prompt create's chat vs. text variants) support field flags directly: the discriminator flag selects the variant and the CLI validates against that variant's schema — langfuse-cli api help <resource> <action> shows the per-variant fields. Unions without a clean discriminator stay --body-json-only rather than guessing.
Command groups
init
doctor
api <resource> <action>
auth login|check|logout
profile add|update|list|show|use|remove
config path|show
skills install [--target all|codex|claude]
completion zsh|bash|fishOutput contract
Successful JSON output has a stable versioned envelope:
{
"schemaVersion": "1",
"command": "api.projects.list",
"data": {},
"meta": {
"status": 200,
"profile": "prod",
"host": "https://langfuse.example.com",
"elapsedMs": 72
}
}Format selection follows --output:
| value | behaviour |
|---|---|
| auto (default) | a table on a terminal, the JSON envelope everywhere else |
| table | human-readable columns |
| json | the versioned envelope (same as --json) |
| raw | the payload only, no envelope (same as --raw) |
A human sees columns and a pipeline receives parseable output, neither having to pass a flag:
$ langfuse-cli profile list
current name host key
------- ---- ---------------------------------- ------
prod https://langfuse.example.com stored
* test https://langfuse-test.example.com stored
Profile: test
$ langfuse-cli profile list | jq -r '.data[].name'
prod
testTable output matches signoz-cli: a header over a dashed rule, columns sized to
their widest cell, key: value blocks with aligned colons, and the trailing
Profile: line on stderr so stdout stays clean even when a table is piped.
Diagnostics and warnings go to stderr; stdout stays clean for pipelines. The single exception is doctor, whose report is the result and so is written to stdout even when it exits nonzero. --out-file <path> writes the response body to a file instead of stdout.
Exit codes
| Code | Meaning | | ---: | --- | | 0 | Successful API response or local command | | 1 | Unexpected internal failure | | 2 | Invalid command or input (usage); no request sent | | 3 | Missing or invalid configuration/credentials; no request sent | | 4 | Network, DNS, TLS, or timeout failure reaching the host | | 5 | The API responded with a non-success HTTP status (response is still printed) | | 6 | Local file or bundled-contract failure |
Install the agent skill
# Install for both Codex and Claude Code (default)
langfuse-cli skills install
# Only one agent
langfuse-cli skills install --target codex
langfuse-cli skills install --target claudeThe skill ships inside the package, so installation needs no network access, no host, and no credentials. It installs SKILL.md together with every references/*.md the skill links to — printing only the top-level file, as the removed get-skill command did, left those links dangling. The operation is idempotent: rerunning updates managed files, reports unchanged when nothing differs, and preserves unrelated files in the target directory.
Targets are ~/.agents/skills/langfuse for Codex and $CLAUDE_CONFIG_DIR/skills/langfuse (default ~/.claude/skills/langfuse) for Claude Code.
Advanced: plugin lifecycle alternatives
The same skill directory is packaged for both agents at plugins/langfuse-cli/skills/langfuse. Use the plugin lifecycle instead when you want the repository marketplace installation.
For Claude Code:
/plugin marketplace add /absolute/path/to/langfuse-cli
/plugin install langfuse-cli@langfuse-cliFor Codex:
codex plugin marketplace add /absolute/path/to/langfuse-cliThe Claude Code manifest is plugins/langfuse-cli/.claude-plugin/plugin.json; the Codex manifest is plugins/langfuse-cli/.codex-plugin/plugin.json.
Shell completion
# zsh — any directory on $fpath
langfuse-cli completion zsh > ~/.zsh/completions/_langfuse-cli
exec zsh
# If that directory is new, put it on $fpath before compinit runs:
# fpath=(~/.zsh/completions $fpath)
# oh-my-zsh users can instead use ~/.oh-my-zsh/completions, which is
# already on $fpath.
# bash
langfuse-cli completion bash > /usr/local/etc/bash_completion.d/langfuse-cli
# fish
langfuse-cli completion fish > ~/.config/fish/completions/langfuse-cli.fishCompletion covers commands, subcommands, global options, profile names, API resources, their actions, and each action's own parameters:
langfuse-cli <TAB> init doctor api auth profile config skills completion
langfuse-cli --profile <TAB> test prod (from your config)
langfuse-cli api <TAB> prompts traces observations datasets scores ...
langfuse-cli api prompts <TAB> create delete get list update-version
langfuse-cli api prompts list --<TAB> --name --label --tag --page --limit --all ...Candidates are grouped, so subcommands are listed as their own block instead of
sorting alphabetically among the options — --json no longer lands between
init and profile. The wire format is value<TAB>description<TAB>group, and
the zsh script offers each group through a separate _describe call.
The generated script contains no command table. It calls back into a hidden
__complete command on every keypress, which is what keeps it correct when the
CLI is upgraded, when profiles change, and — the reason a static script would
be wrong here — when --api-version changes the command surface: 3.50 exposes
22 resources, 4.16 exposes 37. Completion never performs network I/O, so
--api-version auto falls back to the newest bundled contract rather than
probing the server.
The callback reads the config for profile names but never opens the credential store, and any failure yields no candidates instead of an error, so a broken config cannot corrupt the shell's display.
Relationship to upstream
This is a fork of langfuse/langfuse-cli, maintained as a companion to signoz-cli with the same operational contract: keyring-backed credentials, named profiles, an ordered doctor, a versioned JSON envelope, stable exit codes, and a bundled agent skill. It diverges from upstream deliberately:
--secret-keywas removed; useauth loginorLANGFUSE_SECRET_KEY.--output <path>became--out-file <path>;--outputno longer selects a format.--jsonnow emits the versioned envelope rather than{status, headers, body}.get-skillbecameskills install.completion zsh|bash|fishwas added, backed by a__completecallback.bun run compileproduces a standalone executable with the contracts embedded.--outputselects the format (auto|table|json|raw) instead of naming a file, and human-readable output is rendered as tables aligned with signoz-cli.
The multi-version OpenAPI contract system and the conformance suite are inherited from upstream unchanged.
API Reference
See the full Langfuse API Reference.
Contributing
The CLI is implemented in TypeScript and runs on Node.js 20+ or Bun. It has zero external runtime dependencies and never parses OpenAPI during invocation.
See MAINTENANCE.md for build, API snapshot, testing, and release workflows. The version-pinned black-box suite is documented separately in conformance/README.md.
