flyteplugins-omlx 0.0.0a0__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.
File without changes
@@ -0,0 +1,3 @@
1
+ __all__ = ["DEFAULT_OMLX_IMAGE", "OMLXAppEnvironment"]
2
+
3
+ from flyteplugins.omlx._app_environment import DEFAULT_OMLX_IMAGE, OMLXAppEnvironment
@@ -0,0 +1,228 @@
1
+ from __future__ import annotations
2
+
3
+ import shlex
4
+ from dataclasses import dataclass, field, replace
5
+ from typing import Any, Literal, Optional, Union
6
+
7
+ import flyte.app
8
+ import rich.repr
9
+ from flyte import Environment, Image, Resources, SecretRequest
10
+ from flyte.app._types import Port
11
+ from flyte.models import SerializationContext
12
+
13
+ from flyteplugins.omlx._constants import DEFAULT_OMLX_PORT
14
+
15
+
16
+ def _shell_safe(args: list[str]) -> list[str]:
17
+ """Quote args that have to survive a trip through a shell.
18
+
19
+ `fserve` runs the app with `Popen(" ".join(args), shell=True)` (flyte/_bin/serve.py),
20
+ so any token carrying spaces or quotes -- a `--model-dir` under a user name with a
21
+ space, say -- reaches the server mangled unless it is quoted here. `shlex.quote` is
22
+ the identity function for ordinary tokens, so this is a no-op for everything else.
23
+
24
+ Tokens starting with `$` are left alone: `fserve` expands those against the
25
+ environment *before* joining, and quoting would turn the marker into a literal.
26
+ """
27
+ return [arg if arg.startswith("$") else shlex.quote(arg) for arg in args]
28
+
29
+
30
+ # oMLX runs only on macOS with Apple Silicon: `mlx` has no Linux wheels, so this default
31
+ # image only installs the plugin. The `omlx` binary itself is expected on the Mac's host
32
+ # (Homebrew or pip-from-git), where the devbox's Apple Silicon bridge runs the app; the
33
+ # `flyte-omlx` wrapper resolves it (probing Homebrew paths that host-execution's PATH
34
+ # omits). See the plugin README.
35
+ DEFAULT_OMLX_IMAGE = flyte.Image.from_debian_base(name="omlx-app-image").with_pip_packages(
36
+ "flyteplugins-omlx",
37
+ pre=True,
38
+ )
39
+
40
+
41
+ @rich.repr.auto
42
+ @dataclass(kw_only=True, repr=True)
43
+ class OMLXAppEnvironment(flyte.app.AppEnvironment):
44
+ """
45
+ App environment backed by [oMLX](https://github.com/jundot/omlx) for serving
46
+ LLMs on Apple Silicon Macs.
47
+
48
+ oMLX is an OpenAI-compatible inference server built on Apple's MLX framework.
49
+ It is **macOS + Apple Silicon only**. On a flyte devbox started with
50
+ `--apple-silicon`, this environment requests `gpu="AppleSilicon"` by default, so
51
+ the devbox's bridge runs `omlx serve` natively on the Mac (in an environment built
52
+ from the image's pip packages) and forwards the app's URL, port and logs.
53
+
54
+ ```bash
55
+ flyte start devbox --apple-silicon
56
+ flyte create config --devbox
57
+ python examples/accelerators/omlx_serve.py
58
+ ```
59
+
60
+ The user is responsible for installing `omlx` on the host machine:
61
+
62
+ ```bash
63
+ brew tap jundot/omlx https://github.com/jundot/omlx && brew install omlx
64
+ # or: pip install git+https://github.com/jundot/omlx
65
+ ```
66
+
67
+ Args:
68
+ name: The name of the application.
69
+ image: Container image. oMLX cannot run inside Linux containers, so this
70
+ mostly describes the pip *packages* that get rebuilt into the Mac's host
71
+ environment. Leave as default for the devbox workflow; you typically only
72
+ override it to add pip packages (e.g. `with_pip_packages("omlx @ git+...")`).
73
+ port: Port the oMLX server listens on. Defaults to 8000 (oMLX's own default).
74
+ resources: Compute resources. Defaults to `gpu="AppleSilicon"` so the app runs
75
+ on the Mac's GPU via the devbox bridge; other GPU types are rejected.
76
+ secrets: Secrets requested by the app.
77
+ env_vars: Environment variables for the server process.
78
+ scaling: Scaling configuration.
79
+ domain: Custom domain for the deployed app.
80
+ cluster_pool: Target cluster pool.
81
+ requires_auth: Whether the public URL requires Flyte auth.
82
+ type: App type tag.
83
+ model_dir: Local directory of MLX models, passed to `omlx serve --model-dir`.
84
+ Each subdirectory is exposed as a separate `model_id`. If unset, oMLX falls
85
+ back to `~/.omlx/models`.
86
+ model_id: Optional friendly id surfaced in the Flyte UI and usable as the
87
+ `model` in OpenAI client requests. oMLX derives actual served ids from
88
+ `--model-dir` subdirectories or HuggingFace repo ids.
89
+ api_key: Optional API key for the oMLX server (sent through `--api-key`). For
90
+ real secrets, prefer `secrets` + `env_vars`.
91
+ extra_args: Extra CLI args appended to `omlx serve`. See `omlx serve --help`
92
+ for the full list (`--max-model-memory`, `--max-concurrent-requests`,
93
+ `--paged-ssd-cache-dir`, `--hf-endpoint`, `--log-level`, etc.).
94
+ """
95
+
96
+ port: int | Port = DEFAULT_OMLX_PORT
97
+ type: str = "oMLX"
98
+ extra_args: str | list[str] = ""
99
+ model_dir: str = ""
100
+ model_id: str = ""
101
+ api_key: str = ""
102
+ image: str | Image | Literal["auto"] = DEFAULT_OMLX_IMAGE
103
+ _resolved_args: list[str] = field(default_factory=list, init=False, repr=False)
104
+
105
+ def __post_init__(self):
106
+ if self.env_vars is None:
107
+ self.env_vars = {}
108
+
109
+ if self._server is not None:
110
+ raise ValueError("server function cannot be set for OMLXAppEnvironment")
111
+
112
+ if self._on_startup is not None:
113
+ raise ValueError("on_startup function cannot be set for OMLXAppEnvironment")
114
+
115
+ if self._on_shutdown is not None:
116
+ raise ValueError("on_shutdown function cannot be set for OMLXAppEnvironment")
117
+
118
+ if self.args:
119
+ raise ValueError("args cannot be set for OMLXAppEnvironment. Use `extra_args` to add extra arguments.")
120
+
121
+ # oMLX is mac-only: default to running natively on the Mac's GPU through the
122
+ # devbox's Apple Silicon bridge, and refuse any other accelerator.
123
+ device = self.resources.get_device() if self.resources is not None else None
124
+ if device is None:
125
+ if self.resources is None:
126
+ self.resources = Resources(gpu="AppleSilicon")
127
+ else:
128
+ self.resources = replace(self.resources, gpu="AppleSilicon")
129
+ elif device.device_class != "APPLE_SILICON":
130
+ raise ValueError(
131
+ "OMLXAppEnvironment only runs on Apple Silicon (gpu='AppleSilicon') because oMLX uses MLX, "
132
+ "which is darwin/arm64 only; got resources with a non-Apple Silicon device."
133
+ )
134
+
135
+ if isinstance(self.extra_args, str):
136
+ extra_args = shlex.split(self.extra_args)
137
+ else:
138
+ extra_args = list(self.extra_args)
139
+
140
+ # oMLX is served locally on the Mac (devbox ports are bound to 127.0.0.1), so bind
141
+ # loopback by default; a --host in extra_args wins.
142
+ host_args = [] if any(a == "--host" for a in extra_args) else ["--host", "127.0.0.1"]
143
+
144
+ # We invoke the `flyte-omlx` wrapper (defined in this package) instead of `omlx`
145
+ # directly so the user gets an actionable install hint if oMLX is missing on the
146
+ # host. When oMLX is installed, the wrapper `execv`s straight into it.
147
+ cmd: list[str] = [
148
+ "flyte-omlx",
149
+ "serve",
150
+ *host_args,
151
+ "--port",
152
+ str(self.get_port().port),
153
+ ]
154
+ if self.model_dir:
155
+ cmd += ["--model-dir", self.model_dir]
156
+ if self.api_key:
157
+ cmd += ["--api-key", self.api_key]
158
+ cmd += extra_args
159
+
160
+ self.args = _shell_safe(cmd)
161
+ self._resolved_args = cmd
162
+
163
+ if self.image is None or self.image == "auto":
164
+ self.image = DEFAULT_OMLX_IMAGE
165
+
166
+ # Helpful default link to the OpenAI-compatible models endpoint so users can
167
+ # sanity-check the server from the Flyte UI.
168
+ self.links = [flyte.app.Link(path="/v1/models", title="oMLX Models", is_relative=True), *self.links]
169
+
170
+ super().__post_init__()
171
+
172
+ def container_args(self, serialization_context: SerializationContext) -> list[str]:
173
+ """Return the container arguments for oMLX."""
174
+ if isinstance(self.args, str):
175
+ return shlex.split(self.args)
176
+ return self.args or []
177
+
178
+ def clone_with(
179
+ self,
180
+ name: str,
181
+ image: Optional[Union[str, Image, Literal["auto"]]] = None,
182
+ resources: Optional[Resources] = None,
183
+ env_vars: Optional[dict[str, str]] = None,
184
+ secrets: Optional[SecretRequest] = None,
185
+ depends_on: Optional[list[Environment]] = None,
186
+ description: Optional[str] = None,
187
+ interruptible: Optional[bool] = None,
188
+ **kwargs: Any,
189
+ ) -> OMLXAppEnvironment:
190
+ port = kwargs.pop("port", None)
191
+ extra_args = kwargs.pop("extra_args", None)
192
+ model_dir = kwargs.pop("model_dir", None)
193
+ model_id = kwargs.pop("model_id", None)
194
+ api_key = kwargs.pop("api_key", None)
195
+
196
+ if kwargs:
197
+ raise TypeError(f"Unexpected keyword arguments: {list(kwargs.keys())}")
198
+
199
+ kwargs = self._get_kwargs()
200
+ kwargs["name"] = name
201
+ # Reset args so __post_init__ rebuilds them with the new fields.
202
+ kwargs["args"] = None
203
+ kwargs["parameters"] = None
204
+ if image is not None:
205
+ kwargs["image"] = image
206
+ if resources is not None:
207
+ kwargs["resources"] = resources
208
+ if env_vars is not None:
209
+ kwargs["env_vars"] = env_vars
210
+ if secrets is not None:
211
+ kwargs["secrets"] = secrets
212
+ if depends_on is not None:
213
+ kwargs["depends_on"] = depends_on
214
+ if description is not None:
215
+ kwargs["description"] = description
216
+ if interruptible is not None:
217
+ kwargs["interruptible"] = interruptible
218
+ if port is not None:
219
+ kwargs["port"] = port
220
+ if extra_args is not None:
221
+ kwargs["extra_args"] = extra_args
222
+ if model_dir is not None:
223
+ kwargs["model_dir"] = model_dir
224
+ if model_id is not None:
225
+ kwargs["model_id"] = model_id
226
+ if api_key is not None:
227
+ kwargs["api_key"] = api_key
228
+ return replace(self, **kwargs)
@@ -0,0 +1,79 @@
1
+ """Friendly preflight wrapper around the upstream `omlx` console command.
2
+
3
+ An `OMLXAppEnvironment` serves via `omlx serve`. The local-serve / Apple Silicon
4
+ host process invokes `flyte-omlx` instead of `omlx` directly so that, if oMLX isn't
5
+ installed on the Mac, the user sees a clear install hint instead of a raw
6
+ `FileNotFoundError: 'omlx'`.
7
+
8
+ When oMLX *is* installed, this wrapper is transparent: it `execv`'s into the real
9
+ binary (or imports `omlx.cli` if the package is installed but the console script
10
+ isn't on PATH for some reason).
11
+
12
+ `gpu="AppleSilicon"` workloads on the flyte devbox run their app natively on the Mac
13
+ in a `uv` environment whose `PATH` only contains that environment's bin directory. A
14
+ Homebrew-installed oMLX lives at `/opt/homebrew/bin` (Apple Silicon) or
15
+ `/usr/local/bin` (Intel Macs and older setups), neither of which is on that `PATH`, so
16
+ `shutil.which("omlx")` alone would miss it. We probe those locations explicitly.
17
+ """
18
+
19
+ from __future__ import annotations
20
+
21
+ import os
22
+ import shutil
23
+ import sys
24
+
25
+ _INSTALL_HINT = """\
26
+ [flyteplugins-omlx] oMLX is not installed on this machine.
27
+
28
+ oMLX is macOS + Apple Silicon only and is not published to PyPI. Install it
29
+ with one of:
30
+
31
+ brew tap jundot/omlx https://github.com/jundot/omlx && brew install omlx
32
+ pip install git+https://github.com/jundot/omlx
33
+
34
+ Or download the .dmg from https://github.com/jundot/omlx/releases.
35
+
36
+ Then re-run `flyte serve` / redeploy the app. See the plugin README for details.
37
+ """
38
+
39
+ # Common Homebrew prefixes for the `omlx` binary. Checked in `resolve_omlx` after
40
+ # `shutil.which`, because the Apple Silicon host-execution environment's PATH
41
+ # omits Homebrew.
42
+ _HOMEBREW_OMLX_CANDIDATES = ("/opt/homebrew/bin/omlx", "/usr/local/bin/omlx")
43
+
44
+
45
+ def resolve_omlx() -> str | None:
46
+ """The path to the `omlx` binary, or None if it cannot be found anywhere."""
47
+ found = shutil.which("omlx")
48
+ if found:
49
+ return found
50
+ for candidate in _HOMEBREW_OMLX_CANDIDATES:
51
+ if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
52
+ return candidate
53
+ return None
54
+
55
+
56
+ def main() -> int:
57
+ binary = resolve_omlx()
58
+ if binary:
59
+ # Hand off to the real binary, preserving argv. execv replaces this
60
+ # process, so the real omlx becomes the long-running server and its
61
+ # signals/log streams belong to it.
62
+ os.execv(binary, [binary, *sys.argv[1:]])
63
+ return 0 # not reached
64
+
65
+ # Fall back to the installed Python package, in case the script wasn't
66
+ # placed on PATH (e.g. an unusual install layout).
67
+ try:
68
+ import omlx.cli as omlx_cli # type: ignore[import-not-found]
69
+ except ImportError:
70
+ sys.stderr.write(_INSTALL_HINT)
71
+ return 127
72
+
73
+ sys.argv = ["omlx", *sys.argv[1:]]
74
+ rc = omlx_cli.main()
75
+ return int(rc) if rc is not None else 0
76
+
77
+
78
+ if __name__ == "__main__":
79
+ sys.exit(main())
@@ -0,0 +1,9 @@
1
+ """Constants shared by the oMLX plugin."""
2
+
3
+ OMLX_MIN_VERSION = (0, 1, 0)
4
+ OMLX_MIN_VERSION_STR = ".".join(map(str, OMLX_MIN_VERSION))
5
+
6
+ # oMLX's own default serve port. Kept distinct from the SDK's 8080 so that
7
+ # "just construct an OMLXAppEnvironment" matches what `omlx serve` would bind
8
+ # if you ran it by hand.
9
+ DEFAULT_OMLX_PORT = 8000
@@ -0,0 +1,151 @@
1
+ Metadata-Version: 2.4
2
+ Name: flyteplugins-omlx
3
+ Version: 0.0.0a0
4
+ Summary: oMLX plugin for flyte — serve LLMs on Apple Silicon
5
+ Author: Flyte authors
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ Requires-Dist: flyte>=2.0.0b43
9
+
10
+ # Flyte oMLX Plugin
11
+
12
+ Serve LLMs locally on Apple Silicon using [oMLX](https://github.com/jundot/omlx)
13
+ and Flyte Apps.
14
+
15
+ This plugin provides the `OMLXAppEnvironment` class for defining an oMLX-backed
16
+ app environment. oMLX is an OpenAI-compatible inference server built on Apple's
17
+ MLX framework, macOS + Apple Silicon only.
18
+
19
+ The plugin integrates with the **Apple Silicon flyte devbox**: an
20
+ `OMLXAppEnvironment` requests `gpu="AppleSilicon"` by default, so the devbox's
21
+ bridge runs `omlx serve` natively on the Mac, in an environment built from the
22
+ image's pip packages, and forwards the app's URL, port and logs.
23
+
24
+ > **Mac-only.** oMLX runs only on macOS 15+ with Apple Silicon (M1/M2/M3/M4).
25
+ > `mlx` has no Linux wheels, so this plugin cannot run in a Linux container or on
26
+ > a cloud cluster. It is designed for a devbox started with `--apple-silicon`.
27
+
28
+ ## Installation
29
+
30
+ oMLX is **not on PyPI** — install it via Homebrew, the macOS `.dmg`, or from
31
+ source. See the upstream
32
+ [install guide](https://github.com/jundot/omlx#installation):
33
+
34
+ ```bash
35
+ # oMLX itself (one of):
36
+ brew tap jundot/omlx https://github.com/jundot/omlx && brew install omlx
37
+ # or:
38
+ pip install git+https://github.com/jundot/omlx
39
+
40
+ # Then the Flyte plugin:
41
+ pip install --pre flyteplugins-omlx
42
+ ```
43
+
44
+ Verify the install with `omlx --help` before running the plugin.
45
+
46
+ ## Quickstart — serve an LLM from the Apple Silicon devbox
47
+
48
+ `examples/accelerators/omlx_serve.py`:
49
+
50
+ ```python
51
+ import flyte
52
+ from flyteplugins.omlx import OMLXAppEnvironment
53
+
54
+ omlx_app = OMLXAppEnvironment(
55
+ name="omlx-local",
56
+ # oMLX serves whatever is under ~/.omlx/models by default. Set model_dir to a
57
+ # different folder (an absolute, expanded path).
58
+ model_id="qwen3-0.6b",
59
+ extra_args="--log-level info",
60
+ )
61
+ ```
62
+
63
+ Run it against a devbox started with the Apple Silicon bridge:
64
+
65
+ ```bash
66
+ flyte start devbox --apple-silicon
67
+ flyte create config --devbox
68
+ python examples/accelerators/omlx_serve.py
69
+ ```
70
+
71
+ `OMLXAppEnvironment` defaults to `gpu="AppleSilicon"`, so the app runs natively
72
+ on the Mac's GPU. From another shell, hit the OpenAI-compatible API (the app's
73
+ URL is printed by the example):
74
+
75
+ ```python
76
+ from openai import OpenAI
77
+
78
+ client = OpenAI(base_url="http://omlx-local-...:30081/v1", api_key="not-used")
79
+ resp = client.chat.completions.create(
80
+ model="qwen3-0.6b",
81
+ messages=[{"role": "user", "content": "Hello!"}],
82
+ )
83
+ print(resp.choices[0].message.content)
84
+ ```
85
+
86
+ Useful endpoints (also surfaced as Flyte app links):
87
+
88
+ | Endpoint | Description |
89
+ |--------------------|----------------------------------------------|
90
+ | `/v1/chat/completions` | OpenAI chat completions |
91
+ | `/v1/completions` | OpenAI completions |
92
+ | `/v1/embeddings` | OpenAI embeddings |
93
+ | `/v1/models` | List available models |
94
+
95
+ ## Configuration
96
+
97
+ `OMLXAppEnvironment` constructs the `omlx serve --host 127.0.0.1 --port <port> ...`
98
+ invocation that the devbox runs on the Mac. Recognised fields:
99
+
100
+ | Field | Description |
101
+ |---------------|---------------------------------------------------------------------------------------------------|
102
+ | `name` | App name (required, lowercase + hyphens). |
103
+ | `port` | Bind port. Default `8000` (oMLX default). |
104
+ | `model_dir` | Local directory containing MLX-format model subdirectories, mapped to `--model-dir`. Use an expanded absolute path. |
105
+ | `model_id` | Friendly id surfaced in Flyte / OpenAI client requests; oMLX serves each subdir of `model_dir`. |
106
+ | `resources` | `gpu="AppleSilicon"` by default (runs on the Mac via the devbox bridge). Other GPUs are rejected. |
107
+ | `api_key` | Optional `--api-key` for the oMLX server. For real secrets, prefer `secrets` + `env_vars`. |
108
+ | `extra_args` | List or string of extra flags passed to `omlx serve`. See `omlx serve --help`. |
109
+ | `env_vars` | Environment variables for the server process (e.g. `HF_TOKEN`). |
110
+ | `image` | Container image. Sets the pip packages rebuilt into the Mac's host environment. |
111
+ | `scaling` | `flyte.app.Scaling` config. |
112
+
113
+ ### Example: tighten memory / cache config
114
+
115
+ ```python
116
+ omlx_app = OMLXAppEnvironment(
117
+ name="omlx-local",
118
+ model_dir="/Users/me/.omlx/models",
119
+ extra_args=(
120
+ "--max-model-memory 24GB "
121
+ "--max-concurrent-requests 4 "
122
+ "--paged-ssd-cache-dir /Users/me/.omlx/ssd_cache "
123
+ "--paged-ssd-cache-max-size 50GB "
124
+ "--log-level info"
125
+ ),
126
+ )
127
+ ```
128
+
129
+ ## How it works
130
+
131
+ With `flyte start devbox --apple-silicon`, a `gpu="AppleSilicon"` app runs
132
+ **natively on the Mac**. The devbox container (a Linux VM) has no Metal, so the
133
+ bridge hands the app's entrypoint (`fserve`) to the Mac, which runs it in a `uv`
134
+ environment built from the image's pip packages. `fserve` then runs the app's
135
+ `args` — here `omlx serve ...` — on the Mac, and the devbox forwards the app's
136
+ URL, port and logs.
137
+
138
+ The `args` invoke a tiny `flyte-omlx` console-script wrapper (installed by this
139
+ package) which `execv`s into the real `omlx` binary. Because the Apple Silicon
140
+ host environment's PATH only contains its own `bin/` (Homebrew is not on it), the
141
+ wrapper also probes `/opt/homebrew/bin` and `/usr/local/bin` so a
142
+ Homebrew-installed oMLX is found. If oMLX isn't installed, the wrapper prints an
143
+ actionable install hint instead of a raw `FileNotFoundError`.
144
+
145
+ ## Limitations
146
+
147
+ - **Apple Silicon only.** oMLX uses `mlx`, which only ships for darwin/arm64. You
148
+ cannot run this plugin in a Linux container today.
149
+ - oMLX must be installed on the Mac (Homebrew or pip-from-git). The default image
150
+ installs only the plugin, because `mlx` has no Linux wheels with which to build a
151
+ base image.
@@ -0,0 +1,10 @@
1
+ flyteplugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ flyteplugins/omlx/__init__.py,sha256=OVNvya0WcAvUOBczbjKmtRpG4nJhAqHgBOGFDfVOWPM,142
3
+ flyteplugins/omlx/_app_environment.py,sha256=B_fK-bsfkHHk4lVD9LxzoNll1jXt23zs6CU4fdESMdk,9843
4
+ flyteplugins/omlx/_cli.py,sha256=Xp1u1qn6EzJZ0q4rlJnYxULLDQD3nINaDk9VMV6Cplg,2881
5
+ flyteplugins/omlx/_constants.py,sha256=ultyT7SMIPHoKC6GweyGRPui2IaknZ8UEgEg3wezvD4,337
6
+ flyteplugins_omlx-0.0.0a0.dist-info/METADATA,sha256=9R_buwtJwiFMHmmkODGc4W6dB4Ehm2-MpAI6w7B5C0I,6335
7
+ flyteplugins_omlx-0.0.0a0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
8
+ flyteplugins_omlx-0.0.0a0.dist-info/entry_points.txt,sha256=-oW89sQ9JlQ44d1g-GSG5sNyI7V2yPvzacH_sYXekss,59
9
+ flyteplugins_omlx-0.0.0a0.dist-info/top_level.txt,sha256=cgd779rPu9EsvdtuYgUxNHHgElaQvPn74KhB5XSeMBE,13
10
+ flyteplugins_omlx-0.0.0a0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ flyte-omlx = flyteplugins.omlx._cli:main
@@ -0,0 +1 @@
1
+ flyteplugins