litert-cli 0.1.0__py3-none-any.whl

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 (67) hide show
  1. examples/litert_cli.ipynb +313 -0
  2. examples/models/presets/default.py +19 -0
  3. examples/run_cli_demo.sh +38 -0
  4. examples/run_cli_npu.sh +89 -0
  5. examples/run_commands.sh +67 -0
  6. examples/run_models.sh +63 -0
  7. examples/run_smoke_tests.sh +58 -0
  8. examples/utils.ps1 +163 -0
  9. examples/utils.sh +184 -0
  10. litert_cli/__init__.py +15 -0
  11. litert_cli/commands/benchmark/__init__.py +16 -0
  12. litert_cli/commands/benchmark/android.py +212 -0
  13. litert_cli/commands/benchmark/cli.py +294 -0
  14. litert_cli/commands/benchmark/desktop.py +228 -0
  15. litert_cli/commands/benchmark/gcp.py +336 -0
  16. litert_cli/commands/clean.py +73 -0
  17. litert_cli/commands/compile.py +211 -0
  18. litert_cli/commands/convert/__init__.py +20 -0
  19. litert_cli/commands/convert/cli.py +255 -0
  20. litert_cli/commands/convert/generic.py +211 -0
  21. litert_cli/commands/convert/huggingface.py +175 -0
  22. litert_cli/commands/delete.py +56 -0
  23. litert_cli/commands/download.py +274 -0
  24. litert_cli/commands/import.py +124 -0
  25. litert_cli/commands/list.py +132 -0
  26. litert_cli/commands/lm.py +74 -0
  27. litert_cli/commands/quantize.py +193 -0
  28. litert_cli/commands/run/__init__.py +16 -0
  29. litert_cli/commands/run/android.py +394 -0
  30. litert_cli/commands/run/cli.py +297 -0
  31. litert_cli/commands/run/desktop.py +340 -0
  32. litert_cli/commands/visualize.py +234 -0
  33. litert_cli/core/android_utils.py +304 -0
  34. litert_cli/core/android_utils_test.py +236 -0
  35. litert_cli/core/constants.py +131 -0
  36. litert_cli/core/deps.py +180 -0
  37. litert_cli/core/deps_test.py +101 -0
  38. litert_cli/core/inputs.py +203 -0
  39. litert_cli/core/inputs_test.py +176 -0
  40. litert_cli/core/log_filters.py +50 -0
  41. litert_cli/core/models.py +96 -0
  42. litert_cli/core/npu_utils.py +382 -0
  43. litert_cli/core/targets_manager.py +192 -0
  44. litert_cli/core/utils.py +58 -0
  45. litert_cli/litert.py +119 -0
  46. litert_cli/litert_help_test.py +51 -0
  47. litert_cli/litert_test.py +88 -0
  48. litert_cli/models/__init__.py +145 -0
  49. litert_cli/models/asr/__init__.py +15 -0
  50. litert_cli/models/asr/asr_model.py +108 -0
  51. litert_cli/models/asr/parakeet_ctc.py +165 -0
  52. litert_cli/models/asr/runner.py +482 -0
  53. litert_cli/models/base.py +57 -0
  54. litert_cli/test_data/dummy_calib_data.py +26 -0
  55. litert_cli/test_data/dummy_cv_model.py +52 -0
  56. litert_cli/test_data/dummy_cv_model.tflite +0 -0
  57. litert_cli/test_data/generate_test_inputs.py +51 -0
  58. litert_cli/test_data/mobilenet_v3_calib_data.py +25 -0
  59. litert_cli/test_data/quantize_recipe.json +16 -0
  60. litert_cli/test_data/resnet18.py +31 -0
  61. litert_cli-0.1.0.dist-info/METADATA +38 -0
  62. litert_cli-0.1.0.dist-info/RECORD +67 -0
  63. litert_cli-0.1.0.dist-info/WHEEL +5 -0
  64. litert_cli-0.1.0.dist-info/entry_points.txt +2 -0
  65. litert_cli-0.1.0.dist-info/licenses/LICENSE +202 -0
  66. litert_cli-0.1.0.dist-info/top_level.txt +3 -0
  67. tools/build_wheels.py +122 -0
@@ -0,0 +1,274 @@
1
+ # Copyright 2026 The LiteRT CLI Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+
16
+ """Command line interface for downloading LiteRT models from huggingface.
17
+
18
+ External packages like requests, or huggingface_hub are optional and will be
19
+ auto-installed when the command is invoked.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import datetime
25
+ import json
26
+ import pathlib
27
+ import textwrap
28
+ import urllib.parse
29
+
30
+ import click
31
+ import huggingface_hub
32
+ import huggingface_hub.utils
33
+ import requests
34
+
35
+ from ..core import constants
36
+ from ..core import deps
37
+
38
+
39
+ def _download_from_url(model_path: str, output_dir: str | pathlib.Path) -> None:
40
+ """Downloads a model directly from a given URL.
41
+
42
+ Args:
43
+ model_path: The direct URL to the model file.
44
+ output_dir: The directory where the model will be saved.
45
+ """
46
+ click.echo(f"Downloading direct URL: {model_path} to {output_dir}")
47
+
48
+ parsed_url = urllib.parse.urlparse(model_path)
49
+ filename = pathlib.Path(parsed_url.path).name
50
+ if not filename:
51
+ filename = "downloaded_model.tflite"
52
+
53
+ filepath = pathlib.Path(output_dir) / filename
54
+
55
+ try:
56
+ with requests.get(model_path, stream=True) as response:
57
+ response.raise_for_status()
58
+
59
+ total_size = int(response.headers.get("content-length", 0))
60
+ chunk_size = 8192
61
+
62
+ with click.progressbar(
63
+ length=total_size,
64
+ label=f"Downloading {filename}",
65
+ empty_char=" ",
66
+ fill_char=click.style("#", fg="green"),
67
+ ) as bar:
68
+ with open(filepath, "wb") as f:
69
+ for chunk in response.iter_content(chunk_size=chunk_size):
70
+ if chunk:
71
+ f.write(chunk)
72
+ bar.update(len(chunk))
73
+
74
+ click.secho(f"Downloaded to {filepath}", fg="green")
75
+ except requests.exceptions.RequestException as e:
76
+ raise click.ClickException(f"Failed to download URL: {e}") from e
77
+
78
+
79
+ def _download_from_hf(
80
+ repo_id: str,
81
+ output_dir: str | pathlib.Path,
82
+ file_pattern: str | None = None,
83
+ token: str | None = None,
84
+ ) -> None:
85
+ """Downloads a model repository or specific files from HuggingFace Hub.
86
+
87
+ Args:
88
+ repo_id: The HuggingFace Hub repository ID.
89
+ output_dir: The directory where the files will be saved.
90
+ file_pattern: Optional glob pattern to filter files to download.
91
+ token: Optional HuggingFace access token.
92
+ """
93
+
94
+ click.echo(f"Downloading from HuggingFace Hub: {repo_id}")
95
+
96
+ kwargs = {
97
+ "local_dir": output_dir,
98
+ }
99
+
100
+ if token:
101
+ kwargs["token"] = token
102
+ if file_pattern:
103
+ kwargs["allow_patterns"] = file_pattern
104
+
105
+ try:
106
+ downloaded_path = huggingface_hub.snapshot_download(
107
+ repo_id=repo_id, **kwargs
108
+ )
109
+ click.secho(f"Successfully downloaded to {downloaded_path}", fg="green")
110
+ except huggingface_hub.utils.HfHubHTTPError as e:
111
+ if "401" in str(e):
112
+ raise click.ClickException(
113
+ f"Failed to download from HuggingFace: {e}\n"
114
+ "This model might require a token. Use --token"
115
+ ) from e
116
+ raise click.ClickException(
117
+ f"Failed to download from HuggingFace: {e}"
118
+ ) from e
119
+ except Exception as e: # pylint: disable=broad-exception-caught
120
+ # Catching broad exception as fallback for other huggingface_hub errors
121
+ raise click.ClickException(
122
+ f"An error occurred during HuggingFace download: {e}"
123
+ ) from e
124
+
125
+
126
+ @click.command(
127
+ "download",
128
+ help=textwrap.dedent("""\
129
+ Download models from URL or HuggingFace.
130
+
131
+ MODEL_PATH: Direct URL to a file, or a HuggingFace Hub repository ID.
132
+
133
+ Examples:
134
+
135
+ Download a direct URL to the current directory:
136
+
137
+ $ litert download https://example.com/model.tflite
138
+
139
+ Download all .tflite files from a HuggingFace repo:
140
+
141
+ $ litert download Qwen/Qwen1.5-0.5B-Chat --file "*.tflite"
142
+
143
+ Download from a private repository using a token:
144
+
145
+ $ litert download MyOrg/PrivateModel --token hf_your_token
146
+ """),
147
+ )
148
+ @click.argument("model_path")
149
+ @click.option(
150
+ "--model-ref",
151
+ help=(
152
+ "Reference name for the model in centralized cache (e.g."
153
+ " 'mobilenet:v3')."
154
+ ),
155
+ )
156
+ @click.option(
157
+ "--output",
158
+ help="Specify output directory (defaults to centralized cache).",
159
+ )
160
+ @click.option(
161
+ "--file",
162
+ "file_pattern",
163
+ help="Only download specific files in the HF repository (e.g. '*.tflite')",
164
+ )
165
+ @click.option("--token", help="Private model requiring a Token")
166
+ @click.option(
167
+ "--hf-id", help="Optional Hugging Face ID to associate with the model."
168
+ )
169
+ @deps.require_extra("download")
170
+ def download_cmd(
171
+ model_path: str,
172
+ model_ref: str | None,
173
+ output: str | None,
174
+ file_pattern: str | None,
175
+ token: str | None,
176
+ hf_id: str | None,
177
+ ) -> None:
178
+ """Downloads models from URL or HuggingFace.
179
+
180
+ Args:
181
+ model_path: Direct URL to a file, or a HuggingFace Hub repository ID.
182
+ model_ref: Optional reference name for the model.
183
+ output: Optional custom directory to save files.
184
+ file_pattern: Glob pattern to filter downloaded files from HF.
185
+ token: HuggingFace access token for private repositories.
186
+ hf_id: Optional Hugging Face ID to associate with the model.
187
+ """
188
+ # Resolve output directory and handle sub-ref in model_ref
189
+ if model_ref and ":" in model_ref:
190
+ ref, resolved_sub_ref = model_ref.split(":", 1)
191
+ else:
192
+ ref = model_ref
193
+ resolved_sub_ref = None
194
+
195
+ if output:
196
+ output_path = pathlib.Path(output)
197
+ else:
198
+ # Use centralized cache by default for HF models
199
+ if not model_path.startswith(("http://", "https://")):
200
+ # Default to hf_id as ref if not provided
201
+ ref = ref or model_path.replace("/", "__")
202
+ # Flatten ref for directory name consistency
203
+ ref_flat = ref.replace("/", "__") if "/" in ref else ref
204
+ output_path = pathlib.Path(constants.LITERT_MODELS_CACHE_DIR) / ref_flat
205
+ else:
206
+ output_path = pathlib.Path(".")
207
+
208
+ output_path.mkdir(parents=True, exist_ok=True)
209
+
210
+ if model_path.startswith(("http://", "https://")):
211
+ _download_from_url(model_path, output_path)
212
+ else:
213
+ _download_from_hf(model_path, output_path, file_pattern, token)
214
+
215
+ # Save metadata if it's a managed download (non-URL) to the centralized cache
216
+ if not model_path.startswith(("http://", "https://")) and not output:
217
+ final_ref = ref or model_path.replace("/", "__")
218
+
219
+ # Load existing metadata if it exists (to preserve other sub-refs)
220
+ metadata_file = output_path / "metadata.json"
221
+ metadata = {}
222
+ try:
223
+ with open(metadata_file, "r") as f:
224
+ metadata = json.load(f)
225
+ except (IOError, json.JSONDecodeError):
226
+ # If the file doesn't exist or is malformed, start with empty metadata.
227
+ pass
228
+
229
+ # Update or create metadata
230
+ metadata.setdefault("files", [])
231
+ metadata.setdefault("sub_references", {})
232
+
233
+ metadata.update({
234
+ "model_ref": final_ref,
235
+ "hf_id": hf_id or model_path,
236
+ "source": "huggingface",
237
+ "created_at": (
238
+ datetime.datetime.now(datetime.timezone.utc).isoformat()
239
+ ),
240
+ })
241
+
242
+ if file_pattern and file_pattern not in metadata["files"]:
243
+ metadata["files"].append(file_pattern)
244
+
245
+ # Handle sub-references
246
+ if resolved_sub_ref:
247
+ sub_references = metadata["sub_references"]
248
+
249
+ # If we downloaded a specific file, find it in the directory!
250
+ # Since file_pattern might be a glob, we search for matching files.
251
+ matched_files = []
252
+ if file_pattern:
253
+ # Glob search in output_path
254
+ matched_files = list(output_path.glob(file_pattern))
255
+
256
+ file_to_map = ""
257
+ if matched_files:
258
+ # Use the first matched file for now
259
+ file_to_map = matched_files[0].name
260
+ elif file_pattern and "*" not in file_pattern:
261
+ # If it's not a glob, assume it's the file name
262
+ file_to_map = file_pattern
263
+
264
+ sub_references[resolved_sub_ref] = {
265
+ "file": file_to_map,
266
+ "hf_id": hf_id or model_path,
267
+ }
268
+ click.echo(
269
+ f"Mapped sub-reference '{resolved_sub_ref}' to file '{file_to_map}'"
270
+ )
271
+
272
+ with open(metadata_file, "w") as f:
273
+ json.dump(metadata, f, indent=2)
274
+ click.secho(f"Metadata saved to {metadata_file}", fg="green")
@@ -0,0 +1,124 @@
1
+ # Copyright 2026 The LiteRT CLI Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+
16
+ """Command line interface for importing local models into LiteRT cache."""
17
+
18
+ from __future__ import annotations
19
+
20
+ import datetime
21
+ import json
22
+ import pathlib
23
+ import shutil
24
+
25
+ import click
26
+
27
+ from ..core import constants
28
+
29
+
30
+ @click.command(
31
+ "import",
32
+ help="Import a local file or directory into the centralized cache.",
33
+ )
34
+ @click.argument(
35
+ "file_path",
36
+ type=click.Path(
37
+ exists=True, file_okay=True, dir_okay=True, path_type=pathlib.Path
38
+ ),
39
+ )
40
+ @click.option(
41
+ "--model-ref",
42
+ required=True,
43
+ help="Reference name for the model (e.g. 'my_model:enc').",
44
+ )
45
+ @click.option("--hf-id", help="Hugging Face ID to associate with the model.")
46
+ def import_cmd(
47
+ file_path: pathlib.Path, model_ref: str, hf_id: str | None
48
+ ) -> None:
49
+ """Imports a local file or directory into the centralized cache.
50
+
51
+ Args:
52
+ file_path: Path to the local file or directory to import.
53
+ model_ref: Reference name for management.
54
+ hf_id: Optional Hugging Face ID.
55
+ """
56
+ # Parse model_ref for sub-reference if using ':' syntax
57
+ main_ref = model_ref
58
+ resolved_sub_ref = None
59
+
60
+ if ":" in model_ref:
61
+ parts = model_ref.split(":", 1)
62
+ main_ref = parts[0]
63
+ resolved_sub_ref = parts[1]
64
+
65
+ # Flatten main_ref for directory name consistency (e.g. a/b -> a__b)
66
+ main_ref_flat = main_ref.replace("/", "__") if "/" in main_ref else main_ref
67
+ output_path = pathlib.Path(constants.LITERT_MODELS_CACHE_DIR) / main_ref_flat
68
+ output_path.mkdir(parents=True, exist_ok=True)
69
+
70
+ click.echo(f"Importing {file_path} to {output_path}...")
71
+
72
+ try:
73
+ if file_path.is_dir():
74
+ # Copy directory content, not the directory itself, to avoid nested
75
+ # folders
76
+ for item in file_path.iterdir():
77
+ if item.is_dir():
78
+ shutil.copytree(item, output_path / item.name, dirs_exist_ok=True)
79
+ else:
80
+ shutil.copy(item, output_path / item.name)
81
+ else:
82
+ shutil.copy(file_path, output_path / file_path.name)
83
+
84
+ click.secho(f"Successfully imported to {output_path}", fg="green")
85
+
86
+ # Save metadata
87
+ metadata_file = output_path / "metadata.json"
88
+ metadata = {}
89
+ if metadata_file.exists():
90
+ try:
91
+ with open(metadata_file, "r") as f:
92
+ metadata = json.load(f)
93
+ except (IOError, json.JSONDecodeError):
94
+ pass
95
+
96
+ metadata.update({
97
+ "model_ref": main_ref,
98
+ "hf_id": hf_id,
99
+ "source": "imported",
100
+ "import_path": str(file_path.resolve()),
101
+ "created_at": datetime.datetime.now(datetime.timezone.utc).isoformat(),
102
+ })
103
+
104
+ # Handle sub-references
105
+ if resolved_sub_ref:
106
+ if "sub_references" not in metadata:
107
+ metadata["sub_references"] = {}
108
+
109
+ # Use the file name in the destination directory
110
+ file_to_map = file_path.name
111
+ metadata["sub_references"][resolved_sub_ref] = {
112
+ "file": file_to_map,
113
+ "hf_id": hf_id,
114
+ }
115
+ click.echo(
116
+ f"Mapped sub-reference '{resolved_sub_ref}' to file '{file_to_map}'"
117
+ )
118
+
119
+ with open(metadata_file, "w") as f:
120
+ json.dump(metadata, f, indent=2)
121
+ click.secho(f"Metadata saved to {metadata_file}", fg="green")
122
+
123
+ except Exception as e:
124
+ raise click.ClickException(f"Failed to import model: {e}") from e
@@ -0,0 +1,132 @@
1
+ # Copyright 2026 The LiteRT CLI Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+
16
+ """Command line interface for listing managed models in LiteRT cache."""
17
+
18
+ from __future__ import annotations
19
+
20
+ import collections
21
+ import json
22
+ import pathlib
23
+ import sys
24
+
25
+ import click
26
+
27
+ from ..core.constants import LITERT_MODELS_CACHE_DIR
28
+
29
+
30
+ @click.command(
31
+ "list",
32
+ help="List all managed models or detailed contents of a specific model.",
33
+ )
34
+ @click.argument("model_ref", required=False)
35
+ def list_cmd(model_ref: str | None) -> None:
36
+ """Lists managed models. If MODEL_REF is provided, shows detailed contents."""
37
+ cache_dir = pathlib.Path(LITERT_MODELS_CACHE_DIR)
38
+
39
+ if not cache_dir.exists() or not cache_dir.is_dir():
40
+ click.echo("No managed models found (cache directory does not exist).")
41
+ return
42
+
43
+ # Case 1: List detailed contents of a specific model
44
+ if model_ref:
45
+ # Flatten for directory check
46
+ ref_flat = model_ref.replace("/", "__") if "/" in model_ref else model_ref
47
+ model_dir = cache_dir / ref_flat
48
+
49
+ if not model_dir.exists() or not model_dir.is_dir():
50
+ click.secho(f"Error: Managed model '{model_ref}' not found.", fg="red")
51
+ sys.exit(1)
52
+
53
+ metadata_file = model_dir / "metadata.json"
54
+ metadata = {}
55
+ if metadata_file.exists():
56
+ try:
57
+ with open(metadata_file, "r") as f:
58
+ metadata = json.load(f)
59
+ except Exception:
60
+ pass
61
+
62
+ click.echo(
63
+ "Details for managed model:"
64
+ f" {click.style(model_ref, fg='green', bold=True)}"
65
+ )
66
+ if metadata:
67
+ click.echo(f" HF ID: {metadata.get('hf_id', 'N/A')}")
68
+ click.echo(f" Source: {metadata.get('source', 'N/A')}")
69
+ click.echo(f" Created At: {metadata.get('created_at', 'N/A')}")
70
+
71
+ click.echo("\nFiles in model directory:")
72
+ sub_refs = metadata.get("sub_references", {})
73
+ # Reverse mapping for display
74
+ file_to_subrefs = collections.defaultdict(list)
75
+ for sub, info in sub_refs.items():
76
+ f = info.get("file")
77
+ if f:
78
+ file_to_subrefs[f].append(sub)
79
+
80
+ for item in sorted(model_dir.iterdir()):
81
+ if item.name == "metadata.json":
82
+ continue
83
+
84
+ size_kb = item.stat().st_size / 1024
85
+ suffix = ""
86
+ if item.name in file_to_subrefs:
87
+ subs = ", ".join(file_to_subrefs[item.name])
88
+ suffix = f" {click.style(f'[{subs}]', fg='cyan')}"
89
+
90
+ click.echo(f" - {item.name:<30} ({size_kb:>8.1f} KB){suffix}")
91
+ return
92
+
93
+ # Case 2: List all managed models (Default)
94
+ models = [d for d in cache_dir.iterdir() if d.is_dir()]
95
+
96
+ if not models:
97
+ click.echo("No managed models found in cache.")
98
+ return
99
+
100
+ click.echo(f"Managed models in {cache_dir}:")
101
+ click.echo("-" * 60)
102
+
103
+ for model_dir in sorted(models, key=lambda x: x.name):
104
+ ref = model_dir.name
105
+ metadata_file = model_dir / "metadata.json"
106
+ hf_id = "N/A"
107
+ source = "N/A"
108
+ sub_refs = {}
109
+
110
+ if metadata_file.exists():
111
+ try:
112
+ with open(metadata_file, "r") as f:
113
+ metadata = json.load(f)
114
+ # Use original model_ref from metadata if available
115
+ ref = metadata.get("model_ref", ref)
116
+ hf_id = metadata.get("hf_id", "N/A")
117
+ source = metadata.get("source", "N/A")
118
+ sub_refs = metadata.get("sub_references", {})
119
+ except Exception:
120
+ pass
121
+
122
+ click.echo(f"Ref: {click.style(ref, fg='green', bold=True)}")
123
+ click.echo(f" HF ID: {hf_id}")
124
+ click.echo(f" Source: {source}")
125
+
126
+ if sub_refs:
127
+ click.echo(" Sub-references:")
128
+ for sub_ref, info in sub_refs.items():
129
+ file_name = info.get("file", "N/A")
130
+ click.echo(f" - {sub_ref} -> {file_name}")
131
+
132
+ click.echo("-" * 60)
@@ -0,0 +1,74 @@
1
+ # Copyright 2026 The LiteRT CLI Authors.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+
16
+ """Command to call LiteRT-LM CLI tool."""
17
+
18
+ from __future__ import annotations
19
+
20
+ import subprocess
21
+ import sys
22
+
23
+ import click
24
+ from litert_cli.core import deps
25
+
26
+
27
+ @click.command(
28
+ name="lm",
29
+ context_settings=dict(
30
+ ignore_unknown_options=True,
31
+ allow_extra_args=True,
32
+ help_option_names=[],
33
+ ),
34
+ help="""LiteRT-LM CLI commands.
35
+
36
+ This command is a transparent proxy to the native `litert-lm-cli` package.
37
+ Any wildcard arguments used here are forwarded directly to the actual engine.
38
+
39
+ Examples:
40
+
41
+ Display the native litert-lm help:
42
+
43
+ $ litert lm --help
44
+
45
+ Run a generative LLM model:
46
+
47
+ $ litert lm run model.litertlm
48
+ $ litert lm run gemma3-1b
49
+
50
+ Benchmark a generative LLM model:
51
+
52
+ $ litert lm benchmark gemma3-1b
53
+ """,
54
+ )
55
+ @deps.require_extra("lm")
56
+ @click.pass_context
57
+ def lm_cmd(ctx: click.Context) -> None:
58
+ """LiteRT-LM related commands.
59
+
60
+ This command is a transparent proxy to the native `litert-lm-cli` package.
61
+ Any wildcard arguments used here are forwarded directly to the actual engine.
62
+
63
+ Args:
64
+ ctx: click Context object containing forwarded arguments.
65
+ """
66
+ try:
67
+ result = subprocess.run(["litert-lm"] + ctx.args, check=False)
68
+ sys.exit(result.returncode)
69
+ except FileNotFoundError:
70
+ click.secho("Error: 'litert-lm' executable not found in PATH.", fg="red")
71
+ click.secho(
72
+ "Please install 'litert-lm-cli' to use this command.", fg="yellow"
73
+ )
74
+ sys.exit(1)