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/__init__.py +3 -0
- ai_cli/__main__.py +6 -0
- ai_cli/bin/ai-mux-linux-x86_64 +0 -0
- ai_cli/bin/remote-tty-wrapper +153 -0
- ai_cli/ca.py +175 -0
- ai_cli/completion_gen.py +680 -0
- ai_cli/config.py +185 -0
- ai_cli/credentials.py +341 -0
- ai_cli/detached_cleanup.py +135 -0
- ai_cli/housekeeping.py +50 -0
- ai_cli/instructions.py +308 -0
- ai_cli/log.py +53 -0
- ai_cli/main.py +1516 -0
- ai_cli/main_helpers.py +553 -0
- ai_cli/prompt_editor_launcher.py +324 -0
- ai_cli/proxy.py +627 -0
- ai_cli/remote.py +669 -0
- ai_cli/remote_package.py +1111 -0
- ai_cli/session.py +1344 -0
- ai_cli/session_store.py +236 -0
- ai_cli/traffic.py +1510 -0
- ai_cli/traffic_db.py +118 -0
- ai_cli/tui.py +525 -0
- ai_cli/update.py +200 -0
- ai_cli_toolkit-0.2.0.dist-info/METADATA +17 -0
- ai_cli_toolkit-0.2.0.dist-info/RECORD +30 -0
- ai_cli_toolkit-0.2.0.dist-info/WHEEL +5 -0
- ai_cli_toolkit-0.2.0.dist-info/entry_points.txt +2 -0
- ai_cli_toolkit-0.2.0.dist-info/licenses/LICENSE +21 -0
- ai_cli_toolkit-0.2.0.dist-info/top_level.txt +1 -0
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,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
|