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.
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/PKG-INFO +1 -1
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/pyproject.toml +1 -1
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/auth.py +23 -13
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/token_exchange.py +23 -2
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/utils/update_check.py +2 -2
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_auth_commands.py +51 -2
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/uv.lock +1 -1
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/.env.example +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/.github/workflows/ci.yml +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/.github/workflows/install-scripts.yml +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/.github/workflows/release.yml +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/.gitignore +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/.opencode/skill/release/SKILL.md +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/AGENTS.md +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/README.md +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/RELEASE.md +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/install.ps1 +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/install.sh +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/pyrightconfig.json +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/__init__.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/api/__init__.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/api/apps_manager_client.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/api/client.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/assets/__init__.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/assets/callback.html +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/__init__.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/app_knowledge.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/app_knowledge_helpers.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/apps.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/apps_helpers.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/batch.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/batch_helpers.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/build.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/build_helpers.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/flow_types.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/maintenance_check.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/run.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/run_display.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/run_helpers.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/skill.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/upgrade.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/user_story.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/user_story_helpers.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/user_story_modify.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/__init__.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/app_context.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/auth.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/config.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/credentials.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/oauth.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/core/tenants.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/main.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/__init__.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/app.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/base.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/build.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/maintenance_check.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/story_run.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/models/user_story.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/utils/__init__.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/utils/output.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/utils/skill_refresh.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/__init__.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_app_knowledge_commands.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_apps_commands.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_auth.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_batch_commands.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_build_commands.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_code_quality.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_flow_types_commands.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_run_commands.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_skill_command.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_upgrade_command.py +0 -0
- {minitest_cli-0.8.0 → minitest_cli-0.8.2}/tests/test_user_story_commands.py +0 -0
- {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.
|
|
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,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
|
-
|
|
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
|
|
27
|
-
""
|
|
28
|
+
def _find_npx() -> str | None:
|
|
29
|
+
return shutil.which("npx")
|
|
30
|
+
|
|
28
31
|
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
[
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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" {
|
|
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=
|
|
48
|
-
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
|
|
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 =
|
|
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):
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{minitest_cli-0.8.0 → minitest_cli-0.8.2}/src/minitest_cli/commands/app_knowledge_helpers.py
RENAMED
|
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
|
|
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
|
|
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
|
|
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
|