diff --git a/pyproject.toml b/pyproject.toml index 4eb8b2f978..02a4cdd7dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,10 +7,8 @@ dependencies = [ "typer", "click>=8.1", "rich", - "httpx[socks]", "platformdirs", "readchar", - "truststore>=0.10.4", "pyyaml>=6.0", "packaging>=23.0", "pathspec>=0.12.0", diff --git a/src/specify_cli/__init__.py b/src/specify_cli/__init__.py index fbe1bc033f..28831e6cd2 100644 --- a/src/specify_cli/__init__.py +++ b/src/specify_cli/__init__.py @@ -6,7 +6,6 @@ # "rich", # "platformdirs", # "readchar", -# "httpx", # "json5", # ] # /// @@ -39,7 +38,6 @@ from typing import Any, Optional, Tuple import typer -import httpx from rich.console import Console from rich.panel import Panel from rich.text import Text @@ -51,21 +49,6 @@ # For cross-platform keyboard input import readchar -import ssl -import truststore -from datetime import datetime - -ssl_context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT) -client = httpx.Client(verify=ssl_context) - -def _github_token(cli_token: str | None = None) -> str | None: - """Return sanitized GitHub token (cli arg takes precedence) or None.""" - return ((cli_token or os.getenv("GH_TOKEN") or os.getenv("GITHUB_TOKEN") or "").strip()) or None - -def _github_auth_headers(cli_token: str | None = None) -> dict: - """Return Authorization header dict only when a non-empty token exists.""" - token = _github_token(cli_token) - return {"Authorization": f"Bearer {token}"} if token else {} def _build_agent_config() -> dict[str, dict[str, Any]]: """Derive AGENT_CONFIG from INTEGRATION_REGISTRY.""" @@ -1429,45 +1412,11 @@ def version(): except Exception: pass - # Fetch latest template release version - repo_owner = "github" - repo_name = "spec-kit" - api_url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/releases/latest" - - template_version = "unknown" - release_date = "unknown" - - try: - response = client.get( - api_url, - timeout=10, - follow_redirects=True, - headers=_github_auth_headers(), - ) - if response.status_code == 200: - release_data = response.json() - template_version = release_data.get("tag_name", "unknown") - # Remove 'v' prefix if present - if template_version.startswith("v"): - template_version = template_version[1:] - release_date = release_data.get("published_at", "unknown") - if release_date != "unknown": - # Format the date nicely - try: - dt = datetime.fromisoformat(release_date.replace('Z', '+00:00')) - release_date = dt.strftime("%Y-%m-%d") - except Exception: - pass - except Exception: - pass - info_table = Table(show_header=False, box=None, padding=(0, 2)) info_table.add_column("Key", style="cyan", justify="right") info_table.add_column("Value", style="white") info_table.add_row("CLI Version", cli_version) - info_table.add_row("Template Version", template_version) - info_table.add_row("Released", release_date) info_table.add_row("", "") info_table.add_row("Python", platform.python_version()) info_table.add_row("Platform", platform.system()) diff --git a/src/specify_cli/integrations/claude/__init__.py b/src/specify_cli/integrations/claude/__init__.py index 9eb3214614..31972c4b0e 100644 --- a/src/specify_cli/integrations/claude/__init__.py +++ b/src/specify_cli/integrations/claude/__init__.py @@ -112,8 +112,8 @@ def _build_skill_fm(self, name: str, description: str, source: str) -> dict: ) @staticmethod - def _inject_disable_model_invocation(content: str) -> str: - """Insert ``disable-model-invocation: true`` before the closing ``---``.""" + def _inject_frontmatter_flag(content: str, key: str, value: str = "true") -> str: + """Insert ``key: value`` before the closing ``---`` if not already present.""" lines = content.splitlines(keepends=True) # Pre-scan: bail out if already present in frontmatter @@ -125,7 +125,7 @@ def _inject_disable_model_invocation(content: str) -> str: if dash_count == 2: break continue - if dash_count == 1 and stripped.startswith("disable-model-invocation:"): + if dash_count == 1 and stripped.startswith(f"{key}:"): return content # Inject before the closing --- of frontmatter @@ -137,8 +137,13 @@ def _inject_disable_model_invocation(content: str) -> str: if stripped == "---": dash_count += 1 if dash_count == 2 and not injected: - eol = "\r\n" if line.endswith("\r\n") else "\n" - out.append(f"disable-model-invocation: true{eol}") + if line.endswith("\r\n"): + eol = "\r\n" + elif line.endswith("\n"): + eol = "\n" + else: + eol = "" + out.append(f"{key}: {value}{eol}") injected = True out.append(line) return "".join(out) @@ -150,7 +155,7 @@ def setup( parsed_options: dict[str, Any] | None = None, **opts: Any, ) -> list[Path]: - """Install Claude skills, then inject argument-hint and disable-model-invocation.""" + """Install Claude skills, then inject user-invocable, disable-model-invocation, and argument-hint.""" created = super().setup(project_root, manifest, parsed_options, **opts) # Post-process generated skill files @@ -168,8 +173,11 @@ def setup( content_bytes = path.read_bytes() content = content_bytes.decode("utf-8") + # Inject user-invocable: true (Claude skills are accessible via /command) + updated = self._inject_frontmatter_flag(content, "user-invocable") + # Inject disable-model-invocation: true (Claude skills run only when invoked) - updated = self._inject_disable_model_invocation(content) + updated = self._inject_frontmatter_flag(updated, "disable-model-invocation") # Inject argument-hint if available for this skill skill_dir_name = path.parent.name # e.g. "speckit-plan"