code-aide 1.0.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.
- code_aide/__init__.py +3 -0
- code_aide/__main__.py +6 -0
- code_aide/commands_actions.py +344 -0
- code_aide/commands_tools.py +183 -0
- code_aide/config.py +105 -0
- code_aide/console.py +58 -0
- code_aide/constants.py +63 -0
- code_aide/data/tools.json +100 -0
- code_aide/detection.py +184 -0
- code_aide/entry.py +112 -0
- code_aide/install.py +305 -0
- code_aide/operations.py +264 -0
- code_aide/prereqs.py +179 -0
- code_aide/status.py +86 -0
- code_aide/versions.py +356 -0
- code_aide-1.0.0.dist-info/METADATA +133 -0
- code_aide-1.0.0.dist-info/RECORD +20 -0
- code_aide-1.0.0.dist-info/WHEEL +4 -0
- code_aide-1.0.0.dist-info/entry_points.txt +2 -0
- code_aide-1.0.0.dist-info/licenses/LICENSE +191 -0
code_aide/constants.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Shared constants and mutable tool configuration for CLI modules."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict
|
|
4
|
+
|
|
5
|
+
from code_aide.config import load_tools_config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Colors:
|
|
9
|
+
RED = "\033[0;31m"
|
|
10
|
+
GREEN = "\033[0;32m"
|
|
11
|
+
YELLOW = "\033[1;33m"
|
|
12
|
+
BLUE = "\033[0;34m"
|
|
13
|
+
BOLD = "\033[1m"
|
|
14
|
+
NC = "\033[0m"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
PACKAGE_MANAGERS: Dict[str, Dict[str, Any]] = {
|
|
18
|
+
"apt-get": {
|
|
19
|
+
"detect_command": "apt-get",
|
|
20
|
+
"packages": ["nodejs", "npm"],
|
|
21
|
+
"pre_install": [["sudo", "apt-get", "update"]],
|
|
22
|
+
"install_command": ["sudo", "apt-get", "install", "-y"],
|
|
23
|
+
"description": "Debian/Ubuntu",
|
|
24
|
+
},
|
|
25
|
+
"dnf": {
|
|
26
|
+
"detect_command": "dnf",
|
|
27
|
+
"packages": ["nodejs", "npm"],
|
|
28
|
+
"pre_install": [],
|
|
29
|
+
"install_command": ["sudo", "dnf", "install", "-y"],
|
|
30
|
+
"description": "Fedora/RHEL 8+",
|
|
31
|
+
},
|
|
32
|
+
"yum": {
|
|
33
|
+
"detect_command": "yum",
|
|
34
|
+
"packages": ["nodejs", "npm"],
|
|
35
|
+
"pre_install": [],
|
|
36
|
+
"install_command": ["sudo", "yum", "install", "-y"],
|
|
37
|
+
"description": "RHEL/CentOS 7",
|
|
38
|
+
},
|
|
39
|
+
"pacman": {
|
|
40
|
+
"detect_command": "pacman",
|
|
41
|
+
"packages": ["nodejs", "npm"],
|
|
42
|
+
"pre_install": [],
|
|
43
|
+
"install_command": ["sudo", "pacman", "-S", "--noconfirm"],
|
|
44
|
+
"description": "Arch Linux",
|
|
45
|
+
},
|
|
46
|
+
"zypper": {
|
|
47
|
+
"detect_command": "zypper",
|
|
48
|
+
"packages": ["nodejs", "npm"],
|
|
49
|
+
"pre_install": [],
|
|
50
|
+
"install_command": ["sudo", "zypper", "install", "-y"],
|
|
51
|
+
"description": "openSUSE",
|
|
52
|
+
},
|
|
53
|
+
"emerge": {
|
|
54
|
+
"detect_command": "emerge",
|
|
55
|
+
"packages": ["net-libs/nodejs"],
|
|
56
|
+
"pre_install": [],
|
|
57
|
+
"install_command": ["sudo", "emerge", "--quiet-build"],
|
|
58
|
+
"description": "Gentoo",
|
|
59
|
+
},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
TOOLS: Dict[str, Dict[str, Any]] = load_tools_config()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": 1,
|
|
3
|
+
"tools": {
|
|
4
|
+
"cursor": {
|
|
5
|
+
"name": "Cursor CLI",
|
|
6
|
+
"command": "agent",
|
|
7
|
+
"install_type": "direct_download",
|
|
8
|
+
"install_url": "https://cursor.com/install",
|
|
9
|
+
"install_sha256": "7ccf2992a4de12c040854a833db0255a08cdfc91d40c75b78cf66a8746d9bd24",
|
|
10
|
+
"download_url_template": "https://downloads.cursor.com/lab/{version}/{os}/{arch}/agent-cli-package.tar.gz",
|
|
11
|
+
"install_dir": "~/.local/share/cursor-agent/versions/{version}",
|
|
12
|
+
"bin_dir": "~/.local/bin",
|
|
13
|
+
"symlinks": {
|
|
14
|
+
"agent": "cursor-agent",
|
|
15
|
+
"cursor-agent": "cursor-agent"
|
|
16
|
+
},
|
|
17
|
+
"prerequisites": [],
|
|
18
|
+
"min_node_version": null,
|
|
19
|
+
"next_steps": "Run 'agent login' to authenticate",
|
|
20
|
+
"version_args": [
|
|
21
|
+
"--version"
|
|
22
|
+
],
|
|
23
|
+
"docs_url": "https://docs.cursor.com/",
|
|
24
|
+
"default_install": true,
|
|
25
|
+
"latest_version": "2026.02.27-e7d2ef6",
|
|
26
|
+
"latest_date": "2026-02-27"
|
|
27
|
+
},
|
|
28
|
+
"claude": {
|
|
29
|
+
"name": "Claude CLI (Claude Code)",
|
|
30
|
+
"command": "claude",
|
|
31
|
+
"install_type": "self_managed",
|
|
32
|
+
"npm_package": "@anthropic-ai/claude-code",
|
|
33
|
+
"upgrade_command": ["claude", "upgrade"],
|
|
34
|
+
"prerequisites": ["npm"],
|
|
35
|
+
"min_node_version": null,
|
|
36
|
+
"next_steps": "Run 'claude' and then use '/login' to authenticate",
|
|
37
|
+
"version_args": [
|
|
38
|
+
"--version"
|
|
39
|
+
],
|
|
40
|
+
"docs_url": "https://docs.anthropic.com/en/docs/build-with-claude/claude-code",
|
|
41
|
+
"default_install": true,
|
|
42
|
+
"latest_version": "2.1.62",
|
|
43
|
+
"latest_date": "2026-02-27"
|
|
44
|
+
},
|
|
45
|
+
"gemini": {
|
|
46
|
+
"name": "Gemini CLI",
|
|
47
|
+
"command": "gemini",
|
|
48
|
+
"install_type": "npm",
|
|
49
|
+
"npm_package": "@google/gemini-cli",
|
|
50
|
+
"prerequisites": [
|
|
51
|
+
"npm"
|
|
52
|
+
],
|
|
53
|
+
"min_node_version": 20,
|
|
54
|
+
"next_steps": "Run 'gemini' and choose 'Login with Google' to authenticate",
|
|
55
|
+
"version_args": [
|
|
56
|
+
"--version"
|
|
57
|
+
],
|
|
58
|
+
"docs_url": "https://github.com/google-gemini/gemini-cli",
|
|
59
|
+
"default_install": true,
|
|
60
|
+
"latest_version": "0.30.1",
|
|
61
|
+
"latest_date": "2026-02-27"
|
|
62
|
+
},
|
|
63
|
+
"amp": {
|
|
64
|
+
"name": "Amp (Sourcegraph)",
|
|
65
|
+
"command": "amp",
|
|
66
|
+
"install_type": "script",
|
|
67
|
+
"install_url": "https://ampcode.com/install.sh",
|
|
68
|
+
"install_sha256": "852b48bf67bfe0494d1e3536eacbf8f4607617c111921c804b4199a5888a44a4",
|
|
69
|
+
"version_url": "https://storage.googleapis.com/amp-public-assets-prod-0/cli/cli-version.txt",
|
|
70
|
+
"prerequisites": [],
|
|
71
|
+
"min_node_version": null,
|
|
72
|
+
"next_steps": "Run 'amp login' to authenticate",
|
|
73
|
+
"version_args": [
|
|
74
|
+
"--version"
|
|
75
|
+
],
|
|
76
|
+
"docs_url": "https://ampcode.com/manual",
|
|
77
|
+
"default_install": false,
|
|
78
|
+
"latest_version": "0.0.1772222620-g1de875",
|
|
79
|
+
"latest_date": "2026-02-27"
|
|
80
|
+
},
|
|
81
|
+
"codex": {
|
|
82
|
+
"name": "Codex CLI",
|
|
83
|
+
"command": "codex",
|
|
84
|
+
"install_type": "npm",
|
|
85
|
+
"npm_package": "@openai/codex",
|
|
86
|
+
"prerequisites": [
|
|
87
|
+
"npm"
|
|
88
|
+
],
|
|
89
|
+
"min_node_version": 22,
|
|
90
|
+
"next_steps": "Run 'codex' to start",
|
|
91
|
+
"version_args": [
|
|
92
|
+
"--version"
|
|
93
|
+
],
|
|
94
|
+
"docs_url": "https://github.com/openai/codex",
|
|
95
|
+
"default_install": false,
|
|
96
|
+
"latest_version": "0.106.0",
|
|
97
|
+
"latest_date": "2026-02-26"
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
code_aide/detection.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Install-method and system package detection helpers."""
|
|
2
|
+
|
|
3
|
+
import glob as globmod
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
from datetime import datetime, timezone
|
|
9
|
+
from typing import Dict, Optional
|
|
10
|
+
|
|
11
|
+
from code_aide.constants import TOOLS
|
|
12
|
+
from code_aide.console import command_exists
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def detect_install_method(tool_name: str) -> Dict[str, Optional[str]]:
|
|
16
|
+
"""Detect how a tool was actually installed."""
|
|
17
|
+
tool_config = TOOLS.get(tool_name)
|
|
18
|
+
if not tool_config:
|
|
19
|
+
return {"method": None, "detail": None}
|
|
20
|
+
|
|
21
|
+
command_path = shutil.which(tool_config["command"])
|
|
22
|
+
if not command_path:
|
|
23
|
+
return {"method": None, "detail": None}
|
|
24
|
+
|
|
25
|
+
real_path = os.path.realpath(command_path)
|
|
26
|
+
|
|
27
|
+
cellar_match = re.search(r"/Cellar/([^/]+)/", real_path)
|
|
28
|
+
if cellar_match:
|
|
29
|
+
return {"method": "brew_formula", "detail": cellar_match.group(1)}
|
|
30
|
+
|
|
31
|
+
caskroom_match = re.search(r"/Caskroom/([^/]+)/", real_path)
|
|
32
|
+
if caskroom_match:
|
|
33
|
+
return {"method": "brew_cask", "detail": caskroom_match.group(1)}
|
|
34
|
+
|
|
35
|
+
if "/node_modules/" in real_path:
|
|
36
|
+
npm_package = tool_config.get("npm_package")
|
|
37
|
+
if not npm_package:
|
|
38
|
+
match = re.search(r"/node_modules/((?:@[^/]+/)?[^/]+)", real_path)
|
|
39
|
+
if match:
|
|
40
|
+
npm_package = match.group(1)
|
|
41
|
+
|
|
42
|
+
if command_path.startswith("/opt/homebrew/bin/") or command_path.startswith(
|
|
43
|
+
"/usr/local/bin/"
|
|
44
|
+
):
|
|
45
|
+
return {"method": "brew_npm", "detail": npm_package}
|
|
46
|
+
|
|
47
|
+
return {"method": "npm", "detail": npm_package}
|
|
48
|
+
|
|
49
|
+
system_prefixes = ("/opt/", "/usr/bin/", "/usr/sbin/", "/usr/local/bin/")
|
|
50
|
+
if any(real_path.startswith(prefix) for prefix in system_prefixes):
|
|
51
|
+
return {"method": "system", "detail": real_path}
|
|
52
|
+
|
|
53
|
+
return {"method": tool_config["install_type"], "detail": None}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def get_system_package_info(binary_path: str) -> Dict[str, Optional[str]]:
|
|
57
|
+
"""Get package version info for a system-installed binary."""
|
|
58
|
+
result: Dict[str, Optional[str]] = {
|
|
59
|
+
"package": None,
|
|
60
|
+
"installed_version": None,
|
|
61
|
+
"available_version": None,
|
|
62
|
+
"available_date": None,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if not command_exists("qfile"):
|
|
66
|
+
return result
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
proc = subprocess.run(
|
|
70
|
+
["qfile", "-qC", binary_path],
|
|
71
|
+
capture_output=True,
|
|
72
|
+
text=True,
|
|
73
|
+
timeout=5,
|
|
74
|
+
check=False,
|
|
75
|
+
stdin=subprocess.DEVNULL,
|
|
76
|
+
)
|
|
77
|
+
if proc.returncode != 0 or not proc.stdout.strip():
|
|
78
|
+
return result
|
|
79
|
+
package = proc.stdout.strip().split("\n")[0]
|
|
80
|
+
result["package"] = package
|
|
81
|
+
except Exception:
|
|
82
|
+
return result
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
proc = subprocess.run(
|
|
86
|
+
["qlist", "-Iv", package],
|
|
87
|
+
capture_output=True,
|
|
88
|
+
text=True,
|
|
89
|
+
timeout=5,
|
|
90
|
+
check=False,
|
|
91
|
+
stdin=subprocess.DEVNULL,
|
|
92
|
+
)
|
|
93
|
+
if proc.returncode == 0 and proc.stdout.strip():
|
|
94
|
+
installed_cpv = proc.stdout.strip().split("\n")[0]
|
|
95
|
+
proc2 = subprocess.run(
|
|
96
|
+
["qatom", "-F", "%{PV}", installed_cpv],
|
|
97
|
+
capture_output=True,
|
|
98
|
+
text=True,
|
|
99
|
+
timeout=5,
|
|
100
|
+
check=False,
|
|
101
|
+
stdin=subprocess.DEVNULL,
|
|
102
|
+
)
|
|
103
|
+
if proc2.returncode == 0 and proc2.stdout.strip():
|
|
104
|
+
result["installed_version"] = proc2.stdout.strip()
|
|
105
|
+
except Exception:
|
|
106
|
+
pass
|
|
107
|
+
|
|
108
|
+
category, package_name = package.split("/", 1)
|
|
109
|
+
ebuild_dirs = globmod.glob(f"/var/db/repos/*/{category}/{package_name}/")
|
|
110
|
+
ebuilds = []
|
|
111
|
+
for ebuild_dir in ebuild_dirs:
|
|
112
|
+
for entry in os.listdir(ebuild_dir):
|
|
113
|
+
if entry.endswith(".ebuild") and entry.startswith(f"{package_name}-"):
|
|
114
|
+
version = entry[len(f"{package_name}-") : -len(".ebuild")]
|
|
115
|
+
ebuild_path = os.path.join(ebuild_dir, entry)
|
|
116
|
+
ebuilds.append((version, ebuild_path))
|
|
117
|
+
|
|
118
|
+
if ebuilds:
|
|
119
|
+
best_version = None
|
|
120
|
+
best_path = None
|
|
121
|
+
for version, path in ebuilds:
|
|
122
|
+
if best_version is None:
|
|
123
|
+
best_version = version
|
|
124
|
+
best_path = path
|
|
125
|
+
continue
|
|
126
|
+
try:
|
|
127
|
+
proc = subprocess.run(
|
|
128
|
+
[
|
|
129
|
+
"qatom",
|
|
130
|
+
"-c",
|
|
131
|
+
f"{package}-{version}",
|
|
132
|
+
f"{package}-{best_version}",
|
|
133
|
+
],
|
|
134
|
+
capture_output=True,
|
|
135
|
+
text=True,
|
|
136
|
+
timeout=5,
|
|
137
|
+
check=False,
|
|
138
|
+
stdin=subprocess.DEVNULL,
|
|
139
|
+
)
|
|
140
|
+
if proc.returncode == 0 and ">" in proc.stdout:
|
|
141
|
+
best_version = version
|
|
142
|
+
best_path = path
|
|
143
|
+
except Exception:
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
if best_version:
|
|
147
|
+
result["available_version"] = best_version
|
|
148
|
+
if best_path:
|
|
149
|
+
try:
|
|
150
|
+
mtime = os.path.getmtime(best_path)
|
|
151
|
+
result["available_date"] = datetime.fromtimestamp(
|
|
152
|
+
mtime, tz=timezone.utc
|
|
153
|
+
).strftime("%Y-%m-%d")
|
|
154
|
+
except Exception:
|
|
155
|
+
pass
|
|
156
|
+
|
|
157
|
+
return result
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def format_install_method(method: Optional[str], detail: Optional[str]) -> str:
|
|
161
|
+
"""Format detected local install method for display."""
|
|
162
|
+
if method == "brew_formula":
|
|
163
|
+
return f"Homebrew formula ({detail})" if detail else "Homebrew formula"
|
|
164
|
+
if method == "brew_cask":
|
|
165
|
+
return f"Homebrew cask ({detail})" if detail else "Homebrew cask"
|
|
166
|
+
if method == "npm":
|
|
167
|
+
return f"npm ({detail})" if detail else "npm"
|
|
168
|
+
if method == "brew_npm":
|
|
169
|
+
return (
|
|
170
|
+
f"Homebrew prefix npm-global ({detail})"
|
|
171
|
+
if detail
|
|
172
|
+
else "Homebrew prefix npm-global"
|
|
173
|
+
)
|
|
174
|
+
if method == "system":
|
|
175
|
+
return f"system package ({detail})" if detail else "system package"
|
|
176
|
+
if method == "script":
|
|
177
|
+
return "script"
|
|
178
|
+
if method == "direct_download":
|
|
179
|
+
return "direct download"
|
|
180
|
+
if method == "self_managed":
|
|
181
|
+
return "self-managed"
|
|
182
|
+
if method:
|
|
183
|
+
return method
|
|
184
|
+
return "unknown"
|
code_aide/entry.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""Argument parser and CLI entrypoint."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from code_aide import __version__
|
|
6
|
+
from code_aide.commands_actions import (
|
|
7
|
+
cmd_install,
|
|
8
|
+
cmd_remove,
|
|
9
|
+
cmd_update_versions,
|
|
10
|
+
cmd_upgrade,
|
|
11
|
+
)
|
|
12
|
+
from code_aide.commands_tools import cmd_list, cmd_status
|
|
13
|
+
from code_aide.constants import TOOLS
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def main() -> None:
|
|
17
|
+
"""Main function."""
|
|
18
|
+
available_tools = ", ".join(TOOLS.keys())
|
|
19
|
+
parser = argparse.ArgumentParser(
|
|
20
|
+
description="Manage AI coding CLI tools",
|
|
21
|
+
epilog=f"Available tools: {available_tools}",
|
|
22
|
+
)
|
|
23
|
+
parser.add_argument(
|
|
24
|
+
"--version", action="version", version=f"%(prog)s {__version__}"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
|
|
28
|
+
|
|
29
|
+
list_parser = subparsers.add_parser(
|
|
30
|
+
"list", help="List available tools and their status"
|
|
31
|
+
)
|
|
32
|
+
list_parser.set_defaults(func=cmd_list)
|
|
33
|
+
|
|
34
|
+
status_parser = subparsers.add_parser(
|
|
35
|
+
"status", help="Show detailed status for installed tools"
|
|
36
|
+
)
|
|
37
|
+
status_parser.set_defaults(func=cmd_status)
|
|
38
|
+
|
|
39
|
+
install_parser = subparsers.add_parser("install", help="Install tools")
|
|
40
|
+
install_parser.add_argument(
|
|
41
|
+
"tools",
|
|
42
|
+
nargs="*",
|
|
43
|
+
choices=list(TOOLS.keys()),
|
|
44
|
+
help="Tools to install (default: all)",
|
|
45
|
+
)
|
|
46
|
+
install_parser.add_argument(
|
|
47
|
+
"-p",
|
|
48
|
+
"--install-prerequisites",
|
|
49
|
+
action="store_true",
|
|
50
|
+
help="Automatically install prerequisites (Node.js, npm) "
|
|
51
|
+
"using system package manager",
|
|
52
|
+
)
|
|
53
|
+
install_parser.add_argument(
|
|
54
|
+
"-n",
|
|
55
|
+
"--dryrun",
|
|
56
|
+
action="store_true",
|
|
57
|
+
help="Verify SHA256 checksums without installing (dry run mode)",
|
|
58
|
+
)
|
|
59
|
+
install_parser.set_defaults(func=cmd_install)
|
|
60
|
+
|
|
61
|
+
upgrade_parser = subparsers.add_parser("upgrade", help="Upgrade tools")
|
|
62
|
+
upgrade_parser.add_argument(
|
|
63
|
+
"tools",
|
|
64
|
+
nargs="*",
|
|
65
|
+
choices=list(TOOLS.keys()),
|
|
66
|
+
help="Tools to upgrade (default: all)",
|
|
67
|
+
)
|
|
68
|
+
upgrade_parser.set_defaults(func=cmd_upgrade)
|
|
69
|
+
|
|
70
|
+
remove_parser = subparsers.add_parser("remove", help="Remove tools")
|
|
71
|
+
remove_parser.add_argument(
|
|
72
|
+
"tools",
|
|
73
|
+
nargs="*",
|
|
74
|
+
choices=list(TOOLS.keys()),
|
|
75
|
+
help="Tools to remove (default: all)",
|
|
76
|
+
)
|
|
77
|
+
remove_parser.set_defaults(func=cmd_remove)
|
|
78
|
+
|
|
79
|
+
update_versions_parser = subparsers.add_parser(
|
|
80
|
+
"update-versions",
|
|
81
|
+
help="Check upstream sources for latest tool versions",
|
|
82
|
+
)
|
|
83
|
+
update_versions_parser.add_argument(
|
|
84
|
+
"tools",
|
|
85
|
+
nargs="*",
|
|
86
|
+
help="Specific tools to check (default: all)",
|
|
87
|
+
)
|
|
88
|
+
update_versions_parser.add_argument(
|
|
89
|
+
"-n",
|
|
90
|
+
"--dry-run",
|
|
91
|
+
action="store_true",
|
|
92
|
+
help="Show changes only, do not write updates",
|
|
93
|
+
)
|
|
94
|
+
update_versions_parser.add_argument(
|
|
95
|
+
"-y",
|
|
96
|
+
"--yes",
|
|
97
|
+
action="store_true",
|
|
98
|
+
help="Auto-apply updates without prompting",
|
|
99
|
+
)
|
|
100
|
+
update_versions_parser.add_argument(
|
|
101
|
+
"-v",
|
|
102
|
+
"--verbose",
|
|
103
|
+
action="store_true",
|
|
104
|
+
help="Show full SHA256 hashes",
|
|
105
|
+
)
|
|
106
|
+
update_versions_parser.set_defaults(func=cmd_update_versions)
|
|
107
|
+
|
|
108
|
+
args = parser.parse_args()
|
|
109
|
+
if not args.command:
|
|
110
|
+
cmd_status(args)
|
|
111
|
+
else:
|
|
112
|
+
args.func(args)
|