CLI
Background
dci is the command-line interface for Cloud Intelligence™: work with your reports, budgets, anomalies, allocations, and the rest of the platform directly from your terminal — ask about them in plain English, and hand the same tool to your AI agents. It is a standalone binary that generates its commands from the Cloud Intelligence™ API, handles authentication, and renders results for humans (pivoted, readable tables) or machines (JSON, CSV, and the token-efficient TOON format).
This documentation covers:
- Installing the CLI on macOS, Windows, and Linux
- Shell completion setup
- Authentication via OAuth or API token
- Command discovery and usage patterns
- The interactive AI session and the terminal features around it
- Output formats and scripting
- A full, generated command reference (available via the sidebar)
Download and install
Install the dci binary using one of the methods below.
macOS (Homebrew)
brew install doitintl/dci-cli/dci
Windows (WinGet)
winget install DoiT.dci
Windows (Scoop)
scoop bucket add doitintl https://github.com/doitintl/dci-cli
scoop install dci
Linux
Install with one command on any Linux distribution with glibc 2.34 or newer, including Ubuntu 22.04+, Debian 12+, RHEL 9+, Fedora, Google Cloud Shell, and WSL:
curl -fsSL https://raw.githubusercontent.com/doitintl/dci-cli/main/install.sh | sh
The script downloads the latest release for your machine (x86_64 or arm64), verifies it against the release checksums, and installs dci to ~/.local/bin with shell completion for bash, zsh, and fish. It needs no sudo. If ~/.local/bin isn't on your PATH yet, the script prints the line to add.
Cloud Shell keeps only your home directory when the session restarts, so use this install command there: a package installed with dpkg disappears with the VM, while ~/.local/bin persists.
To change what gets installed, pass options after sh -s --:
| Option | What it does |
|---|---|
--version <v> | Install a specific release instead of the latest, e.g. 2.8.0 |
--dir <path> | Install to another directory instead of ~/.local/bin |
--system | Install the .deb or .rpm package system-wide (uses sudo) |
curl -fsSL https://raw.githubusercontent.com/doitintl/dci-cli/main/install.sh | sh -s -- --version 2.8.0
Alpine and other musl-based distributions aren't supported, and neither are older releases such as Ubuntu 20.04, Debian 11, or RHEL 8; the script stops with an explanation and leaves any existing dci in place. Homebrew on Linux also works: brew install doitintl/dci-cli/dci.
Linux packages (Debian/Ubuntu)
To install the .deb package yourself, download it from the latest release (_amd64 or _arm64), then install:
sudo dpkg -i dci_*_linux_amd64.deb
Linux packages (RHEL/Fedora)
To install the .rpm package yourself, download it from the latest release (_amd64 or _arm64), then install:
sudo rpm -i dci_*_linux_amd64.rpm
Direct download
Prebuilt binaries for all platforms are available on the GitHub Releases page.
Validate the installation
dci --version
Keep it up to date
Since v2.5.2, one command updates any install:
dci update
dci update detects how the CLI was installed and does the right thing: Homebrew, Scoop, and WinGet installs run the package manager's own upgrade command after a confirmation; Linux .deb/.rpm installs download the new package, verify it against the release checksums, and install it (sudo prompts interactively — declining prints the exact command to run yourself); standalone binaries, including installs from the Linux install command, replace themselves atomically, also checksum-verified. dci upgrade is an alias.
| Flag | Description |
|---|---|
--check | Only check for a new release and print how to update; installs nothing |
--yes | Skip the confirmation prompt — required before anything installs in scripts and agent mode |
--version vX.Y.Z | Update (or roll back) to a specific release tag (standalone installs only) |
In scripts and agent mode the result is machine-readable JSON (current, latest, updateAvailable, channel, instruction, and updated after an install), and nothing is ever installed without --yes.
The CLI also checks for new releases in the background at most once every few hours and prints a short notice when one is available; set DCI_NO_UPDATE_CHECK=1 to turn that off.
Shell completion
Since v2.1, Homebrew and the Linux .deb/.rpm packages install shell completion automatically, and so does the Linux install command (for zsh, it prints the one fpath line to add to ~/.zshrc). Scoop prints the one PowerShell profile line to add after install. For other setups, generate the script for your shell below.
Bash
# System-wide (requires sudo):
dci completion bash > /etc/bash_completion.d/dci
# Per-user:
mkdir -p ~/.bash_completions
dci completion bash > ~/.bash_completions/dci
echo 'source ~/.bash_completions/dci' >> ~/.bashrc
Zsh
dci completion zsh > "${fpath[1]}/_dci"
Then restart your shell or run exec zsh to pick up the new completions.
Fish
dci completion fish > ~/.config/fish/completions/dci.fish
PowerShell
dci completion powershell > dci.ps1
# Then add `. /path/to/dci.ps1` to your PowerShell profile.
Completion covers all API commands (list-budgets, list-reports, query, etc.) with descriptions, flag completion for each command, and — since v2.1 — your resource names: dci get-report Mon<TAB> offers matching report names, served from a local cache that refreshes in the background. Type names bare when completing (the shell escapes spaces for you); completing inside an open quote is not supported by shell completion frameworks. API command completion requires an active auth session (dci login or DCI_API_KEY). A Tab press never makes a network call or starts a login: on a cold cache the CLI prints a short notice instead of names and fills the cache in the background (suppress notices with DCI_ACTIVE_HELP=0).
Authentication
The CLI supports two authentication methods.
Interactive (OAuth)
Run dci login to authenticate via the console in your browser:
dci login
This opens a browser window, prompts you to log in, completes an OAuth flow, and caches credentials locally. In an interactive terminal, the first command you run triggers this flow automatically; non-interactive sessions (CI, pipes, agents) never wait for a browser — without credentials they fail fast with AUTHENTICATION_REQUIRED and a pointer to DCI_API_KEY. Since v2.7.0, a CI system that allocates a terminal is recognized through the standard CI environment variable and treated as non-interactive too.
Since v2.5.2, the browser confirmation page suggests a next command after a successful sign-in — clicking the suggestion runs it directly in the terminal that started the login (dci login waits up to a minute for the click; Ctrl-C skips the wait). If the sign-in fails, the page shows the error with links back to this guide.
To clear cached credentials:
dci logout
You can also sign in and out from inside the AI session with /login and /logout.
API token (CI/automation)
For non-interactive environments such as CI/CD pipelines, cron jobs, and shell scripts, set the DCI_API_KEY environment variable to a DoiT API token:
export DCI_API_KEY=<YOUR_API_TOKEN>
When DCI_API_KEY is set, the CLI uses it instead of OAuth.
Choose a token type
DoiT supports two API token types for programmatic access. For CLI automation, choose based on whether the job runs as you or as shared organizational automation:
| Token type | Best for | Where to create it |
|---|---|---|
| Personal API token | Scripts or jobs tied to your user |
|
| Service account API token | Shared automation (CI/CD, scheduled jobs) that should not depend on one person |
|
Use a personal API token for local scripts or one-off automation you run yourself. Use a service account API token for pipelines and scheduled jobs that must keep working if you change roles or leave the organization.
When you create a personal API token, choose a permission scope that matches what the CLI commands need:
| Scope | Use when |
|---|---|
| Read only | The job only reads data (for example, dci list-reports) |
| Full access | The job needs every permission your user role grants |
| Custom scope | The job needs a specific subset of permissions |
Service account tokens inherit the permissions assigned to the service account. Grant only the permissions each workload needs.
New tokens may take up to one minute to work after creation.
Treat API tokens like passwords. Store DCI_API_KEY in your secrets manager or CI/CD secret store. Do not commit tokens to source control.
For more on creating tokens and permission behavior, see DoiT Developer Hub: Get started, Personal API tokens, and Service accounts.
Verify authentication
Check your configuration, session state, and active customer context:
dci status
To confirm your identity and permissions against the API, run:
dci validate
Getting started
After installing and authenticating, try a few commands:
dci list-budgets
dci list-reports
dci get-report <report-id>
Use --help on any command for inline usage details; help is terse by default, and --help-full adds the complete request/response schemas:
dci --help
dci list-budgets --help
dci query --help-full
Since v2.7.3, command help describes each positional argument and shows curated usage examples that are checked against the live API before every release, so a command line printed in help is one the API accepts. The anomaly commands and the local commands (dci status, dci query, dci open, dci update, dci skill, and others) were covered first; the rest keep their previous generated help until their examples land. Command categories carry a one-line description, and a flag with a documented example shows it.
If you mistype a command name, the CLI exits with a non-zero code and suggests the closest match — and, at a terminal, the plain-English alternative dci ai "…" alongside it. To jump from the terminal into the console, dci open (or dci open report <id>, budget, allocation) opens the deep link in your browser — or prints it in agent and non-interactive modes.
Two commands answer a common question directly, instead of making you rebuild the filter and sort each time (v2.7.2):
dci budgets-at-risk # budgets currently projected to breach
dci anomalies-recent # anomalies that appeared recently
dci anomalies-recent --window 7d # widen the lookback from the 24-hour default
dci budgets-at-risk is the curated equivalent of dci list-budgets --filter riskStatus:atRisk, in projected-breach order, and dci list-budgets itself color-grades its utilization bars — green through amber past 70% into red past 90% — so an at-risk budget stands out in the table. Since v2.7.6, --window accepts hours, days, and weeks (168h, 7d, 1.5d, 2w); a mixed form such as 1d12h, a zero, a negative, or an oversized window is rejected with a message listing the accepted forms.
For a one-page list of the commands worth knowing, see the CLI cheat sheet.
AI session
Since v2.6.0, dci ai opens an interactive AI session in your terminal: ask about your cloud costs in plain English, and the AI runs dci commands for you and explains the results. You can also run any command yourself inside the session by prefixing it with /, with completion and history. One-shot mode answers a single question and exits:
dci ai # interactive session
dci ai "top 3 cost anomalies this month" # one-shot: answer, then exit
dci ai --quiet "what did we spend on S3 last month" # the answer only
dci ai --verbose "..." > investigation.md # keep the narration when piped
--quiet prints just the answer; --verbose keeps the full narration even when output is piped to a file. The shorthand for --quiet is not -q — that shorthand belongs to another flag and is not used here.
Since v2.7.0, running dci with no arguments at a terminal opens the same session. In pipes, scripts, and CI, bare dci prints the help screen exactly as before, so nothing changes for automation. If you prefer the help screen at your own terminal, run /default help once inside the session; dci --help always prints help.
The AI session is not agent mode, despite the similar names. The session is a person-facing program that drives dci on your behalf in your terminal. Agent mode is the reverse: it strips the CLI of everything interactive so an external AI harness can drive dci and parse its output. A harness never opens the session, and the session is never in agent mode.
Slash commands and keys
Inside the session, / runs a dci command (/list-budgets, /beta run-report) and the following built-ins control the session itself:
| Command | Description |
|---|---|
/help | The slash-command grammar and the commands available. Your first-ever session opens with the full text; later sessions show the compact banner |
/key | Show which Anthropic API key is in use (masked) and where it comes from, including when ANTHROPIC_API_KEY overrides a saved key |
/key set, /key clear | Replace the saved key through the guided entry, or remove it |
/customer | Show or switch the customer context. A switch the AI makes itself applies to the session only — your saved context is never changed |
/model | Pick the AI model |
/export | Save the transcript to a file |
/login, /logout | Sign in or out without leaving the session; commands, role, and tenant refresh live |
/bell | Turn off the terminal bell that rings when an answer that ran commands finishes |
/mouse | Toggle mouse reporting; the choice is remembered across sessions |
/beta | List the early-access commands the way you would type them in the session |
/default help | Make bare dci print the help screen at your terminal again instead of opening the session |
/exit, /quit | Leave the session |
Esc cancels a running command or a streaming answer. Ctrl+L repaints the frame and jumps back to the latest content, which is the key to press when terminal scrolling has disturbed the view. In the command popup, Enter or Tab selects the highlighted command and ↓ scrolls through every match. You can also define your own shortcuts as saved commands.
When an answer runs long, the session rings the terminal bell and — in terminals that support notifications and focus reporting — sends a desktop notification, so you can switch away during an investigation.
Reading the answer
Since v2.7.6, a one-shot dci ai "question" renders its answer as formatted text at a terminal: tables with borders, bold, and inline code, with narration emitted before a lookup shown as its own paragraph while the lookup runs. Piped output, agent mode, NO_COLOR, and an explicit --output format still get the raw stream, so nothing that parses the output changes. With --quiet, a brief thinking… status on stderr shows the session is working until the answer prints.
Answers label their numbers: cost tables state the currency and usage tables name the metric, with one consistent scale per column. The session batches independent commands and runs them concurrently, and presents a long result as a top-10 table plus an aggregated total — ask for the full breakdown when you want every row.
Credentials and privacy
AI features run on your own Anthropic API key. The session walks you through saving one securely the first time, and ANTHROPIC_API_KEY in the environment overrides a saved key. Your questions and the command results the AI reads are sent to Anthropic under that key, and conversations are not stored anywhere but your terminal.
DoiT employees do not need a key: once signed in with dci login, the session runs on DoiT-provided access — short-lived tokens fetched with your existing DoiT sign-in, refreshed behind the scenes, never written to disk (v2.7.2). An explicit key still takes precedence, and DCI_AI_PROVIDED=off opts out of the provided access.
Tuning answers
| Variable | Description |
|---|---|
DCI_AI_EFFORT | Trade answer speed against depth: low, medium, high, or default to restore uncapped reasoning. The default is medium |
DCI_AI_STATS | 1 prints one telemetry line per answer — rounds, tool calls, tokens, timing — for scripting and benchmarking |
Authoring flows from the session
The bundled agent skill teaches CloudFlow authoring and, since v2.7.2, flow-behavior verification: build or refine a flow from a natural-language request, or author a bundle by hand through the export, edit, dry-run, import loop, then deep-validate the draft with dci test-run-cloudflow-flow --dry-run, run it once as a test, and read what every node consumed and produced with dci get-cloudflow-flow-run. Run dci skill update to refresh installed copies — see Agent mode.
Command structure
Commands are generated directly from the Cloud Intelligence™ API. The general structure is:
dci <command> [arguments] [flags]
Examples:
dci list-alerts
dci get-alert <id>
dci list-budgets
dci list-reports
For commands that accept a request body, pipe a JSON file via stdin or pass fields as inline shorthand arguments:
dci create-alert < alert.json
Inline shorthand treats commas structurally, so free text containing a comma has to go through stdin as JSON.
A body field that takes a file — dci ingest-datahub-events-csv's file — is given its path prefixed with @ in the same shorthand. The file is attached by name, and a missing file, an oversized file, or a forgotten @ is reported with a fix before anything is sent:
dci ingest-datahub-events-csv provider: litellm-usage, file: @events.csv
The CLI validates request bodies against the API schema: unknown top-level fields are rejected with a usage error listing the valid fields, instead of being silently ignored by the API.
Resource names
Since v2.1, names work wherever IDs do: dci get-report "Monthly AWS Spend" resolves the name to its ID before the request goes out, and multi-word names work unquoted. Names match exactly, by unique substring, or by close typo. Tab completion offers your resource names from a local cache that refreshes in the background.
dci get-report "Monthly AWS Spend" # name instead of ID
dci get-budget Team Backyard # quoting optional
dci get-report --id <value> # force a literal ID, skip the lookup
dci get-report --name <value> # force a name lookup for an ID-shaped name
DCI_NO_RESOLVE=1 dci get-report <id> # scripts: treat arguments as literal IDs
When a name matches more than one resource, an interactive terminal offers the matches as a menu; scripts get every match with its ID and a clean error, so nothing ever runs on a guess.
Since v2.7.5, resolution and Tab completion also reach commands that act on a sub-resource — dci get-report-config, dci export-cloudflow-flow, and their siblings take a name where they previously took only an ID — and DataHub dataset names, which have no separate ID and so resolved nowhere before. Commands whose identifier is a number, such as dci get-ticket, are excluded: they no longer offer names they cannot accept.
Interactive terminal features
In a terminal, the CLI wraps the same commands in prompts, pickers, and viewers (v2.5.1 and later). None of this appears in pipes, scripts, CI, or agent mode, which get exactly the output they got before — that is what makes one command line safe in both places. Since v2.7.0, a CI system that allocates a terminal is detected through the standard CI environment variable and gets the script behavior, so a pipeline can never hang waiting on a prompt. DCI_NO_TUI=1 switches all of it off in a terminal too, keeping tables and color.
What the terminal adds:
-
Resource picker: leave the identifier off a command that needs one —
dci get-report,dci open report,dci export-datahub-dataset-records— and a filter-as-you-type list opens instead of a usage error. -
Selection menu: a name that matches more than one resource is offered as a list to choose from, described under Resource names.
-
Destructive confirmation: deletes and similar commands show the target's name, owner, and description before anything happens. Cancel is the default, and
Esccancels too. -
Full-screen table viewer:
-M interactivebrowses a result with←→for columns,sto sort,/to filter, andEnterto print the highlighted row's id. -
Query builder:
dci querywith no input composes a report query step by step, then runs it or saves it as aquery.json. -
Spinner: shown while an API request is in flight.
-
Hidden-columns hint: one compact line listing the columns that did not fit the current table.
-
Scroll hint: when output is taller than the terminal, a dim note next to the prompt says how much is above it ("↑ N more lines above — scroll up", v2.7.2).
The AI session is the largest of these terminal-only features and has its own section.
Running queries
dci query runs a Cloud Analytics report query without persisting it. The request body is a JSON object with a config key describing metrics, grouping, time ranges, and filters (there is no SQL input mode):
dci query < query.json
Always include a group limit in query configs — an unlimited grouped query over a long time range can return thousands of rows. Run dci query --help-full to see the full request schema and a complete example payload, or run dci query with no input to build the config interactively.
Report results
Report and query results (result.rows) are positional arrays zipped with result.schema. The following flags shape them:
In the interactive table view, report results render as a pivot by default — groups as rows, time periods as columns, with totals, a first→last trend column, and heatmap shading by magnitude — matching how the console presents a report. Since v2.5.1 this holds for every range: the 14-period cutoff that used to switch wide results to flat rows is gone, and --flat is how you opt out. Machine formats (json, yaml, csv, toon) and agent mode keep the flat row layout; since v2.7.6, an explicit -C column selection keeps the pivot and simply narrows it to the columns you named. Pivot rows whose values are all zero are hidden, and flat rows omit datetime dimension columns that only repeat the timestamp column. Timestamps follow the report's time resolution (hourly results keep the hour on every row; daily and coarser show bare dates) and period columns are always UTC — they label billing buckets, not moments (see Timestamps and timezones).
Monetary amounts are currency-aware when the query config specifies a currency or a response row carries one (such as a budget). Human tables render those amounts with the currency sign rounded to whole units ($239,927, €205,956), and machine-formatted report results include the explicit currency. The CLI does not assume a currency when the API response and request omit it. --raw-numbers restores exact unformatted values. The following flags shape results further:
| Flag | Description |
|---|---|
--flat | Render report results as flat rows instead of the default pivot view |
--pivot | Force the pivot view in any mode or output format |
--heatmap | Shade pivot cells by magnitude in interactive terminals (default on; respects NO_COLOR; --heatmap=false disables) |
--rows keyed | Return rows as objects keyed by schema column names instead of positional arrays |
--rollup <columns> | Total rows in the CLI: rows group by the listed result columns, metric columns are summed, and per-period rows collapse into one total per group (v2.6.0) |
--max-rows <n> | Cap the number of result rows (0 = unlimited). Agent mode defaults to 500; when rows are omitted the result carries rowsOmitted and rowsTotal markers |
--include-empty-rows | Keep rows with a null group and zero metrics (dropped by default; emptyRowsDropped marks how many were removed) |
--drop-unlabeled-rows | Exclude report rows whose group labels are all null; partially labeled rows are always kept (v2.5.0) |
--raw-numbers | Print numbers unformatted in table output |
--chart[=<style>] | Render an ASCII chart of the report next to the prompt, under the table. Human table output only — ignored in machine formats and agent mode, and unaffected by -C |
--chart takes one of five styles, all of them following the report's color theme:
| Style | What it draws |
|---|---|
| stacked columns | The default: one stacked column per period, largest segments on top (v2.5.2) |
line | A line of period totals (v2.5.2) |
sparkline | Period totals on a single line — the quickest look at the shape (v2.7.0) |
heatmap | One row per group and one cell per period, colored by each value's share of the maximum (v2.7.0) |
treemap | Each group's share of the total as proportional rectangles, with exact shares in the legend. It needs no time axis, so it also charts single-period reports the other styles cannot (v2.7.2) |
get-report also accepts --time-range (e.g. P30D), or --start-date and --end-date (yyyy-mm-dd, must be provided together) to override the report's time settings.
Output formats
Commands support multiple output formats via the --output flag:
| Format | Description |
|---|---|
table | Human-readable tabular display (default in interactive terminals) |
json | Machine-readable JSON — recommended for scripting |
yaml | YAML structured output |
csv | Comma-separated values for spreadsheet import (list and report results) |
toon | Compact, token-efficient encoding for LLM agents (default in agent mode) |
auto | Alias for the mode default |
dci list-budgets --output json
dci list-budgets --output table
dci list-reports --output csv > reports.csv
Use --fields id,name to project responses to specific fields before output, and --exclude description to remove fields.
Searching a list
Since v2.6.0, --search <substring> finds items in any list command by keyword, case-insensitively, across every text field and every page of the response. The match runs in the CLI, so it works on endpoints that offer no server-side filter:
dci list-dimensions --search genai
dci list-cloudflows --search cleanup
Some commands also take server-side filters — --filter and --name-contains on reports and budgets, for example. Those are per-command; see the command's page in the reference for the keys it accepts.
Paging
A list command returns one server page by default. Since v2.5.0, --all follows the page tokens and merges every page before rendering, so you get the complete list without chasing tokens yourself:
dci list-reports --all --output json
| Flag | Description |
|---|---|
--all | Fetch and merge every page. On a command that already returns everything, it notes that there was nothing more to fetch |
--page-token | Continue from the token a previous response returned |
--max-results | Rows per page. A value beyond the server's cap fails up front with the real limit |
Paged lists say so when a page token is dropped, and since v2.7.5 --page-token and --max-results fail with a clear message on commands that return their whole response in one request, instead of accepting the value and ignoring it.
Row order
Since v2.7.2, human tables land the rows you came for nearest the prompt: lists sort newest-last, insights rank top-savings-last, and report groups put the largest spenders just above the TOTAL row. Machine formats (json, yaml, csv), agent mode, and piped output keep the classic ordering, so nothing scripted changes. Set the ordering for one command with --output-order, for an environment with the DCI_OUTPUT_ORDER environment variable, or persistently with dci config.
Saving output to a file
--output-file (-O) writes a command's output to a file instead of the terminal, in whatever format the command would have printed. Pass a path, or a directory — including . — to save under the filename the API suggests:
dci list-budgets --output csv --output-file budgets.csv
dci export-datahub-dataset-records my-dataset --all --output-file .
A write that fails partway removes the partial file, so a truncated download never looks like a complete one.
Exporting data
A few commands answer with a file rather than a table. dci export-datahub-dataset-records streams the records of a DataHub dataset as CSV, or as newline-delimited JSON with --format jsonl, and the CLI prints those bytes exactly as the API sent them — --output does not apply to them, --search is rejected because there are no items to filter, and --output-file is how you save them.
The API returns one page per request, requires both time bounds, and accepts at most 366 days per request. --all handles all three: it follows the page tokens, walks the successive time windows, and merges everything into one export with a single header row. Consecutive pages can carry different label and metric columns, so the headers are unioned rather than concatenated and a value never lands under the wrong column. With no --start-time/--end-time at all it also finds the range the dataset covers and prints the range it settled on to stderr, so exporting everything is one command:
dci export-datahub-dataset-records my-dataset --all --output-file records.csv
A window wider than 366 days is rejected before the request, with a pointer to --all. Without --all, an export that has more rows waiting says so on stderr and prints the --page-token to continue from — nothing in a CSV or JSONL stream itself would reveal that the file is partial. For ordinary list commands, --all behaves as described under Paging.
See export-datahub-dataset-records for the full flag list, including --for-reimport, which rewrites an export so it can be loaded into another dataset with ingest-datahub-events-csv.
Table output options
Tables are rendered for reading: text aligns left and numbers right, timestamps display as human-readable dates (bare dates at report grain, minute precision for event times — shown in your local timezone), integral numbers group without decimals, and when a response has more columns than fit the terminal, the table keeps the ones that render readably — identity (id, name) and date columns first — and lists the hidden rest with a hint (-C to choose, -M wrap to wrap, -W to widen). --raw-numbers restores exact unformatted values.
The following flags control how the table is rendered:
| Flag | Short | Default | Description |
|---|---|---|---|
--table-mode | -M | fit | fit truncates long cell values to fit the terminal width. wrap wraps them across multiple lines. interactive opens the full-screen viewer. |
--table-columns | -C | all columns | Comma-separated list of columns to include, in the order listed. |
--table-width | -W | auto-detect | Table width in columns. Defaults to terminal width, falls back to the COLUMNS env var, then 120. |
--table-max-col-width | -X | 0 (auto) | Maximum width per column when fitting or wrapping. 0 lets the CLI decide automatically. |
Example combining multiple options:
dci list-budgets --table-mode wrap --table-columns id,name,amount --table-max-col-width 40
On report output, -C selects pivot columns and keeps the pivot layout (v2.7.6): dci get-report <id> -C service_description,2026-04,TOTAL shows those columns in the order given, and TOTAL and Trend match case-insensitively. A column the pivot does not have is a usage error listing the columns that are available, and pointing to --flat when the name belongs to the flat layout.
Many list commands also ship a curated default view: the most useful columns lead, and ids resolve to names — a report's owner, a role's name — instead of raw identifiers. Machine formats and explicit -C/--fields selections always return the raw fields.
Timestamps and timezones
Interactive tables show event timestamps — created, updated, acknowledged, and similar moments — in your local timezone. Columns carrying such values are titled with a (local) suffix (for example updated (local)), and a one-line note on stderr names the zone in use:
note: times shown in local time (UTC+03:00); pass --utc for UTC
Everything else stays in UTC by design:
- Report period columns (daily and hourly cost buckets) and anomaly usage windows (
started (UTC)) label UTC billing buckets — shifting them would move costs onto the wrong day. - Calendar dates — contract terms, invoice dates, budget periods — render as plain dates.
- Machine formats (
json,yaml,csv,toon) and agent mode always emit UTC or raw epoch values, so scripted output is identical on every machine.
| Control | Effect |
|---|---|
--utc | Keep table timestamps in UTC (time columns are titled (UTC)) |
DCI_TZ=<IANA name> | Display in a specific timezone instead of the system one, e.g. DCI_TZ=Europe/Berlin |
DataHub
DataHub datasets hold the cost and usage events you send to Cloud Intelligence™ yourself. A dataset is addressed by its name — the value dci list-datahub-datasets shows, and the value ingest sends as provider. A dataset's display name is what the console renders and is never an ingest identifier. Since v2.7.5, dataset names complete on Tab, resolve from a partial name, and open the picker when you leave the name off.
Sending events
Two commands ingest events, and both take the request body as inline field: value shorthand or as JSON on stdin (see Command structure):
# A CSV file, or a GZ/ZIP archive containing one
dci ingest-datahub-events-csv provider: litellm-usage, file: @events.csv
dci ingest-datahub-events-csv provider: litellm-usage, file: @events.csv.gz
# A JSON batch
dci ingest-datahub-events < events.json
Since v2.7.4, dci ingest-datahub-events-csv uploads the file named after @. The API's limit is 30 MB per file, so a larger export must be gzipped or zipped first, one CSV per archive, and a file over the limit, a missing file, or a forgotten @ is reported with a fix before anything is sent. The CSV must follow the dataset's schema template, and the dataset must exist — create it with dci create-datahub-dataset name: <dataset> first. dci ingest-datahub-events sends the same events as JSON, each with a provider, an RFC 3339 time, and its dimensions and metrics.
Ingested rows take up to 15 minutes to appear in the DoiT console and in Cloud Analytics reports. There is no delete-by-batch API, so to undo a bad upload, remove the rows with dci delete-datahub-events-by-filter over the time range the file covered, then upload the corrected file.
Copying between datasets
--for-reimport rewrites an export into the vocabulary ingest-datahub-events-csv accepts — dropping the export-only batch, source, export_time, and updated_by columns and renaming event_id to id — so records can be moved from one dataset to another:
dci export-datahub-dataset-records old-dataset --all --for-reimport --output-file records.csv
dci ingest-datahub-events-csv provider: new-dataset, file: @records.csv
See Exporting data for how --all covers pages and time windows, and the DataHub command group for every flag.
CloudFlow
The CLI drives CloudFlow end to end: build a flow, export it, import it elsewhere, test it, run it, and read back what each node did. Flows are addressed by the id that dci list-cloudflows shows first. The API does not filter this listing server-side, so narrow it with --search:
dci list-cloudflows
dci list-cloudflows --search cleanup
Authoring
dci build-cloud-flow creates a new flow and generates its nodes and connections from a natural-language intent, and dci refine-cloud-flow <flow-id> changes an existing one. Since v2.7.2 both answer with a readable result — the flow's ID, the conversationId to pass back for the next refinement, the builder's reply, and the build steps that ran — instead of the raw event stream, and both accept the family-consistent spellings dci build-cloudflow and dci refine-cloudflow:
dci build-cloud-flow < build.json
dci refine-cloud-flow <flow-id> question: "Add an approval step before the node that deletes disks"
Free text containing a comma must go through stdin, because inline shorthand treats commas structurally. Builder output lands as an unpublished draft in your own tenant, so export it and read the nodes before trusting it, and re-export after a refine to confirm the change was saved.
Moving flows between tenants
dci export-cloudflow-flow <flow-id> serializes the flow, plus every flow it references through subflow nodes, into a tenant-neutral, credential-free JSON bundle. Connections, Datastore tables, and global variables travel as named requirements to be rebound at import; credentials, tenant identifiers, and execution state never appear in the bundle. The command prints JSON by default, so redirecting it to a file is safe.
dci import-cloudflow-flow creates every flow of a bundle in the authenticated tenant. Imports are create-only: each call creates new draft flows with new IDs, nothing is published, and no schedule is activated. A real import requires an idempotency key up front, while --dry-run is the API's own dry run and returns the import plan without writing:
dci export-cloudflow-flow <flow-id> > bundle.json
dci import-cloudflow-flow --dry-run < bundle.json
dci import-cloudflow-flow --idempotency-key "$(uuidgen)" < bundle.json
Running and diagnosing
| Command | What it does |
|---|---|
dci test-run-cloudflow-flow | Runs a flow once as a test and accepts a draft. --dry-run deep-validates it with the same checks publishing applies, without starting a run |
dci trigger-cloudflow-flow | Starts a run of a published flow; a draft is rejected with 422 |
dci trigger-cloudflow-webhook | Triggers a published flow whose first node is a webhook trigger, passing the request body through as the webhook payload |
dci stop-cloudflow-flow | Stops the run in progress — no run ID needed, since a flow can only have one active run |
dci list-cloudflow-flow-runs | A flow's runs, newest first. --mode test or --mode standard separates test runs from production ones |
dci get-cloudflow-flow-run | One run's status and, per node, the JSON it consumed and produced — how you find out why a run failed, or that it "succeeded" with the wrong data |
A test run executes for real: the same graph, the same connections, the same credentials, and the same approval behavior — an approval-gated node still parks the run, and no request skips one. It is recorded as a test, so it stays out of run history and dashboard statistics, but it does hold the flow's concurrency lock, so a flow that is already running answers 409.
dci get-cloudflow-flow-run is the diagnosis tool. input is recorded only by action nodes — the cloud and DoiT operations — and is null for transform, code, branch, switch, datastore, subflow, and trigger nodes by design, while output is recorded by every node that finishes, so read a transform's behavior from its output. Payloads appear as soon as a node reaches a terminal status, sensitive values are redacted, and each payload is capped at 64 KB with truncated and totalBytes marking what was cut. Since v2.7.2 a failed run prints in full — status, per-node results, and the failure message — instead of collapsing into an error envelope.
dci trigger-cloudflow-webhook runs real automation and is classified as destructive: terminals confirm, scripts pass --yes, and --dry-run prints a local preview.
Beta commands
dci beta is the early-access command surface, introduced in v2.7.0. Beta commands use your existing sign-in, customer context, and output formats, and idempotency keys are generated for you unless you pass your own. dci commands --beta lists the surface, and an account not yet enrolled in the early-access program gets a clear hint rather than a bare error.
The surface starts with asynchronous report execution:
dci beta run-report # run a saved report asynchronously
dci beta run-report-config # run an inline report configuration
dci beta get-report-operation # poll the operation
dci beta get-report-results # read the rows once it succeeds
dci beta cancel-report-operation # cancel a pending or running operation
Since v2.7.2, dci beta run-report resolves report names like the rest of the CLI: a typed name resolves to its ID, multi-word names work unquoted, and no argument at all opens the report picker, in the shell and in the AI session alike. Inside the session, beta commands complete in the popup and run like any other / command, and a bare /beta lists them.
Agent mode
The CLI auto-detects when it is driven by an AI agent (via common agent environment variables) and switches to agent mode: output defaults to compact TOON, colors and decorations are disabled, and informational messages are routed to stderr so stdout stays parseable. Force it on with --agent or DCI_AGENT_MODE=1, or off with --no-agent; dci status shows the current mode. Agent mode is not the AI session: it is the mode an external harness drives the CLI in, and it never shows a prompt, picker, or viewer.
In agent mode, failures are written to stderr as a JSON envelope with a stable code, message, and retryable flag, plus optional hint, http_status, request_id, and retry_after fields:
{
"error": {
"code": "RESOURCE_NOT_FOUND",
"message": "Not Found",
"retryable": false,
"http_status": 404,
"request_id": "..."
}
}
Agent mode also caps report results at 500 rows by default (see Report results) and fails fast with AUTHENTICATION_REQUIRED when no credentials are available, instead of starting a browser login flow.
To install the CLI's agent skill (usage guidance embedded in the binary) into a coding agent's configuration directory:
dci skill claude # also: codex, cursor, gemini, kiro, opencode
dci skill list # inspect embedded files and token estimates
dci skill update # refresh installed copies after a CLI update
The bundled skill carries more than command syntax: it teaches addressing resources by name rather than id, and, since v2.7.2, CloudFlow authoring and flow-behavior verification — clone real flows instead of guessing node parameters, validate with a dry run, land changes as drafts, and read a test run's per-node payloads before reporting a flow as working. A skill installed from an older release does not have these; dci skill update refreshes it.
For machine-readable command metadata — arguments, flags, output shapes, and destructive-operation classification — use:
dci commands --json
dci commands --beta
Since v2.7.3, dci commands --json also carries each command's curated examples, page notes, and related commands, so an agent can read them without parsing help text.
Documentation for agents
Agents can discover and consume the CLI documentation without a browser:
dci docsprints this page's URL and the other documentation entry points from the terminal- Every page of this Help Center is available as plain Markdown by appending
.mdto its URL — this page is help.doit.com/docs/cli.md - help.doit.com/llms.txt is a machine-readable index of all Help Center pages (including the CLI command reference), and llms-full.txt inlines the full content
- help.doit.com/llms-cli.txt is a compact CLI-only index — the guides plus every command, each linked to its Markdown page — for agents that only need to drive
dci dci skill <agent>installs task-oriented usage guidance directly into the agent's configuration,dci skill updaterefreshes an installed copy, anddci <command> --help-fullincludes the full request/response schema for every command
For agents working with DoiT data through tool calls rather than a CLI, see the MCP server.
Customer context
DoiT employees and multi-tenant users must select which customer their commands operate on. Set it temporarily with the DCI_CUSTOMER_CONTEXT environment variable or the -D/--customer-context flag, or persist a default. The value can be a customer domain (acme.com), a customer ID, or the customer's URL display name as it appears in DoiT Console URLs (console.doit.com/customers/acme/…):
DCI_CUSTOMER_CONTEXT=acme.com dci list-budgets # this command only
dci list-budgets -D acme.com # this command only
dci customer-context set acme.com # persistent default
dci customer-context set acme # URL display name works too
dci customer-context show
dci customer-context clear
A wrong persisted context makes every command fail with PERMISSION_DENIED; the error hint names the active context. Verify access after switching with dci validate.
In the AI session, /customer shows or switches the context for that session only, and signing in as a different account drops the previous account's context and cached names.
Destructive commands
Commands classified as destructive (deletes, flow triggers, and similar) refuse to run without confirmation and exit with code 30:
dci delete-report <id> # refuses: requires confirmation
dci delete-report <id> --dry-run # prints a local preview without executing
dci delete-report <id> --yes # executes
In an interactive terminal the refusal is a confirmation dialog instead: it names the resolved target — name, owner, and description — Cancel is the default, and Esc cancels it.
Set DCI_CONFIRM_DESTRUCTIVE=1 to confirm globally in automation that has been reviewed — do not set it as a blanket default.
Environment variables
| Variable | Description |
|---|---|
DCI_API_KEY | API token for non-interactive authentication (personal or service account) |
DCI_CUSTOMER_CONTEXT | Override the customer context for commands |
DCI_AGENT_MODE | Force agent mode on (1) or off (0) |
DCI_CONFIRM_DESTRUCTIVE | Confirm destructive commands without --yes (use with care) |
DCI_SKIP_BODY_VALIDATION | Bypass client-side request-body validation (e.g. when a brand-new API field is rejected against a stale cached spec) |
DCI_NO_TUI | Disable interactive pickers, prompts, and viewers in a terminal (tables and color are kept) — see Interactive terminal features |
DCI_NO_RESOLVE | Disable name resolution and pickers — arguments are treated as literal IDs |
DCI_NO_UPDATE_CHECK | Disable the background release check and its update notice |
DCI_ACTIVE_HELP | 0 suppresses the short notices shell completion prints, such as the cold-cache notice |
DCI_OUTPUT_ORDER | Row order for human tables — see Row order |
DCI_API_BASE_URL | API base for this invocation only; it is never persisted into your saved configuration |
ANTHROPIC_API_KEY | Anthropic API key for the AI session; overrides a key saved with /key set |
DCI_AI_EFFORT | AI answer effort: low, medium (the default), high, or default for uncapped reasoning |
DCI_AI_STATS | 1 prints one telemetry line per AI answer (rounds, tool calls, tokens, timing) |
DCI_AI_PROVIDED | off opts a DoiT employee out of DoiT-provided AI access, falling back to an explicit key |
NO_COLOR | Disable colored output, including the pivot heatmap and charts |
DCI_TZ | Timezone (IANA name, e.g. Europe/Berlin) for table timestamps — see Timestamps and timezones |
CI is read, not set by you. Since v2.7.0 the CLI treats the standard CI environment variable as a signal that it is running in a pipeline, and uses the script behavior even when the runner allocates a terminal.
Configuration
Configuration is stored in the OS-specific config directory and is created automatically on first run.
| OS | Path |
|---|---|
| macOS | ~/Library/Application Support/dci/apis.json |
| Linux | ~/.config/dci/apis.json |
| Windows | %APPDATA%\dci\apis.json |
To reset configuration, delete the file at the path above and re-run dci login.
Using the CLI in scripts
The CLI is suitable for shell scripts, cron jobs, and CI/CD pipelines.
Recommendations:
- Use
--output jsonfor machine-readable output - Set
DCI_API_KEYto a personal or service account API token for non-interactive authentication; without credentials, non-interactive invocations fail fast instead of waiting for a browser login - Branch on exit codes rather than parsing error text
- Pass
--allwhen you need every page, and--yesbefore any destructive command - Nothing interactive runs in a pipe, a script, or CI, so no script can hang on a prompt;
DCI_NO_RESOLVE=1additionally treats every argument as a literal ID
Example:
export DCI_API_KEY=<YOUR_API_TOKEN>
budgets=$(dci list-budgets --output json)
Exit codes
Exit codes are stable across interactive, script, and agent usage:
| Code | Meaning |
|---|---|
0 | Success |
1 | Generic failure |
2 | Usage error (unknown command, flag, or request body field) |
10 | Authentication required or failed |
11 | Permission denied (check the customer context) |
20 | Resource not found |
21 | Conflict |
30 | Validation error, or a destructive command run without confirmation |
40 | Server or upstream error |
41 | Network error |
50 | Rate limited |
Next steps
- Browse the CLI command reference in the sidebar, or the cheat sheet for the one-page version
- Use
--helpon any command for inline usage details and tested examples - Ask a question in plain English with
dci ai— see AI session - Integrate the CLI into scripts and automation using
DCI_API_KEYand an API token from the console - Install the agent skill with
dci skill <agent>if you drive the CLI from an AI coding assistant, and keep it current withdci skill update