code-aide 1.0.3__tar.gz → 1.2.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.2.0/.github/workflows/publish.yml +46 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/AGENTS.md +3 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/PKG-INFO +18 -10
- {code_aide-1.0.3 → code_aide-1.2.0}/README.md +17 -9
- code_aide-1.2.0/TODO.md +70 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/pyproject.toml +4 -1
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/__init__.py +1 -1
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/commands_actions.py +19 -11
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/config.py +26 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/data/tools.json +12 -7
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/detection.py +3 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/entry.py +7 -1
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/install.py +7 -2
- {code_aide-1.0.3 → code_aide-1.2.0}/tests/test_commands_actions.py +76 -2
- {code_aide-1.0.3 → code_aide-1.2.0}/tests/test_install.py +54 -0
- code_aide-1.0.3/.github/workflows/publish.yml +0 -20
- {code_aide-1.0.3 → code_aide-1.2.0}/.github/workflows/ci.yml +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/.gitignore +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/CLAUDE.md +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/LICENSE +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/__main__.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/commands_tools.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/console.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/constants.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/operations.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/prereqs.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/status.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/src/code_aide/versions.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/tests/test_commands_tools.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/tests/test_config.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/tests/test_console.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/tests/test_constants.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/tests/test_detection.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/tests/test_operations.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/tests/test_status.py +0 -0
- {code_aide-1.0.3 → code_aide-1.2.0}/tests/test_versions.py +0 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
tags: ["v*"]
|
|
5
|
+
jobs:
|
|
6
|
+
publish:
|
|
7
|
+
runs-on: ubuntu-latest
|
|
8
|
+
permissions:
|
|
9
|
+
contents: write
|
|
10
|
+
id-token: write
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
with:
|
|
14
|
+
fetch-depth: 0
|
|
15
|
+
- uses: astral-sh/setup-uv@v5
|
|
16
|
+
- run: uv build
|
|
17
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
18
|
+
- name: Build commit changelog
|
|
19
|
+
id: changelog
|
|
20
|
+
run: |
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
current_tag="${GITHUB_REF_NAME}"
|
|
23
|
+
previous_tag="$(git describe --tags --abbrev=0 "${current_tag}^" 2>/dev/null || true)"
|
|
24
|
+
|
|
25
|
+
{
|
|
26
|
+
echo "body<<EOF"
|
|
27
|
+
echo "## Commits"
|
|
28
|
+
echo
|
|
29
|
+
if [ -n "${previous_tag}" ]; then
|
|
30
|
+
echo "Range: \`${previous_tag}..${current_tag}\`"
|
|
31
|
+
echo
|
|
32
|
+
git log --pretty=format:'* %s (%h)' "${previous_tag}..${current_tag}"
|
|
33
|
+
else
|
|
34
|
+
echo "First tagged release."
|
|
35
|
+
echo
|
|
36
|
+
git log --pretty=format:'* %s (%h)' "${current_tag}"
|
|
37
|
+
fi
|
|
38
|
+
echo
|
|
39
|
+
echo "EOF"
|
|
40
|
+
} >> "${GITHUB_OUTPUT}"
|
|
41
|
+
- name: Create GitHub release notes
|
|
42
|
+
uses: softprops/action-gh-release@v2
|
|
43
|
+
with:
|
|
44
|
+
tag_name: ${{ github.ref_name }}
|
|
45
|
+
generate_release_notes: true
|
|
46
|
+
body: ${{ steps.changelog.outputs.body }}
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
user's local cache (~/.config/code-aide/versions.json)
|
|
8
8
|
- All tests should pass before committing
|
|
9
9
|
- Run `black` formatter on python before commits
|
|
10
|
+
- Write useful commit messages: start subjects with past-tense action verbs
|
|
11
|
+
(`Added`, `Changed`, `Fixed`, `Removed`), keep them user-facing, and keep
|
|
12
|
+
commits focused.
|
|
10
13
|
- Python 3.11+ compatible
|
|
11
14
|
- Keep files under 300 lines where practical
|
|
12
15
|
- Run `format-markdown` on markdown files before commits (if it is available)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-aide
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.2.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
|
|
@@ -73,6 +73,9 @@ code-aide update-versions -n
|
|
|
73
73
|
|
|
74
74
|
# Update version cache
|
|
75
75
|
code-aide update-versions -y
|
|
76
|
+
|
|
77
|
+
# Update bundled version baseline (developer use, before releases)
|
|
78
|
+
code-aide update-versions -b -y
|
|
76
79
|
```
|
|
77
80
|
|
|
78
81
|
## Supported Tools
|
|
@@ -137,27 +140,32 @@ uv run pytest tests/test_install.py::TestDetectOsArch -v
|
|
|
137
140
|
|
|
138
141
|
`publish.yml` publishes to PyPI when a Git tag matching `v*` is pushed.
|
|
139
142
|
|
|
140
|
-
1. Update version
|
|
141
|
-
- `
|
|
142
|
-
- `src/code_aide/
|
|
143
|
-
|
|
143
|
+
1. Update the bundled version baseline:
|
|
144
|
+
- `code-aide update-versions -b -y`
|
|
145
|
+
- `git add src/code_aide/data/tools.json`
|
|
146
|
+
- `git commit -m "Updated bundled version data"`
|
|
147
|
+
2. Update the version string in `src/code_aide/__init__.py` (`__version__`).
|
|
148
|
+
`pyproject.toml` reads it automatically via Hatchling.
|
|
149
|
+
3. Run checks:
|
|
144
150
|
- `uv run pytest tests/ -v`
|
|
145
151
|
- `uv build`
|
|
146
|
-
|
|
147
|
-
- `git add
|
|
152
|
+
4. Commit the release version bump:
|
|
153
|
+
- `git add src/code_aide/__init__.py`
|
|
148
154
|
- `git commit -m "Bumped version to X.Y.Z"`
|
|
149
|
-
|
|
155
|
+
5. Write useful commit messages before tagging:
|
|
150
156
|
- Start subject lines with an action verb in past tense (`Added`, `Changed`,
|
|
151
157
|
`Fixed`, `Removed`).
|
|
152
158
|
- Keep subjects user-facing so auto-generated release notes are meaningful.
|
|
153
159
|
- Group related changes into focused commits instead of one broad commit.
|
|
154
160
|
- Example: `Fixed timeout handling in status command`
|
|
155
|
-
|
|
161
|
+
6. Tag and push:
|
|
156
162
|
- `git tag vX.Y.Z`
|
|
157
163
|
- `git push origin main --follow-tags`
|
|
158
|
-
|
|
164
|
+
7. Confirm GitHub Actions:
|
|
159
165
|
- CI should pass.
|
|
160
166
|
- Publish workflow should upload to PyPI and create GitHub Release notes.
|
|
167
|
+
- Release notes should include generated notes plus a commit summary from the
|
|
168
|
+
previous tag to the current tag.
|
|
161
169
|
|
|
162
170
|
## License
|
|
163
171
|
|
|
@@ -47,6 +47,9 @@ code-aide update-versions -n
|
|
|
47
47
|
|
|
48
48
|
# Update version cache
|
|
49
49
|
code-aide update-versions -y
|
|
50
|
+
|
|
51
|
+
# Update bundled version baseline (developer use, before releases)
|
|
52
|
+
code-aide update-versions -b -y
|
|
50
53
|
```
|
|
51
54
|
|
|
52
55
|
## Supported Tools
|
|
@@ -111,27 +114,32 @@ uv run pytest tests/test_install.py::TestDetectOsArch -v
|
|
|
111
114
|
|
|
112
115
|
`publish.yml` publishes to PyPI when a Git tag matching `v*` is pushed.
|
|
113
116
|
|
|
114
|
-
1. Update version
|
|
115
|
-
- `
|
|
116
|
-
- `src/code_aide/
|
|
117
|
-
|
|
117
|
+
1. Update the bundled version baseline:
|
|
118
|
+
- `code-aide update-versions -b -y`
|
|
119
|
+
- `git add src/code_aide/data/tools.json`
|
|
120
|
+
- `git commit -m "Updated bundled version data"`
|
|
121
|
+
2. Update the version string in `src/code_aide/__init__.py` (`__version__`).
|
|
122
|
+
`pyproject.toml` reads it automatically via Hatchling.
|
|
123
|
+
3. Run checks:
|
|
118
124
|
- `uv run pytest tests/ -v`
|
|
119
125
|
- `uv build`
|
|
120
|
-
|
|
121
|
-
- `git add
|
|
126
|
+
4. Commit the release version bump:
|
|
127
|
+
- `git add src/code_aide/__init__.py`
|
|
122
128
|
- `git commit -m "Bumped version to X.Y.Z"`
|
|
123
|
-
|
|
129
|
+
5. Write useful commit messages before tagging:
|
|
124
130
|
- Start subject lines with an action verb in past tense (`Added`, `Changed`,
|
|
125
131
|
`Fixed`, `Removed`).
|
|
126
132
|
- Keep subjects user-facing so auto-generated release notes are meaningful.
|
|
127
133
|
- Group related changes into focused commits instead of one broad commit.
|
|
128
134
|
- Example: `Fixed timeout handling in status command`
|
|
129
|
-
|
|
135
|
+
6. Tag and push:
|
|
130
136
|
- `git tag vX.Y.Z`
|
|
131
137
|
- `git push origin main --follow-tags`
|
|
132
|
-
|
|
138
|
+
7. Confirm GitHub Actions:
|
|
133
139
|
- CI should pass.
|
|
134
140
|
- Publish workflow should upload to PyPI and create GitHub Release notes.
|
|
141
|
+
- Release notes should include generated notes plus a commit summary from the
|
|
142
|
+
previous tag to the current tag.
|
|
135
143
|
|
|
136
144
|
## License
|
|
137
145
|
|
code_aide-1.2.0/TODO.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# TODO
|
|
2
|
+
|
|
3
|
+
Keep the project focused on managing AI coding CLI tools (install, upgrade,
|
|
4
|
+
remove, status, and version metadata).
|
|
5
|
+
|
|
6
|
+
## Bugs
|
|
7
|
+
|
|
8
|
+
- [x] Standardize `--dryrun` flag: `update-versions` uses `--dry-run` but
|
|
9
|
+
`install` uses `--dryrun`. Change `update-versions` to `--dryrun`.
|
|
10
|
+
- [x] Add missing `success()` message for direct-download installs in non-dryrun
|
|
11
|
+
mode (`install.py`, `install_tool()`).
|
|
12
|
+
- [x] Replace PID-based temp directory naming with `tempfile.mkdtemp()` in
|
|
13
|
+
`install.py` `install_direct_download()`.
|
|
14
|
+
- [x] Guard `package.split("/", 1)` in `detection.py`
|
|
15
|
+
`get_system_package_info()` against missing `/` in subprocess output.
|
|
16
|
+
|
|
17
|
+
## Reliability and Correctness
|
|
18
|
+
|
|
19
|
+
- [ ] Handle missing `node` cleanly during prerequisite checks
|
|
20
|
+
(`FileNotFoundError` path in Node version probing).
|
|
21
|
+
- [ ] Read tool version output from both stdout and stderr so status does not
|
|
22
|
+
miss installed versions.
|
|
23
|
+
- [ ] Make version cache writes atomic (write temp file + rename) to avoid
|
|
24
|
+
partial/corrupted `versions.json`. Apply to `save_bundled_versions()` too.
|
|
25
|
+
- [ ] Warn when `versions.json` cache contains invalid JSON instead of silently
|
|
26
|
+
returning empty data.
|
|
27
|
+
- [ ] Use `os.pathsep` instead of hardcoded `":"` in `prereqs.py`
|
|
28
|
+
`check_path_directories()`.
|
|
29
|
+
|
|
30
|
+
## Security and Integrity
|
|
31
|
+
|
|
32
|
+
- [ ] Add integrity verification for direct-download tarballs (not only install
|
|
33
|
+
script SHA256).
|
|
34
|
+
- [ ] Extend tool metadata to support tarball checksum/signature fields where
|
|
35
|
+
applicable.
|
|
36
|
+
|
|
37
|
+
## CLI and Automation UX
|
|
38
|
+
|
|
39
|
+
- [ ] Add `--json` output mode for `list`, `status`, and `update-versions`.
|
|
40
|
+
- [ ] Add a focused `doctor` command for environment checks (PATH,
|
|
41
|
+
prerequisites, command health).
|
|
42
|
+
- [ ] Consider `install --force` for reinstall/repair flows.
|
|
43
|
+
- [ ] Add cleanup support for stale direct-download versions no longer in use.
|
|
44
|
+
- [ ] Expand `upgrade` help text to mention the default "only out-of-date tools"
|
|
45
|
+
behavior.
|
|
46
|
+
|
|
47
|
+
## Documentation
|
|
48
|
+
|
|
49
|
+
- [ ] Document that running `code-aide` with no subcommand defaults to `status`.
|
|
50
|
+
- [ ] Add `install --dryrun` to the README usage examples.
|
|
51
|
+
- [ ] Document environment variables and config file paths
|
|
52
|
+
(`~/.config/code-aide/versions.json`).
|
|
53
|
+
|
|
54
|
+
## Platform and Package Detection
|
|
55
|
+
|
|
56
|
+
- [ ] Broaden system package metadata detection beyond Gentoo-specific tooling
|
|
57
|
+
where practical.
|
|
58
|
+
|
|
59
|
+
## Maintainability and Tests
|
|
60
|
+
|
|
61
|
+
- [ ] Split larger modules into smaller focused files where practical
|
|
62
|
+
(`commands_actions.py`, `versions.py`, `install.py`).
|
|
63
|
+
- [ ] Add tests for major untested functions:
|
|
64
|
+
- `upgrade_tool()` (completely untested)
|
|
65
|
+
- `cmd_remove()` (completely untested)
|
|
66
|
+
- `fetch_url()` (completely untested)
|
|
67
|
+
- `check_prerequisites()` / `install_nodejs_npm()` (completely untested)
|
|
68
|
+
- `save_bundled_versions()` (untested)
|
|
69
|
+
- `get_system_package_info()` (completely untested)
|
|
70
|
+
- [ ] Add tests for prerequisite edge cases and cache write behavior.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "code-aide"
|
|
3
|
-
|
|
3
|
+
dynamic = ["version"]
|
|
4
4
|
description = "Manage AI coding CLI tools (Claude, Copilot, Cursor, Gemini, Amp, Codex)"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -37,6 +37,9 @@ code-aide = "code_aide.__main__:main"
|
|
|
37
37
|
requires = ["hatchling"]
|
|
38
38
|
build-backend = "hatchling.build"
|
|
39
39
|
|
|
40
|
+
[tool.hatch.version]
|
|
41
|
+
path = "src/code_aide/__init__.py"
|
|
42
|
+
|
|
40
43
|
[tool.pytest.ini_options]
|
|
41
44
|
testpaths = ["tests"]
|
|
42
45
|
|
|
@@ -26,6 +26,7 @@ from code_aide.config import (
|
|
|
26
26
|
load_bundled_tools,
|
|
27
27
|
load_versions_cache,
|
|
28
28
|
merge_cached_versions,
|
|
29
|
+
save_bundled_versions,
|
|
29
30
|
save_updated_versions,
|
|
30
31
|
)
|
|
31
32
|
|
|
@@ -233,7 +234,8 @@ def cmd_update_versions(args: argparse.Namespace) -> None:
|
|
|
233
234
|
"""Handle update-versions command: check upstream for latest tool versions."""
|
|
234
235
|
bundled = load_bundled_tools()
|
|
235
236
|
tools = bundled.get("tools", {})
|
|
236
|
-
|
|
237
|
+
if not args.bundled:
|
|
238
|
+
merge_cached_versions(tools, load_versions_cache())
|
|
237
239
|
|
|
238
240
|
config: Dict[str, Any] = {"tools": tools}
|
|
239
241
|
|
|
@@ -290,11 +292,19 @@ def cmd_update_versions(args: argparse.Namespace) -> None:
|
|
|
290
292
|
tool_entry["latest_date"] = date
|
|
291
293
|
version_info_changed = True
|
|
292
294
|
|
|
295
|
+
def _save(tools: dict) -> str:
|
|
296
|
+
"""Save versions to bundled or user cache. Returns description."""
|
|
297
|
+
if args.bundled:
|
|
298
|
+
path = save_bundled_versions(tools)
|
|
299
|
+
return path
|
|
300
|
+
save_updated_versions(tools)
|
|
301
|
+
return "~/.config/code-aide/versions.json"
|
|
302
|
+
|
|
293
303
|
updates = [result for result in results if result["update"]]
|
|
294
304
|
if not updates:
|
|
295
|
-
if version_info_changed and not args.
|
|
296
|
-
|
|
297
|
-
print("Updated latest version info in
|
|
305
|
+
if version_info_changed and not args.dryrun:
|
|
306
|
+
dest = _save(config["tools"])
|
|
307
|
+
print(f"Updated latest version info in {dest}.")
|
|
298
308
|
if version_info_changed:
|
|
299
309
|
print(
|
|
300
310
|
"No installer checksum updates required "
|
|
@@ -313,7 +323,7 @@ def cmd_update_versions(args: argparse.Namespace) -> None:
|
|
|
313
323
|
for result in updates:
|
|
314
324
|
print(f" {result['tool']}: SHA256 changed")
|
|
315
325
|
|
|
316
|
-
if args.
|
|
326
|
+
if args.dryrun:
|
|
317
327
|
print("\nDry run mode - no changes written.")
|
|
318
328
|
return
|
|
319
329
|
|
|
@@ -325,18 +335,16 @@ def cmd_update_versions(args: argparse.Namespace) -> None:
|
|
|
325
335
|
return
|
|
326
336
|
if answer not in ("y", "yes"):
|
|
327
337
|
if version_info_changed:
|
|
328
|
-
|
|
329
|
-
print(
|
|
330
|
-
"Updated latest version info in ~/.config/code-aide/versions.json."
|
|
331
|
-
)
|
|
338
|
+
dest = _save(config["tools"])
|
|
339
|
+
print(f"Updated latest version info in {dest}.")
|
|
332
340
|
else:
|
|
333
341
|
print("No changes made.")
|
|
334
342
|
return
|
|
335
343
|
|
|
336
344
|
updated = apply_sha256_updates(config, results)
|
|
337
|
-
|
|
345
|
+
dest = _save(config["tools"])
|
|
338
346
|
|
|
339
|
-
print(f"\nUpdated {len(updated)} tool(s) in
|
|
347
|
+
print(f"\nUpdated {len(updated)} tool(s) in {dest}:")
|
|
340
348
|
for name in updated:
|
|
341
349
|
print(f" {name}")
|
|
342
350
|
if version_info_changed:
|
|
@@ -103,3 +103,29 @@ def save_updated_versions(tools: dict) -> None:
|
|
|
103
103
|
if entry:
|
|
104
104
|
cache_data["tools"][tool_key] = entry
|
|
105
105
|
save_versions_cache(cache_data)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def save_bundled_versions(tools: dict) -> str:
|
|
109
|
+
"""Update dynamic fields in the bundled data/tools.json.
|
|
110
|
+
|
|
111
|
+
Loads the existing bundled file to preserve static fields, then
|
|
112
|
+
overwrites only the dynamic version fields from the provided tools
|
|
113
|
+
dict. Returns the file path written.
|
|
114
|
+
"""
|
|
115
|
+
ref = importlib.resources.files("code_aide").joinpath("data/tools.json")
|
|
116
|
+
path = str(ref)
|
|
117
|
+
|
|
118
|
+
with open(path, encoding="utf-8") as f:
|
|
119
|
+
bundled = json.load(f)
|
|
120
|
+
|
|
121
|
+
for tool_key, tool_data in tools.items():
|
|
122
|
+
if tool_key in bundled.get("tools", {}):
|
|
123
|
+
for field in DYNAMIC_FIELDS:
|
|
124
|
+
if field in tool_data:
|
|
125
|
+
bundled["tools"][tool_key][field] = tool_data[field]
|
|
126
|
+
|
|
127
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
128
|
+
json.dump(bundled, f, indent=2)
|
|
129
|
+
f.write("\n")
|
|
130
|
+
|
|
131
|
+
return path
|
|
@@ -30,8 +30,13 @@
|
|
|
30
30
|
"command": "claude",
|
|
31
31
|
"install_type": "self_managed",
|
|
32
32
|
"npm_package": "@anthropic-ai/claude-code",
|
|
33
|
-
"upgrade_command": [
|
|
34
|
-
|
|
33
|
+
"upgrade_command": [
|
|
34
|
+
"claude",
|
|
35
|
+
"upgrade"
|
|
36
|
+
],
|
|
37
|
+
"prerequisites": [
|
|
38
|
+
"npm"
|
|
39
|
+
],
|
|
35
40
|
"min_node_version": null,
|
|
36
41
|
"next_steps": "Run 'claude' and then use '/login' to authenticate",
|
|
37
42
|
"version_args": [
|
|
@@ -39,8 +44,8 @@
|
|
|
39
44
|
],
|
|
40
45
|
"docs_url": "https://docs.anthropic.com/en/docs/build-with-claude/claude-code",
|
|
41
46
|
"default_install": true,
|
|
42
|
-
"latest_version": "2.1.
|
|
43
|
-
"latest_date": "2026-02-
|
|
47
|
+
"latest_version": "2.1.63",
|
|
48
|
+
"latest_date": "2026-02-28"
|
|
44
49
|
},
|
|
45
50
|
"gemini": {
|
|
46
51
|
"name": "Gemini CLI",
|
|
@@ -57,7 +62,7 @@
|
|
|
57
62
|
],
|
|
58
63
|
"docs_url": "https://github.com/google-gemini/gemini-cli",
|
|
59
64
|
"default_install": true,
|
|
60
|
-
"latest_version": "0.
|
|
65
|
+
"latest_version": "0.31.0",
|
|
61
66
|
"latest_date": "2026-02-27"
|
|
62
67
|
},
|
|
63
68
|
"amp": {
|
|
@@ -75,8 +80,8 @@
|
|
|
75
80
|
],
|
|
76
81
|
"docs_url": "https://ampcode.com/manual",
|
|
77
82
|
"default_install": false,
|
|
78
|
-
"latest_version": "0.0.
|
|
79
|
-
"latest_date": "2026-02-
|
|
83
|
+
"latest_version": "0.0.1772308894-g06c525",
|
|
84
|
+
"latest_date": "2026-02-28"
|
|
80
85
|
},
|
|
81
86
|
"codex": {
|
|
82
87
|
"name": "Codex CLI",
|
|
@@ -105,6 +105,9 @@ def get_system_package_info(binary_path: str) -> Dict[str, Optional[str]]:
|
|
|
105
105
|
except Exception:
|
|
106
106
|
pass
|
|
107
107
|
|
|
108
|
+
if "/" not in package:
|
|
109
|
+
return result
|
|
110
|
+
|
|
108
111
|
category, package_name = package.split("/", 1)
|
|
109
112
|
ebuild_dirs = globmod.glob(f"/var/db/repos/*/{category}/{package_name}/")
|
|
110
113
|
ebuilds = []
|
|
@@ -84,7 +84,7 @@ def main() -> None:
|
|
|
84
84
|
)
|
|
85
85
|
update_versions_parser.add_argument(
|
|
86
86
|
"-n",
|
|
87
|
-
"--
|
|
87
|
+
"--dryrun",
|
|
88
88
|
action="store_true",
|
|
89
89
|
help="Show changes only, do not write updates",
|
|
90
90
|
)
|
|
@@ -100,6 +100,12 @@ def main() -> None:
|
|
|
100
100
|
action="store_true",
|
|
101
101
|
help="Show full SHA256 hashes",
|
|
102
102
|
)
|
|
103
|
+
update_versions_parser.add_argument(
|
|
104
|
+
"-b",
|
|
105
|
+
"--bundled",
|
|
106
|
+
action="store_true",
|
|
107
|
+
help="Update bundled data/tools.json instead of user cache (developer use)",
|
|
108
|
+
)
|
|
103
109
|
update_versions_parser.set_defaults(func=cmd_update_versions)
|
|
104
110
|
|
|
105
111
|
args = parser.parse_args()
|
|
@@ -7,6 +7,7 @@ import platform
|
|
|
7
7
|
import shutil
|
|
8
8
|
import subprocess
|
|
9
9
|
import tarfile
|
|
10
|
+
import tempfile
|
|
10
11
|
from typing import Any, Dict, Optional
|
|
11
12
|
|
|
12
13
|
from code_aide.constants import TOOLS
|
|
@@ -162,8 +163,11 @@ def install_direct_download(
|
|
|
162
163
|
tarball_data, _ = fetch_url(download_url, timeout=120)
|
|
163
164
|
success(f"Downloaded {len(tarball_data)} bytes")
|
|
164
165
|
|
|
165
|
-
|
|
166
|
-
os.makedirs(
|
|
166
|
+
install_parent = os.path.dirname(install_dir) or "."
|
|
167
|
+
os.makedirs(install_parent, exist_ok=True)
|
|
168
|
+
temp_dir = tempfile.mkdtemp(
|
|
169
|
+
prefix=os.path.basename(install_dir) + ".tmp-", dir=install_parent
|
|
170
|
+
)
|
|
167
171
|
|
|
168
172
|
try:
|
|
169
173
|
with tarfile.open(fileobj=io.BytesIO(tarball_data), mode="r:gz") as tf:
|
|
@@ -275,6 +279,7 @@ def install_tool(tool_name: str, dryrun: bool = False) -> bool:
|
|
|
275
279
|
if dryrun:
|
|
276
280
|
success(f"{tool_config['name']} verification passed")
|
|
277
281
|
else:
|
|
282
|
+
success(f"{tool_config['name']} installed successfully")
|
|
278
283
|
info(tool_config["next_steps"])
|
|
279
284
|
if "docs_url" in tool_config:
|
|
280
285
|
info(f"Documentation: {tool_config['docs_url']}")
|
|
@@ -57,7 +57,13 @@ class TestCmdUpdateVersions(unittest.TestCase):
|
|
|
57
57
|
args = type(
|
|
58
58
|
"Args",
|
|
59
59
|
(),
|
|
60
|
-
{
|
|
60
|
+
{
|
|
61
|
+
"tools": ["missing"],
|
|
62
|
+
"dryrun": False,
|
|
63
|
+
"yes": False,
|
|
64
|
+
"verbose": False,
|
|
65
|
+
"bundled": False,
|
|
66
|
+
},
|
|
61
67
|
)()
|
|
62
68
|
with mock.patch.object(
|
|
63
69
|
commands_actions,
|
|
@@ -73,7 +79,13 @@ class TestCmdUpdateVersions(unittest.TestCase):
|
|
|
73
79
|
args = type(
|
|
74
80
|
"Args",
|
|
75
81
|
(),
|
|
76
|
-
{
|
|
82
|
+
{
|
|
83
|
+
"tools": [],
|
|
84
|
+
"dryrun": True,
|
|
85
|
+
"yes": False,
|
|
86
|
+
"verbose": False,
|
|
87
|
+
"bundled": False,
|
|
88
|
+
},
|
|
77
89
|
)()
|
|
78
90
|
with (
|
|
79
91
|
mock.patch.object(
|
|
@@ -113,6 +125,68 @@ class TestCmdUpdateVersions(unittest.TestCase):
|
|
|
113
125
|
mock_save.assert_not_called()
|
|
114
126
|
|
|
115
127
|
|
|
128
|
+
def test_bundled_flag_skips_cache_and_saves_to_bundled(self):
|
|
129
|
+
args = type(
|
|
130
|
+
"Args",
|
|
131
|
+
(),
|
|
132
|
+
{
|
|
133
|
+
"tools": [],
|
|
134
|
+
"dryrun": False,
|
|
135
|
+
"yes": True,
|
|
136
|
+
"verbose": False,
|
|
137
|
+
"bundled": True,
|
|
138
|
+
},
|
|
139
|
+
)()
|
|
140
|
+
bundled_tools = {
|
|
141
|
+
"tools": {
|
|
142
|
+
"ok": {
|
|
143
|
+
"install_type": "npm",
|
|
144
|
+
"npm_package": "pkg",
|
|
145
|
+
"latest_version": "1.0.0",
|
|
146
|
+
"latest_date": "2026-01-01",
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
with (
|
|
151
|
+
mock.patch.object(
|
|
152
|
+
commands_actions,
|
|
153
|
+
"load_bundled_tools",
|
|
154
|
+
return_value=bundled_tools,
|
|
155
|
+
),
|
|
156
|
+
mock.patch.object(
|
|
157
|
+
commands_actions, "load_versions_cache", return_value={}
|
|
158
|
+
) as mock_cache,
|
|
159
|
+
mock.patch.object(
|
|
160
|
+
commands_actions, "merge_cached_versions"
|
|
161
|
+
) as mock_merge,
|
|
162
|
+
mock.patch.object(
|
|
163
|
+
commands_actions,
|
|
164
|
+
"check_npm_tool",
|
|
165
|
+
return_value={
|
|
166
|
+
"tool": "ok",
|
|
167
|
+
"type": "npm",
|
|
168
|
+
"version": "2.0.0",
|
|
169
|
+
"date": "2026-02-01",
|
|
170
|
+
"status": "ok",
|
|
171
|
+
"update": None,
|
|
172
|
+
},
|
|
173
|
+
),
|
|
174
|
+
mock.patch.object(commands_actions, "print_check_results_table"),
|
|
175
|
+
mock.patch.object(commands_actions, "save_updated_versions") as mock_save,
|
|
176
|
+
mock.patch.object(
|
|
177
|
+
commands_actions, "save_bundled_versions", return_value="/fake/path"
|
|
178
|
+
) as mock_bundled,
|
|
179
|
+
):
|
|
180
|
+
buf = io.StringIO()
|
|
181
|
+
with contextlib.redirect_stdout(buf):
|
|
182
|
+
commands_actions.cmd_update_versions(args)
|
|
183
|
+
|
|
184
|
+
mock_cache.assert_not_called()
|
|
185
|
+
mock_merge.assert_not_called()
|
|
186
|
+
mock_save.assert_not_called()
|
|
187
|
+
mock_bundled.assert_called_once()
|
|
188
|
+
|
|
189
|
+
|
|
116
190
|
class TestUpgradeNoArgsParsing(unittest.TestCase):
|
|
117
191
|
"""Test that 'code-aide upgrade' with no arguments parses successfully."""
|
|
118
192
|
|
|
@@ -122,3 +122,57 @@ class TestInstallDirectDownloadDryrun(unittest.TestCase):
|
|
|
122
122
|
|
|
123
123
|
result = cli_install.install_direct_download("test", tool_config, dryrun=True)
|
|
124
124
|
self.assertFalse(result)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class TestInstallDirectDownload(unittest.TestCase):
|
|
128
|
+
"""Tests for install_direct_download in non-dryrun mode."""
|
|
129
|
+
|
|
130
|
+
@mock.patch.object(cli_install, "detect_os_arch", return_value=("linux", "x64"))
|
|
131
|
+
@mock.patch.object(cli_install, "fetch_url")
|
|
132
|
+
def test_creates_missing_install_parent_directory(self, mock_fetch, mock_os_arch):
|
|
133
|
+
script_content = b"echo install"
|
|
134
|
+
expected_sha256 = cli_install.hashlib.sha256(script_content).hexdigest()
|
|
135
|
+
|
|
136
|
+
tarball_data = io.BytesIO()
|
|
137
|
+
with tarfile.open(fileobj=tarball_data, mode="w:gz") as tf:
|
|
138
|
+
payload = b"#!/bin/sh\necho ok\n"
|
|
139
|
+
info = tarfile.TarInfo("package/test-bin")
|
|
140
|
+
info.mode = 0o755
|
|
141
|
+
info.size = len(payload)
|
|
142
|
+
tf.addfile(info, io.BytesIO(payload))
|
|
143
|
+
tarball_bytes = tarball_data.getvalue()
|
|
144
|
+
|
|
145
|
+
def _fake_fetch(url, timeout=30):
|
|
146
|
+
if url.endswith("/install"):
|
|
147
|
+
return script_content, None
|
|
148
|
+
return tarball_bytes, None
|
|
149
|
+
|
|
150
|
+
mock_fetch.side_effect = _fake_fetch
|
|
151
|
+
|
|
152
|
+
tool_config = {
|
|
153
|
+
"name": "Test Tool",
|
|
154
|
+
"install_url": "https://example.com/install",
|
|
155
|
+
"install_sha256": expected_sha256,
|
|
156
|
+
"download_url_template": "https://example.com/{version}/{os}/{arch}/pkg.tar.gz",
|
|
157
|
+
"install_dir": "/tmp/test-{version}",
|
|
158
|
+
"bin_dir": "/tmp/test-bin",
|
|
159
|
+
"symlinks": {"test": "test-bin"},
|
|
160
|
+
"latest_version": "1.0.0",
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
with tempfile.TemporaryDirectory() as td:
|
|
164
|
+
install_dir = os.path.join(td, "missing", "versions", "1.0.0")
|
|
165
|
+
bin_dir = os.path.join(td, "bin")
|
|
166
|
+
|
|
167
|
+
result = cli_install.install_direct_download(
|
|
168
|
+
"test",
|
|
169
|
+
tool_config,
|
|
170
|
+
dryrun=False,
|
|
171
|
+
install_dir_override=install_dir,
|
|
172
|
+
bin_dir_override=bin_dir,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
self.assertTrue(result)
|
|
176
|
+
self.assertTrue(os.path.isdir(os.path.dirname(install_dir)))
|
|
177
|
+
self.assertTrue(os.path.exists(os.path.join(install_dir, "test-bin")))
|
|
178
|
+
self.assertTrue(os.path.islink(os.path.join(bin_dir, "test")))
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
name: Publish to PyPI
|
|
2
|
-
on:
|
|
3
|
-
push:
|
|
4
|
-
tags: ["v*"]
|
|
5
|
-
jobs:
|
|
6
|
-
publish:
|
|
7
|
-
runs-on: ubuntu-latest
|
|
8
|
-
permissions:
|
|
9
|
-
contents: write
|
|
10
|
-
id-token: write
|
|
11
|
-
steps:
|
|
12
|
-
- uses: actions/checkout@v4
|
|
13
|
-
- uses: astral-sh/setup-uv@v5
|
|
14
|
-
- run: uv build
|
|
15
|
-
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
16
|
-
- name: Create GitHub release notes
|
|
17
|
-
uses: softprops/action-gh-release@v2
|
|
18
|
-
with:
|
|
19
|
-
tag_name: ${{ github.ref_name }}
|
|
20
|
-
generate_release_notes: true
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|