Connect SaaS data (HubSpot, Stripe, Salesforce, GitHub, Slack, etc.) to Wren Engine for SQL analysis. Guides the user through the full flow: install dlt, pick a SaaS source, set up credentials, run the data pipeline into DuckDB, then auto-generate a Wren semantic project from the loaded data. Use this skill whenever the user mentions: connecting SaaS data, importing data from an API, dlt pipelines, loading HubSpot/Stripe/Salesforce/GitHub/Slack data, querying SaaS data with SQL, or setting up a new data source from a REST API. Also trigger when the user already has a dlt-produced DuckDB file and wants to create a Wren project from it.
74
92%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
High
Do not use without reviewing
Reference docs (
dlt_sources) and theintrospect_dltscript are bundled. Pull references withwren skills get dlt-connector --full; fetch a script withwren skills get dlt-connector --script <name>.
Connect SaaS data to Wren Engine for SQL analysis — from zero to a verified, queryable project in one conversation.
Data analysts who know SQL and some Python, but may not have used dlt or Wren before. Explain concepts briefly when they first appear, but don't over-explain things a SQL-literate person would already know.
This skill walks through a four-phase workflow:
The user might enter at any phase. Ask which phase they're starting from — they may already have a .duckdb file and just need phases 2–4.
The goal is a project that actually queries successfully, not just files that look correct. Always run the verification step before declaring success.
When wren engine connects to a DuckDB file, it ATTACHes it using the filename (without .duckdb extension) as the catalog alias:
ATTACH DATABASE 'stripe_data.duckdb' AS "stripe_data" (READ_ONLY)This means every model's table_reference.catalog must equal the DuckDB filename stem. If the file is hubspot.duckdb, the catalog is hubspot. If it's my_pipeline.duckdb, the catalog is my_pipeline.
Getting this wrong causes "table not found" errors at query time. The introspect_dlt.py script handles this automatically.
Column types must be normalized using wren SDK's type_mapping.parse_type() function, which uses sqlglot to convert database-specific types (like DuckDB's HUGEINT, TIMESTAMP WITH TIME ZONE) into canonical SQL types that wren-core understands. Do not hardcode type mappings — always delegate to parse_type(raw_type, "duckdb").
The introspect_dlt.py script does this automatically when wren SDK is installed.
Ask the user which SaaS service they want to connect. Read dlt_sources for a list of popular verified sources and their auth requirements. If the source isn't listed, check whether dlt has a verified source for it by searching dlthub.com/docs/dlt-ecosystem/verified-sources.
pip install "dlt[duckdb]" --break-system-packagesCreate a Python script that:
destination='duckdb' and a local file pathpipeline.run(source)Here's the general pattern — adapt it per source (check dlt_sources for source-specific templates):
import dlt
pipeline = dlt.pipeline(
pipeline_name="<source>_pipeline",
destination="duckdb",
dataset_name="<source>_data",
)
# Source-specific: check the dlt_sources reference for auth patterns
source = <source_function>(api_key=dlt.secrets.value)
info = pipeline.run(source)
print(info)dlt reads credentials from environment variables or .dlt/secrets.toml. The simplest approach for a one-time run:
# Set the credential as an environment variable
# The exact variable name depends on the source — check the dlt_sources reference
export SOURCES__<SOURCE>__API_KEY="the-actual-key"Ask the user for their API key or token. Remind them:
.dlt/secrets.tomlpython <pipeline_script>.pyAfter the run, confirm:
.duckdb file was created (usually at <pipeline_name>.duckdb)import duckdb
con = duckdb.connect("<pipeline_name>.duckdb", read_only=True)
for row in con.execute("""
SELECT table_schema, table_name,
(SELECT COUNT(*) FROM information_schema.columns c
WHERE c.table_schema = t.table_schema AND c.table_name = t.table_name) as col_count
FROM information_schema.tables t
WHERE table_schema NOT IN ('information_schema', 'pg_catalog')
AND table_name NOT LIKE '_dlt_%'
ORDER BY table_schema, table_name
""").fetchall():
print(f" {row[0]}.{row[1]} ({row[2]} columns)")
con.close()Run the introspection script to auto-generate a complete Wren project from the DuckDB file:
# first fetch the script: wren skills get dlt-connector --script introspect_dlt > introspect_dlt.py
python introspect_dlt.py \
--duckdb-path <path-to-duckdb-file> \
--output-dir <project-directory> \
--project-name <name>This script:
table_reference.catalog to the DuckDB filename stem (matching wren engine's ATTACH behavior)information_schema_dlt_loads, _dlt_pipeline_state, etc.)_dlt_id, _dlt_load_id, _dlt_list_idx) from model definitions_dlt_parent_id columns and table naming conventionswren.type_mapping.parse_type() (sqlglot-based)After running, show the user what was generated:
# Show project summary
cat <project-directory>/wren_project.yml
echo "---"
ls <project-directory>/models/
echo "---"
cat <project-directory>/relationships.ymlSpot-check one generated model to confirm:
table_reference.catalog matches the DuckDB filename (e.g., stripe_data for stripe_data.duckdb)table_reference.schema matches the DuckDB schema (usually main)_dlt_* columns appear in the columns listCreate a Wren profile so the user can query without specifying connection details every time. The url must point to the directory containing the .duckdb file (not the file itself):
import yaml
from pathlib import Path
wren_home = Path.home() / ".wren"
wren_home.mkdir(exist_ok=True)
profiles_file = wren_home / "profiles.yml"
existing = (
(yaml.safe_load(profiles_file.read_text()) or {}) if profiles_file.exists() else {}
)
existing.setdefault("profiles", {})
profile_name = "<source>_dlt"
existing["profiles"][profile_name] = {
"datasource": "duckdb",
"url": str(Path("<duckdb-path>").resolve().parent),
"format": "duckdb",
}
existing["active"] = profile_name
profiles_file.write_text(yaml.dump(existing, default_flow_style=False, sort_keys=False))This phase is not optional. A project that generates YAML but fails at query time is not a success.
cd <project-directory>
wren context buildThis compiles the YAML models into target/mdl.json. If this fails, fix the issues before proceeding (see Troubleshooting below).
Run at least one query per generated model to confirm the project is functional:
# For each model, verify it resolves correctly
wren --sql 'SELECT COUNT(*) as total FROM "<table_name>"'If any query fails, debug and fix the model before moving on. Common issues:
wren profile listOnce basic queries pass, run 2–3 more interesting queries to show the user what their data looks like:
# Preview data
wren --sql 'SELECT * FROM "<table_name>" LIMIT 5'
# If there's a relationship, verify both models are queryable
wren --sql 'SELECT * FROM "<parent>" LIMIT 5'
wren --sql 'SELECT * FROM "<child>" LIMIT 5'Show the results to the user and explain what they're seeing. This is their first look at the data through Wren — make it count.
Only after queries return real data, tell the user the setup is complete. Summarize:
The project is now DuckDB-backed, which is exactly what GenBI snapshot mode
wants. If the user wants to turn this data into a shareable dashboard / web app
and deploy it (Vercel / Cloudflare), hand off to the GenBI workflow:
wren skills get genbi. Its snapshot source is the very .duckdb file this
pipeline produced.
If wren context build fails:
data_source: duckdb is set in wren_project.ymlwren context validate for detailed error messagesIf queries fail with "table not found":
table_reference.catalog doesn't match the DuckDB filename. If the file is pipeline.duckdb, the catalog must be pipeline, not empty string.url points to the directory containing the .duckdb file"hubspot__contacts"If queries fail with type errors:
introspect_dlt.py with wren SDK installed to get proper type normalizationGeneral:
wren profile list_dlt_parent_id / _dlt_id columns are kept in the actual DuckDB tables but hidden from Wren model definitions. They're only used in relationship conditions.table_reference (not ref_sql) since they map directly to DuckDB tables created by dlt.parse_type() with sqlglot's DuckDB dialect. If a type looks wrong, the user can edit the model's metadata.yml directly.7830cc7
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.