Skip to content

Commit 767e6da

Browse files
gotalabclaude
andcommitted
feat: add skill update command and configure ruff linting
- Implement `skillport update` CLI command for updating skills from origin - Add origin tracking with TypedDict (type, url, ref, tree_sha) - Support GitHub origin updates with tree SHA comparison - Configure ruff with F, E, W, I (isort), UP (pyupgrade) rules - Fix circular imports by using deep module imports - Modernize type hints (List -> list, Optional -> |) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 955a4cd commit 767e6da

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+2663
-290
lines changed

pyproject.toml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,18 @@ skillport = "skillport.__main__:main"
6464
skillport-mcp = "skillport.__main__:main" # legacy alias
6565

6666
[tool.ruff]
67+
target-version = "py310"
68+
line-length = 100
69+
6770
exclude = [
6871
".agent/skills",
69-
"test_lancedb_scores",
70-
"test_verification_db",
7172
]
73+
74+
[tool.ruff.lint]
75+
select = ["F", "E", "W", "I", "UP"]
76+
ignore = [
77+
"E501", # line-too-long(formatterに任せる)
78+
]
79+
80+
[tool.ruff.lint.isort]
81+
known-first-party = ["skillport"]

src/skillport/__init__.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
11
"""SkillPort package entry."""
22

3-
from skillport.shared.config import Config
4-
from skillport.shared import exceptions
53
from skillport.modules import (
6-
search_skills,
7-
load_skill,
8-
add_skill,
9-
remove_skill,
10-
list_skills,
11-
read_skill_file,
12-
validate_skill,
13-
SkillSummary,
14-
SkillDetail,
15-
FileContent,
16-
SearchResult,
174
AddResult,
18-
RemoveResult,
5+
FileContent,
6+
IndexBuildResult,
197
ListResult,
8+
ReindexDecision,
9+
RemoveResult,
10+
SearchResult,
11+
SkillDetail,
12+
SkillSummary,
2013
ValidationIssue,
2114
ValidationResult,
15+
add_skill,
2216
build_index,
23-
should_reindex,
24-
index_search,
2517
get_by_id,
18+
index_search,
2619
list_all,
27-
IndexBuildResult,
28-
ReindexDecision,
20+
list_skills,
21+
load_skill,
22+
read_skill_file,
23+
remove_skill,
24+
search_skills,
25+
should_reindex,
26+
validate_skill,
2927
)
28+
from skillport.shared import exceptions
29+
from skillport.shared.config import Config
3030

3131
__all__ = [
3232
"Config",

src/skillport/interfaces/cli/app.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,32 @@
66
- add: Install skills from various sources
77
- list: Show installed skills
88
- remove: Uninstall skills
9+
- update: Update skills from original sources
910
- lint: Validate skill definitions
1011
- serve: Start MCP server
1112
- doc: Generate skill documentation for AGENTS.md
1213
"""
1314

14-
from pathlib import Path
1515
import os
16-
from typing import Optional
16+
from pathlib import Path
1717

1818
import typer
1919

2020
from skillport.shared.config import Config
21-
from .config import load_project_config
22-
from .commands.search import search
23-
from .commands.show import show
21+
22+
from .auto_index import should_auto_reindex
2423
from .commands.add import add
25-
from .commands.remove import remove
26-
from .commands.list import list_cmd
27-
from .commands.lint import lint
28-
from .commands.serve import serve
2924
from .commands.doc import doc
3025
from .commands.init import init
26+
from .commands.lint import lint
27+
from .commands.list import list_cmd
28+
from .commands.remove import remove
29+
from .commands.search import search
30+
from .commands.serve import serve
31+
from .commands.show import show
32+
from .commands.update import update
33+
from .config import load_project_config
3134
from .theme import VERSION, console
32-
from .auto_index import should_auto_reindex
3335

3436

3537
def version_callback(value: bool):
@@ -54,25 +56,25 @@ def version_callback(value: bool):
5456
@app.callback(invoke_without_command=True)
5557
def main(
5658
ctx: typer.Context,
57-
version: Optional[bool] = typer.Option(
59+
version: bool | None = typer.Option(
5860
None,
5961
"--version",
6062
"-v",
6163
callback=version_callback,
6264
is_eager=True,
6365
help="Show version and exit",
6466
),
65-
skills_dir: Optional[Path] = typer.Option(
67+
skills_dir: Path | None = typer.Option(
6668
None,
6769
"--skills-dir",
6870
help="Override skills directory (CLI > env > default)",
6971
),
70-
db_path: Optional[Path] = typer.Option(
72+
db_path: Path | None = typer.Option(
7173
None,
7274
"--db-path",
7375
help="Override LanceDB path (CLI > env > default)",
7476
),
75-
auto_reindex: Optional[bool] = typer.Option(
77+
auto_reindex: bool | None = typer.Option(
7678
None,
7779
"--auto-reindex/--no-auto-reindex",
7880
help="Automatically rebuild index if stale (default: enabled; respects SKILLPORT_AUTO_REINDEX)",
@@ -162,6 +164,19 @@ def main(
162164
" skillport remove team/skill --force",
163165
)(remove)
164166

167+
app.command(
168+
"update",
169+
help="Update skills from their original sources.\n\n"
170+
"By default shows available updates. Use --all to update all,\n"
171+
"or specify a skill ID to update one.\n\n"
172+
"[bold]Examples:[/bold]\n\n"
173+
" skillport update\n\n"
174+
" skillport update my-skill\n\n"
175+
" skillport update --all\n\n"
176+
" skillport update my-skill --force\n\n"
177+
" skillport update --all --dry-run",
178+
)(update)
179+
165180
app.command(
166181
"lint",
167182
help="Validate skill definitions.\n\n"

src/skillport/interfaces/cli/auto_index.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@
33
from __future__ import annotations
44

55
import os
6-
from typing import Optional
76

87
from skillport.modules.indexing import build_index, should_reindex
98
from skillport.shared.config import Config
9+
1010
from .theme import stderr_console
1111

1212

13-
def _env_auto_reindex_default() -> Optional[bool]:
13+
def _env_auto_reindex_default() -> bool | None:
1414
"""Parse SKILLPORT_AUTO_REINDEX env var.
1515
1616
Returns:

src/skillport/interfaces/cli/commands/add.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,23 @@
77
from rich.progress import Progress, SpinnerColumn, TextColumn
88
from rich.prompt import Prompt
99

10-
from skillport.modules.skills import add_skill
11-
from skillport.modules.skills.internal import detect_skills, fetch_github_source, parse_github_url
1210
from skillport.modules.indexing import build_index
11+
from skillport.modules.skills import add_skill
12+
from skillport.modules.skills.internal import (
13+
detect_skills,
14+
fetch_github_source_with_info,
15+
parse_github_url,
16+
)
17+
1318
from ..context import get_config
14-
from ..theme import console, stderr_console, is_interactive, print_error, print_success, print_warning
19+
from ..theme import (
20+
console,
21+
is_interactive,
22+
print_error,
23+
print_success,
24+
print_warning,
25+
stderr_console,
26+
)
1527

1628

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

3749

38-
def _detect_skills_from_source(source: str) -> tuple[list[str], str, Path | None]:
39-
"""Detect skills from source. Returns (skill_names, source_name, temp_dir)."""
50+
def _detect_skills_from_source(source: str) -> tuple[list[str], str, Path | None, str]:
51+
"""Detect skills from source. Returns (skill_names, source_name, temp_dir, commit_sha)."""
4052
source_name = _get_source_name(source)
4153
temp_dir: Path | None = None
54+
commit_sha: str = ""
4255

4356
if source.startswith("https://"):
4457
try:
@@ -50,27 +63,29 @@ def _detect_skills_from_source(source: str) -> tuple[list[str], str, Path | None
5063
transient=True,
5164
) as progress:
5265
progress.add_task(f"Fetching {source}...", total=None)
53-
temp_dir = fetch_github_source(source)
66+
fetch_result = fetch_github_source_with_info(source)
67+
temp_dir = fetch_result.extracted_path
68+
commit_sha = fetch_result.commit_sha
5469

5570
skills = detect_skills(Path(temp_dir))
5671
skill_names = [s.name for s in skills] if skills else [source_name]
57-
return skill_names, source_name, temp_dir
72+
return skill_names, source_name, temp_dir, commit_sha
5873
except Exception as e:
5974
if temp_dir and Path(temp_dir).exists():
6075
shutil.rmtree(temp_dir, ignore_errors=True)
6176
print_warning(f"Could not fetch source: {e}")
62-
return [source_name], source_name, None
77+
return [source_name], source_name, None, ""
6378

6479
source_path = Path(source).expanduser().resolve()
6580
if source_path.exists() and source_path.is_dir():
6681
try:
6782
skills = detect_skills(source_path)
6883
skill_names = [s.name for s in skills] if skills else [source_name]
69-
return skill_names, source_name, None
84+
return skill_names, source_name, None, ""
7085
except Exception:
71-
return [source_name], source_name, None
86+
return [source_name], source_name, None, ""
7287

73-
return [source_name], source_name, None
88+
return [source_name], source_name, None, ""
7489

7590

7691
def add(
@@ -116,11 +131,12 @@ def add(
116131
):
117132
"""Add skills from various sources."""
118133
temp_dir: Path | None = None
134+
commit_sha: str = ""
119135

120136
try:
121137
# Interactive namespace selection for external sources
122138
if _is_external_source(source) and keep_structure is None and namespace is None:
123-
skill_names, source_name, temp_dir = _detect_skills_from_source(source)
139+
skill_names, source_name, temp_dir, commit_sha = _detect_skills_from_source(source)
124140
is_single = len(skill_names) == 1
125141

126142
# Non-interactive mode: use sensible defaults
@@ -137,12 +153,12 @@ def add(
137153
console.print(f"\n[bold]Found {len(skill_names)} skill(s):[/bold] {skill_display}")
138154
console.print("[bold]Where to add?[/bold]")
139155
if is_single:
140-
console.print(f" [cyan][1][/cyan] Flat → skills/{skill_names[0]}/")
141-
console.print(f" [cyan][2][/cyan] Namespace → skills/[dim]<ns>[/dim]/{skill_names[0]}/")
156+
console.print(f" [info][1][/info] Flat → skills/{skill_names[0]}/")
157+
console.print(f" [info][2][/info] Namespace → skills/[dim]<ns>[/dim]/{skill_names[0]}/")
142158
else:
143-
console.print(f" [cyan][1][/cyan] Flat → skills/{skill_names[0]}/, skills/{skill_names[1]}/, ...")
144-
console.print(f" [cyan][2][/cyan] Namespace → skills/[dim]<ns>[/dim]/{skill_names[0]}/, ...")
145-
console.print(" [cyan][3][/cyan] Skip")
159+
console.print(f" [info][1][/info] Flat → skills/{skill_names[0]}/, skills/{skill_names[1]}/, ...")
160+
console.print(f" [info][2][/info] Namespace → skills/[dim]<ns>[/dim]/{skill_names[0]}/, ...")
161+
console.print(" [info][3][/info] Skip")
146162
choice = Prompt.ask("Choice", choices=["1", "2", "3"], default="1")
147163

148164
if choice == "3":
@@ -163,6 +179,7 @@ def add(
163179
namespace=namespace,
164180
name=name,
165181
pre_fetched_dir=temp_dir,
182+
pre_fetched_commit_sha=commit_sha,
166183
)
167184

168185
# Auto-reindex if skills were added

src/skillport/interfaces/cli/commands/doc.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66

77
import re
88
from pathlib import Path
9-
from typing import Optional
109

1110
import typer
1211

13-
from skillport.modules.skills import list_skills, SkillSummary
12+
from skillport.modules.skills import SkillSummary, list_skills
1413
from skillport.shared.config import Config
14+
15+
from ..auto_index import ensure_index_fresh
1516
from ..config import load_project_config
1617
from ..theme import console
17-
from ..auto_index import ensure_index_fresh
1818

1919
MARKER_START = "<!-- SKILLPORT_START -->"
2020
MARKER_END = "<!-- SKILLPORT_END -->"
@@ -172,12 +172,12 @@ def doc(
172172
"--append/--replace",
173173
help="Append to existing file or replace entirely",
174174
),
175-
skills_filter: Optional[str] = typer.Option(
175+
skills_filter: str | None = typer.Option(
176176
None,
177177
"--skills",
178178
help="Comma-separated skill IDs to include",
179179
),
180-
category_filter: Optional[str] = typer.Option(
180+
category_filter: str | None = typer.Option(
181181
None,
182182
"--category",
183183
help="Comma-separated categories to include",

src/skillport/interfaces/cli/commands/init.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
"""
66

77
from pathlib import Path
8-
from typing import Optional
98

109
import typer
1110

1211
from skillport.modules.indexing import build_index
1312
from skillport.modules.skills import list_skills
13+
1414
from ..context import get_config
15-
from .doc import generate_skills_block, update_agents_md
1615
from ..theme import console, print_banner
16+
from .doc import generate_skills_block, update_agents_md
1717

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

111111
def init(
112112
ctx: typer.Context,
113-
skills_dir: Optional[Path] = typer.Option(
113+
skills_dir: Path | None = typer.Option(
114114
None,
115115
"--skills-dir",
116116
"-d",
117117
help="Skills directory path",
118118
),
119-
instructions: Optional[list[str]] = typer.Option(
119+
instructions: list[str] | None = typer.Option(
120120
None,
121121
"--instructions",
122122
"-i",

src/skillport/interfaces/cli/commands/lint.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from skillport.modules.indexing import list_all
77
from skillport.modules.skills.public.validation import validate_skill
8+
89
from ..context import get_config
910
from ..theme import console, print_success, print_warning
1011

src/skillport/interfaces/cli/commands/list.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
import typer
44
from rich.table import Table
55

6-
from skillport.modules.skills import list_skills, ListResult
6+
from skillport.modules.skills import ListResult, list_skills
7+
8+
from ..auto_index import ensure_index_fresh
79
from ..context import get_config
810
from ..theme import console, empty_skills_panel
9-
from ..auto_index import ensure_index_fresh
1011

1112

1213
def list_cmd(

0 commit comments

Comments
 (0)