dh-cli 0.4.3__tar.gz → 0.4.5__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. {dh_cli-0.4.3 → dh_cli-0.4.5}/PKG-INFO +1 -1
  2. {dh_cli-0.4.3 → dh_cli-0.4.5}/pyproject.toml +1 -1
  3. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/hz/deploy.py +15 -3
  4. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/hz/test.py +73 -0
  5. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/hz/tf.py +18 -3
  6. {dh_cli-0.4.3 → dh_cli-0.4.5}/.gitignore +0 -0
  7. {dh_cli-0.4.3 → dh_cli-0.4.5}/LICENSE +0 -0
  8. {dh_cli-0.4.3 → dh_cli-0.4.5}/README.md +0 -0
  9. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/__init__.py +0 -0
  10. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/__init__.py +0 -0
  11. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/aws_batch.py +0 -0
  12. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/__init__.py +0 -0
  13. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/boltz.py +0 -0
  14. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/cancel.py +0 -0
  15. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/clean.py +0 -0
  16. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/embed_t5.py +0 -0
  17. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/finalize.py +0 -0
  18. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/list_jobs.py +0 -0
  19. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/local.py +0 -0
  20. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/logs.py +0 -0
  21. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/protmpnn.py +0 -0
  22. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/protmpnn_to_boltz.py +0 -0
  23. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/retry.py +0 -0
  24. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/status.py +0 -0
  25. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/submit.py +0 -0
  26. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/train.py +0 -0
  27. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/commands/wait_for.py +0 -0
  28. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/fasta_utils.py +0 -0
  29. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/h5_utils.py +0 -0
  30. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/job_id.py +0 -0
  31. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/manifest.py +0 -0
  32. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/batch/s3_transport.py +0 -0
  33. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/cloud_commands.py +0 -0
  34. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/codeartifact.py +0 -0
  35. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/engines_studios/__init__.py +0 -0
  36. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/engines_studios/api_client.py +0 -0
  37. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/engines_studios/auth.py +0 -0
  38. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/engines_studios/engine_commands.py +0 -0
  39. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/engines_studios/progress.py +0 -0
  40. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/engines_studios/ssh_config.py +0 -0
  41. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/engines_studios/studio_commands.py +0 -0
  42. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/github_commands.py +0 -0
  43. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/hz/__init__.py +0 -0
  44. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/hz/local.py +0 -0
  45. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/hz/users.py +0 -0
  46. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/main.py +0 -0
  47. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/utility_commands.py +0 -0
  48. {dh_cli-0.4.3 → dh_cli-0.4.5}/src/dh_cli/warehouse.py +0 -0
  49. {dh_cli-0.4.3 → dh_cli-0.4.5}/tests/hz/test_init.py +0 -0
  50. {dh_cli-0.4.3 → dh_cli-0.4.5}/tests/hz/test_suites.py +0 -0
  51. {dh_cli-0.4.3 → dh_cli-0.4.5}/tests/hz/test_users.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dh-cli
3
- Version: 0.4.3
3
+ Version: 0.4.5
4
4
  Summary: Dayhoff Labs developer CLI
5
5
  Author-email: Dayhoff Labs <dev@dayhofflabs.com>
6
6
  License: # PolyForm Noncommercial License 1.0.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "dh-cli"
7
- version = "0.4.3"
7
+ version = "0.4.5"
8
8
  description = "Dayhoff Labs developer CLI"
9
9
  requires-python = ">=3.11"
10
10
  readme = "README.md"
@@ -7,11 +7,21 @@ from dh_cli.hz import require_repo, run_script
7
7
  deploy_app = typer.Typer(context_settings={"help_option_names": ["-h", "--help"]})
8
8
 
9
9
 
10
+ def _confirm_deploy(target: str, env: str) -> None:
11
+ color = typer.colors.RED if env == "prod" else typer.colors.BRIGHT_GREEN
12
+ env_styled = typer.style(env, fg=color, bold=True)
13
+ typer.echo(f"\n Deploying {target} to {env_styled}\n")
14
+ if not typer.confirm(" Continue?"):
15
+ raise typer.Abort()
16
+ typer.echo()
17
+
18
+
10
19
  @deploy_app.command("api")
11
20
  def deploy_api(
12
- env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
21
+ env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
13
22
  ):
14
23
  """Build and deploy the Horizyn API server."""
24
+ _confirm_deploy("api", env)
15
25
  repo = require_repo("horizyn-api")
16
26
  script = repo / f"server/build-and-push-{env}.sh"
17
27
  run_script(script, cwd=repo)
@@ -19,9 +29,10 @@ def deploy_api(
19
29
 
20
30
  @deploy_app.command("docking")
21
31
  def deploy_docking(
22
- env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
32
+ env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
23
33
  ):
24
34
  """Build and deploy the docking service."""
35
+ _confirm_deploy("docking", env)
25
36
  repo = require_repo("horizyn-api")
26
37
  script = repo / f"docking_service/build-and-push-{env}.sh"
27
38
  run_script(script, cwd=repo)
@@ -29,9 +40,10 @@ def deploy_docking(
29
40
 
30
41
  @deploy_app.command("frontend")
31
42
  def deploy_frontend(
32
- env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
43
+ env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
33
44
  ):
34
45
  """Build and deploy the Horizyn frontend."""
46
+ _confirm_deploy("frontend", env)
35
47
  repo = require_repo("horizyn-frontend")
36
48
  script = repo / "scripts/deploy.sh"
37
49
  run_script(script, args=[env], cwd=repo)
@@ -1,5 +1,6 @@
1
1
  """Run integration test suites."""
2
2
 
3
+ import os
3
4
  import re
4
5
  from pathlib import Path
5
6
  from typing import Optional
@@ -48,6 +49,75 @@ def _resolve_suites(
48
49
  return matched
49
50
 
50
51
 
52
+ def _ensure_jwt_token(env: str, test_dir: Path) -> None:
53
+ """Ensure HORIZYN_TOKEN is set, prompting interactively if needed.
54
+
55
+ Checks (in order): env var with expiry validation, cached token file,
56
+ then falls back to interactive Cognito passwordless auth.
57
+ Sets os.environ["HORIZYN_TOKEN"] so child processes inherit it.
58
+ """
59
+ import json
60
+ import subprocess
61
+ import time
62
+
63
+ token_script = test_dir / "get_jwt_token.py"
64
+ cache_dir = Path.home() / ".horizyn"
65
+ cache_file = cache_dir / f"token_{env}"
66
+
67
+ def _token_valid(token: str) -> bool:
68
+ """Check JWT expiry without verification (5-minute buffer)."""
69
+ import base64
70
+
71
+ try:
72
+ payload = token.split(".")[1]
73
+ padding = 4 - len(payload) % 4
74
+ if padding != 4:
75
+ payload += "=" * padding
76
+ decoded = json.loads(base64.urlsafe_b64decode(payload))
77
+ return time.time() < (decoded.get("exp", 0) - 300)
78
+ except Exception:
79
+ return False
80
+
81
+ # 1. Check existing env var
82
+ existing = os.environ.get("HORIZYN_TOKEN", "")
83
+ if existing and _token_valid(existing):
84
+ typer.secho(" Using existing HORIZYN_TOKEN", fg=typer.colors.GREEN)
85
+ return
86
+
87
+ # 2. Check cached token file
88
+ if cache_file.exists():
89
+ cached = cache_file.read_text().strip()
90
+ if cached and _token_valid(cached):
91
+ os.environ["HORIZYN_TOKEN"] = cached
92
+ typer.secho(f" Using cached token from {cache_file}", fg=typer.colors.GREEN)
93
+ return
94
+ cache_file.unlink(missing_ok=True)
95
+
96
+ # 3. Interactive: run get_jwt_token.py (prompts go to stderr, token to stdout)
97
+ typer.echo()
98
+ typer.secho(" No valid token found — starting authentication.", fg=typer.colors.YELLOW)
99
+ typer.echo()
100
+
101
+ result = subprocess.run(
102
+ ["python3", str(token_script), "--env", env],
103
+ capture_output=False,
104
+ stdout=subprocess.PIPE,
105
+ text=True,
106
+ )
107
+
108
+ if result.returncode != 0 or not result.stdout.strip():
109
+ typer.secho(" Authentication failed.", fg=typer.colors.RED, err=True)
110
+ raise typer.Exit(1)
111
+
112
+ token = result.stdout.strip()
113
+ os.environ["HORIZYN_TOKEN"] = token
114
+
115
+ # Cache for future runs
116
+ cache_dir.mkdir(mode=0o700, exist_ok=True)
117
+ cache_file.write_text(token)
118
+ cache_file.chmod(0o600)
119
+
120
+
51
121
  @test_app.command("list")
52
122
  def list_tests(
53
123
  target: str = typer.Option("local", "--target", "-t", help="local or deployed."),
@@ -98,9 +168,12 @@ def test_deployed(
98
168
  test_dir = repo / "test/server/deployed"
99
169
 
100
170
  if not suites:
171
+ # run_all_tests.sh handles its own token flow
101
172
  run_script(test_dir / "run_all_tests.sh", args=[env], cwd=test_dir)
102
173
  return
103
174
 
175
+ _ensure_jwt_token(env, test_dir)
176
+
104
177
  available = _discover_suites(test_dir)
105
178
  selected = _resolve_suites(available, suites)
106
179
 
@@ -16,6 +16,15 @@ TF_MODULES = {
16
16
  }
17
17
 
18
18
 
19
+ def _confirm_tf(target: str, env: str) -> None:
20
+ color = typer.colors.RED if env == "prod" else typer.colors.BRIGHT_GREEN
21
+ env_styled = typer.style(env, fg=color, bold=True)
22
+ typer.echo(f"\n Terraform apply: {target} in {env_styled}\n")
23
+ if not typer.confirm(" Continue?"):
24
+ raise typer.Abort()
25
+ typer.echo()
26
+
27
+
19
28
  def _run_tf(target: str, env: str, action: str) -> None:
20
29
  repo = require_repo("blueprints")
21
30
  module_path = TF_MODULES.get((target, env))
@@ -28,26 +37,32 @@ def _run_tf(target: str, env: str, action: str) -> None:
28
37
 
29
38
  @tf_app.command("api")
30
39
  def tf_api(
31
- env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
40
+ env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
32
41
  yolo: bool = typer.Option(False, "--yolo", help="Auto-approve (no confirmation prompt)."),
33
42
  ):
34
43
  """Terraform apply for the Horizyn API server."""
44
+ if not yolo:
45
+ _confirm_tf("api", env)
35
46
  _run_tf("api", env, "yolo" if yolo else "plan")
36
47
 
37
48
 
38
49
  @tf_app.command("frontend")
39
50
  def tf_frontend(
40
- env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
51
+ env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
41
52
  yolo: bool = typer.Option(False, "--yolo", help="Auto-approve (no confirmation prompt)."),
42
53
  ):
43
54
  """Terraform apply for the Horizyn frontend."""
55
+ if not yolo:
56
+ _confirm_tf("frontend", env)
44
57
  _run_tf("frontend", env, "yolo" if yolo else "plan")
45
58
 
46
59
 
47
60
  @tf_app.command("docking")
48
61
  def tf_docking(
49
- env: str = typer.Option("prod", "--env", "-e", help="Environment: prod or dev."),
62
+ env: str = typer.Option("dev", "--env", "-e", help="Environment: prod or dev."),
50
63
  yolo: bool = typer.Option(False, "--yolo", help="Auto-approve (no confirmation prompt)."),
51
64
  ):
52
65
  """Terraform apply for the Boltz docking service."""
66
+ if not yolo:
67
+ _confirm_tf("docking", env)
53
68
  _run_tf("docking", env, "yolo" if yolo else "plan")
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