@graphit/cli 0.1.16 → 0.1.19
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/commands/dashboard.js +79 -3
- package/dist/commands/dashboard.js.map +1 -1
- package/dist/commands/kb.js +32 -6
- package/dist/commands/kb.js.map +1 -1
- package/dist/commands/query.js +67 -18
- package/dist/commands/query.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/output/format.d.ts +1 -1
- package/dist/output/format.js +2 -0
- package/dist/output/format.js.map +1 -1
- package/dist/output/styled.d.ts +19 -0
- package/dist/output/styled.js +70 -0
- package/dist/output/styled.js.map +1 -0
- package/package.json +3 -1
- package/skills/graphit/SKILL.md +51 -49
- package/skills/graphit/cursor/graphit-dashboard-planning.mdc +5 -0
- package/skills/graphit/cursor/graphit-kb-traversal.mdc +7 -0
- package/skills/graphit/cursor/graphit-presentations.mdc +56 -0
- package/skills/graphit/cursor/graphit-sql-reference.mdc +35 -0
- package/skills/graphit/cursor/graphit-style.mdc +16 -0
- package/skills/graphit/graphit.mdc +64 -11
- package/skills/graphit/references/dashboard-planning.md +24 -0
- package/skills/graphit/references/governance.md +32 -4
- package/skills/graphit/references/graphit-style.md +27 -0
- package/skills/graphit/references/kb-traversal.md +65 -0
- package/skills/graphit/references/presentations.md +143 -0
- package/skills/graphit/references/sql-reference.md +108 -0
|
@@ -56,3 +56,68 @@ Use `kb_explore` with max_depth=2 and no edge_type filter. Returns the table's d
|
|
|
56
56
|
| 3 (max) | Full neighborhood. Use sparingly - can return many results for connected tables. |
|
|
57
57
|
|
|
58
58
|
Start at depth 1. Only increase if the user's question requires it or the initial results are insufficient.
|
|
59
|
+
|
|
60
|
+
## Presenting KB Results
|
|
61
|
+
|
|
62
|
+
**After `graphit kb list <type>`** - count summary + table with bold names:
|
|
63
|
+
|
|
64
|
+
~~~
|
|
65
|
+
**12 metrics** across 3 tables:
|
|
66
|
+
|
|
67
|
+
| Metric | Table | Calculation | Params |
|
|
68
|
+
|---|---|---|---|
|
|
69
|
+
| **CPI** | **MARKETING_UA** | `SUM(spend)/SUM(installs)` | - |
|
|
70
|
+
| **ARPU** | **MARKETING_UA** | `SUM(revenue)/COUNT(...)` | DAY |
|
|
71
|
+
| **RETENTION** | **PLAYER_QUALITY** | `COUNT(CASE WHEN ...)` | DAY |
|
|
72
|
+
| ... | | | |
|
|
73
|
+
|
|
74
|
+
4 parameterized (need `(DAY=N)` syntax), 8 pre-baked.
|
|
75
|
+
~~~
|
|
76
|
+
|
|
77
|
+
Adapt columns per type: dimensions include semantic type, rules include constraint count, domains include asset count.
|
|
78
|
+
|
|
79
|
+
**After `graphit kb get <type> <name>`** - entity heading + key-value table:
|
|
80
|
+
|
|
81
|
+
~~~
|
|
82
|
+
### **CPI** (metric, verified)
|
|
83
|
+
|
|
84
|
+
*Cost per install*
|
|
85
|
+
|
|
86
|
+
| | |
|
|
87
|
+
|---:|---|
|
|
88
|
+
| **Table** | **MARKETING_UA** |
|
|
89
|
+
| **Calculation** | `SUM(spend) / SUM(installs)` |
|
|
90
|
+
| **Parameters** | none |
|
|
91
|
+
| **Topics** | ACQUISITION, SPEND |
|
|
92
|
+
| **Default dims** | MEDIA_SOURCE, CAMPAIGN_NAME |
|
|
93
|
+
~~~
|
|
94
|
+
|
|
95
|
+
Adapt fields per type. Rules: content, constraints, apply-on, override policy. Dimensions: expression, semantic type, output type.
|
|
96
|
+
|
|
97
|
+
**After `graphit kb search`** - result count + table with type column:
|
|
98
|
+
|
|
99
|
+
~~~
|
|
100
|
+
**5 results** for "revenue":
|
|
101
|
+
|
|
102
|
+
| Type | Name | Description |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| metric | **TOTAL_REVENUE** | Total revenue across all channels |
|
|
105
|
+
| dimension | **REVENUE_BUCKET** | Revenue range segmentation |
|
|
106
|
+
| synonym | GMV | Maps to **TOTAL_REVENUE** (metric) |
|
|
107
|
+
~~~
|
|
108
|
+
|
|
109
|
+
**After `graphit kb explore`** - tree with bold names, indented by level:
|
|
110
|
+
|
|
111
|
+
~~~
|
|
112
|
+
**CPI** (metric) on **MARKETING_UA**:
|
|
113
|
+
- **Calculation:** `SUM(spend) / SUM(installs)`
|
|
114
|
+
- **Tables:** **MARKETING_UA**, **MARKETING_UA_6MO** (secondary)
|
|
115
|
+
- **Related dimensions (8):**
|
|
116
|
+
- **MEDIA_SOURCE** - `media_source` (categorical)
|
|
117
|
+
- **CAMPAIGN_NAME** - `campaign_name` (categorical)
|
|
118
|
+
- ...
|
|
119
|
+
- **Rules:** **EXCLUDE_ORGANIC** (filters organic installs)
|
|
120
|
+
- **Domain:** MARKETING
|
|
121
|
+
~~~
|
|
122
|
+
|
|
123
|
+
For domain exploration, show Domain > Table > Asset hierarchy as a tree.
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Presentations
|
|
2
|
+
|
|
3
|
+
Build full-screen slide deck presentations inside Graphit canvas dashboards using `graphit.presentation()`.
|
|
4
|
+
|
|
5
|
+
## API
|
|
6
|
+
|
|
7
|
+
```js
|
|
8
|
+
var deck = graphit.presentation('#my-deck');
|
|
9
|
+
|
|
10
|
+
deck.slide({ bg: 'dark', layout: 'center', html: '<h1>Title</h1><p>Subtitle</p>' });
|
|
11
|
+
deck.slide({ bg: 'white', layout: 'split', html: '<div>Left panel</div><div>Right panel</div>' });
|
|
12
|
+
deck.slide({ bg: 'paper', layout: 'full', html: '<div id="charts">...</div>' });
|
|
13
|
+
|
|
14
|
+
var ctrl = deck.start();
|
|
15
|
+
// ctrl.go(3) - jump to slide 4
|
|
16
|
+
// ctrl.total - number of slides
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`graphit.presentation(el)` returns a **builder**. Call `.slide()` to add slides (chainable), then `.start()` to render and wire navigation. `start()` returns a controller with `go(n)` and `total`.
|
|
20
|
+
|
|
21
|
+
## Slide Config
|
|
22
|
+
|
|
23
|
+
| Field | Type | Default | Description |
|
|
24
|
+
|-------|------|---------|-------------|
|
|
25
|
+
| `layout` | `'center'` / `'split'` / `'full'` | `'center'` | How content is arranged inside the slide |
|
|
26
|
+
| `bg` | `'paper'` / `'white'` / `'dark'` / `'teal'` / hex | `'paper'` | Fixed background color (not theme-aware) |
|
|
27
|
+
| `html` | `string` | `''` | Raw HTML content for the slide |
|
|
28
|
+
|
|
29
|
+
## Layouts
|
|
30
|
+
|
|
31
|
+
### `center` (default)
|
|
32
|
+
Content is centered horizontally and vertically. Use for title slides, quotes, big stats, CTAs.
|
|
33
|
+
|
|
34
|
+
### `split`
|
|
35
|
+
The first two child elements in `html` become left and right panels (50/50 flex). Use for text + image, comparison, side-by-side content. On mobile (<700px), panels stack vertically.
|
|
36
|
+
|
|
37
|
+
```js
|
|
38
|
+
deck.slide({
|
|
39
|
+
layout: 'split',
|
|
40
|
+
bg: 'white',
|
|
41
|
+
html: '<div><h2>Left Title</h2><p>Text content</p></div><div><img src="data:..."></div>'
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### `full`
|
|
46
|
+
Content flows top-to-bottom with generous padding. Use for tables, grids, live data dashboards, complex layouts that need the full slide width.
|
|
47
|
+
|
|
48
|
+
## Background Themes
|
|
49
|
+
|
|
50
|
+
Backgrounds are **fixed colors** - they do not change with the app's light/dark mode. A dark slide stays dark. Content inside CAN use `var(--graphit-*)` tokens.
|
|
51
|
+
|
|
52
|
+
| Name | Background | Text Color |
|
|
53
|
+
|------|-----------|------------|
|
|
54
|
+
| `paper` | `#F7F6F2` (warm cream) | `#222224` (charcoal) |
|
|
55
|
+
| `white` | `#FFFFFF` | `#222224` |
|
|
56
|
+
| `dark` | `#222224` (charcoal) | `#F7F6F2` (cream) |
|
|
57
|
+
| `teal` | `#4DB6AC` (brand teal) | `#FFFFFF` |
|
|
58
|
+
| hex string | Custom (e.g. `#1a1a2e`) | Inherited from content HTML |
|
|
59
|
+
|
|
60
|
+
## Navigation
|
|
61
|
+
|
|
62
|
+
The slide deck includes built-in navigation:
|
|
63
|
+
- **Arrow keys**: Left/Right to navigate (guarded - skips when focus is on input/textarea)
|
|
64
|
+
- **Spacebar**: Next slide
|
|
65
|
+
- **Home/End**: First/last slide
|
|
66
|
+
- **Prev/Next buttons**: Bottom-right corner with slide counter
|
|
67
|
+
|
|
68
|
+
## Live Data Inside Slides
|
|
69
|
+
|
|
70
|
+
`graphit.resolve()` and `graphit.chart/table/kpi` work inside slides. All resolve calls fire on page load (cached queries are fast). Charts render into their target elements regardless of which slide is visible.
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
// Slide with live entities
|
|
74
|
+
deck.slide({
|
|
75
|
+
layout: 'full',
|
|
76
|
+
bg: 'white',
|
|
77
|
+
html: `
|
|
78
|
+
<h2>Live Data</h2>
|
|
79
|
+
<div data-graphit-id="spend-chart" data-graphit-label="Ad Spend"
|
|
80
|
+
data-graphit-kb="metric:TOTAL_AD_SPEND,table:MARKETING_UA"
|
|
81
|
+
data-graphit-sql="SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) AS spend FROM MARKETING_UA_DS GROUP BY 1 ORDER BY spend DESC LIMIT 6"
|
|
82
|
+
data-graphit-ds="ds_abc123">
|
|
83
|
+
<div id="chart1" class="gh-loading">
|
|
84
|
+
<div class="gh-loading-overlay"><svg class="gh-loading-spin" width="24" height="24" viewBox="0 0 24 24" fill="none"><circle cx="12" cy="12" r="10" stroke="#e5e5e5" stroke-width="2.5"/><path d="M12 2a10 10 0 0 1 10 10" stroke="#4DB6AC" stroke-width="2.5" stroke-linecap="round"/></svg></div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
`
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
// After deck.start(), fire resolve calls
|
|
91
|
+
deck.start();
|
|
92
|
+
|
|
93
|
+
graphit.resolve({
|
|
94
|
+
sql: "SELECT MEDIA_SOURCE, SUM(APPSFLYER_COST) AS spend FROM MARKETING_UA_DS GROUP BY 1 ORDER BY spend DESC LIMIT 6",
|
|
95
|
+
dataSourceId: "ds_abc123",
|
|
96
|
+
target: "#chart1"
|
|
97
|
+
}).then(function(r) {
|
|
98
|
+
graphit.chart("#chart1", { type: "bar", data: r.data, x: "MEDIA_SOURCE", y: "spend", valueFormat: "currency" });
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Entity Wrapping
|
|
103
|
+
|
|
104
|
+
Every data element inside slides should have full `data-graphit-*` entity wrapping, same as regular dashboards. The entity provenance panel, @ mentions, and click-to-inspect all work inside slides.
|
|
105
|
+
|
|
106
|
+
## Example: Complete Pitch Deck
|
|
107
|
+
|
|
108
|
+
```js
|
|
109
|
+
var deck = graphit.presentation('#deck');
|
|
110
|
+
|
|
111
|
+
deck.slide({
|
|
112
|
+
bg: 'paper',
|
|
113
|
+
layout: 'center',
|
|
114
|
+
html: '<h1 style="font-size:6rem;font-weight:300">Company Update</h1><p style="font-size:1.5rem;color:#6B7280;margin-top:16px">Q2 2026</p>'
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
deck.slide({
|
|
118
|
+
bg: 'dark',
|
|
119
|
+
layout: 'split',
|
|
120
|
+
html: '<div style="color:#F7F6F2"><h2 style="font-size:3rem">The Problem</h2><p style="font-size:1.25rem;opacity:0.7;margin-top:16px">Analysts spend 80% of their time assembling dashboards instead of finding insights.</p></div><div style="display:flex;align-items:center;justify-content:center"><img src="data:image/webp;base64,..." style="max-height:60vh;border-radius:16px"></div>'
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
deck.slide({
|
|
124
|
+
bg: 'white',
|
|
125
|
+
layout: 'full',
|
|
126
|
+
html: '<h2 style="font-size:2.5rem;margin-bottom:24px">Key Metrics</h2><div style="display:grid;grid-template-columns:repeat(3,1fr);gap:16px">...</div>'
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
deck.slide({
|
|
130
|
+
bg: 'teal',
|
|
131
|
+
layout: 'center',
|
|
132
|
+
html: '<h2 style="font-size:4rem;color:#fff">Thank You</h2><p style="font-size:1.25rem;color:rgba(255,255,255,0.7);margin-top:16px">company.com</p>'
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
deck.start();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Anti-Patterns
|
|
139
|
+
|
|
140
|
+
- **Don't put too much content on one slide.** If it scrolls, split into multiple slides.
|
|
141
|
+
- **Don't use external fonts via @import or link.** The iframe CSP blocks them. Embed as base64 @font-face or use system fonts.
|
|
142
|
+
- **Don't use external images.** Use data URIs (base64) or inline SVG.
|
|
143
|
+
- **Don't nest presentations.** One `graphit.presentation()` per dashboard.
|
|
@@ -111,6 +111,26 @@ SELECT UNNEST(generate_series(
|
|
|
111
111
|
|
|
112
112
|
The outer SELECT can ONLY reference columns the subquery exposes (its aliases). Base-table columns aggregated away in the subquery do NOT exist at the outer level.
|
|
113
113
|
|
|
114
|
+
## Cache-Friendly SQL (Canvas Resolve)
|
|
115
|
+
|
|
116
|
+
Canvas `graphit.resolve()` queries that follow these shapes serve from a semantic cache in ~10ms on filter changes instead of a full DuckDB recompute (5-37s on wide data sources). Write resolve SQL in this style by default.
|
|
117
|
+
|
|
118
|
+
**Shape rules:**
|
|
119
|
+
- Single table (no JOIN/UNION)
|
|
120
|
+
- WHERE as flat AND of `column = literal`, `column IN (...)`, `column BETWEEN ... AND ...` conjuncts
|
|
121
|
+
- Bare aggregates only: `SUM(col)`, `COUNT(*)`, `MIN(col)`, `MAX(col)` - no wrapping functions (`ROUND(SUM(x))`), no aggregate arithmetic (`SUM(a)/NULLIF(SUM(b),0)`), no `AVG` (v2)
|
|
122
|
+
- Literal dates (`>= '2026-01-01'`), never `CURRENT_DATE` expressions
|
|
123
|
+
- GROUP BY column names or ordinals; ORDER BY / LIMIT allowed (outer only)
|
|
124
|
+
- CTEs are fine when the CTE body follows the same rules
|
|
125
|
+
- Top-N rank queries: project the sort metric in SELECT (`SELECT dim, SUM(metric) AS rv ... ORDER BY rv DESC LIMIT N`), not only in ORDER BY
|
|
126
|
+
|
|
127
|
+
**Shapes that skip the cache** (fall back to normal execution, still correct):
|
|
128
|
+
- `COUNT(DISTINCT x)`, window functions, HAVING, QUALIFY
|
|
129
|
+
- OR / NOT in WHERE
|
|
130
|
+
- Ratio metrics (`SUM(a)/NULLIF(SUM(b),0)`) - compute client-side or use two resolves
|
|
131
|
+
- `CURRENT_DATE`-relative predicates
|
|
132
|
+
- Top-N with aggregate only in ORDER BY (`SELECT dim FROM t GROUP BY dim ORDER BY SUM(metric) DESC LIMIT N` - no decomposable aggregate in SELECT)
|
|
133
|
+
|
|
114
134
|
## Data Source Routing
|
|
115
135
|
|
|
116
136
|
| Situation | Command | Speed |
|
|
@@ -119,3 +139,91 @@ The outer SELECT can ONLY reference columns the subquery exposes (its aliases).
|
|
|
119
139
|
| No data source | `graphit query "SQL" --warehouse --connection <id>` | ~10s, Snowflake |
|
|
120
140
|
|
|
121
141
|
Always prefer cached data sources. Check with `graphit ds list`. If no data source covers the table, suggest creating one for future speed.
|
|
142
|
+
|
|
143
|
+
## Presenting Query Results
|
|
144
|
+
|
|
145
|
+
After every `graphit query`, present results grounded in the KB. Always show which KB assets were used - this is what makes governed queries valuable.
|
|
146
|
+
|
|
147
|
+
**When using KB reference syntax** (`{{metric:X}}`, `{{dim:X}}`), show all five sections:
|
|
148
|
+
|
|
149
|
+
~~~
|
|
150
|
+
**KB Assets:** dimension **CAMPAIGN_CATEGORY**, metric **TOTAL_INSTALLS**, metric **CPI**, table **MARKETING_UA_DS**
|
|
151
|
+
|
|
152
|
+
**Query:**
|
|
153
|
+
```sql
|
|
154
|
+
SELECT
|
|
155
|
+
{{dim:CAMPAIGN_CATEGORY}} AS category,
|
|
156
|
+
{{metric:TOTAL_INSTALLS}} AS installs,
|
|
157
|
+
{{metric:CPI}} AS cpi
|
|
158
|
+
FROM MARKETING_UA_DS
|
|
159
|
+
WHERE ACTIVITY_TIME >= '2026-01-01'
|
|
160
|
+
GROUP BY {{dim:CAMPAIGN_CATEGORY}}
|
|
161
|
+
ORDER BY installs DESC
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Resolved SQL:**
|
|
165
|
+
```sql
|
|
166
|
+
SELECT
|
|
167
|
+
CASE WHEN IS_RETARGETING THEN 'Retargeting'
|
|
168
|
+
WHEN IS_CTV THEN 'Connected TV'
|
|
169
|
+
ELSE 'User Acquisition' END AS category,
|
|
170
|
+
SUM(INSTALLS) AS installs,
|
|
171
|
+
SUM(APPSFLYER_COST) / NULLIF(SUM(INSTALLS), 0) AS cpi
|
|
172
|
+
FROM MARKETING_UA_DS
|
|
173
|
+
WHERE ACTIVITY_TIME >= '2026-01-01'
|
|
174
|
+
GROUP BY 1
|
|
175
|
+
ORDER BY installs DESC
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Results** (3 rows via **ds_abc123**):
|
|
179
|
+
|
|
180
|
+
| Category | Installs | CPI |
|
|
181
|
+
|---|---:|---:|
|
|
182
|
+
| User Acquisition | 80,605 | $0.79 |
|
|
183
|
+
| Retargeting | 12,300 | $1.24 |
|
|
184
|
+
| Connected TV | 3,100 | $2.80 |
|
|
185
|
+
|
|
186
|
+
**Governance:** governed - 3 KB refs, 2 rules enforced (**EXCLUDE_ORGANIC**, **MIN_SPEND**). Max rows: 1,000.
|
|
187
|
+
~~~
|
|
188
|
+
|
|
189
|
+
**When using inline SQL** (no `{{metric:X}}`), show query + results + governance:
|
|
190
|
+
|
|
191
|
+
~~~
|
|
192
|
+
**Query:**
|
|
193
|
+
```sql
|
|
194
|
+
SELECT media_source, SUM(spend) AS spend FROM MARKETING_UA_DS GROUP BY 1
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
**Results** (6 rows via **ds_abc123**):
|
|
198
|
+
|
|
199
|
+
| Media Source | Spend |
|
|
200
|
+
|---|---:|
|
|
201
|
+
| Facebook | $42,100 |
|
|
202
|
+
| Google UAC | $38,500 |
|
|
203
|
+
|
|
204
|
+
**Governance:** ad-hoc - 0 KB refs. Consider using `{{metric:TOTAL_SPEND}}` for governed tier.
|
|
205
|
+
~~~
|
|
206
|
+
|
|
207
|
+
For ad-hoc queries, suggest the KB reference equivalent when one exists. This nudges users toward governed queries.
|
|
208
|
+
|
|
209
|
+
**Always use `--verbose`** to get the resolved SQL. If the user didn't pass it, re-run with `--verbose` so you can show both the reference query and the expanded SQL.
|
|
210
|
+
|
|
211
|
+
Zero rows: explain what you checked and hypothesize why (wrong date range, filter too strict, table empty).
|
|
212
|
+
|
|
213
|
+
## Presenting Data Source Results
|
|
214
|
+
|
|
215
|
+
After `graphit ds list`:
|
|
216
|
+
|
|
217
|
+
~~~
|
|
218
|
+
**3 data sources:**
|
|
219
|
+
|
|
220
|
+
| Name | ID | Rows | Status | Governed |
|
|
221
|
+
|---|---|---:|---|---|
|
|
222
|
+
| **MARKETING_UA_DS** | ds_abc123 | 1,247,832 | active | yes |
|
|
223
|
+
| **PLAYER_QUALITY** | ds_def456 | 892,104 | active | no |
|
|
224
|
+
| **REVENUE_EVENTS** | ds_ghi789 | 3,412,006 | stale | yes |
|
|
225
|
+
|
|
226
|
+
Using **MARKETING_UA_DS** (ds_abc123) which covers spend, installs, and ROAS columns.
|
|
227
|
+
~~~
|
|
228
|
+
|
|
229
|
+
End with a recommendation of which DS to use, or note if none covers the needed table.
|