oasr 0.5.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 (59) hide show
  1. __init__.py +3 -0
  2. __main__.py +6 -0
  3. adapter.py +396 -0
  4. adapters/__init__.py +17 -0
  5. adapters/base.py +254 -0
  6. adapters/claude.py +82 -0
  7. adapters/codex.py +84 -0
  8. adapters/copilot.py +210 -0
  9. adapters/cursor.py +78 -0
  10. adapters/windsurf.py +83 -0
  11. agents/__init__.py +25 -0
  12. agents/base.py +96 -0
  13. agents/claude.py +25 -0
  14. agents/codex.py +25 -0
  15. agents/copilot.py +25 -0
  16. agents/opencode.py +25 -0
  17. agents/registry.py +57 -0
  18. cli.py +97 -0
  19. commands/__init__.py +6 -0
  20. commands/adapter.py +102 -0
  21. commands/add.py +435 -0
  22. commands/clean.py +30 -0
  23. commands/clone.py +178 -0
  24. commands/config.py +163 -0
  25. commands/diff.py +180 -0
  26. commands/exec.py +245 -0
  27. commands/find.py +56 -0
  28. commands/help.py +51 -0
  29. commands/info.py +152 -0
  30. commands/list.py +110 -0
  31. commands/registry.py +447 -0
  32. commands/rm.py +128 -0
  33. commands/status.py +119 -0
  34. commands/sync.py +143 -0
  35. commands/update.py +417 -0
  36. commands/use.py +45 -0
  37. commands/validate.py +74 -0
  38. config/__init__.py +119 -0
  39. config/defaults.py +40 -0
  40. config/schema.py +73 -0
  41. discovery.py +145 -0
  42. manifest.py +437 -0
  43. oasr-0.5.0.dist-info/METADATA +358 -0
  44. oasr-0.5.0.dist-info/RECORD +59 -0
  45. oasr-0.5.0.dist-info/WHEEL +4 -0
  46. oasr-0.5.0.dist-info/entry_points.txt +3 -0
  47. oasr-0.5.0.dist-info/licenses/LICENSE +187 -0
  48. oasr-0.5.0.dist-info/licenses/NOTICE +8 -0
  49. policy/__init__.py +50 -0
  50. policy/defaults.py +27 -0
  51. policy/enforcement.py +98 -0
  52. policy/profile.py +185 -0
  53. registry.py +173 -0
  54. remote.py +482 -0
  55. skillcopy/__init__.py +71 -0
  56. skillcopy/local.py +40 -0
  57. skillcopy/remote.py +98 -0
  58. tracking.py +181 -0
  59. validate.py +362 -0
agents/codex.py ADDED
@@ -0,0 +1,25 @@
1
+ """Codex agent driver."""
2
+
3
+ from pathlib import Path
4
+
5
+ from agents.base import AgentDriver
6
+
7
+
8
+ class CodexDriver(AgentDriver):
9
+ """Driver for Codex CLI agent."""
10
+
11
+ def get_name(self) -> str:
12
+ """Get the agent name."""
13
+ return "codex"
14
+
15
+ def get_binary_name(self) -> str:
16
+ """Get the CLI binary name."""
17
+ return "codex"
18
+
19
+ def build_command(self, skill_content: str, user_prompt: str, cwd: Path) -> list[str]:
20
+ """Build codex exec command.
21
+
22
+ Codex syntax: codex exec "<prompt>"
23
+ """
24
+ injected_prompt = self.format_injected_prompt(skill_content, user_prompt, cwd)
25
+ return ["codex", "exec", injected_prompt]
agents/copilot.py ADDED
@@ -0,0 +1,25 @@
1
+ """GitHub Copilot agent driver."""
2
+
3
+ from pathlib import Path
4
+
5
+ from agents.base import AgentDriver
6
+
7
+
8
+ class CopilotDriver(AgentDriver):
9
+ """Driver for GitHub Copilot CLI agent."""
10
+
11
+ def get_name(self) -> str:
12
+ """Get the agent name."""
13
+ return "copilot"
14
+
15
+ def get_binary_name(self) -> str:
16
+ """Get the CLI binary name."""
17
+ return "copilot"
18
+
19
+ def build_command(self, skill_content: str, user_prompt: str, cwd: Path) -> list[str]:
20
+ """Build copilot command.
21
+
22
+ Copilot syntax: copilot -p "<prompt>"
23
+ """
24
+ injected_prompt = self.format_injected_prompt(skill_content, user_prompt, cwd)
25
+ return ["copilot", "-p", injected_prompt]
agents/opencode.py ADDED
@@ -0,0 +1,25 @@
1
+ """OpenCode CLI agent driver."""
2
+
3
+ from pathlib import Path
4
+
5
+ from agents.base import AgentDriver
6
+
7
+
8
+ class OpenCodeDriver(AgentDriver):
9
+ """Driver for OpenCode CLI agent."""
10
+
11
+ def get_name(self) -> str:
12
+ """Get the agent name."""
13
+ return "opencode"
14
+
15
+ def get_binary_name(self) -> str:
16
+ """Get the CLI binary name."""
17
+ return "opencode"
18
+
19
+ def build_command(self, skill_content: str, user_prompt: str, cwd: Path) -> list[str]:
20
+ """Build opencode run command.
21
+
22
+ OpenCode syntax: opencode run "<prompt>"
23
+ """
24
+ injected_prompt = self.format_injected_prompt(skill_content, user_prompt, cwd)
25
+ return ["opencode", "run", injected_prompt]
agents/registry.py ADDED
@@ -0,0 +1,57 @@
1
+ """Agent driver registry and factory."""
2
+
3
+ from agents.base import AgentDriver
4
+ from agents.claude import ClaudeDriver
5
+ from agents.codex import CodexDriver
6
+ from agents.copilot import CopilotDriver
7
+ from agents.opencode import OpenCodeDriver
8
+
9
+ # Registry of all available drivers
10
+ DRIVERS: dict[str, type[AgentDriver]] = {
11
+ "codex": CodexDriver,
12
+ "copilot": CopilotDriver,
13
+ "claude": ClaudeDriver,
14
+ "opencode": OpenCodeDriver,
15
+ }
16
+
17
+
18
+ def get_driver(agent_name: str) -> AgentDriver:
19
+ """Get driver instance by agent name.
20
+
21
+ Args:
22
+ agent_name: Name of agent (codex, copilot, claude).
23
+
24
+ Returns:
25
+ AgentDriver instance.
26
+
27
+ Raises:
28
+ ValueError: If agent name is invalid.
29
+ """
30
+ if agent_name not in DRIVERS:
31
+ valid = ", ".join(sorted(DRIVERS.keys()))
32
+ raise ValueError(f"Invalid agent '{agent_name}'. Must be one of: {valid}")
33
+
34
+ return DRIVERS[agent_name]()
35
+
36
+
37
+ def detect_available_agents() -> list[str]:
38
+ """Detect which agent binaries are available in PATH.
39
+
40
+ Returns:
41
+ List of available agent names.
42
+ """
43
+ available = []
44
+ for name, driver_class in DRIVERS.items():
45
+ driver = driver_class()
46
+ if driver.detect():
47
+ available.append(name)
48
+ return sorted(available)
49
+
50
+
51
+ def get_all_agent_names() -> list[str]:
52
+ """Get all supported agent names.
53
+
54
+ Returns:
55
+ List of all agent names.
56
+ """
57
+ return sorted(DRIVERS.keys())
cli.py ADDED
@@ -0,0 +1,97 @@
1
+ """CLI entry point (argparse wiring + dispatch).
2
+
3
+ Command implementations live under `src/commands/`.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import json
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ from commands import adapter, clean, clone, config, diff, exec, find, registry, sync, update, use, validate
14
+ from commands import help as help_cmd
15
+
16
+ __version__ = "0.5.0"
17
+
18
+
19
+ def main(argv: list[str] | None = None) -> int:
20
+ """Main CLI entry point."""
21
+ parser = create_parser()
22
+ args = parser.parse_args(argv)
23
+
24
+ if not hasattr(args, "func"):
25
+ parser.print_help()
26
+ return 1
27
+
28
+ try:
29
+ return args.func(args)
30
+ except KeyboardInterrupt:
31
+ print("\nInterrupted.", file=sys.stderr)
32
+ return 130
33
+ except Exception as e:
34
+ if args.json if hasattr(args, "json") else False:
35
+ print(json.dumps({"error": str(e)}), file=sys.stderr)
36
+ else:
37
+ print(f"Error: {e}", file=sys.stderr)
38
+ return 3
39
+
40
+
41
+ def create_parser() -> argparse.ArgumentParser:
42
+ """Create the argument parser."""
43
+ parser = argparse.ArgumentParser(
44
+ prog="oasr",
45
+ description="Open Agent Skills Registry - Manage agent skills across IDE integrations.",
46
+ )
47
+ parser.add_argument(
48
+ "--version",
49
+ action="version",
50
+ version=f"%(prog)s {__version__}",
51
+ )
52
+ parser.add_argument(
53
+ "--config",
54
+ type=Path,
55
+ help="Override config file path",
56
+ )
57
+ parser.add_argument(
58
+ "--json",
59
+ action="store_true",
60
+ help="Output in JSON format",
61
+ )
62
+ parser.add_argument(
63
+ "--quiet",
64
+ action="store_true",
65
+ help="Suppress info and warnings",
66
+ )
67
+
68
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
69
+
70
+ # New taxonomy (v0.3.0+)
71
+ registry.register(subparsers) # Registry operations (add, rm, sync, list)
72
+ diff.register(subparsers) # Show tracked skill status
73
+ sync.register(subparsers) # Refresh tracked skills
74
+ config.register(subparsers) # Configuration management
75
+ clone.register(subparsers) # Clone skills to directory
76
+ exec.register(subparsers) # Execute skills with agent CLI
77
+
78
+ # Deprecated commands
79
+ use.register(subparsers) # DEPRECATED - use clone instead
80
+ find.register(subparsers)
81
+ validate.register(subparsers)
82
+ clean.register(subparsers)
83
+ adapter.register(subparsers)
84
+ update.register(subparsers)
85
+
86
+ # Import and register info command
87
+ from commands import info as info_cmd
88
+
89
+ info_cmd.register(subparsers)
90
+
91
+ help_cmd.register(subparsers, parser)
92
+
93
+ return parser
94
+
95
+
96
+ if __name__ == "__main__":
97
+ raise SystemExit(main())
commands/__init__.py ADDED
@@ -0,0 +1,6 @@
1
+ """Command modules for the ASR CLI.
2
+
3
+ Each command module exposes:
4
+ - register(subparsers): attach argparse parsers
5
+ - run(args): execute command
6
+ """
commands/adapter.py ADDED
@@ -0,0 +1,102 @@
1
+ """`asr adapter` command."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from adapters import ClaudeAdapter, CodexAdapter, CopilotAdapter, CursorAdapter, WindsurfAdapter
11
+ from config import load_config
12
+ from registry import load_registry
13
+
14
+ ADAPTERS = {
15
+ "cursor": CursorAdapter(),
16
+ "windsurf": WindsurfAdapter(),
17
+ "codex": CodexAdapter(),
18
+ "copilot": CopilotAdapter(),
19
+ "claude": ClaudeAdapter(),
20
+ }
21
+
22
+
23
+ def register(subparsers) -> None:
24
+ p = subparsers.add_parser("adapter", help="Generate IDE-specific files")
25
+ p.add_argument("--exclude", help="Comma-separated skill names to exclude")
26
+ p.add_argument("--output-dir", type=Path, default=Path("."), help="Output directory")
27
+ p.add_argument("--copy", action="store_true", help="(Deprecated) Skills are always copied now")
28
+ p.add_argument("--json", action="store_true", help="Output in JSON format")
29
+ p.add_argument("--quiet", action="store_true", help="Suppress info/warnings")
30
+ p.add_argument("--config", type=Path, help="Override config file path")
31
+
32
+ adapter_subs = p.add_subparsers(dest="target", help="Target IDE")
33
+
34
+ for name in ["cursor", "windsurf", "codex", "copilot", "claude"]:
35
+ adapter_subs.add_parser(name, help=f"Generate {name} files")
36
+
37
+ p.set_defaults(func=run)
38
+
39
+
40
+ def run(args: argparse.Namespace) -> int:
41
+ config = load_config(args.config)
42
+ entries = load_registry()
43
+
44
+ if not entries:
45
+ if args.json:
46
+ print(json.dumps({"generated": 0, "error": "no skills registered"}))
47
+ else:
48
+ print("No skills registered. Use 'asr add <path>' first.")
49
+ return 1
50
+
51
+ exclude = set()
52
+ if args.exclude:
53
+ exclude = set(args.exclude.split(","))
54
+
55
+ output_dir = args.output_dir
56
+
57
+ if args.target:
58
+ targets = [args.target]
59
+ else:
60
+ targets = config["adapter"]["default_targets"]
61
+
62
+ total_generated = 0
63
+ total_removed = 0
64
+ results = {}
65
+
66
+ for target in targets:
67
+ if target not in ADAPTERS:
68
+ if not args.quiet:
69
+ print(f"Warning: Unknown adapter target: {target}", file=sys.stderr)
70
+ continue
71
+
72
+ adapter = ADAPTERS[target]
73
+ # Always copy skills now (--copy flag is deprecated but kept for backward compat)
74
+ generated, removed = adapter.generate_all(entries, output_dir, exclude, copy=True)
75
+
76
+ total_generated += len(generated)
77
+ total_removed += len(removed)
78
+
79
+ results[target] = {
80
+ "generated": len(generated),
81
+ "removed": len(removed),
82
+ "output_dir": str(adapter.resolve_output_dir(output_dir)),
83
+ }
84
+
85
+ if args.json:
86
+ print(
87
+ json.dumps(
88
+ {
89
+ "total_generated": total_generated,
90
+ "total_removed": total_removed,
91
+ "targets": results,
92
+ },
93
+ indent=2,
94
+ )
95
+ )
96
+ else:
97
+ for target, info in results.items():
98
+ print(f"{target}: Generated {info['generated']} file(s) in {info['output_dir']}")
99
+ if info["removed"]:
100
+ print(f" Removed {info['removed']} stale file(s)")
101
+
102
+ return 0