lark-cli 0.1.1__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.
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: lark-cli
3
+ Version: 0.1.1
4
+ Summary: A tiny CLI that says Coming soon
5
+ Author: Your Name
6
+ Requires-Python: >=3.7
7
+ Description-Content-Type: text/markdown
8
+
9
+ # lark-cli
10
+
11
+ Python wrapper for the official `larksuite/cli` release binary.
12
+
13
+ ## What it does
14
+
15
+ - Resolves the latest GitHub release from `https://github.com/larksuite/cli`
16
+ - Chooses the current platform asset automatically
17
+ - Runs the extracted `lark-cli` binary
18
+ - Passes all CLI arguments through unchanged
19
+
20
+ ## Usage
21
+
22
+ ```bash
23
+ lark-cli <any-args>
24
+ ```
25
+
26
+ Example:
27
+
28
+ ```bash
29
+ lark-cli api list --page 1
30
+ ```
31
+
32
+ ## Local binary cache location
33
+
34
+ - macOS/Linux: `~/.cache/lark-cli-python-wrapper/current/lark-cli`
35
+ - Windows: `%LOCALAPPDATA%\lark-cli-python-wrapper\current\lark-cli.exe`
36
+
37
+ Set `LARK_CLI_WRAPPER_HOME` to override this location.
38
+
39
+ ## Build & publish to PyPI
40
+
41
+ ```bash
42
+ python3 -m pip install --upgrade build twine
43
+ python3 -m build
44
+ python3 -m twine check dist/*
45
+ ```
46
+
47
+ Before upload, bump `version` in `pyproject.toml` (for example `0.1.1` -> `0.1.2`).
48
+
49
+ > Do not upload with `dist/*` if old files still exist in `dist/`, otherwise PyPI may reject duplicates.
50
+
51
+ Upload only the current version files explicitly:
52
+
53
+ ```bash
54
+ python3 -m twine upload \
55
+ dist/lark_cli-0.1.1-py3-none-any.whl \
56
+ dist/lark-cli-0.1.1.tar.gz \
57
+ --verbose
58
+ ```
@@ -0,0 +1,50 @@
1
+ # lark-cli
2
+
3
+ Python wrapper for the official `larksuite/cli` release binary.
4
+
5
+ ## What it does
6
+
7
+ - Resolves the latest GitHub release from `https://github.com/larksuite/cli`
8
+ - Chooses the current platform asset automatically
9
+ - Runs the extracted `lark-cli` binary
10
+ - Passes all CLI arguments through unchanged
11
+
12
+ ## Usage
13
+
14
+ ```bash
15
+ lark-cli <any-args>
16
+ ```
17
+
18
+ Example:
19
+
20
+ ```bash
21
+ lark-cli api list --page 1
22
+ ```
23
+
24
+ ## Local binary cache location
25
+
26
+ - macOS/Linux: `~/.cache/lark-cli-python-wrapper/current/lark-cli`
27
+ - Windows: `%LOCALAPPDATA%\lark-cli-python-wrapper\current\lark-cli.exe`
28
+
29
+ Set `LARK_CLI_WRAPPER_HOME` to override this location.
30
+
31
+ ## Build & publish to PyPI
32
+
33
+ ```bash
34
+ python3 -m pip install --upgrade build twine
35
+ python3 -m build
36
+ python3 -m twine check dist/*
37
+ ```
38
+
39
+ Before upload, bump `version` in `pyproject.toml` (for example `0.1.1` -> `0.1.2`).
40
+
41
+ > Do not upload with `dist/*` if old files still exist in `dist/`, otherwise PyPI may reject duplicates.
42
+
43
+ Upload only the current version files explicitly:
44
+
45
+ ```bash
46
+ python3 -m twine upload \
47
+ dist/lark_cli-0.1.1-py3-none-any.whl \
48
+ dist/lark-cli-0.1.1.tar.gz \
49
+ --verbose
50
+ ```
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "lark-cli"
7
+ version = "0.1.1"
8
+ description = "A tiny CLI that says Coming soon"
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ authors = [
12
+ { name = "Your Name" }
13
+ ]
14
+
15
+ [project.scripts]
16
+ lark-cli = "lark_cli.cli:main"
17
+
18
+ [tool.pytest.ini_options]
19
+ pythonpath = ["src"]
20
+ testpaths = ["tests"]
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,35 @@
1
+ from pathlib import Path
2
+ import sys
3
+
4
+ from setuptools import setup
5
+ from setuptools.command.develop import develop
6
+ from setuptools.command.install import install
7
+
8
+
9
+ PROJECT_ROOT = Path(__file__).resolve().parent
10
+ SRC_DIR = PROJECT_ROOT / "src"
11
+ sys.path.insert(0, str(SRC_DIR))
12
+
13
+
14
+ class InstallWithBinary(install):
15
+ def run(self):
16
+ super().run()
17
+ from lark_cli.install_hooks import download_release_during_install
18
+
19
+ download_release_during_install()
20
+
21
+
22
+ class DevelopWithBinary(develop):
23
+ def run(self):
24
+ super().run()
25
+ from lark_cli.install_hooks import download_release_during_install
26
+
27
+ download_release_during_install()
28
+
29
+
30
+ setup(
31
+ cmdclass={
32
+ "install": InstallWithBinary,
33
+ "develop": DevelopWithBinary,
34
+ }
35
+ )
@@ -0,0 +1,3 @@
1
+ __all__ = ["main"]
2
+
3
+ from .cli import main
@@ -0,0 +1,10 @@
1
+ import subprocess
2
+ import sys
3
+
4
+ from .release import ensure_installed_binary
5
+
6
+
7
+ def main() -> None:
8
+ binary_path = ensure_installed_binary()
9
+ result = subprocess.run([binary_path, *sys.argv[1:]])
10
+ raise SystemExit(result.returncode)
@@ -0,0 +1,5 @@
1
+ from .release import ensure_installed_binary
2
+
3
+
4
+ def download_release_during_install() -> None:
5
+ ensure_installed_binary()
@@ -0,0 +1,146 @@
1
+ import json
2
+ import os
3
+ import shutil
4
+ import tarfile
5
+ import tempfile
6
+ import time
7
+ import urllib.request
8
+ import zipfile
9
+ from pathlib import Path
10
+ from platform import machine, system
11
+ from stat import S_IXGRP, S_IXOTH, S_IXUSR
12
+ from typing import Dict, Iterable, Tuple
13
+
14
+
15
+ LATEST_RELEASE_API = "https://api.github.com/repos/larksuite/cli/releases/latest"
16
+
17
+
18
+ def normalize_platform(raw_system: str, raw_machine: str) -> Tuple[str, str, str]:
19
+ os_name = raw_system.lower()
20
+ if os_name not in {"darwin", "linux", "windows"}:
21
+ raise RuntimeError(f"Unsupported OS: {raw_system}")
22
+
23
+ normalized_machine = raw_machine.lower()
24
+ if normalized_machine in {"x86_64", "amd64"}:
25
+ arch = "amd64"
26
+ elif normalized_machine in {"arm64", "aarch64"}:
27
+ arch = "arm64"
28
+ else:
29
+ raise RuntimeError(f"Unsupported architecture: {raw_machine}")
30
+
31
+ extension = ".zip" if os_name == "windows" else ".tar.gz"
32
+ return os_name, arch, extension
33
+
34
+
35
+ def choose_asset(assets: Iterable[Dict[str, str]], os_name: str, arch: str, extension: str) -> Dict[str, str]:
36
+ target_suffix = f"-{os_name}-{arch}{extension}"
37
+ for asset in assets:
38
+ name = asset.get("name", "")
39
+ if name.startswith("lark-cli-") and name.endswith(target_suffix):
40
+ return asset
41
+ raise RuntimeError(f"No matching release asset for {os_name}/{arch}{extension}")
42
+
43
+
44
+ def _fetch_latest_release() -> Dict[str, object]:
45
+ request = urllib.request.Request(
46
+ LATEST_RELEASE_API,
47
+ headers={"Accept": "application/vnd.github+json"},
48
+ )
49
+ with urllib.request.urlopen(request, timeout=20) as response:
50
+ payload = json.loads(response.read().decode("utf-8"))
51
+ return payload
52
+
53
+
54
+ def _default_install_root() -> Path:
55
+ custom_root = os.environ.get("LARK_CLI_WRAPPER_HOME")
56
+ if custom_root:
57
+ return Path(custom_root)
58
+
59
+ home = Path.home()
60
+ if os.name == "nt":
61
+ base = Path(os.environ.get("LOCALAPPDATA", home))
62
+ else:
63
+ base = Path(os.environ.get("XDG_CACHE_HOME", home / ".cache"))
64
+ return base / "lark-cli-python-wrapper"
65
+
66
+
67
+ def _find_binary(extracted_dir: Path, binary_name: str) -> Path:
68
+ for root, _, files in os.walk(extracted_dir):
69
+ if binary_name in files:
70
+ return Path(root) / binary_name
71
+ raise RuntimeError(f"Binary '{binary_name}' not found in extracted release archive")
72
+
73
+
74
+ def _extract_archive(archive_path: Path, destination: Path) -> None:
75
+ if archive_path.name.endswith(".tar.gz"):
76
+ with tarfile.open(archive_path, mode="r:gz") as tar:
77
+ tar.extractall(destination)
78
+ return
79
+
80
+ if archive_path.name.endswith(".zip"):
81
+ with zipfile.ZipFile(archive_path, mode="r") as archive:
82
+ archive.extractall(destination)
83
+ return
84
+
85
+ raise RuntimeError(f"Unsupported archive format: {archive_path.name}")
86
+
87
+
88
+ def _download_file(url: str, destination: Path) -> None:
89
+ request = urllib.request.Request(
90
+ url,
91
+ headers={
92
+ "Accept": "application/octet-stream",
93
+ "User-Agent": "lark-cli-python-wrapper",
94
+ },
95
+ )
96
+
97
+ last_error = None
98
+ for _ in range(3):
99
+ try:
100
+ with urllib.request.urlopen(request, timeout=60) as response, destination.open("wb") as file_obj:
101
+ shutil.copyfileobj(response, file_obj)
102
+ return
103
+ except Exception as error: # noqa: BLE001
104
+ last_error = error
105
+ time.sleep(1)
106
+
107
+ if last_error is not None:
108
+ raise last_error
109
+
110
+
111
+ def ensure_installed_binary(force: bool = False) -> str:
112
+ os_name, arch, extension = normalize_platform(system(), machine())
113
+ binary_name = "lark-cli.exe" if os_name == "windows" else "lark-cli"
114
+
115
+ install_root = _default_install_root()
116
+ target_dir = install_root / "current"
117
+ binary_path = target_dir / binary_name
118
+
119
+ if binary_path.exists() and not force:
120
+ return str(binary_path)
121
+
122
+ release = _fetch_latest_release()
123
+ assets = release.get("assets", [])
124
+ if not isinstance(assets, list):
125
+ raise RuntimeError("Release payload missing assets list")
126
+ selected_asset = choose_asset(assets, os_name, arch, extension)
127
+
128
+ target_dir.mkdir(parents=True, exist_ok=True)
129
+
130
+ with tempfile.TemporaryDirectory() as temp_dir:
131
+ temp_path = Path(temp_dir)
132
+ archive_path = temp_path / selected_asset["name"]
133
+ _download_file(selected_asset["browser_download_url"], archive_path)
134
+
135
+ extract_dir = temp_path / "extract"
136
+ extract_dir.mkdir(parents=True, exist_ok=True)
137
+ _extract_archive(archive_path, extract_dir)
138
+ extracted_binary = _find_binary(extract_dir, binary_name)
139
+
140
+ shutil.copy2(extracted_binary, binary_path)
141
+
142
+ if os_name != "windows":
143
+ current_mode = binary_path.stat().st_mode
144
+ binary_path.chmod(current_mode | S_IXUSR | S_IXGRP | S_IXOTH)
145
+
146
+ return str(binary_path)
@@ -0,0 +1,58 @@
1
+ Metadata-Version: 2.4
2
+ Name: lark-cli
3
+ Version: 0.1.1
4
+ Summary: A tiny CLI that says Coming soon
5
+ Author: Your Name
6
+ Requires-Python: >=3.7
7
+ Description-Content-Type: text/markdown
8
+
9
+ # lark-cli
10
+
11
+ Python wrapper for the official `larksuite/cli` release binary.
12
+
13
+ ## What it does
14
+
15
+ - Resolves the latest GitHub release from `https://github.com/larksuite/cli`
16
+ - Chooses the current platform asset automatically
17
+ - Runs the extracted `lark-cli` binary
18
+ - Passes all CLI arguments through unchanged
19
+
20
+ ## Usage
21
+
22
+ ```bash
23
+ lark-cli <any-args>
24
+ ```
25
+
26
+ Example:
27
+
28
+ ```bash
29
+ lark-cli api list --page 1
30
+ ```
31
+
32
+ ## Local binary cache location
33
+
34
+ - macOS/Linux: `~/.cache/lark-cli-python-wrapper/current/lark-cli`
35
+ - Windows: `%LOCALAPPDATA%\lark-cli-python-wrapper\current\lark-cli.exe`
36
+
37
+ Set `LARK_CLI_WRAPPER_HOME` to override this location.
38
+
39
+ ## Build & publish to PyPI
40
+
41
+ ```bash
42
+ python3 -m pip install --upgrade build twine
43
+ python3 -m build
44
+ python3 -m twine check dist/*
45
+ ```
46
+
47
+ Before upload, bump `version` in `pyproject.toml` (for example `0.1.1` -> `0.1.2`).
48
+
49
+ > Do not upload with `dist/*` if old files still exist in `dist/`, otherwise PyPI may reject duplicates.
50
+
51
+ Upload only the current version files explicitly:
52
+
53
+ ```bash
54
+ python3 -m twine upload \
55
+ dist/lark_cli-0.1.1-py3-none-any.whl \
56
+ dist/lark-cli-0.1.1.tar.gz \
57
+ --verbose
58
+ ```
@@ -0,0 +1,15 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ src/lark_cli/__init__.py
5
+ src/lark_cli/cli.py
6
+ src/lark_cli/install_hooks.py
7
+ src/lark_cli/release.py
8
+ src/lark_cli.egg-info/PKG-INFO
9
+ src/lark_cli.egg-info/SOURCES.txt
10
+ src/lark_cli.egg-info/dependency_links.txt
11
+ src/lark_cli.egg-info/entry_points.txt
12
+ src/lark_cli.egg-info/top_level.txt
13
+ tests/test_cli.py
14
+ tests/test_install_hooks.py
15
+ tests/test_release.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ lark-cli = lark_cli.cli:main
@@ -0,0 +1 @@
1
+ lark_cli
@@ -0,0 +1,27 @@
1
+ import pytest
2
+
3
+ from lark_cli import cli
4
+
5
+
6
+ def test_main_forwards_all_args_to_downloaded_binary(monkeypatch):
7
+ captured = {}
8
+
9
+ def fake_ensure_installed_binary():
10
+ return "/tmp/lark-cli"
11
+
12
+ class Result:
13
+ returncode = 7
14
+
15
+ def fake_run(cmd):
16
+ captured["cmd"] = cmd
17
+ return Result()
18
+
19
+ monkeypatch.setattr(cli, "ensure_installed_binary", fake_ensure_installed_binary)
20
+ monkeypatch.setattr(cli.subprocess, "run", fake_run)
21
+ monkeypatch.setattr(cli.sys, "argv", ["lark-cli", "im", "send", "--text", "hello"])
22
+
23
+ with pytest.raises(SystemExit) as raised:
24
+ cli.main()
25
+
26
+ assert raised.value.code == 7
27
+ assert captured["cmd"] == ["/tmp/lark-cli", "im", "send", "--text", "hello"]
@@ -0,0 +1,13 @@
1
+ from lark_cli import install_hooks
2
+
3
+
4
+ def test_install_hook_triggers_download(monkeypatch):
5
+ called = {"value": False}
6
+
7
+ def fake_download():
8
+ called["value"] = True
9
+
10
+ monkeypatch.setattr(install_hooks, "ensure_installed_binary", fake_download)
11
+ install_hooks.download_release_during_install()
12
+
13
+ assert called["value"] is True
@@ -0,0 +1,38 @@
1
+ from lark_cli.release import choose_asset, normalize_platform
2
+ from lark_cli import release
3
+
4
+
5
+ def test_normalize_platform_for_macos_arm64():
6
+ os_name, arch, ext = normalize_platform("Darwin", "arm64")
7
+ assert os_name == "darwin"
8
+ assert arch == "arm64"
9
+ assert ext == ".tar.gz"
10
+
11
+
12
+ def test_choose_asset_for_linux_amd64():
13
+ assets = [
14
+ {"name": "lark-cli-1.0.0-darwin-arm64.tar.gz", "browser_download_url": "a"},
15
+ {"name": "lark-cli-1.0.0-linux-amd64.tar.gz", "browser_download_url": "b"},
16
+ ]
17
+
18
+ selected = choose_asset(assets, "linux", "amd64", ".tar.gz")
19
+ assert selected["browser_download_url"] == "b"
20
+
21
+
22
+ def test_ensure_binary_uses_cached_file_without_network(monkeypatch, tmpdir):
23
+ root = tmpdir.mkdir("wrapper-home")
24
+ current_dir = root.mkdir("current")
25
+ binary_path = current_dir.join("lark-cli")
26
+ binary_path.write("#!/bin/sh\n")
27
+
28
+ monkeypatch.setenv("LARK_CLI_WRAPPER_HOME", str(root))
29
+ monkeypatch.setattr(release, "system", lambda: "Darwin")
30
+ monkeypatch.setattr(release, "machine", lambda: "arm64")
31
+
32
+ def fail_fetch():
33
+ raise AssertionError("network should not be called when cached binary exists")
34
+
35
+ monkeypatch.setattr(release, "_fetch_latest_release", fail_fetch)
36
+
37
+ resolved = release.ensure_installed_binary()
38
+ assert resolved == str(binary_path)