@oxy-hq/cli 0.8.0 → 0.10.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.
Files changed (41) hide show
  1. package/README.md +312 -31
  2. package/dist/main.mjs +11236 -2822
  3. package/dist/main.mjs.map +1 -1
  4. package/dist/{mcp-xxWL3_dz.mjs → mcp-B2KBWNOU.mjs} +86 -3
  5. package/dist/mcp-B2KBWNOU.mjs.map +1 -0
  6. package/package.json +6 -5
  7. package/skills/oxy-agents/SKILL.md +318 -0
  8. package/skills/oxy-agents/references/quick-reference.md +222 -0
  9. package/skills/oxy-agents/templates/agentic-template.yml +141 -0
  10. package/skills/oxy-airway-pipelines/SKILL.md +223 -316
  11. package/skills/oxy-automations/SKILL.md +61 -15
  12. package/skills/oxy-cli/SKILL.md +40 -6
  13. package/skills/oxy-custom-apps/SKILL.md +380 -294
  14. package/skills/oxy-custom-apps/references/minimal-starter.md +71 -0
  15. package/skills/oxy-custom-apps/references/performance-and-usage.md +151 -0
  16. package/skills/oxy-custom-apps/references/reading-data.md +106 -0
  17. package/skills/oxy-custom-apps/references/sandbox-agent-loop.md +307 -0
  18. package/skills/oxy-custom-apps/references/verifying-in-the-browser.md +65 -0
  19. package/skills/oxy-data-apps/SKILL.md +362 -0
  20. package/skills/oxy-data-apps/references/examples.md +263 -0
  21. package/skills/oxy-data-apps/references/quick-reference.md +171 -0
  22. package/skills/oxy-data-apps/templates/dashboard-with-controls.app.yml +128 -0
  23. package/skills/oxy-data-apps/templates/simple-dashboard.app.yml +102 -0
  24. package/skills/oxy-data-architecture/SKILL.md +137 -152
  25. package/skills/oxy-evals/SKILL.md +362 -0
  26. package/skills/oxy-evals/references/diagnosis.md +243 -0
  27. package/skills/oxy-evals/references/results-json.md +145 -0
  28. package/skills/oxy-evals/references/writing-expected.md +112 -0
  29. package/skills/oxy-semantic-modeling/SKILL.md +239 -59
  30. package/skills/oxy-semantic-modeling/references/warehouse-casts.md +57 -0
  31. package/skills/oxy-semantic-modeling/templates/topic-template.yml +38 -0
  32. package/skills/oxy-semantic-modeling/templates/view-template.yml +119 -0
  33. package/template/.claude/hooks/oxyc-cloud-session.sh +66 -0
  34. package/template/.claude/settings.json +16 -0
  35. package/template/.github/workflows/publish.yaml +15 -13
  36. package/template/.nvmrc +1 -1
  37. package/template/.oxyc-managed +18 -5
  38. package/template/README.md +21 -10
  39. package/template/package.json +3 -2
  40. package/template/pnpm-workspace.yaml +28 -17
  41. package/dist/mcp-xxWL3_dz.mjs.map +0 -1
@@ -0,0 +1,171 @@
1
+ # Data App quick reference
2
+
3
+ Field-by-field reference for `.app.yml`. The why and the traps live in
4
+ [SKILL.md](../SKILL.md); this file is for looking things up.
5
+
6
+ ## Top level
7
+
8
+ | Key | Type | Required | Notes |
9
+ | --- | --- | --- | --- |
10
+ | `title` | string | no | Display name in listings. Unset → humanized filename. |
11
+ | `description` | string | no | Free text. |
12
+ | `published` | bool | no | Default `false` = hidden from the sidebar (still in the IDE). |
13
+ | `controls` | list | no | Top-level control declarations (items use `type:`). |
14
+ | `tasks` | list | **yes** (≥ 1) | Data producers. |
15
+ | `display` | list | **yes** (≥ 1) | Rendered blocks, top to bottom. |
16
+
17
+ No other key is accepted. Omit `name:` — the filename is the name, and
18
+ `oxyc validate` reports `name` as unknown.
19
+
20
+ ## Task fields (all types)
21
+
22
+ | Key | Notes |
23
+ | --- | --- |
24
+ | `name` | Unique in the app. Letter first; letters, digits, underscores. |
25
+ | `type` | `execute_sql`, `semantic_query` (use these); `workflow`, `formatter`, `conditional`, `loop_sequential`, `http_request`, `omni_query`, `looker_query` are accepted; `agent` and `airway` fail at run time. |
26
+ | `mode` | `client` (default) or `server`. Forced to `server` unless the task is inline `execute_sql` on a local DuckDB. |
27
+ | `cache` | Optional `{ enabled, path }`. Rarely needed in an app. |
28
+
29
+ ### `execute_sql`
30
+
31
+ | Key | Required | Notes |
32
+ | --- | --- | --- |
33
+ | `database` | yes | A `config.yml` database name. Checked by `oxy validate`. |
34
+ | `sql_query` | one of | Inline SQL. Jinja: `{{ controls.x }}` and friends. |
35
+ | `sql_file` | one of | Workspace-relative `.sql` path. Rendered with the same Jinja; always server-side. |
36
+ | `variables` | no | Map of string → string, available to the SQL template. |
37
+ | `dry_run_limit` | no | ≥ 1. |
38
+ | `export` | no | `{ path, format: sql\|csv\|json\|txt\|docx }` — an automation feature; not needed in apps. |
39
+
40
+ Rows returned: at most 10,000.
41
+
42
+ ### `semantic_query`
43
+
44
+ | Key | Notes |
45
+ | --- | --- |
46
+ | `topic` | Topic name from a `.topic.yml`. |
47
+ | `measures` | `[view.measure, …]` |
48
+ | `dimensions` | `[view.dimension, …]` |
49
+ | `time_dimensions` | `[{ dimension: view.field, granularity: year\|quarter\|month\|week\|day\|hour\|minute\|second }]` |
50
+ | `filters` | `[{ field, op, … }]` — see below |
51
+ | `orders` | `[{ field, direction: asc\|desc }]` |
52
+ | `limit`, `offset` | integers |
53
+ | `variables` | map |
54
+
55
+ Filter ops:
56
+
57
+ | `op` | Extra keys |
58
+ | --- | --- |
59
+ | `eq`, `neq`, `gt`, `gte`, `lt`, `lte` | `value` |
60
+ | `in`, `not_in` | `values: [...]` |
61
+ | `in_date_range`, `not_in_date_range` | `from`, `to` |
62
+
63
+ Output columns: `view__field`; with granularity, `view__field__<granularity>`.
64
+ Controls are **not** rendered into any `semantic_query` field.
65
+
66
+ ### `workflow` (runs an automation)
67
+
68
+ | Key | Notes |
69
+ | --- | --- |
70
+ | `src` | Path to an `.automation.yml` (legacy `.procedure.yml` still works). Don't point it at a `.workflow.yml` — that extension is no longer a recognised automation file. |
71
+ | `variables` | Map passed to the automation. |
72
+
73
+ Its result reaches the app as one text value, so displays cannot address its
74
+ inner steps.
75
+
76
+ ## Displays
77
+
78
+ | `type` | Required | Optional |
79
+ | --- | --- | --- |
80
+ | `markdown` | `content` | — |
81
+ | `table` | `data` | `title`, `formats: { column: currency\|percent\|number }` |
82
+ | `line_chart` | `data`, `x`, `y` | `title`, `series`, `x_axis_label`, `y_axis_label`, `y_format` |
83
+ | `bar_chart` | `data`, `x`, `y` | `title`, `series`, `y_format` |
84
+ | `pie_chart` | `data`, `name`, `value` | `title`, `value_format` |
85
+ | `row` | `children` (≥ 1 display) | `columns` (≥ 1; default = number of children) |
86
+ | `control` | `name`, `control_type` | `label`, `options` or `source`, `default` |
87
+ | `controls` | `items` (control objects using `type:`) | — |
88
+
89
+ Formats: `currency` (USD), `percent` (input already 0–100), `number`
90
+ (thousands separators). Unset → default numeric formatting.
91
+
92
+ `data:` takes a task name. `markdown` `content` is a template over task
93
+ results: `{{ task_name }}` inserts a text result; a table result renders as a
94
+ JSON code block.
95
+
96
+ ## Controls
97
+
98
+ | Field | Required | Applies to | Notes |
99
+ | --- | --- | --- | --- |
100
+ | `name` | yes | all | Read as `{{ controls.<name> }}`. |
101
+ | `control_type` | yes (inline `- type: control`) | all | `select` \| `date` \| `toggle` |
102
+ | `type` | yes (top-level `controls:` and `controls.items`) | all | same values |
103
+ | `label` | no | all | Defaults to `name`. |
104
+ | `default` | no | all | May be Jinja, rendered once at load: `"{{ now(fmt='%Y-%m-%d') }}"`. |
105
+ | `options` | no | `select` | Static list; entries may be Jinja too. |
106
+ | `source` | no | `select` | Task name; its first column fills the dropdown. Use instead of `options`. |
107
+
108
+ Value types: `select` → string, `date` → `YYYY-MM-DD` string, `toggle` →
109
+ boolean. A viewer's empty value falls back to `default`; a value for an
110
+ undeclared control is dropped.
111
+
112
+ ### Jinja the browser can run
113
+
114
+ | Form | Effect |
115
+ | --- | --- |
116
+ | `{{ controls.x }}` | raw value (single quotes doubled) |
117
+ | `{{ controls.x \| sqlquote }}` | `'value'` with embedded quotes escaped |
118
+ | `{{ controls.x \| default('v') }}` | value, or `v` when unset |
119
+ | `{% if controls.x %}…{% endif %}` | include the body when truthy |
120
+
121
+ Anything else runs on the server instead (full Jinja, `sqlquote` included).
122
+
123
+ ## SQL dialect cheatsheet
124
+
125
+ | Dialect | Month bucket | Std-dev | Notes |
126
+ | --- | --- | --- | --- |
127
+ | BigQuery | `DATE_TRUNC(col, MONTH)` | `STDDEV(col)` | backtick identifiers; `DATE_ADD(d, INTERVAL 1 DAY)` |
128
+ | Snowflake | `DATE_TRUNC('month', col)` | `STDDEV(col)` | `"col"` identifiers |
129
+ | Postgres | `DATE_TRUNC('month', col)` | `STDDEV(col)` | `x::date` works here only |
130
+ | DuckDB | `DATE_TRUNC('month', col)` | `STDDEV(col)` | `INTERVAL '1 day'` |
131
+ | ClickHouse | `toStartOfMonth(col)` | `stddevPop(col)` | functions are case-sensitive |
132
+
133
+ `CAST(x AS DATE)` is the portable cast.
134
+
135
+ ## Profiling query
136
+
137
+ Run once per candidate table before choosing a trend or a ranking. Substitute
138
+ real expressions from the table or view — never guess column names.
139
+
140
+ ```sql
141
+ SELECT
142
+ COUNT(*) AS row_count,
143
+ COUNT(DISTINCT <entity_expr>) AS entity_count,
144
+ MIN(<time_expr>) AS min_date,
145
+ MAX(<time_expr>) AS max_date,
146
+ COUNT(DISTINCT DATE_TRUNC('month', <time_expr>)) AS month_count,
147
+ MIN(<measure_expr>) AS min_val,
148
+ MAX(<measure_expr>) AS max_val,
149
+ STDDEV(<measure_expr>) AS measure_stddev
150
+ FROM <table>
151
+ ```
152
+
153
+ Worth charting when `row_count ≥ 100`, `month_count ≥ 3` (for a trend),
154
+ `measure_stddev > 0` (not a flat line), and `entity_count` between 5 and 500
155
+ (for a top-N). If the query fails, simplify once (drop `STDDEV`, swap the date
156
+ function for the dialect's); if it fails again, skip that chart.
157
+
158
+ ## Checklist
159
+
160
+ - [ ] `oxyc validate --file <app>` and `oxy validate --file <app>` pass
161
+ - [ ] No top-level `name:`; `published:` set as intended
162
+ - [ ] Task names unique; every display `data:` names a real task
163
+ - [ ] Chart columns match the task's output columns exactly (`view__field`,
164
+ `__<granularity>` for semantic queries)
165
+ - [ ] No IDs on axes or pie labels
166
+ - [ ] Inline `- type: control` blocks use `control_type:`; no controls inside a `row`
167
+ - [ ] Every `{{ controls.x }}` matches a declared control; every `source:` names a task
168
+ - [ ] Strings and dates go through `| sqlquote` with no extra quotes
169
+ - [ ] No `type: agent` or `type: airway` tasks; no `data: task.step` references
170
+ - [ ] Each task's SQL has been run once against the real database
171
+ - [ ] The app opens with every block populated, and each control re-runs its tasks
@@ -0,0 +1,128 @@
1
+ # Dashboard with interactive controls — copy, rename to <your_app>.app.yml, customize.
2
+ #
3
+ # Shows all three control kinds, a data-driven dropdown (`source:`), a row
4
+ # layout, and control values injected into SQL.
5
+ #
6
+ # As written, every task reads a CSV through a local DuckDB database, so the
7
+ # browser can re-run the SQL itself when a control changes (client mode).
8
+ # - `database: local` must be a DuckDB entry in your config.yml; `'sales.csv'`
9
+ # is a file in that database's dataset directory.
10
+ # - For a warehouse (Postgres, Snowflake, BigQuery, ClickHouse…), change
11
+ # `database:` and the FROM clauses. Those tasks run on the server
12
+ # automatically; adding `mode: server` makes that explicit.
13
+ # - Only {{ controls.x }}, {{ controls.x | sqlquote }},
14
+ # {{ controls.x | default('v') }} and {% if controls.x %}…{% endif %} run in the
15
+ # browser. Anything richer still works, on the server.
16
+
17
+ title: Sales Explorer
18
+ description: |
19
+ Sales filtered live by region, start date, and a holidays-only toggle.
20
+ Every chart and table re-runs when a control changes.
21
+ published: false # flip to true to show the app in the sidebar
22
+
23
+ tasks:
24
+ # Feeds the `region` dropdown: the first column becomes the option list.
25
+ # The 'All' sentinel row lets a viewer clear the filter.
26
+ - name: region_list
27
+ type: execute_sql
28
+ database: local
29
+ sql_query: |
30
+ SELECT region
31
+ FROM (
32
+ SELECT 'All' AS region, 0 AS sort_key
33
+ UNION ALL
34
+ SELECT DISTINCT region, 1 AS sort_key FROM 'sales.csv'
35
+ ) AS options
36
+ ORDER BY sort_key, region
37
+
38
+ - name: revenue_by_category
39
+ type: execute_sql
40
+ database: local
41
+ sql_query: |
42
+ SELECT category, SUM(revenue) AS total_revenue
43
+ FROM 'sales.csv'
44
+ WHERE sale_date >= {{ controls.start_date | sqlquote }}
45
+ AND ({{ controls.region | sqlquote }} = 'All'
46
+ OR region = {{ controls.region | sqlquote }})
47
+ {% if controls.holidays_only %}AND period = 'Holiday'{% endif %}
48
+ GROUP BY category
49
+ ORDER BY total_revenue DESC
50
+
51
+ - name: revenue_by_region
52
+ type: execute_sql
53
+ database: local
54
+ sql_query: |
55
+ SELECT region, SUM(revenue) AS total_revenue
56
+ FROM 'sales.csv'
57
+ WHERE sale_date >= {{ controls.start_date | sqlquote }}
58
+ AND ({{ controls.region | sqlquote }} = 'All'
59
+ OR region = {{ controls.region | sqlquote }})
60
+ {% if controls.holidays_only %}AND period = 'Holiday'{% endif %}
61
+ GROUP BY region
62
+ ORDER BY total_revenue DESC
63
+
64
+ - name: sales_detail
65
+ type: execute_sql
66
+ database: local
67
+ sql_query: |
68
+ SELECT region, category, period, sale_date, revenue
69
+ FROM 'sales.csv'
70
+ WHERE sale_date >= {{ controls.start_date | sqlquote }}
71
+ AND ({{ controls.region | sqlquote }} = 'All'
72
+ OR region = {{ controls.region | sqlquote }})
73
+ {% if controls.holidays_only %}AND period = 'Holiday'{% endif %}
74
+ ORDER BY sale_date DESC
75
+ LIMIT 500
76
+
77
+ display:
78
+ # Controls bar: inline `- type: control` blocks at the top level of `display:`.
79
+ # The kind is `control_type:`, NOT `type:`. Never put a control inside a `row`.
80
+ - type: control
81
+ name: region
82
+ control_type: select
83
+ label: Region
84
+ source: region_list # data-driven dropdown (instead of static `options:`)
85
+ default: All
86
+
87
+ - type: control
88
+ name: start_date
89
+ control_type: date
90
+ label: From date
91
+ default: "2024-01-01"
92
+
93
+ - type: control
94
+ name: holidays_only
95
+ control_type: toggle
96
+ label: Holidays only
97
+ default: false
98
+
99
+ - type: markdown
100
+ content: |
101
+ # Sales Explorer
102
+
103
+ Filter by **region**, **start date**, and **holiday periods** with the
104
+ controls above. Charts and tables update automatically.
105
+
106
+ # Two charts side by side
107
+ - type: row
108
+ columns: 2
109
+ children:
110
+ - type: bar_chart
111
+ title: Revenue by category
112
+ data: revenue_by_category
113
+ x: category
114
+ y: total_revenue
115
+ y_format: currency
116
+
117
+ - type: pie_chart
118
+ title: Revenue by region
119
+ data: revenue_by_region
120
+ name: region
121
+ value: total_revenue
122
+ value_format: currency
123
+
124
+ - type: table
125
+ title: Filtered sales (latest 500)
126
+ data: sales_detail
127
+ formats:
128
+ revenue: currency
@@ -0,0 +1,102 @@
1
+ # Simple dashboard — copy, rename to <your_app>.app.yml, and customize.
2
+ #
3
+ # The app's name is its filename; there is no `name:` key.
4
+ # Customize:
5
+ # - `database:` must be a database name from your config.yml
6
+ # - replace `orders`, `order_date`, `category` and `amount` with your table and columns
7
+ # - the SQL below is Postgres / DuckDB / Snowflake dialect; for BigQuery use
8
+ # DATE_TRUNC(order_date, MONTH), for ClickHouse toStartOfMonth(order_date)
9
+
10
+ title: Orders Overview
11
+ description: |
12
+ Key order metrics, the monthly trend, and a breakdown by category.
13
+ published: false # flip to true to show the app in the sidebar
14
+
15
+ tasks:
16
+ # One row of headline numbers
17
+ - name: summary_metrics
18
+ type: execute_sql
19
+ database: warehouse
20
+ sql_query: |
21
+ SELECT
22
+ COUNT(*) AS order_count,
23
+ COUNT(DISTINCT category) AS category_count,
24
+ ROUND(SUM(amount), 2) AS total_amount,
25
+ ROUND(AVG(amount), 2) AS avg_amount
26
+ FROM orders
27
+ WHERE order_date >= '2024-01-01'
28
+
29
+ # Trend over time
30
+ - name: monthly_trend
31
+ type: execute_sql
32
+ database: warehouse
33
+ sql_query: |
34
+ SELECT
35
+ DATE_TRUNC('month', order_date) AS month,
36
+ COUNT(*) AS order_count,
37
+ ROUND(SUM(amount), 2) AS total_amount
38
+ FROM orders
39
+ WHERE order_date >= '2024-01-01'
40
+ GROUP BY 1
41
+ ORDER BY 1
42
+
43
+ # Breakdown by category
44
+ - name: by_category
45
+ type: execute_sql
46
+ database: warehouse
47
+ sql_query: |
48
+ SELECT
49
+ category,
50
+ COUNT(*) AS order_count,
51
+ ROUND(SUM(amount), 2) AS total_amount
52
+ FROM orders
53
+ WHERE order_date >= '2024-01-01'
54
+ GROUP BY category
55
+ ORDER BY total_amount DESC
56
+
57
+ display:
58
+ - type: markdown
59
+ content: |
60
+ # Orders Overview
61
+
62
+ Key metrics since January 2024.
63
+
64
+ - type: table
65
+ title: Summary
66
+ data: summary_metrics
67
+ formats:
68
+ total_amount: currency
69
+ avg_amount: currency
70
+ order_count: number
71
+
72
+ - type: line_chart
73
+ title: Monthly total
74
+ data: monthly_trend
75
+ x: month
76
+ y: total_amount
77
+ x_axis_label: Month
78
+ y_axis_label: Amount
79
+ y_format: currency
80
+
81
+ - type: row
82
+ columns: 2
83
+ children:
84
+ - type: bar_chart
85
+ title: By category
86
+ data: by_category
87
+ x: category
88
+ y: total_amount
89
+ y_format: currency
90
+
91
+ - type: pie_chart
92
+ title: Share by category
93
+ data: by_category
94
+ name: category
95
+ value: total_amount
96
+ value_format: currency
97
+
98
+ - type: table
99
+ title: Category details
100
+ data: by_category
101
+ formats:
102
+ total_amount: currency