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.
Files changed (51) hide show
  1. {code_aide-1.10.0 → code_aide-1.11.0}/.pre-commit-config.yaml +2 -0
  2. {code_aide-1.10.0 → code_aide-1.11.0}/AGENTS.md +12 -2
  3. {code_aide-1.10.0 → code_aide-1.11.0}/PKG-INFO +2 -1
  4. code_aide-1.11.0/TODO.md +80 -0
  5. {code_aide-1.10.0 → code_aide-1.11.0}/pyproject.toml +1 -0
  6. code_aide-1.11.0/script-archive/README.md +36 -0
  7. code_aide-1.11.0/script-archive/amp-install.sh +487 -0
  8. code_aide-1.11.0/script-archive/claude-install.sh +158 -0
  9. code_aide-1.11.0/script-archive/cursor-install.sh +204 -0
  10. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/__init__.py +1 -1
  11. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/commands_tools.py +29 -0
  12. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/constants.py +7 -0
  13. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/data/tools.json +10 -5
  14. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/detection.py +77 -0
  15. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/install.py +23 -1
  16. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/install_types.py +3 -0
  17. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/operations.py +23 -0
  18. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/prereqs.py +7 -1
  19. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/status.py +44 -0
  20. {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_detection.py +126 -0
  21. {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_install.py +54 -0
  22. {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_install_types.py +9 -0
  23. code_aide-1.10.0/TODO.md +0 -71
  24. {code_aide-1.10.0 → code_aide-1.11.0}/.github/workflows/ci.yml +0 -0
  25. {code_aide-1.10.0 → code_aide-1.11.0}/.github/workflows/publish.yml +0 -0
  26. {code_aide-1.10.0 → code_aide-1.11.0}/.gitignore +0 -0
  27. {code_aide-1.10.0 → code_aide-1.11.0}/.gitlab-ci.yml +0 -0
  28. {code_aide-1.10.0 → code_aide-1.11.0}/CLAUDE.md +0 -0
  29. {code_aide-1.10.0 → code_aide-1.11.0}/LICENSE +0 -0
  30. {code_aide-1.10.0 → code_aide-1.11.0}/README.md +0 -0
  31. {code_aide-1.10.0 → code_aide-1.11.0}/specs/auto-migrate-deprecated-installs.md +0 -0
  32. {code_aide-1.10.0 → code_aide-1.11.0}/specs/claude-native-installer-migration.md +0 -0
  33. {code_aide-1.10.0 → code_aide-1.11.0}/specs/missing-coding-llm-cli-tools.md +0 -0
  34. {code_aide-1.10.0 → code_aide-1.11.0}/specs/pre-commit-uv-setup.md +0 -0
  35. {code_aide-1.10.0 → code_aide-1.11.0}/specs/remove-bundled-version-baseline.md +0 -0
  36. {code_aide-1.10.0 → code_aide-1.11.0}/specs/unify-upgrade-eligibility-with-shared-evaluator.md +0 -0
  37. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/__main__.py +0 -0
  38. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/commands_actions.py +0 -0
  39. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/config.py +0 -0
  40. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/console.py +0 -0
  41. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/entry.py +0 -0
  42. {code_aide-1.10.0 → code_aide-1.11.0}/src/code_aide/versions.py +0 -0
  43. {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_commands_actions.py +0 -0
  44. {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_commands_tools.py +0 -0
  45. {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_config.py +0 -0
  46. {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_console.py +0 -0
  47. {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_constants.py +0 -0
  48. {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_operations.py +0 -0
  49. {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_status.py +0 -0
  50. {code_aide-1.10.0 → code_aide-1.11.0}/tests/test_versions.py +0 -0
  51. {code_aide-1.10.0 → code_aide-1.11.0}/uv.lock +0 -0
@@ -10,7 +10,9 @@ repos:
10
10
  rev: v5.0.0
11
11
  hooks:
12
12
  - id: trailing-whitespace
13
+ exclude: ^script-archive/
13
14
  - id: end-of-file-fixer
15
+ exclude: ^script-archive/
14
16
  - id: check-yaml
15
17
  - id: check-toml
16
18
  - id: check-added-large-files
@@ -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
- - No external dependencies - stdlib only
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
- `pre-commit run --all-files` to check before committing
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.10.0
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
@@ -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 "$@"