ai-cli-toolkit 0.2.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.
ai_cli/update.py ADDED
@@ -0,0 +1,200 @@
1
+ """Tool install/update commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import subprocess
7
+ import sys
8
+ from typing import Optional
9
+
10
+ from ai_cli.config import ensure_config, get_tool_config
11
+ from ai_cli.tools import load_registry
12
+
13
+
14
+ def _run_shell(command: str) -> tuple[int, str]:
15
+ """Run a shell command and return (exit_code, combined_output)."""
16
+ result = subprocess.run(
17
+ ["bash", "-lc", command],
18
+ check=False,
19
+ capture_output=True,
20
+ text=True,
21
+ )
22
+ output = (result.stdout or "") + (result.stderr or "")
23
+ return result.returncode, output.strip()
24
+
25
+
26
+ def available_targets() -> list[str]:
27
+ """Return updatable tool names sorted by registry order."""
28
+ return list(load_registry().keys())
29
+
30
+
31
+ def _regenerate_completions() -> None:
32
+ """Regenerate shell completions to pick up newly installed tool flags."""
33
+ try:
34
+ from ai_cli.completion_gen import generate
35
+ print("\nRegenerating shell completions...")
36
+ generate(shell="all")
37
+ except Exception as exc:
38
+ print(f"Warning: could not regenerate completions: {exc}", file=sys.stderr)
39
+
40
+
41
+ def update_tool(tool_name: str, dry_run: bool = False, method: Optional[str] = None,
42
+ regen_completions: bool = True) -> int:
43
+ """Install or update one tool using its ToolSpec install command."""
44
+ registry = load_registry()
45
+ spec = registry.get(tool_name)
46
+ if spec is None:
47
+ print(f"Unknown tool: {tool_name}", file=sys.stderr)
48
+ return 1
49
+
50
+ # Resolve method (explicit, auto-detected, or default)
51
+ effective_method = method
52
+ if not effective_method and spec.install_methods:
53
+ effective_method = spec.detect_best_method()
54
+
55
+ command = (spec.get_install_command(method) or "").strip()
56
+ if not command:
57
+ if method:
58
+ available = ", ".join(spec.install_methods.keys()) or "(none)"
59
+ print(
60
+ f"Unknown install method '{method}' for {tool_name}. "
61
+ f"Available: {available}",
62
+ file=sys.stderr,
63
+ )
64
+ else:
65
+ print(f"No install/update command configured for {tool_name}.", file=sys.stderr)
66
+ return 1
67
+
68
+ config = ensure_config()
69
+ tool_cfg = get_tool_config(config, tool_name)
70
+
71
+ installed_before = spec.detect_installed(tool_cfg.get("binary", ""))
72
+ version_before = spec.get_version(tool_cfg.get("binary", "")) if installed_before else None
73
+
74
+ status = "update" if installed_before else "install"
75
+ method_label = f" via {effective_method}" if effective_method else ""
76
+ print(f"{tool_name}: running {status}{method_label}")
77
+ print(f" $ {command}")
78
+
79
+ if dry_run:
80
+ return 0
81
+
82
+ code, output = _run_shell(command)
83
+ if output:
84
+ print(output)
85
+ if code != 0:
86
+ print(f"{tool_name}: command failed with exit code {code}", file=sys.stderr)
87
+ return code
88
+
89
+ installed_after = spec.detect_installed(tool_cfg.get("binary", ""))
90
+ version_after = spec.get_version(tool_cfg.get("binary", "")) if installed_after else None
91
+
92
+ if installed_after:
93
+ before = version_before or "unknown"
94
+ after = version_after or "unknown"
95
+ print(f"{tool_name}: done ({before} -> {after})")
96
+ if regen_completions:
97
+ _regenerate_completions()
98
+ return 0
99
+
100
+ print(f"{tool_name}: command succeeded but binary still not found", file=sys.stderr)
101
+ return 1
102
+
103
+
104
+ def update_many(tool_names: list[str], dry_run: bool = False, method: Optional[str] = None) -> int:
105
+ """Update multiple tools and return a combined exit status."""
106
+ if not tool_names:
107
+ print("No tools selected for update.", file=sys.stderr)
108
+ return 1
109
+
110
+ failed: list[str] = []
111
+ succeeded = 0
112
+ for name in tool_names:
113
+ print()
114
+ rc = update_tool(name, dry_run=dry_run, method=method, regen_completions=False)
115
+ if rc != 0:
116
+ failed.append(name)
117
+ else:
118
+ succeeded += 1
119
+
120
+ if succeeded and not dry_run:
121
+ _regenerate_completions()
122
+
123
+ if failed:
124
+ print(f"\nFailed: {', '.join(failed)}", file=sys.stderr)
125
+ return 1
126
+ return 0
127
+
128
+
129
+ def main(argv: Optional[list[str]] = None) -> int:
130
+ """CLI entrypoint for `ai-cli update`."""
131
+ parser = argparse.ArgumentParser(
132
+ prog="ai-cli update",
133
+ description="Install or update wrapped CLI tools.",
134
+ )
135
+ parser.add_argument(
136
+ "tool",
137
+ nargs="?",
138
+ choices=available_targets(),
139
+ help="Tool to update.",
140
+ )
141
+ parser.add_argument(
142
+ "--all",
143
+ action="store_true",
144
+ help="Update all tools.",
145
+ )
146
+ parser.add_argument(
147
+ "--dry-run",
148
+ action="store_true",
149
+ help="Show commands without running them.",
150
+ )
151
+ parser.add_argument(
152
+ "--list",
153
+ action="store_true",
154
+ help="Show configured update commands.",
155
+ )
156
+ parser.add_argument(
157
+ "--method", "-m",
158
+ help="Install method to use (e.g. npm, brew, macports, curl). "
159
+ "Use --list-methods to see available methods per tool.",
160
+ )
161
+ parser.add_argument(
162
+ "--list-methods",
163
+ action="store_true",
164
+ help="Show available install methods per tool.",
165
+ )
166
+ args = parser.parse_args(argv)
167
+
168
+ registry = load_registry()
169
+
170
+ if args.list_methods:
171
+ for name, spec in registry.items():
172
+ best = spec.detect_best_method()
173
+ default = spec.install_command or "(none)"
174
+ print(f"{name}:")
175
+ print(f" default: {default}")
176
+ for method, cmd in spec.install_methods.items():
177
+ marker = " <- auto-detected" if method == best else ""
178
+ print(f" {method}: {cmd}{marker}")
179
+ print()
180
+ return 0
181
+
182
+ if args.list:
183
+ for name, spec in registry.items():
184
+ cmd = spec.install_command or "(none)"
185
+ print(f"{name:<8} {cmd}")
186
+ return 0
187
+
188
+ if args.all:
189
+ return update_many(list(registry.keys()), dry_run=args.dry_run, method=args.method)
190
+
191
+ target = args.tool
192
+ if not target:
193
+ print("Specify a tool or use --all.", file=sys.stderr)
194
+ return 1
195
+
196
+ return update_tool(target, dry_run=args.dry_run, method=args.method)
197
+
198
+
199
+ if __name__ == "__main__":
200
+ raise SystemExit(main())
@@ -0,0 +1,17 @@
1
+ Metadata-Version: 2.4
2
+ Name: ai-cli-toolkit
3
+ Version: 0.2.0
4
+ Summary: Unified AI CLI wrapper for Claude, Codex, Copilot, and Gemini
5
+ Author-email: example-git <admin@xo.vg>
6
+ Requires-Python: >=3.12
7
+ License-File: LICENSE
8
+ Requires-Dist: mitmproxy>=12.1.2
9
+ Requires-Dist: shtab>=1.7
10
+ Requires-Dist: bcrypt>=4.1
11
+ Provides-Extra: dev
12
+ Requires-Dist: mypy>=1.11; extra == "dev"
13
+ Requires-Dist: pre-commit>=3.8; extra == "dev"
14
+ Requires-Dist: pytest>=8.3; extra == "dev"
15
+ Requires-Dist: pytest-cov>=5.0; extra == "dev"
16
+ Requires-Dist: ruff>=0.6.9; extra == "dev"
17
+ Dynamic: license-file
@@ -0,0 +1,30 @@
1
+ ai_cli/__init__.py,sha256=ykhVL4ma8vVE6Hup8HCC-OyZP42kEARImKWBas80qnE,100
2
+ ai_cli/__main__.py,sha256=SDqipGzOAkPYh-3mARY3EVWLI8115tPkmfUJoVDmZNU,127
3
+ ai_cli/ca.py,sha256=IW9Lb8aRfRilHWAHCreW36WVw7bYhjwmsIETiQqt_pM,5376
4
+ ai_cli/completion_gen.py,sha256=y365D8bz1za13JkuEYIXRdGRw29nDJXBAbOop09Lc2k,19558
5
+ ai_cli/config.py,sha256=BZ_KLYoBccnsmJDBlWPcdX2PLvpB_p5NWubMpgRjm1Y,5670
6
+ ai_cli/credentials.py,sha256=dgCkO8w6v_7NO0GRA-JRXlJ1EKE6aFdJLkb4QnLvWxA,10751
7
+ ai_cli/detached_cleanup.py,sha256=a2gGjIqNttXBqhVwMy6HU8SYpwZT3nMddABsb5pObQE,3751
8
+ ai_cli/housekeeping.py,sha256=6XzejdCJVNLFm4mS4hG9U4TP96gUBv01hhLKiwxqiV4,1752
9
+ ai_cli/instructions.py,sha256=oK8FG6QirtvKoKYGYDcebeqzzUx7LfkpfDg_WsfU2QU,10396
10
+ ai_cli/log.py,sha256=3QD6Hjd_RNhFt8BNUSHWGNortncAXvsiWDFAoC5lnok,1654
11
+ ai_cli/main.py,sha256=l5NLGmRZq-DWmVTFiIpCkHmhTjw13aETRu4iy-z7qA8,58708
12
+ ai_cli/main_helpers.py,sha256=5LncZWe_yNRnpL1a3c2bUu3zGaZi_NALdNh3t43y0dQ,18634
13
+ ai_cli/prompt_editor_launcher.py,sha256=i7nJpB-2-Bd7rN-fjCjWuGANbVuyp2wUSMdS1b6sLAk,10393
14
+ ai_cli/proxy.py,sha256=SzwMXYJmBmsR2sY2SCVTAirmFncj_eobPruYhodSA0o,21397
15
+ ai_cli/remote.py,sha256=5qBsZBGcwX8gKCBbuY8YlksrMiZ3UAtQRUCWErIWNM8,26860
16
+ ai_cli/remote_package.py,sha256=28_lrExkMwaCC5yYkSAq8h88Ig88piYQsrKiZ1VdlZ8,41412
17
+ ai_cli/session.py,sha256=yCGQJtSDFK9BbLvsTnC8NM7OMOXoQqEX-WP1jn0CE0o,46533
18
+ ai_cli/session_store.py,sha256=AhUUvIUsQPaIemAwneTBWbhmWOyFUDJvcBSAKxpikmk,7262
19
+ ai_cli/traffic.py,sha256=_LwK7bU_GfF0uklraMaIwCk8AuhHJmWrr2urdvKhn-4,51471
20
+ ai_cli/traffic_db.py,sha256=6Lk3TsaK6TZP99V8cOV9W3f4x-WNuN6F3o_kAdwXdj4,3643
21
+ ai_cli/tui.py,sha256=6Y7NFCHcl8-DGC19ayM1FpbbEO4-xnYBmk-fgXOtay8,17036
22
+ ai_cli/update.py,sha256=DFiTXe950JCIlr-bDJ0KID6ma7cZ4bO41FLVTiKoZCs,6285
23
+ ai_cli/bin/ai-mux-linux-x86_64,sha256=8k4xnWKvvqWUctcaxzv6qMECRJtkFTUhVnSnl0M4rvc,553416
24
+ ai_cli/bin/remote-tty-wrapper,sha256=F5pUlgxJrj-1MKrNHTnIPyb5xLpfsbs6b8KgTjAeA9g,4532
25
+ ai_cli_toolkit-0.2.0.dist-info/licenses/LICENSE,sha256=2dtru61yPdgtJsQHAI0JVBsXTcOy-hmvxMsHcQWAEeM,1068
26
+ ai_cli_toolkit-0.2.0.dist-info/METADATA,sha256=nj2y5DS63qQudd4psytJfKWrhdq1tFW67pHXWMhogjQ,565
27
+ ai_cli_toolkit-0.2.0.dist-info/WHEEL,sha256=YCfwYGOYMi5Jhw2fU4yNgwErybb2IX5PEwBKV4ZbdBo,91
28
+ ai_cli_toolkit-0.2.0.dist-info/entry_points.txt,sha256=XI2ZZPLLsG49qNeVt86Dfxrw8EJxI5yz1z9yKE22WUc,48
29
+ ai_cli_toolkit-0.2.0.dist-info/top_level.txt,sha256=szfcz2SFm71Iu5jtJQtR3rEDLlOIOIaUPzWFUtAHuhA,7
30
+ ai_cli_toolkit-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ ai-cli = ai_cli.main:main_cli
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 example-git
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.
@@ -0,0 +1 @@
1
+ ai_cli