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
102 changes: 51 additions & 51 deletions src/skillport/interfaces/cli/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ def version_callback(value: bool):
app = typer.Typer(
name="skillport",
help="[bold]⚓ SkillPort[/bold] - All Your Agent Skills in One Place\n\n"
"A CLI and MCP server for managing, searching, and serving skills to AI agents.\n\n"
"[dim]Docs: https://github.com/gotalab/skillport[/dim]",
"A CLI and MCP server for managing, searching, and serving skills to AI agents.\n\n"
"[dim]Docs: https://github.com/gotalab/skillport[/dim]",
rich_markup_mode="rich",
no_args_is_help=False,
add_completion=True,
Expand Down Expand Up @@ -110,100 +110,100 @@ def main(
app.command(
"init",
help="Initialize SkillPort for a project.\n\n"
"[bold]Examples:[/bold]\n\n"
" skillport init\n\n"
" skillport init --yes\n\n"
" skillport init -d .agent/skills -i AGENTS.md",
"[bold]Examples:[/bold]\n\n"
" skillport init\n\n"
" skillport init --yes\n\n"
" skillport init -d .agent/skills -i AGENTS.md",
)(init)

app.command(
"search",
help="Search for skills matching a query.\n\n"
"[bold]Examples:[/bold]\n\n"
" skillport search 'PDF extraction'\n\n"
" skillport search code --limit 5\n\n"
" skillport search test --json",
"[bold]Examples:[/bold]\n\n"
" skillport search 'PDF extraction'\n\n"
" skillport search code --limit 5\n\n"
" skillport search test --json",
)(search)

app.command(
"show",
help="Show skill details and instructions.\n\n"
"[bold]Examples:[/bold]\n\n"
" skillport show hello-world\n\n"
" skillport show team/code-review\n\n"
" skillport show pdf --json",
"[bold]Examples:[/bold]\n\n"
" skillport show hello-world\n\n"
" skillport show team/code-review\n\n"
" skillport show pdf --json",
)(show)

app.command(
"add",
help="Add skills from various sources.\n\n"
"[bold]Sources:[/bold]\n\n"
" [dim]Built-in:[/dim] hello-world, template\n\n"
" [dim]Local:[/dim] ./my-skill/, ./collection/\n\n"
" [dim]GitHub:[/dim] https://github.com/user/repo\n\n"
"[bold]Examples:[/bold]\n\n"
" skillport add hello-world\n\n"
" skillport add ./my-skills/ --namespace team\n\n"
" skillport add https://github.com/user/repo --yes",
"[bold]Sources:[/bold]\n\n"
" [dim]Built-in:[/dim] hello-world, template\n\n"
" [dim]Local:[/dim] ./my-skill/, ./collection/\n\n"
" [dim]GitHub:[/dim] https://github.com/user/repo\n\n"
"[bold]Examples:[/bold]\n\n"
" skillport add hello-world\n\n"
" skillport add ./my-skills/ --namespace team\n\n"
" skillport add https://github.com/user/repo --yes",
)(add)

app.command(
"list",
help="List installed skills.\n\n"
"[bold]Examples:[/bold]\n\n"
" skillport list\n\n"
" skillport list --limit 20\n\n"
" skillport list --json",
"[bold]Examples:[/bold]\n\n"
" skillport list\n\n"
" skillport list --limit 20\n\n"
" skillport list --json",
)(list_cmd)

app.command(
"remove",
help="Remove an installed skill.\n\n"
"[bold]Examples:[/bold]\n\n"
" skillport remove hello-world\n\n"
" skillport remove team/skill --force",
"[bold]Examples:[/bold]\n\n"
" skillport remove hello-world\n\n"
" 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",
"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"
"[bold]Examples:[/bold]\n\n"
" skillport lint\n\n"
" skillport lint hello-world",
"[bold]Examples:[/bold]\n\n"
" skillport lint\n\n"
" skillport lint hello-world",
)(lint)

app.command(
"serve",
help="Start the MCP server.\n\n"
"By default, runs in stdio mode (Local) for direct agent integration.\n"
"Use --http for HTTP server (Remote) mode.\n\n"
"[bold]Examples:[/bold]\n\n"
" skillport serve\n\n"
" skillport serve --reindex\n\n"
" skillport serve --http --port 8080",
"By default, runs in stdio mode (Local) for direct agent integration.\n"
"Use --http for HTTP server (Remote) mode.\n\n"
"[bold]Examples:[/bold]\n\n"
" skillport serve\n\n"
" skillport serve --reindex\n\n"
" skillport serve --http --port 8080",
)(serve)

app.command(
"doc",
help="Generate skill documentation for AGENTS.md.\n\n"
"[bold]Examples:[/bold]\n\n"
" skillport doc\n\n"
" skillport doc --all\n\n"
" skillport doc -o .claude/AGENTS.md\n\n"
" skillport doc --category development,testing",
"[bold]Examples:[/bold]\n\n"
" skillport doc\n\n"
" skillport doc --all\n\n"
" skillport doc -o .claude/AGENTS.md\n\n"
" skillport doc --category development,testing",
)(doc)


Expand Down
4 changes: 1 addition & 3 deletions src/skillport/interfaces/cli/auto_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ def ensure_index_fresh(ctx, config: Config, *, force: bool = False) -> None:
return

reason = "force" if force else decision.reason
stderr_console.print(
f"[dim]Auto reindexing (reason={reason})[/dim]", highlight=False
)
stderr_console.print(f"[dim]Auto reindexing (reason={reason})[/dim]", highlight=False)
result = build_index(config=config, force=force)
if not result.success:
stderr_console.print(f"[error]Reindex failed: {result.message}[/error]")
Expand Down
42 changes: 30 additions & 12 deletions src/skillport/interfaces/cli/commands/add.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,16 +148,26 @@ def add(
namespace = namespace or _get_default_namespace(source)
else:
# Interactive mode
skill_display = skill_names[0] if is_single else ", ".join(skill_names[:3]) + ("..." if len(skill_names) > 3 else "")
skill_display = (
skill_names[0]
if is_single
else ", ".join(skill_names[:3]) + ("..." if len(skill_names) > 3 else "")
)

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" [info][1][/info] Flat → skills/{skill_names[0]}/")
console.print(f" [info][2][/info] Namespace → skills/[dim]<ns>[/dim]/{skill_names[0]}/")
console.print(
f" [info][2][/info] Namespace → skills/[dim]<ns>[/dim]/{skill_names[0]}/"
)
else:
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(
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")

Expand Down Expand Up @@ -195,12 +205,14 @@ def add(

# JSON output for programmatic use
if json_output:
console.print_json(data={
"added": result.added,
"skipped": result.skipped,
"message": result.message,
"details": [d.model_dump() for d in getattr(result, "details", [])],
})
console.print_json(
data={
"added": result.added,
"skipped": result.skipped,
"message": result.message,
"details": [d.model_dump() for d in getattr(result, "details", [])],
}
)
if not result.added and result.skipped:
raise typer.Exit(code=1)
return
Expand All @@ -212,7 +224,11 @@ def add(
if result.skipped:
for skill_id in result.skipped:
detail_reason = next(
(d.message for d in getattr(result, "details", []) if d.skill_id == skill_id and d.message),
(
d.message
for d in getattr(result, "details", [])
if d.skill_id == skill_id and d.message
),
None,
)
skip_reason = detail_reason or result.message or "skipped"
Expand All @@ -222,7 +238,9 @@ def add(
if result.added and not result.skipped:
print_success(f"Added {len(result.added)} skill(s)")
elif result.added and result.skipped:
print_warning(f"Added {len(result.added)}, skipped {len(result.skipped)} ({result.message})")
print_warning(
f"Added {len(result.added)}, skipped {len(result.skipped)} ({result.message})"
)
elif result.skipped:
print_error(
result.message or f"All {len(result.skipped)} skill(s) skipped",
Expand Down
3 changes: 1 addition & 2 deletions src/skillport/interfaces/cli/commands/doc.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,8 +249,7 @@ def doc(
# Use instruction files from project config
if not project_config.instructions:
console.print(
"[warning]No instruction files in .skillportrc. "
"Using default AGENTS.md[/warning]"
"[warning]No instruction files in .skillportrc. Using default AGENTS.md[/warning]"
)
output_files = [Path("./AGENTS.md")]
else:
Expand Down
9 changes: 6 additions & 3 deletions src/skillport/interfaces/cli/commands/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
# (display_name, actual_path) - None means "use display as path"
DEFAULT_SKILLS_DIRS = [
("~/.skillport/skills (default)", "~/.skillport/skills"),
(".claude/skills", None),
(".claude/skills (Claude Code)", ".claude/skills"),
("~/.codex/skills (Codex)", "~/.codex/skills"),
(".agent/skills", None),
]

Expand Down Expand Up @@ -96,7 +97,7 @@ def _create_skillportrc(
# Convert skills_dir to string (relative if possible)
skills_dir_str = str(skills_dir)
if skills_dir_str.startswith(str(Path.home())):
skills_dir_str = "~" + skills_dir_str[len(str(Path.home())):]
skills_dir_str = "~" + skills_dir_str[len(str(Path.home())) :]

with open(path, "w", encoding="utf-8") as f:
f.write("# SkillPort Configuration\n")
Expand Down Expand Up @@ -214,4 +215,6 @@ def init(
console.print("[bold green]✨ Ready![/bold green] Start your coding agent to use skills.")
console.print("[dim] Run 'skillport add hello-world' to add your first skill[/dim]")
if skill_count > 5 and instructions:
console.print("[dim] Tip: Edit instruction files to remove skills not relevant to this project[/dim]")
console.print(
"[dim] Tip: Edit instruction files to remove skills not relevant to this project[/dim]"
)
46 changes: 25 additions & 21 deletions src/skillport/interfaces/cli/commands/lint.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,17 @@ def lint(
skills = list_all(limit=1000, config=config)

if skill_id:
skills = [
s for s in skills if s.get("id") == skill_id or s.get("name") == skill_id
]
skills = [s for s in skills if s.get("id") == skill_id or s.get("name") == skill_id]

if not skills:
if json_output:
console.print_json(data={
"valid": False,
"message": "No skills found",
"skills": [],
})
console.print_json(
data={
"valid": False,
"message": "No skills found",
"skills": [],
}
)
else:
print_warning("No skills found to validate.")
raise typer.Exit(code=1)
Expand Down Expand Up @@ -67,15 +67,17 @@ def lint(

# JSON output
if json_output:
console.print_json(data={
"valid": total_fatal == 0,
"skills": all_results,
"summary": {
"total_skills": len(skills),
"fatal_issues": total_fatal,
"warning_issues": total_warning,
},
})
console.print_json(
data={
"valid": total_fatal == 0,
"skills": all_results,
"summary": {
"total_skills": len(skills),
"fatal_issues": total_fatal,
"warning_issues": total_warning,
},
}
)
if total_fatal > 0:
raise typer.Exit(code=1)
return
Expand Down Expand Up @@ -106,10 +108,12 @@ def lint(
if total_warning > 0:
summary_parts.append(f"[warning]{total_warning} warning[/warning]")

console.print(Panel(
f"Checked {len(skills)} skill(s): {', '.join(summary_parts)}",
border_style=summary_style,
))
console.print(
Panel(
f"Checked {len(skills)} skill(s): {', '.join(summary_parts)}",
border_style=summary_style,
)
)

if total_fatal > 0:
raise typer.Exit(code=1)
4 changes: 3 additions & 1 deletion src/skillport/interfaces/cli/commands/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,6 @@ def list_cmd(

# Show truncation notice if applicable
if result.total > limit:
console.print(f"[dim]Showing {limit} of {result.total} skills. Use --limit to show more.[/dim]")
console.print(
f"[dim]Showing {limit} of {result.total} skills. Use --limit to show more.[/dim]"
)
20 changes: 12 additions & 8 deletions src/skillport/interfaces/cli/commands/remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ def remove(
confirm = typer.confirm(f"Remove '{skill_id}'?", default=False)
if not confirm:
if json_output:
console.print_json(data={
"success": False,
"message": "Cancelled by user",
})
console.print_json(
data={
"success": False,
"message": "Cancelled by user",
}
)
else:
console.print("[dim]Cancelled[/dim]")
raise typer.Exit(code=1)
Expand All @@ -66,10 +68,12 @@ def remove(
build_index(config=config, force=False)

if json_output:
console.print_json(data={
"success": result.success,
"message": result.message,
})
console.print_json(
data={
"success": result.success,
"message": result.message,
}
)
if not result.success:
raise typer.Exit(code=1)
return
Expand Down
Loading