@bonnard/cli 0.2.4 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/bon.mjs +15 -2
- package/dist/bin/{validate-BdqZBH2n.mjs → validate-Bc8zGNw7.mjs} +75 -3
- package/dist/docs/topics/features.governance.md +58 -59
- package/dist/docs/topics/features.semantic-layer.md +6 -0
- package/dist/docs/topics/views.md +17 -9
- package/dist/docs/topics/workflow.md +6 -5
- package/dist/templates/claude/skills/bonnard-design-guide/SKILL.md +233 -0
- package/dist/templates/claude/skills/bonnard-get-started/SKILL.md +49 -15
- package/dist/templates/claude/skills/bonnard-metabase-migrate/SKILL.md +28 -9
- package/dist/templates/cursor/rules/bonnard-design-guide.mdc +232 -0
- package/dist/templates/cursor/rules/bonnard-get-started.mdc +49 -15
- package/dist/templates/cursor/rules/bonnard-metabase-migrate.mdc +28 -9
- package/dist/templates/shared/bonnard.md +28 -11
- package/package.json +1 -1
|
@@ -40,9 +40,13 @@ The connection will be tested automatically during `bon deploy`.
|
|
|
40
40
|
|
|
41
41
|
Before creating cubes, understand what tables and columns are available in your warehouse.
|
|
42
42
|
|
|
43
|
+
**Important:** `bon query` is for querying the **deployed semantic layer** — it does NOT
|
|
44
|
+
access your database directly. Use your warehouse's native tools to explore tables.
|
|
45
|
+
|
|
43
46
|
**Options for exploring your data:**
|
|
44
|
-
- Use your database
|
|
47
|
+
- Use your database CLI (e.g., `psql` for Postgres/Redshift, `snowsql` for Snowflake, `bq` for BigQuery) to list tables and columns
|
|
45
48
|
- Check your dbt docs or existing documentation for table schemas
|
|
49
|
+
- Ask the user for table names and column details if you don't have direct database access
|
|
46
50
|
- For the demo dataset, the tables are: `contoso.fact_sales`, `contoso.dim_product`, `contoso.dim_store`, `contoso.dim_customer`
|
|
47
51
|
|
|
48
52
|
Note the table names, column names, and data types — you'll use these in Phase 3.
|
|
@@ -78,6 +82,15 @@ cubes:
|
|
|
78
82
|
sql: total_cost
|
|
79
83
|
description: Sum of product costs
|
|
80
84
|
|
|
85
|
+
# Filtered measures — match what users actually see on dashboards
|
|
86
|
+
- name: return_count
|
|
87
|
+
type: count
|
|
88
|
+
description: >-
|
|
89
|
+
Sales transactions with returns only (return_quantity > 0).
|
|
90
|
+
For all transactions, use count.
|
|
91
|
+
filters:
|
|
92
|
+
- sql: "{CUBE}.return_quantity > 0"
|
|
93
|
+
|
|
81
94
|
dimensions:
|
|
82
95
|
- name: sales_key
|
|
83
96
|
type: number
|
|
@@ -96,8 +109,11 @@ cubes:
|
|
|
96
109
|
```
|
|
97
110
|
|
|
98
111
|
Key rules:
|
|
99
|
-
- Every cube needs a `primary_key` dimension
|
|
100
|
-
- Every measure and dimension should have a `description`
|
|
112
|
+
- Every cube needs a `primary_key` dimension — **it must be unique**. If no column is naturally unique, use `sql` with `ROW_NUMBER()` instead of `sql_table` and add a synthetic key. Non-unique PKs cause dimension queries to silently return empty results.
|
|
113
|
+
- Every measure and dimension should have a `description` — descriptions are how AI agents discover and choose metrics (see `bonnard-design-guide` rule)
|
|
114
|
+
- **Add filtered measures** for any metric users track with filters (e.g., "online revenue" vs "total revenue"). If a dashboard card has a WHERE clause beyond a date range, that filter should be a filtered measure.
|
|
115
|
+
- Measure descriptions should say what's **included and excluded** — e.g., "Online channel only. For all channels, use total_revenue."
|
|
116
|
+
- Dimension descriptions should include **example values** for categorical fields — e.g., "Sales channel (values: Online, Store, Reseller)"
|
|
101
117
|
- Set `data_source` to match the datasource name from Phase 1
|
|
102
118
|
- Use `sql_table` with the full `schema.table` path
|
|
103
119
|
- Use `sql_table` for simple table references, `sql` for complex queries
|
|
@@ -107,26 +123,38 @@ for all 12 measure types, `bon docs cubes.dimensions.types` for dimension types.
|
|
|
107
123
|
|
|
108
124
|
## Phase 4: Create a View
|
|
109
125
|
|
|
110
|
-
Views expose a curated subset of measures and dimensions for
|
|
111
|
-
|
|
126
|
+
Views expose a curated subset of measures and dimensions for specific
|
|
127
|
+
audiences. **Name views by what they answer** (e.g., `sales_performance`),
|
|
128
|
+
not by what table they wrap (e.g., `sales_view`). A view can combine
|
|
129
|
+
measures and dimensions from multiple cubes via `join_path`.
|
|
112
130
|
|
|
113
|
-
|
|
131
|
+
The view **description** is critical — it's how AI agents decide which view
|
|
132
|
+
to query. It should answer: what's in here, when to use it, and when to
|
|
133
|
+
use something else instead. Include key dimension values where helpful.
|
|
134
|
+
|
|
135
|
+
Example using demo data — `bonnard/views/sales_performance.yaml`:
|
|
114
136
|
|
|
115
137
|
```yaml
|
|
116
138
|
views:
|
|
117
|
-
- name:
|
|
118
|
-
description:
|
|
139
|
+
- name: sales_performance
|
|
140
|
+
description: >-
|
|
141
|
+
Retail sales metrics — revenue, cost, and transaction counts by product,
|
|
142
|
+
store, and date. Default view for sales questions. Includes return_count
|
|
143
|
+
for return analysis. For customer demographics, use customer_insights
|
|
144
|
+
instead.
|
|
119
145
|
cubes:
|
|
120
146
|
- join_path: sales
|
|
121
147
|
includes:
|
|
122
148
|
- count
|
|
123
149
|
- total_revenue
|
|
150
|
+
- return_count
|
|
124
151
|
- total_cost
|
|
125
152
|
- date
|
|
126
153
|
- sales_quantity
|
|
127
154
|
```
|
|
128
155
|
|
|
129
|
-
Use `bon docs views` for the full reference.
|
|
156
|
+
Use `bon docs views` for the full reference. See the `bonnard-design-guide`
|
|
157
|
+
rule for principles on naming, descriptions, and view structure.
|
|
130
158
|
|
|
131
159
|
## Phase 5: Validate
|
|
132
160
|
|
|
@@ -162,21 +190,26 @@ After deploying, the output shows what changed (added/modified/removed) and
|
|
|
162
190
|
flags any breaking changes. Use `bon deployments` to see history and
|
|
163
191
|
`bon diff <id>` to review changes from any deployment.
|
|
164
192
|
|
|
165
|
-
## Phase 7: Test with
|
|
193
|
+
## Phase 7: Test with Queries
|
|
166
194
|
|
|
167
|
-
Verify the deployment works using the
|
|
195
|
+
Verify the deployment works using the **view name** from Phase 4:
|
|
168
196
|
|
|
169
197
|
```bash
|
|
170
198
|
# Simple count
|
|
171
|
-
bon query '{"measures": ["
|
|
199
|
+
bon query '{"measures": ["sales_performance.count"]}'
|
|
172
200
|
|
|
173
201
|
# With a dimension
|
|
174
|
-
bon query '{"measures": ["
|
|
202
|
+
bon query '{"measures": ["sales_performance.total_revenue"], "dimensions": ["sales_performance.date"]}'
|
|
175
203
|
|
|
176
204
|
# SQL format
|
|
177
|
-
bon query --sql "SELECT MEASURE(total_revenue) FROM
|
|
205
|
+
bon query --sql "SELECT MEASURE(total_revenue) FROM sales_performance"
|
|
178
206
|
```
|
|
179
207
|
|
|
208
|
+
**Test with natural language too.** If you've set up MCP (Phase 8), ask
|
|
209
|
+
an AI agent questions like "what's our total revenue?" and check whether
|
|
210
|
+
it picks the right view and measure. If it picks the wrong view, the issue
|
|
211
|
+
is usually the view description — see the `bonnard-design-guide` rule.
|
|
212
|
+
|
|
180
213
|
## Phase 8: Connect AI Agents (Optional)
|
|
181
214
|
|
|
182
215
|
Set up MCP so AI agents can query the semantic layer:
|
|
@@ -192,8 +225,9 @@ and other MCP clients. The MCP URL is `https://mcp.bonnard.dev/mcp`.
|
|
|
192
225
|
|
|
193
226
|
After the first cube is working:
|
|
194
227
|
|
|
228
|
+
- **Iterate on descriptions** — test with real questions via MCP, fix agent mistakes by improving view descriptions and adding filtered measures (see `bonnard-design-guide` rule)
|
|
195
229
|
- Add more cubes for other tables
|
|
196
230
|
- Add joins between cubes (`bon docs cubes.joins`)
|
|
231
|
+
- Build audience-centric views that combine multiple cubes — e.g., a `management` view that pulls from `sales`, `users`, and `products` cubes
|
|
197
232
|
- Add calculated measures (`bon docs cubes.measures.calculated`)
|
|
198
233
|
- Add segments for common filters (`bon docs cubes.segments`)
|
|
199
|
-
- Build dashboards (`bon docs dashboards`)
|
|
@@ -121,11 +121,12 @@ highest-referenced table and work down. Create one file per cube in
|
|
|
121
121
|
For each cube:
|
|
122
122
|
1. Set `sql_table` to the full `schema.table` path
|
|
123
123
|
2. Set `data_source` to the datasource name from Phase 3
|
|
124
|
-
3. Add a `primary_key` dimension
|
|
124
|
+
3. Add a `primary_key` dimension — **must be unique**. If no column is naturally unique, use `sql` with `ROW_NUMBER()` instead of `sql_table` and add a synthetic key
|
|
125
125
|
4. Add time dimensions for date/datetime columns
|
|
126
126
|
5. Add measures based on card SQL patterns (Phase 4)
|
|
127
|
-
6. Add
|
|
128
|
-
7. Add
|
|
127
|
+
6. **Add filtered measures** for every card with a WHERE clause beyond date range — e.g., "active offers" (status != cancelled). This is the #1 way to match dashboard numbers.
|
|
128
|
+
7. Add dimensions for columns used as filters (template vars from Phase 2)
|
|
129
|
+
8. Add `description` to every measure and dimension — descriptions should say what's **included and excluded**. Dimension descriptions should include **example values** for categorical fields.
|
|
129
130
|
|
|
130
131
|
Example — `bonnard/cubes/orders.yaml`:
|
|
131
132
|
|
|
@@ -180,15 +181,26 @@ Use `bon docs cubes.joins` for the full reference.
|
|
|
180
181
|
|
|
181
182
|
Map Metabase collections to views. Each top-level collection (business domain)
|
|
182
183
|
from the analysis report becomes a view that composes the relevant cubes.
|
|
184
|
+
**Name views by what they answer** (e.g., `sales_pipeline`), not by what
|
|
185
|
+
table they wrap (e.g., `orders_view`).
|
|
183
186
|
|
|
184
187
|
Create one file per view in `bonnard/views/`.
|
|
185
188
|
|
|
189
|
+
The view **description** is critical — it's how AI agents decide which view
|
|
190
|
+
to query. It should answer: what's in here, when to use it, and when to use
|
|
191
|
+
something else instead. Cross-reference related views to prevent agents from
|
|
192
|
+
picking the wrong one.
|
|
193
|
+
|
|
186
194
|
Example — `bonnard/views/sales_analytics.yaml`:
|
|
187
195
|
|
|
188
196
|
```yaml
|
|
189
197
|
views:
|
|
190
198
|
- name: sales_analytics
|
|
191
|
-
description:
|
|
199
|
+
description: >-
|
|
200
|
+
Sales team view — order revenue, counts, and status breakdowns with
|
|
201
|
+
customer region. Default view for revenue and order questions. Use the
|
|
202
|
+
status dimension (values: pending, completed, cancelled) to filter.
|
|
203
|
+
For customer-level analysis, use customer_insights instead.
|
|
192
204
|
cubes:
|
|
193
205
|
- join_path: orders
|
|
194
206
|
includes:
|
|
@@ -204,7 +216,8 @@ views:
|
|
|
204
216
|
- region
|
|
205
217
|
```
|
|
206
218
|
|
|
207
|
-
Use `bon docs views` for the full reference.
|
|
219
|
+
Use `bon docs views` for the full reference. See the `bonnard-design-guide`
|
|
220
|
+
rule for principles on view naming, descriptions, and structure.
|
|
208
221
|
|
|
209
222
|
## Phase 7: Validate and Deploy
|
|
210
223
|
|
|
@@ -235,23 +248,29 @@ equivalent queries:
|
|
|
235
248
|
|
|
236
249
|
```bash
|
|
237
250
|
# Run a semantic layer query
|
|
238
|
-
bon query '{"measures": ["
|
|
251
|
+
bon query '{"measures": ["sales_analytics.total_revenue"], "dimensions": ["sales_analytics.status"]}'
|
|
239
252
|
|
|
240
253
|
# SQL format
|
|
241
|
-
bon query --sql "SELECT status, MEASURE(total_revenue) FROM
|
|
254
|
+
bon query --sql "SELECT status, MEASURE(total_revenue) FROM sales_analytics GROUP BY 1"
|
|
242
255
|
```
|
|
243
256
|
|
|
244
257
|
Compare the numbers with the corresponding Metabase card. If they don't match:
|
|
245
258
|
- Check the SQL in the card (`bon metabase explore card <id>`) for filters or transformations
|
|
246
259
|
- Ensure the measure type matches the aggregation (SUM vs COUNT vs AVG)
|
|
247
|
-
- Check for WHERE clauses that should be
|
|
260
|
+
- Check for WHERE clauses that should be filtered measures (not segments)
|
|
261
|
+
|
|
262
|
+
**Test with natural language too.** Set up MCP (`bon mcp`) and ask an agent
|
|
263
|
+
the same questions your Metabase dashboards answer. Check whether it picks
|
|
264
|
+
the right **view** and **measure** — most failures are description problems,
|
|
265
|
+
not data problems. See the `bonnard-design-guide` rule for Principle 6.
|
|
248
266
|
|
|
249
267
|
## Next Steps
|
|
250
268
|
|
|
251
269
|
After the core migration is working:
|
|
252
270
|
|
|
271
|
+
- **Iterate on descriptions** — test with real questions via MCP, fix agent mistakes by improving view descriptions and adding filtered measures (see `bonnard-design-guide` rule)
|
|
253
272
|
- Add remaining tables as cubes (work down the reference count list)
|
|
273
|
+
- Build audience-centric views that combine multiple cubes — match how Metabase collections organize data by business domain
|
|
254
274
|
- Add calculated measures for complex card SQL (`bon docs cubes.measures.calculated`)
|
|
255
|
-
- Add segments for common WHERE clauses (`bon docs cubes.segments`)
|
|
256
275
|
- Set up MCP for AI agent access (`bon mcp`)
|
|
257
276
|
- Review and iterate with `bon deployments` and `bon diff <id>`
|
|
@@ -48,9 +48,10 @@ bon datasource add --demo
|
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
This adds a read-only **Contoso** retail database (Postgres) with tables:
|
|
51
|
-
- `fact_sales` — transactions with sales_amount,
|
|
51
|
+
- `fact_sales` — transactions with sales_amount, total_cost, sales_quantity, return_quantity, return_amount, discount_amount, date_key, channel_key, store_key, product_key
|
|
52
52
|
- `dim_product` — product_name, brand_name, manufacturer, unit_cost, unit_price
|
|
53
|
-
- `dim_store` — store_name, store_type, employee_count, selling_area_size
|
|
53
|
+
- `dim_store` — store_name, store_type, employee_count, selling_area_size, status
|
|
54
|
+
- `dim_channel` — channel_name (values: Store, Online, Catalog, Reseller)
|
|
54
55
|
- `dim_customer` — first_name, last_name, gender, yearly_income, education, occupation
|
|
55
56
|
|
|
56
57
|
All tables are in the `contoso` schema. The datasource is named `contoso_demo`.
|
|
@@ -69,7 +70,7 @@ All tables are in the `contoso` schema. The datasource is named `contoso_demo`.
|
|
|
69
70
|
| `bon deployments` | List recent deployments (add `--all` for full history) |
|
|
70
71
|
| `bon diff <deployment-id>` | Show changes in a deployment (`--breaking` for breaking only) |
|
|
71
72
|
| `bon annotate <deployment-id>` | Add reasoning/context to deployment changes |
|
|
72
|
-
| `bon query '{...}'` |
|
|
73
|
+
| `bon query '{...}'` | Query the deployed semantic layer (requires `bon deploy` first, not for raw DB access) |
|
|
73
74
|
| `bon mcp` | Show MCP setup instructions for AI agents |
|
|
74
75
|
| `bon docs` | Browse documentation |
|
|
75
76
|
| `bon metabase connect` | Connect to a Metabase instance (API key) |
|
|
@@ -96,15 +97,19 @@ Topics follow dot notation (e.g., `cubes.dimensions.time`). Use `--recursive` to
|
|
|
96
97
|
|
|
97
98
|
## Workflow
|
|
98
99
|
|
|
99
|
-
1. **
|
|
100
|
-
2. **
|
|
101
|
-
3. **Create
|
|
102
|
-
4. **
|
|
103
|
-
5. **
|
|
104
|
-
6. **
|
|
100
|
+
1. **Start from questions** — Collect the most common questions your team asks about data. Group them by audience.
|
|
101
|
+
2. **Setup datasource** — `bon datasource add --from-dbt` or manual
|
|
102
|
+
3. **Create cubes** — Define measures/dimensions in `bonnard/cubes/*.yaml`. Add filtered measures for any metric with a WHERE clause.
|
|
103
|
+
4. **Create views** — Compose cubes in `bonnard/views/*.yaml`. Name views by audience/use case, not by table.
|
|
104
|
+
5. **Write descriptions** — Descriptions are how AI agents choose views and measures. Lead with scope, cross-reference related views, include dimension values.
|
|
105
|
+
6. **Validate** — `bon validate`
|
|
106
|
+
7. **Deploy** — `bon login` then `bon deploy -m "description of changes"`
|
|
107
|
+
8. **Test with questions** — Query via MCP with real user questions. Check the agent picks the right view and measure.
|
|
108
|
+
9. **Iterate** — Fix agent mistakes by improving descriptions and adding filtered measures. Expect 2-4 iterations.
|
|
105
109
|
|
|
106
110
|
For a guided walkthrough: `/bonnard-get-started`
|
|
107
111
|
For projects migrating from Metabase: `/bonnard-metabase-migrate`
|
|
112
|
+
For design principles: `/bonnard-design-guide`
|
|
108
113
|
|
|
109
114
|
## Deployment & Change Tracking
|
|
110
115
|
|
|
@@ -119,8 +124,20 @@ Every deploy creates a versioned deployment with change detection:
|
|
|
119
124
|
|
|
120
125
|
For CI/CD pipelines, use `bon deploy --ci -m "message"` (non-interactive, fails on issues). Datasources are always synced automatically during deploy.
|
|
121
126
|
|
|
122
|
-
##
|
|
127
|
+
## Design Principles
|
|
128
|
+
|
|
129
|
+
Summary — see `/bonnard-design-guide` for examples and details.
|
|
130
|
+
|
|
131
|
+
1. **Start from questions, not tables** — collect the 10-20 most common questions, build views that answer them
|
|
132
|
+
2. **Views are for audiences, not tables** — name by use case (`sales_pipeline`), not by table (`orders_view`)
|
|
133
|
+
3. **Add filtered measures** — if a dashboard card has a WHERE clause beyond date range, make it a filtered measure
|
|
134
|
+
4. **Descriptions are the discovery API** — lead with scope, cross-reference related views, include dimension values
|
|
135
|
+
5. **Build cross-entity views** — combine cubes when users think across tables; don't force one view per cube
|
|
136
|
+
6. **Test with natural language** — ask an agent real questions via MCP; check it picks the right view and measure
|
|
137
|
+
7. **Iterate** — expect 2-4 rounds; fix agent mistakes by improving descriptions, not data
|
|
138
|
+
|
|
139
|
+
## Technical Gotchas
|
|
123
140
|
|
|
124
141
|
- **Always set `data_source`** on cubes — without it, cubes silently use the default warehouse, which breaks when multiple warehouses are added later. `bon validate` warns about this.
|
|
125
|
-
- **
|
|
142
|
+
- **Primary keys must be unique** — Cube deduplicates on the primary key. If the column isn't unique (e.g., a date with multiple rows per day), dimension queries silently return empty results. For tables without a natural unique column, use a `sql` query with `ROW_NUMBER()` to generate a synthetic key.
|
|
126
143
|
- **Use `sql_table` with full schema path** (e.g., `schema.table_name`) for clarity.
|