diff --git a/CLAUDE.md b/CLAUDE.md index 37421022..75170cff 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -25,6 +25,7 @@ make test # run all tests make lint # run ruff linter make format # format code make check # lint + format-check + test (CI-like) +make hooks # install pre-commit hook make clean # remove caches and build artifacts ``` @@ -164,6 +165,8 @@ Both inherit from `BaseHttpClient` (`http_base.py`) which provides shared retry/ 13. **Idempotency**: `org setup` skips already-registered projects by matching `project_id`. Safe to re-run. +14. **Pre-commit checks are mandatory.** Before every `git commit`, run `ruff check` and `ruff format --check` on changed files. A pre-commit hook (`scripts/pre-commit`, install via `make hooks`) does this automatically. **Never commit without passing lint + format.** If using sub-agents that write code, always run `make check` (or at minimum `ruff check src/ tests/ && ruff format . --check`) before committing their output. + ## Claude Code Plugin (Marketplace) This repo doubles as a Claude Code plugin marketplace. The plugin lives in `plugins/kbagent/` and contains a skill that teaches Claude how to use kbagent. diff --git a/Makefile b/Makefile index 88d01bac..ba697822 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ .DEFAULT_GOAL := help -.PHONY: help install install-mcp sync test test-unit test-integration test-file lint lint-fix format format-check skill-check skill-gen check clean +.PHONY: help install install-mcp sync test test-unit test-integration test-file lint lint-fix format format-check skill-check skill-gen check clean hooks help: ## Show this help message @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}' @@ -51,6 +51,11 @@ skill-check: ## Check SKILL.md is up-to-date (fails if stale) exit 1; \ fi +hooks: ## Install git pre-commit hook (lint + format on staged files) + cp scripts/pre-commit .git/hooks/pre-commit + chmod +x .git/hooks/pre-commit + @echo "Pre-commit hook installed." + check: lint format-check skill-check test ## Run all checks (lint + format + skill + test) clean: ## Remove build artifacts and caches diff --git a/scripts/pre-commit b/scripts/pre-commit new file mode 100755 index 00000000..81bf0038 --- /dev/null +++ b/scripts/pre-commit @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Pre-commit hook: runs ruff lint and format checks on staged Python files. +# Install: make hooks (or: cp scripts/pre-commit .git/hooks/pre-commit && chmod +x .git/hooks/pre-commit) + +set -e + +# Collect staged .py files +STAGED=$(git diff --cached --name-only --diff-filter=ACM | grep '\.py$' || true) + +if [ -z "$STAGED" ]; then + exit 0 +fi + +echo "pre-commit: checking ${STAGED}" | head -5 + +# Lint check (ruff check) +if ! uv run ruff check $STAGED; then + echo "" + echo "Lint failed. Run 'make lint-fix' to auto-fix, then re-stage." + exit 1 +fi + +# Format check (ruff format) +if ! uv run ruff format --check $STAGED 2>/dev/null; then + echo "" + echo "Format check failed. Run 'make format' to fix, then re-stage." + exit 1 +fi diff --git a/src/keboola_agent_cli/manage_client.py b/src/keboola_agent_cli/manage_client.py index b30f67f9..1b4a8f12 100644 --- a/src/keboola_agent_cli/manage_client.py +++ b/src/keboola_agent_cli/manage_client.py @@ -43,6 +43,21 @@ def __enter__(self) -> "ManageClient": def __exit__(self, *args: Any) -> None: self.close() + def verify_token(self) -> dict[str, Any]: + """Verify the manage token and return token/user metadata. + + Calls GET /manage/tokens/verify to retrieve information about + the manage token owner, including user name and email. + + Returns: + Dict with token info including 'user' block (id, name, email). + + Raises: + KeboolaApiError: On API errors. + """ + response = self._do_request("GET", "/manage/tokens/verify") + return response.json() + def list_organization_projects(self, org_id: int) -> list[dict[str, Any]]: """List all projects in an organization. diff --git a/src/keboola_agent_cli/services/org_service.py b/src/keboola_agent_cli/services/org_service.py index ab58c866..0569e72f 100644 --- a/src/keboola_agent_cli/services/org_service.py +++ b/src/keboola_agent_cli/services/org_service.py @@ -95,6 +95,15 @@ def setup_organization( manage_client = self._manage_client_factory(stack_url, manage_token) try: projects = manage_client.list_organization_projects(org_id) + + # Resolve token owner identity for unique token naming + owner_name = "" + try: + token_info = manage_client.verify_token() + user_info = token_info.get("user", {}) + owner_name = user_info.get("email") or user_info.get("name", "") + except Exception: + logger.debug("Could not resolve manage token owner identity") finally: manage_client.close() @@ -150,6 +159,7 @@ def setup_organization( project_name=project_name, alias=alias, token_description=token_description, + owner_name=owner_name, ) # Re-read to get masked token registered = self._config_store.get_project(alias) @@ -199,6 +209,7 @@ def _setup_single_project( project_name: str, alias: str, token_description: str, + owner_name: str = "", ) -> None: """Create a token for a single project, verify it, and register it. @@ -209,8 +220,9 @@ def _setup_single_project( project_name: The project name (from Manage API). alias: The alias to register the project under. token_description: Description for the created token. + owner_name: Email/name of the manage token owner (for unique identification). """ - description = f"{token_description} ({project_name})" + description = f"{token_description} [{owner_name}]" if owner_name else token_description logger.info( "Creating token for project %d (%s) with description '%s'",