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.
Files changed (47) hide show
  1. uacs/__init__.py +12 -0
  2. uacs/adapters/__init__.py +19 -0
  3. uacs/adapters/agent_skill_adapter.py +202 -0
  4. uacs/adapters/agents_md_adapter.py +330 -0
  5. uacs/adapters/base.py +261 -0
  6. uacs/adapters/clinerules_adapter.py +39 -0
  7. uacs/adapters/cursorrules_adapter.py +39 -0
  8. uacs/api.py +262 -0
  9. uacs/cli/__init__.py +6 -0
  10. uacs/cli/context.py +349 -0
  11. uacs/cli/main.py +195 -0
  12. uacs/cli/mcp.py +115 -0
  13. uacs/cli/memory.py +142 -0
  14. uacs/cli/packages.py +309 -0
  15. uacs/cli/skills.py +144 -0
  16. uacs/cli/utils.py +24 -0
  17. uacs/config/repositories.yaml +26 -0
  18. uacs/context/__init__.py +0 -0
  19. uacs/context/agent_context.py +406 -0
  20. uacs/context/shared_context.py +661 -0
  21. uacs/context/unified_context.py +332 -0
  22. uacs/mcp_server_entry.py +80 -0
  23. uacs/memory/__init__.py +5 -0
  24. uacs/memory/simple_memory.py +255 -0
  25. uacs/packages/__init__.py +26 -0
  26. uacs/packages/manager.py +413 -0
  27. uacs/packages/models.py +60 -0
  28. uacs/packages/sources.py +270 -0
  29. uacs/protocols/__init__.py +5 -0
  30. uacs/protocols/mcp/__init__.py +8 -0
  31. uacs/protocols/mcp/manager.py +77 -0
  32. uacs/protocols/mcp/skills_server.py +700 -0
  33. uacs/skills_validator.py +367 -0
  34. uacs/utils/__init__.py +5 -0
  35. uacs/utils/paths.py +24 -0
  36. uacs/visualization/README.md +132 -0
  37. uacs/visualization/__init__.py +36 -0
  38. uacs/visualization/models.py +195 -0
  39. uacs/visualization/static/index.html +857 -0
  40. uacs/visualization/storage.py +402 -0
  41. uacs/visualization/visualization.py +328 -0
  42. uacs/visualization/web_server.py +364 -0
  43. universal_agent_context-0.2.0.dist-info/METADATA +873 -0
  44. universal_agent_context-0.2.0.dist-info/RECORD +47 -0
  45. universal_agent_context-0.2.0.dist-info/WHEEL +4 -0
  46. universal_agent_context-0.2.0.dist-info/entry_points.txt +2 -0
  47. 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
File without changes