lxw-cli 0.1.2__tar.gz → 0.1.3__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.
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/PKG-INFO +29 -1
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/README.md +28 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/pyproject.toml +1 -1
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/mcp.py +31 -10
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_cli.py +26 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/uv.lock +1 -1
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/.env.example +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/.gitattributes +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/.github/workflows/publish.yml +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/.github/workflows/test.yml +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/.gitignore +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/LICENSE +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/docs/tui-screenshot.svg +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/__init__.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/__main__.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/cli.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/__init__.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/_common.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/articles.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/contacts.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/delivery_notes.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/invoices.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/orders.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/quotations.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/vouchers.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/config.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/core/__init__.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/core/client.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/core/constants.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/core/errors.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/core/models.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/core/services.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/mcp_server.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/output.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/tui/__init__.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/tui/app.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/tui/detail.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/tui/entities.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/tui/order_form.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/__init__.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/conftest.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/snapshots/contacts_flat.txt +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/snapshots/contacts_grouped.txt +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/snapshots/invoices_list.txt +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_cli_snapshots.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_client.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_config.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_mcp_server.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_output.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_resolve.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_services.py +0 -0
- {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_tui.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lxw-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3
|
|
4
4
|
Summary: Inoffizielles CLI/TUI/MCP-Tool für die Lexware Office API (vormals lexoffice)
|
|
5
5
|
Project-URL: Homepage, https://github.com/OEM-Mrks/lxw-cli
|
|
6
6
|
Project-URL: Documentation, https://developers.lexware.io/
|
|
@@ -87,6 +87,25 @@ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | ie
|
|
|
87
87
|
Ab dem nächsten cmd-Fenster reicht dann überall einfach `lxw`. Der API-Key
|
|
88
88
|
wird beim ersten Start abgefragt und unter `%APPDATA%\lexware\.env` gespeichert.
|
|
89
89
|
|
|
90
|
+
### Update
|
|
91
|
+
|
|
92
|
+
Auf die neueste Version von PyPI:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
uv tool upgrade lxw-cli # oder: pipx upgrade lxw-cli
|
|
96
|
+
lxw --version # Kontrolle
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Sieht uv eine frisch veröffentlichte Version noch nicht (das PyPI-CDN braucht
|
|
100
|
+
nach einem Release ein paar Minuten), erzwingt `--refresh` den Index-Abgleich:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
uv tool upgrade --refresh lxw-cli
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Der MCP-Server für Claude nutzt das Update automatisch mit — bereits laufende
|
|
107
|
+
Claude-Sessions müssen dafür einmal neu gestartet werden.
|
|
108
|
+
|
|
90
109
|
### Aus dem Quellcode (Entwicklung)
|
|
91
110
|
|
|
92
111
|
```bash
|
|
@@ -96,6 +115,15 @@ uv venv && source .venv/bin/activate
|
|
|
96
115
|
uv pip install -e ".[dev]"
|
|
97
116
|
```
|
|
98
117
|
|
|
118
|
+
Lokalen Arbeitsstand ohne Release als globales `lxw` installieren:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
uv tool install --force --reinstall .
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
(`--reinstall` ist nötig — bei unveränderter Versionsnummer nimmt uv sonst
|
|
125
|
+
den gecachten Build.)
|
|
126
|
+
|
|
99
127
|
## API-Key konfigurieren
|
|
100
128
|
|
|
101
129
|
Lege einen API-Key in deinem Lexware-Account an: <https://app.lexware.de/addons/public-api>
|
|
@@ -52,6 +52,25 @@ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | ie
|
|
|
52
52
|
Ab dem nächsten cmd-Fenster reicht dann überall einfach `lxw`. Der API-Key
|
|
53
53
|
wird beim ersten Start abgefragt und unter `%APPDATA%\lexware\.env` gespeichert.
|
|
54
54
|
|
|
55
|
+
### Update
|
|
56
|
+
|
|
57
|
+
Auf die neueste Version von PyPI:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
uv tool upgrade lxw-cli # oder: pipx upgrade lxw-cli
|
|
61
|
+
lxw --version # Kontrolle
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Sieht uv eine frisch veröffentlichte Version noch nicht (das PyPI-CDN braucht
|
|
65
|
+
nach einem Release ein paar Minuten), erzwingt `--refresh` den Index-Abgleich:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
uv tool upgrade --refresh lxw-cli
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Der MCP-Server für Claude nutzt das Update automatisch mit — bereits laufende
|
|
72
|
+
Claude-Sessions müssen dafür einmal neu gestartet werden.
|
|
73
|
+
|
|
55
74
|
### Aus dem Quellcode (Entwicklung)
|
|
56
75
|
|
|
57
76
|
```bash
|
|
@@ -61,6 +80,15 @@ uv venv && source .venv/bin/activate
|
|
|
61
80
|
uv pip install -e ".[dev]"
|
|
62
81
|
```
|
|
63
82
|
|
|
83
|
+
Lokalen Arbeitsstand ohne Release als globales `lxw` installieren:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
uv tool install --force --reinstall .
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
(`--reinstall` ist nötig — bei unveränderter Versionsnummer nimmt uv sonst
|
|
90
|
+
den gecachten Build.)
|
|
91
|
+
|
|
64
92
|
## API-Key konfigurieren
|
|
65
93
|
|
|
66
94
|
Lege einen API-Key in deinem Lexware-Account an: <https://app.lexware.de/addons/public-api>
|
|
@@ -21,25 +21,46 @@ app = typer.Typer(no_args_is_help=True)
|
|
|
21
21
|
MCP_NAME = "lexware"
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
def _desktop_config_dirs() -> list[Path]:
|
|
25
|
+
"""Candidate directories for Claude Desktop's configuration, per platform.
|
|
26
|
+
|
|
27
|
+
On Windows the classic install uses %APPDATA%\\Claude; the Microsoft-Store
|
|
28
|
+
build (MSIX) virtualizes AppData under
|
|
29
|
+
%LOCALAPPDATA%\\Packages\\Claude_<hash>\\LocalCache\\Roaming\\Claude.
|
|
30
|
+
"""
|
|
31
|
+
if sys.platform == "darwin":
|
|
32
|
+
return [Path.home() / "Library" / "Application Support" / "Claude"]
|
|
33
|
+
if sys.platform == "win32":
|
|
34
|
+
appdata = os.environ.get("APPDATA")
|
|
35
|
+
root = Path(appdata) if appdata else Path.home() / "AppData" / "Roaming"
|
|
36
|
+
dirs = [root / "Claude"]
|
|
37
|
+
local = os.environ.get("LOCALAPPDATA")
|
|
38
|
+
lroot = Path(local) if local else Path.home() / "AppData" / "Local"
|
|
39
|
+
dirs += sorted((lroot / "Packages").glob("Claude_*/LocalCache/Roaming/Claude"))
|
|
40
|
+
return dirs
|
|
41
|
+
return [Path.home() / ".config" / "Claude"]
|
|
42
|
+
|
|
43
|
+
|
|
24
44
|
def desktop_config_path() -> Path:
|
|
25
45
|
"""Location of Claude Desktop's `claude_desktop_config.json`.
|
|
26
46
|
|
|
27
47
|
Claude Desktop (and Cowork) read MCP servers from this file — registering
|
|
28
48
|
via `claude mcp add` only covers Claude Code. `CLAUDE_DESKTOP_CONFIG`
|
|
29
|
-
overrides the path (mostly for tests).
|
|
49
|
+
overrides the path (mostly for tests). Among the platform candidates the
|
|
50
|
+
one already holding a config file wins, then the first existing directory,
|
|
51
|
+
then the classic default.
|
|
30
52
|
"""
|
|
31
53
|
override = os.environ.get("CLAUDE_DESKTOP_CONFIG")
|
|
32
54
|
if override:
|
|
33
55
|
return Path(override).expanduser()
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return Path.home() / ".config" / "Claude" / "claude_desktop_config.json"
|
|
56
|
+
candidates = _desktop_config_dirs()
|
|
57
|
+
for d in candidates:
|
|
58
|
+
if (d / "claude_desktop_config.json").is_file():
|
|
59
|
+
return d / "claude_desktop_config.json"
|
|
60
|
+
for d in candidates:
|
|
61
|
+
if d.is_dir():
|
|
62
|
+
return d / "claude_desktop_config.json"
|
|
63
|
+
return candidates[0] / "claude_desktop_config.json"
|
|
43
64
|
|
|
44
65
|
|
|
45
66
|
def _load_desktop_config(path: Path) -> dict[str, Any]:
|
|
@@ -156,6 +156,32 @@ def test_mcp_install_desktop_leaves_broken_json_untouched(
|
|
|
156
156
|
assert desktop_config.read_text(encoding="utf-8") == "{not json"
|
|
157
157
|
|
|
158
158
|
|
|
159
|
+
def test_desktop_config_path_finds_windows_store_install(
|
|
160
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path
|
|
161
|
+
) -> None:
|
|
162
|
+
"""The MSIX build virtualizes AppData under Packages/Claude_<hash>/…."""
|
|
163
|
+
import lxw_cli.commands.mcp as mcp_cmd_mod
|
|
164
|
+
|
|
165
|
+
monkeypatch.delenv("CLAUDE_DESKTOP_CONFIG", raising=False)
|
|
166
|
+
monkeypatch.setattr(mcp_cmd_mod.sys, "platform", "win32")
|
|
167
|
+
monkeypatch.setenv("APPDATA", str(tmp_path / "Roaming"))
|
|
168
|
+
monkeypatch.setenv("LOCALAPPDATA", str(tmp_path / "Local"))
|
|
169
|
+
store_dir = (
|
|
170
|
+
tmp_path / "Local" / "Packages" / "Claude_pzs8sxrjxfjjc"
|
|
171
|
+
/ "LocalCache" / "Roaming" / "Claude"
|
|
172
|
+
)
|
|
173
|
+
store_dir.mkdir(parents=True)
|
|
174
|
+
|
|
175
|
+
# Only the Store dir exists → it wins over the (missing) classic path.
|
|
176
|
+
assert mcp_cmd_mod.desktop_config_path() == store_dir / "claude_desktop_config.json"
|
|
177
|
+
|
|
178
|
+
# A config file in the classic location takes precedence once present.
|
|
179
|
+
classic = tmp_path / "Roaming" / "Claude"
|
|
180
|
+
classic.mkdir(parents=True)
|
|
181
|
+
(classic / "claude_desktop_config.json").write_text("{}", encoding="utf-8")
|
|
182
|
+
assert mcp_cmd_mod.desktop_config_path() == classic / "claude_desktop_config.json"
|
|
183
|
+
|
|
184
|
+
|
|
159
185
|
@respx.mock
|
|
160
186
|
def test_invoices_list_default_caps_at_25(runner: CliRunner) -> None:
|
|
161
187
|
page0 = [{"id": f"a{i}"} for i in range(100)]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|