CLI reference
Skiff has three project-scoped commands. Every command discovers the nearest skiff.toml from the current directory upward and prepares the complete reviewed plan before acting.
Commands
| Command | Result |
|---|---|
skiff check |
Validate configuration and files, require credentials, and test every source connection |
skiff refresh |
Refresh non-incremental tables fully and append incremental tables past their stored watermarks |
skiff refresh --full |
Ignore all watermarks and rebuild every table from scratch |
skiff refresh --since A --until B |
Replace [A, B) in incremental tables; rebuild non-incremental tables normally |
skiff mcp |
Serve the frozen automatic refresh as one no-argument MCP tool |
Global help and version:
skiff --help
skiff --version
skiff COMMAND --help
Refresh bounds
--since and --until are required together and cannot be combined with --full.
Accepted bounds:
- signed integers;
YYYY-MM-DD;YYYY-MM-DD HH:MM:SS;YYYY-MM-DDTHH:MM:SS.
The two values must be the same kind and since must be earlier than until.
Manifest structure
version = 1
[[sources]]
name = "reporting"
host = "sql.example.com"
port = 1433
database = "Reporting"
user = "skiff_reader"
password_env = "REPORTING_DB_PASS"
trust_cert = false
snapshot = false
[output]
file = "data/reporting.duckdb"
[limits]
max_rows = 500000
timeout_secs = 60
[[queries]]
source = "reporting"
file = "queries/incidents.sql"
table = "incidents"
incremental_key = "opened_at"
[[checks]]
name = "incident ids are unique"
file = "checks/incident_ids.sql"
Field rules and defaults
| Field | Rule |
|---|---|
version |
Required; currently 1 |
sources |
At least one; names are unique case-insensitively |
sources.port |
Defaults to 1433 |
sources.trust_cert |
Defaults to false |
sources.snapshot |
Defaults to false |
output.file |
Relative repo-confined path ending in .duckdb |
limits.max_rows |
Optional; defaults to 500000; use -1 to disable the row limit, otherwise must be greater than zero |
limits.timeout_secs |
Optional; defaults to 60, must be greater than zero |
queries |
At least one; each source must exist and each table must be unique |
queries.file |
Relative repo-confined path ending in .sql |
queries.table |
Letters, digits, and underscores only |
queries.incremental_key |
Optional integer or timestamp output column; letters, digits, and underscores only |
checks |
Optional; names must be unique and files must be repo-confined .sql paths |
Unknown manifest fields are errors.
Output files
The published DuckDB uses owner-only file mode and contains every configured user table plus _skiff_refresh.
_skiff_refresh records the generation identifier, start and completion timestamps, table count, and total row count for the current publication.
Beside the output, Skiff uses:
| Path | Purpose |
|---|---|
.<output-name>.skiff.lock |
Coordinates Skiff refreshes and records the current owner |
.<output-name>.skiff-refresh-<pid>/ |
Private generation directory removed after completion or failure |
MCP report
The no-argument MCP refresh returns JSON with:
generation, file, started_at, completed_at, elapsed_ms,
rows, tables[], schema_changes[]
Each table report contains source, table, rows loaded during this run, total_rows after assembly, and elapsed_ms.
Errors
CLI errors include a message and Action: guidance. Publication errors also state whether anything changed. MCP returns the same information as message, changed, and human_action.