@tsuga/cli
v1.40.0
Published
Tsuga CLI - manage resources from the command line
Maintainers
Readme
@tsuga/cli
Command-line interface for managing Tsuga resources.
Installation
npm install -g @tsuga/clitsuga --helpUpdating
Update to the latest published version:
tsuga self-updateThis 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.fishFish loads it automatically in new shells.
Authentication
All authentication lives under tsuga auth.
tsuga auth status reads the saved credential locally. To ask Tsuga which credential the CLI actually authenticates as, and what it is allowed to do:
tsuga auth whoami
tsuga auth whoami -o jsonLog 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 current authentication method, and when the session expires or expired
tsuga auth status
# Ask Tsuga who this CLI authenticates as
tsuga auth whoami
# Clear the saved user session
tsuga auth logoutAfter tsuga auth login, commands work without an Operation API key.
Sessions renew themselves: the login also saves a refresh token, and the CLI exchanges it for a new access token when the current one is within five minutes of expiry. You log in again when the refresh token expires, when it is revoked, or when the session has no refresh token.
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 configDefaults
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'Current defaults are listed by tsuga config (custom overrides marked with *).
Reset all custom defaults:
tsuga config reset defaultsClear a single default by setting it to an empty value:
tsuga config set default cluster --value ''Clusters
If your tenant has multiple clusters, list them:
tsuga clusters listPick 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 searchPriority order: --cluster flag → TSUGA_CLUSTER_ID env var → defaults.cluster. If unset, the backend falls back to the first cluster.
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>Read-only resources (for example services) support only:
tsuga services list
tsuga services get <ID>You can also pass JSON inline with -d:
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 500When 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.
Explaining a call with --rationale
Every command accepts an optional --rationale flag to record why it is being run. It does not
change the result; the text is sent as a request header on any API call the command makes, and
recorded in the backend request log, which is useful for understanding agent behavior.
tsuga logs patterns --rationale "exploring telemetry to investigate prod outage"--rationale, --cluster and --operation-api-key are shared flags: they may be given before
or after the subcommand, and are inherited by every subcommand.
Available resources
cloud-resources, dashboard-folders, dashboards, ingestion-api-keys, investigations, monitors, notification-integrations, notification-rules, notification-silences, public-tokens, quality-reports, retention-policies, log-routes, services, slos, tag-policies, teams
Notes:
ingestion-api-keysdoes not supportget <ID>.servicesis read-only (list,get).cloud-resourcesandquality-reportsare read-only and only supportlist.- For
quality-reports list, pass--cluster <CLUSTER_ID>(or setTSUGA_CLUSTER_ID/ saved default) when the org has more than one cluster.- Pass
--team <TEAM_NAME>toquality-reports listto scope the response to a single team by name (omits cluster-wide global rows).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-skeletonPipe 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.jsonExamples
# List all monitors
tsuga monitors list
# Get a specific dashboard
tsuga dashboards get abc-123
# List dashboards filtered by owner
tsuga dashboards list -d '{"filters":{"owners":{"values":["team-1","team-2"]}}}'
# Get a specific service
tsuga services get abc-123
# Create a notification rule from a file
tsuga notification-rules create -f rule.json
# Update a log route
tsuga log-routes update abc-123 -d '{"name": "Updated log route"}'
# Delete a retention policy
tsuga retention-policies delete abc-123Telemetry
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 |
Search logs
tsuga logs search --from=-1h --query 'level:ERROR'
tsuga logs search --from 1704067200 --to 1704153600 --query 'service:api'By default, the full JSON response is printed. Use --fields to project specific
dot-paths from each log, and -o tsv|csv for tabular output:
# JSON projected to selected fields (preserves the {logs: [...]} envelope and nesting)
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.nameLog attributes
tsuga logs attributes --from=-1hLog 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 prodSearch traces
tsuga traces search --from=-30m --query 'span_name:GET'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 20RUM attributes
Lists every RUM attribute you can query:
tsuga rum attributesBoth 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
spanAttributes.aggregation.is_summary) for a compact view of large traces:
tsuga traces summarize --trace-id 78c99c5bb1e4a30297fa134b9dbec64f --from=-30mTrace 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
# Per-service totals only, without the range timeline
tsuga traces latency-summary --trace-id 78c99c5bb1e4a30297fa134b9dbec64f --from=-30m --no-ranges
# Collapse timeline ranges shorter than 50ms (0 disables collapsing)
tsuga traces latency-summary --trace-id 78c99c5bb1e4a30297fa134b9dbec64f --from=-30m --min-range-duration-ms 50Contrast 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 elsewhere: terms
are ANDed by whitespace, span_attributes.* addresses span attributes, and
duration is in milliseconds. Run tsuga docs get explore/query-syntax for the
full syntax.
tsuga traces contrast-sets --generate-skeleton > groups.json
tsuga traces contrast-sets -f groups.jsonTime ranges are Unix seconds, and unlike --from / --to they are not resolved
from relative strings — the body takes numbers. The skeleton and the examples
below carry a fixed window that is long past, so substitute your own:
TO=$(date +%s)
FROM=$((TO - 3600))Erroring spans against healthy ones, for one service in one window:
cat > groups.json <<EOF
{
"targetGroup": {"filter": "context.service.name:checkout status_code:error", "timeRange": {"from": $FROM, "to": $TO}},
"baselineGroup": {"filter": "context.service.name:checkout NOT status_code:error", "timeRange": {"from": $FROM, "to": $TO}}
}
EOF
tsuga traces contrast-sets -f groups.jsonSlow spans against normal-latency ones:
tsuga traces contrast-sets -d '{
"targetGroup": {"filter": "context.service.name:checkout duration:>2000", "timeRange": {"from": 1704067200, "to": 1704070800}},
"baselineGroup": {"filter": "context.service.name:checkout duration:<500", "timeRange": {"from": 1704067200, "to": 1704070800}}
}'Before and after a deployment — same filter, two windows:
tsuga traces contrast-sets -d '{
"targetGroup": {"filter": "context.service.name:checkout", "timeRange": {"from": 1704070800, "to": 1704074400}},
"baselineGroup": {"filter": "context.service.name:checkout", "timeRange": {"from": 1704063600, "to": 1704067200}}
}'Each returned value reports targetSupport and baselineSupport (the percentage
of each group carrying it), lift (targetSupport / baselineSupport, omitted
when the value never appears in the baseline), and pValue. Narrow the findings
with topK, minLift, and minSupport; restrict the attributes tested with
candidateAttrs.
Metrics
# List all metrics
tsuga metrics list --from=-1h
# 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.nameAggregation
Run aggregation queries with a JSON body:
tsuga aggregation scalar -f query.json
tsuga aggregation timeseries -f query.jsonUse --generate-skeleton to get a template:
tsuga aggregation scalar --generate-skeleton > query.jsonService 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-abc123Available 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).
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.jsonPromQL
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.jsonOr 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.
Experimental
Commands under tsuga experimental may change or be removed without notice.
Continuous profiling
# Rank functions by self (flat) and inclusive (cum) sample value, like `go tool pprof -top`
tsuga experimental profiles top --service api --type cpu --from=-1h
tsuga experimental 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 experimental 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 experimental 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 saved alongside the file as .bak.<ISO>; restart any running claude / codex sessions afterward.
LLM plugins
tsuga install plugin claude-code
tsuga install plugin codexAdds 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, marketplace auto-update is enabled by patching ~/.claude/settings.json with extraKnownMarketplaces.tsuga.autoUpdate = true; pass --no-auto-update to skip. Restart any running claude / codex sessions afterward.
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/aggregateScalarFeedback
Report friction with Tsuga tools or APIs (a failing command, unusable output, confusing behavior):
tsuga feedback the traces command keeps timing out on large servicesTips
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"}' | tsuga teams create -f -Implementation
The CLI uses Effect v4 services for HTTP, configuration, filesystem access, subprocesses, and
command parsing. Command folders mirror tadmin: each group has a composition index.ts and one
file per action, with nearby tests. Cross-directory application imports use @cli/*. packages/common-cli shares OAuth token exchange, browser callbacks, macOS
keychain access, output, and closed-pipe handling with tadmin. The CLIs retain separate credential
namespaces and authentication policies. Generic field projection lives in common.
Run yarn lint, yarn typecheck, yarn test, and yarn build from packages/cli to verify
changes. Lint includes Effect language-service diagnostics.
