limn 0.1.0__tar.gz

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.
limn-0.1.0/.coverage ADDED
Binary file
limn-0.1.0/.gitignore ADDED
@@ -0,0 +1,29 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ .eggs/
6
+ build/
7
+ dist/
8
+ .venv/
9
+ venv/
10
+
11
+ # Config / secrets
12
+ limn.yaml
13
+ .env
14
+
15
+ # Generated images / scratch
16
+ outputs/
17
+ *.png
18
+ *.jpg
19
+ *.jpeg
20
+ *.webp
21
+ !docs/**/*.png
22
+
23
+ # macOS
24
+ .DS_Store
25
+ ._*
26
+
27
+ # Editors
28
+ .vscode/
29
+ .idea/
limn-0.1.0/CLAUDE.md ADDED
@@ -0,0 +1,70 @@
1
+ # Limn — agent handover / working notes
2
+
3
+ This file is auto-loaded when you open a Claude Code session in this repo. It
4
+ tells you what Limn is, where things stand, and how to start. **`SPEC.md` is the
5
+ source of truth** — read it before writing any code.
6
+
7
+ ## What Limn is
8
+
9
+ A small, privacy-first **text-to-image** tool: type a prompt, get an image. No
10
+ account, minimal options, bring your own provider. Surfaces (in order): a CLI, a
11
+ simple web UI (generate / view / save / delete / regenerate), then a Tauri
12
+ desktop app with bring-your-own-provider, plus a rate-limited, non-storing demo.
13
+
14
+ ## Current status
15
+
16
+ **M1 (CLI) built and published to PyPI as `limn` 0.1.0.** The package lives in
17
+ `src/limn/`: `config.py` (layered ~/.limn.yaml < ./limn.yaml < flags, `${VAR}`
18
+ expansion, per-provider `providers:` overlays), `providers/` (swarmui,
19
+ openai, openai-compatible, gemini/imagen — all plain `requests`, no SDKs),
20
+ `core.py` (generate + save with slug naming / no-overwrite), `cli.py` (Typer,
21
+ single command). Tests in `tests/` (45, all green), basedpyright zero errors,
22
+ ruff clean. Verified end-to-end against the real Imagen API.
23
+
24
+ Design choices made during M1 (beyond SPEC):
25
+ - Providers raise `ProviderError` and the CLI exits non-zero — **no** silent
26
+ text-image fallback like slide-stream (that only makes sense in a video
27
+ pipeline).
28
+ - `openai`/`dalle` and `openai-compatible` share one class; the compatible
29
+ variant *requires* `base_url` so a stray OPENAI_API_KEY never sends prompts
30
+ to the wrong server.
31
+ - Imagen (Gemini API) gets nearest-aspect-ratio mapping from `--size`;
32
+ seed/negative are warn-and-ignore on backends that lack them (OpenAI,
33
+ Gemini API).
34
+
35
+ ## Machine gotcha (this repo lives on an exFAT drive)
36
+
37
+ `uv sync` with a local `.venv` fails here: macOS writes AppleDouble `._*`
38
+ files (SIP-protected `com.apple.provenance` xattr) into binary wheels (ruff,
39
+ basedpyright) and uv's RECORD validation rejects them. Workaround used:
40
+
41
+ ```bash
42
+ export UV_PROJECT_ENVIRONMENT="$HOME/.venvs/limn" # venv on internal disk
43
+ uv sync --all-groups
44
+ uv run --no-sync ruff check src tests && uv run --no-sync basedpyright && uv run --no-sync pytest
45
+ ```
46
+
47
+ ## Next milestones
48
+
49
+ M2 (web UI, `limn serve` behind a `[serve]` extra, like `slide-stream serve`),
50
+ M3 (hosted demo), M4 (Tauri desktop). Release flow: bump version in
51
+ `pyproject.toml` + `src/limn/__init__.py`, `uv build`, `twine upload dist/*`.
52
+
53
+ ## Key decisions already made
54
+
55
+ - **Standalone**, not a slide-stream dependency (slide-stream is a video tool).
56
+ Reuse the *pattern*, not the package. Extract a shared image-providers lib
57
+ later only if it clearly pays off.
58
+ - **Generative providers only** (Limn *makes* images) — no Pexels/Unsplash stock
59
+ unless the user asks.
60
+ - **Minimal knobs** on purpose: prompt, size, count, seed. Advanced params live
61
+ in the provider. Don't add options without a reason.
62
+ - **Privacy-first:** no account, no telemetry, no server-side storage of prompts
63
+ or images; the demo is rate-limited and ephemeral.
64
+
65
+ ## Reference
66
+
67
+ - Companion project: `../slide-stream` (on this machine) —
68
+ https://github.com/michael-borck/slide-stream, published on PyPI. Its image
69
+ providers, layered config, and `serve` web UI are the templates to adapt.
70
+ - Repo: https://github.com/michael-borck/limn (public).
limn-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: limn
3
+ Version: 0.1.0
4
+ Summary: A small, privacy-first text-to-image tool: type a prompt, get an image. Bring your own provider.
5
+ Project-URL: Homepage, https://github.com/michael-borck/limn
6
+ Project-URL: Repository, https://github.com/michael-borck/limn
7
+ Project-URL: Issues, https://github.com/michael-borck/limn/issues
8
+ Author-email: Michael Borck <michael.borck@curtin.edu.au>
9
+ License: MIT
10
+ Keywords: ai,cli,dall-e,image,imagen,swarmui,text-to-image
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: End Users/Desktop
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Multimedia :: Graphics
23
+ Requires-Python: >=3.10
24
+ Requires-Dist: pyyaml>=6.0.0
25
+ Requires-Dist: requests>=2.31.0
26
+ Requires-Dist: rich>=13.0.0
27
+ Requires-Dist: typer>=0.9.0
28
+ Provides-Extra: serve
29
+ Requires-Dist: fastapi>=0.110.0; extra == 'serve'
30
+ Requires-Dist: uvicorn>=0.29.0; extra == 'serve'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # 🖼️ Limn
34
+
35
+ > *to **limn**: to depict or describe; to paint.*
36
+
37
+ A small, privacy-first tool that turns a text description into an image. Type a
38
+ prompt, get a picture — no account, minimal options, bring your own provider.
39
+
40
+ ```bash
41
+ pip install limn
42
+
43
+ limn "a red bicycle against a brick wall" -o bike.png
44
+ ```
45
+
46
+ - **Type a description → get an image.** That's the whole thing.
47
+ - **No account, private by default.** Your prompts and images are yours. The
48
+ only network egress is the generation request to the provider *you* chose.
49
+ - **Bring your own provider** — self-hosted (SwarmUI / any OpenAI-compatible
50
+ endpoint) or a cloud key (Google Imagen, OpenAI / DALL·E). Swap with one
51
+ setting.
52
+ - **Minimal by design.** Prompt, size, count, seed. Want power? Install
53
+ something bigger — Limn stays lean.
54
+
55
+ ## Quick start
56
+
57
+ Pick whichever provider you already have:
58
+
59
+ ```bash
60
+ # Google Imagen (cheap, ~$0.02/image on the Fast tier)
61
+ export GEMINI_API_KEY=...
62
+ limn "watercolour fox" --provider gemini
63
+
64
+ # OpenAI Images
65
+ export OPENAI_API_KEY=...
66
+ limn "watercolour fox" --provider openai
67
+
68
+ # Self-hosted SwarmUI
69
+ export SWARMUI_BASE_URL=https://image.example.org
70
+ limn "watercolour fox" --provider swarmui
71
+
72
+ # Any OpenAI-compatible /v1/images endpoint (LocalAI, ...)
73
+ export OPENAI_BASE_URL=http://localhost:8080/v1
74
+ limn "watercolour fox" --provider openai-compatible
75
+ ```
76
+
77
+ Then set your provider once so you never pass `--provider` again:
78
+
79
+ ```bash
80
+ limn --init-config # writes a commented ./limn.yaml template
81
+ mv limn.yaml ~/.limn.yaml # personal defaults, used everywhere
82
+ ```
83
+
84
+ ## Usage
85
+
86
+ ```
87
+ limn "a red bicycle against a brick wall" -o bike.png
88
+ limn "watercolour fox" --provider swarmui --size 1024x1024 --count 4 --seed 42
89
+ ```
90
+
91
+ | Option | What |
92
+ |---|---|
93
+ | `-p, --provider` | `swarmui`, `openai-compatible`, `openai`, `gemini` |
94
+ | `-m, --model` | Model name (provider-specific) |
95
+ | `-s, --size` | e.g. `1024x1024` (Imagen maps to the nearest aspect ratio) |
96
+ | `-n, --count` | Number of images (1–10) |
97
+ | `--seed` | Reproducibility, where the backend supports it (SwarmUI) |
98
+ | `--negative` | Negative prompt, where supported (SwarmUI) |
99
+ | `-o, --out` | Output file; default is a slug of the prompt, never overwritten |
100
+ | `-c, --config` | Explicit config file |
101
+
102
+ ## Config
103
+
104
+ Layered, later wins: built-in defaults → `~/.limn.yaml` (your provider + keys,
105
+ set once) → `./limn.yaml` → CLI flags. Secrets are referenced as `${VAR}` so no
106
+ plaintext keys live in files:
107
+
108
+ ```yaml
109
+ provider: swarmui
110
+ size: [1024, 1024]
111
+
112
+ providers: # keep several configured; swap with `provider:`
113
+ swarmui:
114
+ base_url: https://image.example.org
115
+ model: juggernautXL_v9
116
+ api_key: "${SWARMUI_TOKEN}"
117
+ gemini:
118
+ model: imagen-4.0-fast-generate-001
119
+ ```
120
+
121
+ ## Roadmap
122
+
123
+ A **CLI** (this release), then a simple **web UI** (`limn serve`: type →
124
+ generate → view → save / delete / regenerate), then a **Tauri desktop app**
125
+ with bring-your-own-provider, plus a rate-limited, non-storing **demo**.
126
+
127
+ 📄 See **[SPEC.md](SPEC.md)** for the full specification.
128
+
129
+ Companion to [slide-stream](https://github.com/michael-borck/slide-stream),
130
+ which shares the same image-provider approach.
limn-0.1.0/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # 🖼️ Limn
2
+
3
+ > *to **limn**: to depict or describe; to paint.*
4
+
5
+ A small, privacy-first tool that turns a text description into an image. Type a
6
+ prompt, get a picture — no account, minimal options, bring your own provider.
7
+
8
+ ```bash
9
+ pip install limn
10
+
11
+ limn "a red bicycle against a brick wall" -o bike.png
12
+ ```
13
+
14
+ - **Type a description → get an image.** That's the whole thing.
15
+ - **No account, private by default.** Your prompts and images are yours. The
16
+ only network egress is the generation request to the provider *you* chose.
17
+ - **Bring your own provider** — self-hosted (SwarmUI / any OpenAI-compatible
18
+ endpoint) or a cloud key (Google Imagen, OpenAI / DALL·E). Swap with one
19
+ setting.
20
+ - **Minimal by design.** Prompt, size, count, seed. Want power? Install
21
+ something bigger — Limn stays lean.
22
+
23
+ ## Quick start
24
+
25
+ Pick whichever provider you already have:
26
+
27
+ ```bash
28
+ # Google Imagen (cheap, ~$0.02/image on the Fast tier)
29
+ export GEMINI_API_KEY=...
30
+ limn "watercolour fox" --provider gemini
31
+
32
+ # OpenAI Images
33
+ export OPENAI_API_KEY=...
34
+ limn "watercolour fox" --provider openai
35
+
36
+ # Self-hosted SwarmUI
37
+ export SWARMUI_BASE_URL=https://image.example.org
38
+ limn "watercolour fox" --provider swarmui
39
+
40
+ # Any OpenAI-compatible /v1/images endpoint (LocalAI, ...)
41
+ export OPENAI_BASE_URL=http://localhost:8080/v1
42
+ limn "watercolour fox" --provider openai-compatible
43
+ ```
44
+
45
+ Then set your provider once so you never pass `--provider` again:
46
+
47
+ ```bash
48
+ limn --init-config # writes a commented ./limn.yaml template
49
+ mv limn.yaml ~/.limn.yaml # personal defaults, used everywhere
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ```
55
+ limn "a red bicycle against a brick wall" -o bike.png
56
+ limn "watercolour fox" --provider swarmui --size 1024x1024 --count 4 --seed 42
57
+ ```
58
+
59
+ | Option | What |
60
+ |---|---|
61
+ | `-p, --provider` | `swarmui`, `openai-compatible`, `openai`, `gemini` |
62
+ | `-m, --model` | Model name (provider-specific) |
63
+ | `-s, --size` | e.g. `1024x1024` (Imagen maps to the nearest aspect ratio) |
64
+ | `-n, --count` | Number of images (1–10) |
65
+ | `--seed` | Reproducibility, where the backend supports it (SwarmUI) |
66
+ | `--negative` | Negative prompt, where supported (SwarmUI) |
67
+ | `-o, --out` | Output file; default is a slug of the prompt, never overwritten |
68
+ | `-c, --config` | Explicit config file |
69
+
70
+ ## Config
71
+
72
+ Layered, later wins: built-in defaults → `~/.limn.yaml` (your provider + keys,
73
+ set once) → `./limn.yaml` → CLI flags. Secrets are referenced as `${VAR}` so no
74
+ plaintext keys live in files:
75
+
76
+ ```yaml
77
+ provider: swarmui
78
+ size: [1024, 1024]
79
+
80
+ providers: # keep several configured; swap with `provider:`
81
+ swarmui:
82
+ base_url: https://image.example.org
83
+ model: juggernautXL_v9
84
+ api_key: "${SWARMUI_TOKEN}"
85
+ gemini:
86
+ model: imagen-4.0-fast-generate-001
87
+ ```
88
+
89
+ ## Roadmap
90
+
91
+ A **CLI** (this release), then a simple **web UI** (`limn serve`: type →
92
+ generate → view → save / delete / regenerate), then a **Tauri desktop app**
93
+ with bring-your-own-provider, plus a rate-limited, non-storing **demo**.
94
+
95
+ 📄 See **[SPEC.md](SPEC.md)** for the full specification.
96
+
97
+ Companion to [slide-stream](https://github.com/michael-borck/slide-stream),
98
+ which shares the same image-provider approach.
limn-0.1.0/SPEC.md ADDED
@@ -0,0 +1,152 @@
1
+ # Limn — specification
2
+
3
+ > *to **limn** (verb): to depict or describe; to paint.*
4
+
5
+ A small, privacy-first tool that turns a text description into an image. Type a
6
+ prompt, get a picture. No account, minimal options, bring your own provider.
7
+
8
+ ---
9
+
10
+ ## 1. Vision
11
+
12
+ Most image tools are either a single locked-in web app (an account, your data on
13
+ their servers) or a full studio (ComfyUI/SwarmUI — powerful but heavy). **Limn
14
+ sits deliberately in between:** the simplest possible "text → image" surface,
15
+ that you own.
16
+
17
+ - **Type a description → get an image.** That's the whole interaction.
18
+ - **No account, no lock-in, privacy by default.** Your prompts and images are
19
+ yours. On the desktop, nothing leaves your machine except the generation
20
+ request to *your* chosen provider.
21
+ - **Bring your own provider.** Point Limn at a self-hosted server (SwarmUI /
22
+ any OpenAI-compatible endpoint) or a cloud key (Google Imagen, OpenAI /
23
+ DALL·E). Swap it with one setting.
24
+ - **Minimal by design.** A handful of knobs (prompt, size, count, seed). If you
25
+ want inpainting, ControlNet, and node graphs, install something bigger —
26
+ Limn stays lean.
27
+
28
+ Think "Black Forest Labs playground, but standalone, private, and yours."
29
+
30
+ ## 2. Principles
31
+
32
+ 1. **Simple beats powerful.** One prompt in, one image out. Advanced parameters
33
+ live in the provider, not in Limn.
34
+ 2. **Local-first & private.** No account, no telemetry, no server-side storage
35
+ of prompts or images (desktop is fully local). The hosted demo is
36
+ rate-limited and stores nothing.
37
+ 3. **Provider-agnostic.** The same thin provider abstraction as `slide-stream`'s
38
+ image layer — self-hosted or cloud, chosen by config.
39
+ 4. **Progressive surfaces.** The same core powers a CLI, a web UI, and a desktop
40
+ app; each is optional.
41
+
42
+ ## 3. Surfaces (roadmap)
43
+
44
+ ### v1 — CLI
45
+ ```
46
+ limn "a red bicycle against a brick wall" -o bike.png
47
+ limn "watercolour fox" --provider swarmui --size 1024x1024 --count 4 --seed 42
48
+ ```
49
+ Options: `--provider`, `--model`, `--size`, `--count`, `--seed`, `--negative`,
50
+ `--out`. Provider selection + API keys from a config file and/or environment
51
+ variables. Prints the saved path(s).
52
+
53
+ ### v2 — Web UI (`limn serve`)
54
+ A single page: type a prompt → **Generate** → the image appears in-app →
55
+ **Save** (to disk), **Delete** (from the session gallery), or **Generate
56
+ another**. A running gallery of the session's images (prompt, thumbnail, seed).
57
+ FastAPI + a self-contained HTML page (same shape as `slide-stream serve`).
58
+ Local-first (binds `127.0.0.1`, auto-opens the browser); optional token when
59
+ hosted.
60
+
61
+ ### v3 — Desktop (Tauri + Python sidecar)
62
+ A native, installable app. Users set **their own image provider** in Settings
63
+ (server URL or API key). Images are saved locally; the app stores nothing
64
+ remotely. Tauri (Rust) shell wrapping the Python core as a sidecar, with the
65
+ runtime bootstrapped on first run via `uv` (thin installer, not a 1GB bundle) —
66
+ the pattern explored for slide-stream. Not on the Mac App Store (runtime
67
+ download rules); distributed as a signed `.dmg` / installer.
68
+
69
+ ### Demo — "try before install"
70
+ A hosted instance (`app.limn.<domain>`) that is **rate-limited and
71
+ non-storing**: generate a few images to try it, nothing saved server-side, with
72
+ a banner inviting users to install locally for full control and privacy. Same
73
+ `--demo` flag idea as slide-stream.
74
+
75
+ ## 4. Providers (v1 set)
76
+
77
+ Generative backends only (Limn *makes* images; it is not a stock-photo
78
+ browser):
79
+
80
+ | Provider | What | Config |
81
+ |---|---|---|
82
+ | `swarmui` | Self-hosted SwarmUI native API | `base_url`, `model`, optional Bearer `api_key` |
83
+ | `openai-compatible` | Any `/v1/images` endpoint (LocalAI, …) | `base_url`, `model`, `api_key` |
84
+ | `openai` / `dalle` | OpenAI Images (DALL·E) | `OPENAI_API_KEY`, `model` |
85
+ | `gemini` / `imagen` | Google Imagen (cheap, ~$0.02/img) | `GEMINI_API_KEY`, `model` |
86
+ | `local` *(later)* | Bundled local diffusion (optional heavy extra) | — |
87
+
88
+ Each provider is a thin client: prompt (+ size/count/seed/negative) → image
89
+ bytes. Modeled on `slide-stream`'s image providers, but **standalone** — Limn
90
+ does not depend on slide-stream (a video tool). If code-sharing becomes
91
+ worthwhile, extract a small shared `image-providers` library later.
92
+
93
+ ## 5. Architecture
94
+
95
+ ```
96
+ limn/ Python package
97
+ providers/ thin per-backend clients (swarmui, openai, gemini, …)
98
+ core.py generate(prompt, opts) -> image bytes / files
99
+ config.py layered config (~/.limn.yaml + ./limn.yaml + env)
100
+ cli.py Typer CLI
101
+ serve.py FastAPI web UI (optional [serve] extra)
102
+ desktop/ Tauri shell + Python sidecar (later)
103
+ ```
104
+
105
+ - **Python 3.10+**, `typer`, `requests`/`httpx`, `pillow`.
106
+ - **Web:** `fastapi` + `uvicorn` behind a `[serve]` extra.
107
+ - **Desktop:** Tauri (Rust) + Python sidecar, `uv`-bootstrapped runtime.
108
+ - **Packaging:** `uv` / hatchling; publish to PyPI (`pip install limn`).
109
+
110
+ ## 6. Config
111
+
112
+ Layered, later wins (mirrors slide-stream): built-in defaults →
113
+ `~/.limn.yaml` (your provider + keys, set once) → `./limn.yaml` → CLI flags.
114
+ Secrets referenced as `${VAR}` so no plaintext keys in files.
115
+
116
+ ```yaml
117
+ provider: swarmui
118
+ base_url: https://image.example.org
119
+ model: juggernautXL_v9
120
+ api_key: "${SWARMUI_TOKEN}"
121
+ size: [1024, 1024]
122
+ ```
123
+
124
+ ## 7. Privacy & governance
125
+
126
+ - **No account. No telemetry.** Nothing is required to run it.
127
+ - **No server-side storage** of prompts or images. Desktop = fully local;
128
+ the demo is ephemeral and rate-limited.
129
+ - **Your provider, your data.** The only network egress is the generation
130
+ request to the provider *you* configured.
131
+ - Clear, short docs on what leaves the machine and when.
132
+
133
+ ## 8. Non-goals
134
+
135
+ - Not a full editing studio (no inpainting / ControlNet / node graphs — use
136
+ ComfyUI/SwarmUI).
137
+ - Not a model host — Limn calls a provider you point it at.
138
+ - Not a prompt-engineering IDE — minimal knobs on purpose.
139
+
140
+ ## 9. Milestones
141
+
142
+ 1. **M1 — CLI:** `limn "prompt" -o out.png`, provider layer (swarmui + one
143
+ cloud), layered config. Publish to PyPI.
144
+ 2. **M2 — Web UI:** `limn serve` with generate / view / save / delete /
145
+ regenerate + session gallery.
146
+ 3. **M3 — Demo:** hosted, rate-limited, non-storing instance + landing page.
147
+ 4. **M4 — Desktop:** Tauri app with BYO-provider settings, local saves.
148
+
149
+ ---
150
+
151
+ *Companion project to [slide-stream](https://github.com/michael-borck/slide-stream),
152
+ which shares the same image-provider approach.*
@@ -0,0 +1,119 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "limn"
7
+ version = "0.1.0"
8
+ description = "A small, privacy-first text-to-image tool: type a prompt, get an image. Bring your own provider."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [
12
+ { name = "Michael Borck", email = "michael.borck@curtin.edu.au" },
13
+ ]
14
+ keywords = ["ai", "image", "text-to-image", "cli", "swarmui", "imagen", "dall-e"]
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Environment :: Console",
18
+ "Intended Audience :: Developers",
19
+ "Intended Audience :: End Users/Desktop",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Operating System :: OS Independent",
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Topic :: Multimedia :: Graphics",
28
+ ]
29
+ requires-python = ">=3.10"
30
+ dependencies = [
31
+ "typer>=0.9.0",
32
+ "rich>=13.0.0",
33
+ "requests>=2.31.0",
34
+ "pyyaml>=6.0.0",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ # Web UI (limn serve) — M2
39
+ serve = [
40
+ "fastapi>=0.110.0",
41
+ "uvicorn>=0.29.0",
42
+ ]
43
+
44
+ [project.scripts]
45
+ limn = "limn.cli:app"
46
+
47
+ [project.urls]
48
+ Homepage = "https://github.com/michael-borck/limn"
49
+ Repository = "https://github.com/michael-borck/limn"
50
+ Issues = "https://github.com/michael-borck/limn/issues"
51
+
52
+ [tool.hatch.build.targets.wheel]
53
+ packages = ["src/limn"]
54
+
55
+ [tool.ruff]
56
+ target-version = "py310"
57
+ line-length = 88
58
+
59
+ [tool.ruff.lint]
60
+ select = [
61
+ "E", # pycodestyle errors
62
+ "W", # pycodestyle warnings
63
+ "F", # pyflakes
64
+ "I", # isort
65
+ "B", # flake8-bugbear
66
+ "C4", # flake8-comprehensions
67
+ "UP", # pyupgrade
68
+ ]
69
+ ignore = [
70
+ "E501", # line too long
71
+ "B008", # function calls in argument defaults (Typer idiom)
72
+ ]
73
+
74
+ [tool.ruff.lint.per-file-ignores]
75
+ "__init__.py" = ["F401"]
76
+
77
+ [tool.ruff.lint.isort]
78
+ known-first-party = ["limn"]
79
+
80
+ [tool.basedpyright]
81
+ include = ["src", "tests"]
82
+ exclude = ["**/__pycache__", "**/._*"]
83
+ venvPath = "."
84
+ venv = ".venv"
85
+ pythonVersion = "3.10"
86
+ pythonPlatform = "All"
87
+ typeCheckingMode = "basic"
88
+ reportMissingImports = true
89
+ reportMissingTypeStubs = false
90
+ reportUnusedImport = true
91
+ reportUnusedClass = true
92
+ reportUnusedFunction = true
93
+ reportUnusedVariable = true
94
+ reportDuplicateImport = true
95
+
96
+ [tool.pytest.ini_options]
97
+ testpaths = ["tests"]
98
+ python_files = ["test_*.py", "*_test.py"]
99
+ addopts = [
100
+ "--strict-markers",
101
+ "--strict-config",
102
+ "--cov=limn",
103
+ "--cov-report=term-missing",
104
+ ]
105
+
106
+ [tool.coverage.run]
107
+ source = ["limn"]
108
+ branch = true
109
+
110
+ [dependency-groups]
111
+ dev = [
112
+ "basedpyright>=1.29.4",
113
+ "build>=1.2.2",
114
+ "pytest>=8.4.0",
115
+ "pytest-cov>=6.0.0",
116
+ "pytest-mock>=3.14.0",
117
+ "ruff>=0.12.0",
118
+ "twine>=6.1.0",
119
+ ]
@@ -0,0 +1,3 @@
1
+ """Limn — a small, privacy-first text-to-image tool."""
2
+
3
+ __version__ = "0.1.0"
@@ -0,0 +1,6 @@
1
+ """Allow `python -m limn`."""
2
+
3
+ from limn.cli import app
4
+
5
+ if __name__ == "__main__":
6
+ app()