amina-cli 0.2.6__tar.gz → 0.2.7__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 (64) hide show
  1. {amina_cli-0.2.6 → amina_cli-0.2.7}/PKG-INFO +1 -1
  2. {amina_cli-0.2.6 → amina_cli-0.2.7}/pyproject.toml +1 -1
  3. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/__init__.py +1 -1
  4. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/client.py +69 -1
  5. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/jobs_cmd.py +74 -0
  6. amina_cli-0.2.7/src/amina_cli/commands/tools/analysis/residue_accessibility.py +121 -0
  7. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/registry.py +11 -0
  8. {amina_cli-0.2.6 → amina_cli-0.2.7}/.gitignore +0 -0
  9. {amina_cli-0.2.6 → amina_cli-0.2.7}/LICENSE +0 -0
  10. {amina_cli-0.2.6 → amina_cli-0.2.7}/README.md +0 -0
  11. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/auth.py +0 -0
  12. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/__init__.py +0 -0
  13. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/auth_cmd.py +0 -0
  14. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/run_cmd.py +0 -0
  15. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/__init__.py +0 -0
  16. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/analysis/__init__.py +0 -0
  17. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/analysis/hydrophobicity.py +0 -0
  18. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/analysis/mmseqs2_cluster.py +0 -0
  19. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/analysis/rmsd.py +0 -0
  20. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/analysis/sasa.py +0 -0
  21. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/analysis/simple_rmsd.py +0 -0
  22. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/analysis/surface_charge.py +0 -0
  23. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/analysis/usalign.py +0 -0
  24. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/design/__init__.py +0 -0
  25. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/design/esm_if1.py +0 -0
  26. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/design/protein_mc.py +0 -0
  27. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/design/proteinmpnn.py +0 -0
  28. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/design/rfdiffusion.py +0 -0
  29. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/display.py +0 -0
  30. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/folding/__init__.py +0 -0
  31. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/folding/boltz2.py +0 -0
  32. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/folding/esmfold.py +0 -0
  33. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/folding/openfold3.py +0 -0
  34. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/folding/protenix.py +0 -0
  35. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/interactions/__init__.py +0 -0
  36. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/interactions/autodock_vina.py +0 -0
  37. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/interactions/diffdock.py +0 -0
  38. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/interactions/dockq.py +0 -0
  39. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/interactions/emngly.py +0 -0
  40. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/interactions/glycosylation_ensemble.py +0 -0
  41. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/interactions/interface_identifier.py +0 -0
  42. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/interactions/isoglyp.py +0 -0
  43. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/interactions/lmngly.py +0 -0
  44. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/interactions/p2rank.py +0 -0
  45. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/interactions/pesto.py +0 -0
  46. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/properties/__init__.py +0 -0
  47. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/properties/aminosol.py +0 -0
  48. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/properties/esm1v.py +0 -0
  49. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/properties/esm2_embedding.py +0 -0
  50. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/utilities/__init__.py +0 -0
  51. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/utilities/activesite_verifier.py +0 -0
  52. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/utilities/chain_select.py +0 -0
  53. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/utilities/distance_calculator.py +0 -0
  54. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/utilities/maxit_convert.py +0 -0
  55. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/utilities/mol_size_calculator.py +0 -0
  56. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/utilities/obabel_convert.py +0 -0
  57. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/utilities/pdb_bfactor_overwrite.py +0 -0
  58. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/utilities/pdb_cleaner.py +0 -0
  59. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/utilities/pdb_quality_assessment.py +0 -0
  60. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/utilities/pdb_to_fasta.py +0 -0
  61. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools/utilities/protein_relaxer.py +0 -0
  62. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/commands/tools_cmd.py +0 -0
  63. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/main.py +0 -0
  64. {amina_cli-0.2.6 → amina_cli-0.2.7}/src/amina_cli/storage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amina-cli
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: CLI for AminoAnalytica protein engineering platform
5
5
  Project-URL: Homepage, https://aminoanalytica.com
6
6
  Project-URL: Documentation, https://docs.aminoanalytica.com
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "amina-cli"
3
- version = "0.2.6"
3
+ version = "0.2.7"
4
4
  description = "CLI for AminoAnalytica protein engineering platform"
5
5
  readme = "README.md"
6
6
  license = {text = "Apache-2.0"}
@@ -9,4 +9,4 @@ Quick start:
9
9
  amina run esmfold --sequence "MKFLILLFNILCLFPVLAADNH"
10
10
  """
11
11
 
12
- __version__ = "0.2.6"
12
+ __version__ = "0.2.7"
@@ -20,7 +20,13 @@ from typing import Any, Optional
20
20
  from uuid import uuid4
21
21
 
22
22
  from amina_cli.auth import get_api_key, AuthError
23
- from amina_cli.registry import get_submit_endpoint, get_status_endpoint, get_queued_status_endpoint, ToolNotFoundError
23
+ from amina_cli.registry import (
24
+ get_submit_endpoint,
25
+ get_status_endpoint,
26
+ get_queued_status_endpoint,
27
+ get_cancel_endpoint,
28
+ ToolNotFoundError,
29
+ )
24
30
 
25
31
 
26
32
  # Default polling configuration
@@ -895,6 +901,68 @@ def check_queued_job_status_sync(job_id: str) -> dict[str, Any]:
895
901
  return asyncio.run(check_queued_job_status(job_id))
896
902
 
897
903
 
904
+ async def cancel_job(job_id: str, api_key: Optional[str] = None) -> dict[str, Any]:
905
+ """
906
+ Cancel a queued or running job.
907
+
908
+ Args:
909
+ job_id: Job ID to cancel
910
+ api_key: Optional API key (uses stored key if not provided)
911
+
912
+ Returns:
913
+ Dict with status info:
914
+ - {"status": "cancelled"} on success
915
+ - {"status": "not_found"} if job doesn't exist
916
+ - {"status": "already_finished"} if job already completed/failed/cancelled
917
+
918
+ Raises:
919
+ AuthenticationError: If API key is invalid
920
+ ToolExecutionError: If request fails unexpectedly
921
+ """
922
+ if api_key is None:
923
+ try:
924
+ api_key = get_api_key()
925
+ except AuthError as e:
926
+ raise AuthenticationError(str(e))
927
+
928
+ cancel_endpoint = get_cancel_endpoint()
929
+ url = f"{cancel_endpoint}?job_id={job_id}"
930
+
931
+ async with httpx.AsyncClient(timeout=30.0) as client:
932
+ try:
933
+ response = await client.post(url, headers={"Authorization": f"Bearer {api_key}"})
934
+ except httpx.RequestError as e:
935
+ raise ToolExecutionError(f"Network error cancelling job: {str(e)}")
936
+
937
+ if response.status_code == 401:
938
+ raise AuthenticationError("Invalid API key. Get a new one at: https://app.aminoanalytica.com/settings/api")
939
+
940
+ if response.status_code == 404:
941
+ return {"status": "not_found", "error": "Job not found"}
942
+
943
+ if response.status_code == 409:
944
+ return {"status": "already_finished", "error": "Job already finished"}
945
+
946
+ if response.status_code == 200:
947
+ return response.json()
948
+
949
+ raise ToolExecutionError(f"Cancel failed with HTTP {response.status_code}: {response.text}")
950
+
951
+
952
+ def cancel_job_sync(job_id: str, api_key: Optional[str] = None) -> dict[str, Any]:
953
+ """
954
+ Synchronous wrapper for cancel_job.
955
+
956
+ Args:
957
+ job_id: Job ID to cancel
958
+ api_key: Optional API key
959
+
960
+ Returns:
961
+ Dict with status info
962
+ """
963
+ return asyncio.run(cancel_job(job_id, api_key))
964
+
965
+
898
966
  def check_endpoint_health(tool_name: str) -> bool:
899
967
  """
900
968
  Check if a tool's endpoint is reachable.
@@ -335,6 +335,80 @@ def wait(
335
335
  raise typer.Exit(1)
336
336
 
337
337
 
338
+ @app.command("cancel")
339
+ def cancel(
340
+ job_ids: list[str] = typer.Argument(
341
+ ...,
342
+ help="Job ID(s) to cancel",
343
+ ),
344
+ json_output: bool = typer.Option(
345
+ False,
346
+ "--json",
347
+ help="Output as JSON",
348
+ ),
349
+ ):
350
+ """
351
+ Cancel one or more queued or running jobs.
352
+
353
+ Sends a cancellation request to the server. Credits are fully refunded.
354
+
355
+ Examples:
356
+ amina jobs cancel abc123
357
+ amina jobs cancel abc123 def456
358
+ amina jobs cancel abc123 --json
359
+ """
360
+ from amina_cli.auth import get_job_info, update_job_status
361
+ from amina_cli.client import cancel_job_sync, ToolExecutionError
362
+
363
+ results = []
364
+
365
+ for job_id in job_ids:
366
+ job_info = get_job_info(job_id)
367
+
368
+ if not job_info:
369
+ results.append({"job_id": job_id, "status": "not_found", "error": "Job not found in local history"})
370
+ continue
371
+
372
+ full_job_id = job_info.get("job_id", job_id)
373
+
374
+ try:
375
+ cancel_result = cancel_job_sync(full_job_id)
376
+ status = cancel_result.get("status", "error")
377
+
378
+ if status == "cancelled":
379
+ update_job_status(full_job_id, "cancelled")
380
+
381
+ results.append(
382
+ {
383
+ "job_id": full_job_id,
384
+ "tool_name": job_info.get("tool_name", ""),
385
+ **cancel_result,
386
+ }
387
+ )
388
+ except ToolExecutionError as e:
389
+ results.append({"job_id": full_job_id, "status": "error", "error": str(e)})
390
+
391
+ if json_output:
392
+ console.print(json.dumps(results, indent=2, default=str))
393
+ return
394
+
395
+ for result in results:
396
+ job_id_short = result.get("job_id", "")[:8]
397
+ status_str = result.get("status", "error")
398
+ tool_name = result.get("tool_name", "")
399
+ label = f" ({tool_name})" if tool_name else ""
400
+
401
+ if status_str == "cancelled":
402
+ console.print(f"[green]\u2713[/green] {job_id_short}...{label}: [green]cancelled[/green]")
403
+ elif status_str == "already_finished":
404
+ console.print(f"[yellow]\u2717[/yellow] {job_id_short}...{label}: [yellow]already finished[/yellow]")
405
+ elif status_str == "not_found":
406
+ console.print(f"[dim]?[/dim] {job_id_short}...: [dim]not found[/dim]")
407
+ else:
408
+ error = result.get("error", "")
409
+ console.print(f"[red]\u2717[/red] {job_id_short}...{label}: [red]{status_str}[/red] - {error}")
410
+
411
+
338
412
  @app.command("download")
339
413
  def download(
340
414
  job_id: str = typer.Argument(
@@ -0,0 +1,121 @@
1
+ """Residue Accessibility analysis tool for the Amina CLI."""
2
+
3
+ import typer
4
+ from pathlib import Path
5
+ from typing import Optional
6
+ from rich.console import Console
7
+
8
+ METADATA = {
9
+ "name": "residue_accessibility",
10
+ "display_name": "Residue Accessibility",
11
+ "category": "analysis",
12
+ "description": "Score residues for binder accessibility beyond SASA using depth, visibility, and curvature",
13
+ "modal_function_name": "residue_accessibility_worker",
14
+ "modal_app_name": "residue-accessibility-api",
15
+ "status": "available",
16
+ "outputs": {
17
+ "json_filepath": "Full structured results (residues, patches, recommendation)",
18
+ "csv_filepath": "Per-residue accessibility scores in CSV format",
19
+ "pdb_filepath": "PDB with B-factors set to accessibility tier scores",
20
+ },
21
+ }
22
+
23
+ console = Console()
24
+
25
+
26
+ def register(app: typer.Typer):
27
+ """Register this tool's command with the app."""
28
+ from amina_cli.commands.tools import run_tool_with_progress
29
+
30
+ @app.command("residue-accessibility")
31
+ def run_residue_accessibility(
32
+ pdb: Path = typer.Option(
33
+ ...,
34
+ "--pdb",
35
+ "-p",
36
+ help="Path to PDB file containing protein structure",
37
+ exists=True,
38
+ ),
39
+ output: Optional[Path] = typer.Option(
40
+ None,
41
+ "--output",
42
+ "-o",
43
+ help="Output directory for results (required unless --background)",
44
+ ),
45
+ residues: Optional[str] = typer.Option(
46
+ None,
47
+ "--residues",
48
+ "-r",
49
+ help="Residues to score in CHAIN:RESNUM format (e.g., 'A:42,A:43,B:10'). "
50
+ "If omitted, all surface-exposed residues are scored.",
51
+ ),
52
+ rsa_threshold: float = typer.Option(
53
+ 0.20,
54
+ "--rsa-threshold",
55
+ help="RSA cutoff for the exposure gate (0.0-1.0). Residues below this are excluded.",
56
+ ),
57
+ n_rays: int = typer.Option(
58
+ 200,
59
+ "--n-rays",
60
+ help="Number of hemisphere rays per residue for visibility scoring (10-1000).",
61
+ ),
62
+ job_name: Optional[str] = typer.Option(
63
+ None,
64
+ "--job-name",
65
+ "-j",
66
+ help="Custom job name for output files (default: random 4-letter code)",
67
+ ),
68
+ background: bool = typer.Option(
69
+ False,
70
+ "--background",
71
+ "-b",
72
+ help="Submit job and return immediately without waiting for completion",
73
+ ),
74
+ ):
75
+ """
76
+ Score protein residues for binder accessibility.
77
+
78
+ Goes beyond SASA to measure residue depth, outward visibility (ray casting),
79
+ and surface curvature. Returns a ranked list of residues scored 0–1 for
80
+ binder accessibility.
81
+
82
+ Examples:
83
+ amina run residue-accessibility --pdb ./target.pdb -o ./results/
84
+ amina run residue-accessibility --pdb ./target.pdb -r "A:42,A:43,A:45" -o ./results/
85
+ amina run residue-accessibility --pdb ./target.pdb --rsa-threshold 0.15 -o ./results/
86
+ amina run residue-accessibility --pdb ./target.pdb -j myjob -o ./results/ --background
87
+ """
88
+ # Validate required options
89
+ if output is None and not background:
90
+ console.print("[red]Error:[/red] --output / -o is required (unless using --background)")
91
+ raise typer.Exit(1)
92
+
93
+ # Validate residue format if provided
94
+ if residues:
95
+ for token in residues.split(","):
96
+ token = token.strip()
97
+ if ":" not in token:
98
+ console.print(
99
+ f"[red]Error:[/red] Invalid residue format '{token}'. Expected CHAIN:RESNUM (e.g., 'A:42')."
100
+ )
101
+ raise typer.Exit(1)
102
+
103
+ # Read PDB file content
104
+ pdb_content = pdb.read_text()
105
+ console.print(f"Read PDB file: {pdb.name}")
106
+
107
+ # Build params dict matching worker's expected fields
108
+ params = {
109
+ "pdb_content": pdb_content,
110
+ "rsa_threshold": rsa_threshold,
111
+ "n_rays": n_rays,
112
+ }
113
+
114
+ if residues:
115
+ params["residues"] = residues
116
+
117
+ if job_name:
118
+ params["job_name"] = job_name
119
+
120
+ # Execute
121
+ run_tool_with_progress("residue_accessibility", params, output, background=background)
@@ -80,6 +80,17 @@ def get_queued_status_endpoint() -> str:
80
80
  return f"{base}-queued-job-status.modal.run"
81
81
 
82
82
 
83
+ def get_cancel_endpoint() -> str:
84
+ """
85
+ Get the cancel_job endpoint URL.
86
+
87
+ Returns:
88
+ Cancel job endpoint URL (job_id added as query param by client)
89
+ """
90
+ base = _get_gateway_base()
91
+ return f"{base}-cancel-job.modal.run"
92
+
93
+
83
94
  # Legacy function for backwards compatibility
84
95
  def get_tool_endpoint(name: str) -> str:
85
96
  """
File without changes
File without changes
File without changes