ixt-cli 0.8.0__tar.gz → 0.9.0__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.
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/.gitignore +1 -0
- ixt_cli-0.9.0/LICENSE +21 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/PKG-INFO +52 -45
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/README.md +49 -43
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/pyproject.toml +2 -6
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/backends/binary.py +71 -19
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/cmd_apply.py +54 -7
- ixt_cli-0.9.0/src/ixt/cli/cmd_lock.py +89 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/commands.py +3 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/parser.py +28 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/config/heuristics.py +47 -1
- ixt_cli-0.9.0/src/ixt/config/lockfile.py +279 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/apply.py +148 -6
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/discover.py +84 -1
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/install.py +4 -0
- ixt_cli-0.9.0/src/ixt/core/lockfile.py +504 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/data/heuristics.toml +1 -1
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/net/npm.py +18 -2
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/net/pypi.py +12 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_apply.py +372 -2
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_argparse_completion.py +1 -1
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_binary_backend.py +193 -18
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_cli.py +9 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_discover.py +80 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_heuristics.py +33 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_install.py +1 -1
- ixt_cli-0.9.0/tests/unit/test_lock_write.py +561 -0
- ixt_cli-0.9.0/tests/unit/test_lockfile.py +195 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/__init__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/__main__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/backends/__init__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/backends/binary_resolver.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/backends/node.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/backends/python.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/__init__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/argparse_completion.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/cmd_cache.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/cmd_config.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/cmd_info.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/cmd_install.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/cmd_misc.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/cmd_registry.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/cmd_upgrade.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/cli/render.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/config/__init__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/config/asset_index.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/config/asset_pattern_cache.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/config/env_policy.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/config/flags.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/config/fs_policy.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/config/models.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/config/registry.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/config/settings.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/config/setup_toml.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/config/toml.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/__init__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/apply_actions.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/backend.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/bootstrap.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/cache.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/doctor.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/export.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/expose.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/extract.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/hooks.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/identity.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/inject.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/install_local.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/locks.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/pathlink.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/resolution_stats.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/resolve.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/resolve_cache.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/runtimes.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/save.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/setup_completions.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/setup_path.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/core/upgrade.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/data/__init__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/data/asset_index.json +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/data/registry.toml +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/libs/__init__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/libs/constants.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/libs/fmt.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/libs/logger.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/libs/output.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/libs/req_spec.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/libs/semver.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/libs/shell.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/libs/style.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/net/__init__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/net/github_api.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/net/gitlab_api.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/net/http.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/net/source.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/platform/__init__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/src/ixt/platform/win.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/__init__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/conftest.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/e2e/README.md +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/e2e/conftest.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/e2e/docker/Dockerfile.alpine +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/e2e/docker/Dockerfile.fedora +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/e2e/docker/Dockerfile.ubuntu +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/e2e/playground_incus.sh +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/e2e/run_e2e_docker.sh +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/e2e/run_e2e_incus.sh +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/e2e/runtime_matrix.sh +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/e2e/test_e2e.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/integration/__init__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/integration/test_lifecycle.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/integration/test_policy_runtime.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/__init__.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_asset_index.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_asset_pattern_cache.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_backend.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_bootstrap.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_cache.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_doctor.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_env_policy.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_export.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_expose.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_extract.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_flags.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_fs_policy.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_github_api.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_gitlab_api.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_hooks.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_http.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_inject.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_install_local.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_models.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_namespacing.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_node_backend.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_output.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_pathlink.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_platform.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_python_backend.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_registry.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_req_spec.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_resolve.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_resolve_cache.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_runtimes.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_save.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_semver.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_settings.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_setup_path.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_setup_toml.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_source.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_style.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_toml.py +0 -0
- {ixt_cli-0.8.0 → ixt_cli-0.9.0}/tests/unit/test_upgrade.py +0 -0
ixt_cli-0.9.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Gaëtan Montury
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ixt-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Universal CLI tool isolator - manage Python, Node, and binary tools with per-tool isolation
|
|
5
5
|
Project-URL: Homepage, https://gitlab.com/pytgaen-group/ixt
|
|
6
6
|
Project-URL: Repository, https://gitlab.com/pytgaen-group/ixt.git
|
|
7
7
|
Project-URL: Issues, https://gitlab.com/pytgaen-group/ixt/-/issues
|
|
8
|
-
Project-URL: Documentation, https://gitlab.
|
|
8
|
+
Project-URL: Documentation, https://ixt-39f604.gitlab.io
|
|
9
9
|
Author-email: Gaëtan Montury <10528250-pytgaen@users.noreply.gitlab.com>
|
|
10
10
|
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
11
12
|
Keywords: cli,github,isolation,npm,package-manager,pypi,tool
|
|
12
13
|
Classifier: Development Status :: 4 - Beta
|
|
13
14
|
Classifier: Environment :: Console
|
|
@@ -27,30 +28,37 @@ Requires-Python: >=3.10
|
|
|
27
28
|
Description-Content-Type: text/markdown
|
|
28
29
|
|
|
29
30
|
<p align="center">
|
|
30
|
-
<img src="assets/ixt.jpg" alt="ixt" width="600">
|
|
31
|
+
<img src="https://ixt-39f604.gitlab.io/assets/ixt.jpg" alt="ixt" width="600">
|
|
31
32
|
</p>
|
|
32
33
|
|
|
33
34
|
<h1 align="center">ixt — Universal CLI Tool Isolator</h1>
|
|
34
35
|
|
|
35
36
|
<p align="center">
|
|
36
|
-
<a href="https://gitlab.com/pytgaen-group/ixt/-/
|
|
37
|
-
<a href="https://gitlab.com/pytgaen-group/ixt/-/
|
|
37
|
+
<a href="https://gitlab.com/pytgaen-group/ixt/-/tags"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgitlab.com%2Fapi%2Fv4%2Fprojects%2F81169049%2Frepository%2Ftags%3Fper_page%3D1&query=%24%5B0%5D.name&label=release" alt="Release"></a>
|
|
38
|
+
<a href="https://gitlab.com/pytgaen-group/ixt/-/blob/main/.gitlab-ci.yml"><img src="https://img.shields.io/badge/build-GitLab%20CI-blue?logo=gitlab" alt="Build: GitLab CI"></a>
|
|
39
|
+
<a href="https://pypi.org/project/ixt-cli/"><img src="https://img.shields.io/pypi/v/ixt-cli?label=pypi" alt="PyPI"></a>
|
|
40
|
+
<a href="https://pepy.tech/projects/ixt-cli"><img src="https://static.pepy.tech/badge/ixt-cli/month" alt="PyPI Downloads"></a>
|
|
38
41
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
|
|
39
42
|
<img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="Python 3.10+">
|
|
40
43
|
</p>
|
|
41
44
|
|
|
42
45
|
<p align="center">
|
|
43
|
-
<img src="assets/wip.jpg" alt="Work in Progress" width="300">
|
|
46
|
+
<img src="https://ixt-39f604.gitlab.io/assets/wip.jpg" alt="Work in Progress" width="300">
|
|
44
47
|
</p>
|
|
45
48
|
|
|
46
49
|
**One CLI for Python, Node, and binary tools — each in its own isolated env.**
|
|
47
|
-
**Optional Linux runtime policy can restrict env vars and filesystem access per tool.**
|
|
48
50
|
|
|
49
51
|
*Zero third-party deps. Zero system pollution. One command, any ecosystem.*
|
|
50
52
|
|
|
53
|
+
- **Day-one GitHub releases** — install a tool the day it ships, without
|
|
54
|
+
waiting for a formula, registry entry, or plugin.
|
|
55
|
+
- **No system runtime needed** — ixt is pure Python stdlib; `uv` and `bun` are
|
|
56
|
+
bootstrapped on first use, so the host does not need Node, npm, or a package
|
|
57
|
+
manager pre-installed.
|
|
58
|
+
|
|
51
59
|
```bash
|
|
52
60
|
ixt tool install ruff # Python (PyPI)
|
|
53
|
-
ixt tool install @anthropic-ai/claude-code #
|
|
61
|
+
ixt tool install @anthropic-ai/claude-code # npm package via bun
|
|
54
62
|
ixt tool install ripgrep # Binary (GitHub — via built-in registry)
|
|
55
63
|
```
|
|
56
64
|
|
|
@@ -62,10 +70,10 @@ From PyPI. The published package is `ixt-cli`; it installs the `ixt` command.
|
|
|
62
70
|
|
|
63
71
|
```bash
|
|
64
72
|
# One-shot via uvx (requires uv)
|
|
65
|
-
uvx --from ixt-cli
|
|
73
|
+
uvx --from ixt-cli ixt --help
|
|
66
74
|
|
|
67
75
|
# Persistent install via uv tool
|
|
68
|
-
uv tool install ixt-cli
|
|
76
|
+
uv tool install ixt-cli
|
|
69
77
|
```
|
|
70
78
|
|
|
71
79
|
Or from source:
|
|
@@ -83,16 +91,16 @@ ixt setup path
|
|
|
83
91
|
|
|
84
92
|
### Integrity verification
|
|
85
93
|
|
|
86
|
-
Release tags are SSH-signed. Installing from PyPI
|
|
87
|
-
|
|
88
|
-
|
|
94
|
+
Release tags are SSH-signed. Installing from PyPI does not verify the Git tag
|
|
95
|
+
signature locally. Use the clone flow when you want to verify a signed release
|
|
96
|
+
before installing:
|
|
89
97
|
|
|
90
98
|
```bash
|
|
91
99
|
# Verify the signed tag (requires the maintainer's SSH public key in git's allowed_signers)
|
|
92
100
|
git clone https://gitlab.com/pytgaen-group/ixt.git && cd ixt
|
|
93
101
|
git fetch --tags
|
|
94
|
-
git verify-tag
|
|
95
|
-
git checkout --detach
|
|
102
|
+
git verify-tag vX.Y.Z
|
|
103
|
+
git checkout --detach vX.Y.Z
|
|
96
104
|
uv tool install .
|
|
97
105
|
```
|
|
98
106
|
|
|
@@ -144,30 +152,6 @@ ixt tool install @npm:prettier
|
|
|
144
152
|
ixt tool install @gh:BurntSushi/ripgrep
|
|
145
153
|
```
|
|
146
154
|
|
|
147
|
-
## 🛡️ Runtime policy
|
|
148
|
-
|
|
149
|
-
Dependency isolation is the baseline — but ixt also lets you control **what each tool can see at runtime**: which environment variables it receives, which filesystem paths it can read or write. This is a runtime policy for commands launched through ixt shims, not a sandbox for install-time package-manager scripts. If the install step itself is untrusted, use a disposable VM/container/user.
|
|
150
|
-
|
|
151
|
-
```bash
|
|
152
|
-
# Avoid passing secrets in the runtime environment
|
|
153
|
-
ixt tool config some-cli env base os-common
|
|
154
|
-
ixt tool config some-cli env allow '*RUFF*'
|
|
155
|
-
|
|
156
|
-
# Restrict filesystem at runtime: read-only host, hide ~/.aws and ~/.ssh
|
|
157
|
-
ixt tool config some-cli fs base app-common
|
|
158
|
-
ixt tool config some-cli fs scratch ~/.aws ~/.ssh
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
When a policy is active, the PATH shim becomes a small Python wrapper that — when [bubblewrap](https://github.com/containers/bubblewrap) is installed — runs the tool with PID isolation and a private `/proc`. The filtered env reaches the tool, and `/proc/$PPID/environ` reads from inside cannot recover the host secrets. Filesystem policy also requires bwrap. Without bwrap, env policy still cleans the env reaching the tool but `/proc` reads remain exfiltrable — the shim warns once per session in this **degraded "hygiene-only" mode**. Reset the policy and you're back to a direct symlink with zero overhead.
|
|
162
|
-
|
|
163
|
-
The policy is replayable: declare `env_base`, `env_allow`, `fs_scratch`, etc. inside `ixt.toml` and `ixt tool apply` reproduces it on any machine. Use `IXT_SHIM_DEBUG=1` to dump the effective env, `IXT_POLICY_QUIET=1` to silence the degraded-mode warning, `IXT_DISABLE_BWRAP=1` to force degraded mode for debugging.
|
|
164
|
-
|
|
165
|
-
> ⚠️ **Linux only for now.** Enforcement relies on POSIX `execve` and Linux namespaces. On macOS/Windows the policy is saved to `ixt.json` but **not enforced at runtime** — the tool runs with the full environment.
|
|
166
|
-
> Install [bubblewrap](https://github.com/containers/bubblewrap) for **effective env/fs enforcement**: `sudo apt install bubblewrap` / `sudo dnf install bubblewrap` / `sudo pacman -S bubblewrap` / `sudo apk add bubblewrap`. The env axis runs without it but only as a hygiene measure (cleans logs/traces); the fs axis requires it and errors out otherwise. Network isolation is not implemented yet.
|
|
167
|
-
> This is not a general malware sandbox.
|
|
168
|
-
|
|
169
|
-
[Full policy reference →](https://gitlab.com/pytgaen-group/ixt/-/blob/main/docs/policy/index.md)
|
|
170
|
-
|
|
171
155
|
## 📚 Built-in Registry
|
|
172
156
|
|
|
173
157
|
ixt ships with a built-in registry that maps common tool names to their source. No need to remember the GitHub path:
|
|
@@ -202,6 +186,24 @@ ixt tool install sharkdp/fd@10 # latest 10.x.x
|
|
|
202
186
|
ixt tool install ruff>=0.5.0 # PEP 508 (Python)
|
|
203
187
|
```
|
|
204
188
|
|
|
189
|
+
## 🔒 Declarative locks
|
|
190
|
+
|
|
191
|
+
`ixt.toml` is the human-edited intent: which tools you want, with optional
|
|
192
|
+
version bounds, exposure rules, and injections. `ixt.lock` is generated from
|
|
193
|
+
that intent and records the exact resolved versions and binary release assets.
|
|
194
|
+
|
|
195
|
+
For teams and CI, commit both files:
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
ixt lock write
|
|
199
|
+
ixt lock check
|
|
200
|
+
ixt tool apply --locked --strict
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
`--locked` replays the exact lock instead of resolving newer versions.
|
|
204
|
+
`--strict` fails before changing the machine if `ixt.toml` and `ixt.lock`
|
|
205
|
+
diverge.
|
|
206
|
+
|
|
205
207
|
## 🧰 Commands
|
|
206
208
|
|
|
207
209
|
| Command | Description |
|
|
@@ -215,7 +217,11 @@ ixt tool install ruff>=0.5.0 # PEP 508 (Python)
|
|
|
215
217
|
| `ixt tool upgrade <pkg>` | Upgrade a tool |
|
|
216
218
|
| `ixt tool upgrade --all` | Upgrade all tools |
|
|
217
219
|
| `ixt tool apply` | Sync tools from declarative config |
|
|
220
|
+
| `ixt tool apply --locked --strict` | Sync using exact versions and assets from ixt.lock |
|
|
218
221
|
| `ixt tool export` | Export installed tools as ixt.toml (stdout), preserves install intent |
|
|
222
|
+
| `ixt lock write` | Resolve ixt.toml and write ixt.lock |
|
|
223
|
+
| `ixt lock check` | Fail if ixt.lock is missing or stale |
|
|
224
|
+
| `ixt lock diff` | Show semantic differences between ixt.toml and ixt.lock |
|
|
219
225
|
| `ixt asset-index export` | Export portable binary-resolution metadata as JSON |
|
|
220
226
|
| `ixt tool config <pkg> expose <rules...>` | Change exposed binaries |
|
|
221
227
|
| `ixt tool config <pkg> inject <dep>` | Inject a package into the tool's env |
|
|
@@ -238,7 +244,8 @@ ixt tool install ruff>=0.5.0 # PEP 508 (Python)
|
|
|
238
244
|
```
|
|
239
245
|
$IXT_HOME/ # default ~/.local/share/ixt (respects $XDG_DATA_HOME)
|
|
240
246
|
├── config/
|
|
241
|
-
│ ├── ixt.toml
|
|
247
|
+
│ ├── ixt.toml # global declarative manifest fallback
|
|
248
|
+
│ ├── ixt.lock # global lock when using the global ixt.toml
|
|
242
249
|
│ ├── registry.toml
|
|
243
250
|
│ └── heuristics.toml
|
|
244
251
|
└── installed/
|
|
@@ -262,12 +269,12 @@ $IXT_CACHE_HOME/ # default ~/.cache/ixt (respects $XDG_CACHE_HOME)
|
|
|
262
269
|
| | uv tool | pipx | npm -g | aqua | mise | proto | **ixt** |
|
|
263
270
|
| ------------------------ | ------- | ---- | ------ | ---- | --------- | ----- | ------- |
|
|
264
271
|
| Python tools (PyPI) | ✅ | ✅ | — | — | ✅ (pipx) | — | ✅ |
|
|
265
|
-
|
|
|
272
|
+
| npm packages | — | — | ✅ | — | ✅ (npm) | — | ✅ |
|
|
266
273
|
| GitHub Releases binaries | — | — | — | ✅ | ✅ (ubi) | ~ | ✅ |
|
|
274
|
+
| Day-one GitHub releases | — | — | — | ❌ | ❌ | ~ | ✅ |
|
|
267
275
|
| Dependency isolation | ✅ | ✅ | ❌ | — | partial | — | ✅ |
|
|
268
276
|
| Fine-grained exposure | — | — | — | — | — | — | ✅ |
|
|
269
|
-
|
|
|
270
|
-
| Bootstrap from scratch | — | — | — | — | curl | curl | ✅ |
|
|
277
|
+
| No system runtime needed | — | — | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
271
278
|
| Declarative config | — | — | — | YAML | TOML | TOML | TOML |
|
|
272
279
|
|
|
273
280
|
ixt is **not** a runtime version manager (mise, proto, asdf cover that). It manages the **last mile**: install, isolate, expose and group CLI tools across three ecosystems.
|
|
@@ -285,10 +292,10 @@ uv run ruff format src/ tests/ # format
|
|
|
285
292
|
|
|
286
293
|
## 🤝 Contributing
|
|
287
294
|
|
|
288
|
-
Development happens on **GitLab**: <https://gitlab.com/pytgaen-group/ixt>.
|
|
295
|
+
Development happens on **GitLab**: <https://gitlab.com/pytgaen-group/ixt>. Any GitHub repository for this project is a **read-only mirror** of GitLab; it is not the review or issue tracker. Please open issues and merge requests on GitLab.
|
|
289
296
|
|
|
290
297
|
Bug reports and reproduction recipes are welcome. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for details, or open an issue to start a discussion.
|
|
291
298
|
|
|
292
299
|
## 📄 License
|
|
293
300
|
|
|
294
|
-
MIT
|
|
301
|
+
MIT. See [`LICENSE`](LICENSE).
|
|
@@ -1,28 +1,35 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="assets/ixt.jpg" alt="ixt" width="600">
|
|
2
|
+
<img src="https://ixt-39f604.gitlab.io/assets/ixt.jpg" alt="ixt" width="600">
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<h1 align="center">ixt — Universal CLI Tool Isolator</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
<a href="https://gitlab.com/pytgaen-group/ixt/-/
|
|
9
|
-
<a href="https://gitlab.com/pytgaen-group/ixt/-/
|
|
8
|
+
<a href="https://gitlab.com/pytgaen-group/ixt/-/tags"><img src="https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgitlab.com%2Fapi%2Fv4%2Fprojects%2F81169049%2Frepository%2Ftags%3Fper_page%3D1&query=%24%5B0%5D.name&label=release" alt="Release"></a>
|
|
9
|
+
<a href="https://gitlab.com/pytgaen-group/ixt/-/blob/main/.gitlab-ci.yml"><img src="https://img.shields.io/badge/build-GitLab%20CI-blue?logo=gitlab" alt="Build: GitLab CI"></a>
|
|
10
|
+
<a href="https://pypi.org/project/ixt-cli/"><img src="https://img.shields.io/pypi/v/ixt-cli?label=pypi" alt="PyPI"></a>
|
|
11
|
+
<a href="https://pepy.tech/projects/ixt-cli"><img src="https://static.pepy.tech/badge/ixt-cli/month" alt="PyPI Downloads"></a>
|
|
10
12
|
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License: MIT"></a>
|
|
11
13
|
<img src="https://img.shields.io/badge/python-3.10%2B-blue.svg" alt="Python 3.10+">
|
|
12
14
|
</p>
|
|
13
15
|
|
|
14
16
|
<p align="center">
|
|
15
|
-
<img src="assets/wip.jpg" alt="Work in Progress" width="300">
|
|
17
|
+
<img src="https://ixt-39f604.gitlab.io/assets/wip.jpg" alt="Work in Progress" width="300">
|
|
16
18
|
</p>
|
|
17
19
|
|
|
18
20
|
**One CLI for Python, Node, and binary tools — each in its own isolated env.**
|
|
19
|
-
**Optional Linux runtime policy can restrict env vars and filesystem access per tool.**
|
|
20
21
|
|
|
21
22
|
*Zero third-party deps. Zero system pollution. One command, any ecosystem.*
|
|
22
23
|
|
|
24
|
+
- **Day-one GitHub releases** — install a tool the day it ships, without
|
|
25
|
+
waiting for a formula, registry entry, or plugin.
|
|
26
|
+
- **No system runtime needed** — ixt is pure Python stdlib; `uv` and `bun` are
|
|
27
|
+
bootstrapped on first use, so the host does not need Node, npm, or a package
|
|
28
|
+
manager pre-installed.
|
|
29
|
+
|
|
23
30
|
```bash
|
|
24
31
|
ixt tool install ruff # Python (PyPI)
|
|
25
|
-
ixt tool install @anthropic-ai/claude-code #
|
|
32
|
+
ixt tool install @anthropic-ai/claude-code # npm package via bun
|
|
26
33
|
ixt tool install ripgrep # Binary (GitHub — via built-in registry)
|
|
27
34
|
```
|
|
28
35
|
|
|
@@ -34,10 +41,10 @@ From PyPI. The published package is `ixt-cli`; it installs the `ixt` command.
|
|
|
34
41
|
|
|
35
42
|
```bash
|
|
36
43
|
# One-shot via uvx (requires uv)
|
|
37
|
-
uvx --from ixt-cli
|
|
44
|
+
uvx --from ixt-cli ixt --help
|
|
38
45
|
|
|
39
46
|
# Persistent install via uv tool
|
|
40
|
-
uv tool install ixt-cli
|
|
47
|
+
uv tool install ixt-cli
|
|
41
48
|
```
|
|
42
49
|
|
|
43
50
|
Or from source:
|
|
@@ -55,16 +62,16 @@ ixt setup path
|
|
|
55
62
|
|
|
56
63
|
### Integrity verification
|
|
57
64
|
|
|
58
|
-
Release tags are SSH-signed. Installing from PyPI
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
Release tags are SSH-signed. Installing from PyPI does not verify the Git tag
|
|
66
|
+
signature locally. Use the clone flow when you want to verify a signed release
|
|
67
|
+
before installing:
|
|
61
68
|
|
|
62
69
|
```bash
|
|
63
70
|
# Verify the signed tag (requires the maintainer's SSH public key in git's allowed_signers)
|
|
64
71
|
git clone https://gitlab.com/pytgaen-group/ixt.git && cd ixt
|
|
65
72
|
git fetch --tags
|
|
66
|
-
git verify-tag
|
|
67
|
-
git checkout --detach
|
|
73
|
+
git verify-tag vX.Y.Z
|
|
74
|
+
git checkout --detach vX.Y.Z
|
|
68
75
|
uv tool install .
|
|
69
76
|
```
|
|
70
77
|
|
|
@@ -116,30 +123,6 @@ ixt tool install @npm:prettier
|
|
|
116
123
|
ixt tool install @gh:BurntSushi/ripgrep
|
|
117
124
|
```
|
|
118
125
|
|
|
119
|
-
## 🛡️ Runtime policy
|
|
120
|
-
|
|
121
|
-
Dependency isolation is the baseline — but ixt also lets you control **what each tool can see at runtime**: which environment variables it receives, which filesystem paths it can read or write. This is a runtime policy for commands launched through ixt shims, not a sandbox for install-time package-manager scripts. If the install step itself is untrusted, use a disposable VM/container/user.
|
|
122
|
-
|
|
123
|
-
```bash
|
|
124
|
-
# Avoid passing secrets in the runtime environment
|
|
125
|
-
ixt tool config some-cli env base os-common
|
|
126
|
-
ixt tool config some-cli env allow '*RUFF*'
|
|
127
|
-
|
|
128
|
-
# Restrict filesystem at runtime: read-only host, hide ~/.aws and ~/.ssh
|
|
129
|
-
ixt tool config some-cli fs base app-common
|
|
130
|
-
ixt tool config some-cli fs scratch ~/.aws ~/.ssh
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
When a policy is active, the PATH shim becomes a small Python wrapper that — when [bubblewrap](https://github.com/containers/bubblewrap) is installed — runs the tool with PID isolation and a private `/proc`. The filtered env reaches the tool, and `/proc/$PPID/environ` reads from inside cannot recover the host secrets. Filesystem policy also requires bwrap. Without bwrap, env policy still cleans the env reaching the tool but `/proc` reads remain exfiltrable — the shim warns once per session in this **degraded "hygiene-only" mode**. Reset the policy and you're back to a direct symlink with zero overhead.
|
|
134
|
-
|
|
135
|
-
The policy is replayable: declare `env_base`, `env_allow`, `fs_scratch`, etc. inside `ixt.toml` and `ixt tool apply` reproduces it on any machine. Use `IXT_SHIM_DEBUG=1` to dump the effective env, `IXT_POLICY_QUIET=1` to silence the degraded-mode warning, `IXT_DISABLE_BWRAP=1` to force degraded mode for debugging.
|
|
136
|
-
|
|
137
|
-
> ⚠️ **Linux only for now.** Enforcement relies on POSIX `execve` and Linux namespaces. On macOS/Windows the policy is saved to `ixt.json` but **not enforced at runtime** — the tool runs with the full environment.
|
|
138
|
-
> Install [bubblewrap](https://github.com/containers/bubblewrap) for **effective env/fs enforcement**: `sudo apt install bubblewrap` / `sudo dnf install bubblewrap` / `sudo pacman -S bubblewrap` / `sudo apk add bubblewrap`. The env axis runs without it but only as a hygiene measure (cleans logs/traces); the fs axis requires it and errors out otherwise. Network isolation is not implemented yet.
|
|
139
|
-
> This is not a general malware sandbox.
|
|
140
|
-
|
|
141
|
-
[Full policy reference →](https://gitlab.com/pytgaen-group/ixt/-/blob/main/docs/policy/index.md)
|
|
142
|
-
|
|
143
126
|
## 📚 Built-in Registry
|
|
144
127
|
|
|
145
128
|
ixt ships with a built-in registry that maps common tool names to their source. No need to remember the GitHub path:
|
|
@@ -174,6 +157,24 @@ ixt tool install sharkdp/fd@10 # latest 10.x.x
|
|
|
174
157
|
ixt tool install ruff>=0.5.0 # PEP 508 (Python)
|
|
175
158
|
```
|
|
176
159
|
|
|
160
|
+
## 🔒 Declarative locks
|
|
161
|
+
|
|
162
|
+
`ixt.toml` is the human-edited intent: which tools you want, with optional
|
|
163
|
+
version bounds, exposure rules, and injections. `ixt.lock` is generated from
|
|
164
|
+
that intent and records the exact resolved versions and binary release assets.
|
|
165
|
+
|
|
166
|
+
For teams and CI, commit both files:
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
ixt lock write
|
|
170
|
+
ixt lock check
|
|
171
|
+
ixt tool apply --locked --strict
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
`--locked` replays the exact lock instead of resolving newer versions.
|
|
175
|
+
`--strict` fails before changing the machine if `ixt.toml` and `ixt.lock`
|
|
176
|
+
diverge.
|
|
177
|
+
|
|
177
178
|
## 🧰 Commands
|
|
178
179
|
|
|
179
180
|
| Command | Description |
|
|
@@ -187,7 +188,11 @@ ixt tool install ruff>=0.5.0 # PEP 508 (Python)
|
|
|
187
188
|
| `ixt tool upgrade <pkg>` | Upgrade a tool |
|
|
188
189
|
| `ixt tool upgrade --all` | Upgrade all tools |
|
|
189
190
|
| `ixt tool apply` | Sync tools from declarative config |
|
|
191
|
+
| `ixt tool apply --locked --strict` | Sync using exact versions and assets from ixt.lock |
|
|
190
192
|
| `ixt tool export` | Export installed tools as ixt.toml (stdout), preserves install intent |
|
|
193
|
+
| `ixt lock write` | Resolve ixt.toml and write ixt.lock |
|
|
194
|
+
| `ixt lock check` | Fail if ixt.lock is missing or stale |
|
|
195
|
+
| `ixt lock diff` | Show semantic differences between ixt.toml and ixt.lock |
|
|
191
196
|
| `ixt asset-index export` | Export portable binary-resolution metadata as JSON |
|
|
192
197
|
| `ixt tool config <pkg> expose <rules...>` | Change exposed binaries |
|
|
193
198
|
| `ixt tool config <pkg> inject <dep>` | Inject a package into the tool's env |
|
|
@@ -210,7 +215,8 @@ ixt tool install ruff>=0.5.0 # PEP 508 (Python)
|
|
|
210
215
|
```
|
|
211
216
|
$IXT_HOME/ # default ~/.local/share/ixt (respects $XDG_DATA_HOME)
|
|
212
217
|
├── config/
|
|
213
|
-
│ ├── ixt.toml
|
|
218
|
+
│ ├── ixt.toml # global declarative manifest fallback
|
|
219
|
+
│ ├── ixt.lock # global lock when using the global ixt.toml
|
|
214
220
|
│ ├── registry.toml
|
|
215
221
|
│ └── heuristics.toml
|
|
216
222
|
└── installed/
|
|
@@ -234,12 +240,12 @@ $IXT_CACHE_HOME/ # default ~/.cache/ixt (respects $XDG_CACHE_HOME)
|
|
|
234
240
|
| | uv tool | pipx | npm -g | aqua | mise | proto | **ixt** |
|
|
235
241
|
| ------------------------ | ------- | ---- | ------ | ---- | --------- | ----- | ------- |
|
|
236
242
|
| Python tools (PyPI) | ✅ | ✅ | — | — | ✅ (pipx) | — | ✅ |
|
|
237
|
-
|
|
|
243
|
+
| npm packages | — | — | ✅ | — | ✅ (npm) | — | ✅ |
|
|
238
244
|
| GitHub Releases binaries | — | — | — | ✅ | ✅ (ubi) | ~ | ✅ |
|
|
245
|
+
| Day-one GitHub releases | — | — | — | ❌ | ❌ | ~ | ✅ |
|
|
239
246
|
| Dependency isolation | ✅ | ✅ | ❌ | — | partial | — | ✅ |
|
|
240
247
|
| Fine-grained exposure | — | — | — | — | — | — | ✅ |
|
|
241
|
-
|
|
|
242
|
-
| Bootstrap from scratch | — | — | — | — | curl | curl | ✅ |
|
|
248
|
+
| No system runtime needed | — | — | ❌ | ❌ | ❌ | ❌ | ✅ |
|
|
243
249
|
| Declarative config | — | — | — | YAML | TOML | TOML | TOML |
|
|
244
250
|
|
|
245
251
|
ixt is **not** a runtime version manager (mise, proto, asdf cover that). It manages the **last mile**: install, isolate, expose and group CLI tools across three ecosystems.
|
|
@@ -257,10 +263,10 @@ uv run ruff format src/ tests/ # format
|
|
|
257
263
|
|
|
258
264
|
## 🤝 Contributing
|
|
259
265
|
|
|
260
|
-
Development happens on **GitLab**: <https://gitlab.com/pytgaen-group/ixt>.
|
|
266
|
+
Development happens on **GitLab**: <https://gitlab.com/pytgaen-group/ixt>. Any GitHub repository for this project is a **read-only mirror** of GitLab; it is not the review or issue tracker. Please open issues and merge requests on GitLab.
|
|
261
267
|
|
|
262
268
|
Bug reports and reproduction recipes are welcome. See [`CONTRIBUTING.md`](CONTRIBUTING.md) for details, or open an issue to start a discussion.
|
|
263
269
|
|
|
264
270
|
## 📄 License
|
|
265
271
|
|
|
266
|
-
MIT
|
|
272
|
+
MIT. See [`LICENSE`](LICENSE).
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "ixt-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.9.0" # to bump
|
|
8
8
|
description = "Universal CLI tool isolator - manage Python, Node, and binary tools with per-tool isolation"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -37,10 +37,6 @@ dev = [
|
|
|
37
37
|
"pytest-cov>=4.1",
|
|
38
38
|
"pyright>=1.1",
|
|
39
39
|
]
|
|
40
|
-
# Standalone binary build (Nuitka). Not a runtime dependency — install with
|
|
41
|
-
# `uv sync --group build`. [onefile] pulls zstandard for payload compression.
|
|
42
|
-
# See notes/standalone-build.md.
|
|
43
|
-
build = ["nuitka[onefile]>=2.5"]
|
|
44
40
|
|
|
45
41
|
[project.scripts]
|
|
46
42
|
ixt = "ixt.cli:main"
|
|
@@ -49,7 +45,7 @@ ixt = "ixt.cli:main"
|
|
|
49
45
|
Homepage = "https://gitlab.com/pytgaen-group/ixt"
|
|
50
46
|
Repository = "https://gitlab.com/pytgaen-group/ixt.git"
|
|
51
47
|
Issues = "https://gitlab.com/pytgaen-group/ixt/-/issues"
|
|
52
|
-
Documentation = "https://gitlab.
|
|
48
|
+
Documentation = "https://ixt-39f604.gitlab.io"
|
|
53
49
|
|
|
54
50
|
[tool.hatch.build.targets.wheel]
|
|
55
51
|
packages = ["src/ixt"]
|
|
@@ -23,6 +23,7 @@ from ixt.config.heuristics import (
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
if TYPE_CHECKING:
|
|
26
|
+
from ixt.config.lockfile import BinaryLockMetadata
|
|
26
27
|
from ixt.config.setup_toml import SetupConfig
|
|
27
28
|
from ixt.config.settings import Settings, get_settings
|
|
28
29
|
from ixt.core.backend import BackendType
|
|
@@ -30,10 +31,12 @@ from ixt.core.discover import find_executables
|
|
|
30
31
|
from ixt.core.extract import extract, is_archive
|
|
31
32
|
from ixt.libs.logger import get_logger, get_verbosity
|
|
32
33
|
from ixt.net.source import Asset, Release, ReleaseSource, RepoSpec, parse_spec
|
|
34
|
+
from ixt.platform import get_arch, get_os
|
|
33
35
|
|
|
34
36
|
__all__ = [
|
|
35
37
|
"AssetSelectionError",
|
|
36
38
|
"BinaryBackend",
|
|
39
|
+
"BinaryPlatformError",
|
|
37
40
|
"BinarySpecError",
|
|
38
41
|
"HookEntry",
|
|
39
42
|
"SetupMetadata",
|
|
@@ -89,6 +92,31 @@ class AssetSelectionError(RuntimeError):
|
|
|
89
92
|
)
|
|
90
93
|
|
|
91
94
|
|
|
95
|
+
class BinaryPlatformError(RuntimeError):
|
|
96
|
+
"""Raised when extracted executables are incompatible with the current platform."""
|
|
97
|
+
|
|
98
|
+
def __init__(
|
|
99
|
+
self,
|
|
100
|
+
spec: str,
|
|
101
|
+
release_tag: str,
|
|
102
|
+
asset_name: str,
|
|
103
|
+
rejected: list[tuple[Path, str]],
|
|
104
|
+
):
|
|
105
|
+
self.spec = spec
|
|
106
|
+
self.release_tag = release_tag
|
|
107
|
+
self.asset_name = asset_name
|
|
108
|
+
self.rejected = rejected
|
|
109
|
+
platform = f"{get_os().value}/{get_arch().value}"
|
|
110
|
+
details = ", ".join(
|
|
111
|
+
f"{path.name} ({format_label})"
|
|
112
|
+
for path, format_label in sorted(rejected, key=lambda item: str(item[0]))
|
|
113
|
+
)
|
|
114
|
+
super().__init__(
|
|
115
|
+
f"No executable compatible with {platform} found for '{spec}' "
|
|
116
|
+
f"(release {release_tag}, asset {asset_name}). Rejected: {details}"
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
|
|
92
120
|
class BinarySpecError(ValueError):
|
|
93
121
|
"""Raised when a spec cannot be parsed as a binary backend spec."""
|
|
94
122
|
|
|
@@ -396,6 +424,7 @@ class BinaryBackend:
|
|
|
396
424
|
setup_path: Path | None = None,
|
|
397
425
|
asset_pattern: str | None = None,
|
|
398
426
|
known_tag: str | None = None,
|
|
427
|
+
locked_binary: BinaryLockMetadata | None = None,
|
|
399
428
|
use_cache: bool = True,
|
|
400
429
|
) -> dict | None:
|
|
401
430
|
"""Compatibility adapter for the shared backend protocol."""
|
|
@@ -410,6 +439,7 @@ class BinaryBackend:
|
|
|
410
439
|
setup_path=setup_path,
|
|
411
440
|
asset_pattern=asset_pattern,
|
|
412
441
|
known_tag=known_tag,
|
|
442
|
+
locked_binary=locked_binary,
|
|
413
443
|
use_cache=use_cache,
|
|
414
444
|
)
|
|
415
445
|
|
|
@@ -422,6 +452,7 @@ class BinaryBackend:
|
|
|
422
452
|
setup_path: Path | None = None,
|
|
423
453
|
asset_pattern: str | None = None,
|
|
424
454
|
known_tag: str | None = None,
|
|
455
|
+
locked_binary: BinaryLockMetadata | None = None,
|
|
425
456
|
use_cache: bool = True,
|
|
426
457
|
) -> dict | None:
|
|
427
458
|
"""Resolve, download, extract, and discover one atomic binary tool.
|
|
@@ -460,24 +491,34 @@ class BinaryBackend:
|
|
|
460
491
|
)
|
|
461
492
|
release_pattern_name = setup.name if setup and setup.name else None
|
|
462
493
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
else:
|
|
471
|
-
release = self._fetch_release(
|
|
472
|
-
source,
|
|
473
|
-
repo_spec,
|
|
474
|
-
env_dir=env_dir,
|
|
475
|
-
upgrade=upgrade,
|
|
476
|
-
known_tag=known_tag,
|
|
477
|
-
asset_pattern=release_pattern,
|
|
478
|
-
pattern_name=release_pattern_name,
|
|
494
|
+
if locked_binary is not None:
|
|
495
|
+
if not repo_spec.version:
|
|
496
|
+
raise BinarySpecError("locked binary install requires a pinned release tag")
|
|
497
|
+
release = Release(
|
|
498
|
+
tag=repo_spec.version,
|
|
499
|
+
assets=[Asset(name=locked_binary.asset, url=locked_binary.url)],
|
|
500
|
+
resolution_source="lockfile",
|
|
479
501
|
)
|
|
480
|
-
log.debug(f"release: {release.tag} ({
|
|
502
|
+
log.debug(f"release (lockfile): {release.tag} ({locked_binary.asset})")
|
|
503
|
+
else:
|
|
504
|
+
# Reuse the release already fetched during version resolution (e.g. an
|
|
505
|
+
# upgrade or a dry-run within the TTL window). This skips a second API
|
|
506
|
+
# call — the only option for GitLab, whose asset URLs can't be rebuilt
|
|
507
|
+
# from a pattern like GitHub's.
|
|
508
|
+
release = self._cached_release(spec) if use_cache and not repo_spec.version else None
|
|
509
|
+
if release is not None:
|
|
510
|
+
log.debug(f"release (resolve cache): {release.tag} ({len(release.assets)} assets)")
|
|
511
|
+
else:
|
|
512
|
+
release = self._fetch_release(
|
|
513
|
+
source,
|
|
514
|
+
repo_spec,
|
|
515
|
+
env_dir=env_dir,
|
|
516
|
+
upgrade=upgrade,
|
|
517
|
+
known_tag=known_tag,
|
|
518
|
+
asset_pattern=release_pattern,
|
|
519
|
+
pattern_name=release_pattern_name,
|
|
520
|
+
)
|
|
521
|
+
log.debug(f"release: {release.tag} ({len(release.assets)} assets)")
|
|
481
522
|
|
|
482
523
|
if setup is None and not setup_resolved:
|
|
483
524
|
setup = self._resolve_setup(
|
|
@@ -491,7 +532,10 @@ class BinaryBackend:
|
|
|
491
532
|
if asset_pattern:
|
|
492
533
|
log.debug(f"user-forced asset_pattern: {asset_pattern!r}")
|
|
493
534
|
selection_pattern = _selection_pattern(effective_pattern, release.tag)
|
|
494
|
-
|
|
535
|
+
if locked_binary is None:
|
|
536
|
+
asset = self._select_asset(release, selection_pattern, tool_name, spec)
|
|
537
|
+
else:
|
|
538
|
+
asset = release.assets[0]
|
|
495
539
|
log.debug(f"selected asset: {asset.name}")
|
|
496
540
|
|
|
497
541
|
archive_path = self._download_asset(
|
|
@@ -512,8 +556,16 @@ class BinaryBackend:
|
|
|
512
556
|
log.debug(f"extracted to: {result.root}")
|
|
513
557
|
|
|
514
558
|
heuristics = load_heuristics()
|
|
515
|
-
|
|
559
|
+
rejected_executables: list[tuple[Path, str]] = []
|
|
560
|
+
executables = find_executables(
|
|
561
|
+
result.root,
|
|
562
|
+
expose=heuristics.expose,
|
|
563
|
+
log=log,
|
|
564
|
+
rejected=rejected_executables,
|
|
565
|
+
)
|
|
516
566
|
log.debug(f"executables: {[e.name for e in executables]}")
|
|
567
|
+
if not executables and rejected_executables:
|
|
568
|
+
raise BinaryPlatformError(spec, release.tag, asset.name, rejected_executables)
|
|
517
569
|
|
|
518
570
|
binaries = {exe.name: str(exe) for exe in executables}
|
|
519
571
|
|