Skip to content

Using the CLI

The skforecast-ai command runs the same pipeline as the Python API from a terminal. Point it at a CSV file or URL, name the target column and the horizon, and it returns the profile of the data, the plan, the predictions, the metrics or the standalone Python script behind them. The core commands need no LLM and no configuration.

This guide shows how to use the commands together. The CLI reference, generated from the code, lists every option of every command; in the terminal, run skforecast-ai <command> --help.


Quick start

The CLI is part of the core installation (pip install skforecast-ai). The examples in this guide use a public monthly dataset with two exogenous variables; set its URL once:

DATA="https://raw.githubusercontent.com/skforecast/skforecast-datasets/main/data/h2o_exog.csv"
# Inspect the data and see the recommended forecaster and estimator
skforecast-ai profile "$DATA" --target y --date-column fecha

# Hold out the last 12 months, forecast them and report the metrics
skforecast-ai forecast "$DATA" --target y --date-column fecha --steps 12 --test-size 12

Commands

Command What it does LLM
profile Inspect a dataset and recommend a forecaster and an estimator
plan Build the forecasting plan: forecaster, estimator, lags, window features, interval
refine-plan Change fields of a saved plan without profiling the data again Optional
forecast Profile, plan, generate the script and run it
forecast-code Generate the forecasting script without running it
backtest Evaluate the plan with time series cross-validation Optional
backtest-code Generate the backtesting script without running it
compare Backtest several candidates on the same folds and rank them
ask Answer a question about your data, a plan or forecasting in general Required
check-llm Check how the LLM configuration resolves Required
config Show, set and locate the configuration file
mcp Serve the workflow to coding agents as an MCP server

The commands that read data take a local CSV path or an https:// URL as their first argument. The file can be separated by ,, ; or tabulators, with . or , as decimal mark (what a spreadsheet exports in most of Europe), and compressed (.csv.gz); .tsv files are read too. The separator and the decimal mark are detected from the first lines of the file, and the generated script reads the file with them. When a file reads in more than one way, the command fails and asks for them: profile it with --sep and --decimal and pass the saved profile on. Two things a spreadsheet often exports are not read yet: the file must be UTF-8 (in Excel, save as "CSV UTF-8", not "CSV"), and dates written day first (31/01/2024) are read month first or rejected, so write them in ISO 8601 (2024-01-31).

skforecast-ai profile sales.csv --target revenue --sep ";" --decimal "," --format json -o profile.json
skforecast-ai plan --from-profile profile.json --steps 30

Your data

The combination of --target, --date-column and --series-id-column tells the CLI which layout the CSV has.

Layout Options Example
Single series --target with one column --target y --date-column fecha
Multi-series, wide --target with comma-separated columns --target "item_1,item_2,item_3" --date-column date
Multi-series, long --target plus --series-id-column --target revenue --date-column date --series-id-column store_id
# Multi-series, wide: one column per series
ITEMS="https://raw.githubusercontent.com/skforecast/skforecast-datasets/main/data/simulated_items_sales.csv"
skforecast-ai forecast "$ITEMS" --target "item_1,item_2,item_3" --date-column date --steps 7

# Multi-series, long: one row per series and date (local file)
skforecast-ai forecast sales.csv --target revenue --date-column date --series-id-column store_id --steps 30

Every command that reads data accepts these three options, so the examples below apply to any layout.


Forecast

forecast runs the whole pipeline in one call and works in two modes:

  • Evaluation mode (--test-size): holds out the end of the series, forecasts it and reports metrics. --test-size takes an integer (the last N observations), a float in (0, 1) (the last fraction) or a date (the start of the test set); the test set must hold exactly --steps observations. To evaluate over a longer period, use backtest.
  • Prediction mode (the default): trains on all the data and forecasts the next --steps periods. There is no ground truth, so there are no metrics. When the data has exogenous variables, their future values are required: pass --exog with a CSV that has the date column and the same exogenous columns, covering the horizon.
# Evaluation mode: hold out the last 12 observations and forecast them
skforecast-ai forecast "$DATA" --target y --date-column fecha --steps 12 --test-size 12

# Prediction mode: this dataset has exogenous variables, so their future values are needed
skforecast-ai forecast "$DATA" --target y --date-column fecha --steps 12 --exog future_exog.csv

# Prediction intervals, with the predictions and the script saved to files
skforecast-ai forecast "$DATA" --target y --date-column fecha --steps 12 --test-size 12 \
  --interval "0.1,0.9" --output-predictions preds.csv --output-code forecast.py

A series without exogenous variables forecasts the future with no extra input:

H2O="https://raw.githubusercontent.com/skforecast/skforecast-datasets/main/data/h2o.csv"
skforecast-ai forecast "$H2O" --target x --date-column fecha --steps 12

--interval takes two quantiles between 0 and 1: "0.1,0.9" is an 80% interval. Percentile values such as "10,90" are deprecated and will stop working in a future skforecast release.

forecast-code builds the same plan and writes the prediction script instead of running it, to the terminal or to --output. The script is the code forecast would execute: when the data has exogenous variables, it reads their future values from exog_future.csv. --format json wraps the script with the profile and the plan.

skforecast-ai forecast-code "$DATA" --target y --date-column fecha --steps 12 --output forecast.py

Adjust the plan

plan shows the modeling decisions without running anything: forecaster, estimator, lags, window features, calendar features, metric and interval. Each of them can be overridden, and the assistant fills in the rest: --metric (comma-separated, the first one ranks), --use-exog true|false, --differentiation N, --calendar-features month,day_of_week (or none), --target-transformer StandardScaler|none and --dropna-from-series true|false join the options below. auto asks for the rule, which is what refining a saved plan needs to undo a choice. --exog-columns (of plan and profile) chooses the exogenous columns when the data is profiled: comma-separated names, or none; the other columns are not used.

skforecast-ai plan "$DATA" --target y --date-column fecha --steps 12

# Choose the forecaster, the estimator and its hyperparameters
skforecast-ai plan "$DATA" --target y --date-column fecha --steps 12 \
  --forecaster ForecasterDirect --estimator Ridge --estimator-kwargs '{"alpha": 0.5}'

# Explicit lags and window features instead of the deterministic selection
skforecast-ai plan "$DATA" --target y --date-column fecha --steps 12 \
  --lags "1,2,3,12" --window-features '[{"stats": ["mean"], "window_size": 12}]'

# Difference the target, leave the exogenous columns out, rank by MSE
skforecast-ai plan "$DATA" --target y --date-column fecha --steps 12 \
  --differentiation 1 --use-exog false --metric mean_squared_error

forecast-code, backtest-code, forecast and backtest take the same overrides, also with --from-plan, where they are applied on top of the saved plan. --help of each command lists the options it supports.


Backtest and compare

backtest evaluates the plan over several folds, as it would run in production. The cross-validation options you do not pass (--initial-train-size, --fold-stride, --refit, --fixed-train-size, --gap, --allow-incomplete-fold) are decided by the assistant from the profile and the plan. --fixed-train-size and --expanding-train only apply with --refit.

# Folds decided by the assistant
skforecast-ai backtest "$DATA" --target y --date-column fecha --steps 12

# Your own folds: 100 initial observations, a new fold every 6 steps,
# refit in every fold on a fixed training window, and 3 steps between
# training and test
skforecast-ai backtest "$DATA" --target y --date-column fecha --steps 12 \
  --initial-train-size 100 --fold-stride 6 --refit --fixed-train-size --gap 3

# Save the predictions of every fold and the script
skforecast-ai backtest "$DATA" --target y --date-column fecha --steps 12 \
  --output-predictions backtest_preds.csv --output-code backtest.py

backtest-code takes the data, plan and cross-validation options and writes the backtesting script instead of running it.

skforecast-ai backtest-code "$DATA" --target y --date-column fecha --steps 12 \
  --initial-train-size 100 --no-refit --output backtest.py

compare backtests several candidates on the same folds and ranks them by the metric. Without --candidates, the candidates are built from the profile. With it, each candidate is a [name, config] pair; the config takes forecaster, estimator, estimator_kwargs, lags, window_features, use_exog, differentiation, calendar_features, target_transformer and dropna_from_series, and all the candidates must belong to the same forecaster family.

For a single series, a seasonal naive baseline (ForecasterEquivalentDate) is ranked as one more row, and the explanation says whether the best candidate beats it; --no-baseline leaves it out.

# Candidates built from the profile
skforecast-ai compare "$DATA" --target y --date-column fecha --steps 12

# Your own candidates, ranked by the first metric; save the script of the winner
skforecast-ai compare "$DATA" --target y --date-column fecha --steps 12 \
  --candidates '[["recursive", {"forecaster": "ForecasterRecursive"}], ["direct", {"forecaster": "ForecasterDirect", "estimator": "Ridge", "lags": [1, 2, 3, 12]}]]' \
  --metric "mean_absolute_scaled_error,mean_absolute_error" \
  --output-code best.py

# Leave the seasonal naive baseline out of the ranking
skforecast-ai compare "$DATA" --target y --date-column fecha --steps 12 --no-baseline

compare also takes the cross-validation options of backtest.


Save, reuse and chain

A saved plan separates the modeling decision from its execution: review it, keep it under version control, and run it again later against updated data.

# Save the plan
skforecast-ai plan "$DATA" --target y --date-column fecha --steps 12 --format json --output plan.json

# Change the horizon and add an interval without profiling the data again
skforecast-ai refine-plan --from-plan plan.json --steps 6 --interval "0.1,0.9" \
  --format json --output plan_6.json

# Go back to the deterministic selection of lags and window features
skforecast-ai refine-plan --from-plan plan.json --lags auto --window-features auto \
  --format json --output plan_auto.json

# Generate the script from the saved plan: no data argument needed
skforecast-ai forecast-code --from-plan plan.json --output forecast.py

# Run the saved plan; options given with --from-plan are applied on top of it
skforecast-ai forecast "$DATA" --from-plan plan.json --test-size 12 --interval "0.1,0.9"
skforecast-ai backtest "$DATA" --from-plan plan.json

--from-plan reads the file written by plan or refine-plan, a bundle with the profile and the plan. --from-profile reads the file written by profile --format json and is accepted by plan, compare and ask. The plan fixes the horizon: --steps with --from-plan must match it (use refine-plan --steps to change it). With --from-plan, forecast-code and backtest-code need no data argument (the script loads the path recorded in the profile); forecast, backtest and compare always need the data because they run the model. Data passed with a saved profile are profiled again: updated data (new rows, a new series) run with the new profile, which says so in its warnings, columns the profile does not name are not used, and data of another structure (another frequency, an exogenous column missing) exit with code 1 and say how they differ.

Both options read from standard input when given -, so the commands chain with pipes. -q hides the progress spinners.

skforecast-ai profile "$DATA" --target y --date-column fecha --format json -q | \
  skforecast-ai plan --from-profile - --steps 12 --format json -q | \
  skforecast-ai refine-plan --from-plan - --interval "0.1,0.9" --format json -q | \
  skforecast-ai forecast-code --from-plan - --output forecast.py

Pipes work in bash, zsh and fish. On Windows, use Git Bash or WSL: PowerShell pipes pass objects instead of text.


Use the LLM

The LLM commands need the [llm] extra and a provider (see Configuring the LLM). The LLM explains the decisions and turns plain-language descriptions into plan or cross-validation settings; every suggestion is validated before it is used, and the rest of the pipeline stays deterministic.

Set the provider once, then check that it works before running anything else. check-llm reports where each setting comes from and exits with code 1 when a check fails, so it can guard a script.

export SKFORECAST_AI_LLM="openai:gpt-5.5"

# Check the configuration; --test-call also sends a one-line prompt to the model
skforecast-ai check-llm --test-call

ask answers a general question, or a question about a dataset (its profile), a plan or a saved file. --data takes a local CSV file; adding --steps builds a plan, so the answer covers the plan and the generated script.

# General question
skforecast-ai ask "How do I choose between recursive and direct strategies?"

# About a dataset: the LLM receives its profile
skforecast-ai ask "Why this forecaster?" --data h2o_exog.csv --target y --date-column fecha

# About the plan built for it and its script
skforecast-ai ask "Why these lags?" --data h2o_exog.csv --target y --date-column fecha --steps 12

# About a saved plan
skforecast-ai ask "Why these lags?" --from-plan plan.json

# Choose the skills sent with the question
skforecast-ai ask "How do I set up prediction intervals?" --skills "prediction-intervals"

The LLM receives summary statistics of the data (frequency, date range, target statistics, missing values, significant lags), the plan and the script, never the observations. See What is sent to the LLM and, for --skills, Skills.

Two commands take a --prompt in plain language. In refine-plan, it describes the domain and the LLM proposes lags and window features; in backtest, it describes how the model is deployed and the LLM translates it into the cross-validation strategy.

skforecast-ai refine-plan --from-plan plan.json \
  --prompt "Monthly sales with a yearly cycle and a strong December peak" \
  --format json --output plan_llm.json

skforecast-ai backtest "$DATA" --target y --date-column fecha --steps 12 \
  --prompt "We retrain every month with a one-month data delay"

Serve coding agents

mcp runs the MCP server, so a coding agent (Claude Code, Cursor, Claude Desktop) calls the deterministic workflow as tools. It needs the [mcp] extra, speaks over stdio and is started by the agent, which you configure with the command below. --allow-dir is required: the server only reads CSV files inside that directory, given by their absolute path. --allow-project-dir takes its place where Claude Code starts the server: the directory is then the project of the session.

pip install "skforecast-ai[mcp]"

skforecast-ai mcp --allow-dir /path/to/data

How to connect an agent, the skill that teaches it the workflow, what the agent sees and the limits of the server are in MCP server for coding agents; the tools and their errors, in the MCP server reference.


Configuration

Settings that apply to every call are kept in ~/.config/skforecast-ai/config.toml.

skforecast-ai config set llm.provider "openai:gpt-5.5"
skforecast-ai config show   # current values, API key masked
skforecast-ai config path   # location of the file

LLM resolution precedence

Each LLM setting is read from the first source that defines it: the command option, then the environment variable, then the configuration file.

Setting Option Environment variable Config key
Provider and model --llm SKFORECAST_AI_LLM llm.provider
Endpoint --base-url SKFORECAST_AI_BASE_URL llm.base_url
API key --api-key SKFORECAST_AI_API_KEY llm.api_key
Send data to the LLM --send-data-to-llm SKFORECAST_AI_SEND_DATA_TO_LLM llm.send_data_to_llm

--base-url is the server URL for Ollama and OpenAI-compatible endpoints, and the AWS region for Bedrock; see Providers and credentials for the model strings and the environment variable each provider reads. --send-data-to-llm exists for parity with the Python API: the CLI never sends observations, whatever its value. --skills is not read from the configuration; pass it on each call.


Scripting

  • --format json prints the full result as JSON, the same content as model_dump_json() in Python. The default is a table (profile, plan, refine-plan, forecast, backtest, compare, check-llm), the script (forecast-code, backtest-code) or text (ask).
  • --output (-o) writes the output of profile, plan, refine-plan and the *-code commands to a file. --output-predictions and --output-code save the predictions and the script of the commands that run a model.
  • --quiet (-q) hides the progress spinners.
  • Warnings and errors go to standard error, so they never mix with the JSON document that --format json prints on standard output. With --format json, an error is printed as a JSON object, {"error": {"code": ..., "message": ..., "field": ..., "hint": ...}}, with the stable code and the argument or field at fault of Exceptions and warnings.
Exit code Meaning
0 Success
1 Error: missing file, unknown column, no LLM configured, unreachable URL, failed execution or a failed check-llm
2 Invalid usage: unknown option, missing argument or a value the option does not accept (such as --format xml or --interval 0.1), printed as text by the parser; with --format json, a value of a valid option that the command rejects is the JSON object above

Shell completion for commands and options (bash, zsh, fish, PowerShell) is installed with skforecast-ai --install-completion; restart the shell afterwards.