oasr 0.5.1__py3-none-any.whl → 0.6.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.
agents/base.py CHANGED
@@ -34,25 +34,39 @@ class AgentDriver(ABC):
34
34
  return shutil.which(self.get_binary_name()) is not None
35
35
 
36
36
  @abstractmethod
37
- def build_command(self, skill_content: str, user_prompt: str, cwd: Path) -> list[str]:
37
+ def build_command(
38
+ self,
39
+ skill_content: str,
40
+ user_prompt: str,
41
+ cwd: Path,
42
+ extra_args: list[str] | None = None,
43
+ ) -> list[str]:
38
44
  """Build the command to execute.
39
45
 
40
46
  Args:
41
47
  skill_content: Full SKILL.md content.
42
48
  user_prompt: User's prompt/request.
43
49
  cwd: Current working directory.
50
+ extra_args: Additional CLI arguments for the agent (optional).
44
51
 
45
52
  Returns:
46
53
  Command as list of strings (for subprocess).
47
54
  """
48
55
 
49
- def execute(self, skill_content: str, user_prompt: str, cwd: Path | None = None) -> subprocess.CompletedProcess:
56
+ def execute(
57
+ self,
58
+ skill_content: str,
59
+ user_prompt: str,
60
+ cwd: Path | None = None,
61
+ extra_args: list[str] | None = None,
62
+ ) -> subprocess.CompletedProcess:
50
63
  """Execute skill with agent.
51
64
 
52
65
  Args:
53
66
  skill_content: Full SKILL.md content.
54
67
  user_prompt: User's prompt/request.
55
68
  cwd: Working directory for execution (defaults to current dir).
69
+ extra_args: Additional CLI arguments for the agent (optional).
56
70
 
57
71
  Returns:
58
72
  CompletedProcess with stdout/stderr/returncode.
@@ -64,7 +78,7 @@ class AgentDriver(ABC):
64
78
  raise FileNotFoundError(f"{self.get_name()} binary '{self.get_binary_name()}' not found in PATH")
65
79
 
66
80
  working_dir = cwd or Path.cwd()
67
- cmd = self.build_command(skill_content, user_prompt, working_dir)
81
+ cmd = self.build_command(skill_content, user_prompt, working_dir, extra_args=extra_args)
68
82
 
69
83
  return subprocess.run(
70
84
  cmd,
agents/claude.py CHANGED
@@ -16,10 +16,20 @@ class ClaudeDriver(AgentDriver):
16
16
  """Get the CLI binary name."""
17
17
  return "claude"
18
18
 
19
- def build_command(self, skill_content: str, user_prompt: str, cwd: Path) -> list[str]:
19
+ def build_command(
20
+ self,
21
+ skill_content: str,
22
+ user_prompt: str,
23
+ cwd: Path,
24
+ extra_args: list[str] | None = None,
25
+ ) -> list[str]:
20
26
  """Build claude command.
21
27
 
22
28
  Claude syntax: claude <prompt> -p
23
29
  """
24
30
  injected_prompt = self.format_injected_prompt(skill_content, user_prompt, cwd)
25
- return ["claude", injected_prompt, "-p"]
31
+ cmd = ["claude"]
32
+ if extra_args:
33
+ cmd.extend(extra_args)
34
+ cmd.extend([injected_prompt, "-p"])
35
+ return cmd
agents/codex.py CHANGED
@@ -16,10 +16,20 @@ class CodexDriver(AgentDriver):
16
16
  """Get the CLI binary name."""
17
17
  return "codex"
18
18
 
19
- def build_command(self, skill_content: str, user_prompt: str, cwd: Path) -> list[str]:
19
+ def build_command(
20
+ self,
21
+ skill_content: str,
22
+ user_prompt: str,
23
+ cwd: Path,
24
+ extra_args: list[str] | None = None,
25
+ ) -> list[str]:
20
26
  """Build codex exec command.
21
27
 
22
28
  Codex syntax: codex exec "<prompt>"
23
29
  """
24
30
  injected_prompt = self.format_injected_prompt(skill_content, user_prompt, cwd)
25
- return ["codex", "exec", injected_prompt]
31
+ cmd = ["codex", "exec"]
32
+ if extra_args:
33
+ cmd.extend(extra_args)
34
+ cmd.append(injected_prompt)
35
+ return cmd
agents/copilot.py CHANGED
@@ -16,10 +16,20 @@ class CopilotDriver(AgentDriver):
16
16
  """Get the CLI binary name."""
17
17
  return "copilot"
18
18
 
19
- def build_command(self, skill_content: str, user_prompt: str, cwd: Path) -> list[str]:
19
+ def build_command(
20
+ self,
21
+ skill_content: str,
22
+ user_prompt: str,
23
+ cwd: Path,
24
+ extra_args: list[str] | None = None,
25
+ ) -> list[str]:
20
26
  """Build copilot command.
21
27
 
22
28
  Copilot syntax: copilot -p "<prompt>"
23
29
  """
24
30
  injected_prompt = self.format_injected_prompt(skill_content, user_prompt, cwd)
25
- return ["copilot", "-p", injected_prompt]
31
+ cmd = ["copilot"]
32
+ if extra_args:
33
+ cmd.extend(extra_args)
34
+ cmd.extend(["-p", injected_prompt])
35
+ return cmd
agents/opencode.py CHANGED
@@ -16,10 +16,20 @@ class OpenCodeDriver(AgentDriver):
16
16
  """Get the CLI binary name."""
17
17
  return "opencode"
18
18
 
19
- def build_command(self, skill_content: str, user_prompt: str, cwd: Path) -> list[str]:
19
+ def build_command(
20
+ self,
21
+ skill_content: str,
22
+ user_prompt: str,
23
+ cwd: Path,
24
+ extra_args: list[str] | None = None,
25
+ ) -> list[str]:
20
26
  """Build opencode run command.
21
27
 
22
28
  OpenCode syntax: opencode run "<prompt>"
23
29
  """
24
30
  injected_prompt = self.format_injected_prompt(skill_content, user_prompt, cwd)
25
- return ["opencode", "run", injected_prompt]
31
+ cmd = ["opencode", "run"]
32
+ if extra_args:
33
+ cmd.extend(extra_args)
34
+ cmd.append(injected_prompt)
35
+ return cmd
cli.py CHANGED
@@ -10,10 +10,10 @@ import json
10
10
  import sys
11
11
  from pathlib import Path
12
12
 
13
- from commands import adapter, clean, clone, config, diff, exec, find, registry, sync, update, use, validate
13
+ from commands import adapter, clean, clone, config, diff, exec, find, profile, registry, sync, update, use, validate
14
14
  from commands import help as help_cmd
15
15
 
16
- __version__ = "0.5.1"
16
+ __version__ = "0.6.0"
17
17
 
18
18
 
19
19
  def main(argv: list[str] | None = None) -> int:
@@ -72,6 +72,7 @@ def create_parser() -> argparse.ArgumentParser:
72
72
  diff.register(subparsers) # Show tracked skill status
73
73
  sync.register(subparsers) # Refresh tracked skills
74
74
  config.register(subparsers) # Configuration management
75
+ profile.register(subparsers) # Profile selection
75
76
  clone.register(subparsers) # Clone skills to directory
76
77
  exec.register(subparsers) # Execute skills with agent CLI
77
78
 
@@ -88,6 +89,11 @@ def create_parser() -> argparse.ArgumentParser:
88
89
 
89
90
  info_cmd.register(subparsers)
90
91
 
92
+ # Import and register completion command
93
+ from commands import completion as completion_cmd
94
+
95
+ completion_cmd.register_parser(subparsers)
96
+
91
97
  help_cmd.register(subparsers, parser)
92
98
 
93
99
  return parser
commands/completion.py ADDED
@@ -0,0 +1,345 @@
1
+ """Shell completion management for OASR."""
2
+
3
+ import os
4
+ import platform
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from config import load_config
9
+
10
+
11
+ def register_parser(subparsers):
12
+ """Register the completion subcommand."""
13
+ parser = subparsers.add_parser(
14
+ "completion",
15
+ help="Generate and install shell completions",
16
+ description="Generate shell completion scripts for bash, zsh, fish, and PowerShell.",
17
+ )
18
+
19
+ parser.add_argument(
20
+ "shell",
21
+ nargs="?",
22
+ choices=["bash", "zsh", "fish", "powershell", "install", "uninstall"],
23
+ help="Shell type or action (install/uninstall)",
24
+ )
25
+
26
+ parser.add_argument(
27
+ "--force",
28
+ action="store_true",
29
+ help="Force installation even if already installed",
30
+ )
31
+
32
+ parser.add_argument(
33
+ "--dry-run",
34
+ action="store_true",
35
+ help="Show what would be done without doing it",
36
+ )
37
+
38
+ parser.set_defaults(func=run)
39
+
40
+
41
+ def detect_shell():
42
+ """
43
+ Auto-detect the user's shell based on platform and environment.
44
+
45
+ Returns:
46
+ str: Shell name (bash, zsh, fish, powershell)
47
+ """
48
+ if platform.system() == "Windows":
49
+ shell_env = os.environ.get("SHELL", "").lower()
50
+ if "bash" in shell_env:
51
+ return "bash"
52
+ # Check for PowerShell
53
+ if "PSModulePath" in os.environ or "POWERSHELL" in os.environ.get("SHELL", "").upper():
54
+ return "powershell"
55
+ if "zsh" in shell_env:
56
+ return "zsh"
57
+ if "fish" in shell_env:
58
+ return "fish"
59
+ # Default to PowerShell on Windows
60
+ return "powershell"
61
+ else:
62
+ # Linux/macOS
63
+ shell = os.environ.get("SHELL", "")
64
+ if "zsh" in shell:
65
+ return "zsh"
66
+ elif "fish" in shell:
67
+ return "fish"
68
+ else:
69
+ # Default to bash on Unix
70
+ return "bash"
71
+
72
+
73
+ def get_completion_script(shell):
74
+ """
75
+ Get the completion script content for the specified shell.
76
+
77
+ Args:
78
+ shell: Shell name (bash, zsh, fish, powershell)
79
+
80
+ Returns:
81
+ str: Completion script content
82
+ """
83
+ # Map shell names to completion files
84
+ script_files = {
85
+ "bash": "bash.sh",
86
+ "zsh": "zsh.sh",
87
+ "fish": "fish.fish",
88
+ "powershell": "powershell.ps1",
89
+ }
90
+
91
+ if shell not in script_files:
92
+ return ""
93
+
94
+ # Get the path to the completion script
95
+ completion_file = Path(__file__).parent.parent / "completions" / script_files[shell]
96
+
97
+ if not completion_file.exists():
98
+ return f"# {shell} completion script not found"
99
+
100
+ return completion_file.read_text()
101
+
102
+
103
+ def get_completion_path(shell, system=False):
104
+ """
105
+ Get the installation path for completion script.
106
+
107
+ Args:
108
+ shell: Shell name
109
+ system: If True, use system-wide path (requires root)
110
+
111
+ Returns:
112
+ Path: Installation path
113
+ """
114
+ paths = {
115
+ "bash": {
116
+ "user": Path.home() / ".bash_completion.d" / "oasr",
117
+ "system": Path("/etc/bash_completion.d/oasr"),
118
+ },
119
+ "zsh": {
120
+ "user": Path.home() / ".zsh" / "completion" / "_oasr",
121
+ "system": Path("/usr/local/share/zsh/site-functions/_oasr"),
122
+ },
123
+ "fish": {
124
+ "user": Path.home() / ".config" / "fish" / "completions" / "oasr.fish",
125
+ "system": Path("/usr/share/fish/vendor_completions.d/oasr.fish"),
126
+ },
127
+ "powershell": {
128
+ "user": Path.home() / ".config" / "powershell" / "oasr_completion.ps1",
129
+ "system": Path.home() / ".config" / "powershell" / "oasr_completion.ps1",
130
+ },
131
+ }
132
+
133
+ level = "system" if system else "user"
134
+ return paths.get(shell, {}).get(level)
135
+
136
+
137
+ def print_activation_instructions(shell, path):
138
+ """
139
+ Print instructions for activating completions.
140
+
141
+ Args:
142
+ shell: Shell name
143
+ path: Installation path
144
+ """
145
+ instructions = {
146
+ "bash": f"""
147
+ ✓ Completions installed for bash
148
+
149
+ To activate, add this to your ~/.bashrc:
150
+ source {path}
151
+
152
+ Or run:
153
+ echo 'source {path}' >> ~/.bashrc
154
+
155
+ Then restart your shell or run:
156
+ source ~/.bashrc
157
+ """,
158
+ "zsh": f"""
159
+ ✓ Completions installed for zsh
160
+
161
+ To activate, add this to your ~/.zshrc:
162
+ fpath=({path.parent} $fpath)
163
+ autoload -Uz compinit && compinit
164
+
165
+ Then restart your shell or run:
166
+ source ~/.zshrc
167
+ """,
168
+ "fish": f"""
169
+ ✓ Completions installed for fish
170
+
171
+ Completions will be active in new fish shells.
172
+ To activate now, run:
173
+ source {path}
174
+ """,
175
+ "powershell": f"""
176
+ ✓ Completions installed for PowerShell
177
+
178
+ To activate, add this to your PowerShell profile ($PROFILE):
179
+ . {path}
180
+
181
+ Or run:
182
+ echo '. {path}' >> $PROFILE
183
+
184
+ Then restart PowerShell or run:
185
+ . $PROFILE
186
+ """,
187
+ }
188
+
189
+ print(instructions.get(shell, ""))
190
+
191
+
192
+ def is_already_installed(shell):
193
+ """
194
+ Check if completions are already installed for the shell.
195
+
196
+ Args:
197
+ shell: Shell name
198
+
199
+ Returns:
200
+ bool: True if already installed
201
+ """
202
+ path = get_completion_path(shell)
203
+ if not path or not path.exists():
204
+ return False
205
+
206
+ # Check for our signature
207
+ with open(path) as f:
208
+ content = f.read()
209
+ return "# oasr completion" in content.lower()
210
+
211
+
212
+ def run_output(shell):
213
+ """
214
+ Output the completion script to stdout.
215
+
216
+ Args:
217
+ shell: Shell name
218
+
219
+ Returns:
220
+ int: Exit code
221
+ """
222
+ script = get_completion_script(shell)
223
+ print(script)
224
+ return 0
225
+
226
+
227
+ def run_install(args):
228
+ """
229
+ Install completion script.
230
+
231
+ Args:
232
+ args: Parsed arguments
233
+
234
+ Returns:
235
+ int: Exit code
236
+ """
237
+ shell = args.shell if args.shell != "install" else detect_shell()
238
+
239
+ # Check config
240
+ config = load_config()
241
+ if not config.get("oasr", {}).get("completions", True):
242
+ print("Completions are disabled in config (oasr.completions = false)")
243
+ return 1
244
+
245
+ # Check if already installed
246
+ if is_already_installed(shell) and not args.force:
247
+ print(f"✓ Completions already installed for {shell}")
248
+ print(" Use --force to reinstall")
249
+ return 0
250
+
251
+ # Get paths
252
+ path = get_completion_path(shell)
253
+ if not path:
254
+ print(f"Error: Unsupported shell: {shell}", file=sys.stderr)
255
+ return 1
256
+
257
+ # Dry run
258
+ if args.dry_run:
259
+ print(f"Would install completion to: {path}")
260
+ print_activation_instructions(shell, path)
261
+ return 0
262
+
263
+ # Get script
264
+ script = get_completion_script(shell)
265
+
266
+ # Create directory
267
+ path.parent.mkdir(parents=True, exist_ok=True)
268
+
269
+ # Backup existing
270
+ if path.exists() and not is_already_installed(shell):
271
+ import time
272
+
273
+ backup = path.parent / f"{path.name}.backup.{int(time.time())}"
274
+ path.rename(backup)
275
+ print(f"Backed up existing file to: {backup}")
276
+
277
+ # Write script
278
+ path.write_text(script)
279
+
280
+ # Success message
281
+ print_activation_instructions(shell, path)
282
+
283
+ return 0
284
+
285
+
286
+ def run_uninstall(args):
287
+ """
288
+ Uninstall completion script.
289
+
290
+ Args:
291
+ args: Parsed arguments
292
+
293
+ Returns:
294
+ int: Exit code
295
+ """
296
+ shell = args.shell if args.shell != "uninstall" else detect_shell()
297
+
298
+ path = get_completion_path(shell)
299
+ if not path or not path.exists():
300
+ print(f"No completions installed for {shell}")
301
+ return 0
302
+
303
+ # Dry run
304
+ if args.dry_run:
305
+ print(f"Would remove: {path}")
306
+ return 0
307
+
308
+ # Remove file
309
+ path.unlink()
310
+ print(f"✓ Removed completions for {shell}")
311
+
312
+ return 0
313
+
314
+
315
+ def run(args):
316
+ """
317
+ Execute completion command.
318
+
319
+ Args:
320
+ args: Parsed arguments
321
+
322
+ Returns:
323
+ int: Exit code
324
+ """
325
+ # No shell specified - show help
326
+ if not args.shell:
327
+ detected = detect_shell()
328
+ print(f"Detected shell: {detected}")
329
+ print("\nUsage:")
330
+ print(" oasr completion bash # Output bash completion script")
331
+ print(" oasr completion zsh # Output zsh completion script")
332
+ print(" oasr completion fish # Output fish completion script")
333
+ print(" oasr completion powershell # Output PowerShell completion script")
334
+ print(" oasr completion install # Auto-detect and install")
335
+ print(" oasr completion uninstall # Remove installed completions")
336
+ return 0
337
+
338
+ # Handle actions
339
+ if args.shell == "install":
340
+ return run_install(args)
341
+ elif args.shell == "uninstall":
342
+ return run_uninstall(args)
343
+ else:
344
+ # Output script to stdout
345
+ return run_output(args.shell)