fix(obsidian-kb): fix SKILL.md YAML format and restore files

This commit is contained in:
OpenClaw Agent
2026-02-07 05:50:00 +08:00
parent a0e43a5136
commit 1f76a40d1f
108 changed files with 9321 additions and 2503 deletions
+222
View File
@@ -0,0 +1,222 @@
#!/usr/bin/env python3
# /// script
# requires-python = ">=3.11"
# dependencies = ["rich", "typer"]
# ///
"""
GitHub PR Tool - Fetch, preview, merge, and test PRs locally.
"""
import json
import subprocess
import sys
from pathlib import Path
from typing import Optional
import typer
from rich.console import Console
from rich.panel import Panel
from rich.table import Table
app = typer.Typer(help="GitHub PR Tool - fetch, preview, merge, and test PRs locally")
console = Console()
def run(cmd: list[str], check: bool = True, capture: bool = True) -> subprocess.CompletedProcess:
"""Run a command and return result."""
result = subprocess.run(cmd, capture_output=capture, text=True)
if check and result.returncode != 0:
console.print(f"[red]Command failed:[/red] {' '.join(cmd)}")
if result.stderr:
console.print(f"[dim]{result.stderr}[/dim]")
raise typer.Exit(1)
return result
def get_pr_info(repo: str, pr_number: int) -> dict:
"""Fetch PR details from GitHub."""
result = run([
"gh", "pr", "view", str(pr_number),
"--repo", repo,
"--json", "title,author,state,headRefName,baseRefName,additions,deletions,files,statusCheckRollup,comments,url"
])
return json.loads(result.stdout)
def detect_package_manager() -> Optional[str]:
"""Detect which package manager to use."""
cwd = Path.cwd()
if (cwd / "pnpm-lock.yaml").exists():
return "pnpm"
if (cwd / "yarn.lock").exists():
return "yarn"
if (cwd / "bun.lockb").exists():
return "bun"
if (cwd / "package-lock.json").exists():
return "npm"
if (cwd / "package.json").exists():
return "npm"
return None
@app.command()
def preview(
repo: str = typer.Argument(..., help="Repository (owner/repo)"),
pr_number: int = typer.Argument(..., help="PR number"),
):
"""Preview a PR's details, files, and CI status."""
console.print(f"[blue]Fetching PR #{pr_number} from {repo}...[/blue]")
pr = get_pr_info(repo, pr_number)
# Header panel
status_color = "green" if pr["state"] == "OPEN" else "red"
console.print(Panel(
f"[bold]{pr['title']}[/bold]\n\n"
f"Author: {pr['author']['login']}\n"
f"State: [{status_color}]{pr['state']}[/{status_color}]\n"
f"Branch: {pr['headRefName']} → {pr['baseRefName']}\n"
f"Changes: [green]+{pr['additions']}[/green] / [red]-{pr['deletions']}[/red]\n"
f"URL: {pr['url']}",
title=f"PR #{pr_number}",
))
# Files table
if pr.get("files"):
table = Table(title="Files Changed")
table.add_column("File", style="cyan")
table.add_column("+", style="green", justify="right")
table.add_column("-", style="red", justify="right")
for f in pr["files"][:20]: # Limit to 20 files
table.add_row(f["path"], str(f["additions"]), str(f["deletions"]))
if len(pr["files"]) > 20:
table.add_row(f"... and {len(pr['files']) - 20} more", "", "")
console.print(table)
# CI Status
checks = pr.get("statusCheckRollup", [])
if checks:
console.print("\n[bold]CI Status:[/bold]")
for check in checks[:10]:
status = check.get("conclusion", check.get("status", "PENDING"))
icon = "✅" if status == "SUCCESS" else "❌" if status == "FAILURE" else "⏳"
name = check.get("name", "Unknown")[:50]
console.print(f" {icon} {name}")
@app.command()
def fetch(
repo: str = typer.Argument(..., help="Repository (owner/repo)"),
pr_number: int = typer.Argument(..., help="PR number"),
branch: Optional[str] = typer.Option(None, "--branch", "-b", help="Local branch name"),
remote: str = typer.Option("upstream", "--remote", "-r", help="Remote name"),
):
"""Fetch a PR branch locally."""
branch_name = branch or f"pr/{pr_number}"
console.print(f"[blue]Fetching PR #{pr_number} from {remote}...[/blue]")
# Fetch the PR
run(["git", "fetch", remote, f"pull/{pr_number}/head:{branch_name}"])
console.print(f"[green]✓ PR fetched to branch:[/green] {branch_name}")
console.print(f"\n[dim]To checkout: git checkout {branch_name}[/dim]")
console.print(f"[dim]To merge: git merge {branch_name}[/dim]")
@app.command()
def merge(
repo: str = typer.Argument(..., help="Repository (owner/repo)"),
pr_number: int = typer.Argument(..., help="PR number"),
remote: str = typer.Option("upstream", "--remote", "-r", help="Remote name"),
no_install: bool = typer.Option(False, "--no-install", help="Skip dependency install"),
branch: Optional[str] = typer.Option(None, "--branch", "-b", help="Local branch name"),
):
"""Fetch and merge a PR into the current branch."""
branch_name = branch or f"pr/{pr_number}"
# Get PR info first
console.print(f"[blue]Fetching PR #{pr_number} info...[/blue]")
pr = get_pr_info(repo, pr_number)
console.print(f"[bold]{pr['title']}[/bold]")
console.print(f"[dim]+{pr['additions']} / -{pr['deletions']} across {len(pr.get('files', []))} files[/dim]\n")
# Fetch the PR
console.print(f"[blue]Fetching PR branch...[/blue]")
run(["git", "fetch", remote, f"pull/{pr_number}/head:{branch_name}"])
# Merge
console.print(f"[blue]Merging into current branch...[/blue]")
result = run(["git", "merge", branch_name, "--no-edit"], check=False)
if result.returncode != 0:
console.print("[yellow]⚠️ Merge conflicts detected. Resolve manually.[/yellow]")
raise typer.Exit(1)
console.print("[green]✓ Merged successfully[/green]")
# Install dependencies
if not no_install:
pm = detect_package_manager()
if pm:
console.print(f"\n[blue]Installing dependencies with {pm}...[/blue]")
install_cmd = [pm, "install"]
if pm == "pnpm":
install_cmd.append("--force") # Handle patch issues
run(install_cmd, capture=False)
console.print(f"[green]✓ Dependencies installed[/green]")
@app.command()
def test(
repo: str = typer.Argument(..., help="Repository (owner/repo)"),
pr_number: int = typer.Argument(..., help="PR number"),
remote: str = typer.Option("upstream", "--remote", "-r", help="Remote name"),
):
"""Full test cycle: fetch, merge, install, build, test."""
branch_name = f"pr/{pr_number}"
# Get PR info
console.print(f"[blue]Fetching PR #{pr_number} info...[/blue]")
pr = get_pr_info(repo, pr_number)
console.print(Panel(f"[bold]{pr['title']}[/bold]\n+{pr['additions']} / -{pr['deletions']}", title=f"PR #{pr_number}"))
# Fetch
console.print(f"\n[blue]Step 1/4: Fetching PR branch...[/blue]")
run(["git", "fetch", remote, f"pull/{pr_number}/head:{branch_name}"])
console.print("[green]✓ Fetched[/green]")
# Merge
console.print(f"\n[blue]Step 2/4: Merging...[/blue]")
result = run(["git", "merge", branch_name, "--no-edit"], check=False)
if result.returncode != 0:
console.print("[red]✗ Merge conflicts - resolve manually[/red]")
raise typer.Exit(1)
console.print("[green]✓ Merged[/green]")
# Install
pm = detect_package_manager()
if pm:
console.print(f"\n[blue]Step 3/4: Installing dependencies ({pm})...[/blue]")
install_cmd = [pm, "install"]
if pm == "pnpm":
install_cmd.append("--force")
run(install_cmd, capture=False)
console.print("[green]✓ Installed[/green]")
# Build
console.print(f"\n[blue]Step 4/4: Building...[/blue]")
build_result = run([pm, "run", "build"], check=False, capture=False)
if build_result.returncode != 0:
console.print("[yellow]⚠️ Build had errors (may still work)[/yellow]")
else:
console.print("[green]✓ Built[/green]")
console.print(f"\n[bold green]✓ PR #{pr_number} merged and tested![/bold green]")
if __name__ == "__main__":
app()