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.
- leitum-0.1.0/.claude/history/260613-1700-Initial-Prompt-DE.md +43 -0
- leitum-0.1.0/.claude/history/260613-1701-Initial-Prompt-EN.md +38 -0
- leitum-0.1.0/.claude/history/260613-1729-Initial-Review.md +260 -0
- leitum-0.1.0/.github/workflows/ci.yml +42 -0
- leitum-0.1.0/.github/workflows/release.yml +31 -0
- leitum-0.1.0/.gitignore +9 -0
- leitum-0.1.0/CHANGELOG.md +24 -0
- leitum-0.1.0/CLAUDE.md +132 -0
- leitum-0.1.0/LICENSE +164 -0
- leitum-0.1.0/NOTICE +4 -0
- leitum-0.1.0/PKG-INFO +151 -0
- leitum-0.1.0/README.md +116 -0
- leitum-0.1.0/SECURITY.md +22 -0
- leitum-0.1.0/docs/commands.md +111 -0
- leitum-0.1.0/docs/configuration.md +93 -0
- leitum-0.1.0/docs/getting-started.md +70 -0
- leitum-0.1.0/docs/providers/requesty.md +38 -0
- leitum-0.1.0/docs/troubleshooting.md +77 -0
- leitum-0.1.0/prd/00-overview.md +101 -0
- leitum-0.1.0/prd/01-configuration.md +310 -0
- leitum-0.1.0/prd/02-cli.md +167 -0
- leitum-0.1.0/prd/03-selection-flows.md +216 -0
- leitum-0.1.0/prd/04-launch.md +94 -0
- leitum-0.1.0/prd/05-management-commands.md +152 -0
- leitum-0.1.0/prd/06-testing.md +122 -0
- leitum-0.1.0/prd/07-packaging-and-docs.md +192 -0
- leitum-0.1.0/pyproject.toml +74 -0
- leitum-0.1.0/src/leitum/__init__.py +3 -0
- leitum-0.1.0/src/leitum/__main__.py +3 -0
- leitum-0.1.0/src/leitum/cli.py +238 -0
- leitum-0.1.0/src/leitum/commands/__init__.py +0 -0
- leitum-0.1.0/src/leitum/commands/claude.py +182 -0
- leitum-0.1.0/src/leitum/commands/doctor.py +185 -0
- leitum-0.1.0/src/leitum/commands/init.py +30 -0
- leitum-0.1.0/src/leitum/commands/provider.py +256 -0
- leitum-0.1.0/src/leitum/commands/refresh.py +44 -0
- leitum-0.1.0/src/leitum/config/__init__.py +21 -0
- leitum-0.1.0/src/leitum/config/env.py +26 -0
- leitum-0.1.0/src/leitum/config/io.py +81 -0
- leitum-0.1.0/src/leitum/config/models.py +88 -0
- leitum-0.1.0/src/leitum/config/paths.py +35 -0
- leitum-0.1.0/src/leitum/config/permissions.py +29 -0
- leitum-0.1.0/src/leitum/launch.py +165 -0
- leitum-0.1.0/src/leitum/providers/__init__.py +3 -0
- leitum-0.1.0/src/leitum/providers/cache.py +71 -0
- leitum-0.1.0/src/leitum/providers/discovery.py +64 -0
- leitum-0.1.0/src/leitum/selection/__init__.py +3 -0
- leitum-0.1.0/src/leitum/selection/interactive.py +98 -0
- leitum-0.1.0/src/leitum/selection/resolver.py +233 -0
- leitum-0.1.0/src/leitum/state.py +93 -0
- leitum-0.1.0/tests/conftest.py +109 -0
- leitum-0.1.0/tests/integration/__init__.py +0 -0
- leitum-0.1.0/tests/integration/test_end_to_end.py +192 -0
- leitum-0.1.0/tests/unit/__init__.py +0 -0
- leitum-0.1.0/tests/unit/commands/__init__.py +0 -0
- leitum-0.1.0/tests/unit/config/__init__.py +0 -0
- leitum-0.1.0/tests/unit/config/test_env.py +44 -0
- leitum-0.1.0/tests/unit/config/test_models.py +101 -0
- leitum-0.1.0/tests/unit/config/test_permissions.py +35 -0
- leitum-0.1.0/tests/unit/providers/__init__.py +0 -0
- leitum-0.1.0/tests/unit/providers/test_discovery.py +145 -0
- leitum-0.1.0/tests/unit/selection/__init__.py +0 -0
- leitum-0.1.0/tests/unit/selection/test_resolver.py +128 -0
- leitum-0.1.0/tests/unit/test_launch.py +111 -0
- leitum-0.1.0/tests/unit/test_state.py +60 -0
- 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/*
|
leitum-0.1.0/.gitignore
ADDED
|
@@ -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.
|