@bonnard/cli 0.2.15 → 0.3.0

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.
@@ -0,0 +1,95 @@
1
+ # SDK
2
+
3
+ > Build data apps and dashboards on top of your semantic layer.
4
+
5
+ The Bonnard SDK (`@bonnard/sdk`) is a lightweight TypeScript/JavaScript client for querying your deployed semantic layer. Zero dependencies, ~3KB minified — works in Node.js, browsers, and edge runtimes.
6
+
7
+ ## Two ways to use it
8
+
9
+ ### npm (TypeScript / Node.js / React)
10
+
11
+ ```bash
12
+ npm install @bonnard/sdk
13
+ ```
14
+
15
+ ```typescript
16
+ import { createClient } from '@bonnard/sdk';
17
+
18
+ const bon = createClient({ apiKey: 'bon_pk_...' });
19
+ const { data } = await bon.query({
20
+ measures: ['orders.revenue'],
21
+ dimensions: ['orders.city'],
22
+ });
23
+ ```
24
+
25
+ ### CDN (browser / HTML dashboards)
26
+
27
+ ```html
28
+ <script src="https://cdn.jsdelivr.net/npm/@bonnard/sdk/dist/bonnard.iife.js"></script>
29
+ <script>
30
+ const bon = Bonnard.createClient({ apiKey: 'bon_pk_...' });
31
+ </script>
32
+ ```
33
+
34
+ Exposes `window.Bonnard` with `createClient` and `toCubeQuery`. See [sdk.browser](sdk.browser) for the full browser quickstart.
35
+
36
+ ## Authentication
37
+
38
+ Two modes depending on your use case:
39
+
40
+ | Mode | Key type | Use case |
41
+ |------|----------|----------|
42
+ | **Publishable key** | `bon_pk_...` | Public dashboards, client-side apps — safe to expose in HTML |
43
+ | **Token exchange** | `bon_sk_...` → JWT | Multi-tenant / embedded analytics — server exchanges secret key for scoped token |
44
+
45
+ ```typescript
46
+ // Simple: publishable key
47
+ const bon = createClient({ apiKey: 'bon_pk_...' });
48
+
49
+ // Multi-tenant: token exchange
50
+ const bon = createClient({
51
+ fetchToken: async () => {
52
+ const res = await fetch('/my-backend/bonnard-token');
53
+ const { token } = await res.json();
54
+ return token;
55
+ },
56
+ });
57
+ ```
58
+
59
+ The SDK automatically caches tokens and refreshes them 60 seconds before expiry.
60
+
61
+ See [sdk.authentication](sdk.authentication) for the full auth guide.
62
+
63
+ ## What you can query
64
+
65
+ | Method | Purpose |
66
+ |--------|---------|
67
+ | `query()` | JSON query — measures, dimensions, filters, time dimensions |
68
+ | `rawQuery()` | Pass a Cube-native query object directly |
69
+ | `sql()` | SQL with `MEASURE()` syntax |
70
+ | `explore()` | Discover available views, measures, and dimensions |
71
+
72
+ See [sdk.query-reference](sdk.query-reference) for the full API reference.
73
+
74
+ ## Building HTML dashboards
75
+
76
+ The SDK pairs with any chart library for single-file HTML dashboards. No build step required — load both from CDN and start querying.
77
+
78
+ | Library | CDN size (gzip) | Best for |
79
+ |---------|----------------|----------|
80
+ | **Chart.js** | ~65 KB | Default choice — most LLM training data, smallest payload |
81
+ | **ECharts** | ~160 KB | Rich interactivity, built-in dark theme |
82
+ | **ApexCharts** | ~130 KB | Best defaults out of box, SVG rendering |
83
+
84
+ Each guide includes a complete, copy-pasteable HTML starter template:
85
+
86
+ - [sdk.chartjs](sdk.chartjs) — Chart.js + SDK
87
+ - [sdk.echarts](sdk.echarts) — ECharts + SDK
88
+ - [sdk.apexcharts](sdk.apexcharts) — ApexCharts + SDK
89
+
90
+ ## See also
91
+
92
+ - [sdk.browser](sdk.browser) — Browser / CDN quickstart
93
+ - [sdk.query-reference](sdk.query-reference) — Full query API reference
94
+ - [sdk.authentication](sdk.authentication) — Auth patterns
95
+ - [security-context](security-context) — Row-level security for multi-tenant apps
@@ -0,0 +1,307 @@
1
+ # Query API Reference
2
+
3
+ > Complete reference for querying the Bonnard semantic layer via the SDK.
4
+
5
+ ## query()
6
+
7
+ Execute a JSON query against the semantic layer. All field names must be fully qualified (e.g. `orders.revenue`, not `revenue`).
8
+
9
+ ```javascript
10
+ const { data } = await bon.query({
11
+ measures: ['orders.revenue', 'orders.count'],
12
+ dimensions: ['orders.city'],
13
+ filters: [
14
+ { dimension: 'orders.status', operator: 'equals', values: ['completed'] },
15
+ ],
16
+ timeDimension: {
17
+ dimension: 'orders.created_at',
18
+ granularity: 'month',
19
+ dateRange: ['2025-01-01', '2025-12-31'],
20
+ },
21
+ orderBy: { 'orders.revenue': 'desc' },
22
+ limit: 10,
23
+ });
24
+ ```
25
+
26
+ ### Parameters
27
+
28
+ | Parameter | Type | Required | Description |
29
+ |-----------|------|----------|-------------|
30
+ | `measures` | `string[]` | No | Numeric aggregations to compute (e.g. `['orders.revenue']`) |
31
+ | `dimensions` | `string[]` | No | Group-by columns (e.g. `['orders.city']`) |
32
+ | `filters` | `Filter[]` | No | Row-level filters |
33
+ | `timeDimension` | `TimeDimension` | No | Time-based grouping and date range |
34
+ | `orderBy` | `Record<string, 'asc' \| 'desc'>` | No | Sort order |
35
+ | `limit` | `number` | No | Max rows to return |
36
+
37
+ At least one of `measures` or `dimensions` is required.
38
+
39
+ ### Response
40
+
41
+ ```javascript
42
+ {
43
+ data: [
44
+ { "orders.revenue": 125000, "orders.count": 340, "orders.city": "Berlin" },
45
+ { "orders.revenue": 98000, "orders.count": 280, "orders.city": "Munich" },
46
+ ],
47
+ annotation: {
48
+ measures: {
49
+ "orders.revenue": { title: "Revenue", type: "number" },
50
+ "orders.count": { title: "Count", type: "number" },
51
+ },
52
+ dimensions: {
53
+ "orders.city": { title: "City", type: "string" },
54
+ },
55
+ }
56
+ }
57
+ ```
58
+
59
+ - `data` — array of result rows, keyed by fully qualified field names
60
+ - `annotation` — optional metadata with field titles and types
61
+
62
+ ## Filters
63
+
64
+ ```javascript
65
+ filters: [
66
+ { dimension: 'orders.status', operator: 'equals', values: ['completed'] },
67
+ { dimension: 'orders.revenue', operator: 'gt', values: [1000] },
68
+ ]
69
+ ```
70
+
71
+ ### Filter operators
72
+
73
+ | Operator | Description | Example values |
74
+ |----------|-------------|---------------|
75
+ | `equals` | Exact match (any of values) | `['completed', 'shipped']` |
76
+ | `notEquals` | Exclude matches | `['cancelled']` |
77
+ | `contains` | Substring match | `['berlin']` |
78
+ | `gt` | Greater than | `[1000]` |
79
+ | `gte` | Greater than or equal | `[1000]` |
80
+ | `lt` | Less than | `[100]` |
81
+ | `lte` | Less than or equal | `[100]` |
82
+
83
+ ## Time dimensions
84
+
85
+ Group data by time periods and filter by date range.
86
+
87
+ ```javascript
88
+ timeDimension: {
89
+ dimension: 'orders.created_at',
90
+ granularity: 'month',
91
+ dateRange: ['2025-01-01', '2025-06-30'],
92
+ }
93
+ ```
94
+
95
+ ### Granularities
96
+
97
+ | Granularity | Groups by |
98
+ |-------------|-----------|
99
+ | `day` | Calendar day |
100
+ | `week` | ISO week (Monday start) |
101
+ | `month` | Calendar month |
102
+ | `quarter` | Calendar quarter |
103
+ | `year` | Calendar year |
104
+
105
+ Omit `granularity` to filter by date range without time grouping.
106
+
107
+ ### Date range formats
108
+
109
+ ```javascript
110
+ // ISO date strings (tuple)
111
+ dateRange: ['2025-01-01', '2025-12-31']
112
+
113
+ // Single string (relative)
114
+ dateRange: 'last 30 days'
115
+ dateRange: 'last 6 months'
116
+ dateRange: 'this year'
117
+ dateRange: 'last year'
118
+ dateRange: 'today'
119
+ dateRange: 'yesterday'
120
+ dateRange: 'last week'
121
+ dateRange: 'last month'
122
+ dateRange: 'last quarter'
123
+ ```
124
+
125
+ ## Ordering and pagination
126
+
127
+ ```javascript
128
+ // Sort by revenue descending
129
+ orderBy: { 'orders.revenue': 'desc' }
130
+
131
+ // Multiple sort keys
132
+ orderBy: { 'orders.city': 'asc', 'orders.revenue': 'desc' }
133
+
134
+ // Limit results
135
+ limit: 10
136
+ ```
137
+
138
+ ## rawQuery()
139
+
140
+ Pass a Cube-native query object directly, bypassing the SDK's query format conversion. Use when you need features not exposed by `query()` (e.g. segments, offset).
141
+
142
+ ```javascript
143
+ const { data } = await bon.rawQuery({
144
+ measures: ['orders.revenue'],
145
+ dimensions: ['orders.city'],
146
+ order: [['orders.revenue', 'desc']],
147
+ limit: 10,
148
+ offset: 20,
149
+ });
150
+ ```
151
+
152
+ ## sql()
153
+
154
+ Execute a SQL query using Cube's SQL API. Use `MEASURE()` to reference semantic layer measures.
155
+
156
+ ```javascript
157
+ const { data } = await bon.sql(
158
+ `SELECT city, MEASURE(revenue), MEASURE(count)
159
+ FROM orders
160
+ WHERE status = 'completed'
161
+ GROUP BY 1
162
+ ORDER BY 2 DESC
163
+ LIMIT 10`
164
+ );
165
+ ```
166
+
167
+ Response includes an optional schema:
168
+
169
+ ```javascript
170
+ {
171
+ data: [
172
+ { city: "Berlin", revenue: 125000, count: 340 },
173
+ ],
174
+ schema: [
175
+ { name: "city", type: "string" },
176
+ { name: "revenue", type: "number" },
177
+ { name: "count", type: "number" },
178
+ ]
179
+ }
180
+ ```
181
+
182
+ ## explore()
183
+
184
+ Discover available views, measures, dimensions, and segments.
185
+
186
+ ```javascript
187
+ const meta = await bon.explore();
188
+
189
+ for (const view of meta.cubes) {
190
+ console.log(`${view.name}: ${view.description || ''}`);
191
+ for (const m of view.measures) {
192
+ console.log(` measure: ${m.name} (${m.type})`);
193
+ }
194
+ for (const d of view.dimensions) {
195
+ console.log(` dimension: ${d.name} (${d.type})`);
196
+ }
197
+ }
198
+ ```
199
+
200
+ By default returns only views (`viewsOnly: true`). To include underlying cubes:
201
+
202
+ ```javascript
203
+ const meta = await bon.explore({ viewsOnly: false });
204
+ ```
205
+
206
+ ### Response shape
207
+
208
+ ```javascript
209
+ {
210
+ cubes: [
211
+ {
212
+ name: "orders",
213
+ title: "Orders",
214
+ description: "Customer orders",
215
+ type: "view",
216
+ measures: [
217
+ { name: "orders.revenue", title: "Revenue", type: "number" },
218
+ { name: "orders.count", title: "Count", type: "number" },
219
+ ],
220
+ dimensions: [
221
+ { name: "orders.city", title: "City", type: "string" },
222
+ { name: "orders.created_at", title: "Created At", type: "time" },
223
+ ],
224
+ segments: [],
225
+ }
226
+ ]
227
+ }
228
+ ```
229
+
230
+ ## toCubeQuery()
231
+
232
+ Utility function that converts SDK `QueryOptions` into a Cube-native query object. Useful for debugging or when you need to inspect the query before sending.
233
+
234
+ ```javascript
235
+ import { toCubeQuery } from '@bonnard/sdk';
236
+ // or in browser: Bonnard.toCubeQuery(...)
237
+
238
+ const cubeQuery = Bonnard.toCubeQuery({
239
+ measures: ['orders.revenue'],
240
+ dimensions: ['orders.city'],
241
+ orderBy: { 'orders.revenue': 'desc' },
242
+ });
243
+
244
+ console.log(JSON.stringify(cubeQuery, null, 2));
245
+ // {
246
+ // "measures": ["orders.revenue"],
247
+ // "dimensions": ["orders.city"],
248
+ // "order": [["orders.revenue", "desc"]]
249
+ // }
250
+ ```
251
+
252
+ ## Common patterns
253
+
254
+ ### KPI query (single row, no dimensions)
255
+
256
+ ```javascript
257
+ const { data } = await bon.query({
258
+ measures: ['orders.revenue', 'orders.count', 'orders.avg_value'],
259
+ });
260
+ const kpis = data[0];
261
+ // { "orders.revenue": 1250000, "orders.count": 3400, "orders.avg_value": 367.6 }
262
+ ```
263
+
264
+ ### Top N with dimension
265
+
266
+ ```javascript
267
+ const { data } = await bon.query({
268
+ measures: ['orders.revenue'],
269
+ dimensions: ['orders.city'],
270
+ orderBy: { 'orders.revenue': 'desc' },
271
+ limit: 10,
272
+ });
273
+ ```
274
+
275
+ ### Time series
276
+
277
+ ```javascript
278
+ const { data } = await bon.query({
279
+ measures: ['orders.revenue'],
280
+ timeDimension: {
281
+ dimension: 'orders.created_at',
282
+ granularity: 'month',
283
+ dateRange: 'last 12 months',
284
+ },
285
+ });
286
+ // data[0]["orders.created_at"] is an ISO date string like "2025-01-01T00:00:00.000"
287
+ ```
288
+
289
+ ### Filtered breakdown
290
+
291
+ ```javascript
292
+ const { data } = await bon.query({
293
+ measures: ['orders.revenue'],
294
+ dimensions: ['orders.product_category'],
295
+ filters: [
296
+ { dimension: 'orders.city', operator: 'equals', values: ['Berlin'] },
297
+ { dimension: 'orders.revenue', operator: 'gt', values: [100] },
298
+ ],
299
+ orderBy: { 'orders.revenue': 'desc' },
300
+ });
301
+ ```
302
+
303
+ ## See also
304
+
305
+ - [sdk.browser](sdk.browser) — Browser / CDN quickstart
306
+ - [sdk.authentication](sdk.authentication) — Auth patterns
307
+ - [sdk.chartjs](sdk.chartjs) — Building dashboards with Chart.js
@@ -0,0 +1,145 @@
1
+ ---
2
+ name: bonnard-build-dashboard
3
+ description: Guide a user through building and deploying a markdown dashboard. Use when user says "build a dashboard", "create a chart", "visualize data", or wants to create a dashboard.
4
+ allowed-tools: Bash(bon *), Write, Edit, Read
5
+ ---
6
+
7
+ # Build & Deploy a Markdown Dashboard
8
+
9
+ This skill guides you through creating a markdown dashboard with built-in
10
+ chart components and deploying it to Bonnard.
11
+
12
+ ## Phase 1: Explore Available Data
13
+
14
+ Discover what measures and dimensions are available to query:
15
+
16
+ ```bash
17
+ # List all views and their fields
18
+ bon docs schema
19
+
20
+ # Query a specific view to see what data looks like
21
+ bon query '{"measures": ["view_name.measure"], "dimensions": ["view_name.dimension"], "limit": 5}'
22
+
23
+ # Or use SQL format
24
+ bon query --sql "SELECT MEASURE(total_revenue), date FROM sales_performance LIMIT 5"
25
+ ```
26
+
27
+ Ask the user what data they want to visualize. Match their request to
28
+ available views and measures.
29
+
30
+ ## Phase 2: Learn the Format
31
+
32
+ Review the dashboard format docs for reference:
33
+
34
+ ```bash
35
+ bon docs dashboards # Overview + format
36
+ bon docs dashboards.components # Chart components (BigValue, LineChart, BarChart, etc.)
37
+ bon docs dashboards.queries # Query block syntax
38
+ bon docs dashboards.inputs # Interactive filters (DateRange, Dropdown)
39
+ bon docs dashboards.examples # Complete examples
40
+ ```
41
+
42
+ ## Phase 3: Build the Markdown File
43
+
44
+ Create a `.md` file with three parts:
45
+
46
+ 1. **YAML frontmatter** — title and optional description
47
+ 2. **Query blocks** — ` ```query name ` code fences with YAML query options
48
+ 3. **Components** — `<BigValue />`, `<LineChart />`, `<BarChart />`, etc.
49
+
50
+ Key points:
51
+ - All field names must be fully qualified: `orders.total_revenue`, not `total_revenue`
52
+ - Each component references a query by name: `data={query_name}`
53
+ - Consecutive `<BigValue>` components auto-group into a row
54
+ - Use `<Grid cols="2">` to place charts side by side
55
+ - Use `<DateRange>` and `<Dropdown>` for interactive filters
56
+
57
+ Example structure:
58
+
59
+ ```markdown
60
+ ---
61
+ title: Revenue Dashboard
62
+ description: Key revenue metrics and trends
63
+ ---
64
+
65
+ ` ``query total_revenue
66
+ measures: [orders.total_revenue]
67
+ ` ``
68
+
69
+ ` ``query order_count
70
+ measures: [orders.count]
71
+ ` ``
72
+
73
+ <BigValue data={total_revenue} value="orders.total_revenue" title="Revenue" fmt="eur2" />
74
+ <BigValue data={order_count} value="orders.count" title="Orders" />
75
+
76
+ ## Trend
77
+
78
+ ` ``query monthly
79
+ measures: [orders.total_revenue]
80
+ timeDimension:
81
+ dimension: orders.created_at
82
+ granularity: month
83
+ ` ``
84
+
85
+ <LineChart data={monthly} x="orders.created_at" y="orders.total_revenue" title="Monthly Revenue" yFmt="eur" />
86
+ ```
87
+
88
+ Save the file (e.g., `dashboard.md`).
89
+
90
+ ## Phase 4: Preview Locally
91
+
92
+ Preview the dashboard with live reload:
93
+
94
+ ```bash
95
+ bon dashboard dev dashboard.md
96
+ ```
97
+
98
+ This opens a browser with the rendered dashboard. Edit the `.md` file and
99
+ the preview updates automatically. Queries run against the deployed
100
+ semantic layer using the user's credentials.
101
+
102
+ Requires `bon login` — no API key needed.
103
+
104
+ ## Phase 5: Deploy
105
+
106
+ Deploy the dashboard to Bonnard:
107
+
108
+ ```bash
109
+ bon dashboard deploy dashboard.md
110
+ ```
111
+
112
+ This will:
113
+ - Upload the markdown content
114
+ - Assign a slug (derived from filename, or use `--slug`)
115
+ - Extract the title from frontmatter
116
+ - Print the URL where the dashboard is accessible
117
+
118
+ Options:
119
+ - `--slug <slug>` — custom URL slug (default: derived from filename)
120
+ - `--title <title>` — override frontmatter title
121
+
122
+ ## Phase 6: View Live
123
+
124
+ Open the deployed dashboard in the browser:
125
+
126
+ ```bash
127
+ bon dashboard open dashboard
128
+ ```
129
+
130
+ ## Iteration
131
+
132
+ To update, edit the `.md` file and redeploy:
133
+
134
+ ```bash
135
+ bon dashboard deploy dashboard.md
136
+ ```
137
+
138
+ Each deploy increments the version. Use `bon dashboard list` to see all
139
+ deployed dashboards with their versions and URLs.
140
+
141
+ To remove a dashboard:
142
+
143
+ ```bash
144
+ bon dashboard remove dashboard
145
+ ```
@@ -30,7 +30,7 @@ bon datasource add --name my_warehouse --type postgres \
30
30
  bon datasource add
31
31
  ```
32
32
 
33
- Supported types: `postgres`, `redshift`, `snowflake`, `bigquery`, `databricks`.
33
+ Supported types: `postgres`, `redshift`, `snowflake`, `bigquery`, `databricks`, `duckdb`.
34
34
 
35
35
  The demo option adds a read-only Contoso retail dataset with tables like
36
36
  `fact_sales`, `dim_product`, `dim_store`, and `dim_customer`.
@@ -65,7 +65,7 @@ bon datasource add --from-dbt
65
65
  bon datasource add
66
66
  ```
67
67
 
68
- Supported types: `postgres`, `redshift`, `snowflake`, `bigquery`, `databricks`.
68
+ Supported types: `postgres`, `redshift`, `snowflake`, `bigquery`, `databricks`, `duckdb`.
69
69
 
70
70
  The connection will be tested automatically during `bon deploy`.
71
71
 
@@ -0,0 +1,144 @@
1
+ ---
2
+ description: "Guide for building and deploying markdown dashboards with built-in chart components. Use when user says 'build a dashboard', 'create a chart', 'visualize data', or wants to create a dashboard."
3
+ alwaysApply: false
4
+ ---
5
+
6
+ # Build & Deploy a Markdown Dashboard
7
+
8
+ This guide walks through creating a markdown dashboard with built-in
9
+ chart components and deploying it to Bonnard.
10
+
11
+ ## Phase 1: Explore Available Data
12
+
13
+ Discover what measures and dimensions are available to query:
14
+
15
+ ```bash
16
+ # List all views and their fields
17
+ bon docs schema
18
+
19
+ # Query a specific view to see what data looks like
20
+ bon query '{"measures": ["view_name.measure"], "dimensions": ["view_name.dimension"], "limit": 5}'
21
+
22
+ # Or use SQL format
23
+ bon query --sql "SELECT MEASURE(total_revenue), date FROM sales_performance LIMIT 5"
24
+ ```
25
+
26
+ Ask the user what data they want to visualize. Match their request to
27
+ available views and measures.
28
+
29
+ ## Phase 2: Learn the Format
30
+
31
+ Review the dashboard format docs for reference:
32
+
33
+ ```bash
34
+ bon docs dashboards # Overview + format
35
+ bon docs dashboards.components # Chart components (BigValue, LineChart, BarChart, etc.)
36
+ bon docs dashboards.queries # Query block syntax
37
+ bon docs dashboards.inputs # Interactive filters (DateRange, Dropdown)
38
+ bon docs dashboards.examples # Complete examples
39
+ ```
40
+
41
+ ## Phase 3: Build the Markdown File
42
+
43
+ Create a `.md` file with three parts:
44
+
45
+ 1. **YAML frontmatter** — title and optional description
46
+ 2. **Query blocks** — ` ```query name ` code fences with YAML query options
47
+ 3. **Components** — `<BigValue />`, `<LineChart />`, `<BarChart />`, etc.
48
+
49
+ Key points:
50
+ - All field names must be fully qualified: `orders.total_revenue`, not `total_revenue`
51
+ - Each component references a query by name: `data={query_name}`
52
+ - Consecutive `<BigValue>` components auto-group into a row
53
+ - Use `<Grid cols="2">` to place charts side by side
54
+ - Use `<DateRange>` and `<Dropdown>` for interactive filters
55
+
56
+ Example structure:
57
+
58
+ ```markdown
59
+ ---
60
+ title: Revenue Dashboard
61
+ description: Key revenue metrics and trends
62
+ ---
63
+
64
+ ` ``query total_revenue
65
+ measures: [orders.total_revenue]
66
+ ` ``
67
+
68
+ ` ``query order_count
69
+ measures: [orders.count]
70
+ ` ``
71
+
72
+ <BigValue data={total_revenue} value="orders.total_revenue" title="Revenue" fmt="eur2" />
73
+ <BigValue data={order_count} value="orders.count" title="Orders" />
74
+
75
+ ## Trend
76
+
77
+ ` ``query monthly
78
+ measures: [orders.total_revenue]
79
+ timeDimension:
80
+ dimension: orders.created_at
81
+ granularity: month
82
+ ` ``
83
+
84
+ <LineChart data={monthly} x="orders.created_at" y="orders.total_revenue" title="Monthly Revenue" yFmt="eur" />
85
+ ```
86
+
87
+ Save the file (e.g., `dashboard.md`).
88
+
89
+ ## Phase 4: Preview Locally
90
+
91
+ Preview the dashboard with live reload:
92
+
93
+ ```bash
94
+ bon dashboard dev dashboard.md
95
+ ```
96
+
97
+ This opens a browser with the rendered dashboard. Edit the `.md` file and
98
+ the preview updates automatically. Queries run against the deployed
99
+ semantic layer using the user's credentials.
100
+
101
+ Requires `bon login` — no API key needed.
102
+
103
+ ## Phase 5: Deploy
104
+
105
+ Deploy the dashboard to Bonnard:
106
+
107
+ ```bash
108
+ bon dashboard deploy dashboard.md
109
+ ```
110
+
111
+ This will:
112
+ - Upload the markdown content
113
+ - Assign a slug (derived from filename, or use `--slug`)
114
+ - Extract the title from frontmatter
115
+ - Print the URL where the dashboard is accessible
116
+
117
+ Options:
118
+ - `--slug <slug>` — custom URL slug (default: derived from filename)
119
+ - `--title <title>` — override frontmatter title
120
+
121
+ ## Phase 6: View Live
122
+
123
+ Open the deployed dashboard in the browser:
124
+
125
+ ```bash
126
+ bon dashboard open dashboard
127
+ ```
128
+
129
+ ## Iteration
130
+
131
+ To update, edit the `.md` file and redeploy:
132
+
133
+ ```bash
134
+ bon dashboard deploy dashboard.md
135
+ ```
136
+
137
+ Each deploy increments the version. Use `bon dashboard list` to see all
138
+ deployed dashboards with their versions and URLs.
139
+
140
+ To remove a dashboard:
141
+
142
+ ```bash
143
+ bon dashboard remove dashboard
144
+ ```