universal-agent-context 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uacs/__init__.py +12 -0
- uacs/adapters/__init__.py +19 -0
- uacs/adapters/agent_skill_adapter.py +202 -0
- uacs/adapters/agents_md_adapter.py +330 -0
- uacs/adapters/base.py +261 -0
- uacs/adapters/clinerules_adapter.py +39 -0
- uacs/adapters/cursorrules_adapter.py +39 -0
- uacs/api.py +262 -0
- uacs/cli/__init__.py +6 -0
- uacs/cli/context.py +349 -0
- uacs/cli/main.py +195 -0
- uacs/cli/mcp.py +115 -0
- uacs/cli/memory.py +142 -0
- uacs/cli/packages.py +309 -0
- uacs/cli/skills.py +144 -0
- uacs/cli/utils.py +24 -0
- uacs/config/repositories.yaml +26 -0
- uacs/context/__init__.py +0 -0
- uacs/context/agent_context.py +406 -0
- uacs/context/shared_context.py +661 -0
- uacs/context/unified_context.py +332 -0
- uacs/mcp_server_entry.py +80 -0
- uacs/memory/__init__.py +5 -0
- uacs/memory/simple_memory.py +255 -0
- uacs/packages/__init__.py +26 -0
- uacs/packages/manager.py +413 -0
- uacs/packages/models.py +60 -0
- uacs/packages/sources.py +270 -0
- uacs/protocols/__init__.py +5 -0
- uacs/protocols/mcp/__init__.py +8 -0
- uacs/protocols/mcp/manager.py +77 -0
- uacs/protocols/mcp/skills_server.py +700 -0
- uacs/skills_validator.py +367 -0
- uacs/utils/__init__.py +5 -0
- uacs/utils/paths.py +24 -0
- uacs/visualization/README.md +132 -0
- uacs/visualization/__init__.py +36 -0
- uacs/visualization/models.py +195 -0
- uacs/visualization/static/index.html +857 -0
- uacs/visualization/storage.py +402 -0
- uacs/visualization/visualization.py +328 -0
- uacs/visualization/web_server.py +364 -0
- universal_agent_context-0.2.0.dist-info/METADATA +873 -0
- universal_agent_context-0.2.0.dist-info/RECORD +47 -0
- universal_agent_context-0.2.0.dist-info/WHEEL +4 -0
- universal_agent_context-0.2.0.dist-info/entry_points.txt +2 -0
- universal_agent_context-0.2.0.dist-info/licenses/LICENSE +21 -0
uacs/cli/memory.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""CLI commands for memory management."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
from rich.console import Console
|
|
8
|
+
from rich.table import Table
|
|
9
|
+
|
|
10
|
+
from uacs.memory.simple_memory import SimpleMemoryStore
|
|
11
|
+
from uacs.cli.utils import get_project_root
|
|
12
|
+
|
|
13
|
+
app = typer.Typer(help="Manage memory storage and queries")
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _get_store(project_root: Path | None = None) -> SimpleMemoryStore:
|
|
18
|
+
"""Create a memory store for the current project."""
|
|
19
|
+
root = project_root or get_project_root()
|
|
20
|
+
global_override = os.environ.get("UACS_GLOBAL_MEMORY")
|
|
21
|
+
global_path = Path(global_override) if global_override else None
|
|
22
|
+
return SimpleMemoryStore(project_path=root, global_path=global_path)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _validate_scope(scope: str) -> str:
|
|
26
|
+
scope_normalized = scope.lower()
|
|
27
|
+
if scope_normalized not in {"project", "global", "both"}:
|
|
28
|
+
raise typer.BadParameter("Scope must be project, global, or both")
|
|
29
|
+
return scope_normalized
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@app.command("init")
|
|
33
|
+
def init_memory(
|
|
34
|
+
scope: str = typer.Option(
|
|
35
|
+
"project",
|
|
36
|
+
"--scope",
|
|
37
|
+
"-s",
|
|
38
|
+
help="Scope to initialize: project, global, or both",
|
|
39
|
+
),
|
|
40
|
+
):
|
|
41
|
+
"""Initialize simple JSON memory directories."""
|
|
42
|
+
scope_normalized = _validate_scope(scope)
|
|
43
|
+
store = _get_store()
|
|
44
|
+
|
|
45
|
+
initialized_paths = []
|
|
46
|
+
for current_scope in (
|
|
47
|
+
["project", "global"] if scope_normalized == "both" else [scope_normalized]
|
|
48
|
+
):
|
|
49
|
+
path = store.init_storage(current_scope)
|
|
50
|
+
initialized_paths.append(path)
|
|
51
|
+
|
|
52
|
+
for path in initialized_paths:
|
|
53
|
+
console.print(f"[green]✓[/green] Initialized memory at {path}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@app.command("stats")
|
|
57
|
+
def memory_stats():
|
|
58
|
+
"""Show memory statistics for project and global scopes."""
|
|
59
|
+
store = _get_store()
|
|
60
|
+
stats = store.get_stats()
|
|
61
|
+
|
|
62
|
+
table = Table(title="Memory Statistics")
|
|
63
|
+
table.add_column("Scope", style="cyan")
|
|
64
|
+
table.add_column("Entries", justify="right")
|
|
65
|
+
table.add_column("Size (KB)", justify="right")
|
|
66
|
+
table.add_column("Last Updated", justify="left")
|
|
67
|
+
table.add_column("Path", justify="left")
|
|
68
|
+
|
|
69
|
+
for scope in ("project", "global"):
|
|
70
|
+
scope_stats = stats.get(scope, {})
|
|
71
|
+
size_kb = scope_stats.get("size_bytes", 0) / 1024
|
|
72
|
+
table.add_row(
|
|
73
|
+
scope.capitalize(),
|
|
74
|
+
str(scope_stats.get("entries", 0)),
|
|
75
|
+
f"{size_kb:.1f}",
|
|
76
|
+
scope_stats.get("last_updated") or "—",
|
|
77
|
+
scope_stats.get("path", "—"),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
console.print(table)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@app.command("search")
|
|
84
|
+
def search_memory(
|
|
85
|
+
query: str = typer.Argument(..., help="Query to search in memory entries"),
|
|
86
|
+
scope: str = typer.Option(
|
|
87
|
+
"both",
|
|
88
|
+
"--scope",
|
|
89
|
+
"-s",
|
|
90
|
+
help="Scope to search: project, global, or both",
|
|
91
|
+
),
|
|
92
|
+
):
|
|
93
|
+
"""Search memory entries by substring match."""
|
|
94
|
+
scope_normalized = _validate_scope(scope)
|
|
95
|
+
store = _get_store()
|
|
96
|
+
results = store.search(query, scope=scope_normalized)
|
|
97
|
+
|
|
98
|
+
if not results:
|
|
99
|
+
console.print("[yellow]No results found[/yellow]")
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
for idx, entry in enumerate(results, start=1):
|
|
103
|
+
console.print(f"\n[bold]{idx}. {entry.key}[/bold] ({entry.scope})")
|
|
104
|
+
console.print(f"[dim]Created:[/dim] {entry.created_at}")
|
|
105
|
+
console.print(f"[dim]Updated:[/dim] {entry.updated_at}")
|
|
106
|
+
console.print_json(data=entry.data)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@app.command("clean")
|
|
110
|
+
def clean_memory(
|
|
111
|
+
scope: str = typer.Option(
|
|
112
|
+
"project",
|
|
113
|
+
"--scope",
|
|
114
|
+
"-s",
|
|
115
|
+
help="Scope to clean: project or global",
|
|
116
|
+
),
|
|
117
|
+
older_than_days: int = typer.Option(
|
|
118
|
+
30,
|
|
119
|
+
"--older-than",
|
|
120
|
+
"-o",
|
|
121
|
+
help="Delete entries older than the given number of days",
|
|
122
|
+
),
|
|
123
|
+
):
|
|
124
|
+
"""Delete old memory entries for the given scope."""
|
|
125
|
+
scope_normalized = _validate_scope(scope)
|
|
126
|
+
if scope_normalized == "both":
|
|
127
|
+
raise typer.BadParameter("Clean supports project or global scope only")
|
|
128
|
+
|
|
129
|
+
store = _get_store()
|
|
130
|
+
deleted = store.clean(older_than_days=older_than_days, scope=scope_normalized)
|
|
131
|
+
|
|
132
|
+
console.print(
|
|
133
|
+
f"[green]✓[/green] Deleted {deleted} entr{'y' if deleted == 1 else 'ies'} "
|
|
134
|
+
f"from {scope_normalized} memory older than {older_than_days} days"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
__all__ = ["app"]
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
if __name__ == "__main__":
|
|
142
|
+
app()
|
uacs/cli/packages.py
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
"""CLI commands for minimal package management.
|
|
2
|
+
|
|
3
|
+
Inspired by GitHub CLI extensions pattern - simple, focused commands
|
|
4
|
+
for installing packages from GitHub, Git URLs, or local paths.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import typer
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
from uacs.packages import PackageManager
|
|
14
|
+
from uacs.cli.utils import get_project_root
|
|
15
|
+
|
|
16
|
+
app = typer.Typer(help="Package management (GitHub CLI-style)")
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_package_manager() -> PackageManager:
|
|
21
|
+
"""Get PackageManager instance for current project."""
|
|
22
|
+
return PackageManager(get_project_root())
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@app.command("install")
|
|
26
|
+
def install(
|
|
27
|
+
source: str = typer.Argument(
|
|
28
|
+
...,
|
|
29
|
+
help="Package source: owner/repo, https://github.com/...git, or ./local/path",
|
|
30
|
+
),
|
|
31
|
+
force: bool = typer.Option(
|
|
32
|
+
False, "--force", "-f", help="Overwrite if already installed"
|
|
33
|
+
),
|
|
34
|
+
no_validate: bool = typer.Option(
|
|
35
|
+
False, "--no-validate", help="Skip validation after install"
|
|
36
|
+
),
|
|
37
|
+
):
|
|
38
|
+
"""Install a package from GitHub, Git URL, or local path.
|
|
39
|
+
|
|
40
|
+
Examples:
|
|
41
|
+
uacs install owner/repo # GitHub repository
|
|
42
|
+
uacs install https://github.com/owner/repo.git # Git URL
|
|
43
|
+
uacs install ./local/path # Local directory
|
|
44
|
+
uacs install owner/repo --force # Overwrite existing
|
|
45
|
+
uacs install owner/repo --no-validate # Skip validation
|
|
46
|
+
"""
|
|
47
|
+
pm = get_package_manager()
|
|
48
|
+
|
|
49
|
+
try:
|
|
50
|
+
# Determine source type and display info
|
|
51
|
+
if source.startswith(("http://", "https://", "git@")):
|
|
52
|
+
console.print(f"[cyan]Installing from Git URL...[/cyan]")
|
|
53
|
+
elif source.startswith(("./", "../", "/")):
|
|
54
|
+
console.print(f"[cyan]Installing from local path...[/cyan]")
|
|
55
|
+
elif "/" in source:
|
|
56
|
+
console.print(f"[cyan]Installing {source} from GitHub...[/cyan]")
|
|
57
|
+
else:
|
|
58
|
+
console.print(
|
|
59
|
+
"[red]✗[/red] Invalid source format. Use: owner/repo, git URL, or ./path"
|
|
60
|
+
)
|
|
61
|
+
return
|
|
62
|
+
|
|
63
|
+
# Install with progress
|
|
64
|
+
with Progress(
|
|
65
|
+
SpinnerColumn(),
|
|
66
|
+
TextColumn("[progress.description]{task.description}"),
|
|
67
|
+
console=console,
|
|
68
|
+
) as progress:
|
|
69
|
+
progress.add_task(description="Installing...", total=None)
|
|
70
|
+
package = pm.install(source, force=force, validate=not no_validate)
|
|
71
|
+
|
|
72
|
+
# Show success
|
|
73
|
+
console.print(f"[green]✓[/green] Successfully installed: [bold]{package.name}[/bold]")
|
|
74
|
+
|
|
75
|
+
# Display package info
|
|
76
|
+
table = Table(show_header=False, box=None, padding=(0, 2))
|
|
77
|
+
table.add_column("Field", style="dim")
|
|
78
|
+
table.add_column("Value")
|
|
79
|
+
|
|
80
|
+
table.add_row("Name", package.name)
|
|
81
|
+
table.add_row("Source", package.source)
|
|
82
|
+
table.add_row("Type", package.source_type.value)
|
|
83
|
+
if package.version:
|
|
84
|
+
table.add_row("Version", package.version)
|
|
85
|
+
if package.location:
|
|
86
|
+
table.add_row("Location", str(package.location))
|
|
87
|
+
|
|
88
|
+
console.print(table)
|
|
89
|
+
|
|
90
|
+
# Show validation warnings
|
|
91
|
+
if not package.is_valid:
|
|
92
|
+
console.print("\n[yellow]⚠[/yellow] Validation warnings:")
|
|
93
|
+
for error in package.validation_errors:
|
|
94
|
+
console.print(f" • {error}")
|
|
95
|
+
|
|
96
|
+
console.print("\n[dim]List packages with: uacs list[/dim]")
|
|
97
|
+
|
|
98
|
+
except ValueError as e:
|
|
99
|
+
console.print(f"[red]✗[/red] {e}")
|
|
100
|
+
except Exception as e:
|
|
101
|
+
console.print(f"[red]✗[/red] Error installing package: {e}")
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
@app.command("list")
|
|
105
|
+
def list_packages(
|
|
106
|
+
show_invalid: bool = typer.Option(
|
|
107
|
+
False, "--show-invalid", help="Include invalid packages"
|
|
108
|
+
),
|
|
109
|
+
):
|
|
110
|
+
"""List installed packages.
|
|
111
|
+
|
|
112
|
+
Examples:
|
|
113
|
+
uacs list # List all valid packages
|
|
114
|
+
uacs list --show-invalid # Include invalid packages
|
|
115
|
+
"""
|
|
116
|
+
pm = get_package_manager()
|
|
117
|
+
|
|
118
|
+
try:
|
|
119
|
+
packages = pm.list_installed()
|
|
120
|
+
|
|
121
|
+
if not packages:
|
|
122
|
+
console.print("[yellow]No packages installed[/yellow]")
|
|
123
|
+
console.print("\n[dim]Install with: uacs install owner/repo[/dim]")
|
|
124
|
+
return
|
|
125
|
+
|
|
126
|
+
# Create table
|
|
127
|
+
table = Table(
|
|
128
|
+
title="Installed Packages",
|
|
129
|
+
show_header=True,
|
|
130
|
+
header_style="bold cyan",
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
table.add_column("Name", style="cyan", width=25)
|
|
134
|
+
table.add_column("Source", style="green", width=35)
|
|
135
|
+
table.add_column("Type", style="magenta", width=10)
|
|
136
|
+
table.add_column("Version", style="blue", width=12)
|
|
137
|
+
table.add_column("Status", style="white", width=10)
|
|
138
|
+
|
|
139
|
+
for pkg in packages:
|
|
140
|
+
status = "[green]✓[/green]" if pkg.is_valid else "[red]✗[/red]"
|
|
141
|
+
version = pkg.version or "-"
|
|
142
|
+
|
|
143
|
+
table.add_row(
|
|
144
|
+
pkg.name,
|
|
145
|
+
pkg.source,
|
|
146
|
+
pkg.source_type.value,
|
|
147
|
+
version,
|
|
148
|
+
status,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
console.print(table)
|
|
152
|
+
console.print(f"\n[dim]Total installed: {len(packages)}[/dim]")
|
|
153
|
+
|
|
154
|
+
# Show validation summary
|
|
155
|
+
invalid_count = sum(1 for p in packages if not p.is_valid)
|
|
156
|
+
if invalid_count > 0:
|
|
157
|
+
console.print(
|
|
158
|
+
f"[yellow]{invalid_count} package(s) have validation errors[/yellow]"
|
|
159
|
+
)
|
|
160
|
+
console.print("[dim]Use 'uacs validate <name>' for details[/dim]")
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
console.print(f"[red]✗[/red] Error listing packages: {e}")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
@app.command("validate")
|
|
167
|
+
def validate_package(
|
|
168
|
+
package_name: str = typer.Argument(..., help="Package name to validate"),
|
|
169
|
+
):
|
|
170
|
+
"""Validate an installed package.
|
|
171
|
+
|
|
172
|
+
Examples:
|
|
173
|
+
uacs validate my-package # Validate specific package
|
|
174
|
+
"""
|
|
175
|
+
pm = get_package_manager()
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
with Progress(
|
|
179
|
+
SpinnerColumn(),
|
|
180
|
+
TextColumn("[progress.description]{task.description}"),
|
|
181
|
+
console=console,
|
|
182
|
+
) as progress:
|
|
183
|
+
progress.add_task(description="Validating...", total=None)
|
|
184
|
+
is_valid, errors = pm.validate(package_name)
|
|
185
|
+
|
|
186
|
+
if is_valid:
|
|
187
|
+
console.print(
|
|
188
|
+
f"[green]✓[/green] Package '[bold]{package_name}[/bold]' is valid"
|
|
189
|
+
)
|
|
190
|
+
else:
|
|
191
|
+
console.print(
|
|
192
|
+
f"[red]✗[/red] Package '[bold]{package_name}[/bold]' has validation errors:"
|
|
193
|
+
)
|
|
194
|
+
for error in errors:
|
|
195
|
+
console.print(f" • {error}")
|
|
196
|
+
|
|
197
|
+
except ValueError as e:
|
|
198
|
+
console.print(f"[red]✗[/red] {e}")
|
|
199
|
+
except Exception as e:
|
|
200
|
+
console.print(f"[red]✗[/red] Error validating package: {e}")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@app.command("remove")
|
|
204
|
+
def remove_package(
|
|
205
|
+
package_name: str = typer.Argument(..., help="Package name to remove"),
|
|
206
|
+
yes: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation"),
|
|
207
|
+
):
|
|
208
|
+
"""Remove an installed package.
|
|
209
|
+
|
|
210
|
+
Examples:
|
|
211
|
+
uacs remove my-package # Remove with confirmation
|
|
212
|
+
uacs remove my-package -y # Remove without confirmation
|
|
213
|
+
"""
|
|
214
|
+
pm = get_package_manager()
|
|
215
|
+
|
|
216
|
+
try:
|
|
217
|
+
# Get package info for confirmation
|
|
218
|
+
packages = pm.list_installed()
|
|
219
|
+
package = next((p for p in packages if p.name == package_name), None)
|
|
220
|
+
|
|
221
|
+
if not package:
|
|
222
|
+
console.print(f"[red]✗[/red] Package '{package_name}' not found")
|
|
223
|
+
console.print("\n[dim]List packages with: uacs list[/dim]")
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
# Show package info
|
|
227
|
+
console.print(f"\n[bold]Package: {package.name}[/bold]")
|
|
228
|
+
console.print(f" Source: {package.source}")
|
|
229
|
+
console.print(f" Type: {package.source_type.value}")
|
|
230
|
+
if package.location:
|
|
231
|
+
console.print(f" Location: {package.location}")
|
|
232
|
+
|
|
233
|
+
# Confirm removal
|
|
234
|
+
if not yes:
|
|
235
|
+
confirm = typer.confirm(f"\nRemove '{package_name}'?")
|
|
236
|
+
if not confirm:
|
|
237
|
+
console.print("[yellow]Cancelled[/yellow]")
|
|
238
|
+
return
|
|
239
|
+
|
|
240
|
+
# Remove package
|
|
241
|
+
with Progress(
|
|
242
|
+
SpinnerColumn(),
|
|
243
|
+
TextColumn("[progress.description]{task.description}"),
|
|
244
|
+
console=console,
|
|
245
|
+
) as progress:
|
|
246
|
+
progress.add_task(description="Removing...", total=None)
|
|
247
|
+
pm.remove(package_name)
|
|
248
|
+
|
|
249
|
+
console.print(f"[green]✓[/green] Removed '{package_name}'")
|
|
250
|
+
|
|
251
|
+
except ValueError as e:
|
|
252
|
+
console.print(f"[red]✗[/red] {e}")
|
|
253
|
+
except Exception as e:
|
|
254
|
+
console.print(f"[red]✗[/red] Error removing package: {e}")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
@app.command("update")
|
|
258
|
+
def update_package(
|
|
259
|
+
package_name: str = typer.Argument(..., help="Package name to update"),
|
|
260
|
+
):
|
|
261
|
+
"""Update a package to the latest version.
|
|
262
|
+
|
|
263
|
+
Examples:
|
|
264
|
+
uacs update my-package # Update to latest version
|
|
265
|
+
"""
|
|
266
|
+
pm = get_package_manager()
|
|
267
|
+
|
|
268
|
+
try:
|
|
269
|
+
# Get current package info
|
|
270
|
+
packages = pm.list_installed()
|
|
271
|
+
package = next((p for p in packages if p.name == package_name), None)
|
|
272
|
+
|
|
273
|
+
if not package:
|
|
274
|
+
console.print(f"[red]✗[/red] Package '{package_name}' not found")
|
|
275
|
+
console.print("\n[dim]List packages with: uacs list[/dim]")
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
console.print(f"[cyan]Updating {package_name}...[/cyan]")
|
|
279
|
+
console.print(f" Current source: {package.source}")
|
|
280
|
+
|
|
281
|
+
# Update with progress
|
|
282
|
+
with Progress(
|
|
283
|
+
SpinnerColumn(),
|
|
284
|
+
TextColumn("[progress.description]{task.description}"),
|
|
285
|
+
console=console,
|
|
286
|
+
) as progress:
|
|
287
|
+
progress.add_task(description="Updating...", total=None)
|
|
288
|
+
updated_package = pm.update(package_name)
|
|
289
|
+
|
|
290
|
+
# Show success
|
|
291
|
+
console.print(
|
|
292
|
+
f"[green]✓[/green] Successfully updated: [bold]{updated_package.name}[/bold]"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Display version info if available
|
|
296
|
+
if updated_package.version:
|
|
297
|
+
console.print(f" Version: {updated_package.version}")
|
|
298
|
+
|
|
299
|
+
except ValueError as e:
|
|
300
|
+
console.print(f"[red]✗[/red] {e}")
|
|
301
|
+
except Exception as e:
|
|
302
|
+
console.print(f"[red]✗[/red] Error updating package: {e}")
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
__all__ = ["app"]
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
if __name__ == "__main__":
|
|
309
|
+
app()
|
uacs/cli/skills.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""CLI commands for managing agent skills."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import typer
|
|
7
|
+
import yaml
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.markdown import Markdown
|
|
10
|
+
from rich.panel import Panel
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
from uacs import UACS
|
|
14
|
+
from uacs.adapters import FormatAdapterRegistry
|
|
15
|
+
from uacs.adapters.agent_skill_adapter import AgentSkillAdapter
|
|
16
|
+
from uacs.skills_validator import SkillValidator
|
|
17
|
+
from uacs.cli.utils import get_project_root
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(help="Manage agent skills")
|
|
20
|
+
console = Console()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_uacs() -> UACS:
|
|
24
|
+
"""Get UACS instance for current project."""
|
|
25
|
+
return UACS(get_project_root())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@app.command()
|
|
29
|
+
def validate(
|
|
30
|
+
skill_path: Path = typer.Argument(..., help="Path to skill directory"),
|
|
31
|
+
strict: bool = typer.Option(False, "--strict", help="Fail on warnings"),
|
|
32
|
+
):
|
|
33
|
+
"""Validate a skill against the Agent Skills specification."""
|
|
34
|
+
result = SkillValidator.validate_file(skill_path)
|
|
35
|
+
|
|
36
|
+
if result.valid:
|
|
37
|
+
if strict and result.warnings:
|
|
38
|
+
console.print(f"[red]✗[/red] Skill at {skill_path} has warnings (strict mode)")
|
|
39
|
+
for warning in result.warnings:
|
|
40
|
+
console.print(f" - {warning.field}: {warning.message}")
|
|
41
|
+
raise typer.Exit(code=1)
|
|
42
|
+
|
|
43
|
+
console.print(f"[green]✓[/green] Skill at {skill_path} is valid")
|
|
44
|
+
for warning in result.warnings:
|
|
45
|
+
console.print(f" [yellow]![/yellow] {warning.field}: {warning.message}")
|
|
46
|
+
else:
|
|
47
|
+
console.print(f"[red]✗[/red] Skill at {skill_path} is invalid")
|
|
48
|
+
for error in result.errors:
|
|
49
|
+
console.print(f" - {error.field}: {error.message}")
|
|
50
|
+
raise typer.Exit(code=1)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@app.command("read-properties")
|
|
54
|
+
def read_properties(
|
|
55
|
+
skill_path: Path = typer.Argument(..., help="Path to skill directory"),
|
|
56
|
+
output_format: str = typer.Option("json", "--format", "-f", help="Output format (json/yaml)"),
|
|
57
|
+
):
|
|
58
|
+
"""Read skill properties (frontmatter)."""
|
|
59
|
+
skill_file = skill_path / "SKILL.md"
|
|
60
|
+
if not skill_file.exists():
|
|
61
|
+
console.print(f"[red]✗[/red] SKILL.md not found in {skill_path}")
|
|
62
|
+
raise typer.Exit(code=1)
|
|
63
|
+
|
|
64
|
+
content = skill_file.read_text()
|
|
65
|
+
frontmatter, _, errors = SkillValidator.extract_frontmatter(content)
|
|
66
|
+
|
|
67
|
+
if errors:
|
|
68
|
+
console.print(f"[red]✗[/red] Failed to parse frontmatter")
|
|
69
|
+
for error in errors:
|
|
70
|
+
console.print(f" - {error.message}")
|
|
71
|
+
raise typer.Exit(code=1)
|
|
72
|
+
|
|
73
|
+
if output_format == "json":
|
|
74
|
+
print(json.dumps(frontmatter, indent=2))
|
|
75
|
+
elif output_format == "yaml":
|
|
76
|
+
print(yaml.dump(frontmatter))
|
|
77
|
+
else:
|
|
78
|
+
console.print(f"[red]✗[/red] Unknown format: {output_format}")
|
|
79
|
+
raise typer.Exit(code=1)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
@app.command("to-prompt")
|
|
83
|
+
def to_prompt(
|
|
84
|
+
skill_paths: list[Path] = typer.Argument(..., help="Paths to skill directories"),
|
|
85
|
+
):
|
|
86
|
+
"""Convert skills to prompt format."""
|
|
87
|
+
adapter = AgentSkillAdapter()
|
|
88
|
+
prompts = []
|
|
89
|
+
|
|
90
|
+
for path in skill_paths:
|
|
91
|
+
skill_file = path / "SKILL.md"
|
|
92
|
+
if not skill_file.exists():
|
|
93
|
+
console.print(f"[red]✗[/red] SKILL.md not found in {path}")
|
|
94
|
+
raise typer.Exit(code=1)
|
|
95
|
+
|
|
96
|
+
content = skill_file.read_text()
|
|
97
|
+
adapter.parsed = adapter.parse(content)
|
|
98
|
+
prompts.append(adapter.to_system_prompt())
|
|
99
|
+
|
|
100
|
+
print("\n\n".join(prompts))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@app.command()
|
|
104
|
+
def list(
|
|
105
|
+
json_output: bool = typer.Option(False, "--json", help="Output as JSON"),
|
|
106
|
+
):
|
|
107
|
+
"""List all available skills in the project."""
|
|
108
|
+
uacs = get_uacs()
|
|
109
|
+
skills = uacs.get_capabilities()
|
|
110
|
+
|
|
111
|
+
if json_output:
|
|
112
|
+
print(json.dumps(skills, indent=2))
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
table = Table(title="Available Skills")
|
|
116
|
+
table.add_column("Name", style="cyan")
|
|
117
|
+
table.add_column("Origin", style="yellow", width=40)
|
|
118
|
+
table.add_column("Source", style="green")
|
|
119
|
+
table.add_column("Description")
|
|
120
|
+
|
|
121
|
+
for skill in skills.get("agent_skills", []):
|
|
122
|
+
source_path = skill.get("source", "unknown")
|
|
123
|
+
skill_name = skill.get("name", "unknown")
|
|
124
|
+
|
|
125
|
+
# Check if skill was installed via package manager
|
|
126
|
+
try:
|
|
127
|
+
installed_packages = uacs.list_packages()
|
|
128
|
+
package = next((p for p in installed_packages if p.name == skill_name), None)
|
|
129
|
+
if package:
|
|
130
|
+
origin = package.source # e.g., "owner/repo" or "local"
|
|
131
|
+
else:
|
|
132
|
+
origin = "local"
|
|
133
|
+
except Exception:
|
|
134
|
+
origin = "local"
|
|
135
|
+
|
|
136
|
+
table.add_row(
|
|
137
|
+
skill_name,
|
|
138
|
+
origin,
|
|
139
|
+
source_path,
|
|
140
|
+
skill.get("description", "")[:100]
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
console.print(table)
|
|
144
|
+
|
uacs/cli/utils.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Shared CLI utilities for UACS commands."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_project_root() -> Path:
|
|
8
|
+
"""Get the effective project root directory.
|
|
9
|
+
|
|
10
|
+
Prioritizes PWD environment variable to handle cases where the tool
|
|
11
|
+
is invoked via 'uv run --directory ...' which changes the process CWD.
|
|
12
|
+
"""
|
|
13
|
+
# Check if PWD is set and valid
|
|
14
|
+
pwd = os.environ.get("PWD")
|
|
15
|
+
if pwd:
|
|
16
|
+
path = Path(pwd)
|
|
17
|
+
if path.exists() and path.is_dir():
|
|
18
|
+
return path
|
|
19
|
+
|
|
20
|
+
# Fallback to current working directory
|
|
21
|
+
return Path.cwd()
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
__all__ = ["get_project_root"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
repositories:
|
|
2
|
+
- owner: modelcontextprotocol
|
|
3
|
+
repo: servers
|
|
4
|
+
type: mcp
|
|
5
|
+
path: src
|
|
6
|
+
description: Official Model Context Protocol servers
|
|
7
|
+
- owner: agentskills
|
|
8
|
+
repo: agentskills
|
|
9
|
+
type: skill
|
|
10
|
+
path: skills-ref
|
|
11
|
+
description: Agent Skills reference implementations
|
|
12
|
+
- owner: ComposioHQ
|
|
13
|
+
repo: awesome-claude-skills
|
|
14
|
+
type: skill
|
|
15
|
+
path: ""
|
|
16
|
+
description: Curated collection of Claude skills
|
|
17
|
+
- owner: huggingface
|
|
18
|
+
repo: skills
|
|
19
|
+
type: skill
|
|
20
|
+
path: ""
|
|
21
|
+
description: Hugging Face Agent Skills
|
|
22
|
+
- owner: anthropics
|
|
23
|
+
repo: skills
|
|
24
|
+
type: skill
|
|
25
|
+
path: skills
|
|
26
|
+
description: Anthropic Official Skills
|
uacs/context/__init__.py
ADDED
|
File without changes
|