leitum 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.
Files changed (66) hide show
  1. leitum-0.1.0/.claude/history/260613-1700-Initial-Prompt-DE.md +43 -0
  2. leitum-0.1.0/.claude/history/260613-1701-Initial-Prompt-EN.md +38 -0
  3. leitum-0.1.0/.claude/history/260613-1729-Initial-Review.md +260 -0
  4. leitum-0.1.0/.github/workflows/ci.yml +42 -0
  5. leitum-0.1.0/.github/workflows/release.yml +31 -0
  6. leitum-0.1.0/.gitignore +9 -0
  7. leitum-0.1.0/CHANGELOG.md +24 -0
  8. leitum-0.1.0/CLAUDE.md +132 -0
  9. leitum-0.1.0/LICENSE +164 -0
  10. leitum-0.1.0/NOTICE +4 -0
  11. leitum-0.1.0/PKG-INFO +151 -0
  12. leitum-0.1.0/README.md +116 -0
  13. leitum-0.1.0/SECURITY.md +22 -0
  14. leitum-0.1.0/docs/commands.md +111 -0
  15. leitum-0.1.0/docs/configuration.md +93 -0
  16. leitum-0.1.0/docs/getting-started.md +70 -0
  17. leitum-0.1.0/docs/providers/requesty.md +38 -0
  18. leitum-0.1.0/docs/troubleshooting.md +77 -0
  19. leitum-0.1.0/prd/00-overview.md +101 -0
  20. leitum-0.1.0/prd/01-configuration.md +310 -0
  21. leitum-0.1.0/prd/02-cli.md +167 -0
  22. leitum-0.1.0/prd/03-selection-flows.md +216 -0
  23. leitum-0.1.0/prd/04-launch.md +94 -0
  24. leitum-0.1.0/prd/05-management-commands.md +152 -0
  25. leitum-0.1.0/prd/06-testing.md +122 -0
  26. leitum-0.1.0/prd/07-packaging-and-docs.md +192 -0
  27. leitum-0.1.0/pyproject.toml +74 -0
  28. leitum-0.1.0/src/leitum/__init__.py +3 -0
  29. leitum-0.1.0/src/leitum/__main__.py +3 -0
  30. leitum-0.1.0/src/leitum/cli.py +238 -0
  31. leitum-0.1.0/src/leitum/commands/__init__.py +0 -0
  32. leitum-0.1.0/src/leitum/commands/claude.py +182 -0
  33. leitum-0.1.0/src/leitum/commands/doctor.py +185 -0
  34. leitum-0.1.0/src/leitum/commands/init.py +30 -0
  35. leitum-0.1.0/src/leitum/commands/provider.py +256 -0
  36. leitum-0.1.0/src/leitum/commands/refresh.py +44 -0
  37. leitum-0.1.0/src/leitum/config/__init__.py +21 -0
  38. leitum-0.1.0/src/leitum/config/env.py +26 -0
  39. leitum-0.1.0/src/leitum/config/io.py +81 -0
  40. leitum-0.1.0/src/leitum/config/models.py +88 -0
  41. leitum-0.1.0/src/leitum/config/paths.py +35 -0
  42. leitum-0.1.0/src/leitum/config/permissions.py +29 -0
  43. leitum-0.1.0/src/leitum/launch.py +165 -0
  44. leitum-0.1.0/src/leitum/providers/__init__.py +3 -0
  45. leitum-0.1.0/src/leitum/providers/cache.py +71 -0
  46. leitum-0.1.0/src/leitum/providers/discovery.py +64 -0
  47. leitum-0.1.0/src/leitum/selection/__init__.py +3 -0
  48. leitum-0.1.0/src/leitum/selection/interactive.py +98 -0
  49. leitum-0.1.0/src/leitum/selection/resolver.py +233 -0
  50. leitum-0.1.0/src/leitum/state.py +93 -0
  51. leitum-0.1.0/tests/conftest.py +109 -0
  52. leitum-0.1.0/tests/integration/__init__.py +0 -0
  53. leitum-0.1.0/tests/integration/test_end_to_end.py +192 -0
  54. leitum-0.1.0/tests/unit/__init__.py +0 -0
  55. leitum-0.1.0/tests/unit/commands/__init__.py +0 -0
  56. leitum-0.1.0/tests/unit/config/__init__.py +0 -0
  57. leitum-0.1.0/tests/unit/config/test_env.py +44 -0
  58. leitum-0.1.0/tests/unit/config/test_models.py +101 -0
  59. leitum-0.1.0/tests/unit/config/test_permissions.py +35 -0
  60. leitum-0.1.0/tests/unit/providers/__init__.py +0 -0
  61. leitum-0.1.0/tests/unit/providers/test_discovery.py +145 -0
  62. leitum-0.1.0/tests/unit/selection/__init__.py +0 -0
  63. leitum-0.1.0/tests/unit/selection/test_resolver.py +128 -0
  64. leitum-0.1.0/tests/unit/test_launch.py +111 -0
  65. leitum-0.1.0/tests/unit/test_state.py +60 -0
  66. leitum-0.1.0/uv.lock +710 -0
@@ -0,0 +1,43 @@
1
+ Ich möchte ein kleines CLI Tool in Python erstellen, dass einen Coding Agenten wie Claude Code so launcht, dass alternative LLM Router und Modelle verwendet werden können.
2
+
3
+ Ein gutes Beispiel für das was ich möchte ist z.B. das der Ollama CLI Befehlt `ollama launch`, den ich in der Kommandozeile vor `claude` einfüge und damit auf lokale Ollama Modelle statt Anthropic Modelle und APIs zugreife - siehe auch https://docs.ollama.com/integrations/claude-code.
4
+
5
+ Ein anderes Wertkzeug, dass das als eine Funktion hat ist das der CLI von OMLX (https://github.com/jundot/omlx). Das Tool besitzt ebenfalls einen `launch` Befehl hat. Das CLI ist selber in Python geschrieben, hier ist der relevante Code für den Launch von Claude Code mit so angepasstem Environment, dass aben die durch OMLX bereitgestellten Modelle ausgewählt und verwendet werden können.
6
+
7
+ Mein CLI Tool soll `leitum` heißen und nach dem Vorbild der obigen Beispiele funktionieren, d.h. mit dem Aufruf `leitum claude` Claude Code mit einem vorgegeben, konfigurierten API-Provider aufrufen. Für den Start soll erst einmal nur Claude Code unterstützt werden, andere Tools wie copilot-cli, opencode etc. folgen vielleicht später.
8
+
9
+ Hier meine funktionalen Ideen:
10
+
11
+ * Konfiguration über YAML, mit Konfigurationsdateien in $HOME/.config/leitum
12
+ * `api-providers.yaml` zum Hinterlegen von Providern mit Zugangsdaten. Jeder Provider hat einen Identifier/Namen, eine URL und ein API Token. Zusätzlich *kann* eine Liste der Modelle hinterlegt werden, pro Modell mit der Angabe des technischen Modellnamen sowie (optional) eines Anzeigenamens.
13
+ * Der Referenzprovider für die erste Iteration des Projektes ist Requesty.ai. Die Standard Claude Code Integration von Requesty ist hier beschrieben: https://docs.requesty.ai/integrations/claude-code
14
+ * Das Tool speichert die zuletzt gesetzten Kontext-Parameter in seinem Config-Verzeichnis, z.B. den zuletzt ausgewählten Provider. Hier denke ich an eine ähnliche Verwaltung wie einen Kubernetes kubectl context.yaml, wo mehrere Kontexte hinterlegt werden können und ein current-context Feld den zuletzt gesetzten Kontext sich merkt und beim nächsten Start widerverwendet.
15
+ * Ein Provider kann mit dem Parameter `--provider <name>` bzw. `-p <name>` vorgegeben werden. Wenn das nicht geschieht und außerdem mehr als ein Provider hinterlegt ist, dann soll eine Auswahldialog per Curses oder ähnlichem angezeigt werden, um einen Provider zu wählen. Der zuletzt gewählte ist immer vorbelegt. Jede Auswahl im der Kontext-Config ("current-context") gespeichert für den nächsten Start. Wenn nur ein Provider da ist, wird dieser automatisch verwendet. `--use-last-provider` bzw. `-P` überspringt den Provider-Auswahldialog und benutzt den zuletzt ausgewählen.
16
+ * Danach erfolgt die Modellauswahl, ähnlich im grundsätzlichen Vorgehen
17
+ * Verfügbare Modelle sind entweder die beim Provider als Liste der Verfügbaren per API Call erhaltenen, oder die in `api-providers.yaml` vorgegebenen. Wenn es vorgegebene git, haben die immer Vorrang und die Liste der verfügbaren wird nicht abgerufen bzw. ignoriert.
18
+ * die Modellauswahl erfolgt wieder über eine curses Liste, wenn nicht schon vorgegeben über die folgend beschriebenen Parameter und natürlich nur, wenn mehr als ein Modell verfügbar ist. Vorbelegt ist immer die letzte Auswahl, falls vorhanden. Auswählbar sind
19
+ * START-MODELL, entspricht dem Modell das beim Aufruf von `claude` mit dem `--model` Parameter übergeben wird
20
+ * OPUS-MODELL, zum Setzen der Environment Variable `ANTHROPIC_DEFAULT_OPUS_MODEL` für den Start von `claude`
21
+ * SONNET-MODELL, zum Setzen der Environment Variable `ANTHROPIC_DEFAULT_SONNET_MODEL` für den Start von `claude`
22
+ * HAIKU-MODELL, zum Setzen der Environment Variable `ANTHROPIC_DEFAULT_HAIKU_MODEL` für den Start von `claude`
23
+ * `--model <name>` bzw. `-m <name>` wählt alternativ das START-MODELL direkt aus. `--use-last-model` bzw. `-M` wählt, falls vorhanden, das letzte Modell aus und führt zum Überspringen der interaktiven Modellauswahl
24
+ * `--opus <name>` bzw. `-o <name>` wählt alternativ das OPUS-MODELL direkt aus. `--use-last-opus` bzw. `-O` wählt, falls vorhanden, das letzte Modell aus
25
+ * `--sonnet <name>` bzw. `-s <name>` wählt alternativ das SONNET-MODELL direkt aus. `--use-last-sonnet` bzw. `-S` wählt, falls vorhanden, das letzte Modell aus
26
+ * `--haiku <name>` bzw. `-h <name>` wählt alternativ das HAIKU-MODELL direkt aus. `--use-last-haiku` bzw. `-H` wählt, falls vorhanden, das letzte Modell aus
27
+ * nach erfolgten Vorgaben launcht claude mit der gewählten Umgebungsvorgabe.
28
+
29
+ Weitere nicht-fachliche Ideen und Vorgaben:
30
+ * Sprache für Dokumentation, Commit-Messages, Pull Requests und ähnliches ist Englisch
31
+ * Sprache für PRDs ist die des Prompts, also jetzt gerade Deutsch
32
+ * das Tool soll auf Pypi veröffentlicht werden und per uvx oder Homebrew ausgeführt bzw. installiert werden können. Referenzsystem ist zunächst ein aktuelles macOS.
33
+ * Die Dokumentation des Tools soll in Markdown gestaltet sein, so dass bei einer Veröffentlichung auf GitHub ein sofortiger Einstig in die Dokumentation erfolgen kann
34
+
35
+ Bitte mache folgendes:
36
+ 1. durchleuchte die gemachten Vorgaben und hinterfrage diese auf Konsistenz und Sinnhaftigkeit. Mache Verbesserungsvorschläge, wenn passend. Diskutiere intensiv alle offenen Fragen, Unklarheiten und Entscheidungen mit mir. Benutze die erarbeiteten Erkenntnisse für die folgenden Aufgaben
37
+ 2. Erstelle eine CLAUDE.md, die alle grundsätzlichen Vorgaben für das Projekt enthält. Halte diese so ausführlich wie nötig und so knapp wie möglich. Halte darin auch fest, dass das der gesamte Kommunikationsstil im Projekt sein soll.
38
+ 3. Erstelle danach einen detaillierten Umsetzungsplan in Form von PRD-Dokumenten. Das beinhaltet den Produktiven Code, Tests und Dokumentation. Stelle Fragen, wenn nötig. Setze noch nichts aus den erstellten PRDs um.
39
+
40
+
41
+
42
+
43
+
@@ -0,0 +1,38 @@
1
+ I want to build a small Python CLI tool that launches a coding agent such as Claude Code in a way that allows alternative LLM routers and models to be used.
2
+
3
+ A good example of what I want is the Ollama CLI command `ollama launch`, which I prepend to `claude` on the command line so that I access local Ollama models instead of Anthropic models and APIs — see also https://docs.ollama.com/integrations/claude-code.
4
+
5
+ Another tool that has this as a feature is the CLI of OMLX (https://github.com/jundot/omlx). It also has a `launch` command. The CLI itself is written in Python; here is the relevant code for launching Claude Code with an adjusted environment so that the models provided by OMLX can be selected and used.
6
+
7
+ My CLI tool shall be called `leitum` and work along the lines of the examples above, i.e. invoking `leitum claude` to call Claude Code with a pre-configured API provider. To begin with, only Claude Code shall be supported; other tools such as copilot-cli, opencode, etc. may follow later.
8
+
9
+ Here are my functional ideas:
10
+
11
+ * Configuration via YAML, with configuration files located in `$HOME/.config/leitum`
12
+ * `api-providers.yaml` to register providers along with their credentials. Each provider has an identifier/name, a URL, and an API token. Additionally, a list of models *may* be provided, with each model specifying the technical model name and (optionally) a display name.
13
+ * The reference provider for the first iteration of the project is Requesty.ai. The standard Claude Code integration for Requesty is described here: https://docs.requesty.ai/integrations/claude-code
14
+ * The tool stores the most recently set context parameters in its config directory, e.g. the last selected provider. I'm thinking of a management approach similar to a Kubernetes kubectl `context.yaml`, where multiple contexts can be stored and a `current-context` field remembers the most recently set context and reuses it on the next start.
15
+ * A provider can be specified via the parameter `--provider <name>` or `-p <name>`. If this is not done and more than one provider is registered, a selection dialog (via Curses or similar) shall be shown to pick a provider. The most recently chosen one is always pre-selected. Each selection is stored in the context config (`current-context`) for the next start. If only one provider is present, it is used automatically. `--use-last-provider` or `-P` skips the provider-selection dialog and uses the most recently chosen provider.
16
+ * After that, the model selection happens, similar in its basic approach:
17
+ * Available models are either those retrieved from the provider as a list of available models via an API call, or those pre-configured in `api-providers.yaml`. If pre-configured ones exist, they always take precedence and the list of available models is not retrieved or is ignored.
18
+ * The model selection again uses a curses list, unless already specified via the parameters described below, and of course only if more than one model is available. The most recent selection is always pre-selected, if one exists. The selectable items are:
19
+ * START MODEL — corresponds to the model passed to `claude` via the `--model` parameter
20
+ * OPUS MODEL — sets the environment variable `ANTHROPIC_DEFAULT_OPUS_MODEL` for the `claude` start
21
+ * SONNET MODEL — sets the environment variable `ANTHROPIC_DEFAULT_SONNET_MODEL` for the `claude` start
22
+ * HAIKU MODEL — sets the environment variable `ANTHROPIC_DEFAULT_HAIKU_MODEL` for the `claude` start
23
+ * `--model <name>` or `-m <name>` alternatively selects the START MODEL directly. `--use-last-model` or `-M`, if available, selects the last model and skips the interactive model selection.
24
+ * `--opus <name>` or `-o <name>` alternatively selects the OPUS MODEL directly. `--use-last-opus` or `-O`, if available, selects the last model.
25
+ * `--sonnet <name>` or `-s <name>` alternatively selects the SONNET MODEL directly. `--use-last-sonnet` or `-S`, if available, selects the last model.
26
+ * `--haiku <name>` or `-h <name>` alternatively selects the HAIKU MODEL directly. `--use-last-haiku` or `-H`, if available, selects the last model.
27
+ * Once the specifications are in place, `claude` is launched with the chosen environment configuration.
28
+
29
+ Further non-functional ideas and requirements:
30
+ * The language for documentation, commit messages, pull requests, and similar artifacts is English.
31
+ * The language for PRDs is the language of the prompt — at this moment, German.
32
+ * The tool shall be published on PyPI and be runnable/installable via `uvx` or Homebrew. The reference system is, for now, a current version of macOS.
33
+ * The tool's documentation shall be authored in Markdown so that, when published on GitHub, the documentation is immediately accessible.
34
+
35
+ Please do the following:
36
+ 1. Scrutinize the requirements above and challenge them for consistency and soundness. Make improvement suggestions where appropriate. Discuss intensively with me all open questions, ambiguities, and decisions. Use the insights gained for the subsequent tasks.
37
+ 2. Create a `CLAUDE.md` that contains all foundational requirements for the project. Keep it as detailed as necessary and as concise as possible. Also state therein that this is meant to be the overall communication style for the project.
38
+ 3. Afterwards, create a detailed implementation plan in the form of PRD documents. This includes production code, tests, and documentation. Ask questions if needed. Do not yet implement anything from the created PRDs.
@@ -0,0 +1,260 @@
1
+ # Initial Specification Review — 2026-06-13
2
+
3
+ This document captures the review of the initial product specification for
4
+ `leitum` (a Python CLI that launches Claude Code against alternative LLM
5
+ routers), the open questions that emerged from the review, and the
6
+ pre-decisions that were used to author `CLAUDE.md` and the PRDs in `prd/`.
7
+
8
+ ## Context
9
+
10
+ - Original specification: `intial-prompt.md` (German, user-authored).
11
+ - Outputs of this session: `CLAUDE.md` (English) and PRDs `00`–`07` in
12
+ `prd/` (German).
13
+ - No code was written in this session — pre-implementation alignment only.
14
+
15
+ ## Review findings
16
+
17
+ ### 1. Flag collisions
18
+
19
+ - `-h` for `--haiku` collides with the universal `--help` short flag, which
20
+ every standard CLI framework reserves.
21
+ - `-p` for `--provider` collides with Claude Code's own `-p`/`--print`
22
+ short flag, which would create ambiguity when users want to use Claude
23
+ Code's print mode through `leitum`.
24
+
25
+ ### 2. Authentication mechanics
26
+
27
+ - The original specification did not fix which environment variable carries
28
+ the auth token. Requesty uses `ANTHROPIC_AUTH_TOKEN`; other Anthropic-
29
+ compatible routers may use `ANTHROPIC_API_KEY` or other names.
30
+ - A pre-existing `ANTHROPIC_API_KEY` on the user's shell could silently win
31
+ over the provider's auth value if not actively scrubbed before launch.
32
+
33
+ ### 3. Token storage
34
+
35
+ - Storing API tokens in plain text in `~/.config/leitum/api-providers.yaml`
36
+ is a security risk and forecloses sharing the config across machines.
37
+ - The specification did not mention permission modes for any of the on-disk
38
+ files.
39
+
40
+ ### 4. "Context" semantics
41
+
42
+ - The spec referenced kubectl-style contexts but described behavior that
43
+ matches a simple "remember last selection" model, not the kubectl model
44
+ (which is a named bundle plus a `current-context` pointer).
45
+ - Two distinct interpretations existed: (A) remember last selection only,
46
+ or (B) named, switchable profiles. Implementation effort differs by
47
+ roughly 3×.
48
+
49
+ ### 5. Model-slot UX
50
+
51
+ - The spec implied four sequential interactive selections per launch
52
+ (start, opus, sonnet, haiku), which is tedious for daily use.
53
+ - "Last selection" was implied as a single global value, but each provider
54
+ has a different model space, so "last model" only makes sense per
55
+ provider.
56
+ - The spec did not say what happens if a slot remains unset — does `claude`
57
+ launch with no `--model`? Does the env var get set to an empty value?
58
+
59
+ ### 6. Model discovery via API
60
+
61
+ - The spec said "fetch the model list from the provider," but did not
62
+ specify the endpoint, caching behavior, or fallback on failure. Different
63
+ providers expose different endpoints (OpenAI-compatible `/v1/models`,
64
+ Ollama `/api/tags`, etc.).
65
+
66
+ ### 7. Curses or higher-level UI?
67
+
68
+ - "Curses" was named explicitly, but raw curses is verbose and brittle on
69
+ modern terminals. Several high-quality TTY-prompt libraries exist that
70
+ remove most of the friction.
71
+
72
+ ### 8. Lifecycle commands
73
+
74
+ - The spec described `leitum claude` but not how the user creates,
75
+ inspects, or removes providers. A bare-YAML workflow is viable but
76
+ hostile to first-time users.
77
+
78
+ ### 9. Pass-through arguments
79
+
80
+ - The spec did not explicitly define what should happen to extra arguments
81
+ after `claude`. Without a rule, an invocation like `leitum claude
82
+ --resume` would be ambiguous.
83
+
84
+ ### 10. Language ambiguity for CLAUDE.md
85
+
86
+ - The spec set "documentation → English, PRDs → German" but did not
87
+ classify `CLAUDE.md` itself.
88
+
89
+ ### 11. Minor items
90
+
91
+ - Naming "START-MODELL" as a slot is awkward outside German; "main/primary"
92
+ reads better in English documentation, but the slot identifier should
93
+ stay machine-stable.
94
+ - No `--dry-run` or `--verbose` modes were specified, though both are
95
+ cheap and standard.
96
+ - Python minimum version, license, build backend, packaging tools were not
97
+ pinned.
98
+
99
+ ## Decisions taken to author CLAUDE.md and the PRDs
100
+
101
+ The decisions below were either confirmed interactively with the user or
102
+ taken autonomously where the trade-off was small. Decisions confirmed by
103
+ the user are marked **[user]**; decisions taken autonomously and reported
104
+ back are marked **[auto]**.
105
+
106
+ ### Flag layout and pass-through **[user]**
107
+
108
+ - `leitum`-own options must appear **before** the subcommand name; every
109
+ argument after `claude` is forwarded unchanged to the `claude` binary.
110
+ - This eliminates the `-p` collision entirely: leitum's `-p` is consumed
111
+ before `claude` ever sees it, and `claude`'s `-p` lives in the
112
+ pass-through bucket.
113
+
114
+ ### Haiku short flags **[user, revised]**
115
+
116
+ - `--haiku` short flag: `-k`.
117
+ - `--use-last-haiku` short flag: `-K`.
118
+ - `-h` remains `--help` per universal convention.
119
+ - (Initial draft used "no short flag" for `--haiku`; user revised to
120
+ `-k`/`-K`.)
121
+
122
+ ### Context model **[user]**
123
+
124
+ - v1 implements **last-selection-only**. No named profiles.
125
+ - `state.yaml` stores `last_provider` plus, per provider, the last
126
+ selected model for each of the four slots.
127
+
128
+ ### Model selection UX **[user]**
129
+
130
+ - A **single dialog** with four slot rows, not four sequential selects.
131
+ - Each slot can be set to "do not set", which omits the corresponding
132
+ env var (and, for `start`, omits `--model` entirely).
133
+ - Pre-population per slot: explicit flag → `--use-last-*` → provider
134
+ defaults → last state → `roles` match → "do not set".
135
+
136
+ ### Interactive prompt library **[user]**
137
+
138
+ - `questionary` instead of raw curses. The selection-algorithm logic is
139
+ factored out of the UI layer so it remains unit-testable without a TTY.
140
+
141
+ ### Authentication scheme **[auto]**
142
+
143
+ - Per provider, `auth.token` plus an optional `auth.env_var` (default
144
+ `ANTHROPIC_AUTH_TOKEN`). Tokens support `${VAR}` interpolation against
145
+ the live shell environment so users can keep secrets outside the file.
146
+ - Before launch, `ANTHROPIC_API_KEY` is actively removed from the child
147
+ environment unless the active provider's `auth.env_var` is exactly
148
+ `ANTHROPIC_API_KEY`.
149
+
150
+ ### File security **[auto]**
151
+
152
+ - `api-providers.yaml` and `state.yaml`: mode `0600`.
153
+ - Config directories: mode `0700`.
154
+ - On read, a permissions check warns if the file is more permissive than
155
+ `0600` but does not silently rewrite.
156
+ - Tokens never appear in stdout, stderr, or any log line — including with
157
+ `--verbose`. `leitum provider show --reveal-token` is the single opt-in
158
+ path to display a token, with confirmation and a stderr warning.
159
+
160
+ ### Model discovery **[auto]**
161
+
162
+ - API contract for v1: OpenAI-compatible `GET /v1/models` with bearer auth.
163
+ - Cache: `~/.cache/leitum/models/<provider>.json`, 24 hour TTL, JSON.
164
+ - `leitum refresh` clears the cache and re-fetches.
165
+ - On HTTP failure: stale cache is acceptable; without any cache, the
166
+ command errors with a clear hint and exits with code 4.
167
+ - If the YAML defines a `models` list for the provider, the API is never
168
+ contacted for that provider.
169
+
170
+ ### Slot default behavior **[auto]**
171
+
172
+ - An unset `start` slot launches `claude` without `--model` — Claude Code
173
+ uses its own default.
174
+ - Unset `opus` / `sonnet` / `haiku` slots simply omit the corresponding
175
+ `ANTHROPIC_DEFAULT_*_MODEL` env var.
176
+
177
+ ### "Last model" scoping **[auto]**
178
+
179
+ - The "last selection" for each model slot is stored **per provider**, not
180
+ globally. Otherwise `--use-last-model` would suggest nonsense after a
181
+ provider switch.
182
+
183
+ ### Management subcommands **[auto]**
184
+
185
+ - `leitum init`, `leitum provider {list,show,add,remove}`,
186
+ `leitum refresh`, `leitum doctor`, `leitum completions` are in scope for
187
+ v1.
188
+ - `leitum doctor` is read-only diagnostics: it reports, never repairs.
189
+
190
+ ### Diagnostics flags **[auto]**
191
+
192
+ - `--dry-run` prints the resolved child environment (token values
193
+ redacted) and the final exec line, then exits without launching.
194
+ - `--verbose` / `-v` writes progress events to stderr, including resolved
195
+ provider, set env-var names (never values), and the exec line.
196
+
197
+ ### Tech-stack choices **[auto]**
198
+
199
+ - Python 3.11+.
200
+ - CLI: `typer`. Prompts: `questionary`. Validation: `pydantic` v2.
201
+ - YAML I/O: `ruamel.yaml` (preserves comments and order on write).
202
+ - HTTP: `httpx` (sync client by default).
203
+ - Tests: `pytest` + `pytest-mock` + `respx` + `freezegun`.
204
+ - Linting / formatting: `ruff`. Static typing: `mypy --strict`.
205
+ - Build backend: `hatchling`. Dev workflow: `uv`.
206
+
207
+ ### Distribution **[auto]**
208
+
209
+ - Primary: PyPI as `leitum`, runnable through `uvx leitum`.
210
+ - Secondary: dedicated Homebrew tap. Homebrew-core submission is deferred.
211
+ - macOS is the reference platform; Linux must stay functional; Windows is
212
+ not supported in v1.
213
+
214
+ ### License **[user, revised]**
215
+
216
+ - **Apache 2.0**. SPDX identifier `Apache-2.0` in `pyproject.toml`,
217
+ `LICENSE` and `NOTICE` files at the repository root.
218
+ - (Initial draft used MIT; user revised to Apache 2.0.)
219
+
220
+ ### Language convention for CLAUDE.md **[auto]**
221
+
222
+ - `CLAUDE.md` is English, in line with the broader "documentation is
223
+ English" rule. PRDs remain German, per explicit user instruction.
224
+
225
+ ### Exit codes **[auto]**
226
+
227
+ - A defined ladder: 0 success, 2 argument error, 3 configuration error,
228
+ 4 unresolved provider/model, 5 `claude` binary missing, 130 user
229
+ cancellation, otherwise pass-through from the launched process.
230
+
231
+ ### Schema versioning **[auto]**
232
+
233
+ - Both `api-providers.yaml` and `state.yaml` carry `schema_version: 1`.
234
+ Migrations land in `leitum/config/migrations/` with a `.bak` snapshot
235
+ before any in-place rewrite.
236
+
237
+ ## Items intentionally deferred
238
+
239
+ The following were considered and explicitly placed in the roadmap rather
240
+ than v1:
241
+
242
+ - macOS Keychain integration for token storage.
243
+ - Named, kubectl-style contexts/profiles.
244
+ - Additional agents (`copilot-cli`, `opencode`, `gemini-cli`).
245
+ - Provider presets beyond Requesty (OpenRouter, LiteLLM, Ollama).
246
+ - Homebrew-core submission.
247
+ - A `mkdocs`-based documentation site (v1 ships Markdown in `docs/`).
248
+
249
+ ## Artifacts produced this session
250
+
251
+ - `CLAUDE.md`
252
+ - `prd/00-overview.md`
253
+ - `prd/01-configuration.md`
254
+ - `prd/02-cli.md`
255
+ - `prd/03-selection-flows.md`
256
+ - `prd/04-launch.md`
257
+ - `prd/05-management-commands.md`
258
+ - `prd/06-testing.md`
259
+ - `prd/07-packaging-and-docs.md`
260
+ - `.claude/history/260613-1729-Initial-Review.md` (this file)
@@ -0,0 +1,42 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ test:
11
+ name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
12
+ runs-on: ${{ matrix.os }}
13
+ strategy:
14
+ matrix:
15
+ python-version: ["3.11", "3.12", "3.13"]
16
+ os: [macos-latest, ubuntu-latest]
17
+
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Install uv
22
+ uses: astral-sh/setup-uv@v4
23
+ with:
24
+ version: "latest"
25
+
26
+ - name: Set up Python ${{ matrix.python-version }}
27
+ run: uv python install ${{ matrix.python-version }}
28
+
29
+ - name: Install dependencies
30
+ run: uv sync --dev
31
+
32
+ - name: Ruff lint
33
+ run: uv run ruff check src/ tests/
34
+
35
+ - name: Ruff format check
36
+ run: uv run ruff format --check src/ tests/
37
+
38
+ - name: Mypy
39
+ run: uv run mypy src/
40
+
41
+ - name: Pytest
42
+ run: uv run pytest
@@ -0,0 +1,31 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ permissions:
9
+ id-token: write
10
+ contents: write
11
+
12
+ jobs:
13
+ release:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v4
20
+
21
+ - name: Build
22
+ run: uv build
23
+
24
+ - name: Publish to PyPI
25
+ uses: pypa/gh-action-pypi-publish@release/v1
26
+
27
+ - name: Create GitHub Release
28
+ uses: softprops/action-gh-release@v2
29
+ with:
30
+ generate_release_notes: true
31
+ files: dist/*
@@ -0,0 +1,9 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ *.egg-info/
9
+ .pytest_cache/
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5
+
6
+ ## [Unreleased]
7
+
8
+ ## [0.1.0] - 2026-06-14
9
+
10
+ ### Added
11
+ - `leitum claude` — launch Claude Code via a configured provider
12
+ - Provider configuration in `~/.config/leitum/api-providers.yaml`
13
+ - Interactive provider and model selection with `questionary`
14
+ - State persistence to `~/.local/state/leitum/state.yaml`
15
+ - Project-local config via `leitum.yaml`
16
+ - Model discovery via API with 24-hour cache
17
+ - `leitum init` — initialize config
18
+ - `leitum provider list/show/add/remove` — manage providers
19
+ - `leitum refresh` — refresh model cache
20
+ - `leitum doctor` — sanity check suite
21
+ - `leitum completions` — shell completion scripts
22
+ - `--dry-run` and `--verbose` flags
23
+ - Full test suite (unit + integration)
24
+ - CI via GitHub Actions (Python 3.11–3.13, macOS + Linux)
leitum-0.1.0/CLAUDE.md ADDED
@@ -0,0 +1,132 @@
1
+ # leitum — Agent Working Instructions
2
+
3
+ `leitum` is a small Python CLI that launches coding agents (initially Claude Code)
4
+ against alternative LLM routers/providers (initially Requesty). It mirrors the
5
+ ergonomics of `ollama launch` and `omlx launch`: the user runs `leitum claude` and
6
+ leitum prepares the environment so that Claude Code talks to the chosen provider
7
+ and models instead of the Anthropic API.
8
+
9
+ This file is loaded automatically by Claude Code at session start. Read it first.
10
+
11
+ ## Languages
12
+
13
+ - **Code, documentation, comments, README, commit messages, PR titles/bodies, issue
14
+ titles/bodies, code review comments**: English.
15
+ - **Product Requirements Documents (PRDs in `prd/`)**: German. They reflect the
16
+ language of the original prompt.
17
+ - **Conversation with the user**: whatever language the user writes in. The current
18
+ user writes German; respond in German unless asked otherwise.
19
+
20
+ When in doubt: code-facing artifacts are English, product-thinking artifacts are
21
+ German.
22
+
23
+ ## Product scope (v1)
24
+
25
+ - One agent: Claude Code.
26
+ - One reference provider: [Requesty](https://docs.requesty.ai/integrations/claude-code).
27
+ The configuration model must already generalize to other Anthropic-compatible
28
+ routers (OpenRouter, LiteLLM, local Ollama in Anthropic-shim mode).
29
+ - Configuration in `~/.config/leitum/`, runtime state in `~/.local/state/leitum/`
30
+ (XDG), model cache in `~/.cache/leitum/`. An optional project-local
31
+ `leitum.yaml` in the current working directory pins provider/model choices
32
+ per repository (see PRD 01 and PRD 03). It is checked into version control
33
+ and must never contain tokens.
34
+ - Interactive TTY selection where ambiguous; non-interactive when fully specified
35
+ by flags or by a single-option configuration.
36
+
37
+ Out of scope for v1: copilot-cli, opencode, other agents; macOS Keychain;
38
+ named/multi-context profiles (kubectl-style); GUI; Windows support.
39
+
40
+ ## Tech stack
41
+
42
+ - Python 3.11+.
43
+ - CLI framework: [`typer`](https://typer.tiangolo.com/).
44
+ - Interactive prompts: [`questionary`](https://questionary.readthedocs.io/).
45
+ - Config validation: [`pydantic` v2](https://docs.pydantic.dev/).
46
+ - YAML: [`ruamel.yaml`](https://yaml.readthedocs.io/) (preserves comments/order).
47
+ - HTTP for model discovery: [`httpx`](https://www.python-httpx.org/).
48
+ - Tests: `pytest`, `pytest-mock`, fake `claude` binary via fixture.
49
+ - Build backend: `hatchling` with `pyproject.toml`.
50
+ - Package/dev workflow: `uv` (lockfile, sync, run).
51
+ - License: Apache 2.0.
52
+
53
+ ## Distribution
54
+
55
+ - Primary: PyPI as `leitum`, runnable via `uvx leitum`.
56
+ - Secondary: Homebrew via a dedicated tap (`brew tap <owner>/leitum`).
57
+ Homebrew-core submission is a later goal, not v1.
58
+ - Reference platform: current macOS. Linux must keep working; Windows is not
59
+ supported in v1.
60
+
61
+ ## CLI shape
62
+
63
+ ```
64
+ leitum [LEITUM_OPTS] <subcommand> [SUBCOMMAND_ARGS_PASSED_THROUGH]
65
+ ```
66
+
67
+ - `leitum claude [...]` launches Claude Code. Every argument that follows
68
+ `claude` is passed through to the `claude` binary unchanged. Leitum's own
69
+ flags must appear **before** the subcommand name.
70
+ - Management subcommands (`leitum provider …`, `leitum doctor`, `leitum refresh`,
71
+ `leitum init`) are first-class and do not pass through.
72
+ - Use `--dry-run` to print the resolved environment and final exec line without
73
+ launching. Use `-v`/`--verbose` for progress logging on stderr.
74
+
75
+ ## Behavioral rules
76
+
77
+ - **Never** write a token, API key, or any secret to logs, stdout, or to files
78
+ outside `~/.config/leitum/api-providers.yaml`. Redact in verbose output.
79
+ - Files in `~/.config/leitum/` must be created with mode `0600`. If an existing
80
+ file has weaker permissions, warn and offer to fix; do not silently rewrite.
81
+ - Before launching, strip a host-inherited `ANTHROPIC_API_KEY` from the child
82
+ environment so it cannot override the provider's auth.
83
+ - The user's last interactive selections (provider, and per-provider models) are
84
+ persisted to `~/.local/state/leitum/state.yaml`. Treat this file as cache:
85
+ recoverable on loss, schema-versioned (`schema_version: 1`).
86
+ - Model discovery via API is **only** used if the provider has no model list in
87
+ YAML. Results are cached under `~/.cache/leitum/models/<provider>.json` with a
88
+ 24h TTL; `leitum refresh` clears the cache.
89
+
90
+ ## Coding conventions
91
+
92
+ - Type hints on all public functions. `mypy --strict` should pass.
93
+ - Format with `ruff format`, lint with `ruff check`. Keep both clean before
94
+ commit.
95
+ - Module layout under `src/leitum/`:
96
+ - `cli.py` (typer app, root command, pass-through plumbing)
97
+ - `config/` (pydantic models, YAML I/O, paths, env interpolation)
98
+ - `state.py` (last-used persistence)
99
+ - `providers/` (model discovery, HTTP, cache)
100
+ - `selection/` (provider + model interactive selection)
101
+ - `launch.py` (env composition, exec)
102
+ - `commands/` (subcommand handlers: claude, provider, doctor, refresh, init)
103
+ - Tests in `tests/`, mirroring the source tree.
104
+ - No emojis in code or docs.
105
+ - Comments only when the *why* is non-obvious. Don't comment what the code says.
106
+
107
+ ## Git workflow
108
+
109
+ - `main` is the default branch and always green.
110
+ - Feature branches: `feat/<topic>`, `fix/<topic>`, `docs/<topic>`.
111
+ - Commit messages: imperative, English, Conventional-Commits style
112
+ (`feat: …`, `fix: …`, `docs: …`, `test: …`, `refactor: …`, `chore: …`).
113
+ - One logical change per commit. Squash trivia before opening a PR.
114
+ - PR titles in the same Conventional-Commits style; PR bodies in English with a
115
+ short summary and a test plan checklist.
116
+
117
+ ## When you make changes
118
+
119
+ 1. If you change observable CLI behavior, update `README.md` and any affected
120
+ PRD.
121
+ 2. If you change a YAML schema, bump the schema version and document the
122
+ migration in the PRD for configuration.
123
+ 3. Run `ruff format`, `ruff check`, `mypy --strict`, and `pytest` before
124
+ declaring a task done. State explicitly when any check is skipped and why.
125
+ 4. Manual smoke test for Claude-launch changes: `leitum --dry-run claude` and
126
+ verify the printed environment matches expectations.
127
+
128
+ ## PRDs
129
+
130
+ The authoritative product specification lives in `prd/`. Read the matching PRD
131
+ before changing the corresponding area of the code, and update it when the
132
+ product behavior changes.