code-aide 1.16.2__tar.gz → 1.17.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.
Files changed (66) hide show
  1. {code_aide-1.16.2 → code_aide-1.17.0}/.github/workflows/ci.yml +2 -0
  2. {code_aide-1.16.2 → code_aide-1.17.0}/.gitlab-ci.yml +1 -0
  3. {code_aide-1.16.2 → code_aide-1.17.0}/.pre-commit-config.yaml +8 -0
  4. code_aide-1.17.0/Makefile +29 -0
  5. {code_aide-1.16.2 → code_aide-1.17.0}/PKG-INFO +38 -30
  6. {code_aide-1.16.2 → code_aide-1.17.0}/README.md +37 -29
  7. code_aide-1.17.0/TODO.md +77 -0
  8. {code_aide-1.16.2 → code_aide-1.17.0}/pyproject.toml +0 -3
  9. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/__init__.py +1 -1
  10. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/commands_actions.py +74 -4
  11. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/commands_tools.py +49 -74
  12. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/config.py +38 -12
  13. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/console.py +10 -0
  14. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/constants.py +33 -16
  15. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/data/tools.json +2 -0
  16. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/entry.py +13 -0
  17. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/install.py +66 -7
  18. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/operation_handlers.py +10 -2
  19. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/operations.py +15 -3
  20. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/package_managers.py +1 -1
  21. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/prereqs.py +4 -5
  22. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/versions.py +18 -9
  23. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_commands_actions.py +237 -0
  24. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_config.py +54 -2
  25. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_console.py +16 -0
  26. code_aide-1.17.0/tests/test_constants.py +45 -0
  27. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_install.py +96 -0
  28. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_prereqs.py +29 -0
  29. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_versions.py +31 -3
  30. code_aide-1.16.2/TODO.md +0 -78
  31. code_aide-1.16.2/tests/test_constants.py +0 -21
  32. {code_aide-1.16.2 → code_aide-1.17.0}/.github/workflows/publish.yml +0 -0
  33. {code_aide-1.16.2 → code_aide-1.17.0}/.gitignore +0 -0
  34. {code_aide-1.16.2 → code_aide-1.17.0}/AGENTS.md +0 -0
  35. {code_aide-1.16.2 → code_aide-1.17.0}/CLAUDE.md +0 -0
  36. {code_aide-1.16.2 → code_aide-1.17.0}/LICENSE +0 -0
  37. {code_aide-1.16.2 → code_aide-1.17.0}/script-archive/README.md +0 -0
  38. {code_aide-1.16.2 → code_aide-1.17.0}/script-archive/amp-install.sh +0 -0
  39. {code_aide-1.16.2 → code_aide-1.17.0}/script-archive/antigravity-install.sh +0 -0
  40. {code_aide-1.16.2 → code_aide-1.17.0}/script-archive/claude-install.sh +0 -0
  41. {code_aide-1.16.2 → code_aide-1.17.0}/script-archive/cursor-install.sh +0 -0
  42. {code_aide-1.16.2 → code_aide-1.17.0}/specs/2026-05-03-qwen3.6-code-quality-analysis-original.md +0 -0
  43. {code_aide-1.16.2 → code_aide-1.17.0}/specs/2026-05-03-qwen3.6-code-quality-analysis.md +0 -0
  44. {code_aide-1.16.2 → code_aide-1.17.0}/specs/antigravity-cli-integration.md +0 -0
  45. {code_aide-1.16.2 → code_aide-1.17.0}/specs/auto-migrate-deprecated-installs.md +0 -0
  46. {code_aide-1.16.2 → code_aide-1.17.0}/specs/claude-native-installer-migration.md +0 -0
  47. {code_aide-1.16.2 → code_aide-1.17.0}/specs/missing-coding-llm-cli-tools.md +0 -0
  48. {code_aide-1.16.2 → code_aide-1.17.0}/specs/pre-commit-uv-setup.md +0 -0
  49. {code_aide-1.16.2 → code_aide-1.17.0}/specs/remove-bundled-version-baseline.md +0 -0
  50. {code_aide-1.16.2 → code_aide-1.17.0}/specs/unify-upgrade-eligibility-with-shared-evaluator.md +0 -0
  51. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/__main__.py +0 -0
  52. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/detection.py +0 -0
  53. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/detection_types.py +0 -0
  54. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/install_types.py +0 -0
  55. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/package_info.py +0 -0
  56. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/status.py +0 -0
  57. {code_aide-1.16.2 → code_aide-1.17.0}/src/code_aide/status_print.py +0 -0
  58. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_commands_tools.py +0 -0
  59. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_detection.py +0 -0
  60. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_entry.py +0 -0
  61. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_error_paths.py +0 -0
  62. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_install_types.py +0 -0
  63. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_operations.py +0 -0
  64. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_package_managers.py +0 -0
  65. {code_aide-1.16.2 → code_aide-1.17.0}/tests/test_status.py +0 -0
  66. {code_aide-1.16.2 → code_aide-1.17.0}/uv.lock +0 -0
@@ -20,5 +20,7 @@ jobs:
20
20
  run: uv run ruff check .
21
21
  - name: Format check
22
22
  run: uv run black --check .
23
+ - name: Type check
24
+ run: uv run mypy
23
25
  - name: Run tests
24
26
  run: uv run pytest tests/ -v
@@ -12,6 +12,7 @@ variables:
12
12
  script:
13
13
  - uv run ruff check .
14
14
  - uv run black --check .
15
+ - uv run mypy
15
16
  - uv run pytest tests/ -v
16
17
 
17
18
  check:python3.11:
@@ -36,3 +36,11 @@ repos:
36
36
  rev: 0.10.6
37
37
  hooks:
38
38
  - id: uv-lock
39
+ - repo: local
40
+ hooks:
41
+ - id: mypy
42
+ name: mypy
43
+ entry: uv run mypy
44
+ language: system
45
+ pass_filenames: false
46
+ files: ^src/code_aide/.*\.py$
@@ -0,0 +1,29 @@
1
+ # code-aide quality checks (uv-based)
2
+
3
+ .PHONY: build install test lint format format-check typecheck hooks check
4
+
5
+ build:
6
+ uv build
7
+
8
+ install:
9
+ uv sync
10
+
11
+ test:
12
+ uv run pytest -n4 -q
13
+
14
+ lint:
15
+ uv run ruff check .
16
+
17
+ format:
18
+ uv run black .
19
+
20
+ format-check:
21
+ uv run black --check .
22
+
23
+ typecheck:
24
+ uv run mypy
25
+
26
+ hooks:
27
+ pre-commit install
28
+
29
+ check: lint format-check typecheck
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: code-aide
3
- Version: 1.16.2
3
+ Version: 1.17.0
4
4
  Summary: Manage AI coding CLI tools (Claude, Antigravity, 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
@@ -70,9 +70,15 @@ code-aide install -p
70
70
  # Upgrade installed tools (no args = only out-of-date tools)
71
71
  code-aide upgrade [NAMES]
72
72
 
73
- # Remove tools
73
+ # Remove tools (prompts before removing all managed tools)
74
74
  code-aide remove [NAMES]
75
75
 
76
+ # Preview a removal without changing anything
77
+ code-aide remove -n
78
+
79
+ # Remove all managed tools without prompting
80
+ code-aide remove -y
81
+
76
82
  # Check upstream for latest versions (dry-run)
77
83
  code-aide update-versions -n
78
84
 
@@ -106,8 +112,8 @@ code-aide uses a two-layer version data model:
106
112
  versions of code-aide.
107
113
 
108
114
  2. **User's local version cache** (`~/.config/code-aide/versions.json`): Written
109
- by `code-aide update-versions`. Provides latest versions, dates, and updated
110
- SHA256 checksums.
115
+ by `code-aide update-versions`. Provides latest versions, dates, updated
116
+ SHA256 checksums, and recorded tarball checksums for direct_download tools.
111
117
 
112
118
  Run `code-aide update-versions` to get the latest version data without waiting
113
119
  for a new code-aide release.
@@ -118,32 +124,34 @@ Each entry in `tools.json` describes one managed tool. Required fields are
118
124
  `name`, `command`, and `install_type`; the others are optional and only
119
125
  consulted when the relevant install method or platform applies.
120
126
 
121
- | Field | Required | Notes |
122
- |:--------------------------------|:---------|:---------------------------------------------------------------------------------------------|
123
- | `name` | Yes | Display name for status output |
124
- | `command` | Yes | Binary name (looked up in `PATH`) |
125
- | `aliases` | No | Alternate names accepted by commands and resolved to the canonical tool key |
126
- | `install_type` | Yes | One of `npm`, `script`, `direct_download`, `pkg` |
127
- | `version_args` | No | List of arguments passed to the binary to print its version (default: `["--version"]`) |
128
- | `version_url` | No | URL queried by `update-versions` to discover the latest upstream version |
129
- | `version_manifest_url_template` | No | JSON manifest URL template with `{platform}`; reads its `version` field |
130
- | `latest_version` | Cache | Last known upstream version (filled by `update-versions`) |
131
- | `latest_date` | Cache | Release date for `latest_version` |
132
- | `prerequisites` | No | List of required external commands (e.g. `["npm"]`) |
133
- | `min_node_version` | No | Minimum major Node.js version required |
134
- | `npm_package` | npm | npm package name (e.g. `@anthropic-ai/claude-code`) |
135
- | `install_url` | script | URL of the install script that will be downloaded and piped to `bash` |
136
- | `install_sha256` | script | SHA-256 of the install script body required for verified installs |
137
- | `install_script_path_prepend` | script | Extra directories prepended to `PATH` when the install script runs (e.g. `["~/.local/bin"]`) |
138
- | `download_url_template` | direct | URL template with `{version}`, `{os}`, `{arch}` placeholders |
139
- | `install_dir` | direct | Directory to extract into; supports `{version}` |
140
- | `bin_dir` | direct | Directory where symlinks for `command` are created (e.g. `~/.local/bin`) |
141
- | `symlinks` | direct | Map of `link_name` → relative path inside `install_dir` to symlink into `bin_dir` |
142
- | `freebsd_port` | FreeBSD | FreeBSD ports/pkg name (used when running on FreeBSD) |
143
- | `freebsd_pkg_repo` | FreeBSD | Optional non-default pkg repository name (passed via `pkg install -r <repo>`) |
144
- | `next_steps` | No | Free-form text printed after a successful install |
145
- | `docs_url` | No | Documentation URL printed after install |
146
- | `self_updates` | No | Tool updates itself when run; excludes it from managed upgrade actions |
127
+ | Field | Required | Notes |
128
+ |:--------------------------------|:---------|:----------------------------------------------------------------------------------------------------------------|
129
+ | `name` | Yes | Display name for status output |
130
+ | `command` | Yes | Binary name (looked up in `PATH`) |
131
+ | `aliases` | No | Alternate names accepted by commands and resolved to the canonical tool key |
132
+ | `install_type` | Yes | One of `npm`, `script`, `direct_download`, `pkg` |
133
+ | `version_args` | No | List of arguments passed to the binary to print its version (default: `["--version"]`) |
134
+ | `version_url` | No | URL queried by `update-versions` to discover the latest upstream version |
135
+ | `version_manifest_url_template` | No | JSON manifest URL template with `{platform}`; reads its `version` field |
136
+ | `version_extract_pattern` | No | Regex applied to the install script to extract the version before generic heuristics |
137
+ | `latest_version` | Cache | Last known upstream version (filled by `update-versions`) |
138
+ | `latest_date` | Cache | Release date for `latest_version` |
139
+ | `prerequisites` | No | List of required external commands (e.g. `["npm"]`) |
140
+ | `min_node_version` | No | Minimum major Node.js version required |
141
+ | `npm_package` | npm | npm package name (e.g. `@anthropic-ai/claude-code`) |
142
+ | `install_url` | script | URL of the install script that will be downloaded and piped to `bash` |
143
+ | `install_sha256` | script | SHA-256 of the install script body required for verified installs |
144
+ | `download_sha256` | Cache | SHA-256 of the downloaded tarball for direct_download tools; recorded by `update-versions`, verified on install |
145
+ | `install_script_path_prepend` | script | Extra directories prepended to `PATH` when the install script runs (e.g. `["~/.local/bin"]`) |
146
+ | `download_url_template` | direct | URL template with `{version}`, `{os}`, `{arch}` placeholders |
147
+ | `install_dir` | direct | Directory to extract into; supports `{version}` |
148
+ | `bin_dir` | direct | Directory where symlinks for `command` are created (e.g. `~/.local/bin`) |
149
+ | `symlinks` | direct | Map of `link_name` relative path inside `install_dir` to symlink into `bin_dir` |
150
+ | `freebsd_port` | FreeBSD | FreeBSD ports/pkg name (used when running on FreeBSD) |
151
+ | `freebsd_pkg_repo` | FreeBSD | Optional non-default pkg repository name (passed via `pkg install -r <repo>`) |
152
+ | `next_steps` | No | Free-form text printed after a successful install |
153
+ | `docs_url` | No | Documentation URL printed after install |
154
+ | `self_updates` | No | Tool updates itself when run; excludes it from managed upgrade actions |
147
155
 
148
156
  ## Features
149
157
 
@@ -43,9 +43,15 @@ code-aide install -p
43
43
  # Upgrade installed tools (no args = only out-of-date tools)
44
44
  code-aide upgrade [NAMES]
45
45
 
46
- # Remove tools
46
+ # Remove tools (prompts before removing all managed tools)
47
47
  code-aide remove [NAMES]
48
48
 
49
+ # Preview a removal without changing anything
50
+ code-aide remove -n
51
+
52
+ # Remove all managed tools without prompting
53
+ code-aide remove -y
54
+
49
55
  # Check upstream for latest versions (dry-run)
50
56
  code-aide update-versions -n
51
57
 
@@ -79,8 +85,8 @@ code-aide uses a two-layer version data model:
79
85
  versions of code-aide.
80
86
 
81
87
  2. **User's local version cache** (`~/.config/code-aide/versions.json`): Written
82
- by `code-aide update-versions`. Provides latest versions, dates, and updated
83
- SHA256 checksums.
88
+ by `code-aide update-versions`. Provides latest versions, dates, updated
89
+ SHA256 checksums, and recorded tarball checksums for direct_download tools.
84
90
 
85
91
  Run `code-aide update-versions` to get the latest version data without waiting
86
92
  for a new code-aide release.
@@ -91,32 +97,34 @@ Each entry in `tools.json` describes one managed tool. Required fields are
91
97
  `name`, `command`, and `install_type`; the others are optional and only
92
98
  consulted when the relevant install method or platform applies.
93
99
 
94
- | Field | Required | Notes |
95
- |:--------------------------------|:---------|:---------------------------------------------------------------------------------------------|
96
- | `name` | Yes | Display name for status output |
97
- | `command` | Yes | Binary name (looked up in `PATH`) |
98
- | `aliases` | No | Alternate names accepted by commands and resolved to the canonical tool key |
99
- | `install_type` | Yes | One of `npm`, `script`, `direct_download`, `pkg` |
100
- | `version_args` | No | List of arguments passed to the binary to print its version (default: `["--version"]`) |
101
- | `version_url` | No | URL queried by `update-versions` to discover the latest upstream version |
102
- | `version_manifest_url_template` | No | JSON manifest URL template with `{platform}`; reads its `version` field |
103
- | `latest_version` | Cache | Last known upstream version (filled by `update-versions`) |
104
- | `latest_date` | Cache | Release date for `latest_version` |
105
- | `prerequisites` | No | List of required external commands (e.g. `["npm"]`) |
106
- | `min_node_version` | No | Minimum major Node.js version required |
107
- | `npm_package` | npm | npm package name (e.g. `@anthropic-ai/claude-code`) |
108
- | `install_url` | script | URL of the install script that will be downloaded and piped to `bash` |
109
- | `install_sha256` | script | SHA-256 of the install script body required for verified installs |
110
- | `install_script_path_prepend` | script | Extra directories prepended to `PATH` when the install script runs (e.g. `["~/.local/bin"]`) |
111
- | `download_url_template` | direct | URL template with `{version}`, `{os}`, `{arch}` placeholders |
112
- | `install_dir` | direct | Directory to extract into; supports `{version}` |
113
- | `bin_dir` | direct | Directory where symlinks for `command` are created (e.g. `~/.local/bin`) |
114
- | `symlinks` | direct | Map of `link_name` → relative path inside `install_dir` to symlink into `bin_dir` |
115
- | `freebsd_port` | FreeBSD | FreeBSD ports/pkg name (used when running on FreeBSD) |
116
- | `freebsd_pkg_repo` | FreeBSD | Optional non-default pkg repository name (passed via `pkg install -r <repo>`) |
117
- | `next_steps` | No | Free-form text printed after a successful install |
118
- | `docs_url` | No | Documentation URL printed after install |
119
- | `self_updates` | No | Tool updates itself when run; excludes it from managed upgrade actions |
100
+ | Field | Required | Notes |
101
+ |:--------------------------------|:---------|:----------------------------------------------------------------------------------------------------------------|
102
+ | `name` | Yes | Display name for status output |
103
+ | `command` | Yes | Binary name (looked up in `PATH`) |
104
+ | `aliases` | No | Alternate names accepted by commands and resolved to the canonical tool key |
105
+ | `install_type` | Yes | One of `npm`, `script`, `direct_download`, `pkg` |
106
+ | `version_args` | No | List of arguments passed to the binary to print its version (default: `["--version"]`) |
107
+ | `version_url` | No | URL queried by `update-versions` to discover the latest upstream version |
108
+ | `version_manifest_url_template` | No | JSON manifest URL template with `{platform}`; reads its `version` field |
109
+ | `version_extract_pattern` | No | Regex applied to the install script to extract the version before generic heuristics |
110
+ | `latest_version` | Cache | Last known upstream version (filled by `update-versions`) |
111
+ | `latest_date` | Cache | Release date for `latest_version` |
112
+ | `prerequisites` | No | List of required external commands (e.g. `["npm"]`) |
113
+ | `min_node_version` | No | Minimum major Node.js version required |
114
+ | `npm_package` | npm | npm package name (e.g. `@anthropic-ai/claude-code`) |
115
+ | `install_url` | script | URL of the install script that will be downloaded and piped to `bash` |
116
+ | `install_sha256` | script | SHA-256 of the install script body required for verified installs |
117
+ | `download_sha256` | Cache | SHA-256 of the downloaded tarball for direct_download tools; recorded by `update-versions`, verified on install |
118
+ | `install_script_path_prepend` | script | Extra directories prepended to `PATH` when the install script runs (e.g. `["~/.local/bin"]`) |
119
+ | `download_url_template` | direct | URL template with `{version}`, `{os}`, `{arch}` placeholders |
120
+ | `install_dir` | direct | Directory to extract into; supports `{version}` |
121
+ | `bin_dir` | direct | Directory where symlinks for `command` are created (e.g. `~/.local/bin`) |
122
+ | `symlinks` | direct | Map of `link_name` relative path inside `install_dir` to symlink into `bin_dir` |
123
+ | `freebsd_port` | FreeBSD | FreeBSD ports/pkg name (used when running on FreeBSD) |
124
+ | `freebsd_pkg_repo` | FreeBSD | Optional non-default pkg repository name (passed via `pkg install -r <repo>`) |
125
+ | `next_steps` | No | Free-form text printed after a successful install |
126
+ | `docs_url` | No | Documentation URL printed after install |
127
+ | `self_updates` | No | Tool updates itself when run; excludes it from managed upgrade actions |
120
128
 
121
129
  ## Features
122
130
 
@@ -0,0 +1,77 @@
1
+ # TODO
2
+
3
+ Keep the project focused on managing AI coding CLI tools (install, upgrade,
4
+ remove, status, and version metadata).
5
+
6
+ ## Summary
7
+
8
+ | # | Item | Risk | Effort | Notes |
9
+ |:---|:--------------------------------------------|:---------|:--------|:-------------------------------------------------------------------|
10
+ | 1 | ~~Handle missing `node` cleanly~~ (done) | - | - | Fixed in `prereqs.py` `check_prerequisites()` |
11
+ | 2 | Read version from stdout+stderr | Med | Low | Some tools write version to stderr; shown as not installed |
12
+ | 3 | ~~Atomic version cache writes~~ (done) | - | - | Fixed in `config.py` `save_versions_cache()` |
13
+ | 4 | ~~Warn on invalid cache JSON~~ (done) | - | - | `config.py` `load_versions_cache()` logs a warning |
14
+ | 5 | ~~Use `os.pathsep` not `":"`~~ (done) | - | - | Fixed in `prereqs.py` `check_path_directories()` |
15
+ | 6 | ~~Verify direct-download tarballs~~ (done) | - | - | `download_sha256` recorded by update-versions, verified on install |
16
+ | 7 | ~~Tarball checksum metadata fields~~ (done) | - | - | `download_sha256` field in tools schema and versions cache |
17
+ | 8 | `--json` output mode | Low | Med | Needed for CI/automation consumers |
18
+ | 9 | `doctor` command | Low | Med | Health checks exist but are scattered |
19
+ | 10 | `install --force` | Low | Trivial | Internal `force` param exists; not exposed in CLI |
20
+ | 11 | Cleanup stale direct-download versions | Low | Med | Old version dirs accumulate on disk |
21
+ | 12 | Document default subcommand | Very Low | Trivial | One line in README |
22
+ | 13 | Document `install --dryrun` | Very Low | Trivial | One line in README |
23
+ | 14 | Document env vars and config paths | Low | Low | `XDG_CONFIG_HOME` undocumented |
24
+ | 15 | Cross-distro package detection | Med | High | Only Gentoo supported; other Linux distros get nothing |
25
+ | 16 | Split large modules | Low | High | Refactoring only; no behavior change |
26
+ | 17 | Tests for untested functions | Med | High | `fetch_url`, `get_system_package_info` |
27
+ | 18 | Tests for edge cases | Med | Med | PATH oddities, more version parsing corners |
28
+
29
+ ## Reliability and Correctness
30
+
31
+ - [x] Handle missing `node` cleanly during prerequisite checks (`OSError` path
32
+ in Node version probing).
33
+ - [ ] Read tool version output from both stdout and stderr so status does not
34
+ miss installed versions.
35
+ - [x] Make version cache writes atomic (temp file + rename) to avoid
36
+ partial/corrupted `versions.json`.
37
+ - [x] Warn when `versions.json` cache contains invalid JSON instead of silently
38
+ returning empty data.
39
+ - [x] Use `os.pathsep` instead of hardcoded `":"` in `prereqs.py`
40
+ `check_path_directories()`.
41
+
42
+ ## Security and Integrity
43
+
44
+ - [x] Verify direct-download tarballs against a checksum recorded by
45
+ `update-versions` (warn when no checksum has been recorded yet).
46
+ - [x] Extend tool metadata with a `download_sha256` field for tarball checksums.
47
+ - [ ] Consider signature verification (beyond SHA256) for downloads where
48
+ upstream publishes signatures.
49
+
50
+ ## CLI and Automation UX
51
+
52
+ - [ ] Add `--json` output mode for `list`, `status`, and `update-versions`.
53
+ - [ ] Add a focused `doctor` command for environment checks (PATH,
54
+ prerequisites, command health).
55
+ - [ ] Consider `install --force` for reinstall/repair flows.
56
+ - [ ] Add cleanup support for stale direct-download versions no longer in use.
57
+
58
+ ## Documentation
59
+
60
+ - [ ] Document that running `code-aide` with no subcommand defaults to `status`.
61
+ - [ ] Add `install --dryrun` to the README usage examples.
62
+ - [ ] Document environment variables and config file paths
63
+ (`~/.config/code-aide/versions.json`).
64
+
65
+ ## Platform and Package Detection
66
+
67
+ - [ ] Broaden system package metadata detection beyond Gentoo-specific tooling
68
+ where practical.
69
+
70
+ ## Maintainability and Tests
71
+
72
+ - [ ] Split larger modules into smaller focused files where practical
73
+ (`commands_actions.py`, `versions.py`, `install.py`).
74
+ - [ ] Add tests for remaining untested functions:
75
+ - `fetch_url()` (network; needs mocking)
76
+ - `get_system_package_info()` (Gentoo tooling)
77
+ - [ ] Add tests for more prerequisite edge cases and PATH oddities.
@@ -47,9 +47,6 @@ filterwarnings = [
47
47
  "error",
48
48
  "ignore::DeprecationWarning:_pytest.*",
49
49
  ]
50
- markers = [
51
- "network: tests that make real network calls (deselect with '-m \"not network\"')",
52
- ]
53
50
 
54
51
  [tool.mypy]
55
52
  python_version = "3.11"
@@ -1,3 +1,3 @@
1
1
  """code-aide - Manage AI coding CLI tools."""
2
2
 
3
- __version__ = "1.16.2"
3
+ __version__ = "1.17.0"
@@ -6,7 +6,7 @@ from typing import Any, Dict, List
6
6
 
7
7
  from code_aide.constants import TOOLS
8
8
  from code_aide.detection import detect_install_method, get_brew_package_info
9
- from code_aide.install import install_tool
9
+ from code_aide.install import fetch_download_checksum, install_tool
10
10
  from code_aide.install_types import (
11
11
  InstallMethod,
12
12
  InstallType,
@@ -34,6 +34,7 @@ from code_aide.versions import (
34
34
  print_check_results_table,
35
35
  )
36
36
  from code_aide.config import (
37
+ ensure_versions_cache,
37
38
  load_bundled_tools,
38
39
  load_versions_cache,
39
40
  merge_cached_versions,
@@ -75,7 +76,7 @@ def cmd_install(args: argparse.Namespace) -> None:
75
76
  )
76
77
 
77
78
  installed = []
78
- installed_bin_dirs = []
79
+ installed_bin_dirs: List[str] = []
79
80
  failed = []
80
81
 
81
82
  for tool in tools_to_install:
@@ -136,6 +137,9 @@ def cmd_upgrade(args: argparse.Namespace) -> None:
136
137
  info(f"Upgrading specified tools: {', '.join(tools_to_upgrade)}")
137
138
  else:
138
139
  tools_to_upgrade = []
140
+ # Default selection is driven by latest_version, so refresh stale
141
+ # cache data first; named upgrades below skip this.
142
+ ensure_versions_cache(TOOLS)
139
143
  for name, config in TOOLS.items():
140
144
  assessment = ToolUpgradeEvaluator(name, config).evaluate()
141
145
  if assessment.actionable_by_upgrade and name not in tools_to_upgrade:
@@ -198,14 +202,37 @@ def cmd_upgrade(args: argparse.Namespace) -> None:
198
202
  )
199
203
 
200
204
 
205
+ def _confirm_remove_all(tools: List[str]) -> bool:
206
+ """Prompt before removing every managed tool."""
207
+ print(f"This will remove {len(tools)} managed tools: {', '.join(tools)}")
208
+ try:
209
+ answer = input("Remove all of them? [y/N] ").strip().lower()
210
+ except (EOFError, KeyboardInterrupt):
211
+ print()
212
+ return False
213
+ return answer in ("y", "yes")
214
+
215
+
201
216
  def cmd_remove(args: argparse.Namespace) -> None:
202
217
  """Handle remove command."""
218
+ dryrun = getattr(args, "dryrun", False)
219
+ assume_yes = getattr(args, "yes", False)
220
+
203
221
  if args.tools:
204
222
  tools_to_remove = args.tools
205
223
  info(f"Removing specified tools: {', '.join(tools_to_remove)}")
206
224
  else:
207
225
  tools_to_remove = list(TOOLS.keys())
208
- info(f"No tools specified, removing all: {', '.join(tools_to_remove)}")
226
+ if dryrun:
227
+ info(
228
+ "[DRYRUN] Would remove all managed tools: "
229
+ f"{', '.join(tools_to_remove)}"
230
+ )
231
+ else:
232
+ info(f"No tools specified, removing all: {', '.join(tools_to_remove)}")
233
+ if not assume_yes and not _confirm_remove_all(tools_to_remove):
234
+ print("Aborted.")
235
+ return
209
236
 
210
237
  tools_to_remove = resolve_tool_names(tools_to_remove, TOOLS)
211
238
  validate_tools(tools_to_remove)
@@ -216,6 +243,14 @@ def cmd_remove(args: argparse.Namespace) -> None:
216
243
 
217
244
  for tool in tools_to_remove:
218
245
  print()
246
+ if dryrun:
247
+ info(f"=== Checking {tool} ===")
248
+ if is_tool_installed(tool):
249
+ removed.append(tool)
250
+ else:
251
+ skipped.append(tool)
252
+ continue
253
+
219
254
  info(f"=== Removing {tool} ===")
220
255
 
221
256
  if not is_tool_installed(tool):
@@ -229,8 +264,19 @@ def cmd_remove(args: argparse.Namespace) -> None:
229
264
 
230
265
  print()
231
266
  print("=" * 42)
232
- info("Removal Summary")
267
+ if dryrun:
268
+ info("Removal Summary (dry run)")
269
+ else:
270
+ info("Removal Summary")
233
271
  print("=" * 42)
272
+ if dryrun:
273
+ if removed:
274
+ success(f"Would remove: {', '.join(removed)}")
275
+ if skipped:
276
+ warning(f"Not installed (skipped): {', '.join(skipped)}")
277
+ print()
278
+ success("Dry run completed without removing anything!")
279
+ return
234
280
 
235
281
  if removed:
236
282
  success(f"Successfully removed: {', '.join(removed)}")
@@ -326,6 +372,30 @@ def cmd_update_versions(args: argparse.Namespace) -> None:
326
372
  tool_entry["latest_version"] = normalized
327
373
  version_info_changed = True
328
374
 
375
+ # Record the tarball checksum for direct-download tools so later
376
+ # installs can verify integrity against it.
377
+ if not args.dryrun:
378
+ for tool_name in tool_names:
379
+ tool_entry = config["tools"][tool_name]
380
+ if (
381
+ parse_install_type(tool_entry.get("install_type"))
382
+ != InstallType.DIRECT_DOWNLOAD
383
+ ):
384
+ continue
385
+ if "download_url_template" not in tool_entry:
386
+ continue
387
+ if not tool_entry.get("latest_version"):
388
+ continue
389
+ checksum = fetch_download_checksum(tool_entry)
390
+ if not checksum:
391
+ continue
392
+ if tool_entry.get("download_sha256") != checksum:
393
+ tool_entry["download_sha256"] = checksum
394
+ version_info_changed = True
395
+ print(f"Recorded download SHA256 for {tool_name}.")
396
+ else:
397
+ print(f"Download SHA256 for {tool_name} is unchanged.")
398
+
329
399
  def _save(tools: dict) -> str:
330
400
  """Save versions to user cache. Returns description."""
331
401
  save_updated_versions(tools)
@@ -30,6 +30,7 @@ from code_aide.status import (
30
30
  print_brew_version_status,
31
31
  print_pkg_version_status,
32
32
  print_system_version_status,
33
+ ToolUpgradeAssessment,
33
34
  ToolUpgradeEvaluator,
34
35
  UpgradeDecision,
35
36
  VersionDisplayState,
@@ -161,6 +162,51 @@ def _generic_version_annotation(
161
162
  )
162
163
 
163
164
 
165
+ def _print_version_status(
166
+ assessment: ToolUpgradeAssessment,
167
+ status_version: str,
168
+ tool_path: str | None,
169
+ tool_config: dict,
170
+ ) -> None:
171
+ """Print the long-form version line for one installed tool."""
172
+ package_info = assessment.package_info
173
+ if assessment.install_method == InstallMethod.SYSTEM and tool_path and package_info:
174
+ print_system_version_status(
175
+ status_version, assessment.latest_version, package_info
176
+ )
177
+ return
178
+
179
+ if assessment.install_method == InstallMethod.PKG:
180
+ if package_info and package_info.get("available_version"):
181
+ print_pkg_version_status(
182
+ status_version,
183
+ assessment.latest_version,
184
+ package_info,
185
+ repo=tool_config.get("freebsd_pkg_repo"),
186
+ )
187
+ return
188
+ elif assessment.install_method in (
189
+ InstallMethod.BREW_FORMULA,
190
+ InstallMethod.BREW_CASK,
191
+ ):
192
+ if package_info and package_info.get("available_version"):
193
+ print_brew_version_status(
194
+ status_version, assessment.latest_version, package_info
195
+ )
196
+ return
197
+
198
+ if (
199
+ assessment.latest_version
200
+ and assessment.version_state == VersionDisplayState.UP_TO_DATE
201
+ ):
202
+ print(
203
+ f" Version: {status_version} "
204
+ f"{Colors.GREEN}(up to date){Colors.NC}"
205
+ )
206
+ else:
207
+ print(_generic_version_annotation(status_version, assessment.latest_version))
208
+
209
+
164
210
  def cmd_status_compact() -> None:
165
211
  """Show compact one-line-per-tool status."""
166
212
  ensure_versions_cache(TOOLS)
@@ -242,80 +288,9 @@ def cmd_status(args: argparse.Namespace) -> None:
242
288
  print(f" Status: {Colors.GREEN}✓ Installed{Colors.NC}")
243
289
 
244
290
  if status["version"]:
245
- if (
246
- assessment.install_method == InstallMethod.SYSTEM
247
- and tool_path
248
- and assessment.package_info
249
- ):
250
- print_system_version_status(
251
- status["version"],
252
- assessment.latest_version,
253
- assessment.package_info,
254
- )
255
- elif assessment.install_method == InstallMethod.PKG:
256
- if assessment.package_info and assessment.package_info.get(
257
- "available_version"
258
- ):
259
- print_pkg_version_status(
260
- status["version"],
261
- assessment.latest_version,
262
- assessment.package_info,
263
- repo=tool_config.get("freebsd_pkg_repo"),
264
- )
265
- elif assessment.latest_version:
266
- if assessment.version_state == VersionDisplayState.UP_TO_DATE:
267
- print(
268
- f" Version: {status['version']} "
269
- f"{Colors.GREEN}(up to date){Colors.NC}"
270
- )
271
- else:
272
- print(
273
- _generic_version_annotation(
274
- status["version"], assessment.latest_version
275
- )
276
- )
277
- else:
278
- print(f" Version: {status['version']}")
279
- elif assessment.install_method in (
280
- InstallMethod.BREW_FORMULA,
281
- InstallMethod.BREW_CASK,
282
- ):
283
- if assessment.package_info and assessment.package_info.get(
284
- "available_version"
285
- ):
286
- print_brew_version_status(
287
- status["version"],
288
- assessment.latest_version,
289
- assessment.package_info,
290
- )
291
- elif assessment.latest_version:
292
- if assessment.version_state == VersionDisplayState.UP_TO_DATE:
293
- print(
294
- f" Version: {status['version']} "
295
- f"{Colors.GREEN}(up to date){Colors.NC}"
296
- )
297
- else:
298
- print(
299
- _generic_version_annotation(
300
- status["version"], assessment.latest_version
301
- )
302
- )
303
- else:
304
- print(f" Version: {status['version']}")
305
- elif assessment.latest_version:
306
- if assessment.version_state == VersionDisplayState.UP_TO_DATE:
307
- print(
308
- f" Version: {status['version']} "
309
- f"{Colors.GREEN}(up to date){Colors.NC}"
310
- )
311
- else:
312
- print(
313
- _generic_version_annotation(
314
- status["version"], assessment.latest_version
315
- )
316
- )
317
- else:
318
- print(f" Version: {status['version']}")
291
+ _print_version_status(
292
+ assessment, status["version"], tool_path, tool_config
293
+ )
319
294
 
320
295
  if assessment.decision == UpgradeDecision.UPGRADE:
321
296
  outdated_count += 1