diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 9c3d5bb..f03d1b8 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,55 +1,74 @@ { - "marketplace": "claude-plugins", + "name": "keeper-security", + "owner": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "metadata": { + "description": "Keeper Security Agent Kit - KSM CLI, Commander, and setup skills for Claude Code", + "version": "0.1.0" + }, "plugins": [ { - "id": "keeper-secrets", - "name": "keeper-secrets", - "description": "Retrieve, inject, and manage secrets from Keeper Vault using KSM CLI for developer workflows", - "version": "1.0.0", - "author": "Keeper Security", - "keywords": [ - "secrets", - "vault", - "credentials", - "ksm", - "devops" - ], - "path": "./skills/keeper-secrets" - }, - { - "id": "keeper-admin", "name": "keeper-admin", - "description": "Manage Keeper Vault, enterprise administration, PAM, and privileged access using Commander CLI", - "version": "1.0.0", - "author": "Keeper Security", + "source": "./plugins/keeper-admin", + "description": "Manage Keeper Vault, enterprise administration, PAM, and privileged access using Keeper Commander CLI (keeper).", + "version": "0.1.0", + "author": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "homepage": "https://github.com/Keeper-Security/agent-kit", + "repository": "https://github.com/Keeper-Security/agent-kit", + "license": "Apache-2.0", + "category": "development", "keywords": [ "admin", "vault", "pam", "enterprise", "keeper" - ], - "path": "./skills/keeper-admin" + ] + }, + { + "name": "keeper-secrets", + "source": "./plugins/keeper-secrets", + "description": "Retrieve, inject, and manage secrets from Keeper Vault using KSM CLI (ksm) for developer workflows, including ksm exec, interpolate, and Keeper notation.", + "version": "0.1.0", + "author": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "homepage": "https://github.com/Keeper-Security/agent-kit", + "repository": "https://github.com/Keeper-Security/agent-kit", + "license": "Apache-2.0", + "category": "development", + "keywords": [ + "secrets", + "vault", + "credentials", + "ksm", + "devops" + ] }, { - "id": "keeper-setup", "name": "keeper-setup", - "description": "Install and configure Keeper CLI tools (KSM CLI and Commander) for the Keeper Security agent kit", - "version": "1.0.0", - "author": "Keeper Security", + "source": "./plugins/keeper-setup", + "description": "Install and configure Keeper CLI tools (KSM CLI and Commander) for the Keeper Security agent kit, including profiles and authentication.", + "version": "0.1.0", + "author": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "homepage": "https://github.com/Keeper-Security/agent-kit", + "repository": "https://github.com/Keeper-Security/agent-kit", + "license": "Apache-2.0", + "category": "development", "keywords": [ "setup", "install", "configuration" - ], - "path": "./skills/keeper-setup" + ] } - ], - "homepage": "https://github.com/Keeper-Security/agent-kit", - "repository": "https://github.com/Keeper-Security/agent-kit", - "license": "Apache-2.0", - "support": { - "email": "commanders@keepersecurity.com", - "url": "https://keepersecurity.com/support" - } + ] } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json deleted file mode 100644 index 16b2e20..0000000 --- a/.claude-plugin/plugin.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "keeper-security", - "description": "Keeper Security Agent Kit: zero-trust secrets management and privileged access for AI agents. Enables agents to securely retrieve, inject, and manage credentials through KSM CLI and Keeper Commander.", - "version": "1.1.0", - "homepage": "https://github.com/Keeper-Security/agent-kit", - "author": { - "name": "Keeper Security", - "email": "commander@keepersecurity.com", - "url": "https://keepersecurity.com" - }, - "keywords": [ - "agent-kit", - "credentials", - "devops", - "keeper-security", - "keeper", - "ksm", - "nhi", - "non-human-identity", - "pam", - "password-manager", - "privileged-access", - "secrets", - "security", - "vault", - "zero-knowledge", - "zero-trust" - ], - "category": "security" -} diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json new file mode 100644 index 0000000..fe25ff8 --- /dev/null +++ b/.cursor-plugin/marketplace.json @@ -0,0 +1,81 @@ +{ + "name": "keeper-security", + "owner": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "metadata": { + "description": "Keeper Security Agent Kit - KSM CLI, Commander, and setup skills for Claude Code", + "version": "0.1.0", + "homepage": "https://github.com/Keeper-Security/agent-kit", + "repository": "https://github.com/Keeper-Security/agent-kit", + "license": "Apache-2.0", + "support": { + "email": "commanders@keepersecurity.com", + "url": "https://keepersecurity.com/support" + } + }, + "plugins": [ + { + "name": "keeper-admin", + "source": "./plugins/keeper-admin", + "description": "Manage Keeper Vault, enterprise administration, PAM, and privileged access using Commander CLI", + "version": "0.1.0", + "author": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "homepage": "https://github.com/Keeper-Security/agent-kit", + "repository": "https://github.com/Keeper-Security/agent-kit", + "license": "Apache-2.0", + "category": "development", + "keywords": [ + "admin", + "vault", + "pam", + "enterprise", + "keeper" + ] + }, + { + "name": "keeper-secrets", + "source": "./plugins/keeper-secrets", + "description": "Retrieve, inject, and manage secrets from Keeper Vault using KSM CLI for developer workflows", + "version": "0.1.0", + "author": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "homepage": "https://github.com/Keeper-Security/agent-kit", + "repository": "https://github.com/Keeper-Security/agent-kit", + "license": "Apache-2.0", + "category": "development", + "keywords": [ + "secrets", + "vault", + "credentials", + "ksm", + "devops" + ] + }, + { + "name": "keeper-setup", + "source": "./plugins/keeper-setup", + "description": "Install and configure Keeper CLI tools (KSM CLI and Commander) for the Keeper Security agent kit", + "version": "0.1.0", + "author": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "homepage": "https://github.com/Keeper-Security/agent-kit", + "repository": "https://github.com/Keeper-Security/agent-kit", + "license": "Apache-2.0", + "category": "development", + "keywords": [ + "setup", + "install", + "configuration" + ] + } + ] +} diff --git a/.github/ACT.md b/.github/ACT.md index dc6e19d..dc78193 100644 --- a/.github/ACT.md +++ b/.github/ACT.md @@ -4,7 +4,7 @@ **act** is a CLI that runs **GitHub Actions workflows on your computer** instead of on GitHub. It starts **Docker** containers that behave like GitHub’s `ubuntu-latest` runners, checks out your repo (or copies the working tree), and runs the same `run:` steps your YAML defines. -You use it to **catch CI failures before you push**-for example broken `plugin.json`, bad SKILL frontmatter, or failed skill checks. +You use it to **smoke-test GitHub workflows before you push** (semantic PR, release-please). **Plugin and skill validation** runs via **pre-commit** (`plugin-ci`) or **`task validate`**, not as a hosted workflow. ## How it works (mental model) @@ -30,25 +30,16 @@ From the **repository root**: ./scripts/act-all.sh ``` -That script lists and runs **validate-plugin** and **test-skills** with `act push` (these **must** succeed - the script exits non-zero if either fails). It then runs **semantic-pull-request** (`act pull_request` + [`.github/act/event-pull_request.json`](act/event-pull_request.json)) and **release-please** (`act push`) as **extra smoke tests**. Those two workflows call the **GitHub REST API** (`pulls.get`, release-please), so they **usually fail inside act** without a real `GITHUB_TOKEN`; that is expected and the script still exits **0** after the first two workflows pass. +That script runs **`task validate`** first (same as the **pre-commit** `plugin-ci` hook; **must** succeed—the script exits non-zero if it fails). It then runs **semantic-pull-request** (`act pull_request` + [`.github/act/event-pull_request.json`](act/event-pull_request.json)) and **release-please** (`act push`) as **extra smoke tests**. Those two workflows call the **GitHub REST API** (`pulls.get`, release-please), so they **usually fail inside act** without a real `GITHUB_TOKEN`; that is expected and the script still exits **0** after plugin validation passes. -**Manual - plugin + skills only:** - -**1. See what would run (job names):** - -```bash -act -l -W .github/workflows/validate-plugin.yml -act -l -W .github/workflows/test-skills.yml -``` - -**2. Run the same checks GitHub runs (simulates `push`):** +**Manual - plugin + skills only (no act):** ```bash -act push -W .github/workflows/validate-plugin.yml -act push -W .github/workflows/test-skills.yml +task validate +# or: pre-commit run plugin-ci --all-files ``` -Order does not matter between the two files; run both to match what CI exercises for plugin + skills. +Requires **`jq`** (same as `scripts/validate.sh`). **Manual - semantic PR workflow (`pull_request`):** @@ -70,14 +61,13 @@ Locally, **release-please** usually fails at the step that talks to the GitHub A | Workflow file | What it mainly validates | | --- | --- | -| [`validate-plugin.yml`](workflows/validate-plugin.yml) | `plugin.json`, `marketplace.json`, SKILL frontmatter, reference files, etc. | -| [`test-skills.yml`](workflows/test-skills.yml) | Skill descriptions/triggers, example checks, doc headings in root files | +| *(none on GitHub for plugins)* | Plugin/skill checks: **`task validate`** (also **pre-commit** hook `plugin-ci`). | | [`semantic-pull-request.yml`](workflows/semantic-pull-request.yml) | PR title matches Conventional Commits (squash-merge title) | | [`release-please.yml`](workflows/release-please.yml) | Release PR + changelog + GitHub Release via release-please | ## Convenience: Task wrapper -If you use [Task](https://taskfile.dev/), [`Taskfile.yaml`](../Taskfile.yaml) defines **`task act`**, which runs [`scripts/act-all.sh`](../scripts/act-all.sh) (all workflows). For Markdown formatting before a PR, run **`task fmt`** separately. You can ignore Task and run `./scripts/act-all.sh` or the manual commands above. +If you use [Task](https://taskfile.dev/), [`Taskfile.yaml`](../Taskfile.yaml) defines **`task validate`** (plugin validation) and **`task act`**, which runs [`scripts/act-all.sh`](../scripts/act-all.sh). For Markdown formatting before a PR, run **`task fmt`** separately. You can ignore Task and run `./scripts/act-all.sh` or the manual commands above. ## Differences vs running on GitHub @@ -86,7 +76,6 @@ If you use [Task](https://taskfile.dev/), [`Taskfile.yaml`](../Taskfile.yaml) de | `paths:` filters | Workflow may be skipped if paths don’t match | Ignored for “should this run?” - you chose the file with `-W` | | Runners | GitHub-hosted Ubuntu | Docker image (e.g. `ghcr.io/catthehacker/ubuntu:act-latest`) | | **`GITHUB_TOKEN`** | Injected automatically for each job as `secrets.GITHUB_TOKEN` (scoped to the repo, permissions from the workflow `permissions:` block). You do **not** add it under **Settings → Secrets**. | **Not** the same as GitHub’s token. act may set a placeholder or empty value; it does **not** mint a real token that can call `api.github.com` like the hosted runner. To exercise API-using actions locally you must pass a token yourself (e.g. `act … -s GITHUB_TOKEN=…` with a fine-scoped PAT)-optional and easy to get wrong, so we treat API workflows as **CI-only** for “full success.” | -| `import yaml` in Python | Works after we install **`python3-yaml`** via `apt` in the workflow | Same; that step exists so act matches GitHub | | **release-please** / **semantic PR** actions | Full GitHub API + real `GITHUB_TOKEN` | May fail or partially run without a PAT; use for YAML/container smoke tests | ## Troubleshooting @@ -95,7 +84,6 @@ If you use [Task](https://taskfile.dev/), [`Taskfile.yaml`](../Taskfile.yaml) de | --- | --- | | Docker not running | Start Docker Desktop / Linux daemon | | `unable to get git ref` | Add an initial commit; harmless for file copy | -| `ModuleNotFoundError: yaml` | Workflows should install `python3-yaml`; pull latest `.github/workflows` | | Slow first run | Normal while Docker pulls the runner image | ## See also diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index fe644e0..6c4eecc 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,5 +1,3 @@ -# Pull request - ## Summary @@ -13,6 +11,6 @@ - [ ] Documentation (skills, references, README, etc.) -- [ ] Plugin / marketplace metadata (`.claude-plugin/`) +- [ ] Plugin / marketplace metadata (`.claude-plugin/`or `.cursor-plugin/`) - [ ] CI / tooling (workflows, hooks, Taskfile) - [ ] Other (describe below) diff --git a/.github/workflows/test-skills.yml b/.github/workflows/test-skills.yml deleted file mode 100644 index 31a7e3f..0000000 --- a/.github/workflows/test-skills.yml +++ /dev/null @@ -1,253 +0,0 @@ -name: Test Skills Trigger - -on: - push: - branches: [main, develop] - paths: - - "skills/**" - pull_request: - branches: [main, develop] - paths: - - "skills/**" - -jobs: - test-skill-triggers: - name: Verify Skill Trigger Patterns - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Install PyYAML (frontmatter validation) - run: | - sudo apt-get update -qq - sudo apt-get install -y python3-yaml - - - name: Extract and Test Skill Descriptions - run: | - python << 'EOF' - import os - import re - import yaml - - # Map of skill names to trigger keywords they should recognize - expected_triggers = { - "keeper-secrets": [ - "keeper", "ksm", "secret", "vault", "credentials", - "keeper://", "password", "api key", "certificate", - "ci/cd", "docker", "kubernetes" - ], - "keeper-admin": [ - "keeper", "commander", "admin", "enterprise", - "user management", "pam", "rotation", "session" - ], - "keeper-setup": [ - "install", "keeper", "setup", "configure", - "ksm", "commander", "cli" - ] - } - - skills_dir = "skills" - found_skills = {} - - for skill_name in os.listdir(skills_dir): - skill_path = os.path.join(skills_dir, skill_name, "SKILL.md") - if not os.path.exists(skill_path): - continue - - with open(skill_path) as f: - content = f.read() - - # Extract frontmatter - match = re.match(r'^---\n(.*?)\n---', content, re.DOTALL) - if not match: - print(f"⚠ {skill_name}: No frontmatter") - continue - - try: - frontmatter = yaml.safe_load(match.group(1)) - except: - print(f"⚠ {skill_name}: Invalid YAML") - continue - - description = frontmatter.get("description", "") - found_skills[skill_name] = description - - print(f"\n📋 Skill: {skill_name}") - print(f" Description length: {len(description)} chars") - - # Check for trigger keywords - description_lower = description.lower() - expected = expected_triggers.get(skill_name, []) - found_triggers = [] - - for trigger in expected: - if trigger.lower() in description_lower: - found_triggers.append(trigger) - - if found_triggers: - print(f" ✓ Found {len(found_triggers)}/{len(expected)} expected triggers") - else: - print(f" ⚠ No expected triggers found in description") - - print(f"\n✓ Analyzed {len(found_skills)} skills") - EOF - - - name: Verify Skill Content - run: | - python << 'EOF' - import os - - skills_content_checks = { - "keeper-secrets/SKILL.md": [ - "When to Use KSM", - "Prerequisites", - "Core Commands", - "ksm exec", - "ksm interpolate", - "Guardrails" - ], - "keeper-admin/SKILL.md": [ - "When to Use Commander", - "Prerequisites", - "Vault Operations", - "Enterprise Administration", - "Guardrails" - ], - "keeper-setup/SKILL.md": [ - "Quick Install", - "KSM CLI", - "Commander", - "Troubleshooting" - ] - } - - print("Checking skill content coverage...") - errors = [] - - for skill_path, expected_sections in skills_content_checks.items(): - full_path = f"skills/{skill_path}" - if not os.path.exists(full_path): - errors.append(f"Missing: {full_path}") - continue - - with open(full_path) as f: - content = f.read() - - skill_name = skill_path.split('/')[0] - missing_sections = [] - - for section in expected_sections: - if section not in content: - missing_sections.append(section) - - if missing_sections: - print(f"⚠ {skill_name}: Missing sections: {', '.join(missing_sections)}") - else: - print(f"✓ {skill_name}: All expected sections present") - - if errors: - print("\n❌ Content check failed:") - for error in errors: - print(f" - {error}") - exit(1) - EOF - - validate-examples: - name: Validate Code Examples - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Check Code Block Formatting - run: | - python << 'EOF' - import os - import re - - def check_markdown_file(filepath): - with open(filepath) as f: - content = f.read() - - # Find all code blocks - code_blocks = re.findall(r'```(\w+)?\n(.*?)```', content, re.DOTALL) - - issues = [] - - for lang, code in code_blocks: - # Check for language specification - if not lang: - # Count newlines to identify problematic blocks - if '\n' in code and code.strip(): - issues.append("Code block without language specification") - - # Check for hardcoded secrets - if re.search(r'password\s*=\s*["\'](?!<)(?!keeper)', code, re.IGNORECASE): - issues.append("Possible hardcoded password in example") - if re.search(r'token\s*=\s*["\'](?!<)(?!keeper)', code, re.IGNORECASE): - issues.append("Possible hardcoded token in example") - - return issues - - print("Checking for insecure examples...") - - for root, dirs, files in os.walk("skills"): - for file in files: - if file.endswith(".md"): - filepath = os.path.join(root, file) - issues = check_markdown_file(filepath) - - if issues: - print(f"\n⚠ {filepath}:") - for issue in issues: - print(f" - {issue}") - else: - print(f"✓ {filepath}") - EOF - - documentation-checks: - name: Documentation Quality - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Check Documentation Completeness - run: | - python << 'EOF' - import os - - required_docs = [ - ("README.md", ["Installation", "Prerequisites", "Documentation", "Contributing", "License", "Security"]), - ("SECURITY.md", ["Reporting", "Supported versions"]), - ("CONTRIBUTING.md", ["How to Contribute", "Testing Guidelines", "Submission Checklist"]), - ("LICENSE.md", []) - ] - - print("Checking documentation files...") - - for doc_file, expected_sections in required_docs: - if not os.path.exists(doc_file): - print(f"⚠ Missing: {doc_file}") - continue - - with open(doc_file) as f: - content = f.read() - - missing = [] - for section in expected_sections: - if section not in content: - missing.append(section) - - if missing: - print(f"⚠ {doc_file}: Missing: {', '.join(missing)}") - else: - print(f"✓ {doc_file}") - EOF - - summary: - name: Test Summary - runs-on: ubuntu-latest - needs: [test-skill-triggers, validate-examples, documentation-checks] - if: always() - steps: - - name: Results - run: echo "✓ Skill testing complete" diff --git a/.github/workflows/validate-plugin.yml b/.github/workflows/validate-plugin.yml deleted file mode 100644 index 45fab2a..0000000 --- a/.github/workflows/validate-plugin.yml +++ /dev/null @@ -1,359 +0,0 @@ -name: Validate Plugin Configuration - -on: - push: - branches: [main, develop] - paths: - - ".claude-plugin/**" - - "skills/**" - - ".github/workflows/validate-plugin.yml" - pull_request: - branches: [main, develop] - paths: - - ".claude-plugin/**" - - "skills/**" - - ".github/workflows/validate-plugin.yml" - -jobs: - validate-plugin-json: - name: Validate plugin.json Schema - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Validate plugin.json - run: | - # Check file exists - if [ ! -f ".claude-plugin/plugin.json" ]; then - echo "ERROR: .claude-plugin/plugin.json not found" - exit 1 - fi - - # Validate JSON syntax - if ! python -m json.tool .claude-plugin/plugin.json > /dev/null; then - echo "ERROR: Invalid JSON in plugin.json" - exit 1 - fi - - echo "✓ plugin.json is valid JSON" - - - name: Check Required Fields - run: | - python << 'EOF' - import json - - with open(".claude-plugin/plugin.json") as f: - data = json.load(f) - - required = ["name", "description", "version", "author", "keywords", "category"] - for field in required: - if field not in data: - print(f"ERROR: Missing required field '{field}'") - exit(1) - print(f"✓ {field}: {data[field]}") - - if not isinstance(data.get("keywords"), list): - print("ERROR: 'keywords' must be an array") - exit(1) - - print("✓ All required fields present and valid") - EOF - - validate-marketplace-json: - name: Validate marketplace.json - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Validate marketplace.json - run: | - if [ -f ".claude-plugin/marketplace.json" ]; then - if ! python -m json.tool .claude-plugin/marketplace.json > /dev/null; then - echo "ERROR: Invalid JSON in marketplace.json" - exit 1 - fi - echo "✓ marketplace.json is valid JSON" - else - echo "⚠ marketplace.json not found (optional for self-hosted)" - fi - - - name: Check Marketplace Structure - run: | - python << 'EOF' - import json - import os - - if not os.path.exists(".claude-plugin/marketplace.json"): - print("marketplace.json not present - skipping") - exit(0) - - with open(".claude-plugin/marketplace.json") as f: - data = json.load(f) - - if "plugins" not in data: - print("ERROR: Missing 'plugins' array in marketplace.json") - exit(1) - - for plugin in data["plugins"]: - required = ["id", "name", "description", "version"] - for field in required: - if field not in plugin: - print(f"ERROR: Plugin missing '{field}'") - exit(1) - - print(f"✓ marketplace.json contains {len(data['plugins'])} plugins") - for plugin in data["plugins"]: - print(f" - {plugin['name']} v{plugin['version']}") - EOF - - validate-skill-files: - name: Validate SKILL.md Files - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Install PyYAML (frontmatter validation) - run: | - sudo apt-get update -qq - sudo apt-get install -y python3-yaml - - - name: Find All SKILL.md Files - run: | - echo "Found SKILL.md files:" - find skills -name "SKILL.md" -type f | while read file; do - echo " - $file" - done - - - name: Validate Frontmatter - run: | - python << 'EOF' - import os - import re - import yaml - - skill_files = [] - for root, dirs, files in os.walk("skills"): - for file in files: - if file == "SKILL.md": - skill_files.append(os.path.join(root, file)) - - if not skill_files: - print("ERROR: No SKILL.md files found") - exit(1) - - errors = [] - - for filepath in skill_files: - print(f"\nValidating {filepath}") - - with open(filepath, 'r') as f: - content = f.read() - - # Check for frontmatter - if not content.startswith('---'): - errors.append(f"{filepath}: Missing frontmatter") - continue - - # Extract frontmatter - match = re.match(r'^---\n(.*?)\n---\n', content, re.DOTALL) - if not match: - errors.append(f"{filepath}: Invalid frontmatter syntax") - continue - - try: - frontmatter = yaml.safe_load(match.group(1)) - except yaml.YAMLError as e: - errors.append(f"{filepath}: Invalid YAML frontmatter: {e}") - continue - - # Validate required fields - if "name" not in frontmatter: - errors.append(f"{filepath}: Missing 'name' field") - elif not isinstance(frontmatter["name"], str): - errors.append(f"{filepath}: 'name' must be a string") - elif len(frontmatter["name"]) > 64: - errors.append(f"{filepath}: 'name' must be ≤64 characters") - else: - print(f" ✓ name: {frontmatter['name']}") - - if "description" not in frontmatter: - errors.append(f"{filepath}: Missing 'description' field") - elif not isinstance(frontmatter["description"], str): - errors.append(f"{filepath}: 'description' must be a string") - else: - # Warn if too long for API but don't fail - if len(frontmatter["description"]) > 200: - print(f" ⚠ description is {len(frontmatter['description'])} chars (200+ may be truncated in some APIs)") - print(f" ✓ description: {frontmatter['description'][:50]}...") - - # Check for content after frontmatter - content_after = content[match.end():].strip() - if not content_after: - errors.append(f"{filepath}: No content after frontmatter") - else: - print(f" ✓ Has {len(content_after.split())} words of content") - - if errors: - print("\n❌ ERRORS FOUND:") - for error in errors: - print(f" - {error}") - exit(1) - else: - print("\n✓ All SKILL.md files are valid") - EOF - - - name: Check Markdown Syntax - run: | - python << 'EOF' - import os - import re - - skill_files = [] - for root, dirs, files in os.walk("skills"): - for file in files: - if file == "SKILL.md": - skill_files.append(os.path.join(root, file)) - - errors = [] - - for filepath in skill_files: - with open(filepath, 'r') as f: - lines = f.readlines() - - in_frontmatter = False - frontmatter_count = 0 - last_heading_level = 0 - in_code_block = False - code_language = None - - for i, line in enumerate(lines, 1): - # Track frontmatter - if line.strip() == '---': - frontmatter_count += 1 - if frontmatter_count == 2: - in_frontmatter = False - continue - - if in_frontmatter: - continue - - # Track code blocks - if line.startswith('```'): - if not in_code_block: - match = re.match(r'^```(\w+)?', line) - code_language = match.group(1) if match else None - if not code_language: - errors.append(f"{filepath}:{i}: Code block missing language specification") - in_code_block = not in_code_block - - # Check heading hierarchy - if line.startswith('##'): - match = re.match(r'^(#+)', line) - if match: - level = len(match.group(1)) - if level > last_heading_level + 1: - errors.append(f"{filepath}:{i}: Heading jumps from H{last_heading_level} to H{level}") - last_heading_level = level - - if errors: - print("❌ Markdown issues found:") - for error in errors: - print(f" - {error}") - # Don't fail - these are warnings - print("\n⚠ Fix these issues for better readability") - else: - print("✓ Markdown syntax looks good") - EOF - - check-references: - name: Validate Reference Files - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Check Reference Files Exist - run: | - echo "Checking for reference documentation files..." - - required_refs=( - "skills/keeper-secrets/references/ksm-commands.md" - "skills/keeper-secrets/references/keeper-notation.md" - "skills/keeper-secrets/references/ksm-exec-patterns.md" - "skills/keeper-admin/references/commander-commands.md" - "skills/keeper-admin/references/enterprise-mgmt.md" - "skills/keeper-admin/references/pam-commands.md" - "skills/keeper-admin/references/rotation-commands.md" - ) - - missing=() - for ref in "${required_refs[@]}"; do - if [ ! -f "$ref" ]; then - missing+=("$ref") - else - echo " ✓ $ref" - fi - done - - if [ ${#missing[@]} -gt 0 ]; then - echo "" - echo "❌ Missing reference files:" - for ref in "${missing[@]}"; do - echo " - $ref" - done - exit 1 - fi - - echo "" - echo "✓ All reference files present" - - - name: Check Reference Content - run: | - python << 'EOF' - import os - - refs = { - "skills/keeper-secrets/references/ksm-commands.md": 50, - "skills/keeper-secrets/references/keeper-notation.md": 40, - "skills/keeper-secrets/references/ksm-exec-patterns.md": 60, - "skills/keeper-admin/references/commander-commands.md": 50, - "skills/keeper-admin/references/enterprise-mgmt.md": 40, - "skills/keeper-admin/references/pam-commands.md": 40, - "skills/keeper-admin/references/rotation-commands.md": 40, - } - - for filepath, min_lines in refs.items(): - if not os.path.exists(filepath): - print(f"⚠ {filepath} not found (skipping)") - continue - - with open(filepath) as f: - content = f.read() - lines = content.count('\n') - - if lines < min_lines: - print(f"⚠ {filepath}: {lines} lines (expected ≥{min_lines})") - else: - print(f"✓ {filepath}: {lines} lines") - EOF - - summary: - name: Validation Summary - runs-on: ubuntu-latest - needs: - [ - validate-plugin-json, - validate-marketplace-json, - validate-skill-files, - check-references, - ] - if: always() - steps: - - name: Check Results - run: | - if [ "${{ needs.validate-plugin-json.result }}" = "failure" ] || \ - [ "${{ needs.validate-skill-files.result }}" = "failure" ] || \ - [ "${{ needs.check-references.result }}" = "failure" ]; then - echo "❌ Validation failed" - exit 1 - fi - echo "✓ All validations passed" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ec247b4..a1ebd10 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,11 +14,20 @@ repos: # Markdown formatting - id: fmt name: rumdl fmt (check) - stages: [commit] + stages: [pre-commit] language: system entry: task fmt pass_filenames: false + # Plugin manifests (Claude/Cursor), marketplaces, skills, triggers, docs (scripts/validate.sh) + - id: plugin-ci + name: Plugin and skill validation + stages: [pre-commit] + language: system + entry: task validate + pass_filenames: false + files: ^(plugins/|\.claude-plugin/|\.cursor-plugin/|scripts/validate\.sh|Taskfile\.yaml|README\.md|SECURITY\.md|CONTRIBUTING\.md|LICENSE\.md) + # Standard pre-commit hooks - repo: https://github.com/pre-commit/pre-commit-hooks rev: v6.0.0 diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 5fdd883..466df71 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "1.1.0" + ".": "0.1.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d56da5..6d13c6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,27 +2,20 @@ All notable changes to **Keeper Security Agent Kit** are documented in this file. -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +The format is based on [Keep a Changelog](https://keepachangelog.com/en/0.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [1.1.0](https://github.com/Keeper-Security/agent-kit/compare/v1.0.0...v1.1.0) (2026-03-25) - - -### Features - -* initial commit ([aef4ab0](https://github.com/Keeper-Security/agent-kit/commit/aef4ab0d8fe469443118dcfeec073b939b62e2fa)) - -## [1.0.0] - 2026-03-25 +## [0.1.0] - 2026-03-31 ### Added -- **Skills** - Three agent skills with reference documentation under `skills/*/references/`: - - `keeper-secrets` - KSM CLI (`ksm`): secrets retrieval, `ksm exec`, `ksm interpolate`, Keeper notation, Docker/Kubernetes/CI patterns. +- **Plugins** - Three agent plugins with reference documentation under `plugins/*/references/`: - `keeper-admin` - Keeper Commander (`keeper`): vault, enterprise admin, PAM, rotation, and related command references. + - `keeper-secrets` - KSM CLI (`ksm`): secrets retrieval, `ksm exec`, `ksm interpolate`, Keeper notation, Docker/Kubernetes/CI patterns. - `keeper-setup` - Installing and configuring the KSM and Commander CLIs, regions, and troubleshooting. -- **Distribution** - Claude Code plugin metadata (`.claude-plugin/`), marketplace listing, and install paths via [Vercel Skills CLI](https://github.com/vercel-labs/skills) (`npx skills add`) or manual copy into an agent’s skills directory. +- **Distribution** - Claude Code plugin metadata (`.claude-plugin/`), Cursor plugin metadata (`.cursor-plugin/`), marketplace listing, and install paths via [Vercel Skills CLI](https://github.com/vercel-labs/skills) (`npx skills add`) or manual copy into an agent’s skills directory. - **Documentation** - README, CONTRIBUTING, SECURITY, `TEST_PROMPTS.md`, and links to official Keeper docs (Secrets Manager overview, Commander overview, Keeper notation). - **CI** - GitHub Actions workflows to validate plugin JSON, marketplace JSON, SKILL frontmatter, reference files, and skill trigger/content checks; `python3-yaml` installed in workflows for reliable frontmatter parsing locally and on runners. - **Developer tooling** - `Taskfile` tasks for Markdown (`task fmt`), dependencies (`task install`), and running CI locally with [act](https://github.com/nektos/act) (`task act`); [`.github/ACT.md`](.github/ACT.md) documents local workflow runs. -- **Community** - Issue templates, pull request template, `CODEOWNERS`, Apache 2.0 [`LICENSE.md`](LICENSE.md). +- **Community** - Issue templates, pull request template, `CODEOWNERS`, Apache 2.0 [`LICENSE.md`](LICENSE.md), and pre-commit hooks for Markdown format checks, Conventional Commit messages, and plugin/skill validation when you commit. -[1.0.0]: https://github.com/Keeper-Security/agent-kit/releases/tag/v1.0.0 +[0.1.0]: https://github.com/Keeper-Security/agent-kit/releases/tag/v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b2f495e..57955ce 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -91,7 +91,9 @@ pip install pre-commit pre-commit install ``` -Hooks call `task fmt` (rumdl) and validate **commit messages** (Conventional Commits via `scripts/validate-commit-msg.sh`). `default_install_hook_types` includes `commit-msg`, so `pre-commit install` registers both. Before you open a PR, run `task fmt` or `pre-commit run --all-files`. Deeper validation (plugin JSON, skill frontmatter, references) runs in **GitHub Actions** on push/PR. +You also need **`jq`** on your PATH for `scripts/validate.sh` (e.g. `brew install jq` on macOS). + +Hooks call `task fmt` (rumdl), validate **commit messages** (Conventional Commits via `scripts/validate-commit-msg.sh`), and **`task validate`** (full check: `scripts/validate.sh` — manifests, skills, triggers, examples, root docs). `default_install_hook_types` includes `commit-msg`, so `pre-commit install` registers both stages. Before you open a PR, run `task fmt`, **`task validate`** (or `pre-commit run plugin-ci --all-files`), or `pre-commit run --all-files`. ### Commit messages (Conventional Commits) @@ -107,19 +109,19 @@ Pull requests are checked by **Semantic PR** (`.github/workflows/semantic-pull-r ### Releases and changelog -- **[release-please](https://github.com/googleapis/release-please)** runs on pushes to `main` (`.github/workflows/release-please.yml`). It opens a **release pull request** that bumps `version.txt`, `.release-please-manifest.json`, `CHANGELOG.md`, and versions in `.claude-plugin/plugin.json` and `marketplace.json` based on merged conventional commits. +- **[release-please](https://github.com/googleapis/release-please)** runs on pushes to `main` (`.github/workflows/release-please.yml`). It opens a **release pull request** that bumps `version.txt`, `.release-please-manifest.json`, `CHANGELOG.md`, and every version field listed in `release-please-config.json` → `extra-files` (`.claude-plugin/plugin.json`, `.claude-plugin/marketplace.json` `metadata.version` and each plugin’s `version`, `.cursor-plugin/marketplace.json` `metadata.version`, `plugins/**/.cursor-plugin/plugin.json`, and the versioned example in `docs/add-a-plugin.md`). - When that release PR is **merged**, GitHub receives a **release** with notes derived from those commits (and the updated changelog). - Configure paths in `release-please-config.json` and `.release-please-manifest.json`. #### `version.txt` (how the version is updated) - **Role** - One line, semver only (e.g. `1.2.3`). With **`release-type: simple`** in `release-please-config.json`, this file is the **canonical project version** release-please reads and writes. -- **Normal workflow** - **Do not** edit `version.txt` by hand to ship a release. Land conventional commits on `main`, let release-please open its **release PR**, review it, and **merge that PR**. The PR updates `version.txt` together with `.release-please-manifest.json`, `CHANGELOG.md`, and the `version` fields in `.claude-plugin/plugin.json` and `marketplace.json` (see `extra-files` in `release-please-config.json`). +- **Normal workflow** - **Do not** edit `version.txt` by hand to ship a release. Land conventional commits on `main`, let release-please open its **release PR**, review it, and **merge that PR**. The PR updates `version.txt` together with `.release-please-manifest.json`, `CHANGELOG.md`, and all paths in `release-please-config.json` → `extra-files` (see above). - **Avoid drift** - If you ever change a version **outside** that flow (unusual), update **every** file release-please keeps in sync so they all match; mismatches break automation and CI expectations. ### Optional: run CI workflows locally with [act](https://github.com/nektos/act) -Use **[`.github/ACT.md`](.github/ACT.md)** for prerequisites, how `act` maps to GitHub Actions, and the exact commands. From the repo root, **`./scripts/act-all.sh`** (or **`task act`**) runs **act** against every workflow under `.github/workflows`. **validate-plugin** and **test-skills** must pass locally (same as CI). **semantic-pull-request** and **release-please** are included for smoke coverage but typically do not finish inside act without GitHub API access; see **ACT.md**. +Use **[`.github/ACT.md`](.github/ACT.md)** for prerequisites, how `act` maps to GitHub Actions, and the exact commands. From the repo root, **`./scripts/act-all.sh`** (or **`task act`**) runs **`task validate`** first (same checks as the **pre-commit** `plugin-ci` hook), then **act** against **semantic-pull-request** and **release-please**. Plugin validation is **not** duplicated on GitHub; use pre-commit (or `task validate`) before you push. **semantic-pull-request** and **release-please** often do not finish inside act without GitHub API access; see **ACT.md**. ## Testing Guidelines @@ -293,7 +295,7 @@ Focus on: ## Versioning -**Plugin / repo version** - [release-please](https://github.com/googleapis/release-please) manages **one** semver for the whole distribution: `version.txt`, `.release-please-manifest.json`, `CHANGELOG.md`, and the `version` fields in **`.claude-plugin/plugin.json`** and **`.claude-plugin/marketplace.json`** (see `release-please-config.json` → `extra-files`). It does **not** bump per-skill versions; skills under `skills/` ship as part of that plugin release. +**Plugin / repo version** - [release-please](https://github.com/googleapis/release-please) manages **one** semver for the whole distribution: `version.txt`, `.release-please-manifest.json`, `CHANGELOG.md`, **`.claude-plugin/plugin.json`**, **`.claude-plugin/marketplace.json`** (`metadata.version` and each listed plugin’s `version`), **`.cursor-plugin/marketplace.json`** (`metadata.version`), **`plugins/**/.cursor-plugin/plugin.json`**, and the **docs/example** block in **`docs/add-a-plugin.md`** (see `release-please-config.json` → `extra-files`). CI runs **`scripts/verify-repo-versions.py`** to ensure `version.txt` matches those files. It does **not** bump per-skill versions; skills under `skills/` ship as part of that plugin release. **Change significance** (for commit messages and changelog tone; not separate skill semver in this repo): diff --git a/README.md b/README.md index 444177b..a589167 100644 --- a/README.md +++ b/README.md @@ -35,11 +35,9 @@ Pick **one** path. Official paths may require the plugin to be listed in each ma ```bash /plugin marketplace add Keeper-Security/agent-kit -/plugin install keeper-security@keeper-security +/plugin install keeper-secrets@keeper-security ``` -You can also install individual plugins if your marketplace lists them separately (e.g. `keeper-secrets@keeper-security`, `keeper-admin@keeper-security`, `keeper-setup@keeper-security`). - ### Any agent (Vercel Skills CLI) ```bash @@ -67,14 +65,13 @@ cd agent-kit | Claude Code | `~/.claude/skills/` | | Cursor | `~/.cursor/skills/` | | Codex | `~/.codex/skills/` | -| GitHub Copilot | `~/.github-copilot/extensions/skills/` | -| Windsurf / Roo Code | `~/.windsurf/skills/` (adjust per product docs) | +| GitHub Copilot | `~/.github/skills/` | ```bash -mkdir -p ~/.claude/skills # example: Claude Code +# example: Claude Code +mkdir -p ~/.claude/skills cp -r skills/keeper-secrets skills/keeper-admin skills/keeper-setup ~/.claude/skills/ ls ~/.claude/skills/ -# expect: keeper-secrets keeper-admin keeper-setup ``` Repeat for each agent you use; paths differ per product. @@ -93,20 +90,6 @@ Remove the skill directories from that agent’s `skills` folder, or with Vercel --- -## Repository layout - -- `.claude-plugin/` - Claude Code plugin and marketplace metadata -- `skills/` - `keeper-secrets`, `keeper-admin`, `keeper-setup` plus reference markdown -- `TEST_PROMPTS.md` - example prompts to check that agents load the right skill -- `Taskfile.yaml` - dev tasks (`task fmt`, `task install`, `task act` - see [`.github/ACT.md`](.github/ACT.md)) -- `CHANGELOG.md` - release history -- `version.txt` - single-line semver for the repo; **updated by release-please** when you merge its release PR (do not hand-edit for normal releases - see [CONTRIBUTING.md - Releases and changelog](CONTRIBUTING.md#releases-and-changelog)) -- `.github/workflows/` - `validate-plugin.yml` (manifests, frontmatter, references) and `test-skills.yml` (skill trigger patterns) - -CI validates manifests, skill frontmatter, and documentation structure on pushes and PRs. - ---- - ## Documentation **Keeper (install, configure, command reference):** @@ -115,8 +98,6 @@ CI validates manifests, skill frontmatter, and documentation structure on pushes - [Commander CLI](https://docs.keeper.io/en/keeperpam/commander-cli/overview) - install, shell, admin commands - [Keeper notation](https://docs.keeper.io/en/keeperpam/secrets-manager/about/keeper-notation) - `keeper://` references for secrets -**This repo:** After the CLIs are installed and authenticated, use each skill’s `SKILL.md` and `skills/*/references/*.md` for agent-oriented patterns (e.g. `ksm exec`, Docker/Kubernetes, CI, Commander admin). - --- ## Security @@ -128,24 +109,6 @@ The **[Keeper Security](https://keepersecurity.com)** agent kit is documentation --- -## Supported agents - -Works with many agents via the Vercel Skills CLI (Claude Code, Cursor, Codex, Copilot, Windsurf, Roo Code, Gemini CLI, and others-see upstream docs for the current list). - ---- - -## Troubleshooting (short) - -| Issue | What to try | -| --- | --- | -| Agent ignores Keeper skills | Confirm `skills/keeper-*/SKILL.md` exists under the right `~/.…/skills/` path; restart the agent | -| `ksm: command not found` | Install the KSM CLI per [KSM CLI docs](https://docs.keeper.io/en/keeperpam/secrets-manager/overview); ensure your shell `PATH` includes the install location | -| `keeper: command not found` | Install Commander per [Commander CLI docs](https://docs.keeper.io/en/keeperpam/commander-cli/overview) | -| Keyring / profile issues | See KSM CLI docs for keyring and profile options (including file-based config where appropriate) | -| Skills in one agent only | Install or copy skills per agent, or use `npx skills add` per environment | - ---- - ## Contributing We welcome issues and pull requests. diff --git a/Taskfile.yaml b/Taskfile.yaml index adeed1b..0d14c1f 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -25,3 +25,8 @@ tasks: - if ! command -v cargo-binstall >/dev/null 2>&1; then curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash; fi - cargo binstall rumdl prek - if [ -z "${SKIP_PREK_INSTALL:-}" ]; then prek install; else echo "Skipping prek install"; fi + + validate: + desc: Run full validation on plugins + cmds: + - ./scripts/validate-plugins.sh diff --git a/plugins/keeper-admin/.claude-plugin/plugin.json b/plugins/keeper-admin/.claude-plugin/plugin.json new file mode 100644 index 0000000..aaeb5bb --- /dev/null +++ b/plugins/keeper-admin/.claude-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "keeper-admin", + "description": "Manage Keeper Vault, enterprise administration, PAM, and privileged access using Keeper Commander CLI (keeper).", + "version": "0.1.0", + "author": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "license": "Apache-2.0", + "keywords": [ + "admin", + "vault", + "pam", + "enterprise", + "keeper", + "commander", + "sso", + "rotation", + "privileged-access" + ], + "repository": "https://github.com/Keeper-Security/agent-kit" +} diff --git a/plugins/keeper-admin/.cursor-plugin/plugin.json b/plugins/keeper-admin/.cursor-plugin/plugin.json new file mode 100644 index 0000000..052b4a6 --- /dev/null +++ b/plugins/keeper-admin/.cursor-plugin/plugin.json @@ -0,0 +1,23 @@ +{ + "name": "keeper-admin", + "description": "Manage Keeper Vault, enterprise administration, PAM, and privileged access using Keeper Commander CLI (keeper).", + "version": "0.1.0", + "author": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "license": "Apache-2.0", + "keywords": [ + "admin", + "vault", + "pam", + "enterprise", + "keeper", + "commander", + "sso", + "rotation", + "privileged-access" + ], + "repository": "https://github.com/Keeper-Security/agent-kit", + "logo": "assets/logo.png" +} diff --git a/plugins/keeper-admin/assets/logo.png b/plugins/keeper-admin/assets/logo.png new file mode 100644 index 0000000..d4d6f4f Binary files /dev/null and b/plugins/keeper-admin/assets/logo.png differ diff --git a/skills/keeper-admin/SKILL.md b/plugins/keeper-admin/skills/keeper-admin/SKILL.md similarity index 100% rename from skills/keeper-admin/SKILL.md rename to plugins/keeper-admin/skills/keeper-admin/SKILL.md diff --git a/skills/keeper-admin/references/commander-commands.md b/plugins/keeper-admin/skills/keeper-admin/references/commander-commands.md similarity index 100% rename from skills/keeper-admin/references/commander-commands.md rename to plugins/keeper-admin/skills/keeper-admin/references/commander-commands.md diff --git a/skills/keeper-admin/references/enterprise-mgmt.md b/plugins/keeper-admin/skills/keeper-admin/references/enterprise-mgmt.md similarity index 100% rename from skills/keeper-admin/references/enterprise-mgmt.md rename to plugins/keeper-admin/skills/keeper-admin/references/enterprise-mgmt.md diff --git a/skills/keeper-admin/references/pam-commands.md b/plugins/keeper-admin/skills/keeper-admin/references/pam-commands.md similarity index 100% rename from skills/keeper-admin/references/pam-commands.md rename to plugins/keeper-admin/skills/keeper-admin/references/pam-commands.md diff --git a/skills/keeper-admin/references/rotation-commands.md b/plugins/keeper-admin/skills/keeper-admin/references/rotation-commands.md similarity index 100% rename from skills/keeper-admin/references/rotation-commands.md rename to plugins/keeper-admin/skills/keeper-admin/references/rotation-commands.md diff --git a/plugins/keeper-secrets/.claude-plugin/plugin.json b/plugins/keeper-secrets/.claude-plugin/plugin.json new file mode 100644 index 0000000..c093763 --- /dev/null +++ b/plugins/keeper-secrets/.claude-plugin/plugin.json @@ -0,0 +1,24 @@ +{ + "name": "keeper-secrets", + "version": "0.1.0", + "description": "Retrieve, inject, and manage secrets from Keeper Vault using KSM CLI (ksm) for developer workflows, including ksm exec, interpolate, and Keeper notation.", + "author": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "license": "Apache-2.0", + "keywords": [ + "secrets", + "vault", + "credentials", + "ksm", + "devops", + "keeper", + "keeper-secrets-manager", + "non-human-identity", + "ci-cd", + "docker", + "kubernetes" + ], + "repository": "https://github.com/Keeper-Security/agent-kit" +} diff --git a/plugins/keeper-secrets/.cursor-plugin/plugin.json b/plugins/keeper-secrets/.cursor-plugin/plugin.json new file mode 100644 index 0000000..5e05db5 --- /dev/null +++ b/plugins/keeper-secrets/.cursor-plugin/plugin.json @@ -0,0 +1,25 @@ +{ + "name": "keeper-secrets", + "version": "0.1.0", + "description": "Retrieve, inject, and manage secrets from Keeper Vault using KSM CLI (ksm) for developer workflows, including ksm exec, interpolate, and Keeper notation.", + "author": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "license": "Apache-2.0", + "keywords": [ + "secrets", + "vault", + "credentials", + "ksm", + "devops", + "keeper", + "keeper-secrets-manager", + "non-human-identity", + "ci-cd", + "docker", + "kubernetes" + ], + "repository": "https://github.com/Keeper-Security/agent-kit", + "logo": "assets/logo.png" +} diff --git a/plugins/keeper-secrets/assets/logo.png b/plugins/keeper-secrets/assets/logo.png new file mode 100644 index 0000000..d4d6f4f Binary files /dev/null and b/plugins/keeper-secrets/assets/logo.png differ diff --git a/skills/keeper-secrets/SKILL.md b/plugins/keeper-secrets/skills/keeper-secrets/SKILL.md similarity index 100% rename from skills/keeper-secrets/SKILL.md rename to plugins/keeper-secrets/skills/keeper-secrets/SKILL.md diff --git a/skills/keeper-secrets/references/keeper-notation.md b/plugins/keeper-secrets/skills/keeper-secrets/references/keeper-notation.md similarity index 100% rename from skills/keeper-secrets/references/keeper-notation.md rename to plugins/keeper-secrets/skills/keeper-secrets/references/keeper-notation.md diff --git a/skills/keeper-secrets/references/ksm-commands.md b/plugins/keeper-secrets/skills/keeper-secrets/references/ksm-commands.md similarity index 100% rename from skills/keeper-secrets/references/ksm-commands.md rename to plugins/keeper-secrets/skills/keeper-secrets/references/ksm-commands.md diff --git a/skills/keeper-secrets/references/ksm-exec-patterns.md b/plugins/keeper-secrets/skills/keeper-secrets/references/ksm-exec-patterns.md similarity index 100% rename from skills/keeper-secrets/references/ksm-exec-patterns.md rename to plugins/keeper-secrets/skills/keeper-secrets/references/ksm-exec-patterns.md diff --git a/plugins/keeper-setup/.claude-plugin/plugin.json b/plugins/keeper-setup/.claude-plugin/plugin.json new file mode 100644 index 0000000..055fc47 --- /dev/null +++ b/plugins/keeper-setup/.claude-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "keeper-setup", + "version": "0.1.0", + "description": "Install and configure Keeper CLI tools (KSM CLI and Commander) for the Keeper Security agent kit, including profiles and authentication.", + "author": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "license": "Apache-2.0", + "keywords": [ + "setup", + "install", + "configuration", + "keeper", + "ksm", + "commander", + "cli", + "authentication", + "profiles" + ], + "repository": "https://github.com/Keeper-Security/agent-kit" +} diff --git a/plugins/keeper-setup/.cursor-plugin/plugin.json b/plugins/keeper-setup/.cursor-plugin/plugin.json new file mode 100644 index 0000000..43f7cd1 --- /dev/null +++ b/plugins/keeper-setup/.cursor-plugin/plugin.json @@ -0,0 +1,23 @@ +{ + "name": "keeper-setup", + "version": "0.1.0", + "description": "Install and configure Keeper CLI tools (KSM CLI and Commander) for the Keeper Security agent kit, including profiles and authentication.", + "author": { + "name": "Keeper Security", + "email": "commanders@keepersecurity.com" + }, + "license": "Apache-2.0", + "keywords": [ + "setup", + "install", + "configuration", + "keeper", + "ksm", + "commander", + "cli", + "authentication", + "profiles" + ], + "repository": "https://github.com/Keeper-Security/agent-kit", + "logo": "assets/logo.png" +} diff --git a/plugins/keeper-setup/assets/logo.png b/plugins/keeper-setup/assets/logo.png new file mode 100644 index 0000000..d4d6f4f Binary files /dev/null and b/plugins/keeper-setup/assets/logo.png differ diff --git a/skills/keeper-setup/SKILL.md b/plugins/keeper-setup/skills/keeper-setup/SKILL.md similarity index 100% rename from skills/keeper-setup/SKILL.md rename to plugins/keeper-setup/skills/keeper-setup/SKILL.md diff --git a/release-please-config.json b/release-please-config.json index a4c8d6b..56f2b95 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -5,8 +5,38 @@ "changelog-path": "CHANGELOG.md", "release-type": "simple", "extra-files": [ - ".claude-plugin/plugin.json", - ".claude-plugin/marketplace.json" + { + "type": "json", + "path": ".claude-plugin/marketplace.json", + "jsonpath": "$.metadata.version" + }, + { + "type": "json", + "path": ".claude-plugin/marketplace.json", + "jsonpath": "$.plugins[*].version" + }, + { + "type": "json", + "path": ".cursor-plugin/marketplace.json", + "jsonpath": "$.metadata.version" + }, + { + "type": "json", + "path": ".cursor-plugin/marketplace.json", + "jsonpath": "$.plugins[*].version" + }, + { + "type": "json", + "path": "plugins/**/.cursor-plugin/plugin.json", + "glob": true, + "jsonpath": "$.version" + }, + { + "type": "json", + "path": "plugins/**/.claude-plugin/plugin.json", + "glob": true, + "jsonpath": "$.version" + } ] } } diff --git a/scripts/act-all.sh b/scripts/act-all.sh index 137905e..657e1cf 100755 --- a/scripts/act-all.sh +++ b/scripts/act-all.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash -# Run act against every workflow under .github/workflows (local CI smoke test). +# Run act against GitHub Actions workflows (local smoke test). # -# validate-plugin + test-skills must succeed (same as GitHub push CI). +# Plugin validation runs via pre-commit locally (`task validate`); +# this script runs it first so act matches "full local check" expectations. # semantic-pull-request and release-please call the GitHub API; act usually # cannot complete those jobs locally - we still run them to exercise YAML and # container setup; non-zero exit is expected and does not fail this script. @@ -20,9 +21,8 @@ run_list_push() { act push -W ".github/workflows/${wf}" } -echo "=== Workflows: validate-plugin, test-skills (must pass locally) ===" -run_list_push validate-plugin.yml -run_list_push test-skills.yml +echo "=== Plugin validation (pre-commit plugin-ci / task validate) — must pass locally ===" +task validate echo "" echo "=== Workflow: semantic-pull-request (pull_request; GitHub API required for success) ===" @@ -53,4 +53,4 @@ if [[ "${rp}" -ne 0 ]]; then fi echo "" -echo "=== act run finished (validate-plugin + test-skills passed above) ===" +echo "=== act run finished (plugin validation + act smoke tests above) ===" diff --git a/scripts/validate-plugins.sh b/scripts/validate-plugins.sh new file mode 100755 index 0000000..282559c --- /dev/null +++ b/scripts/validate-plugins.sh @@ -0,0 +1,574 @@ +#!/usr/bin/env bash +# Validate plugins (Claude/Cursor manifests, marketplaces, skills, references), +# skill triggers, section coverage, markdown code-block heuristics, and root docs. +# Requires bash, jq, and standard POSIX utilities (awk, grep, find, sort). +# +# Markdown example checks run in parallel. Override concurrency with VALIDATE_MAX_JOBS +# (default: min(8, CPU count)). +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +export AGENT_KIT_ROOT="$ROOT" +cd "$ROOT" + +errors=() +add_err() { errors+=("$1"); } + +ok() { printf ' ✓ %s\n' "$1"; } + +if ! command -v jq >/dev/null 2>&1; then + echo "ERROR: jq is required for JSON validation (https://jqlang.org/)" >&2 + exit 2 +fi + +validate_plugin_json() { + local label="$1" json="$2" + local k + for k in name description version author license keywords repository; do + if ! jq -e ".${k} != null" "$json" >/dev/null 2>&1; then + add_err "$label: missing required field '$k'" + fi + done + if ! jq -e '.author | type == "object"' "$json" >/dev/null 2>&1; then + add_err "$label: 'author' must be an object" + else + if ! jq -e '.author.name != null and (.author.name | type == "string")' "$json" >/dev/null 2>&1; then + add_err "$label: author missing 'name'" + fi + if ! jq -e '.author.email != null and (.author.email | type == "string")' "$json" >/dev/null 2>&1; then + add_err "$label: author missing 'email'" + fi + fi + if ! jq -e '.keywords | type == "array"' "$json" >/dev/null 2>&1; then + add_err "$label: 'keywords' must be an array" + fi +} + +validate_cursor_logo() { + local plugin_root="$1" label="$2" json="$3" + if ! jq -e '.logo != null' "$json" >/dev/null 2>&1; then + return 0 + fi + if ! jq -e '.logo | type == "string"' "$json" >/dev/null 2>&1; then + add_err "$label: 'logo' must be a string" + return 0 + fi + local logo + logo=$(jq -r '.logo' "$json") + if [[ ! -f "$plugin_root/$logo" ]]; then + add_err "$label: logo file not found: $plugin_root/$logo" + fi +} + +# SKILL.md: single-line name + description in YAML frontmatter (repo convention) +validate_skill_md() { + local f="$1" rel="$2" + local first fm name_line desc_line name_val body + + first=$(head -n1 "$f" | tr -d '\r') + if [[ "$first" != '---' ]]; then + add_err "$rel: missing opening ---" + return + fi + + fm=$(awk ' + /^---$/ { if (++c == 1) next; if (c == 2) exit } + c == 1 { print } + ' "$f") + + if [[ -z "$fm" ]]; then + add_err "$rel: invalid frontmatter block" + return + fi + + name_line=$(echo "$fm" | grep -E '^name:' | head -n1 || true) + desc_line=$(echo "$fm" | grep -E '^description:' | head -n1 || true) + + if [[ -z "$name_line" ]]; then + add_err "$rel: frontmatter missing 'name'" + else + name_val=$(echo "$name_line" | sed 's/^name:[[:space:]]*//') + if ((${#name_val} > 64)); then + add_err "$rel: 'name' must be ≤64 characters" + fi + fi + + if [[ -z "$desc_line" ]]; then + add_err "$rel: frontmatter missing 'description'" + fi + + body=$(awk ' + /^---$/ { if (++c == 2) { body=1; next } } + body { print } + ' "$f") + if [[ -z "${body//[$' \t\r\n']/}" ]]; then + add_err "$rel: no body after frontmatter" + fi +} + +validate_marketplace() { + local mp="$1" + local n i name src exp got + + if [[ ! -f "$mp" ]]; then + add_err "Missing marketplace: $mp" + return + fi + + if ! jq -e . "$mp" >/dev/null 2>&1; then + add_err "$mp: invalid JSON" + return + fi + + for k in name owner metadata plugins; do + if ! jq -e ".${k} != null" "$mp" >/dev/null 2>&1; then + add_err "$mp: missing top-level key '$k'" + fi + done + + if ! jq -e '.plugins | type == "array"' "$mp" >/dev/null 2>&1; then + add_err "$mp: 'plugins' must be an array" + return + fi + + n=$(jq '.plugins | length' "$mp") + u=$(jq '.plugins | map(.name) | unique | length' "$mp") + if [[ "$n" -ne "$u" ]]; then + add_err "$mp: duplicate plugin name in plugins[]" + fi + + for ((i = 0; i < n; i++)); do + if ! jq -e ".plugins[$i] | type == \"object\"" "$mp" >/dev/null 2>&1; then + add_err "$mp: plugins[$i] must be an object" + continue + fi + for req in name source description version; do + if ! jq -e ".plugins[$i].${req} != null" "$mp" >/dev/null 2>&1; then + add_err "$mp: plugins[$i] missing '$req'" + fi + done + name=$(jq -r ".plugins[$i].name" "$mp") + src=$(jq -r ".plugins[$i].source" "$mp") + src="${src#./}" + if [[ ! -d "$ROOT/$src" ]]; then + add_err "$mp: plugin '$name' source not a directory: $src" + else + exp="$ROOT/plugins/$name" + got="$(cd "$ROOT/$src" && pwd -P)" + if [[ "$(cd "$exp" 2>/dev/null && pwd -P)" != "$got" ]]; then + add_err "$mp: plugin '$name' source path should be ./plugins/$name, got ./$src" + fi + fi + done + + mapfile -t mp_names < <(jq -r '.plugins[].name' "$mp" | sort -u) + mapfile -t want < <(printf '%s\n' "${plugin_ids[@]}" | sort -u) + + if ! cmp -s <(printf '%s\n' "${mp_names[@]}") <(printf '%s\n' "${want[@]}"); then + local miss extra + miss=$(comm -23 <(printf '%s\n' "${want[@]}") <(printf '%s\n' "${mp_names[@]}") | paste -sd, - || true) + extra=$(comm -13 <(printf '%s\n' "${want[@]}") <(printf '%s\n' "${mp_names[@]}") | paste -sd, - || true) + [[ -n "$miss" ]] && add_err "$mp: marketplace missing plugins: ${miss//,/, }" + [[ -n "$extra" ]] && add_err "$mp: marketplace has unknown plugins: ${extra//,/, }" + fi +} + +# --- Skill trigger keywords (description substring match, case-insensitive) --- +_desc_contains() { + local hay="$1" needle="$2" + local h n + h=$(printf '%s' "$hay" | tr '[:upper:]' '[:lower:]') + n=$(printf '%s' "$needle" | tr '[:upper:]' '[:lower:]') + [[ "$h" == *"$n"* ]] +} + +check_skill_triggers() { + local f skill_name fm desc total found kw + mapfile -t _skill_paths < <(find "$ROOT/plugins" -type f -path '*/skills/*/SKILL.md' | sort) + for f in "${_skill_paths[@]}"; do + skill_name=$(basename "$(dirname "$f")") + fm=$(awk ' + /^---$/ { if (++c == 1) next; if (c == 2) exit } + c == 1 { print } + ' "$f") + if [[ -z "$fm" ]]; then + echo "⚠ $skill_name: No frontmatter" + continue + fi + desc=$(echo "$fm" | grep -E '^description:' | head -n1 | sed 's/^description:[[:space:]]*//') + if [[ -z "$desc" ]]; then + echo "⚠ $skill_name: No description in frontmatter" + continue + fi + echo "" + echo "📋 Skill: $skill_name" + echo " Description length: ${#desc} chars" + total=0 + found=0 + case "$skill_name" in + keeper-secrets) + for kw in keeper ksm secret vault credentials 'keeper://' password 'api key' certificate 'ci/cd' docker kubernetes; do + ((total++)) || true + if _desc_contains "$desc" "$kw"; then + ((found++)) || true + fi + done + ;; + keeper-admin) + for kw in keeper commander admin enterprise 'user management' pam rotation session; do + ((total++)) || true + if _desc_contains "$desc" "$kw"; then + ((found++)) || true + fi + done + ;; + keeper-setup) + for kw in install keeper setup configure ksm commander cli; do + ((total++)) || true + if _desc_contains "$desc" "$kw"; then + ((found++)) || true + fi + done + ;; + *) + echo " ⚠ No expected trigger list for skill id $skill_name" + continue + ;; + esac + if [[ "$found" -gt 0 ]]; then + echo " ✓ Found $found/$total expected triggers" + else + echo " ⚠ No expected triggers found in description" + fi + done + echo "" + echo "✓ Analyzed ${#_skill_paths[@]} skills" +} + +_check_skill_sections() { + local path="$1" plugin_id="$2" + shift 2 + local secs=("$@") content missing s + if [[ ! -f "$ROOT/$path" ]]; then + add_err "Missing: $path" + return + fi + content=$(cat "$ROOT/$path") + missing=() + for s in "${secs[@]}"; do + if ! printf '%s' "$content" | grep -Fq "$s"; then + missing+=("$s") + fi + done + if [[ ${#missing[@]} -gt 0 ]]; then + echo "⚠ $plugin_id: Missing sections: ${missing[*]}" + else + echo "✓ $plugin_id: All expected sections present" + fi +} + +check_skill_content_sections() { + echo "Checking skill content coverage..." + _check_skill_sections "plugins/keeper-secrets/skills/keeper-secrets/SKILL.md" keeper-secrets \ + "When to Use KSM" "Prerequisites" "Core Commands" "ksm exec" "ksm interpolate" "Guardrails" + _check_skill_sections "plugins/keeper-admin/skills/keeper-admin/SKILL.md" keeper-admin \ + "When to Use Commander" "Prerequisites" "Vault Operations" "Enterprise Administration" "Guardrails" + _check_skill_sections "plugins/keeper-setup/skills/keeper-setup/SKILL.md" keeper-setup \ + "Quick Install" "KSM CLI" "Commander" "Troubleshooting" +} + +# Scan markdown for fenced code blocks; warn on missing language or suspicious literals. +check_md_file_examples() { + local filepath="$1" + local rel="${filepath#"$ROOT"/}" + local _fence + _fence=$(printf '\x60\x60\x60') + local line in_block=0 lang="" code="" issues=() + + while IFS= read -r line || [[ -n "${line:-}" ]]; do + if [[ "${line:0:3}" == "$_fence" ]]; then + if [[ "$in_block" -eq 1 ]]; then + # closing fence + if [[ -z "$lang" && "$code" == *$'\n'* && -n "${code//[$' \t\r\n']/}" ]]; then + issues+=("Code block without language specification") + fi + if [[ -n "$code" ]]; then + if printf '%s' "$code" | grep -qiE 'password[[:space:]]*=[[:space:]]*['\''"]' && ! printf '%s' "$code" | grep -qi 'keeper'; then + issues+=("Possible hardcoded password in example") + fi + if printf '%s' "$code" | grep -qiE 'token[[:space:]]*=[[:space:]]*['\''"]' && ! printf '%s' "$code" | grep -qi 'keeper'; then + issues+=("Possible hardcoded token in example") + fi + fi + in_block=0 + lang="" + code="" + else + # opening fence + in_block=1 + lang=$(echo "$line" | sed 's/^```//' | awk '{print $1}' | tr -d '\r') + code="" + fi + continue + fi + if [[ "$in_block" -eq 1 ]]; then + code+="$line"$'\n' + fi + done <"$filepath" + + if [[ ${#issues[@]} -gt 0 ]]; then + echo "" + echo "⚠ $rel:" + for m in "${issues[@]}"; do + echo " - $m" + done + else + echo "✓ $rel" + fi +} + +_validate_max_jobs() { + local max="${VALIDATE_MAX_JOBS:-8}" + local ncpu + ncpu=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4) + if ((max > ncpu)); then max=$ncpu; fi + if ((max < 1)); then max=1; fi + printf '%s' "$max" +} + +check_md_examples_all() { + echo "Checking for insecure examples..." + local max + max=$(_validate_max_jobs) + mapfile -t _md_files < <(find "$ROOT/plugins" -type f -name '*.md' | sort) + if [[ ${#_md_files[@]} -eq 0 ]]; then + return 0 + fi + local tmpdir job active + tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/validate-plugins-md.XXXXXX") + trap 'rm -rf "$tmpdir"' RETURN + job=0 + for f in "${_md_files[@]}"; do + while true; do + active=$(jobs -p 2>/dev/null | wc -l | tr -d ' ') + [[ "${active:-0}" -lt "$max" ]] && break + sleep 0.02 + done + job=$((job + 1)) + ( check_md_file_examples "$f" >"$tmpdir/$(printf '%05d' "$job").out" ) & + done + wait 2>/dev/null || true + local o + mapfile -t _outs < <(find "$tmpdir" -maxdepth 1 -name '*.out' -print | sort -V) + for o in "${_outs[@]}"; do + cat "$o" + done + rm -rf "$tmpdir" + trap - RETURN +} + +check_root_docs() { + echo "Checking documentation files..." + local doc sections missing s + # (file, pipe-separated sections; empty = existence only) + for doc in README.md SECURITY.md CONTRIBUTING.md LICENSE.md; do + if [[ ! -f "$ROOT/$doc" ]]; then + echo "⚠ Missing: $doc" + continue + fi + missing=() + case "$doc" in + README.md) + for s in Installation Prerequisites Documentation Contributing License Security; do + grep -Fq "$s" "$ROOT/$doc" || missing+=("$s") + done + ;; + SECURITY.md) + for s in Reporting "Supported versions"; do + grep -Fq "$s" "$ROOT/$doc" || missing+=("$s") + done + ;; + CONTRIBUTING.md) + for s in "How to Contribute" "Testing Guidelines" "Submission Checklist"; do + grep -Fq "$s" "$ROOT/$doc" || missing+=("$s") + done + ;; + LICENSE.md) + missing=() + ;; + esac + if [[ ${#missing[@]} -gt 0 ]]; then + echo "⚠ $doc: Missing: ${missing[*]}" + else + echo "✓ $doc" + fi + done +} + +# --- discover plugins (sorted ids) --- +plugin_ids=() +while IFS= read -r line; do + [[ -n "$line" ]] && plugin_ids+=("$line") +done < <( + for d in "$ROOT"/plugins/*/; do + [[ -d "$d" ]] || continue + base=$(basename "$d") + [[ "$base" == .* ]] && continue + if [[ -f "${d}.claude-plugin/plugin.json" || -f "${d}.cursor-plugin/plugin.json" ]]; then + printf '%s\n' "$base" + fi + done | sort +) + +if [[ ! -d "$ROOT/plugins" ]]; then + echo "ERROR: plugins directory not found: $ROOT/plugins" + exit 1 +fi + +if [[ ${#plugin_ids[@]} -eq 0 ]]; then + echo "ERROR: No plugin directories under $ROOT/plugins" + exit 1 +fi + +echo "== Plugin manifests (Claude + Cursor) ==" +for pid in "${plugin_ids[@]}"; do + pr="$ROOT/plugins/$pid" + echo "" + echo "--- $pid ---" + cj="$pr/.claude-plugin/plugin.json" + uj="$pr/.cursor-plugin/plugin.json" + + if [[ ! -f "$cj" ]]; then + add_err "Missing Claude manifest: $cj" + else + if ! jq -e . "$cj" >/dev/null 2>&1; then + add_err "$cj: invalid JSON" + else + validate_plugin_json "$cj" "$cj" + pname=$(jq -r '.name' "$cj") + if [[ "$pname" != "$pid" ]]; then + add_err "$cj: 'name' must match directory ('$pid')" + else + ver=$(jq -r '.version' "$cj") + ok "Claude plugin.json ($ver)" + fi + fi + fi + + if [[ ! -f "$uj" ]]; then + add_err "Missing Cursor manifest: $uj" + else + if ! jq -e . "$uj" >/dev/null 2>&1; then + add_err "$uj: invalid JSON" + else + validate_plugin_json "$uj" "$uj" + validate_cursor_logo "$pr" "$uj" "$uj" + pname=$(jq -r '.name' "$uj") + if [[ "$pname" != "$pid" ]]; then + add_err "$uj: 'name' must match directory ('$pid')" + else + ver=$(jq -r '.version' "$uj") + ok "Cursor plugin.json ($ver)" + fi + fi + fi +done + +echo "" +echo "== Root marketplaces ==" +for mp_path in "$ROOT/.claude-plugin/marketplace.json" "$ROOT/.cursor-plugin/marketplace.json"; do + label="Claude" + [[ "$mp_path" == *cursor* ]] && label="Cursor" + echo "" + echo "--- $label ---" + n_err=${#errors[@]} + validate_marketplace "$mp_path" + if [[ ${#errors[@]} -eq "$n_err" ]]; then + n=$(jq '.plugins | length' "$mp_path") + ok "${mp_path#"$ROOT"/}: $n plugins" + fi +done + +echo "" +echo "== SKILL.md files ==" +mapfile -t skill_files < <(find "$ROOT/plugins" -type f -path '*/skills/*/SKILL.md' | sort) +if [[ ${#skill_files[@]} -eq 0 ]]; then + add_err "No SKILL.md files under $ROOT/plugins" +else + for sf in "${skill_files[@]}"; do + rel="${sf#"$ROOT"/}" + echo "" + echo "Validating $rel" + n_err=${#errors[@]} + validate_skill_md "$sf" "$rel" + if [[ ${#errors[@]} -eq "$n_err" ]]; then + ok "frontmatter and body" + fi + done +fi + +echo "" +echo "== Reference docs ==" +for rel in \ + "skills/keeper-secrets/references/ksm-commands.md" \ + "skills/keeper-secrets/references/keeper-notation.md" \ + "skills/keeper-secrets/references/ksm-exec-patterns.md"; do + p="$ROOT/plugins/keeper-secrets/$rel" + if [[ ! -f "$p" ]]; then + add_err "Missing reference: $p" + else + n=$(wc -l <"$p" | tr -d ' ') + if [[ "$n" -lt 5 ]]; then + add_err "Reference file suspiciously short ($n lines): $p" + else + ok "keeper-secrets/$rel ($n lines)" + fi + fi +done + +for rel in \ + "skills/keeper-admin/references/commander-commands.md" \ + "skills/keeper-admin/references/enterprise-mgmt.md" \ + "skills/keeper-admin/references/pam-commands.md" \ + "skills/keeper-admin/references/rotation-commands.md"; do + p="$ROOT/plugins/keeper-admin/$rel" + if [[ ! -f "$p" ]]; then + add_err "Missing reference: $p" + else + n=$(wc -l <"$p" | tr -d ' ') + if [[ "$n" -lt 5 ]]; then + add_err "Reference file suspiciously short ($n lines): $p" + else + ok "keeper-admin/$rel ($n lines)" + fi + fi +done + +echo "" +echo "== Skill trigger keywords ==" +check_skill_triggers + +echo "" +echo "== Skill content sections ==" +check_skill_content_sections + +echo "" +echo "== Skill examples (markdown code blocks) ==" +check_md_examples_all + +echo "" +echo "== Root documentation ==" +check_root_docs + +if [[ ${#errors[@]} -gt 0 ]]; then + echo "" + echo "❌ Validation failed:" + for e in "${errors[@]}"; do + echo " - $e" + done + exit 1 +fi + +echo "" +echo "✓ All validations passed" diff --git a/version.txt b/version.txt index 9084fa2..6e8bf73 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.1.0 +0.1.0