code-aide 1.7.3__tar.gz → 1.10.0__tar.gz
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-1.7.3 → code_aide-1.10.0}/.gitignore +1 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/PKG-INFO +1 -1
- code_aide-1.10.0/specs/unify-upgrade-eligibility-with-shared-evaluator.md +87 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/__init__.py +1 -1
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/commands_actions.py +33 -34
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/commands_tools.py +101 -101
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/config.py +20 -3
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/data/tools.json +0 -1
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/detection.py +99 -34
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/install.py +18 -29
- code_aide-1.10.0/src/code_aide/install_types.py +90 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/operations.py +47 -24
- code_aide-1.10.0/src/code_aide/status.py +418 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/versions.py +36 -6
- {code_aide-1.7.3 → code_aide-1.10.0}/tests/test_commands_actions.py +77 -11
- {code_aide-1.7.3 → code_aide-1.10.0}/tests/test_commands_tools.py +120 -11
- {code_aide-1.7.3 → code_aide-1.10.0}/tests/test_config.py +41 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/tests/test_install.py +3 -36
- code_aide-1.10.0/tests/test_install_types.py +61 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/tests/test_operations.py +74 -12
- code_aide-1.10.0/tests/test_status.py +355 -0
- code_aide-1.7.3/src/code_aide/status.py +0 -116
- code_aide-1.7.3/tests/test_status.py +0 -76
- {code_aide-1.7.3 → code_aide-1.10.0}/.github/workflows/ci.yml +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/.github/workflows/publish.yml +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/.gitlab-ci.yml +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/.pre-commit-config.yaml +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/AGENTS.md +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/CLAUDE.md +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/LICENSE +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/README.md +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/TODO.md +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/pyproject.toml +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/specs/auto-migrate-deprecated-installs.md +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/specs/claude-native-installer-migration.md +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/specs/missing-coding-llm-cli-tools.md +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/specs/pre-commit-uv-setup.md +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/specs/remove-bundled-version-baseline.md +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/__main__.py +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/console.py +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/constants.py +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/entry.py +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/src/code_aide/prereqs.py +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/tests/test_console.py +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/tests/test_constants.py +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/tests/test_detection.py +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/tests/test_versions.py +0 -0
- {code_aide-1.7.3 → code_aide-1.10.0}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-aide
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.0
|
|
4
4
|
Summary: Manage AI coding CLI tools (Claude, Copilot, Cursor, Gemini, Amp, Codex)
|
|
5
5
|
Project-URL: Homepage, https://github.com/dajobe/code-aide
|
|
6
6
|
Project-URL: Repository, https://github.com/dajobe/code-aide
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Unify upgrade eligibility with a shared evaluator
|
|
2
|
+
|
|
3
|
+
## Summary
|
|
4
|
+
|
|
5
|
+
Introduce a single evaluator class that computes tool state once and exposes
|
|
6
|
+
normalized decisions for both `status` and default `upgrade`. Use typed
|
|
7
|
+
`Enum` values, not strings, for all decision outcomes and display states so
|
|
8
|
+
callers cannot drift by comparing ad hoc literals.
|
|
9
|
+
|
|
10
|
+
## Implementation changes
|
|
11
|
+
|
|
12
|
+
- Add a small evaluator in `src/code_aide/status.py` as the single source of
|
|
13
|
+
truth for:
|
|
14
|
+
- install presence
|
|
15
|
+
- detected install method
|
|
16
|
+
- package-manager metadata
|
|
17
|
+
- version relationship to catalog/package state
|
|
18
|
+
- normalized outcome for both status rendering and default `upgrade`
|
|
19
|
+
- Define typed enums for evaluator output:
|
|
20
|
+
- `UpgradeDecision`: `CURRENT`, `UPGRADE`, `MIGRATION`, `PACKAGE_MANAGED`,
|
|
21
|
+
`NOT_INSTALLED`, `UNKNOWN`
|
|
22
|
+
- `VersionDisplayState`: `UP_TO_DATE`, `OUTDATED`, `UNAVAILABLE`
|
|
23
|
+
- optional compact-state enum if needed, otherwise derive compact text
|
|
24
|
+
from `UpgradeDecision`
|
|
25
|
+
- Return a typed result object/dataclass from the evaluator containing:
|
|
26
|
+
- `decision: UpgradeDecision`
|
|
27
|
+
- `version_state: VersionDisplayState`
|
|
28
|
+
- `actionable_by_upgrade: bool`
|
|
29
|
+
- optional package/upstream fields needed by full status
|
|
30
|
+
- Keep all decision rules inside the evaluator:
|
|
31
|
+
- exact catalog match => `CURRENT`
|
|
32
|
+
- installed newer than catalog => `CURRENT`
|
|
33
|
+
- deprecated detected install (`npm`/`brew_npm` vs configured non-matching
|
|
34
|
+
method) => `MIGRATION`
|
|
35
|
+
- Homebrew outdated => `UPGRADE`
|
|
36
|
+
- Homebrew current => `CURRENT`
|
|
37
|
+
- system-managed install => `PACKAGE_MANAGED`, never auto-selected by
|
|
38
|
+
default `upgrade`
|
|
39
|
+
- missing latest-version metadata or missing CLI version => `UNKNOWN`, not
|
|
40
|
+
auto-selected
|
|
41
|
+
- Refactor `cmd_status` and compact status in
|
|
42
|
+
`src/code_aide/commands_tools.py` to consume evaluator results only.
|
|
43
|
+
- Refactor default-selection logic in `cmd_upgrade` in
|
|
44
|
+
`src/code_aide/commands_actions.py` to use the same evaluator result.
|
|
45
|
+
- Keep explicit `code-aide upgrade <tool>` behavior unchanged; the evaluator
|
|
46
|
+
governs automatic selection and status semantics.
|
|
47
|
+
|
|
48
|
+
## Public interfaces and types
|
|
49
|
+
|
|
50
|
+
- Add an internal evaluator class, for example `ToolUpgradeEvaluator`.
|
|
51
|
+
- Add internal enums and a typed result/dataclass; no caller should branch
|
|
52
|
+
on raw strings after this refactor.
|
|
53
|
+
- Do not change CLI flags, command names, or user-facing command flow.
|
|
54
|
+
|
|
55
|
+
## Test plan
|
|
56
|
+
|
|
57
|
+
- Add evaluator-focused unit tests covering enum outputs for:
|
|
58
|
+
- installed version equals catalog latest
|
|
59
|
+
- installed version newer than catalog latest
|
|
60
|
+
- installed version older than catalog latest
|
|
61
|
+
- Homebrew tool with `outdated=True`
|
|
62
|
+
- Homebrew tool with `outdated=False`
|
|
63
|
+
- system-managed tool with newer upstream metadata
|
|
64
|
+
- deprecated npm install requiring migration
|
|
65
|
+
- missing version metadata or unknown state
|
|
66
|
+
- Add command-level regressions proving shared behavior:
|
|
67
|
+
- full `status` says `up to date` for newer-than-catalog installs and
|
|
68
|
+
default `upgrade` does not auto-select them
|
|
69
|
+
- full/compact `status` and default `upgrade` both treat Homebrew current
|
|
70
|
+
installs as non-upgradeable
|
|
71
|
+
- system-managed installs are not auto-selected by default `upgrade`
|
|
72
|
+
- migration-required installs still appear as actionable for default
|
|
73
|
+
`upgrade`
|
|
74
|
+
- Run the targeted suite:
|
|
75
|
+
- `tests/test_status.py`
|
|
76
|
+
- `tests/test_commands_tools.py`
|
|
77
|
+
- `tests/test_commands_actions.py`
|
|
78
|
+
- any existing detection/operations tests touched by the refactor
|
|
79
|
+
|
|
80
|
+
## Assumptions
|
|
81
|
+
|
|
82
|
+
- The evaluator, enums, and result dataclass remain internal APIs.
|
|
83
|
+
- Full `status` still shows migration warnings separately from upgrade
|
|
84
|
+
footers, but both are derived from the same enum-backed evaluator result.
|
|
85
|
+
- `Upgradeable` in the footer means `selected by default code-aide upgrade`.
|
|
86
|
+
- Display text remains string-based at the CLI boundary only; decision logic
|
|
87
|
+
uses enums throughout.
|
|
@@ -5,12 +5,14 @@ import sys
|
|
|
5
5
|
from typing import Any, Dict, List
|
|
6
6
|
|
|
7
7
|
from code_aide.constants import TOOLS
|
|
8
|
-
from code_aide.detection import
|
|
9
|
-
detect_install_method,
|
|
10
|
-
get_brew_package_info,
|
|
11
|
-
is_deprecated_install,
|
|
12
|
-
)
|
|
8
|
+
from code_aide.detection import detect_install_method, get_brew_package_info
|
|
13
9
|
from code_aide.install import install_tool
|
|
10
|
+
from code_aide.install_types import (
|
|
11
|
+
InstallMethod,
|
|
12
|
+
InstallType,
|
|
13
|
+
parse_install_method,
|
|
14
|
+
parse_install_type,
|
|
15
|
+
)
|
|
14
16
|
from code_aide.console import error, info, success, warning
|
|
15
17
|
from code_aide.operations import (
|
|
16
18
|
UpgradeResult,
|
|
@@ -23,14 +25,13 @@ from code_aide.prereqs import (
|
|
|
23
25
|
check_prerequisites,
|
|
24
26
|
is_tool_installed,
|
|
25
27
|
)
|
|
26
|
-
from code_aide.status import
|
|
28
|
+
from code_aide.status import ToolUpgradeEvaluator
|
|
27
29
|
from code_aide.versions import (
|
|
28
30
|
apply_sha256_updates,
|
|
29
31
|
check_npm_tool,
|
|
30
32
|
check_script_tool,
|
|
31
33
|
normalize_version,
|
|
32
34
|
print_check_results_table,
|
|
33
|
-
status_version_matches_latest,
|
|
34
35
|
)
|
|
35
36
|
from code_aide.config import (
|
|
36
37
|
load_bundled_tools,
|
|
@@ -131,28 +132,9 @@ def cmd_upgrade(args: argparse.Namespace) -> None:
|
|
|
131
132
|
else:
|
|
132
133
|
tools_to_upgrade = []
|
|
133
134
|
for name, config in TOOLS.items():
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if name not in tools_to_upgrade:
|
|
138
|
-
tools_to_upgrade.append(name)
|
|
139
|
-
continue
|
|
140
|
-
latest = config.get("latest_version")
|
|
141
|
-
if not latest:
|
|
142
|
-
continue
|
|
143
|
-
install_info = detect_install_method(name)
|
|
144
|
-
if install_info["method"] in ("brew_formula", "brew_cask"):
|
|
145
|
-
pkg_info = get_brew_package_info(
|
|
146
|
-
install_info["method"], install_info["detail"]
|
|
147
|
-
)
|
|
148
|
-
if pkg_info.get("outdated") is False:
|
|
149
|
-
continue
|
|
150
|
-
status = get_tool_status(name, config)
|
|
151
|
-
if status["version"] and status_version_matches_latest(
|
|
152
|
-
status["version"], latest
|
|
153
|
-
):
|
|
154
|
-
continue
|
|
155
|
-
tools_to_upgrade.append(name)
|
|
135
|
+
assessment = ToolUpgradeEvaluator(name, config).evaluate()
|
|
136
|
+
if assessment.actionable_by_upgrade and name not in tools_to_upgrade:
|
|
137
|
+
tools_to_upgrade.append(name)
|
|
156
138
|
if tools_to_upgrade:
|
|
157
139
|
info(f"Upgrading out-of-date tools: {', '.join(tools_to_upgrade)}")
|
|
158
140
|
else:
|
|
@@ -282,20 +264,20 @@ def cmd_update_versions(args: argparse.Namespace) -> None:
|
|
|
282
264
|
results: List[Dict[str, Any]] = []
|
|
283
265
|
for name in tool_names:
|
|
284
266
|
tool_config = tools[name]
|
|
285
|
-
install_type = tool_config["install_type"]
|
|
267
|
+
install_type = parse_install_type(tool_config["install_type"])
|
|
286
268
|
|
|
287
|
-
if install_type ==
|
|
269
|
+
if install_type == InstallType.NPM:
|
|
288
270
|
results.append(check_npm_tool(name, tool_config, args.verbose))
|
|
289
|
-
elif install_type in (
|
|
271
|
+
elif install_type in (InstallType.SCRIPT, InstallType.DIRECT_DOWNLOAD):
|
|
290
272
|
results.append(check_script_tool(name, tool_config, args.verbose))
|
|
291
273
|
else:
|
|
292
274
|
results.append(
|
|
293
275
|
{
|
|
294
276
|
"tool": name,
|
|
295
|
-
"type": install_type,
|
|
277
|
+
"type": tool_config["install_type"],
|
|
296
278
|
"version": "-",
|
|
297
279
|
"date": "-",
|
|
298
|
-
"status": f"unknown type: {install_type}",
|
|
280
|
+
"status": f"unknown type: {tool_config['install_type']}",
|
|
299
281
|
"update": None,
|
|
300
282
|
}
|
|
301
283
|
)
|
|
@@ -320,6 +302,23 @@ def cmd_update_versions(args: argparse.Namespace) -> None:
|
|
|
320
302
|
tool_entry["latest_date"] = date
|
|
321
303
|
version_info_changed = True
|
|
322
304
|
|
|
305
|
+
# Refresh latest_version from Homebrew for tools installed via brew, so
|
|
306
|
+
# "upstream" in status reflects the correct source for those installs.
|
|
307
|
+
for tool_name in tool_names:
|
|
308
|
+
install_info = detect_install_method(tool_name)
|
|
309
|
+
method = parse_install_method(install_info["method"])
|
|
310
|
+
if method not in (InstallMethod.BREW_FORMULA, InstallMethod.BREW_CASK):
|
|
311
|
+
continue
|
|
312
|
+
pkg_info = get_brew_package_info(method, install_info["detail"])
|
|
313
|
+
avail = pkg_info.get("available_version")
|
|
314
|
+
if not avail:
|
|
315
|
+
continue
|
|
316
|
+
normalized = normalize_version(avail)
|
|
317
|
+
tool_entry = config["tools"][tool_name]
|
|
318
|
+
if tool_entry.get("latest_version") != normalized:
|
|
319
|
+
tool_entry["latest_version"] = normalized
|
|
320
|
+
version_info_changed = True
|
|
321
|
+
|
|
323
322
|
def _save(tools: dict) -> str:
|
|
324
323
|
"""Save versions to user cache. Returns description."""
|
|
325
324
|
save_updated_versions(tools)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Read-only CLI commands: list and status."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
3
5
|
import argparse
|
|
4
6
|
import platform
|
|
5
7
|
import shutil
|
|
@@ -9,21 +11,24 @@ from code_aide.constants import Colors, PACKAGE_MANAGERS, TOOLS
|
|
|
9
11
|
from code_aide.detection import (
|
|
10
12
|
format_install_method,
|
|
11
13
|
format_migration_warning,
|
|
12
|
-
get_brew_package_info,
|
|
13
|
-
get_system_package_info,
|
|
14
14
|
detect_install_method,
|
|
15
15
|
)
|
|
16
16
|
from code_aide.console import command_exists, info, warning
|
|
17
|
+
from code_aide.install_types import (
|
|
18
|
+
InstallMethod,
|
|
19
|
+
parse_install_method,
|
|
20
|
+
parse_install_type,
|
|
21
|
+
)
|
|
17
22
|
from code_aide.prereqs import detect_package_manager, is_tool_installed
|
|
18
23
|
from code_aide.status import (
|
|
19
|
-
get_tool_status,
|
|
20
24
|
print_brew_version_status,
|
|
21
25
|
print_system_version_status,
|
|
26
|
+
ToolUpgradeEvaluator,
|
|
27
|
+
UpgradeDecision,
|
|
28
|
+
VersionDisplayState,
|
|
22
29
|
)
|
|
23
30
|
from code_aide.versions import (
|
|
24
31
|
extract_version_from_string,
|
|
25
|
-
status_version_matches_latest,
|
|
26
|
-
version_is_newer,
|
|
27
32
|
)
|
|
28
33
|
|
|
29
34
|
|
|
@@ -44,7 +49,9 @@ def cmd_list(args: argparse.Namespace) -> None:
|
|
|
44
49
|
print(f"{Colors.BLUE}{tool_config['name']}{Colors.NC}")
|
|
45
50
|
print(f" Command: {tool_config['command']}")
|
|
46
51
|
print(f" Status: {status}")
|
|
47
|
-
|
|
52
|
+
install_type = parse_install_type(tool_config["install_type"])
|
|
53
|
+
managed_by = install_type.value if install_type else tool_config["install_type"]
|
|
54
|
+
print(f" Managed by: {managed_by} (code-aide)")
|
|
48
55
|
|
|
49
56
|
if installed:
|
|
50
57
|
tool_path = shutil.which(tool_config["command"])
|
|
@@ -104,31 +111,45 @@ def cmd_list(args: argparse.Namespace) -> None:
|
|
|
104
111
|
def _short_install_method(method: str | None) -> str:
|
|
105
112
|
"""Return a short label for an install method."""
|
|
106
113
|
labels = {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
InstallMethod.BREW_FORMULA: "brew",
|
|
115
|
+
InstallMethod.BREW_CASK: "cask",
|
|
116
|
+
InstallMethod.NPM: "npm",
|
|
117
|
+
InstallMethod.BREW_NPM: "brew-npm",
|
|
118
|
+
InstallMethod.SYSTEM: "system",
|
|
119
|
+
InstallMethod.SCRIPT: "script",
|
|
120
|
+
InstallMethod.DIRECT_DOWNLOAD: "download",
|
|
114
121
|
}
|
|
115
|
-
|
|
122
|
+
install_method = parse_install_method(method)
|
|
123
|
+
if install_method is None:
|
|
124
|
+
return str(method or "unknown")
|
|
125
|
+
return labels.get(install_method, str(install_method))
|
|
116
126
|
|
|
117
127
|
|
|
118
128
|
def _compact_version_status(
|
|
119
|
-
|
|
129
|
+
installed_version: str | None, version_state: VersionDisplayState
|
|
120
130
|
) -> tuple[str, str]:
|
|
121
131
|
"""Return (version_str, status_indicator) for compact display."""
|
|
122
|
-
if not
|
|
132
|
+
if not installed_version:
|
|
123
133
|
return ("", "")
|
|
124
|
-
ver = extract_version_from_string(
|
|
125
|
-
if
|
|
126
|
-
return (ver, "")
|
|
127
|
-
if status_version_matches_latest(version, latest_version):
|
|
134
|
+
ver = extract_version_from_string(installed_version) or installed_version
|
|
135
|
+
if version_state == VersionDisplayState.UP_TO_DATE:
|
|
128
136
|
return (ver, f"{Colors.GREEN}ok{Colors.NC}")
|
|
129
|
-
if
|
|
130
|
-
return (ver, f"{Colors.YELLOW}
|
|
131
|
-
return (ver,
|
|
137
|
+
if version_state == VersionDisplayState.OUTDATED:
|
|
138
|
+
return (ver, f"{Colors.YELLOW}old{Colors.NC}")
|
|
139
|
+
return (ver, "")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _generic_version_annotation(
|
|
143
|
+
installed_version: str, latest_version: str | None
|
|
144
|
+
) -> str:
|
|
145
|
+
"""Return the generic version line for non-package-managed status."""
|
|
146
|
+
if not latest_version:
|
|
147
|
+
return f" Version: {installed_version}"
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
f" Version: {installed_version} "
|
|
151
|
+
f"{Colors.YELLOW}(latest: {latest_version}){Colors.NC}"
|
|
152
|
+
)
|
|
132
153
|
|
|
133
154
|
|
|
134
155
|
def cmd_status_compact() -> None:
|
|
@@ -136,7 +157,8 @@ def cmd_status_compact() -> None:
|
|
|
136
157
|
rows: list[tuple[str, str, str, str, str]] = []
|
|
137
158
|
for tool_name, tool_config in TOOLS.items():
|
|
138
159
|
name = tool_config["command"]
|
|
139
|
-
|
|
160
|
+
assessment = ToolUpgradeEvaluator(tool_name, tool_config).evaluate()
|
|
161
|
+
status = assessment.status
|
|
140
162
|
|
|
141
163
|
if not status["installed"]:
|
|
142
164
|
opt_in = not tool_config.get("default_install", True)
|
|
@@ -148,10 +170,10 @@ def cmd_status_compact() -> None:
|
|
|
148
170
|
continue
|
|
149
171
|
|
|
150
172
|
tool_path = shutil.which(tool_config["command"]) or ""
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
173
|
+
method = _short_install_method(assessment.install_method)
|
|
174
|
+
ver, ver_status = _compact_version_status(
|
|
175
|
+
assessment.installed_version, assessment.version_state
|
|
176
|
+
)
|
|
155
177
|
|
|
156
178
|
state = f"{Colors.GREEN}ok{Colors.NC}"
|
|
157
179
|
if ver_status:
|
|
@@ -193,106 +215,90 @@ def cmd_status(args: argparse.Namespace) -> None:
|
|
|
193
215
|
print()
|
|
194
216
|
|
|
195
217
|
outdated_count = 0
|
|
218
|
+
outdated_tools: List[str] = []
|
|
196
219
|
migration_count = 0
|
|
197
|
-
config_outdated: List[str] = []
|
|
198
220
|
|
|
199
221
|
for tool_name, tool_config in TOOLS.items():
|
|
200
222
|
print(f"{Colors.BLUE}{tool_config['name']}{Colors.NC}")
|
|
201
223
|
|
|
202
|
-
|
|
224
|
+
tool_path = shutil.which(tool_config["command"])
|
|
225
|
+
assessment = ToolUpgradeEvaluator(
|
|
226
|
+
tool_name, tool_config, tool_path=tool_path
|
|
227
|
+
).evaluate()
|
|
228
|
+
status = assessment.status
|
|
203
229
|
|
|
204
230
|
if not status["installed"]:
|
|
205
231
|
print(f" Status: {Colors.RED}✗ Not installed{Colors.NC}")
|
|
206
232
|
else:
|
|
207
233
|
print(f" Status: {Colors.GREEN}✓ Installed{Colors.NC}")
|
|
208
234
|
|
|
209
|
-
tool_path = shutil.which(tool_config["command"])
|
|
210
|
-
install_info = detect_install_method(tool_name)
|
|
211
|
-
is_system = install_info["method"] == "system"
|
|
212
|
-
|
|
213
235
|
if status["version"]:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
236
|
+
if (
|
|
237
|
+
assessment.install_method == InstallMethod.SYSTEM
|
|
238
|
+
and tool_path
|
|
239
|
+
and assessment.package_info
|
|
240
|
+
):
|
|
218
241
|
print_system_version_status(
|
|
219
|
-
status["version"],
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
pkg_info = get_brew_package_info(
|
|
223
|
-
install_info["method"], install_info["detail"]
|
|
242
|
+
status["version"],
|
|
243
|
+
assessment.latest_version,
|
|
244
|
+
assessment.package_info,
|
|
224
245
|
)
|
|
225
|
-
|
|
246
|
+
elif assessment.install_method in (
|
|
247
|
+
InstallMethod.BREW_FORMULA,
|
|
248
|
+
InstallMethod.BREW_CASK,
|
|
249
|
+
):
|
|
250
|
+
if assessment.package_info and assessment.package_info.get(
|
|
251
|
+
"available_version"
|
|
252
|
+
):
|
|
226
253
|
print_brew_version_status(
|
|
227
|
-
status["version"],
|
|
254
|
+
status["version"],
|
|
255
|
+
assessment.latest_version,
|
|
256
|
+
assessment.package_info,
|
|
228
257
|
)
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if status_version_matches_latest(
|
|
233
|
-
status["version"], latest_version
|
|
234
|
-
):
|
|
235
|
-
version_annotation = (
|
|
258
|
+
elif assessment.latest_version:
|
|
259
|
+
if assessment.version_state == VersionDisplayState.UP_TO_DATE:
|
|
260
|
+
print(
|
|
236
261
|
f" Version: {status['version']} "
|
|
237
262
|
f"{Colors.GREEN}(up to date){Colors.NC}"
|
|
238
263
|
)
|
|
239
264
|
else:
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
if installed_ver and version_is_newer(
|
|
244
|
-
installed_ver, latest_version
|
|
245
|
-
):
|
|
246
|
-
version_annotation = (
|
|
247
|
-
f" Version: {status['version']} "
|
|
248
|
-
f"{Colors.YELLOW}(newer than configured "
|
|
249
|
-
f"{latest_version}){Colors.NC}"
|
|
250
|
-
)
|
|
251
|
-
config_outdated.append(tool_name)
|
|
252
|
-
else:
|
|
253
|
-
version_annotation = (
|
|
254
|
-
f" Version: {status['version']} "
|
|
255
|
-
f"{Colors.YELLOW}(latest: {latest_version}){Colors.NC}"
|
|
265
|
+
print(
|
|
266
|
+
_generic_version_annotation(
|
|
267
|
+
status["version"], assessment.latest_version
|
|
256
268
|
)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
269
|
+
)
|
|
270
|
+
else:
|
|
271
|
+
print(f" Version: {status['version']}")
|
|
272
|
+
elif assessment.latest_version:
|
|
273
|
+
if assessment.version_state == VersionDisplayState.UP_TO_DATE:
|
|
274
|
+
print(
|
|
262
275
|
f" Version: {status['version']} "
|
|
263
276
|
f"{Colors.GREEN}(up to date){Colors.NC}"
|
|
264
277
|
)
|
|
265
278
|
else:
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
):
|
|
270
|
-
version_annotation = (
|
|
271
|
-
f" Version: {status['version']} "
|
|
272
|
-
f"{Colors.YELLOW}(newer than configured "
|
|
273
|
-
f"{latest_version}){Colors.NC}"
|
|
274
|
-
)
|
|
275
|
-
config_outdated.append(tool_name)
|
|
276
|
-
else:
|
|
277
|
-
version_annotation = (
|
|
278
|
-
f" Version: {status['version']} "
|
|
279
|
-
f"{Colors.YELLOW}(latest: {latest_version}){Colors.NC}"
|
|
279
|
+
print(
|
|
280
|
+
_generic_version_annotation(
|
|
281
|
+
status["version"], assessment.latest_version
|
|
280
282
|
)
|
|
281
|
-
|
|
282
|
-
print(version_annotation)
|
|
283
|
+
)
|
|
283
284
|
else:
|
|
284
285
|
print(f" Version: {status['version']}")
|
|
285
286
|
|
|
287
|
+
if assessment.decision == UpgradeDecision.UPGRADE:
|
|
288
|
+
outdated_count += 1
|
|
289
|
+
outdated_tools.append(tool_name)
|
|
290
|
+
|
|
286
291
|
if tool_path:
|
|
287
292
|
print(f" Location: {tool_path}")
|
|
288
293
|
print(
|
|
289
294
|
" Installed via: "
|
|
290
|
-
f"{format_install_method(
|
|
295
|
+
f"{format_install_method(assessment.install_method, assessment.install_detail)}"
|
|
291
296
|
)
|
|
292
|
-
|
|
293
|
-
if migration_msg:
|
|
294
|
-
warning(f" {migration_msg}")
|
|
297
|
+
if assessment.decision == UpgradeDecision.MIGRATION:
|
|
295
298
|
migration_count += 1
|
|
299
|
+
migration_msg = format_migration_warning(tool_name)
|
|
300
|
+
if migration_msg:
|
|
301
|
+
warning(f" {migration_msg}")
|
|
296
302
|
|
|
297
303
|
if status["user"]:
|
|
298
304
|
print(f" User: {status['user']}")
|
|
@@ -306,17 +312,11 @@ def cmd_status(args: argparse.Namespace) -> None:
|
|
|
306
312
|
|
|
307
313
|
print()
|
|
308
314
|
|
|
309
|
-
if config_outdated:
|
|
310
|
-
tools_str = " ".join(config_outdated)
|
|
311
|
-
print(
|
|
312
|
-
f"{Colors.YELLOW}Configured version outdated for: "
|
|
313
|
-
f"{', '.join(config_outdated)}. Run 'code-aide update-versions "
|
|
314
|
-
f"{tools_str}' to update.{Colors.NC}"
|
|
315
|
-
)
|
|
316
315
|
if outdated_count > 0:
|
|
316
|
+
names = ", ".join(outdated_tools)
|
|
317
317
|
print(
|
|
318
318
|
f"{Colors.YELLOW}{outdated_count} tool(s) can be upgraded with "
|
|
319
|
-
f"'code-aide upgrade'.{Colors.NC}"
|
|
319
|
+
f"'code-aide upgrade': {names}.{Colors.NC}"
|
|
320
320
|
)
|
|
321
321
|
if migration_count > 0:
|
|
322
322
|
print(
|
|
@@ -9,6 +9,8 @@ import importlib.resources
|
|
|
9
9
|
import json
|
|
10
10
|
import os
|
|
11
11
|
|
|
12
|
+
from code_aide.install_types import InstallType, parse_install_type
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
def get_config_dir() -> str:
|
|
14
16
|
"""Return XDG config directory for code-aide.
|
|
@@ -70,6 +72,14 @@ def merge_cached_versions(tools: dict, cache: dict) -> None:
|
|
|
70
72
|
if tool_key in cached_tools:
|
|
71
73
|
for field in DYNAMIC_FIELDS:
|
|
72
74
|
if field in cached_tools[tool_key]:
|
|
75
|
+
if (
|
|
76
|
+
field == "install_sha256"
|
|
77
|
+
and parse_install_type(tool_data.get("install_type"))
|
|
78
|
+
== InstallType.DIRECT_DOWNLOAD
|
|
79
|
+
):
|
|
80
|
+
# Script checksum does not apply to tarball installs; ignore
|
|
81
|
+
# stale cache from older releases or mistaken updates.
|
|
82
|
+
continue
|
|
73
83
|
tool_data[field] = cached_tools[tool_key][field]
|
|
74
84
|
|
|
75
85
|
|
|
@@ -77,9 +87,10 @@ def load_tools_config() -> dict:
|
|
|
77
87
|
"""Load tool config: bundled definitions merged with cached versions.
|
|
78
88
|
|
|
79
89
|
Two-layer model: the bundled tools.json provides tool definitions and
|
|
80
|
-
install_sha256 checksums. The user's version
|
|
81
|
-
update-versions) provides latest_version, latest_date,
|
|
82
|
-
install_sha256 values when present.
|
|
90
|
+
install_sha256 checksums (for script-type tools). The user's version
|
|
91
|
+
cache (from update-versions) provides latest_version, latest_date,
|
|
92
|
+
and updated install_sha256 values when present. Cached install_sha256
|
|
93
|
+
is ignored for direct_download tools (tarball installs).
|
|
83
94
|
"""
|
|
84
95
|
bundled = load_bundled_tools()
|
|
85
96
|
cache = load_versions_cache()
|
|
@@ -99,6 +110,12 @@ def save_updated_versions(tools: dict) -> None:
|
|
|
99
110
|
entry = {}
|
|
100
111
|
for field in DYNAMIC_FIELDS:
|
|
101
112
|
if field in tool_data:
|
|
113
|
+
if (
|
|
114
|
+
field == "install_sha256"
|
|
115
|
+
and parse_install_type(tool_data.get("install_type"))
|
|
116
|
+
== InstallType.DIRECT_DOWNLOAD
|
|
117
|
+
):
|
|
118
|
+
continue
|
|
102
119
|
entry[field] = tool_data[field]
|
|
103
120
|
if entry:
|
|
104
121
|
cache_data["tools"][tool_key] = entry
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
"command": "agent",
|
|
7
7
|
"install_type": "direct_download",
|
|
8
8
|
"install_url": "https://cursor.com/install",
|
|
9
|
-
"install_sha256": "7ccf2992a4de12c040854a833db0255a08cdfc91d40c75b78cf66a8746d9bd24",
|
|
10
9
|
"download_url_template": "https://downloads.cursor.com/lab/{version}/{os}/{arch}/agent-cli-package.tar.gz",
|
|
11
10
|
"install_dir": "~/.local/share/cursor-agent/versions/{version}",
|
|
12
11
|
"bin_dir": "~/.local/bin",
|