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
14 changes: 12 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,18 @@ skillport = "skillport.__main__:main"
skillport-mcp = "skillport.__main__:main" # legacy alias

[tool.ruff]
target-version = "py310"
line-length = 100

exclude = [
".agent/skills",
"test_lancedb_scores",
"test_verification_db",
]

[tool.ruff.lint]
select = ["F", "E", "W", "I", "UP"]
ignore = [
"E501", # line-too-long(formatterに任せる)
Copy link

Copilot AI Dec 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ruff configuration comment "line-too-long(formatterに任せる)" contains Japanese "(leave it to the formatter)". This should be translated to English: "line-too-long (handled by formatter)".

Suggested change
"E501", # line-too-long(formatterに任せる)
"E501", # line-too-long (handled by formatter)

Copilot uses AI. Check for mistakes.
]

[tool.ruff.lint.isort]
known-first-party = ["skillport"]
36 changes: 18 additions & 18 deletions src/skillport/__init__.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
"""SkillPort package entry."""

from skillport.shared.config import Config
from skillport.shared import exceptions
from skillport.modules import (
search_skills,
load_skill,
add_skill,
remove_skill,
list_skills,
read_skill_file,
validate_skill,
SkillSummary,
SkillDetail,
FileContent,
SearchResult,
AddResult,
RemoveResult,
FileContent,
IndexBuildResult,
ListResult,
ReindexDecision,
RemoveResult,
SearchResult,
SkillDetail,
SkillSummary,
ValidationIssue,
ValidationResult,
add_skill,
build_index,
should_reindex,
index_search,
get_by_id,
index_search,
list_all,
IndexBuildResult,
ReindexDecision,
list_skills,
load_skill,
read_skill_file,
remove_skill,
search_skills,
should_reindex,
validate_skill,
)
from skillport.shared import exceptions
from skillport.shared.config import Config

__all__ = [
"Config",
Expand Down
43 changes: 29 additions & 14 deletions src/skillport/interfaces/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,32 @@
- add: Install skills from various sources
- list: Show installed skills
- remove: Uninstall skills
- update: Update skills from original sources
- lint: Validate skill definitions
- serve: Start MCP server
- doc: Generate skill documentation for AGENTS.md
"""

from pathlib import Path
import os
from typing import Optional
from pathlib import Path

import typer

from skillport.shared.config import Config
from .config import load_project_config
from .commands.search import search
from .commands.show import show

from .auto_index import should_auto_reindex
from .commands.add import add
from .commands.remove import remove
from .commands.list import list_cmd
from .commands.lint import lint
from .commands.serve import serve
from .commands.doc import doc
from .commands.init import init
from .commands.lint import lint
from .commands.list import list_cmd
from .commands.remove import remove
from .commands.search import search
from .commands.serve import serve
from .commands.show import show
from .commands.update import update
from .config import load_project_config
from .theme import VERSION, console
from .auto_index import should_auto_reindex


def version_callback(value: bool):
Expand All @@ -54,25 +56,25 @@ def version_callback(value: bool):
@app.callback(invoke_without_command=True)
def main(
ctx: typer.Context,
version: Optional[bool] = typer.Option(
version: bool | None = typer.Option(
None,
"--version",
"-v",
callback=version_callback,
is_eager=True,
help="Show version and exit",
),
skills_dir: Optional[Path] = typer.Option(
skills_dir: Path | None = typer.Option(
None,
"--skills-dir",
help="Override skills directory (CLI > env > default)",
),
db_path: Optional[Path] = typer.Option(
db_path: Path | None = typer.Option(
None,
"--db-path",
help="Override LanceDB path (CLI > env > default)",
),
auto_reindex: Optional[bool] = typer.Option(
auto_reindex: bool | None = typer.Option(
None,
"--auto-reindex/--no-auto-reindex",
help="Automatically rebuild index if stale (default: enabled; respects SKILLPORT_AUTO_REINDEX)",
Expand Down Expand Up @@ -162,6 +164,19 @@ def main(
" skillport remove team/skill --force",
)(remove)

app.command(
"update",
help="Update skills from their original sources.\n\n"
"By default shows available updates. Use --all to update all,\n"
"or specify a skill ID to update one.\n\n"
"[bold]Examples:[/bold]\n\n"
" skillport update\n\n"
" skillport update my-skill\n\n"
" skillport update --all\n\n"
" skillport update my-skill --force\n\n"
" skillport update --all --dry-run",
)(update)

app.command(
"lint",
help="Validate skill definitions.\n\n"
Expand Down
4 changes: 2 additions & 2 deletions src/skillport/interfaces/cli/auto_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
from __future__ import annotations

import os
from typing import Optional

from skillport.modules.indexing import build_index, should_reindex
from skillport.shared.config import Config

from .theme import stderr_console


def _env_auto_reindex_default() -> Optional[bool]:
def _env_auto_reindex_default() -> bool | None:
"""Parse SKILLPORT_AUTO_REINDEX env var.

Returns:
Expand Down
51 changes: 34 additions & 17 deletions src/skillport/interfaces/cli/commands/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,23 @@
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.prompt import Prompt

from skillport.modules.skills import add_skill
from skillport.modules.skills.internal import detect_skills, fetch_github_source, parse_github_url
from skillport.modules.indexing import build_index
from skillport.modules.skills import add_skill
from skillport.modules.skills.internal import (
detect_skills,
fetch_github_source_with_info,
parse_github_url,
)

from ..context import get_config
from ..theme import console, stderr_console, is_interactive, print_error, print_success, print_warning
from ..theme import (
console,
is_interactive,
print_error,
print_success,
print_warning,
stderr_console,
)


def _is_external_source(source: str) -> bool:
Expand All @@ -35,10 +47,11 @@ def _get_default_namespace(source: str) -> str:
return Path(source.rstrip("/")).name


def _detect_skills_from_source(source: str) -> tuple[list[str], str, Path | None]:
"""Detect skills from source. Returns (skill_names, source_name, temp_dir)."""
def _detect_skills_from_source(source: str) -> tuple[list[str], str, Path | None, str]:
"""Detect skills from source. Returns (skill_names, source_name, temp_dir, commit_sha)."""
source_name = _get_source_name(source)
temp_dir: Path | None = None
commit_sha: str = ""

if source.startswith("https://"):
try:
Expand All @@ -50,27 +63,29 @@ def _detect_skills_from_source(source: str) -> tuple[list[str], str, Path | None
transient=True,
) as progress:
progress.add_task(f"Fetching {source}...", total=None)
temp_dir = fetch_github_source(source)
fetch_result = fetch_github_source_with_info(source)
temp_dir = fetch_result.extracted_path
commit_sha = fetch_result.commit_sha

skills = detect_skills(Path(temp_dir))
skill_names = [s.name for s in skills] if skills else [source_name]
return skill_names, source_name, temp_dir
return skill_names, source_name, temp_dir, commit_sha
except Exception as e:
if temp_dir and Path(temp_dir).exists():
shutil.rmtree(temp_dir, ignore_errors=True)
print_warning(f"Could not fetch source: {e}")
return [source_name], source_name, None
return [source_name], source_name, None, ""

source_path = Path(source).expanduser().resolve()
if source_path.exists() and source_path.is_dir():
try:
skills = detect_skills(source_path)
skill_names = [s.name for s in skills] if skills else [source_name]
return skill_names, source_name, None
return skill_names, source_name, None, ""
except Exception:
return [source_name], source_name, None
return [source_name], source_name, None, ""

return [source_name], source_name, None
return [source_name], source_name, None, ""


def add(
Expand Down Expand Up @@ -116,11 +131,12 @@ def add(
):
"""Add skills from various sources."""
temp_dir: Path | None = None
commit_sha: str = ""

try:
# Interactive namespace selection for external sources
if _is_external_source(source) and keep_structure is None and namespace is None:
skill_names, source_name, temp_dir = _detect_skills_from_source(source)
skill_names, source_name, temp_dir, commit_sha = _detect_skills_from_source(source)
is_single = len(skill_names) == 1

# Non-interactive mode: use sensible defaults
Expand All @@ -137,12 +153,12 @@ def add(
console.print(f"\n[bold]Found {len(skill_names)} skill(s):[/bold] {skill_display}")
console.print("[bold]Where to add?[/bold]")
if is_single:
console.print(f" [cyan][1][/cyan] Flat → skills/{skill_names[0]}/")
console.print(f" [cyan][2][/cyan] Namespace → skills/[dim]<ns>[/dim]/{skill_names[0]}/")
console.print(f" [info][1][/info] Flat → skills/{skill_names[0]}/")
console.print(f" [info][2][/info] Namespace → skills/[dim]<ns>[/dim]/{skill_names[0]}/")
else:
console.print(f" [cyan][1][/cyan] Flat → skills/{skill_names[0]}/, skills/{skill_names[1]}/, ...")
console.print(f" [cyan][2][/cyan] Namespace → skills/[dim]<ns>[/dim]/{skill_names[0]}/, ...")
console.print(" [cyan][3][/cyan] Skip")
console.print(f" [info][1][/info] Flat → skills/{skill_names[0]}/, skills/{skill_names[1]}/, ...")
console.print(f" [info][2][/info] Namespace → skills/[dim]<ns>[/dim]/{skill_names[0]}/, ...")
console.print(" [info][3][/info] Skip")
choice = Prompt.ask("Choice", choices=["1", "2", "3"], default="1")

if choice == "3":
Expand All @@ -163,6 +179,7 @@ def add(
namespace=namespace,
name=name,
pre_fetched_dir=temp_dir,
pre_fetched_commit_sha=commit_sha,
)

# Auto-reindex if skills were added
Expand Down
10 changes: 5 additions & 5 deletions src/skillport/interfaces/cli/commands/doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@

import re
from pathlib import Path
from typing import Optional

import typer

from skillport.modules.skills import list_skills, SkillSummary
from skillport.modules.skills import SkillSummary, list_skills
from skillport.shared.config import Config

from ..auto_index import ensure_index_fresh
from ..config import load_project_config
from ..theme import console
from ..auto_index import ensure_index_fresh

MARKER_START = "<!-- SKILLPORT_START -->"
MARKER_END = "<!-- SKILLPORT_END -->"
Expand Down Expand Up @@ -172,12 +172,12 @@ def doc(
"--append/--replace",
help="Append to existing file or replace entirely",
),
skills_filter: Optional[str] = typer.Option(
skills_filter: str | None = typer.Option(
None,
"--skills",
help="Comma-separated skill IDs to include",
),
category_filter: Optional[str] = typer.Option(
category_filter: str | None = typer.Option(
None,
"--category",
help="Comma-separated categories to include",
Expand Down
8 changes: 4 additions & 4 deletions src/skillport/interfaces/cli/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
"""

from pathlib import Path
from typing import Optional

import typer

from skillport.modules.indexing import build_index
from skillport.modules.skills import list_skills

from ..context import get_config
from .doc import generate_skills_block, update_agents_md
from ..theme import console, print_banner
from .doc import generate_skills_block, update_agents_md

# Default choices for interactive mode
# (display_name, actual_path) - None means "use display as path"
Expand Down Expand Up @@ -110,13 +110,13 @@ def _create_skillportrc(

def init(
ctx: typer.Context,
skills_dir: Optional[Path] = typer.Option(
skills_dir: Path | None = typer.Option(
None,
"--skills-dir",
"-d",
help="Skills directory path",
),
instructions: Optional[list[str]] = typer.Option(
instructions: list[str] | None = typer.Option(
None,
"--instructions",
"-i",
Expand Down
1 change: 1 addition & 0 deletions src/skillport/interfaces/cli/commands/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from skillport.modules.indexing import list_all
from skillport.modules.skills.public.validation import validate_skill

from ..context import get_config
from ..theme import console, print_success, print_warning

Expand Down
5 changes: 3 additions & 2 deletions src/skillport/interfaces/cli/commands/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import typer
from rich.table import Table

from skillport.modules.skills import list_skills, ListResult
from skillport.modules.skills import ListResult, list_skills

from ..auto_index import ensure_index_fresh
from ..context import get_config
from ..theme import console, empty_skills_panel
from ..auto_index import ensure_index_fresh


def list_cmd(
Expand Down
Loading