@tsuga/cli
Command-line interface for managing Tsuga resources.
Installation
npm install -g @tsuga/cli
tsuga --help
Updating
Update to the latest published version:
tsuga self-update
This runs npm install -g @tsuga/cli@latest. The CLI also checks for new versions once a day and prints a notice when one is available.
Shell completion
Tab-complete commands and flags in bash, zsh, or fish.
For bash and zsh, add the matching line to your shell startup file:
# ~/.bashrc
eval "$(tsuga --completions bash)"
# ~/.zshrc
eval "$(tsuga --completions zsh)"
Then open a new shell (or source the file).
For fish, write the script to your completions directory:
tsuga --completions fish > ~/.config/fish/completions/tsuga.fish
Fish loads it automatically in new shells.
Authentication
All authentication lives under tsuga auth.
Log in as a user (recommended)
For interactive use, log in as a user with a browser authorization flow:
# Opens your browser to an authorization URL, or prints the URL if it can't be opened.
tsuga auth login
# Print the saved user access token
tsuga auth token
# Show the saved authentication method, and when the session expires or expired
tsuga auth status
# Ask Tsuga which credential this CLI authenticates as, and what it is allowed to do
tsuga auth whoami
# Clear the saved user session
tsuga auth logout
After tsuga auth login, commands work without an Operation API key. Sessions renew themselves until the refresh token expires or is revoked.
Operation API key
For non-interactive use (CI, scripts), authenticate with an Operation API key. Generate one from your Tsuga account settings, then save it once:
tsuga auth operation-key <OPERATION_API_KEY>
This writes the operation API key to $XDG_CONFIG_HOME/tsuga/config.json, or ~/.config/tsuga/config.json when XDG_CONFIG_HOME is unset. Alternatively, set it per-session without saving:
export TSUGA_OPERATION_API_KEY=<OPERATION_API_KEY>
A TSUGA_OPERATION_API_KEY env var overrides the saved credential for that one invocation.
Configuration
Show current configuration (includes defaults):
tsuga config
Usage data
The first time a command succeeds in an interactive terminal, the CLI asks once whether to send usage data.
Change the answer with tsuga config set telemetry on (or off). TSUGA_TELEMETRY=1 (or 0) overrides the saved answer.
Defaults
Commands have built-in defaults so you can omit common flags:
| Flag | Default |
|---|---|
--from | -30m |
--to | now |
--query | * |
--max-results | 100 |
Override defaults persistently. The value is given with --value:
tsuga config set default from --value=-1h
tsuga config set default query --value 'level:ERROR'
Defaults exist for from, to, query, max-results and cluster (see Clusters); any other name is rejected. from and to must be a valid time and max-results a positive integer, so a typo is reported when the default is set rather than by every later command.
Current defaults are listed by tsuga config (custom overrides marked with *).
Reset all custom defaults:
tsuga config reset defaults
Clear a single default by setting it to an empty value. Its built-in default, if it has one, applies again:
tsuga config set default cluster --value ''
Clusters
If your tenant has multiple clusters, list them:
tsuga clusters list
Pick the cluster you want to use by default via the defaults mechanism:
tsuga config set default cluster --value <CLUSTER_ID>
Override per-command without persisting:
# Environment variable
export TSUGA_CLUSTER_ID=<CLUSTER_ID>
# CLI flag (highest priority)
tsuga --cluster <CLUSTER_ID> logs search
Priority order: --cluster flag → TSUGA_CLUSTER_ID env var → defaults.cluster. If none is set, a single-cluster organization uses its cluster; with more than one cluster, telemetry and resource commands fail and list the clusters to pick from.
Usage
Most resources follow the same CRUD pattern:
tsuga <RESOURCE> list
tsuga <RESOURCE> get <ID>
tsuga <RESOURCE> create -f payload.json
tsuga <RESOURCE> update <ID> -f payload.json
tsuga <RESOURCE> delete <ID>
You can also pass JSON inline with -d. A command takes the body from -f or -d, never both:
tsuga teams create -d '{"name": "platform", "visibility": "public"}'
Pagination
list returns the first 100 items by default. Paginated resources accept --limit (up to 1000) and --offset:
tsuga monitors list --limit 500
tsuga monitors list --limit 500 --offset 500
# Follow every page and print one list
tsuga monitors list --all
When a page leaves items behind, the CLI prints a notice to stderr and leaves stdout as
pure JSON, so | jq and -o csv are unaffected:
Showing 100 of 4212 (offset 0). Use --limit (max 1000) and --offset 100 for the next page.
For resources with server-side filters, narrowing the query beats paging through everything.
Available resources
adaptive-notification-rules, cloud-resources, custom-roles, dashboard-folders, dashboards, ingestion-api-keys, investigations, log-routes, metric-routes, monitor-folders, monitors, notification-integrations, notification-rules, notification-silences, public-tokens, quality-reports, retention-policies, services, slos, tag-policies, team-memberships, teams, users
Notes:
ingestion-api-keysdoes not supportget <ID>.servicesandusersare read-only (list,get).cloud-resourcesis read-only and only supportslist.quality-reportsis read-only and supportslistplusexamples <ID>.team-membershipssupportslist,createanddelete.deletetakes a{userId, teamId}body instead of an ID.investigationsis beta: it requires an operation API key with theinvestigationspermission and the API may change.public-tokensmanages the RUM public tokens configured in browser and mobile SDKs. It requires an operation API key with the RUM public tokens (rum-public-token) permission.
tsuga public-tokens create --generate-skeleton > app.json
# edit app.json
tsuga public-tokens create -f app.json | jq -r .token
tsuga public-tokens list | jq -r '.[0].token'
Generating a request skeleton
Any create or update command accepts --generate-skeleton to print a JSON template of the expected request body:
tsuga monitors create --generate-skeleton
tsuga notification-rules update abc-123 --generate-skeleton
Pipe the output to a file, fill it in, then pass it back with -f:
tsuga monitors create --generate-skeleton > monitor.json
# edit monitor.json
tsuga monitors create -f monitor.json
Telemetry
Time formats
Use an equals sign for negative offsets, such as --from=-1h.
--from and --to accept any of:
| Format | Example |
|---|---|
| Relative | -30m, -1h, -7d, -30s |
| Now | now |
| Unix seconds | 1704067200 |
| ISO 8601 | 2024-01-01T00:00:00Z |
Start with a narrow window and widen it step by step only when the result is empty. Every query runs on your cluster, and wide unfiltered scans slow it down for everyone.
Search logs
tsuga logs search --from=-1h --query 'level:ERROR'
tsuga logs search --from 1704067200 --to 1704153600 --query 'context.service.name:api'
# JSON array projected to selected fields (preserves nested objects)
tsuga logs search --from=-10m \
--fields timestamp,level,message,context.k8s.pod.name
# TSV with default columns: timestamp, level, message
tsuga logs search --from=-10m --query 'level:ERROR' -o tsv
# CSV with custom columns
tsuga logs search --from=-10m -o csv \
--fields timestamp,level,message,context.k8s.pod.name
Log attributes
tsuga logs attributes --from=-1h
# Only dot-paths containing "k8s", case-insensitively
tsuga logs attributes --from=-1h --search k8s
Log patterns
# Group recent logs into patterns
tsuga logs patterns --from=-1h --query 'level:ERROR'
# List new error patterns observed in the time range
tsuga logs new-error-patterns --from=-24h --team my-team --service api --env prod
# List error patterns whose volume increased for a team
tsuga logs error-pattern-increases --from=-24h --team my-team --env prod
Search traces
tsuga traces search --from=-30m --query 'span.name:GET'
# TSV with default columns: start_time, duration, context.service.name, span.name, status_code, trace_id
tsuga traces search --from=-30m --query 'span.name:GET' -o tsv
Search RUM events
Prefix query fields with the stream they belong to (measurements., events., or
exceptions.), and filter on one stream per query. A query without a prefixed field, including
the default *, searches the events stream:
tsuga rum search --from=-1h --query 'events.view.name:/checkout'
tsuga rum search --from=-1h --query 'exceptions.exception.type:*' --max-results 20
RUM attributes
Lists every RUM attribute you can query:
tsuga rum attributes
Both RUM commands require an operation API key with the RUM events (rum) read permission.
Summarize a trace
Aggregates groups of similar spans into synthetic summary spans (marked with
span_attributes.aggregation.is_summary) for a compact view of large traces:
tsuga traces summarize --trace-id 78c99c5bb1e4a30297fa134b9dbec64f --from=-30m
Trace latency summary
Summarizes where wall-clock time is spent in a trace: each slice of the timeline is attributed to the services with active leaf spans, with per-service totals:
tsuga traces latency-summary --trace-id 78c99c5bb1e4a30297fa134b9dbec64f --from=-30m
Contrast sets
Compares a target group of spans against a baseline and returns the attribute values that are over-represented in the target — what those spans have in common that the baseline ones do not. Each group carries its own filter and time range.
Filters are written in TQL, the same query language as --query. Time ranges are Unix seconds.
Erroring spans against healthy ones:
tsuga traces contrast-sets -d '{
"targetGroup": {"filter": "context.service.name:checkout status_code:error", "timeRange": {"from": 1704067200, "to": 1704070800}},
"baselineGroup": {"filter": "context.service.name:checkout NOT status_code:error", "timeRange": {"from": 1704067200, "to": 1704070800}}
}'
Run tsuga traces contrast-sets --generate-skeleton for the full request body.
Metrics
# List metrics, sorted by name, one page at a time
tsuga metrics list --from=-1h --limit 500 --offset 1000
# List metrics whose name contains "http" (case-insensitive)
tsuga metrics list --from=-1h --search http
# Get a specific metric
tsuga metrics get my.metric.name --from=-1h
# List dashboards and monitors that use a metric
tsuga metrics assets-usage my.metric.name
Aggregation
Run aggregation queries with a JSON body:
tsuga aggregation scalar -f query.json
tsuga aggregation timeseries -f query.json
Use --generate-skeleton to get a template:
tsuga aggregation scalar --generate-skeleton > query.json
Service graph
Inspect service dependency graphs:
tsuga service-graph get <SERVICE_ID> --from=-1h --query 'env:prod'
Kubernetes
Explore Kubernetes resources observed across the organization, inferred from your telemetry:
tsuga kubernetes pods --search my-service
tsuga kubernetes clusters
tsuga kubernetes nodes --search ip-10-0
tsuga kubernetes events --search my-pod
tsuga kubernetes pod-details --k8s-cluster prod --k8s-namespace default --pod-name my-pod-abc123
Available resources: pods, clusters, namespaces, nodes, deployments, statefulsets, daemonsets, events (list, with an optional --search), and pod-details, pod-manifest, deployment-details (single-resource lookups, with required identifying flags).
Queues
List message queues inferred from producer and consumer messaging spans, with their publish, consume, and consumer error rates:
tsuga queues list --env prod --stats-window 24h
tsuga queues get --name orders --messaging-system kafka --env prod
Grok
Test grok parsing rules against sample log lines before using them in a log route:
tsuga grok parse --generate-skeleton > grok-test.json
# edit grok-test.json
tsuga grok parse -f grok-test.json
PromQL
Run a PromQL query against the metrics backend. Returns a timeseries shape matching tsuga aggregation timeseries.
tsuga promql query --generate-skeleton > query.json
# edit query.json
tsuga promql query -f query.json
Or inline:
tsuga promql query -d '{"query":"sum by (\"context.service.name\") (sum_over_time({\"traces.span.metrics.calls\", \"span.kind\"=\"SPAN_KIND_SERVER\"}[5m]))","timeRange":{"from":1704067200,"to":1704070800},"step":"30s"}'
timeRange.from / timeRange.to are Unix timestamps in seconds.
Continuous profiling
# Rank functions by self (flat) and inclusive (cum) sample value, like `go tool pprof -top`
tsuga profiles top --service api --type cpu --from=-1h
tsuga profiles top --service api --type alloc_space --from=-1h --limit 10 -o tsv
# Download the merged profile as raw pprof protobuf and open it with pprof
tsuga profiles pprof --service api --type cpu --from=-1h -O cpu.pb
go tool pprof -http=: cpu.pb
# Raw folded stacks (`root;...;leaf` with a value), with the usual -f / -d / --generate-skeleton
tsuga profiles query -d '{"service":"api","type":"cpu","timeRange":{"from":1704067200,"to":1704070800}}'
--type is a pprof sample type such as cpu, samples, wall, off_cpu, alloc_space, inuse_space, alloc_objects, inuse_objects, or goroutine. --filter takes a Tsuga query over the profile labels; only label equality, negation, and wildcards are accepted.
LLM telemetry
Configure local Claude Code (~/.claude/settings.json) or Codex (~/.codex/config.toml) to ship OpenTelemetry data to your Tsuga cluster. Needs an OTLP base URL and an ingestion API key (not the same as your operation API key).
tsuga setup claude-code --endpoint <OTLP_ENDPOINT> --ingestion-api-key <INGESTION_API_KEY>
tsuga setup codex --endpoint <OTLP_ENDPOINT> --ingestion-api-key <INGESTION_API_KEY>
Both options also read from env (TSUGA_OTLP_ENDPOINT, TSUGA_INGESTION_API_KEY). Pass --no-include-prompts to strip user prompts from telemetry (default: include); for claude-code this also strips tool content, but codex only strips user prompts. The previous config is backed up next to the file; restart any running claude / codex sessions afterward.
LLM plugins
tsuga install plugin claude-code
tsuga install plugin codex
Adds the tsuga marketplace (tsuga-dev/agent-plugins) and installs the tsuga plugin via the target CLI (claude / codex must be on PATH). For Claude Code, it also turns on marketplace auto-update; pass --no-auto-update to skip. Restart any running claude / codex sessions afterward.
Shared links
Expand a short link copied from Logs or Traces, or a widget share link, and decode the query, filters, time range, cluster, and Analytics settings it carries. A full app.tsuga.com URL is decoded locally without an API call:
tsuga unshorten https://app.tsuga.com/l/<CODE>
tsuga unshorten https://api.tsuga.com/v0/widget-shares/<ORG_ID>/<CODE>
tsuga unshorten 'https://app.tsuga.com/logs?query=level%3AERROR'
Documentation
Search Tsuga documentation and the full API reference, then fetch a page by path:
tsuga docs search 'log routes'
tsuga docs get explore/query-syntax
tsuga docs get api/aggregateScalar
Feedback
Report friction with Tsuga tools or APIs (a failing command, unusable output, confusing behavior):
tsuga feedback the traces command keeps timing out on large services
Tips
Default output is JSON, so you can pipe to jq:
tsuga dashboards list | jq '.[].name'
tsuga monitors list | jq '.[] | select(.priority == 1)'
Read from stdin with -f -:
echo '{"name": "my-team", "visibility": "public"}' | tsuga teams create -f -
Command schema
tsuga schema prints the command tree as JSON: command paths, descriptions, positional arguments, and flags.