code-aide 1.10.0__tar.gz → 1.11.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.10.0 → code_aide-1.11.0}/.pre-commit-config.yaml +2 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/AGENTS.md +12 -2
- {code_aide-1.10.0 → code_aide-1.11.0}/PKG-INFO +2 -1
- code_aide-1.11.0/TODO.md +80 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/pyproject.toml +1 -0
- code_aide-1.11.0/script-archive/README.md +36 -0
- code_aide-1.11.0/script-archive/amp-install.sh +487 -0
- code_aide-1.11.0/script-archive/claude-install.sh +158 -0
- code_aide-1.11.0/script-archive/cursor-install.sh +204 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/__init__.py +1 -1
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/commands_tools.py +29 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/constants.py +7 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/data/tools.json +10 -5
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/detection.py +77 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/install.py +23 -1
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/install_types.py +3 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/operations.py +23 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/prereqs.py +7 -1
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/status.py +44 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_detection.py +126 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_install.py +54 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_install_types.py +9 -0
- code_aide-1.10.0/TODO.md +0 -71
- {code_aide-1.10.0 → code_aide-1.11.0}/.github/workflows/ci.yml +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/.github/workflows/publish.yml +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/.gitignore +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/.gitlab-ci.yml +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/CLAUDE.md +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/LICENSE +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/README.md +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/specs/auto-migrate-deprecated-installs.md +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/specs/claude-native-installer-migration.md +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/specs/missing-coding-llm-cli-tools.md +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/specs/pre-commit-uv-setup.md +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/specs/remove-bundled-version-baseline.md +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/specs/unify-upgrade-eligibility-with-shared-evaluator.md +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/__main__.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/commands_actions.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/config.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/console.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/entry.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/versions.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_commands_actions.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_commands_tools.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_config.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_console.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_constants.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_operations.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_status.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_versions.py +0 -0
- {code_aide-1.10.0 → code_aide-1.11.0}/uv.lock +0 -0
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
# AGENTS.md - AI Coding Agent Instructions
|
|
2
2
|
|
|
3
|
+
- Always use `uv run` to run python code and scripts.
|
|
4
|
+
- Run tests with `uv run pytest`.
|
|
5
|
+
|
|
3
6
|
## Key Design Decisions
|
|
4
7
|
|
|
5
|
-
-
|
|
8
|
+
- Runtime dependencies are stdlib only (`dependencies` in pyproject.toml).
|
|
9
|
+
Dev tooling (pytest, black, ruff) lives in `dependency-groups` for tests
|
|
10
|
+
and linting.
|
|
11
|
+
- Primary supported environments are macOS and Linux (see pyproject
|
|
12
|
+
classifiers); avoid assuming Windows-specific behavior unless explicitly
|
|
13
|
+
in scope.
|
|
14
|
+
- For planned behavior or refactors, read matching notes under `specs/`
|
|
15
|
+
before inventing design.
|
|
6
16
|
- Two-layer version data: bundled definitions (with SHA256 checksums),
|
|
7
17
|
user's local cache (~/.config/code-aide/versions.json)
|
|
8
18
|
- All tests should pass before committing
|
|
9
19
|
- pre-commit runs black and ruff on commit; run `black` to format and then
|
|
10
|
-
|
|
20
|
+
`pre-commit run --all-files` to check before committing
|
|
11
21
|
- When changing pyproject.toml dependencies, run `uv lock` and commit
|
|
12
22
|
uv.lock
|
|
13
23
|
- Write useful commit messages: start subjects with past-tense action verbs
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-aide
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.11.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
|
|
@@ -14,6 +14,7 @@ Classifier: Environment :: Console
|
|
|
14
14
|
Classifier: Intended Audience :: Developers
|
|
15
15
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
16
|
Classifier: Operating System :: MacOS
|
|
17
|
+
Classifier: Operating System :: POSIX :: BSD :: FreeBSD
|
|
17
18
|
Classifier: Operating System :: POSIX :: Linux
|
|
18
19
|
Classifier: Programming Language :: Python :: 3
|
|
19
20
|
Classifier: Programming Language :: Python :: 3.11
|
code_aide-1.11.0/TODO.md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
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 | Med | Low | Unhandled `FileNotFoundError` crashes prereq checks |
|
|
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 | Med-High | Low | Process crash can corrupt `versions.json` |
|
|
13
|
+
| 4 | Warn on invalid cache JSON | Low | Low | Silent data loss; user sees empty versions |
|
|
14
|
+
| 5 | Use `os.pathsep` not `":"` | High | Trivial | Breaks PATH checks on Windows entirely |
|
|
15
|
+
| 6 | Verify direct-download tarballs | High | Med | No integrity check before extracting; MITM risk |
|
|
16
|
+
| 7 | Tarball checksum metadata fields | High | Low | Prerequisite for item 6 |
|
|
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 | `cmd_remove`, `fetch_url`, prereqs, detection |
|
|
27
|
+
| 18 | Tests for edge cases | Med | Med | Cache corruption, PATH oddities, version parsing |
|
|
28
|
+
|
|
29
|
+
## Reliability and Correctness
|
|
30
|
+
|
|
31
|
+
- [ ] Handle missing `node` cleanly during prerequisite checks
|
|
32
|
+
(`FileNotFoundError` path in Node version probing).
|
|
33
|
+
- [ ] Read tool version output from both stdout and stderr so status does
|
|
34
|
+
not miss installed versions.
|
|
35
|
+
- [ ] Make version cache writes atomic (write temp file + rename) to avoid
|
|
36
|
+
partial/corrupted `versions.json`.
|
|
37
|
+
- [ ] Warn when `versions.json` cache contains invalid JSON instead of
|
|
38
|
+
silently returning empty data.
|
|
39
|
+
- [ ] Use `os.pathsep` instead of hardcoded `":"` in `prereqs.py`
|
|
40
|
+
`check_path_directories()`.
|
|
41
|
+
|
|
42
|
+
## Security and Integrity
|
|
43
|
+
|
|
44
|
+
- [ ] Add integrity verification for direct-download tarballs (not only
|
|
45
|
+
install script SHA256).
|
|
46
|
+
- [ ] Extend tool metadata to support tarball checksum/signature fields
|
|
47
|
+
where applicable.
|
|
48
|
+
|
|
49
|
+
## CLI and Automation UX
|
|
50
|
+
|
|
51
|
+
- [ ] Add `--json` output mode for `list`, `status`, and `update-versions`.
|
|
52
|
+
- [ ] Add a focused `doctor` command for environment checks (PATH,
|
|
53
|
+
prerequisites, command health).
|
|
54
|
+
- [ ] Consider `install --force` for reinstall/repair flows.
|
|
55
|
+
- [ ] Add cleanup support for stale direct-download versions no longer in
|
|
56
|
+
use.
|
|
57
|
+
|
|
58
|
+
## Documentation
|
|
59
|
+
|
|
60
|
+
- [ ] Document that running `code-aide` with no subcommand defaults to
|
|
61
|
+
`status`.
|
|
62
|
+
- [ ] Add `install --dryrun` to the README usage examples.
|
|
63
|
+
- [ ] Document environment variables and config file paths
|
|
64
|
+
(`~/.config/code-aide/versions.json`).
|
|
65
|
+
|
|
66
|
+
## Platform and Package Detection
|
|
67
|
+
|
|
68
|
+
- [ ] Broaden system package metadata detection beyond Gentoo-specific
|
|
69
|
+
tooling where practical.
|
|
70
|
+
|
|
71
|
+
## Maintainability and Tests
|
|
72
|
+
|
|
73
|
+
- [ ] Split larger modules into smaller focused files where practical
|
|
74
|
+
(`commands_actions.py`, `versions.py`, `install.py`).
|
|
75
|
+
- [ ] Add tests for major untested functions:
|
|
76
|
+
- `cmd_remove()` (completely untested)
|
|
77
|
+
- `fetch_url()` (completely untested)
|
|
78
|
+
- `check_prerequisites()` / `install_nodejs_npm()` (completely untested)
|
|
79
|
+
- `get_system_package_info()` (completely untested)
|
|
80
|
+
- [ ] Add tests for prerequisite edge cases and cache write behavior.
|
|
@@ -16,6 +16,7 @@ classifiers = [
|
|
|
16
16
|
"License :: OSI Approved :: Apache Software License",
|
|
17
17
|
"Operating System :: MacOS",
|
|
18
18
|
"Operating System :: POSIX :: Linux",
|
|
19
|
+
"Operating System :: POSIX :: BSD :: FreeBSD",
|
|
19
20
|
"Programming Language :: Python :: 3",
|
|
20
21
|
"Programming Language :: Python :: 3.11",
|
|
21
22
|
"Programming Language :: Python :: 3.12",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Script Archive
|
|
2
|
+
|
|
3
|
+
This directory contains archived copies of third-party install scripts used
|
|
4
|
+
by code-aide. These are committed so that when an upstream script changes
|
|
5
|
+
(detected by SHA256 mismatch during upgrade), you can diff the new version
|
|
6
|
+
against the known-good archived copy.
|
|
7
|
+
|
|
8
|
+
## When a SHA256 verification fails
|
|
9
|
+
|
|
10
|
+
1. Download the new script to a temp file:
|
|
11
|
+
|
|
12
|
+
curl -fsSL <install_url> -o /tmp/new-script.sh
|
|
13
|
+
|
|
14
|
+
2. Diff against the archived version:
|
|
15
|
+
|
|
16
|
+
diff script-archive/<tool>-install.sh /tmp/new-script.sh
|
|
17
|
+
|
|
18
|
+
3. Review the changes for anything suspicious.
|
|
19
|
+
|
|
20
|
+
4. If the script is safe, update both the archive and the hash:
|
|
21
|
+
|
|
22
|
+
cp /tmp/new-script.sh script-archive/<tool>-install.sh
|
|
23
|
+
# Update install_sha256 in src/code_aide/data/tools.json
|
|
24
|
+
|
|
25
|
+
5. Commit both changes together.
|
|
26
|
+
|
|
27
|
+
## Tool metadata
|
|
28
|
+
|
|
29
|
+
See `src/code_aide/data/tools.json` for install URLs, SHA256 hashes, and
|
|
30
|
+
per-tool notes (e.g. legacy script status).
|
|
31
|
+
|
|
32
|
+
## Pre-commit hooks
|
|
33
|
+
|
|
34
|
+
The `.pre-commit-config.yaml` excludes `script-archive/` from
|
|
35
|
+
`trailing-whitespace` and `end-of-file-fixer` hooks so that archived scripts
|
|
36
|
+
retain their exact upstream bytes.
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Amp CLI Binary Installation Script (EXPERIMENTAL - NOT DOCUMENTED)
|
|
5
|
+
# This is a secondary install script for testing binary distribution
|
|
6
|
+
# Downloads pre-compiled Amp CLI binary instead of using npm
|
|
7
|
+
|
|
8
|
+
# Configuration
|
|
9
|
+
AMP_HOME="${AMP_HOME:-$HOME/.amp}"
|
|
10
|
+
BIN_DIR="$AMP_HOME/bin"
|
|
11
|
+
STORAGE_BASE="https://static.ampcode.com"
|
|
12
|
+
AMP_URL="${AMP_URL:-https://ampcode.com}"
|
|
13
|
+
|
|
14
|
+
# Colors for output
|
|
15
|
+
RED='\033[0;31m'
|
|
16
|
+
GREEN='\033[0;32m'
|
|
17
|
+
YELLOW='\033[1;33m'
|
|
18
|
+
BLUE='\033[0;34m'
|
|
19
|
+
NC='\033[0m' # No Color
|
|
20
|
+
|
|
21
|
+
# Cleanup on interrupt
|
|
22
|
+
cleanup() {
|
|
23
|
+
echo -e "\n${YELLOW}Installation interrupted...${NC}"
|
|
24
|
+
rm -f "$AMP_HOME/amp-install-"* 2>/dev/null || true
|
|
25
|
+
exit 1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
trap cleanup INT TERM
|
|
29
|
+
|
|
30
|
+
log() {
|
|
31
|
+
echo -e "${BLUE}[INFO]${NC} $1" >&2
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
warn() {
|
|
35
|
+
echo -e "${YELLOW}[WARN]${NC} $1" >&2
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
error() {
|
|
39
|
+
echo -e "${RED}[ERROR]${NC} $1" >&2
|
|
40
|
+
exit 1
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
success() {
|
|
44
|
+
echo -e "${GREEN}[SUCCESS]${NC} $1" >&2
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Check if command exists
|
|
48
|
+
command_exists() {
|
|
49
|
+
command -v "$1" >/dev/null 2>&1
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# Require command to exist or exit with error
|
|
53
|
+
need_cmd() {
|
|
54
|
+
if ! command_exists "$1"; then
|
|
55
|
+
error "need '$1' (command not found)"
|
|
56
|
+
fi
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Check all prerequisite commands upfront
|
|
60
|
+
check_prereqs() {
|
|
61
|
+
for cmd in uname mktemp chmod mkdir rm; do
|
|
62
|
+
need_cmd "$cmd"
|
|
63
|
+
done
|
|
64
|
+
|
|
65
|
+
# Check for sha256 checksum command (shasum on macOS/BSD, sha256sum on Linux)
|
|
66
|
+
if command_exists shasum; then
|
|
67
|
+
SHA256_CMD="shasum -a 256"
|
|
68
|
+
elif command_exists sha256sum; then
|
|
69
|
+
SHA256_CMD="sha256sum"
|
|
70
|
+
else
|
|
71
|
+
error "need 'shasum' or 'sha256sum' (command not found)"
|
|
72
|
+
fi
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Detect target platform for CLI binary
|
|
76
|
+
detect_platform() {
|
|
77
|
+
local platform
|
|
78
|
+
platform="$(uname -s) $(uname -m)"
|
|
79
|
+
|
|
80
|
+
case $platform in
|
|
81
|
+
'Darwin x86_64')
|
|
82
|
+
target=darwin-x64
|
|
83
|
+
;;
|
|
84
|
+
'Darwin arm64')
|
|
85
|
+
target=darwin-arm64
|
|
86
|
+
;;
|
|
87
|
+
'Linux aarch64' | 'Linux arm64')
|
|
88
|
+
target=linux-arm64
|
|
89
|
+
;;
|
|
90
|
+
'MINGW64'*)
|
|
91
|
+
target=windows-x64
|
|
92
|
+
;;
|
|
93
|
+
'Linux riscv64')
|
|
94
|
+
error 'Not supported on riscv64'
|
|
95
|
+
;;
|
|
96
|
+
'Linux x86_64' | *)
|
|
97
|
+
target=linux-x64
|
|
98
|
+
;;
|
|
99
|
+
esac
|
|
100
|
+
|
|
101
|
+
# Check for baseline builds on x64
|
|
102
|
+
case "$target" in
|
|
103
|
+
'darwin-x64')
|
|
104
|
+
# Check Rosetta 2
|
|
105
|
+
if [[ $(sysctl -n sysctl.proc_translated 2>/dev/null) = 1 ]]; then
|
|
106
|
+
target=darwin-arm64
|
|
107
|
+
log "Your shell is running in Rosetta 2. Using $target instead"
|
|
108
|
+
elif [[ $(sysctl -a 2>/dev/null | grep machdep.cpu | grep AVX2) == '' ]]; then
|
|
109
|
+
target="darwin-x64-baseline"
|
|
110
|
+
fi
|
|
111
|
+
;;
|
|
112
|
+
'linux-x64')
|
|
113
|
+
# Check AVX2 support
|
|
114
|
+
if [[ $(grep avx2 /proc/cpuinfo 2>/dev/null) = '' ]]; then
|
|
115
|
+
target="linux-x64-baseline"
|
|
116
|
+
fi
|
|
117
|
+
;;
|
|
118
|
+
'windows-x64')
|
|
119
|
+
# For Windows, default to baseline for better compatibility
|
|
120
|
+
target="windows-x64-baseline"
|
|
121
|
+
;;
|
|
122
|
+
esac
|
|
123
|
+
|
|
124
|
+
echo "$target"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
# Robust downloader that handles snap curl issues
|
|
128
|
+
downloader() {
|
|
129
|
+
local url="$1"
|
|
130
|
+
local output_file="$2"
|
|
131
|
+
|
|
132
|
+
# Check if we have a broken snap curl
|
|
133
|
+
local snap_curl=0
|
|
134
|
+
if command_exists curl; then
|
|
135
|
+
local curl_path
|
|
136
|
+
curl_path=$(command -v curl)
|
|
137
|
+
if [[ "$curl_path" == *"/snap/"* ]]; then
|
|
138
|
+
snap_curl=1
|
|
139
|
+
fi
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
# Check if we have a working (non-snap) curl
|
|
143
|
+
if command_exists curl && [[ $snap_curl -eq 0 ]]; then
|
|
144
|
+
curl -fsSL "$url" -o "$output_file"
|
|
145
|
+
# Try wget for both no curl and the broken snap curl
|
|
146
|
+
elif command_exists wget; then
|
|
147
|
+
wget -q --show-progress "$url" -O "$output_file"
|
|
148
|
+
# If we can't fall back from broken snap curl to wget, report the broken snap curl
|
|
149
|
+
elif [[ $snap_curl -eq 1 ]]; then
|
|
150
|
+
error "curl installed with snap cannot download files due to missing permissions. Please uninstall it and reinstall curl with a different package manager (e.g., apt)."
|
|
151
|
+
else
|
|
152
|
+
error "Neither curl nor wget found. Please install one of them."
|
|
153
|
+
fi
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# Download file with progress
|
|
157
|
+
download_file() {
|
|
158
|
+
local url="$1"
|
|
159
|
+
local output_file="$2"
|
|
160
|
+
|
|
161
|
+
log "Downloading $(basename "$output_file")..."
|
|
162
|
+
|
|
163
|
+
# Use secure temporary file
|
|
164
|
+
local temp_file
|
|
165
|
+
temp_file=$(mktemp "$(dirname "$output_file")/tmp.XXXXXX")
|
|
166
|
+
|
|
167
|
+
# Download to temp file first, then atomic move
|
|
168
|
+
downloader "$url" "$temp_file"
|
|
169
|
+
mv "$temp_file" "$output_file"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# Verify SHA256 checksum
|
|
173
|
+
verify_checksum() {
|
|
174
|
+
local file="$1"
|
|
175
|
+
local expected_checksum="$2"
|
|
176
|
+
|
|
177
|
+
log "Verifying checksum..."
|
|
178
|
+
|
|
179
|
+
local actual_checksum
|
|
180
|
+
actual_checksum=$($SHA256_CMD "$file" | cut -d' ' -f1)
|
|
181
|
+
|
|
182
|
+
if [[ "$actual_checksum" != "$expected_checksum" ]]; then
|
|
183
|
+
error "Checksum verification failed!\nExpected: $expected_checksum\nActual: $actual_checksum"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
success "Checksum verified"
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# Verify signature using minisign
|
|
190
|
+
verify_signature() {
|
|
191
|
+
local file="$1"
|
|
192
|
+
local signature_url="$2"
|
|
193
|
+
|
|
194
|
+
if ! command_exists minisign; then
|
|
195
|
+
log "minisign not installed, skipping signature verification"
|
|
196
|
+
return 0
|
|
197
|
+
fi
|
|
198
|
+
|
|
199
|
+
local signature_path="$AMP_HOME/amp-install-signature.minisign"
|
|
200
|
+
local pubkey_path="$AMP_HOME/signing-key.pub"
|
|
201
|
+
|
|
202
|
+
download_file "$signature_url" "$signature_path"
|
|
203
|
+
download_file "$AMP_URL/.well-known/signing-key.pub" "$pubkey_path"
|
|
204
|
+
|
|
205
|
+
if ! minisign -Vm "$file" -x "$signature_path" -p "$pubkey_path" >/dev/null 2>&1; then
|
|
206
|
+
rm -f "$signature_path" "$pubkey_path"
|
|
207
|
+
error "Signature verification failed! The binary may have been tampered with."
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
rm -f "$signature_path" "$pubkey_path"
|
|
211
|
+
success "Signature verified"
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
# Fetch latest CLI version
|
|
215
|
+
fetch_latest_version() {
|
|
216
|
+
local version_url="$STORAGE_BASE/cli/cli-version.txt"
|
|
217
|
+
local version_path="$AMP_HOME/amp-install-version.txt"
|
|
218
|
+
|
|
219
|
+
download_file "$version_url" "$version_path"
|
|
220
|
+
cat "$version_path"
|
|
221
|
+
rm -f "$version_path"
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# Install Amp CLI binary
|
|
225
|
+
install_amp_binary() {
|
|
226
|
+
local platform="$1"
|
|
227
|
+
local binary_name="amp"
|
|
228
|
+
|
|
229
|
+
# Windows uses .exe extension
|
|
230
|
+
if [[ "$platform" == *"windows"* ]]; then
|
|
231
|
+
binary_name="amp.exe"
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
# Fetch version first to ensure consistent downloads (avoids race conditions
|
|
235
|
+
# where a new version is published mid-download)
|
|
236
|
+
log "Fetching latest version..."
|
|
237
|
+
local version
|
|
238
|
+
version=$(fetch_latest_version)
|
|
239
|
+
log "Installing version: $version"
|
|
240
|
+
|
|
241
|
+
# Use versioned URLs if available, fall back to root for backwards compatibility
|
|
242
|
+
local binary_url="$STORAGE_BASE/cli/${version}/amp-${platform}"
|
|
243
|
+
local checksum_url="$STORAGE_BASE/cli/${version}/${platform}-amp.sha256"
|
|
244
|
+
local minisign_signature_url="$binary_url.minisig"
|
|
245
|
+
|
|
246
|
+
# Check if versioned path exists, fall back to root path
|
|
247
|
+
if ! curl -fsSL --head "$checksum_url" >/dev/null 2>&1; then
|
|
248
|
+
binary_url="$STORAGE_BASE/cli/amp-${platform}"
|
|
249
|
+
checksum_url="$STORAGE_BASE/cli/${platform}-amp.sha256"
|
|
250
|
+
minisign_signature_url="$binary_url.minisig"
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# Add .exe for Windows downloads
|
|
254
|
+
if [[ "$platform" == *"windows"* ]]; then
|
|
255
|
+
binary_url="${binary_url}.exe"
|
|
256
|
+
fi
|
|
257
|
+
|
|
258
|
+
local binary_path="$BIN_DIR/$binary_name"
|
|
259
|
+
local checksum_path="$AMP_HOME/amp-install-checksum.txt"
|
|
260
|
+
|
|
261
|
+
# Download checksum first
|
|
262
|
+
download_file "$checksum_url" "$checksum_path"
|
|
263
|
+
local expected_checksum
|
|
264
|
+
expected_checksum=$(cat "$checksum_path")
|
|
265
|
+
|
|
266
|
+
# Download binary
|
|
267
|
+
download_file "$binary_url" "$binary_path"
|
|
268
|
+
|
|
269
|
+
# Verify checksum
|
|
270
|
+
verify_checksum "$binary_path" "$expected_checksum"
|
|
271
|
+
|
|
272
|
+
# Verify signature (optional, only if minisign is installed)
|
|
273
|
+
# Disabled until release signing is enabled
|
|
274
|
+
# verify_signature "$binary_path" "$minisign_signature_url"
|
|
275
|
+
|
|
276
|
+
# Make executable
|
|
277
|
+
chmod +x "$binary_path"
|
|
278
|
+
|
|
279
|
+
# Clean up checksum file
|
|
280
|
+
rm -f "$checksum_path"
|
|
281
|
+
|
|
282
|
+
success "Amp CLI binary installed to $binary_path"
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
# Check if a directory is in PATH
|
|
286
|
+
dir_in_path() {
|
|
287
|
+
local check_dir="$1"
|
|
288
|
+
# Normalize the directory path if it exists
|
|
289
|
+
if [[ -d "$check_dir" ]]; then
|
|
290
|
+
check_dir=$(cd "$check_dir" 2>/dev/null && pwd) || return 1
|
|
291
|
+
fi
|
|
292
|
+
echo ":$PATH:" | grep -q ":$check_dir:"
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
# Try to create a symlink in a directory that's already in PATH
|
|
296
|
+
try_symlink_in_path() {
|
|
297
|
+
local binary_name="$1"
|
|
298
|
+
|
|
299
|
+
# Preferred directories to symlink into (in order of preference)
|
|
300
|
+
local preferred_dirs=(
|
|
301
|
+
"$HOME/.local/bin"
|
|
302
|
+
"$HOME/bin"
|
|
303
|
+
"$HOME/.bin"
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
for dir in "${preferred_dirs[@]}"; do
|
|
307
|
+
if dir_in_path "$dir"; then
|
|
308
|
+
# Directory is in PATH, try to create symlink
|
|
309
|
+
mkdir -p "$dir" 2>/dev/null || continue
|
|
310
|
+
|
|
311
|
+
local symlink_path="$dir/$binary_name"
|
|
312
|
+
local target_path="$BIN_DIR/$binary_name"
|
|
313
|
+
|
|
314
|
+
# Remove existing symlink if it points elsewhere
|
|
315
|
+
if [[ -L "$symlink_path" ]]; then
|
|
316
|
+
rm -f "$symlink_path"
|
|
317
|
+
fi
|
|
318
|
+
|
|
319
|
+
if ln -sf "$target_path" "$symlink_path" 2>/dev/null; then
|
|
320
|
+
success "Created symlink: $symlink_path -> $target_path"
|
|
321
|
+
return 0
|
|
322
|
+
fi
|
|
323
|
+
fi
|
|
324
|
+
done
|
|
325
|
+
|
|
326
|
+
return 1
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
# Update PATH in shell profile
|
|
330
|
+
update_shell_profile() {
|
|
331
|
+
local binary_name="amp"
|
|
332
|
+
|
|
333
|
+
# First, try to symlink into a directory already in PATH
|
|
334
|
+
if try_symlink_in_path "$binary_name"; then
|
|
335
|
+
# Symlink created, no need to modify shell profile
|
|
336
|
+
return
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
# Create ~/.local/bin and symlink amp there (instead of adding ~/.amp/bin to PATH)
|
|
340
|
+
local local_bin_dir="$HOME/.local/bin"
|
|
341
|
+
mkdir -p "$local_bin_dir" 2>/dev/null || true
|
|
342
|
+
local symlink_path="$local_bin_dir/$binary_name"
|
|
343
|
+
local target_path="$BIN_DIR/$binary_name"
|
|
344
|
+
|
|
345
|
+
# Remove existing symlink if it points elsewhere
|
|
346
|
+
if [[ -L "$symlink_path" ]]; then
|
|
347
|
+
rm -f "$symlink_path"
|
|
348
|
+
fi
|
|
349
|
+
|
|
350
|
+
if ln -sf "$target_path" "$symlink_path" 2>/dev/null; then
|
|
351
|
+
success "Created symlink: $symlink_path -> $target_path"
|
|
352
|
+
else
|
|
353
|
+
warn "Could not create symlink in $local_bin_dir"
|
|
354
|
+
warn "Please add $BIN_DIR to your PATH manually:"
|
|
355
|
+
echo " export PATH=\"$BIN_DIR:\$PATH\""
|
|
356
|
+
return
|
|
357
|
+
fi
|
|
358
|
+
|
|
359
|
+
# Fall back to modifying shell profile to add ~/.local/bin
|
|
360
|
+
# Detect shell from $SHELL or default
|
|
361
|
+
local default_shell="bash"
|
|
362
|
+
if [[ "$(uname -s)" == "Darwin" ]]; then
|
|
363
|
+
default_shell="zsh"
|
|
364
|
+
fi
|
|
365
|
+
local os_name
|
|
366
|
+
os_name="$(uname -s)"
|
|
367
|
+
|
|
368
|
+
local shell_name
|
|
369
|
+
shell_name=$(basename "${SHELL:-$default_shell}")
|
|
370
|
+
|
|
371
|
+
local shell_profile=""
|
|
372
|
+
local path_export=""
|
|
373
|
+
|
|
374
|
+
case "$shell_name" in
|
|
375
|
+
zsh)
|
|
376
|
+
shell_profile="$HOME/.zshrc"
|
|
377
|
+
path_export="export PATH=\"\$HOME/.local/bin:\$PATH\""
|
|
378
|
+
;;
|
|
379
|
+
bash)
|
|
380
|
+
if [[ "$os_name" == "Darwin" ]]; then
|
|
381
|
+
if [[ -f "$HOME/.bash_profile" ]]; then
|
|
382
|
+
shell_profile="$HOME/.bash_profile"
|
|
383
|
+
elif [[ -f "$HOME/.bashrc" ]]; then
|
|
384
|
+
shell_profile="$HOME/.bashrc"
|
|
385
|
+
else
|
|
386
|
+
shell_profile="$HOME/.bash_profile"
|
|
387
|
+
fi
|
|
388
|
+
else
|
|
389
|
+
if [[ -f "$HOME/.bashrc" ]]; then
|
|
390
|
+
shell_profile="$HOME/.bashrc"
|
|
391
|
+
elif [[ -f "$HOME/.bash_profile" ]]; then
|
|
392
|
+
shell_profile="$HOME/.bash_profile"
|
|
393
|
+
else
|
|
394
|
+
shell_profile="$HOME/.bashrc"
|
|
395
|
+
fi
|
|
396
|
+
fi
|
|
397
|
+
path_export="export PATH=\"\$HOME/.local/bin:\$PATH\""
|
|
398
|
+
;;
|
|
399
|
+
fish)
|
|
400
|
+
shell_profile="$HOME/.config/fish/config.fish"
|
|
401
|
+
path_export="fish_add_path \"\$HOME/.local/bin\""
|
|
402
|
+
;;
|
|
403
|
+
*)
|
|
404
|
+
warn "Unknown shell: $shell_name"
|
|
405
|
+
warn "Please add ~/.local/bin to your PATH manually:"
|
|
406
|
+
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
|
|
407
|
+
return
|
|
408
|
+
;;
|
|
409
|
+
esac
|
|
410
|
+
|
|
411
|
+
# Check if ~/.local/bin is already in PATH config (ignore commented lines, match PATH assignments)
|
|
412
|
+
if [[ -f "$shell_profile" ]] && grep -v '^\s*#' "$shell_profile" 2>/dev/null | grep -qE 'PATH=.*\.local/bin'; then
|
|
413
|
+
log "~/.local/bin already configured in $shell_profile"
|
|
414
|
+
echo ""
|
|
415
|
+
log "To use amp immediately, run:"
|
|
416
|
+
echo " export PATH=\"\$HOME/.local/bin:\$PATH\""
|
|
417
|
+
return
|
|
418
|
+
fi
|
|
419
|
+
|
|
420
|
+
# Ask user before modifying shell config
|
|
421
|
+
local tilde_profile="${shell_profile/#$HOME/\~}"
|
|
422
|
+
echo ""
|
|
423
|
+
if [[ -t 0 ]]; then
|
|
424
|
+
# Interactive: ask user
|
|
425
|
+
read -p "Add ~/.local/bin to your PATH in $tilde_profile? [y/n] " -n 1 -r
|
|
426
|
+
echo ""
|
|
427
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
|
428
|
+
log "Skipped modifying shell config."
|
|
429
|
+
log "To use amp, add ~/.local/bin to your PATH manually:"
|
|
430
|
+
echo " $path_export"
|
|
431
|
+
return
|
|
432
|
+
fi
|
|
433
|
+
else
|
|
434
|
+
# Non-interactive: show instructions instead
|
|
435
|
+
log "Non-interactive mode: skipping shell config modification."
|
|
436
|
+
log "To use amp, add ~/.local/bin to your PATH:"
|
|
437
|
+
echo " $path_export"
|
|
438
|
+
return
|
|
439
|
+
fi
|
|
440
|
+
|
|
441
|
+
# Create config file if it doesn't exist
|
|
442
|
+
if [[ ! -f "$shell_profile" ]]; then
|
|
443
|
+
mkdir -p "$(dirname "$shell_profile")"
|
|
444
|
+
touch "$shell_profile"
|
|
445
|
+
fi
|
|
446
|
+
|
|
447
|
+
# Add to PATH
|
|
448
|
+
{
|
|
449
|
+
echo ""
|
|
450
|
+
echo "# Amp CLI"
|
|
451
|
+
echo "$path_export"
|
|
452
|
+
} >> "$shell_profile"
|
|
453
|
+
|
|
454
|
+
success "Added ~/.local/bin to PATH in $tilde_profile"
|
|
455
|
+
echo ""
|
|
456
|
+
log "To use amp immediately, run:"
|
|
457
|
+
echo " $path_export"
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
# Main installation
|
|
461
|
+
main() {
|
|
462
|
+
log "Starting Amp CLI binary installation..."
|
|
463
|
+
|
|
464
|
+
# Check prerequisites
|
|
465
|
+
check_prereqs
|
|
466
|
+
|
|
467
|
+
# Create directories
|
|
468
|
+
mkdir -p "$BIN_DIR"
|
|
469
|
+
|
|
470
|
+
# Detect platform
|
|
471
|
+
local platform
|
|
472
|
+
platform=$(detect_platform)
|
|
473
|
+
log "Detected platform: $platform"
|
|
474
|
+
|
|
475
|
+
# Install binary
|
|
476
|
+
install_amp_binary "$platform"
|
|
477
|
+
|
|
478
|
+
# Update shell profile
|
|
479
|
+
update_shell_profile
|
|
480
|
+
|
|
481
|
+
echo ""
|
|
482
|
+
success "Amp CLI installed successfully!"
|
|
483
|
+
log "Run 'amp --help' to get started"
|
|
484
|
+
log "Visit $AMP_URL for documentation"
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
main "$@"
|