minitest-cli 0.8.0__tar.gz → 0.8.2__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 (75) hide show
  1. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/PKG-INFO +1 -1
  2. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/pyproject.toml +1 -1
  3. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/auth.py +23 -13
  4. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/token_exchange.py +23 -2
  5. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/utils/update_check.py +2 -2
  6. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_auth_commands.py +51 -2
  7. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/uv.lock +1 -1
  8. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/.env.example +0 -0
  9. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/.github/workflows/ci.yml +0 -0
  10. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/.github/workflows/install-scripts.yml +0 -0
  11. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/.github/workflows/release.yml +0 -0
  12. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/.gitignore +0 -0
  13. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/.opencode/skill/release/SKILL.md +0 -0
  14. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/AGENTS.md +0 -0
  15. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/README.md +0 -0
  16. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/RELEASE.md +0 -0
  17. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/install.ps1 +0 -0
  18. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/install.sh +0 -0
  19. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/pyrightconfig.json +0 -0
  20. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/__init__.py +0 -0
  21. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/api/__init__.py +0 -0
  22. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/api/apps_manager_client.py +0 -0
  23. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/api/client.py +0 -0
  24. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/assets/__init__.py +0 -0
  25. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/assets/callback.html +0 -0
  26. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/__init__.py +0 -0
  27. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/app_knowledge.py +0 -0
  28. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/app_knowledge_helpers.py +0 -0
  29. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/apps.py +0 -0
  30. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/apps_helpers.py +0 -0
  31. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/batch.py +0 -0
  32. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/batch_helpers.py +0 -0
  33. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/build.py +0 -0
  34. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/build_helpers.py +0 -0
  35. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/flow_types.py +0 -0
  36. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/maintenance_check.py +0 -0
  37. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/run.py +0 -0
  38. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/run_display.py +0 -0
  39. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/run_helpers.py +0 -0
  40. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/skill.py +0 -0
  41. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/upgrade.py +0 -0
  42. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/user_story.py +0 -0
  43. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/user_story_helpers.py +0 -0
  44. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/user_story_modify.py +0 -0
  45. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/__init__.py +0 -0
  46. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/app_context.py +0 -0
  47. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/auth.py +0 -0
  48. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/config.py +0 -0
  49. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/credentials.py +0 -0
  50. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/oauth.py +0 -0
  51. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/tenants.py +0 -0
  52. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/main.py +0 -0
  53. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/__init__.py +0 -0
  54. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/app.py +0 -0
  55. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/base.py +0 -0
  56. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/build.py +0 -0
  57. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/maintenance_check.py +0 -0
  58. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/story_run.py +0 -0
  59. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/user_story.py +0 -0
  60. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/utils/__init__.py +0 -0
  61. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/utils/output.py +0 -0
  62. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/utils/skill_refresh.py +0 -0
  63. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/__init__.py +0 -0
  64. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_app_knowledge_commands.py +0 -0
  65. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_apps_commands.py +0 -0
  66. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_auth.py +0 -0
  67. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_batch_commands.py +0 -0
  68. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_build_commands.py +0 -0
  69. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_code_quality.py +0 -0
  70. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_flow_types_commands.py +0 -0
  71. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_run_commands.py +0 -0
  72. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_skill_command.py +0 -0
  73. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_upgrade_command.py +0 -0
  74. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_user_story_commands.py +0 -0
  75. {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: minitest-cli
3
- Version: 0.8.0
3
+ Version: 0.8.2
4
4
  Summary: Minitest CLI – command-line interface for the Minitest testing platform
5
5
  Project-URL: Homepage, https://minitap.ai/
6
6
  Project-URL: Source, https://github.com/minitap-ai/minitest-cli
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "minitest-cli"
3
- version = "0.8.0"
3
+ version = "0.8.2"
4
4
  description = "Minitest CLI – command-line interface for the Minitest testing platform"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,7 @@
1
1
  """Authentication commands: login, logout, status."""
2
2
 
3
3
  import json
4
+ import shutil
4
5
  import subprocess
5
6
  from datetime import UTC, datetime
6
7
 
@@ -20,19 +21,22 @@ from minitest_cli.utils.output import output, print_error, print_info, print_suc
20
21
  app = typer.Typer(name="auth", help="Authentication management.")
21
22
 
22
23
  SKILL_NAME = "minitest-cli"
23
- SKILL_INSTALL_CMD = "npx skills add minitap-ai/agent-skills --skill minitest-cli"
24
+ SKILL_INSTALL_ARGS = ["skills", "add", "minitap-ai/agent-skills", "--skill", "minitest-cli"]
25
+ SKILL_INSTALL_DISPLAY = "npx " + " ".join(SKILL_INSTALL_ARGS)
24
26
 
25
27
 
26
- def _is_skill_installed() -> bool:
27
- """Check if the minitest-cli skill is installed via ``npx skills ls``.
28
+ def _find_npx() -> str | None:
29
+ return shutil.which("npx")
30
+
28
31
 
29
- Queries both project-level and global scopes so the detection stays in
30
- sync with whatever directories the ``skills`` CLI manages.
31
- """
32
+ def _is_skill_installed() -> bool:
33
+ npx = _find_npx()
34
+ if npx is None:
35
+ return False
32
36
  for flags in (["--json"], ["--json", "-g"]):
33
37
  try:
34
38
  result = subprocess.run(
35
- ["npx", "skills", "ls", *flags],
39
+ [npx, "skills", "ls", *flags],
36
40
  capture_output=True,
37
41
  text=True,
38
42
  timeout=30,
@@ -42,7 +46,6 @@ def _is_skill_installed() -> bool:
42
46
  if any(s.get("name") == SKILL_NAME for s in skills):
43
47
  return True
44
48
  except (subprocess.TimeoutExpired, json.JSONDecodeError, FileNotFoundError, OSError):
45
- # npx not available or unexpected output – fall through
46
49
  pass
47
50
  return False
48
51
 
@@ -81,14 +84,21 @@ def login() -> None:
81
84
  answer = "n"
82
85
  print() # newline after ^C / ^D
83
86
  if answer in ("", "y", "yes"):
84
- print_info("")
85
- print_info(f" Running: {SKILL_INSTALL_CMD}")
86
- print_info("")
87
- subprocess.run(SKILL_INSTALL_CMD.split(), check=False)
87
+ npx = _find_npx()
88
+ if npx is None:
89
+ print_info("")
90
+ print_error(" npx not found on PATH. Install Node.js, then run:")
91
+ print_info(f" {SKILL_INSTALL_DISPLAY}")
92
+ print_info("")
93
+ else:
94
+ print_info("")
95
+ print_info(f" Running: {SKILL_INSTALL_DISPLAY}")
96
+ print_info("")
97
+ subprocess.run([npx, *SKILL_INSTALL_ARGS], check=False)
88
98
  else:
89
99
  print_info("")
90
100
  print_info(" You can install it later with:")
91
- print_info(f" {SKILL_INSTALL_CMD}")
101
+ print_info(f" {SKILL_INSTALL_DISPLAY}")
92
102
  print_info("")
93
103
 
94
104
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import base64
6
+ import json
5
7
  import sys
6
8
  import time
7
9
  from typing import Any, NoReturn
@@ -40,12 +42,21 @@ def parse_and_save_token_response(settings: Settings, data: dict[str, Any]) -> C
40
42
  if not isinstance(user, dict):
41
43
  user = {}
42
44
  expires_in = data.get("expires_in", 3600)
45
+
46
+ user_id = user.get("id", "")
47
+ email = user.get("email", "")
48
+
49
+ if not email or not user_id:
50
+ claims = _decode_jwt_claims(data["access_token"])
51
+ user_id = user_id or claims.get("sub", "")
52
+ email = email or claims.get("email", "")
53
+
43
54
  creds = Credentials(
44
55
  access_token=data["access_token"],
45
56
  refresh_token=data["refresh_token"],
46
57
  expires_at=time.time() + int(expires_in),
47
- user_id=user.get("id", ""),
48
- email=user.get("email", ""),
58
+ user_id=user_id,
59
+ email=email,
49
60
  )
50
61
  save_credentials(settings, creds)
51
62
  return creds
@@ -53,6 +64,16 @@ def parse_and_save_token_response(settings: Settings, data: dict[str, Any]) -> C
53
64
  return None
54
65
 
55
66
 
67
+ def _decode_jwt_claims(token: str) -> dict[str, Any]:
68
+ """Decode JWT payload without verification. Returns empty dict on failure."""
69
+ try:
70
+ payload = token.split(".")[1]
71
+ payload += "=" * (-len(payload) % 4)
72
+ return json.loads(base64.urlsafe_b64decode(payload))
73
+ except Exception: # noqa: BLE001
74
+ return {}
75
+
76
+
56
77
  def register_oauth_client(supabase_url: str, redirect_uri: str) -> str:
57
78
  """Dynamically register an OAuth2 client with Supabase and return the client_id."""
58
79
  register_url = f"{supabase_url}/auth/v1/oauth/clients/register"
@@ -1,4 +1,4 @@
1
- """PyPI version check – cached for 24 hours, non-blocking, max 2s timeout."""
1
+ """PyPI version check – cached for 1 hour, non-blocking, max 2s timeout."""
2
2
 
3
3
  import json
4
4
  import shutil
@@ -13,7 +13,7 @@ from minitest_cli.core.config import Settings
13
13
  from minitest_cli.utils.output import print_warning
14
14
 
15
15
  CACHE_FILE_NAME = ".last_update_check"
16
- CACHE_TTL_SECONDS = 86400 # 24 hours
16
+ CACHE_TTL_SECONDS = 3600
17
17
  CHECK_TIMEOUT_SECONDS = 2.0
18
18
  PYPI_URL = "https://pypi.org/pypi/minitest-cli/json"
19
19
 
@@ -2,12 +2,16 @@
2
2
 
3
3
  import base64
4
4
  import json
5
+ import os
6
+ import stat
7
+ import sys
5
8
  import time
6
9
  from unittest.mock import patch
7
10
 
8
11
  import typer
9
12
  from typer.testing import CliRunner
10
13
 
14
+ from minitest_cli.commands.auth import SKILL_INSTALL_ARGS
11
15
  from minitest_cli.commands.auth import app as auth_app
12
16
  from minitest_cli.core.auth import Credentials, save_credentials
13
17
  from minitest_cli.core.config import Settings
@@ -55,13 +59,13 @@ def _patch_context(settings, json_mode=False):
55
59
  ]
56
60
 
57
61
 
58
- def _run_with_context(command, settings, json_mode=False, args=None):
62
+ def _run_with_context(command, settings, json_mode=False, args=None, stdin=None):
59
63
  """Run an auth command with patched context."""
60
64
  patches = _patch_context(settings, json_mode)
61
65
  for p in patches:
62
66
  p.start()
63
67
  try:
64
- result = runner.invoke(auth_app, args or [command])
68
+ result = runner.invoke(auth_app, args or [command], input=stdin)
65
69
  finally:
66
70
  for p in patches:
67
71
  p.stop()
@@ -85,6 +89,51 @@ class TestLoginCommand:
85
89
  assert result.exit_code == 0
86
90
  assert "test@example.com" in result.output
87
91
 
92
+ def test_login_skill_install_uses_resolved_npx_path(self, tmp_path, monkeypatch):
93
+ # Regression: bare "npx" passed to subprocess.run fails on Windows where
94
+ # npx is npx.cmd. Verify the real shutil.which lookup happens and its
95
+ # absolute result is what reaches subprocess as argv[0].
96
+ bin_dir = tmp_path / "bin"
97
+ bin_dir.mkdir()
98
+ npx_name = "npx.cmd" if sys.platform == "win32" else "npx"
99
+ npx_path = bin_dir / npx_name
100
+ npx_path.write_text("#!/bin/sh\nexit 0\n")
101
+ npx_path.chmod(npx_path.stat().st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)
102
+ monkeypatch.setenv("PATH", str(bin_dir), prepend=os.pathsep)
103
+
104
+ settings = _make_settings(tmp_path)
105
+ creds = _make_credentials()
106
+
107
+ with (
108
+ patch("minitest_cli.commands.auth.oauth_pkce_login", return_value=creds),
109
+ patch("minitest_cli.commands.auth._is_skill_installed", return_value=False),
110
+ patch("minitest_cli.commands.auth.subprocess.run") as mock_run,
111
+ ):
112
+ result = _run_with_context("login", settings, stdin="y\n")
113
+
114
+ assert result.exit_code == 0
115
+ mock_run.assert_called_once()
116
+ argv = mock_run.call_args.args[0]
117
+ assert argv[0] != "npx"
118
+ assert os.path.isabs(argv[0])
119
+ assert os.path.samefile(argv[0], npx_path)
120
+ assert argv[1:] == SKILL_INSTALL_ARGS
121
+
122
+ def test_login_skill_install_skipped_when_npx_missing(self, tmp_path, monkeypatch):
123
+ monkeypatch.setenv("PATH", "")
124
+ settings = _make_settings(tmp_path)
125
+ creds = _make_credentials()
126
+
127
+ with (
128
+ patch("minitest_cli.commands.auth.oauth_pkce_login", return_value=creds),
129
+ patch("minitest_cli.commands.auth._is_skill_installed", return_value=False),
130
+ patch("minitest_cli.commands.auth.subprocess.run") as mock_run,
131
+ ):
132
+ result = _run_with_context("login", settings, stdin="y\n")
133
+
134
+ assert result.exit_code == 0
135
+ mock_run.assert_not_called()
136
+
88
137
 
89
138
  class TestLogoutCommand:
90
139
  def test_logout_exits_2_when_env_token_set(self, tmp_path):
@@ -141,7 +141,7 @@ wheels = [
141
141
 
142
142
  [[package]]
143
143
  name = "minitest-cli"
144
- version = "0.8.0"
144
+ version = "0.8.2"
145
145
  source = { editable = "." }
146
146
  dependencies = [
147
147
  { name = "httpx" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes