Skip to content

Commit 2d1ccfc

Browse files
Merge pull request #59 from Database-Tycoon/v0.1.9
Release v0.1.9 — Google Sheets, managed .venv, Quack, notify & schedule
2 parents b976700 + 6e4627d commit 2d1ccfc

45 files changed

Lines changed: 3236 additions & 36 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,34 @@
11
All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
22

3+
## [0.1.9] - 2026-06-10
4+
5+
_Headline: Google Sheets source ([#52][]). Plus managed project-local `.venv` + `doctor --fix` ([#57][]), DuckDB Quack live warehouse ([#42][]), pipeline notifications ([#46][]), and scheduled runs ([#48][]). Layer-granular backup ([#31][]) is deferred — no backup track exists yet to make layer-aware (it depended on the deferred DuckLake path). Also closes two High-severity findings from a security review ([#60][], [#61][]). Cycle plan in [`docs/proposals/v0.1.9-scope.md`](docs/proposals/v0.1.9-scope.md)._
6+
7+
### Added
8+
9+
- **`tycoon schedule` — local cron-less scheduled runs** ([#48][]). Run any tycoon command on a timer via the platform-native scheduler — macOS launchd LaunchAgents, Linux `systemd --user` timers — with no daemon and no cloud. `tycoon schedule add <name> --command "data run-all" --at 04:00 [--cadence daily|hourly|weekly] [--notify]`, plus `list` / `remove` / `status` (which tails the run log). Output lands in `~/.local/share/tycoon/schedule/<name>/run.log`; scheduled runs execute from the project root so `tycoon.yml` resolves. `tycoon doctor` reports the count of installed schedules. Windows prints a Task Scheduler pointer (unsupported in v1). Pairs with `--notify` ([#46][]) so unattended runs report in.
10+
- **Pipeline notifications — `tycoon notify` + `run-all --notify`** ([#46][]). A small local-first notification surface so unattended runs don't fail silently. `tycoon notify <success|error|info> <message> [-f key=val ...]` posts to a webhook read from `$TYCOON_NOTIFY_WEBHOOK_URL` (Slack incoming webhooks get a colour-coded attachment; any other URL gets a generic JSON envelope). `tycoon data run-all --notify` emits `success` on completion and `error` (with the failing stage) on failure — best-effort, so a notification problem never fails the pipeline. Non-secret prefs (which severities to emit, a source label) live in an optional `notify:` block in `tycoon.yml`; the URL never does. Webhook + Slack only for v1.
11+
- **DuckDB Quack — live multi-client warehouse, folded into `tycoon start`** ([#42][]). When the [Quack](https://duckdb.org/quack/) extension is available (currently `core_nightly`), `tycoon start` also serves the warehouse over Quack's local RPC protocol on `:9494`, turning the single-writer DuckDB file into a multi-client server. No new commands or flags: a per-project token is generated once into `.tycoon/secrets.yml` (gitignored), and `tycoon data query` attaches over Quack automatically when the server is up (labelled `warehouse (Quack)`), falling back to direct file access otherwise. Skipped silently where the extension can't load. Opt-in by nature (core_nightly-only) and local-only. Coordinating standalone dbt writes against a served warehouse is a planned follow-up.
12+
- **`tycoon setup` builds a managed project-local `.venv`** ([#57][]). The second piece of the managed-environment work: `tycoon setup` creates a `.venv` beside `tycoon.yml` on a supported interpreter via `uv venv --python` (uv auto-downloads a python-build-standalone CPython if the machine only has an unsupported one — zero manual installs), pins it with `.python-version`, and installs tycoon + its dbt/dlt/duckdb stack into it. Flags: `--python` (default 3.13), `--from` (install spec; `-e .` for dev checkouts), `--no-install`, `--force`, `--no-prompt`. If `uv` is absent it points at the standalone installer rather than running it. **`tycoon doctor --fix`** runs the same build flow when the interpreter check fails. Together with the v0.1.9 interpreter check, this closes the Python-version trap that produced [#55][].
13+
- **Google Sheets data source** ([#52][]). `tycoon data sources add google_sheets` registers a spreadsheet as a first-class source — pulling tabs/ranges into DuckDB with the header row becoming typed columns. Auth is a Google service-account JSON key (headless/cron-friendly), defaulting to the standard `${GOOGLE_APPLICATION_CREDENTIALS}` env var; leaving the key blank falls back to dlt's own resolution (the OAuth / ADC path). Config is `spreadsheet_url_or_id` (required) and an optional comma-separated `range_names` (blank loads every sheet). Works under `--no-prompt` for scripted bootstrap. Per project convention the dlt `google_sheets` verified source isn't bundled — it's pulled on demand via `dlt init`. First cut is full-refresh replace per run (Sheets has no native incremental key).
14+
- **`tycoon doctor` checks the Python interpreter version** ([#57][]). A new first check verifies the running interpreter is within tycoon's supported range (`>=3.12,<3.14`) and fails with an actionable hint otherwise — calling out that 3.14 has no dbt wheels yet and pointing at `uv venv --python 3.13`. tycoon runs dbt out of the same interpreter it lives in, so an out-of-range interpreter previously failed far from its cause at `data transform run` (this is how [#55][] stayed invisible). First sub-piece of the managed-`.venv` onboarding work; runs even without a `tycoon.yml`.
15+
16+
### Security
17+
18+
- **Fivetran credentials can no longer leak into a committed `tycoon.yml`** ([#60][]). `stack.ingestion_metadata.api_key` / `api_secret` are now `pydantic.SecretStr`, so they mask to `**********` in any `repr`/traceback, and their field docs steer you to `${FIVETRAN_API_SECRET}`-style env-var indirection (the loader already expands `${VAR}` before validation, keeping the literal secret out of the file). `save_project` now preserves the hand-authored `ingestion_metadata` block verbatim on round-trip — previously a `sources add` / `register` write would re-dump the *expanded* secret straight back into `tycoon.yml`. The scaffolded `.gitignore` also now excludes `.env`, `.dlt/secrets.toml`, and `**/profiles.yml` (the files that actually hold credentials); `tycoon.yml` stays committable as the shareable stack template.
19+
- **SQL identifier injection in Rill export and `data schema` is closed** ([#61][]). Schema/table/column names sourced from `tycoon.yml` or introspected from a DuckDB file are now quoted via a shared `quote_identifier` helper before interpolation into SQL. Previously an attacker-crafted `schema:` value in a shared project could break out of the `COPY … TO` statement that `tycoon data analyze --rill` runs — and DuckDB's `COPY`/`INSTALL` can write files and load extensions, so this was a path to arbitrary file write, not just data disclosure. The export's destination-path literal is escaped too. (The broader `tycoon.yml` identifier/path validation layer is tracked in [#65][].)
20+
21+
[#31]: https://github.com/Database-Tycoon/tycoon-cli/issues/31
22+
[#42]: https://github.com/Database-Tycoon/tycoon-cli/issues/42
23+
[#46]: https://github.com/Database-Tycoon/tycoon-cli/issues/46
24+
[#48]: https://github.com/Database-Tycoon/tycoon-cli/issues/48
25+
[#52]: https://github.com/Database-Tycoon/tycoon-cli/issues/52
26+
[#55]: https://github.com/Database-Tycoon/tycoon-cli/issues/55
27+
[#57]: https://github.com/Database-Tycoon/tycoon-cli/issues/57
28+
[#60]: https://github.com/Database-Tycoon/tycoon-cli/issues/60
29+
[#61]: https://github.com/Database-Tycoon/tycoon-cli/issues/61
30+
[#65]: https://github.com/Database-Tycoon/tycoon-cli/issues/65
31+
332
## [0.1.8] - 2026-05-30
433

534
_A small, focused release: live Fivetran status in `tycoon data status` ([#50][]) plus a Python-version guardrail ([#55][]) so dbt-incompatible interpreters (3.14) aren't silently selected. The Quack headline and the backup / operate-in-prod tracks planned in [`docs/proposals/v0.1.8-scope.md`](docs/proposals/v0.1.8-scope.md) continue toward a later cut. See [`docs/releases/v0.1.8.md`](docs/releases/v0.1.8.md) for the full narrative._

docs/commands/data/query.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ tycoon data query --db .tycoon/metadata.duckdb \
4242
tycoon data query --db ./snap.duckdb "SHOW ALL TABLES"
4343
```
4444

45+
### Live warehouse via Quack (v0.1.9)
46+
47+
When a [Quack](../start.md#quack-the-live-multi-client-warehouse-v019) server is holding the warehouse — i.e. `tycoon start` is running — a plain warehouse query (no `--db` / `--source` / `--raw`) **attaches over Quack** instead of opening the file. You'll see the result labelled `warehouse (Quack)`. This is what lets you query the live warehouse while the rest of the stack is up, rather than hitting DuckDB's single-writer file lock. When no server is running, the command opens the file directly as before. It's automatic — there's no flag.
48+
4549
`--source` is a convenience for the common case of "show me the raw landed data". It walks tycoon's path resolution:
4650

4751
1. `config.raw_db` if it contains the `raw_<name>` schema (single-DB mode — current default)

docs/commands/data/run-all.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,19 @@ tycoon data run-all --skip-dbt
6666

6767
Every source ingest and the final `dbt build` are captured into `.tycoon/metadata.duckdb` exactly as if you'd run them individually. `tycoon data history` will show one entry per source plus one for the dbt build.
6868

69+
## Notifications (`--notify`)
70+
71+
For unattended runs, `--notify` posts a webhook notification on completion: `success` when the run finishes, `error` (with the failing stage and a message tail) if ingestion or `dbt build` fails. Set `$TYCOON_NOTIFY_WEBHOOK_URL` first; which severities fire is governed by the `notify.severities` block in `tycoon.yml`. The webhook is best-effort — a notification failure warns but never fails the run. See [`tycoon notify`](../notify.md) for setup and payload shapes.
72+
73+
```bash
74+
export TYCOON_NOTIFY_WEBHOOK_URL="https://hooks.slack.com/services/..."
75+
tycoon data run-all --notify
76+
```
77+
6978
## Related
7079

7180
- [`tycoon data sources run`](sources.md#run-ingest) — single-source ingest
7281
- [`tycoon data transform build`](transform.md#build-run-test-together) — dbt build alone
7382
- [`tycoon data history`](history.md) — review what `run-all` did
83+
- [`tycoon notify`](../notify.md) — the notification surface `--notify` uses
84+
- [`tycoon schedule`](../schedule.md) — run `run-all --notify` on a timer

docs/commands/data/sources.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Output:
2828
│ github │ Developer Tools │ Issues, PRs, commits │ issues, ... │
2929
│ slack │ Communication │ Channels, users, ... │ channels, ... │
3030
│ stripe │ Payments │ Customers, ... │ customers, ... │
31+
│ google_she…│ Spreadsheets │ Tabs/ranges from a sheet │ sheets │
3132
│ rest_api │ Generic │ Any REST API │ pokemon, ... │
3233
│ filesystem │ Generic │ Local CSV / Parquet │ files │
3334
└────────────┴─────────────────┴──────────────────────────┴────────────────────┘
@@ -54,6 +55,7 @@ Then type-specific config:
5455

5556
- **github** — repo owner + name, optional access token env var name
5657
- **slack** — workspace + bot token env var name
58+
- **google_sheets** — service-account key path, spreadsheet URL/ID, optional tab/range list
5759
- **rest_api** — base URL, dataset list, optional auth header
5860
- **filesystem** — directory path + glob pattern
5961
- **sql_database** — connection string + table list
@@ -100,6 +102,34 @@ Flag reference:
100102

101103
Catalog credentials default to `${ENV_VAR}` references in both modes — set the env var separately. Catalog-source files (`dlt init`-style downloads) are not auto-installed under `--no-prompt`; run `tycoon data sources catalog install <type>` separately if you need them.
102104

105+
### Google Sheets
106+
107+
Google Sheets pulls one or more tabs/ranges from a spreadsheet into DuckDB. The header row becomes typed columns. First cut is **full-refresh replace** per run — Sheets has no native incremental key.
108+
109+
**1. Get a service-account key (headless/cron-friendly).** Create a service account at [console.cloud.google.com/iam-admin/serviceaccounts](https://console.cloud.google.com/iam-admin/serviceaccounts), enable the **Google Sheets API** for the project, and download a JSON key. Then **share the spreadsheet** with the service-account's email (`...@....iam.gserviceaccount.com`) — read access is enough.
110+
111+
**2. Point tycoon at the key.** The credential defaults to the standard `${GOOGLE_APPLICATION_CREDENTIALS}` env var:
112+
113+
```bash
114+
export GOOGLE_APPLICATION_CREDENTIALS=~/keys/sheets-sa.json
115+
```
116+
117+
**3. Register the source.** Interactively (`tycoon data sources add google_sheets`) you'll be prompted for the key path, the spreadsheet URL/ID, and an optional tab/range list. Non-interactively:
118+
119+
```bash
120+
tycoon data sources add google_sheets \
121+
--name marketing-sheet \
122+
--config 'spreadsheet_url_or_id=https://docs.google.com/spreadsheets/d/<ID>/edit' \
123+
--config 'range_names=Sheet1,Q1 2026!A1:F' \
124+
--no-prompt
125+
```
126+
127+
- **`spreadsheet_url_or_id`** — the full share URL or just the ID. Required.
128+
- **`range_names`** — comma-separated tab names and/or A1 ranges. Leave blank to load **every** sheet in the spreadsheet.
129+
- **`credentials_path`** — defaults to `${GOOGLE_APPLICATION_CREDENTIALS}`. Leave it blank to fall back to dlt's own credential resolution (env / `secrets.toml`), which is where the **OAuth / Application Default Credentials** path lives if you'd rather not use a service account.
130+
131+
**4. Ingest:** `tycoon data sources run marketing-sheet`. The first run downloads the dlt `google_sheets` verified source to `~/.tycoon/sources/` (or `tycoon data sources catalog install google_sheets` under `--no-prompt`), then loads into `raw_<name>` and auto-scaffolds a staging dbt model.
132+
103133
## `list` — list registered sources
104134

105135
```bash

docs/commands/doctor.md

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,47 @@ Health check for the tycoon environment — `tycoon.yml` validity, dbt / Rill /
66

77
```
88
tycoon doctor [OPTIONS]
9+
10+
Options:
11+
--fix Attempt to repair fixable problems. Currently: when the Python
12+
interpreter is out of range, build a project-local `.venv` on a
13+
supported interpreter (via uv) for you to activate.
14+
-h, --help Show this message and exit.
915
```
1016

11-
No flags. Just runs a series of checks and prints one panel per check.
17+
Runs a series of checks and prints one panel per check. With `--fix`, doctor also attempts to repair the problems it knows how to fix (see [below](#fixing-problems-fix)).
1218

1319
## What it checks
1420

1521
The output is a sequence of `Checking ...` panels:
1622

17-
### 1. `tycoon.yml`
23+
### 1. Python interpreter (v0.1.9)
24+
25+
Confirms the interpreter running tycoon is within the supported range, **`>=3.12,<3.14`** (mirrors `requires-python` in `pyproject.toml`).
26+
27+
- `OK Python 3.13 is in the supported range (>=3.12,<3.14).`
28+
- `ERROR Python 3.11 is too old. ... run tycoon setup (or uv venv --python 3.12).`
29+
- `ERROR Python 3.14 is too new for tycoon's dbt stack (dbt-core / dbt-duckdb have no 3.14 wheels yet) ... run tycoon setup (or uv venv --python 3.13).`
30+
31+
This is the first check because tycoon runs dbt out of the *same* interpreter it lives in (it resolves dbt at `Path(sys.executable).parent / "dbt"`). A too-new interpreter — notably 3.14, which has no dbt wheels — otherwise fails far from its cause, at `tycoon data transform run`, which is exactly how [#55](https://github.com/Database-Tycoon/tycoon-cli/issues/55) stayed invisible. Surfacing the mismatch here makes it the first thing you see. Environment-level, so it runs even without a `tycoon.yml`. When this check fails, [`tycoon doctor --fix`](#fixing-problems-fix) (or [`tycoon setup`](setup.md)) builds a corrected `.venv`.
32+
33+
### 2. `tycoon.yml`
1834

1935
Confirms `tycoon.yml` exists in the current directory or a parent. Errors if not — with a hint to run `tycoon init`.
2036

21-
### 2. dbt project
37+
### 3. dbt project
2238

2339
If `stack.transformation = dbt`: confirms `dbt_project_dir` exists and contains `dbt_project.yml`. Reports a clean skip ("dbt: skipped by choice (stack.transformation = none)") if the user opted out of dbt.
2440

25-
### 3. Rill project
41+
### 4. Rill project
2642

2743
If `stack.bi = rill`: confirms `rill_dir` exists. Reports a skip if `bi: none` or a different BI tool is configured.
2844

29-
### 4. Dagster
45+
### 5. Dagster
3046

3147
If the `[dagster]` extra is installed: confirms the `dagster` binary is on `$PATH`. The orchestrator is optional — most projects don't need it.
3248

33-
### 5. Warehouse auth
49+
### 6. Warehouse auth
3450

3551
For DuckDB warehouses: nothing to check (no auth).
3652

@@ -43,7 +59,7 @@ Reports `OK token (env)`, `OK OAuth (cached session)`, or `ERROR not configured`
4359

4460
For Snowflake / BigQuery / Redshift: warehouse auth lives in dbt's `profiles.yml` and isn't tycoon's concern. `doctor` skips these.
4561

46-
### 6. Layer coverage (v0.1.7)
62+
### 7. Layer coverage (v0.1.7)
4763

4864
When `stack.transformation = dbt` and a compiled dbt manifest exists, doctor verifies that every registered source in `tycoon.yml` has at least one staging model:
4965

@@ -54,7 +70,7 @@ Silently skipped when `transformation: none` or when the manifest hasn't been co
5470

5571
See [layered architecture](../recipes/layered-architecture.md) for the underlying classification rules.
5672

57-
### 7. Observability
73+
### 8. Observability
5874

5975
Reports the state of `.tycoon/metadata.duckdb`:
6076

@@ -64,6 +80,16 @@ Reports the state of `.tycoon/metadata.duckdb`:
6480

6581
Useful when "my dashboards are empty" — usually it means observability hasn't fired yet.
6682

83+
## Fixing problems (`--fix`)
84+
85+
`tycoon doctor --fix` runs all the checks, then attempts to repair the ones it knows how to fix. Today that's the **Python interpreter** check:
86+
87+
- The running interpreter can't be swapped under tycoon's own feet, so the repair is to build a project-local `.venv` on a supported interpreter (via `uv venv --python 3.13`) and pin it with `.python-version` — the same flow as [`tycoon setup`](setup.md). You then `source .venv/bin/activate` and re-run.
88+
- If `uv` isn't installed, `--fix` can't proceed and prints the one-line installer instead of running it.
89+
- When the interpreter is already in range, `--fix` is a no-op.
90+
91+
`--fix` never lowers the bar: an unfixable check still reports its error.
92+
6793
## Exit codes
6894

6995
`tycoon doctor` always exits **0**, even when checks fail. The output is informational; nothing else in tycoon depends on doctor having a clean run.

0 commit comments

Comments
 (0)