Output Formats
dtctl supports multiple output formats to suit different workflows – from human-readable tables for interactive use to structured JSON for scripting and AI agents.
Table (Default)
The default output format is a compact, human-readable table:
dtctl get workflows
ID NAME STATE TRIGGER LAST RUN
wf-abc123 Daily Health Check enabled Schedule 2025-01-15 08:00
wf-def456 Incident Remediation enabled Event 2025-01-15 12:34
wf-ghi789 Weekly Report enabled Schedule 2025-01-13 06:00
JSON
Output as JSON for scripting and piping to tools like jq:
dtctl get workflow wf-123 -o json
# Pipe to jq for field extraction
dtctl get workflows -o json | jq '.[].name'
YAML
Output as YAML, useful for round-tripping with dtctl apply:
dtctl get workflow wf-123 -o yaml
Wide
The wide format adds additional columns that are hidden in the default table view:
dtctl get workflows -o wide
ID NAME STATE TRIGGER OWNER LAST RUN LAST STATUS
wf-abc123 Daily Health Check enabled Schedule user@example.com 2025-01-15 08:00 SUCCESS
wf-def456 Incident Remediation enabled Event user@example.com 2025-01-15 12:34 FAILED
Describe
The describe command renders a vertical key-value view with full detail by default:
dtctl describe workflow wf-123
ID: wf-abc123
Name: Daily Health Check
State: enabled
Trigger: Schedule (0 8 * * *)
Owner: user@example.com
Created: 2025-01-01 10:00:00
Modified: 2025-01-14 15:30:00
Tasks: 3
All describe subcommands support the -o / --output flag to get structured output:
# JSON output for scripting
dtctl describe workflow wf-123 -o json
# YAML output for round-tripping
dtctl describe slo my-slo -o yaml
# Agent mode envelope
dtctl describe dashboard my-dash -A
CSV
Export as CSV for spreadsheets and data pipelines:
# Export workflows to a CSV file
dtctl get workflows -o csv > workflows.csv
# Export DQL query results as CSV
dtctl query 'fetch logs | filter status == "ERROR" | limit 100' -o csv > errors.csv
JSON Lines and Parquet (large query exports)
For dtctl query, two additional formats are tailored to large result exports:
# JSON Lines: one compact JSON object per line (newline-delimited JSON).
# Serialised one record at a time and read natively by most local data tooling.
dtctl query 'fetch logs | limit 1000' -o jsonl > logs.jsonl
# Parquet: a columnar binary file, ideal for downstream analytics tooling.
# Pair with a raised --max-result-records when exporting large populations.
dtctl query 'fetch logs' --max-result-records 100000 -o parquet > logs.parquet
Notes:
-o jsonlhas no schema and appends one object per line, so it tolerates rows with differing fields. Each record is encoded as it is written rather than building the whole result into a single buffer.-o parquetderives its column schema from the DQL column types (it requests type information automatically). Nested or variant columns that do not map cleanly to a columnar type are stored as a JSON-encoded string column rather than being dropped. An empty result still produces a valid Parquet file (never a zero-byte file): it carries the DQL schema when types are known, otherwise a single placeholder column so the file stays readable by mainstream tooling (a column-less file is rejected by DuckDB, pyarrow, and pandas).- Parquet files also carry the DQL types in the file footer, under the
key-value metadata key
dtctl.dql.types(a JSON object mapping column name to DQL type, e.g.{"status.code":"long","content":"string"}). This lets a reader recover type information the physical schema alone loses — a Graillongis stored asINT64, but Grail’s own JSON serialiser emits it as a quoted string, so a consumer reproducing Grail’s wire form needs the declared type to know which columns to stringify. The footer records every declared column, including ones that were null in every row (Grail omits null fields from records, so such a column has no physical column in the file). Read it with DuckDB’sparquet_kv_metadata()or any Parquet footer reader.
Column types (--include-types)
Pass --include-types to surface the DQL per-column type information the query
API returns. In json and yaml output it appears as a top-level types key
alongside records, preserving the API’s shape (indexRange + mappings):
dtctl query 'fetch logs | limit 1' -o json --include-types
# {
# "records": [ { "content": "...", "loglevel": "INFO", "status.code": "200" } ],
# "types": [
# {
# "indexRange": [0, 0],
# "mappings": {
# "content": { "type": "string" },
# "loglevel": { "type": "string" },
# "status.code": { "type": "long" }
# }
# }
# ]
# }
Notes:
- Only with an explicit flag. The block is emitted only when you pass
--include-typesyourself.--typedand Parquet output request the same metadata internally to do their work, but that does not add thetypeskey. json/yamlonly.jsonl(one record per line) andcsv(tabular) have no place for a document-level sibling, so the block is not emitted there.- Note the distinction from
--typedbelow:--include-typesreports the declared type while leaving values in their wire form (so alongstill reads as"200"), whereas--typeduses the same metadata to rewrite the values.
Numeric typing (--typed)
The Grail query API deliberately serialises integer-valued columns (long,
duration) as JSON strings to preserve full int64 precision for
JavaScript/TypeScript consumers. dtctl’s json, yaml, and jsonl output
faithfully passes that through, so a count() reads as "42" (a string):
dtctl query 'fetch logs | summarize c = count()' -o json
# [ { "c": "42" } ]
Pass --typed to cast scalar columns to their native types using the DQL type
metadata — long/duration become JSON numbers, boolean becomes a real
boolean — so the output is ready for jq, pandas, or DuckDB without a
tonumber step:
dtctl query 'fetch logs | summarize c = count()' -o json --typed
# [ { "c": 42 } ]
Notes:
- Opt-in by design. The default output stays faithful to the API’s wire
encoding.
--typedimplies--include-typesso the type metadata is available. - Precision-safe in dtctl. A
longis emitted as its full decimal digits, unquoted and lossless (never routed through a float). The only precision risk is in a downstream consumer that parses JSON numbers as 64-bit floats (browserJSON.parse, olderjq) — which is exactly why it is opt-in. - Timestamps stay strings. JSON/YAML have no native date type, so
timestampcolumns keep their portable RFC3339 string form.string,ip,timeframe, and nested record/array columns are left unchanged. - Values that do not cleanly parse to their declared type (including non-finite
doubles such as
"NaN"/"Infinity", which JSON cannot represent) are left as strings rather than failing the output.
Plain Mode
The --plain flag disables colors, progress indicators, and interactive prompts. This is useful for piping output or running in non-interactive environments:
dtctl get workflows --plain
Color output follows the no-color.org standard:
--plainflag disables colorNO_COLORenvironment variable disables color- Non-TTY output (piped) disables color automatically
FORCE_COLOR=1overrides TTY detection to force color on
Command Catalog
dtctl can describe its own commands in machine-readable form:
# Minimal overview: verbs, resources, subcommands (defaults to TOON, ideal for AI agent bootstrap)
dtctl commands
# Brief catalog: adds mutating status, access levels, flag types, and scopes
dtctl commands --brief -o json
# Full command catalog: descriptions, flag defaults, and global flags
dtctl commands --full -o json
# Human-readable how-to guide in Markdown
dtctl commands howto
Unlike other commands, dtctl commands defaults to TOON (the most compact format) rather than the table format; pass -o json or -o yaml to override.
Agent Mode
The --agent (or -A) flag wraps all output in a structured JSON envelope designed for AI agent consumption:
dtctl get workflows --agent
{
"ok": true,
"result": [...],
"context": {
"verb": "get",
"resource": "workflow",
"suggestions": [...]
}
}
Agent mode is auto-detected when running inside AI agent environments (GitHub Copilot, Claude Code, Cursor, OpenCode, and others). To opt out of auto-detection:
dtctl get workflows --no-agent
Agent mode implies --plain – no colors and no interactive prompts. See AI Agent Mode for full details.
Pagination
List commands support server-side pagination with the --chunk-size flag:
# Fetch in chunks of 200
dtctl get workflows --chunk-size 200
# Default chunk size is 500
dtctl get workflows
# Disable chunking (fetch all at once)
dtctl get workflows --chunk-size=0
All pages are fetched automatically and combined into a single result set.