Automate creation of Obsidian markdown files for art collection
Project description
gesso
Generate Obsidian-ready painting notes:
gesso new— build notes from an input list (original workflow).gesso enrich— read an existing note, propose factual frontmatter updates from cached or live LLM metadata, and write a proposal Markdown file (vault note is untouched unless--apply).
Use --model to pick the backend (default Perplexity). Set the matching API
key in your environment or .env:
| Backend | Keys | Example --model |
|---|---|---|
| Perplexity | PERPLEXITY_API_KEY |
perplexity (default), perplexity:sonar-pro, or sonar-pro |
| Kimi (Moonshot) | MOONSHOT_API_KEY or KIMI_API_KEY |
kimi, kimi:kimi-k2.6, or kimi-k2.6 |
For compatibility, gesso --input … (flags without a subcommand) is interpreted as
gesso new --input ….
Pre-requisites
uvfor dependency management
Quickstart
- Install deps:
uv sync - Export
PERPLEXITY_API_KEY(or add to.env). For Kimi, setMOONSHOT_API_KEYorKIMI_API_KEY. - Generate notes:
gesso new --input data/input.txt --output outputs/
# Kimi: gesso new --model kimi --input data/input.txt --output outputs/
Defaults for new: --input data/input.txt --output outputs/ --cache .cache
--template data/example-template.md
Legacy shorthand (prepend new automatically):
gesso --input data/input.txt --output outputs/
Enrich (gesso enrich)
Enrichment parses YAML frontmatter from an existing note, derives plaintext
title and artist from it, derives factual fields via
extract_template_fields, fills only missing values from .cache/ first then
minimal LLM calls (query_painting_metadata).
Personal / vault keys are never queried or overwritten from the API in this pass:
rating, seen, created, tags, category, plus template blacklist entries
such as title and artist (your note wording wins).
Example:
gesso enrich \
--note /path/to/Retrieve.md \
--template '/path/to/Painting Template.md' \
--attachments /path/to/Attachments \
--cache .cache \
--proposal-only
- Proposals land under
--proposal-dir(defaultmanifests/painting-enrichment) as{note-stem}.enrich-proposal.md. --proposal-onlyreaffirms vault-safe mode (proposals always written).--applyalso rewrites the note frontmatter and keeps body text — explicit opt-in.--attachmentsappears in proposals for tooling;--download-imageis reserved for a future enhancement (currently errors).
PERPLEXITY_API_KEY (or Kimi keys when using --model kimi) is required only when gaps remain after .cache/ cannot satisfy them.
Input & Template
- Input lines:
number: Title, Artist - Template: Use
--templateto specify a custom template file (default:data/example-template.md). Output files land inoutputs/{title}.md.
Template Field Extraction
The tool automatically extracts YAML frontmatter field names from your template and queries the configured model for those fields. metadata fields you want to collect.
Blacklisted fields (user-defined, not from the API): title, date,
created, category, rating, seen, tags, artist. These fields are
always excluded from API queries.
Example template:
---
created: {{date}}
title: "{{title}}"
artist:
year: # ← Collected from the LLM
style: # ← Collected from the LLM
medium: # ← Collected from the LLM
museum: # ← Collected from the LLM
image: # ← Collected from the LLM
rating: # ← NOT collected (blacklisted)
tags: # ← NOT collected (blacklisted)
- paintings
---
The tool prints which fields it's collecting at startup:
[INFO] Collecting fields via perplexity (sonar-pro): year, style, medium, museum, image
Field name mapping: Template field names are mapped to API field names where
needed (e.g., image → image_url). Most fields use the same name in both
template and API.
- Cache:
.cache/{normalized_title_artist}.jsonfor Perplexity (legacy), or.cache/{normalized}.kimi.jsonwhen using--model kimi, to keep providers separate.
[!warning] If you change the template structure, you need to clear out the cache to ensure the code requeries everything. Otherwise, it'll just write an empty field
Flow (per painting — new)
- Extract fields from template YAML frontmatter (excluding blacklisted fields).
- Parse input line into
{number, title, artist}. - Check cache; otherwise query the configured LLM for metadata (only the fields found in template).
- Post-process strings into Obsidian-friendly lists/links.
- Render template placeholders and frontmatter with collected data.
- Write markdown to the output directory.
Flow (enrich)
- Parse note frontmatter and body separately.
- Infer plaintext title/artist from frontmatter values.
- Determine missing factual fields from template extract + note values.
- Pull values from
.cache/first; optionally call the LLM for leftovers. - Post-process fetched keys only (
post_process_fieldswithfields_only). - Write
*.enrich-proposal.mdunder--proposal-dir(always). Optionally--apply.
Module reference
parse_input(filepath): read lines into structured dicts; warns on invalid rows.extract_template_fields(template_path): extract YAML frontmatter field names, filtering out blacklisted fields.get_cache_key(title, artist, *, provider): normalize to cache filename (Perplexity keeps the legacy.jsonname).load_from_cache / save_to_cache: JSON cache helpers under.cache/.query_painting_metadata(..., resolved=ResolvedModel): dispatches to Perplexity or Kimi (OpenAI-compatible).post_process_fields(data, template_fields): convert comma-separated strings to lists and wrap wikilinks; drop"Unknown"values (except for description).render_markdown(template_path, painting_data, today, template_fields): fill template placeholders and YAML lists dynamically based on template fields.write_output(output_dir, filename, content): ensure dirs exist and persist markdown.run_new(...): programmatic entry for generating notes from inputs.post_process_fields(data, template_fields, fields_only=...): factual string cleanup; enrich passesfields_onlyso untouched note keys are not erased.main(argv=None): setuptools console entry (gessoscript); parsesargvorsys.argvrun_enrich(...)(gesso.enrich): programmatic enrich + proposal paths.
Usage Examples
gesso new --input data/input.txt
gesso new --input data/input.txt --template my-template.md
gesso new --input paintings.txt --output notes/ --cache .cache/ --template templates/custom.md
# backward compatible
gesso --input data/input.txt
gesso enrich \
--note ./Notes/Retrieve.md \
--template "./Templates/Painting Template.md" \
--attachments ./Attachments \
--cache .cache \
--model kimi \
--proposal-dir manifests/painting-enrichment
Error handling
- Missing env key: fails fast (
newruns always validate; enrich only validates when a live query is necessary). - Template errors: fails if template file not found or has no fields to collect.
- Input parse errors: warn and skip lines.
- API failures: log error and skip that painting.
- Cache/IO issues: log warnings and continue to the next item.
Development
- Python 3.13+, managed with
uv. - Style:
uv run ruff format --check .anduv run ruff check . - Tests:
uv run pytest -v --cov --cov-report=xml - CI mirrors these steps (
.github/workflows/ci.yml).
Contributing
- Keep README aligned with behavior; update TODOs when adding features.
- Add tests for new parsing, templating, or API edge cases.
- Run lint/format/tests before sending changes.
Tasks
lint
uv run ruff check . --fix --unsafe-fixes
uv run ruff format .
test
pytest
sync
uv sync
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gesso-0.3.0.tar.gz.
File metadata
- Download URL: gesso-0.3.0.tar.gz
- Upload date:
- Size: 27.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
223fbc1281ab3ea143e0f2e67097242af567528fd959d5ae9dfc01ac77c0fe6d
|
|
| MD5 |
8f0ef2add073d6c8334f0306006a98b0
|
|
| BLAKE2b-256 |
3f94ec7578591658844b2016c66af8c5f1366589d5d2bf53a29306185a1c5f78
|
File details
Details for the file gesso-0.3.0-py3-none-any.whl.
File metadata
- Download URL: gesso-0.3.0-py3-none-any.whl
- Upload date:
- Size: 21.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.8 {"installer":{"name":"uv","version":"0.11.8","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f7cf5419c736d92c0203743a60ab24dc4c15b5163d4e80bbfba19ab2eb0098e4
|
|
| MD5 |
d967609a742ddc67ec4d8340c2ca1c91
|
|
| BLAKE2b-256 |
c6d9c4aaa3399210dc77f306b6d8cb1c56b0f5842c0ebdbf764b57b9ba55645d
|