code-aide 1.13.0__tar.gz → 1.14.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 (52) hide show
  1. {code_aide-1.13.0 → code_aide-1.14.0}/PKG-INFO +1 -1
  2. {code_aide-1.13.0 → code_aide-1.14.0}/script-archive/amp-install.sh +2 -9
  3. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/__init__.py +1 -1
  4. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/data/tools.json +4 -1
  5. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/install.py +23 -1
  6. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/operations.py +5 -1
  7. {code_aide-1.13.0 → code_aide-1.14.0}/tests/test_install.py +96 -0
  8. {code_aide-1.13.0 → code_aide-1.14.0}/tests/test_operations.py +59 -0
  9. {code_aide-1.13.0 → code_aide-1.14.0}/.github/workflows/ci.yml +0 -0
  10. {code_aide-1.13.0 → code_aide-1.14.0}/.github/workflows/publish.yml +0 -0
  11. {code_aide-1.13.0 → code_aide-1.14.0}/.gitignore +0 -0
  12. {code_aide-1.13.0 → code_aide-1.14.0}/.gitlab-ci.yml +0 -0
  13. {code_aide-1.13.0 → code_aide-1.14.0}/.pre-commit-config.yaml +0 -0
  14. {code_aide-1.13.0 → code_aide-1.14.0}/AGENTS.md +0 -0
  15. {code_aide-1.13.0 → code_aide-1.14.0}/CLAUDE.md +0 -0
  16. {code_aide-1.13.0 → code_aide-1.14.0}/LICENSE +0 -0
  17. {code_aide-1.13.0 → code_aide-1.14.0}/README.md +0 -0
  18. {code_aide-1.13.0 → code_aide-1.14.0}/TODO.md +0 -0
  19. {code_aide-1.13.0 → code_aide-1.14.0}/pyproject.toml +0 -0
  20. {code_aide-1.13.0 → code_aide-1.14.0}/script-archive/README.md +0 -0
  21. {code_aide-1.13.0 → code_aide-1.14.0}/script-archive/claude-install.sh +0 -0
  22. {code_aide-1.13.0 → code_aide-1.14.0}/script-archive/cursor-install.sh +0 -0
  23. {code_aide-1.13.0 → code_aide-1.14.0}/specs/auto-migrate-deprecated-installs.md +0 -0
  24. {code_aide-1.13.0 → code_aide-1.14.0}/specs/claude-native-installer-migration.md +0 -0
  25. {code_aide-1.13.0 → code_aide-1.14.0}/specs/missing-coding-llm-cli-tools.md +0 -0
  26. {code_aide-1.13.0 → code_aide-1.14.0}/specs/pre-commit-uv-setup.md +0 -0
  27. {code_aide-1.13.0 → code_aide-1.14.0}/specs/remove-bundled-version-baseline.md +0 -0
  28. {code_aide-1.13.0 → code_aide-1.14.0}/specs/unify-upgrade-eligibility-with-shared-evaluator.md +0 -0
  29. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/__main__.py +0 -0
  30. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/commands_actions.py +0 -0
  31. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/commands_tools.py +0 -0
  32. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/config.py +0 -0
  33. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/console.py +0 -0
  34. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/constants.py +0 -0
  35. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/detection.py +0 -0
  36. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/entry.py +0 -0
  37. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/install_types.py +0 -0
  38. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/package_managers.py +0 -0
  39. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/prereqs.py +0 -0
  40. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/status.py +0 -0
  41. {code_aide-1.13.0 → code_aide-1.14.0}/src/code_aide/versions.py +0 -0
  42. {code_aide-1.13.0 → code_aide-1.14.0}/tests/test_commands_actions.py +0 -0
  43. {code_aide-1.13.0 → code_aide-1.14.0}/tests/test_commands_tools.py +0 -0
  44. {code_aide-1.13.0 → code_aide-1.14.0}/tests/test_config.py +0 -0
  45. {code_aide-1.13.0 → code_aide-1.14.0}/tests/test_console.py +0 -0
  46. {code_aide-1.13.0 → code_aide-1.14.0}/tests/test_constants.py +0 -0
  47. {code_aide-1.13.0 → code_aide-1.14.0}/tests/test_detection.py +0 -0
  48. {code_aide-1.13.0 → code_aide-1.14.0}/tests/test_install_types.py +0 -0
  49. {code_aide-1.13.0 → code_aide-1.14.0}/tests/test_package_managers.py +0 -0
  50. {code_aide-1.13.0 → code_aide-1.14.0}/tests/test_status.py +0 -0
  51. {code_aide-1.13.0 → code_aide-1.14.0}/tests/test_versions.py +0 -0
  52. {code_aide-1.13.0 → code_aide-1.14.0}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: code-aide
3
- Version: 1.13.0
3
+ Version: 1.14.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
@@ -1,10 +1,6 @@
1
1
  #!/usr/bin/env bash
2
2
  set -euo pipefail
3
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
4
  # Configuration
9
5
  AMP_HOME="${AMP_HOME:-$HOME/.amp}"
10
6
  BIN_DIR="$AMP_HOME/bin"
@@ -437,11 +433,8 @@ update_shell_profile() {
437
433
  return
438
434
  fi
439
435
  else
440
- # Non-interactive: show instructions instead
441
- log "Non-interactive mode: skipping shell config modification."
442
- log "To use amp, add ~/.local/bin to your PATH:"
443
- echo " $path_export"
444
- return
436
+ # Non-interactive: add automatically
437
+ log "Adding ~/.local/bin to PATH in $tilde_profile..."
445
438
  fi
446
439
 
447
440
  # Create config file if it doesn't exist
@@ -1,3 +1,3 @@
1
1
  """code-aide - Manage AI coding CLI tools."""
2
2
 
3
- __version__ = "1.13.0"
3
+ __version__ = "1.14.0"
@@ -64,7 +64,10 @@
64
64
  "command": "amp",
65
65
  "install_type": "script",
66
66
  "install_url": "https://ampcode.com/install.sh",
67
- "install_sha256": "a529b01e1e3653ad3e77cf8e5ec1f2cede5d65c880a62e783a3365bbd825be6d",
67
+ "install_sha256": "2442f20759a2be3beaef6691c7e5bf349f469eb0ff7ea2885746500243446e24",
68
+ "install_script_path_prepend": [
69
+ "~/.local/bin"
70
+ ],
68
71
  "version_url": "https://storage.googleapis.com/amp-public-assets-prod-0/cli/cli-version.txt",
69
72
  "prerequisites": [],
70
73
  "min_node_version": null,
@@ -43,6 +43,7 @@ def run_install_script(
43
43
  tool_name: str,
44
44
  expected_sha256: Optional[str] = None,
45
45
  dryrun: bool = False,
46
+ env: Optional[Dict[str, str]] = None,
46
47
  ) -> bool:
47
48
  """Download and run an installation script with SHA256 verification."""
48
49
  try:
@@ -82,6 +83,7 @@ def run_install_script(
82
83
  ["bash"],
83
84
  stdin=subprocess.PIPE,
84
85
  stderr=subprocess.PIPE,
86
+ env=env,
85
87
  )
86
88
  _, stderr = bash_process.communicate(input=script_content)
87
89
 
@@ -99,6 +101,22 @@ def run_install_script(
99
101
  return False
100
102
 
101
103
 
104
+ def get_install_script_env(tool_config: Dict[str, Any]) -> Optional[Dict[str, str]]:
105
+ """Return environment overrides for running a tool's install script."""
106
+ path_prepend = tool_config.get("install_script_path_prepend", [])
107
+ if not path_prepend:
108
+ return None
109
+
110
+ env = os.environ.copy()
111
+ expanded_paths = [os.path.expanduser(path) for path in path_prepend]
112
+ current_path = env.get("PATH", "")
113
+ path_parts = [*expanded_paths]
114
+ if current_path:
115
+ path_parts.append(current_path)
116
+ env["PATH"] = os.pathsep.join(path_parts)
117
+ return env
118
+
119
+
102
120
  ARCH_MAP = {
103
121
  "x86_64": "x64",
104
122
  "amd64": "x64",
@@ -327,7 +345,11 @@ def install_tool(tool_name: str, dryrun: bool = False, force: bool = False) -> b
327
345
  install_url = tool_config["install_url"]
328
346
  expected_sha256 = tool_config.get("install_sha256")
329
347
  if run_install_script(
330
- install_url, tool_config["name"], expected_sha256, dryrun
348
+ install_url,
349
+ tool_config["name"],
350
+ expected_sha256,
351
+ dryrun,
352
+ env=get_install_script_env(tool_config),
331
353
  ):
332
354
  if dryrun:
333
355
  success(f"{tool_config['name']} verification passed")
@@ -16,6 +16,7 @@ from code_aide.detection import (
16
16
  )
17
17
  from code_aide.package_managers import query_package_owner
18
18
  from code_aide.install import (
19
+ get_install_script_env,
19
20
  install_direct_download,
20
21
  install_tool,
21
22
  run_install_script,
@@ -225,7 +226,10 @@ def upgrade_tool(tool_name: str) -> UpgradeResult:
225
226
  install_url = tool_config["install_url"]
226
227
  expected_sha256 = tool_config.get("install_sha256")
227
228
  if run_install_script(
228
- install_url, tool_config["name"], expected_sha256
229
+ install_url,
230
+ tool_config["name"],
231
+ expected_sha256,
232
+ env=get_install_script_env(tool_config),
229
233
  ):
230
234
  pass
231
235
  else:
@@ -148,6 +148,67 @@ class TestExtractTarMember(unittest.TestCase):
148
148
  self.assertTrue(os.path.exists(extracted))
149
149
 
150
150
 
151
+ class TestInstallScriptEnv(unittest.TestCase):
152
+ """Tests for install script environment handling."""
153
+
154
+ def test_no_env_without_path_prepend(self):
155
+ self.assertIsNone(cli_install.get_install_script_env({}))
156
+
157
+ def test_prepends_expanded_paths(self):
158
+ with tempfile.TemporaryDirectory() as td:
159
+ with mock.patch.dict(
160
+ os.environ,
161
+ {"HOME": td, "PATH": "/usr/bin"},
162
+ clear=True,
163
+ ):
164
+ env = cli_install.get_install_script_env(
165
+ {"install_script_path_prepend": ["~/.local/bin"]}
166
+ )
167
+
168
+ self.assertIsNotNone(env)
169
+ self.assertEqual(
170
+ env["PATH"],
171
+ os.path.join(td, ".local", "bin") + os.pathsep + "/usr/bin",
172
+ )
173
+
174
+ def test_path_prepend_without_existing_path(self):
175
+ with tempfile.TemporaryDirectory() as td:
176
+ with mock.patch.dict(os.environ, {"HOME": td}, clear=True):
177
+ env = cli_install.get_install_script_env(
178
+ {"install_script_path_prepend": ["~/.local/bin"]}
179
+ )
180
+
181
+ self.assertIsNotNone(env)
182
+ self.assertEqual(env["PATH"], os.path.join(td, ".local", "bin"))
183
+
184
+ @mock.patch.object(cli_install, "fetch_url")
185
+ @mock.patch.object(cli_install.subprocess, "Popen")
186
+ def test_run_install_script_passes_env_to_bash(self, mock_popen, mock_fetch):
187
+ script_content = b"#!/usr/bin/env bash\n"
188
+ expected_sha256 = cli_install.hashlib.sha256(script_content).hexdigest()
189
+ process = mock.Mock()
190
+ process.communicate.return_value = (b"", b"")
191
+ process.returncode = 0
192
+ mock_popen.return_value = process
193
+ mock_fetch.return_value = (script_content, None)
194
+ env = {"PATH": "/tmp/bin:/usr/bin"}
195
+
196
+ result = cli_install.run_install_script(
197
+ "https://example.com/install.sh",
198
+ "Example",
199
+ expected_sha256,
200
+ env=env,
201
+ )
202
+
203
+ self.assertTrue(result)
204
+ mock_popen.assert_called_once_with(
205
+ ["bash"],
206
+ stdin=cli_install.subprocess.PIPE,
207
+ stderr=cli_install.subprocess.PIPE,
208
+ env=env,
209
+ )
210
+
211
+
151
212
  class TestInstallDirectDownloadDryrun(unittest.TestCase):
152
213
  """Tests for install_direct_download in dryrun mode."""
153
214
 
@@ -270,3 +331,38 @@ class TestInstallTool(unittest.TestCase):
270
331
  "/usr/local/bin/test-tool"
271
332
  )
272
333
  mock_info.assert_any_call("[DRYRUN] Would install npm package: test-tool")
334
+
335
+ def test_script_install_passes_configured_env(self):
336
+ tool_config = {
337
+ "name": "Test Tool",
338
+ "command": "test-tool",
339
+ "install_type": "script",
340
+ "install_url": "https://example.com/install.sh",
341
+ "install_sha256": "abc123",
342
+ "install_script_path_prepend": ["~/.local/bin"],
343
+ "next_steps": "Run test-tool",
344
+ }
345
+ env = {"PATH": "/tmp/bin:/usr/bin"}
346
+
347
+ with (
348
+ mock.patch.dict(cli_install.TOOLS, {"test": tool_config}, clear=True),
349
+ mock.patch.object(cli_install, "command_exists", return_value=False),
350
+ mock.patch.object(cli_install.platform, "system", return_value="Darwin"),
351
+ mock.patch.object(
352
+ cli_install, "get_install_script_env", return_value=env
353
+ ) as mock_env,
354
+ mock.patch.object(
355
+ cli_install, "run_install_script", return_value=True
356
+ ) as mock_script,
357
+ ):
358
+ result = cli_install.install_tool("test")
359
+
360
+ self.assertTrue(result)
361
+ mock_env.assert_called_once_with(tool_config)
362
+ mock_script.assert_called_once_with(
363
+ "https://example.com/install.sh",
364
+ "Test Tool",
365
+ "abc123",
366
+ False,
367
+ env=env,
368
+ )
@@ -244,6 +244,65 @@ class TestMigrateInstallMethod(unittest.TestCase):
244
244
  mock_dd.assert_called_once_with("cursor", tool_config)
245
245
  mock_script.assert_not_called()
246
246
 
247
+ def test_upgrade_script_passes_configured_env(self):
248
+ """Script upgrades pass per-tool install environment overrides."""
249
+ tool_config = {
250
+ "name": "Amp (Sourcegraph)",
251
+ "command": "amp",
252
+ "install_type": "script",
253
+ "install_url": "https://ampcode.com/install.sh",
254
+ "install_sha256": "abc123",
255
+ "install_script_path_prepend": ["~/.local/bin"],
256
+ }
257
+ env = {"PATH": "/tmp/bin:/usr/bin"}
258
+ with (
259
+ mock.patch.dict(cli_operations.TOOLS, {"amp": tool_config}),
260
+ mock.patch.object(cli_operations, "is_tool_installed", return_value=True),
261
+ mock.patch.object(
262
+ cli_operations, "is_deprecated_install", return_value=False
263
+ ),
264
+ mock.patch.object(
265
+ cli_operations,
266
+ "detect_install_method",
267
+ side_effect=[
268
+ {"method": "script", "detail": "native installer"},
269
+ {"method": "script", "detail": "native installer"},
270
+ ],
271
+ ),
272
+ mock.patch.object(
273
+ cli_operations, "get_install_script_env", return_value=env
274
+ ) as mock_env,
275
+ mock.patch.object(
276
+ cli_operations, "run_install_script", return_value=True
277
+ ) as mock_script,
278
+ mock.patch.object(
279
+ cli_operations,
280
+ "_get_upgrade_snapshot",
281
+ side_effect=[
282
+ {
283
+ "method": "script",
284
+ "detail": "native installer",
285
+ "version": "1.0.0",
286
+ },
287
+ {
288
+ "method": "script",
289
+ "detail": "native installer",
290
+ "version": "2.0.0",
291
+ },
292
+ ],
293
+ ),
294
+ ):
295
+ result = cli_operations.upgrade_tool("amp")
296
+
297
+ self.assertEqual(result, UpgradeResult.CHANGED)
298
+ mock_env.assert_called_once_with(tool_config)
299
+ mock_script.assert_called_once_with(
300
+ "https://ampcode.com/install.sh",
301
+ "Amp (Sourcegraph)",
302
+ "abc123",
303
+ env=env,
304
+ )
305
+
247
306
  def test_migration_fails_on_remove(self):
248
307
  """Migration fails if remove_tool returns False."""
249
308
  tool_config = {
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