ixt-cli 0.8.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.
- ixt/__init__.py +8 -0
- ixt/__main__.py +8 -0
- ixt/backends/__init__.py +1 -0
- ixt/backends/binary.py +935 -0
- ixt/backends/binary_resolver.py +307 -0
- ixt/backends/node.py +490 -0
- ixt/backends/python.py +234 -0
- ixt/cli/__init__.py +31 -0
- ixt/cli/argparse_completion.py +557 -0
- ixt/cli/cmd_apply.py +404 -0
- ixt/cli/cmd_cache.py +86 -0
- ixt/cli/cmd_config.py +295 -0
- ixt/cli/cmd_info.py +116 -0
- ixt/cli/cmd_install.py +508 -0
- ixt/cli/cmd_misc.py +261 -0
- ixt/cli/cmd_registry.py +35 -0
- ixt/cli/cmd_upgrade.py +336 -0
- ixt/cli/commands.py +70 -0
- ixt/cli/parser.py +555 -0
- ixt/cli/render.py +85 -0
- ixt/config/__init__.py +5 -0
- ixt/config/asset_index.py +305 -0
- ixt/config/asset_pattern_cache.py +87 -0
- ixt/config/env_policy.py +340 -0
- ixt/config/flags.py +29 -0
- ixt/config/fs_policy.py +17 -0
- ixt/config/heuristics.py +465 -0
- ixt/config/models.py +176 -0
- ixt/config/registry.py +145 -0
- ixt/config/settings.py +173 -0
- ixt/config/setup_toml.py +179 -0
- ixt/config/toml.py +416 -0
- ixt/core/__init__.py +16 -0
- ixt/core/apply.py +564 -0
- ixt/core/apply_actions.py +106 -0
- ixt/core/backend.py +187 -0
- ixt/core/bootstrap.py +410 -0
- ixt/core/cache.py +332 -0
- ixt/core/discover.py +150 -0
- ixt/core/doctor.py +591 -0
- ixt/core/export.py +419 -0
- ixt/core/expose.py +350 -0
- ixt/core/extract.py +261 -0
- ixt/core/hooks.py +182 -0
- ixt/core/identity.py +148 -0
- ixt/core/inject.py +143 -0
- ixt/core/install.py +509 -0
- ixt/core/install_local.py +229 -0
- ixt/core/locks.py +54 -0
- ixt/core/pathlink.py +86 -0
- ixt/core/resolution_stats.py +191 -0
- ixt/core/resolve.py +150 -0
- ixt/core/resolve_cache.py +185 -0
- ixt/core/runtimes.py +192 -0
- ixt/core/save.py +237 -0
- ixt/core/setup_completions.py +11 -0
- ixt/core/setup_path.py +368 -0
- ixt/core/upgrade.py +596 -0
- ixt/data/__init__.py +10 -0
- ixt/data/asset_index.json +574 -0
- ixt/data/heuristics.toml +98 -0
- ixt/data/registry.toml +71 -0
- ixt/libs/__init__.py +3 -0
- ixt/libs/constants.py +4 -0
- ixt/libs/fmt.py +108 -0
- ixt/libs/logger.py +109 -0
- ixt/libs/output.py +25 -0
- ixt/libs/req_spec.py +115 -0
- ixt/libs/semver.py +149 -0
- ixt/libs/shell.py +126 -0
- ixt/libs/style.py +238 -0
- ixt/net/__init__.py +1 -0
- ixt/net/github_api.py +158 -0
- ixt/net/gitlab_api.py +149 -0
- ixt/net/http.py +194 -0
- ixt/net/npm.py +24 -0
- ixt/net/pypi.py +26 -0
- ixt/net/source.py +163 -0
- ixt/platform/__init__.py +131 -0
- ixt/platform/win.py +68 -0
- ixt_cli-0.8.0.dist-info/METADATA +294 -0
- ixt_cli-0.8.0.dist-info/RECORD +84 -0
- ixt_cli-0.8.0.dist-info/WHEEL +4 -0
- ixt_cli-0.8.0.dist-info/entry_points.txt +2 -0
ixt/cli/parser.py
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"""Argument parser for the ixt CLI — built on stdlib argparse."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
from importlib.metadata import version
|
|
7
|
+
|
|
8
|
+
from ixt.cli.argparse_completion import SUPPORTED_COMPLETION_SHELLS, complete_with
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _get_version() -> str:
|
|
12
|
+
try:
|
|
13
|
+
return version("ixt-cli")
|
|
14
|
+
except Exception:
|
|
15
|
+
return "unknown"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def effective_verbosity(args: argparse.Namespace) -> int:
|
|
19
|
+
"""Return global verbosity, including aliases accepted after a subcommand."""
|
|
20
|
+
return (getattr(args, "verbose", 0) or 0) + (getattr(args, "_verbose_after", 0) or 0)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def normalize_verbosity(args: argparse.Namespace) -> int:
|
|
24
|
+
"""Fold internal verbosity aliases into ``args.verbose`` and return it."""
|
|
25
|
+
verbosity = effective_verbosity(args)
|
|
26
|
+
args.verbose = verbosity
|
|
27
|
+
if hasattr(args, "_verbose_after"):
|
|
28
|
+
delattr(args, "_verbose_after")
|
|
29
|
+
return verbosity
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _add_global_verbose_alias(parser: argparse.ArgumentParser) -> None:
|
|
33
|
+
"""Accept global verbosity after selected subcommands.
|
|
34
|
+
|
|
35
|
+
argparse only accepts top-level options before the subcommand. Adding the
|
|
36
|
+
same destination on selected subparsers preserves the global ``args.verbose``
|
|
37
|
+
contract while allowing forms like ``ixt tool upgrade --all -v``. ``list -v`` is
|
|
38
|
+
intentionally excluded because it already means "show exact installed ids".
|
|
39
|
+
"""
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--verbose",
|
|
42
|
+
"-v",
|
|
43
|
+
action="count",
|
|
44
|
+
default=0,
|
|
45
|
+
dest="_verbose_after",
|
|
46
|
+
help=argparse.SUPPRESS,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
51
|
+
"""Build and return the top-level argument parser with all subcommands."""
|
|
52
|
+
parser = argparse.ArgumentParser(
|
|
53
|
+
prog="ixt",
|
|
54
|
+
description="Universal CLI tool isolator",
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--version",
|
|
58
|
+
"-V",
|
|
59
|
+
action="version",
|
|
60
|
+
version=f"%(prog)s {_get_version()}",
|
|
61
|
+
)
|
|
62
|
+
parser.add_argument(
|
|
63
|
+
"--verbose",
|
|
64
|
+
"-v",
|
|
65
|
+
action="count",
|
|
66
|
+
default=0,
|
|
67
|
+
help="Increase output verbosity (can be repeated)",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
sub = parser.add_subparsers(dest="resource", metavar="COMMAND")
|
|
71
|
+
|
|
72
|
+
# -- tool ------------------------------------------------------------------
|
|
73
|
+
p_tool = sub.add_parser("tool", help="Manage installed tools")
|
|
74
|
+
tool_sub = p_tool.add_subparsers(dest="tool_action", metavar="ACTION", required=True)
|
|
75
|
+
|
|
76
|
+
# -- tool add --------------------------------------------------------------
|
|
77
|
+
p_add = tool_sub.add_parser(
|
|
78
|
+
"add",
|
|
79
|
+
help="Add a tool to ixt.toml without installing it",
|
|
80
|
+
)
|
|
81
|
+
p_add.set_defaults(command="add")
|
|
82
|
+
p_add.add_argument(
|
|
83
|
+
"package",
|
|
84
|
+
help="Package spec to persist in ixt.toml (e.g. ruff, @scope/pkg, owner/repo)",
|
|
85
|
+
)
|
|
86
|
+
p_add.add_argument(
|
|
87
|
+
"--bare",
|
|
88
|
+
action="store_true",
|
|
89
|
+
help="Persist the tool without exposing any binary",
|
|
90
|
+
)
|
|
91
|
+
p_add.add_argument(
|
|
92
|
+
"--slot",
|
|
93
|
+
default=None,
|
|
94
|
+
metavar="SLOT",
|
|
95
|
+
help="Persist as a side-by-side slot (entry key)",
|
|
96
|
+
)
|
|
97
|
+
p_add.add_argument(
|
|
98
|
+
"--runtime",
|
|
99
|
+
choices=["bun", "bun-strict", "node"],
|
|
100
|
+
default=None,
|
|
101
|
+
help=(
|
|
102
|
+
"Runtime for npm packages. 'bun-strict' persists node_shim=false; "
|
|
103
|
+
"'node' persists runtime='node'. Ignored for python/binary backends."
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
p_add.add_argument(
|
|
107
|
+
"--asset-pattern",
|
|
108
|
+
dest="asset_pattern",
|
|
109
|
+
default=None,
|
|
110
|
+
metavar="PATTERN",
|
|
111
|
+
help=(
|
|
112
|
+
"Persist a forced asset selection pattern (binary backend only). "
|
|
113
|
+
"Supports {version}, {tag}, {os}, {arch} placeholders."
|
|
114
|
+
),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# -- tool install ----------------------------------------------------------
|
|
118
|
+
p_install = tool_sub.add_parser(
|
|
119
|
+
"install",
|
|
120
|
+
help="Install a tool in an isolated environment",
|
|
121
|
+
epilog=(
|
|
122
|
+
"Post-install configuration goes through 'ixt tool config <target> <action>' "
|
|
123
|
+
"(expose / inject / uninject)."
|
|
124
|
+
),
|
|
125
|
+
)
|
|
126
|
+
p_install.set_defaults(command="install")
|
|
127
|
+
p_install.add_argument(
|
|
128
|
+
"package",
|
|
129
|
+
nargs="?",
|
|
130
|
+
help="Package spec (e.g. ruff, ruff>=0.5). Omit when using --from.",
|
|
131
|
+
)
|
|
132
|
+
complete_with(
|
|
133
|
+
p_install.add_argument(
|
|
134
|
+
"--from",
|
|
135
|
+
dest="from_path",
|
|
136
|
+
default=None,
|
|
137
|
+
metavar="PATH",
|
|
138
|
+
help=(
|
|
139
|
+
"Install from a local directory (auto-detected backend). "
|
|
140
|
+
"Mutually exclusive with the positional package spec."
|
|
141
|
+
),
|
|
142
|
+
),
|
|
143
|
+
"dir",
|
|
144
|
+
)
|
|
145
|
+
p_install.add_argument(
|
|
146
|
+
"--bare",
|
|
147
|
+
action="store_true",
|
|
148
|
+
help="Install without exposing any binary (configure later with ixt tool config)",
|
|
149
|
+
)
|
|
150
|
+
p_install.add_argument(
|
|
151
|
+
"--reinstall",
|
|
152
|
+
action="store_true",
|
|
153
|
+
help="Reinstall if already installed",
|
|
154
|
+
)
|
|
155
|
+
p_install.add_argument(
|
|
156
|
+
"--slot",
|
|
157
|
+
default=None,
|
|
158
|
+
metavar="SLOT",
|
|
159
|
+
help="Install into a side-by-side slot (id prefix)",
|
|
160
|
+
)
|
|
161
|
+
p_install.add_argument(
|
|
162
|
+
"--runtime",
|
|
163
|
+
choices=["bun", "bun-strict", "node"],
|
|
164
|
+
default="bun",
|
|
165
|
+
help=(
|
|
166
|
+
"Runtime for npm packages (default: bun). "
|
|
167
|
+
"'bun' rewrites '#!/usr/bin/env node' shebangs to bun. "
|
|
168
|
+
"'bun-strict' runs bun without shebang rewrite (host must have node). "
|
|
169
|
+
"'node' uses node natively. Ignored for python/binary backends."
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
complete_with(
|
|
173
|
+
p_install.add_argument(
|
|
174
|
+
"--setup-file",
|
|
175
|
+
"-s",
|
|
176
|
+
dest="setup",
|
|
177
|
+
default=None,
|
|
178
|
+
metavar="PATH",
|
|
179
|
+
help="Use a local ixt.setup.toml (overrides remote fetch)",
|
|
180
|
+
),
|
|
181
|
+
"file",
|
|
182
|
+
)
|
|
183
|
+
p_install.add_argument(
|
|
184
|
+
"--asset-pattern",
|
|
185
|
+
dest="asset_pattern",
|
|
186
|
+
default=None,
|
|
187
|
+
metavar="PATTERN",
|
|
188
|
+
help=(
|
|
189
|
+
"Force the asset selection pattern (binary backend only). "
|
|
190
|
+
"Supports {version}, {tag}, {os}, {arch} placeholders. "
|
|
191
|
+
"Persisted in the tool's ixt.json; survives upgrades, "
|
|
192
|
+
"lost on uninstall."
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
p_install.add_argument(
|
|
196
|
+
"--save",
|
|
197
|
+
action="store_true",
|
|
198
|
+
help="Add the tool to ixt.toml",
|
|
199
|
+
)
|
|
200
|
+
p_install.add_argument(
|
|
201
|
+
"--dry-run",
|
|
202
|
+
action="store_true",
|
|
203
|
+
dest="dry_run",
|
|
204
|
+
help="Show what would be installed without touching disk or network",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# -- tool uninstall --------------------------------------------------------
|
|
208
|
+
p_uninstall = tool_sub.add_parser("uninstall", help="Remove an installed tool")
|
|
209
|
+
p_uninstall.set_defaults(command="uninstall")
|
|
210
|
+
p_uninstall.add_argument(
|
|
211
|
+
"target",
|
|
212
|
+
nargs="?",
|
|
213
|
+
help="Tool spec or @id:<installed-id> to remove",
|
|
214
|
+
)
|
|
215
|
+
p_uninstall.add_argument(
|
|
216
|
+
"--all",
|
|
217
|
+
action="store_true",
|
|
218
|
+
dest="uninstall_all",
|
|
219
|
+
help="Remove all installed tools",
|
|
220
|
+
)
|
|
221
|
+
p_uninstall.add_argument(
|
|
222
|
+
"--save",
|
|
223
|
+
action="store_true",
|
|
224
|
+
help="Remove the tool from ixt.toml",
|
|
225
|
+
)
|
|
226
|
+
p_uninstall.add_argument(
|
|
227
|
+
"--yes",
|
|
228
|
+
"-y",
|
|
229
|
+
dest="yes",
|
|
230
|
+
action="store_true",
|
|
231
|
+
help="Skip confirmation prompt for --all",
|
|
232
|
+
)
|
|
233
|
+
p_uninstall.add_argument(
|
|
234
|
+
"--dry-run",
|
|
235
|
+
action="store_true",
|
|
236
|
+
dest="dry_run",
|
|
237
|
+
help="Show what would be removed without touching disk",
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
# -- tool upgrade ----------------------------------------------------------
|
|
241
|
+
p_upgrade = tool_sub.add_parser("upgrade", help="Upgrade a tool to the latest version")
|
|
242
|
+
p_upgrade.set_defaults(command="upgrade")
|
|
243
|
+
_add_global_verbose_alias(p_upgrade)
|
|
244
|
+
p_upgrade.add_argument(
|
|
245
|
+
"tool",
|
|
246
|
+
nargs="?",
|
|
247
|
+
help="Tool spec or @id:<installed-id> (omit for --all)",
|
|
248
|
+
)
|
|
249
|
+
p_upgrade.add_argument(
|
|
250
|
+
"--all",
|
|
251
|
+
action="store_true",
|
|
252
|
+
dest="upgrade_all",
|
|
253
|
+
help="Upgrade all installed tools",
|
|
254
|
+
)
|
|
255
|
+
p_upgrade.add_argument(
|
|
256
|
+
"--dry-run",
|
|
257
|
+
action="store_true",
|
|
258
|
+
dest="dry_run",
|
|
259
|
+
help="Resolve new versions and show old -> new without installing",
|
|
260
|
+
)
|
|
261
|
+
p_upgrade.add_argument(
|
|
262
|
+
"--no-cache",
|
|
263
|
+
action="store_true",
|
|
264
|
+
dest="no_cache",
|
|
265
|
+
help="Ignore the resolve cache; always check upstream for the latest version",
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# -- tool list -------------------------------------------------------------
|
|
269
|
+
p_list = tool_sub.add_parser("list", help="List all installed tools")
|
|
270
|
+
p_list.set_defaults(command="list")
|
|
271
|
+
p_list.add_argument(
|
|
272
|
+
"--verbose",
|
|
273
|
+
"-v",
|
|
274
|
+
action="store_true",
|
|
275
|
+
dest="verbose_list",
|
|
276
|
+
help="Show exact installed ids",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# -- tool info -------------------------------------------------------------
|
|
280
|
+
p_info = tool_sub.add_parser("info", help="Show details about an installed tool")
|
|
281
|
+
p_info.set_defaults(command="info")
|
|
282
|
+
p_info.add_argument("tool", help="Tool spec or @id:<installed-id>")
|
|
283
|
+
|
|
284
|
+
# -- tool config -----------------------------------------------------------
|
|
285
|
+
p_config = tool_sub.add_parser(
|
|
286
|
+
"config",
|
|
287
|
+
help="Change an installed tool's settings (expose / inject / env / fs / …)",
|
|
288
|
+
)
|
|
289
|
+
p_config.set_defaults(command="config")
|
|
290
|
+
p_config.add_argument(
|
|
291
|
+
"tool",
|
|
292
|
+
help=(
|
|
293
|
+
"Tool spec, @id:<installed-id>, or '__all__' to apply the action "
|
|
294
|
+
"to every installed tool (expose only)"
|
|
295
|
+
),
|
|
296
|
+
)
|
|
297
|
+
config_action = p_config.add_subparsers(dest="config_action", metavar="ACTION", required=True)
|
|
298
|
+
|
|
299
|
+
p_conf_expose = config_action.add_parser(
|
|
300
|
+
"expose", help="Change which binaries are exposed to PATH"
|
|
301
|
+
)
|
|
302
|
+
p_conf_expose.add_argument(
|
|
303
|
+
"rules", nargs="+", metavar="RULE", help="Exposure rules (e.g. __all__, name:alias)"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
p_conf_inject = config_action.add_parser(
|
|
307
|
+
"inject", help="Add a package to the tool's environment"
|
|
308
|
+
)
|
|
309
|
+
p_conf_inject.add_argument("package", help="Package spec to inject")
|
|
310
|
+
|
|
311
|
+
p_conf_uninject = config_action.add_parser(
|
|
312
|
+
"uninject", help="Remove an injected package from the tool's environment"
|
|
313
|
+
)
|
|
314
|
+
p_conf_uninject.add_argument("package", help="Package name to remove")
|
|
315
|
+
|
|
316
|
+
p_conf_env = config_action.add_parser(
|
|
317
|
+
"env", help="Manage the environment variable policy for a tool"
|
|
318
|
+
)
|
|
319
|
+
env_sub = p_conf_env.add_subparsers(dest="env_action", metavar="ACTION")
|
|
320
|
+
|
|
321
|
+
p_env_base = env_sub.add_parser("base", help="Set the base policy (all / none / os-common)")
|
|
322
|
+
p_env_base.add_argument("value", choices=["all", "none", "os-common"])
|
|
323
|
+
|
|
324
|
+
p_env_allow = env_sub.add_parser("allow", help="Add glob patterns to the allow list")
|
|
325
|
+
p_env_allow.add_argument("patterns", nargs="+", metavar="PATTERN")
|
|
326
|
+
|
|
327
|
+
p_env_deny = env_sub.add_parser("deny", help="Add glob patterns to the deny list (always wins)")
|
|
328
|
+
p_env_deny.add_argument("patterns", nargs="+", metavar="PATTERN")
|
|
329
|
+
p_env_deny.add_argument(
|
|
330
|
+
"--except",
|
|
331
|
+
dest="exceptions",
|
|
332
|
+
nargs="+",
|
|
333
|
+
metavar="PATTERN",
|
|
334
|
+
default=[],
|
|
335
|
+
help="Variables excluded from this deny rule",
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
env_sub.add_parser("list", help="Show the current env policy (default when no action given)")
|
|
339
|
+
env_sub.add_parser("reset", help="Clear all env policy rules (restores symlink)")
|
|
340
|
+
|
|
341
|
+
p_conf_fs = config_action.add_parser(
|
|
342
|
+
"fs", help="Manage the filesystem policy for a tool (requires bwrap)"
|
|
343
|
+
)
|
|
344
|
+
fs_sub = p_conf_fs.add_subparsers(dest="fs_action", metavar="ACTION")
|
|
345
|
+
|
|
346
|
+
p_fs_base = fs_sub.add_parser(
|
|
347
|
+
"base", help="Set the fs base policy (all / app-common / app-minimal / none)"
|
|
348
|
+
)
|
|
349
|
+
p_fs_base.add_argument("value", choices=["all", "app-common", "app-minimal", "none"])
|
|
350
|
+
|
|
351
|
+
p_fs_ro = fs_sub.add_parser("ro", help="Add read-only bind paths")
|
|
352
|
+
complete_with(p_fs_ro.add_argument("paths", nargs="+", metavar="PATH"), "file")
|
|
353
|
+
|
|
354
|
+
p_fs_rw = fs_sub.add_parser("rw", help="Add read-write bind paths")
|
|
355
|
+
complete_with(p_fs_rw.add_argument("paths", nargs="+", metavar="PATH"), "file")
|
|
356
|
+
|
|
357
|
+
p_fs_scratch = fs_sub.add_parser(
|
|
358
|
+
"scratch", help="Add scratch paths (empty rw tmpfs, discarded on exit)"
|
|
359
|
+
)
|
|
360
|
+
complete_with(p_fs_scratch.add_argument("paths", nargs="+", metavar="PATH"), "file")
|
|
361
|
+
|
|
362
|
+
fs_sub.add_parser("list", help="Show the current fs policy (default when no action given)")
|
|
363
|
+
fs_sub.add_parser("reset", help="Clear all fs policy rules (restores direct exec)")
|
|
364
|
+
|
|
365
|
+
# -- tool apply ------------------------------------------------------------
|
|
366
|
+
p_apply = tool_sub.add_parser("apply", help="Install/sync tools from ixt.toml")
|
|
367
|
+
p_apply.set_defaults(command="apply")
|
|
368
|
+
complete_with(
|
|
369
|
+
p_apply.add_argument(
|
|
370
|
+
"config_file",
|
|
371
|
+
nargs="?",
|
|
372
|
+
default=None,
|
|
373
|
+
help="Path to ixt.toml (default: auto-discover)",
|
|
374
|
+
),
|
|
375
|
+
"file",
|
|
376
|
+
)
|
|
377
|
+
p_apply.add_argument(
|
|
378
|
+
"--remove",
|
|
379
|
+
action="store_true",
|
|
380
|
+
help="Remove tools not listed in ixt.toml",
|
|
381
|
+
)
|
|
382
|
+
p_apply.add_argument(
|
|
383
|
+
"--yes",
|
|
384
|
+
"-y",
|
|
385
|
+
dest="yes",
|
|
386
|
+
action="store_true",
|
|
387
|
+
help="Skip confirmation prompts (e.g. --remove)",
|
|
388
|
+
)
|
|
389
|
+
p_apply.add_argument(
|
|
390
|
+
"--dry-run",
|
|
391
|
+
action="store_true",
|
|
392
|
+
dest="dry_run",
|
|
393
|
+
help="Show what would change without installing, reinstalling, or removing",
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# -- tool export -----------------------------------------------------------
|
|
397
|
+
p_export_tools = tool_sub.add_parser(
|
|
398
|
+
"export",
|
|
399
|
+
help="Export installed tools as ixt.toml (stdout), preserving install intent",
|
|
400
|
+
)
|
|
401
|
+
p_export_tools.set_defaults(command="export", export_action="tools")
|
|
402
|
+
p_export_tools.add_argument(
|
|
403
|
+
"names",
|
|
404
|
+
nargs="*",
|
|
405
|
+
help="Tool spec or @id:<installed-id> selectors to export (default: all installed tools)",
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
# -- asset-index export ----------------------------------------------------
|
|
409
|
+
p_asset_index = sub.add_parser("asset-index", help="Manage asset_index.json metadata")
|
|
410
|
+
asset_index_sub = p_asset_index.add_subparsers(
|
|
411
|
+
dest="asset_index_action",
|
|
412
|
+
metavar="ACTION",
|
|
413
|
+
required=True,
|
|
414
|
+
)
|
|
415
|
+
p_export_asset_index = asset_index_sub.add_parser(
|
|
416
|
+
"export",
|
|
417
|
+
help="Export portable binary-resolution metadata as asset_index.json",
|
|
418
|
+
)
|
|
419
|
+
p_export_asset_index.set_defaults(command="export", export_action="asset-index")
|
|
420
|
+
complete_with(
|
|
421
|
+
p_export_asset_index.add_argument(
|
|
422
|
+
"--from-registry",
|
|
423
|
+
dest="from_registry",
|
|
424
|
+
default=None,
|
|
425
|
+
metavar="PATH",
|
|
426
|
+
help="Generate asset-index patterns from a registry.toml file",
|
|
427
|
+
),
|
|
428
|
+
"file",
|
|
429
|
+
)
|
|
430
|
+
p_export_asset_index.add_argument(
|
|
431
|
+
"--all-platforms",
|
|
432
|
+
action="store_true",
|
|
433
|
+
help="Generate the initial linux/macos x86_64/aarch64 platform matrix",
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
# -- cache -----------------------------------------------------------------
|
|
437
|
+
p_cache = sub.add_parser("cache", help="Inspect and prune ixt caches")
|
|
438
|
+
p_cache.set_defaults(command="cache")
|
|
439
|
+
cache_sub = p_cache.add_subparsers(dest="cache_action", metavar="ACTION")
|
|
440
|
+
|
|
441
|
+
cache_sub.add_parser("info", help="Show cache paths and sizes")
|
|
442
|
+
|
|
443
|
+
p_cache_prune = cache_sub.add_parser(
|
|
444
|
+
"prune",
|
|
445
|
+
help="Prune old indexed download artifacts",
|
|
446
|
+
)
|
|
447
|
+
p_cache_prune.add_argument(
|
|
448
|
+
"--keep",
|
|
449
|
+
type=int,
|
|
450
|
+
default=2,
|
|
451
|
+
metavar="N",
|
|
452
|
+
help="Keep the newest N indexed download artifacts per repository (default: 2)",
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
p_cache_clear = cache_sub.add_parser("clear", help="Clear a cache subtree")
|
|
456
|
+
p_cache_clear.add_argument(
|
|
457
|
+
"target",
|
|
458
|
+
choices=["downloads", "metadata", "tmp", "all"],
|
|
459
|
+
help="'downloads' (network artifacts), 'metadata' (resolutions + indexes), 'tmp', or 'all'",
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
# -- registry --------------------------------------------------------------
|
|
463
|
+
p_registry = sub.add_parser("registry", help="Inspect registry entries")
|
|
464
|
+
p_registry.set_defaults(command="registry")
|
|
465
|
+
registry_sub = p_registry.add_subparsers(dest="registry_action", metavar="ACTION")
|
|
466
|
+
|
|
467
|
+
registry_sub.add_parser("list", help="List registry short names and specs")
|
|
468
|
+
|
|
469
|
+
# -- runtime ---------------------------------------------------------------
|
|
470
|
+
p_runtimes = sub.add_parser("runtime", help="Inspect, prune, and upgrade ixt-managed runtimes")
|
|
471
|
+
p_runtimes.set_defaults(command="runtime")
|
|
472
|
+
runtimes_sub = p_runtimes.add_subparsers(dest="runtimes_action", metavar="ACTION")
|
|
473
|
+
|
|
474
|
+
runtimes_sub.add_parser("info", help="Show ixt-managed runtime state")
|
|
475
|
+
|
|
476
|
+
p_runtimes_prune = runtimes_sub.add_parser(
|
|
477
|
+
"prune",
|
|
478
|
+
help="Remove unused ixt-managed runtimes",
|
|
479
|
+
)
|
|
480
|
+
p_runtimes_prune.add_argument(
|
|
481
|
+
"--all",
|
|
482
|
+
action="store_true",
|
|
483
|
+
help="Remove all ixt-managed runtimes, even when currently used",
|
|
484
|
+
)
|
|
485
|
+
|
|
486
|
+
p_runtimes_upgrade = runtimes_sub.add_parser(
|
|
487
|
+
"upgrade",
|
|
488
|
+
help="Install or replace an ixt-managed runtime",
|
|
489
|
+
)
|
|
490
|
+
p_runtimes_upgrade.add_argument(
|
|
491
|
+
"name",
|
|
492
|
+
choices=["uv", "bun", "all"],
|
|
493
|
+
help="Runtime to upgrade",
|
|
494
|
+
)
|
|
495
|
+
|
|
496
|
+
# -- doctor ----------------------------------------------------------------
|
|
497
|
+
p_doctor = sub.add_parser("doctor", help="Check ixt health and environment")
|
|
498
|
+
p_doctor.set_defaults(command="doctor")
|
|
499
|
+
p_doctor.add_argument(
|
|
500
|
+
"--no-network",
|
|
501
|
+
action="store_true",
|
|
502
|
+
help="Skip GitHub, PyPI, npm, and latest-version network probes",
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
# -- environment -----------------------------------------------------------
|
|
506
|
+
p_environment = sub.add_parser("environment", help="Show ixt configuration and paths")
|
|
507
|
+
p_environment.set_defaults(command="environment")
|
|
508
|
+
p_environment.add_argument(
|
|
509
|
+
"--sizes",
|
|
510
|
+
action="store_true",
|
|
511
|
+
help="Also show sizes for config, installed tools, runtimes, and caches",
|
|
512
|
+
)
|
|
513
|
+
|
|
514
|
+
# -- tool shell ------------------------------------------------------------
|
|
515
|
+
p_shell = tool_sub.add_parser("shell", help="Open a subshell in a tool's environment directory")
|
|
516
|
+
p_shell.set_defaults(command="shell")
|
|
517
|
+
p_shell.add_argument("tool", help="Tool spec or @id:<installed-id>")
|
|
518
|
+
|
|
519
|
+
# -- tool where ------------------------------------------------------------
|
|
520
|
+
p_where = tool_sub.add_parser("where", help="Print a tool's environment directory")
|
|
521
|
+
p_where.set_defaults(command="where")
|
|
522
|
+
p_where.add_argument("tool", help="Tool spec or @id:<installed-id>")
|
|
523
|
+
|
|
524
|
+
# -- setup -----------------------------------------------------------------
|
|
525
|
+
p_setup = sub.add_parser("setup", help="Configure shell integration (PATH, ...)")
|
|
526
|
+
p_setup.set_defaults(command="setup")
|
|
527
|
+
setup_sub = p_setup.add_subparsers(dest="setup_command", metavar="SUBCOMMAND")
|
|
528
|
+
|
|
529
|
+
p_setup_path = setup_sub.add_parser("path", help="Add ixt bin directory to PATH")
|
|
530
|
+
p_setup_path.add_argument(
|
|
531
|
+
"--check",
|
|
532
|
+
action="store_true",
|
|
533
|
+
help="Only check, don't modify anything",
|
|
534
|
+
)
|
|
535
|
+
from ixt.core.setup_path import KNOWN_SHELLS
|
|
536
|
+
|
|
537
|
+
p_setup_path.add_argument(
|
|
538
|
+
"--shell",
|
|
539
|
+
choices=list(KNOWN_SHELLS),
|
|
540
|
+
default=None,
|
|
541
|
+
help="Override shell detection (default: $SHELL)",
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
p_setup_completions = setup_sub.add_parser(
|
|
545
|
+
"completions",
|
|
546
|
+
help="Print shell completion script",
|
|
547
|
+
)
|
|
548
|
+
p_setup_completions.add_argument(
|
|
549
|
+
"--shell",
|
|
550
|
+
choices=list(SUPPORTED_COMPLETION_SHELLS),
|
|
551
|
+
required=True,
|
|
552
|
+
help="Shell to generate completions for",
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
return parser
|
ixt/cli/render.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Small CLI rendering helpers shared across command modules."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from ixt.config.models import ToolRecord
|
|
8
|
+
from ixt.libs.style import cyan, dim
|
|
9
|
+
from ixt.net.source import parse_spec
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def id_suffix(tool_id: str) -> str:
|
|
13
|
+
"""Render the compact installed-id suffix used in command summaries."""
|
|
14
|
+
return dim(f"(id: {tool_id})")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def format_source_tag(label: str | None) -> str:
|
|
18
|
+
"""Render a compact backend/source tag, or an empty suffix."""
|
|
19
|
+
return dim(f"[{label}]") if label else ""
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def source_label_for_record(record: ToolRecord) -> str:
|
|
23
|
+
"""Return the user-facing package source for an installed record."""
|
|
24
|
+
if record.source == "local":
|
|
25
|
+
return "local"
|
|
26
|
+
if record.backend == "python":
|
|
27
|
+
return "pypi"
|
|
28
|
+
if record.backend == "node":
|
|
29
|
+
return "npm"
|
|
30
|
+
if record.backend == "binary":
|
|
31
|
+
repo_spec = parse_spec(record.spec)
|
|
32
|
+
return repo_spec.platform if repo_spec is not None else "binary"
|
|
33
|
+
return record.backend
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def source_label_for_spec(
|
|
37
|
+
spec: str,
|
|
38
|
+
*,
|
|
39
|
+
backend_hint: str | None = None,
|
|
40
|
+
tool_id: str | None = None,
|
|
41
|
+
) -> str | None:
|
|
42
|
+
"""Best-effort source label for an action that has not produced metadata yet."""
|
|
43
|
+
if backend_hint in {"pypi", "python"}:
|
|
44
|
+
return "pypi"
|
|
45
|
+
if backend_hint in {"npm", "node"}:
|
|
46
|
+
return "npm"
|
|
47
|
+
|
|
48
|
+
repo_spec = parse_spec(spec)
|
|
49
|
+
if repo_spec is not None:
|
|
50
|
+
return repo_spec.platform
|
|
51
|
+
|
|
52
|
+
if backend_hint in {"github", "binary"}:
|
|
53
|
+
return "github"
|
|
54
|
+
|
|
55
|
+
if tool_id:
|
|
56
|
+
if tool_id.endswith(".pypi"):
|
|
57
|
+
return "pypi"
|
|
58
|
+
if tool_id.endswith(".npm"):
|
|
59
|
+
return "npm"
|
|
60
|
+
if tool_id.endswith(".github"):
|
|
61
|
+
return "github"
|
|
62
|
+
if tool_id.endswith(".gitlab"):
|
|
63
|
+
return "gitlab"
|
|
64
|
+
|
|
65
|
+
return None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def format_exposed_bin(exposed_name: str, source_path: str) -> str:
|
|
69
|
+
"""Render one exposed shim, showing aliases as ``alias -> source``."""
|
|
70
|
+
source_basename = Path(source_path).name
|
|
71
|
+
if source_basename == exposed_name:
|
|
72
|
+
return cyan(exposed_name)
|
|
73
|
+
return f"{cyan(exposed_name)} {dim('->')} {cyan(source_basename)}"
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def format_exposed_bins(exposed_bins: dict[str, str]) -> str:
|
|
77
|
+
"""Render exposed PATH shim names, or ``none``.
|
|
78
|
+
|
|
79
|
+
The source binary path is intentionally hidden here. ``exposed:`` answers
|
|
80
|
+
"what command names did ixt add to PATH?", not "which file do they point to?"
|
|
81
|
+
Detailed source paths are available through ``ixt tool info``.
|
|
82
|
+
"""
|
|
83
|
+
if not exposed_bins:
|
|
84
|
+
return dim("none")
|
|
85
|
+
return ", ".join(cyan(name) for name in sorted(exposed_bins))
|