Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```

Expand Down Expand Up @@ -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.
Expand Down
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -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}'
Expand Down Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions scripts/pre-commit
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions src/keboola_agent_cli/manage_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
14 changes: 13 additions & 1 deletion src/keboola_agent_cli/services/org_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.

Expand All @@ -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'",
Expand Down
Loading