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/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)