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.
Files changed (52) hide show
  1. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/PKG-INFO +29 -1
  2. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/README.md +28 -0
  3. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/pyproject.toml +1 -1
  4. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/mcp.py +31 -10
  5. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_cli.py +26 -0
  6. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/uv.lock +1 -1
  7. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/.env.example +0 -0
  8. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/.gitattributes +0 -0
  9. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/.github/workflows/publish.yml +0 -0
  10. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/.github/workflows/test.yml +0 -0
  11. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/.gitignore +0 -0
  12. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/LICENSE +0 -0
  13. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/docs/tui-screenshot.svg +0 -0
  14. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/__init__.py +0 -0
  15. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/__main__.py +0 -0
  16. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/cli.py +0 -0
  17. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/__init__.py +0 -0
  18. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/_common.py +0 -0
  19. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/articles.py +0 -0
  20. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/contacts.py +0 -0
  21. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/delivery_notes.py +0 -0
  22. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/invoices.py +0 -0
  23. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/orders.py +0 -0
  24. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/quotations.py +0 -0
  25. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/commands/vouchers.py +0 -0
  26. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/config.py +0 -0
  27. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/core/__init__.py +0 -0
  28. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/core/client.py +0 -0
  29. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/core/constants.py +0 -0
  30. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/core/errors.py +0 -0
  31. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/core/models.py +0 -0
  32. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/core/services.py +0 -0
  33. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/mcp_server.py +0 -0
  34. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/output.py +0 -0
  35. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/tui/__init__.py +0 -0
  36. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/tui/app.py +0 -0
  37. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/tui/detail.py +0 -0
  38. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/tui/entities.py +0 -0
  39. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/src/lxw_cli/tui/order_form.py +0 -0
  40. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/__init__.py +0 -0
  41. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/conftest.py +0 -0
  42. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/snapshots/contacts_flat.txt +0 -0
  43. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/snapshots/contacts_grouped.txt +0 -0
  44. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/snapshots/invoices_list.txt +0 -0
  45. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_cli_snapshots.py +0 -0
  46. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_client.py +0 -0
  47. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_config.py +0 -0
  48. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_mcp_server.py +0 -0
  49. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_output.py +0 -0
  50. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_resolve.py +0 -0
  51. {lxw_cli-0.1.2 → lxw_cli-0.1.3}/tests/test_services.py +0 -0
  52. {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.2
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>
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "lxw-cli"
7
- version = "0.1.2"
7
+ version = "0.1.3"
8
8
  description = "Inoffizielles CLI/TUI/MCP-Tool für die Lexware Office API (vormals lexoffice)"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -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
- if sys.platform == "darwin":
35
- return Path.home() / "Library" / "Application Support" / "Claude" / (
36
- "claude_desktop_config.json"
37
- )
38
- if sys.platform == "win32":
39
- appdata = os.environ.get("APPDATA")
40
- root = Path(appdata) if appdata else Path.home() / "AppData" / "Roaming"
41
- return root / "Claude" / "claude_desktop_config.json"
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)]
@@ -744,7 +744,7 @@ wheels = [
744
744
 
745
745
  [[package]]
746
746
  name = "lxw-cli"
747
- version = "0.1.2"
747
+ version = "0.1.3"
748
748
  source = { editable = "." }
749
749
  dependencies = [
750
750
  { name = "fastmcp" },
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