oasr 0.5.2__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.
commands/update.py CHANGED
@@ -6,6 +6,8 @@ import argparse
6
6
  import json
7
7
  import subprocess
8
8
  import sys
9
+ import urllib.request
10
+ from importlib import metadata
9
11
  from pathlib import Path
10
12
 
11
13
 
@@ -215,6 +217,51 @@ def get_stats(repo_path: Path, old_commit: str, new_commit: str) -> dict:
215
217
  return stats
216
218
 
217
219
 
220
+ def get_installed_version(package: str = "oasr") -> str | None:
221
+ """Get installed package version."""
222
+ try:
223
+ return metadata.version(package)
224
+ except metadata.PackageNotFoundError:
225
+ return None
226
+
227
+
228
+ def get_latest_pypi_version(package: str = "oasr") -> str | None:
229
+ """Fetch the latest version from PyPI."""
230
+ try:
231
+ with urllib.request.urlopen(f"https://pypi.org/pypi/{package}/json", timeout=5) as response:
232
+ data = json.load(response)
233
+ return data.get("info", {}).get("version")
234
+ except Exception:
235
+ return None
236
+
237
+
238
+ def upgrade_from_pypi(package: str = "oasr") -> tuple[bool, str]:
239
+ """Upgrade ASR using uv or pip."""
240
+ commands = [
241
+ ["uv", "pip", "install", "--upgrade", package],
242
+ [sys.executable, "-m", "pip", "install", "--upgrade", package],
243
+ ]
244
+ last_error = ""
245
+
246
+ for cmd in commands:
247
+ try:
248
+ result = subprocess.run(
249
+ cmd,
250
+ capture_output=True,
251
+ text=True,
252
+ timeout=60,
253
+ )
254
+ if result.returncode == 0:
255
+ runner = "uv" if cmd[0] == "uv" else "pip"
256
+ return True, f"Updated with {runner}"
257
+ last_error = result.stderr.strip() or result.stdout.strip()
258
+ except (subprocess.TimeoutExpired, FileNotFoundError):
259
+ last_error = "Update timed out" if isinstance(sys.exc_info()[1], subprocess.TimeoutExpired) else last_error
260
+ continue
261
+
262
+ return False, last_error or "Failed to update with pip"
263
+
264
+
218
265
  def reinstall_asr(repo_path: Path) -> tuple[bool, str]:
219
266
  """Reinstall ASR using uv or pip.
220
267
 
@@ -293,10 +340,29 @@ def run(args: argparse.Namespace) -> int:
293
340
 
294
341
  if not repo_path:
295
342
  if args.json:
296
- print(json.dumps({"success": False, "error": "Could not find ASR git repository"}))
297
- else:
298
- print("✗ Could not find ASR git repository", file=sys.stderr)
299
- print(" Make sure ASR is installed from git (git clone + pip install -e .)", file=sys.stderr)
343
+ print(
344
+ json.dumps(
345
+ {
346
+ "success": False,
347
+ "error": "Could not find ASR git repository",
348
+ "hint": "Install from git or use pip to update",
349
+ }
350
+ )
351
+ )
352
+ return 1
353
+
354
+ print("✗ Could not find ASR git repository", file=sys.stderr)
355
+ print(" This command updates git installs only.", file=sys.stderr)
356
+ print(" To update PyPI installs:", file=sys.stderr)
357
+ print(" pip install --upgrade oasr", file=sys.stderr)
358
+
359
+ latest_version = get_latest_pypi_version()
360
+ installed_version = get_installed_version()
361
+ if latest_version and installed_version and latest_version != installed_version:
362
+ print(
363
+ f"\nUpdate available: {installed_version} → {latest_version}",
364
+ file=sys.stderr,
365
+ )
300
366
  return 1
301
367
 
302
368
  if not args.quiet and not args.json:
@@ -306,8 +372,10 @@ def run(args: argparse.Namespace) -> int:
306
372
  if not (repo_path / ".git").exists():
307
373
  if args.json:
308
374
  print(json.dumps({"success": False, "error": "Not a git repository"}))
309
- else:
310
- print(f"✗ {repo_path} is not a git repository", file=sys.stderr)
375
+ return 1
376
+ print(f"✗ {repo_path} is not a git repository", file=sys.stderr)
377
+ print(" Use pip to update PyPI installs:", file=sys.stderr)
378
+ print(" pip install --upgrade oasr", file=sys.stderr)
311
379
  return 1
312
380
 
313
381
  # Get remote URL
@@ -348,10 +416,24 @@ def run(args: argparse.Namespace) -> int:
348
416
 
349
417
  # Check if already up to date
350
418
  if message == "already_up_to_date":
419
+ latest_version = get_latest_pypi_version()
420
+ installed_version = get_installed_version()
421
+
351
422
  if args.json:
352
- print(json.dumps({"success": True, "updated": False, "message": "Already up to date"}))
423
+ payload = {"success": True, "updated": False, "message": "Already up to date"}
424
+ if latest_version and installed_version:
425
+ payload["installed_version"] = installed_version
426
+ payload["latest_version"] = latest_version
427
+ payload["pypi_update_available"] = latest_version != installed_version
428
+ print(json.dumps(payload))
353
429
  else:
354
430
  print("✓ Already up to date")
431
+ if latest_version and installed_version and latest_version != installed_version:
432
+ print(
433
+ f"\nPyPI update available: {installed_version} → {latest_version}",
434
+ file=sys.stderr,
435
+ )
436
+ print("Run: pip install --upgrade oasr", file=sys.stderr)
355
437
  return 0
356
438
 
357
439
  # Get new commit
completions/bash.sh CHANGED
@@ -20,13 +20,13 @@ _oasr_agents() {
20
20
  _oasr_profiles() {
21
21
  # Get profile names from config
22
22
  local profiles
23
- profiles=$(oasr config list 2>/dev/null | grep "^profiles\." | sed 's/^profiles\.\([^=]*\)=.*/\1/' | sort -u)
23
+ profiles=$(oasr config profiles --names 2>/dev/null)
24
24
  COMPREPLY=($(compgen -W "$profiles" -- "${COMP_WORDS[COMP_CWORD]}"))
25
25
  }
26
26
 
27
27
  _oasr_config_keys() {
28
28
  # Common config keys
29
- COMPREPLY=($(compgen -W "agent profile adapter.default validation.strict validation.show_references oasr.completions" -- "${COMP_WORDS[COMP_CWORD]}"))
29
+ COMPREPLY=($(compgen -W "agent profile oasr.default_profile adapter.default_targets validation.strict validation.reference_max_lines oasr.completions" -- "${COMP_WORDS[COMP_CWORD]}"))
30
30
  }
31
31
 
32
32
  _oasr_completion_shells() {
@@ -39,7 +39,7 @@ _oasr() {
39
39
 
40
40
  # Top-level commands
41
41
  if [ $COMP_CWORD -eq 1 ]; then
42
- COMPREPLY=($(compgen -W "registry diff sync config clone exec use find validate clean adapter update info help completion" -- "$cur"))
42
+ COMPREPLY=($(compgen -W "registry diff sync config profile clone exec use find validate clean adapter update info help completion" -- "$cur"))
43
43
  return 0
44
44
  fi
45
45
 
@@ -69,7 +69,7 @@ _oasr() {
69
69
  *)
70
70
  # Complete skill names and flags
71
71
  if [[ "$cur" == -* ]]; then
72
- COMPREPLY=($(compgen -W "--agent --profile --agent-flags -y --yes --confirm -p --prompt" -- "$cur"))
72
+ COMPREPLY=($(compgen -W "--agent --profile --agent-flags -y --yes --confirm -p --prompt --unsafe" -- "$cur"))
73
73
  else
74
74
  _oasr_skills
75
75
  fi
@@ -111,7 +111,7 @@ _oasr() {
111
111
 
112
112
  config)
113
113
  if [ $COMP_CWORD -eq 2 ]; then
114
- COMPREPLY=($(compgen -W "set get list path" -- "$cur"))
114
+ COMPREPLY=($(compgen -W "set get list agent validation adapter oasr profiles man validate path" -- "$cur"))
115
115
  return 0
116
116
  fi
117
117
 
@@ -127,12 +127,18 @@ _oasr() {
127
127
  agent)
128
128
  _oasr_agents
129
129
  ;;
130
- profile)
130
+ profile|oasr.default_profile)
131
131
  _oasr_profiles
132
132
  ;;
133
133
  validation.strict|oasr.completions)
134
134
  COMPREPLY=($(compgen -W "true false" -- "$cur"))
135
135
  ;;
136
+ adapter.default_targets)
137
+ return 0
138
+ ;;
139
+ validation.reference_max_lines)
140
+ return 0
141
+ ;;
136
142
  esac
137
143
  fi
138
144
  return 0
@@ -186,6 +192,13 @@ _oasr() {
186
192
  fi
187
193
  ;;
188
194
 
195
+ profile)
196
+ if [ $COMP_CWORD -eq 2 ]; then
197
+ _oasr_profiles
198
+ return 0
199
+ fi
200
+ ;;
201
+
189
202
  find|validate|sync|diff|clean|update|help)
190
203
  # These commands have limited or no additional completion
191
204
  return 0
completions/fish.fish CHANGED
@@ -18,15 +18,16 @@ function __oasr_agents
18
18
  end
19
19
 
20
20
  function __oasr_profiles
21
- oasr config list 2>/dev/null | grep '^profiles\.' | sed 's/^profiles\.\([^=]*\)=.*/\1/' | sort -u
21
+ oasr config profiles --names 2>/dev/null
22
22
  end
23
23
 
24
24
  function __oasr_config_keys
25
25
  echo agent
26
26
  echo profile
27
- echo adapter.default
27
+ echo oasr.default_profile
28
+ echo adapter.default_targets
28
29
  echo validation.strict
29
- echo validation.show_references
30
+ echo validation.reference_max_lines
30
31
  echo oasr.completions
31
32
  end
32
33
 
@@ -45,6 +46,7 @@ complete -c oasr -f -n __fish_use_subcommand -a registry -d "Manage skill regist
45
46
  complete -c oasr -f -n __fish_use_subcommand -a diff -d "Show tracked skill status"
46
47
  complete -c oasr -f -n __fish_use_subcommand -a sync -d "Refresh tracked skills"
47
48
  complete -c oasr -f -n __fish_use_subcommand -a config -d "Manage configuration"
49
+ complete -c oasr -f -n __fish_use_subcommand -a profile -d "Select execution profile"
48
50
  complete -c oasr -f -n __fish_use_subcommand -a clone -d "Clone skills to directory"
49
51
  complete -c oasr -f -n __fish_use_subcommand -a exec -d "Execute a skill"
50
52
  complete -c oasr -f -n __fish_use_subcommand -a use -d "DEPRECATED - use clone"
@@ -58,6 +60,7 @@ complete -c oasr -f -n __fish_use_subcommand -a help -d "Show help"
58
60
  complete -c oasr -f -n __fish_use_subcommand -a completion -d "Manage shell completions"
59
61
 
60
62
  # exec command
63
+ complete -c oasr -f -n "__fish_seen_subcommand_from exec" -l unsafe -d "Pass unsafe agent flags"
61
64
  complete -c oasr -f -n "__fish_seen_subcommand_from exec" -l agent -d "Agent to use" -a "(__oasr_agents)"
62
65
  complete -c oasr -f -n "__fish_seen_subcommand_from exec" -l profile -d "Policy profile" -a "(__oasr_profiles)"
63
66
  complete -c oasr -f -n "__fish_seen_subcommand_from exec" -l agent-flags -d "Additional agent flags"
@@ -78,17 +81,28 @@ complete -c oasr -f -n "__fish_seen_subcommand_from info; and not __fish_seen_su
78
81
  complete -c oasr -f -n "__fish_seen_subcommand_from validate; and not __fish_seen_subcommand_from (__oasr_skills)" -a "(__oasr_skills)" -d "Skill"
79
82
 
80
83
  # config subcommands
81
- complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list path" -a "set" -d "Set configuration value"
82
- complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list path" -a "get" -d "Get configuration value"
83
- complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list path" -a "list" -d "List all configuration"
84
- complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list path" -a "path" -d "Show config file path"
84
+ complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list agent validation adapter oasr profiles man validate path" -a "set" -d "Set configuration value"
85
+ complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list agent validation adapter oasr profiles man validate path" -a "get" -d "Get configuration value"
86
+ complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list agent validation adapter oasr profiles man validate path" -a "list" -d "List all configuration"
87
+ complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list agent validation adapter oasr profiles man validate path" -a "agent" -d "Show agent configuration"
88
+ complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list agent validation adapter oasr profiles man validate path" -a "validation" -d "Show validation settings"
89
+ complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list agent validation adapter oasr profiles man validate path" -a "adapter" -d "Show adapter settings"
90
+ complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list agent validation adapter oasr profiles man validate path" -a "oasr" -d "Show core settings"
91
+ complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list agent validation adapter oasr profiles man validate path" -a "profiles" -d "Show profiles"
92
+ complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list agent validation adapter oasr profiles man validate path" -a "man" -d "Show config reference"
93
+ complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list agent validation adapter oasr profiles man validate path" -a "validate" -d "Validate config file"
94
+ complete -c oasr -f -n "__fish_seen_subcommand_from config; and not __fish_seen_subcommand_from set get list agent validation adapter oasr profiles man validate path" -a "path" -d "Show config file path"
85
95
 
86
96
  # config set/get
87
97
  complete -c oasr -f -n "__fish_seen_subcommand_from config; and __fish_seen_subcommand_from set get" -a "(__oasr_config_keys)" -d "Config key"
88
98
  complete -c oasr -f -n "__fish_seen_subcommand_from config; and __fish_seen_subcommand_from set; and __fish_seen_subcommand_from agent" -a "(__oasr_agents)" -d "Agent"
89
99
  complete -c oasr -f -n "__fish_seen_subcommand_from config; and __fish_seen_subcommand_from set; and __fish_seen_subcommand_from profile" -a "(__oasr_profiles)" -d "Profile"
100
+ complete -c oasr -f -n "__fish_seen_subcommand_from config; and __fish_seen_subcommand_from set; and __fish_seen_subcommand_from oasr.default_profile" -a "(__oasr_profiles)" -d "Profile"
90
101
  complete -c oasr -f -n "__fish_seen_subcommand_from config; and __fish_seen_subcommand_from set; and __fish_seen_subcommand_from validation.strict oasr.completions" -a "true false" -d "Boolean"
91
102
 
103
+ # profile command
104
+ complete -c oasr -f -n "__fish_seen_subcommand_from profile" -a "(__oasr_profiles)" -d "Profile"
105
+
92
106
  # registry subcommands
93
107
  complete -c oasr -f -n "__fish_seen_subcommand_from registry; and not __fish_seen_subcommand_from add rm sync list validate prune" -a "add" -d "Add skill to registry"
94
108
  complete -c oasr -f -n "__fish_seen_subcommand_from registry; and not __fish_seen_subcommand_from add rm sync list validate prune" -a "rm" -d "Remove skill from registry"
@@ -18,10 +18,8 @@ function Get-OasrAgents {
18
18
  }
19
19
 
20
20
  function Get-OasrProfiles {
21
- $profiles = oasr config list 2>$null | Select-String '^profiles\.' | ForEach-Object {
22
- if ($_ -match '^profiles\.([^=]+)=') {
23
- $matches[1]
24
- }
21
+ $profiles = oasr config profiles --names 2>$null | ForEach-Object {
22
+ $_
25
23
  } | Sort-Object -Unique
26
24
  return $profiles
27
25
  }
@@ -30,9 +28,10 @@ function Get-OasrConfigKeys {
30
28
  return @(
31
29
  'agent',
32
30
  'profile',
33
- 'adapter.default',
31
+ 'oasr.default_profile',
32
+ 'adapter.default_targets',
34
33
  'validation.strict',
35
- 'validation.show_references',
34
+ 'validation.reference_max_lines',
36
35
  'oasr.completions'
37
36
  )
38
37
  }
@@ -48,7 +47,7 @@ $oasrCompletion = {
48
47
  # First argument - main commands
49
48
  if ($elementCount -eq 2) {
50
49
  $commands = @(
51
- 'registry', 'diff', 'sync', 'config', 'clone', 'exec', 'use',
50
+ 'registry', 'diff', 'sync', 'config', 'profile', 'clone', 'exec', 'use',
52
51
  'find', 'validate', 'clean', 'adapter', 'update', 'info',
53
52
  'help', 'completion'
54
53
  )
@@ -79,7 +78,7 @@ $oasrCompletion = {
79
78
  }
80
79
  default {
81
80
  if ($wordToComplete -like '-*') {
82
- @('--agent', '--profile', '--agent-flags', '-y', '--yes', '--confirm', '-p', '--prompt') |
81
+ @('--agent', '--profile', '--agent-flags', '-y', '--yes', '--confirm', '-p', '--prompt', '--unsafe') |
83
82
  Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
84
83
  [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
85
84
  }
@@ -93,6 +92,67 @@ $oasrCompletion = {
93
92
  }
94
93
  }
95
94
 
95
+ 'config' {
96
+ if ($elementCount -eq 3) {
97
+ @('set', 'get', 'list', 'agent', 'validation', 'adapter', 'oasr', 'profiles', 'man', 'validate', 'path') |
98
+ Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
99
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
100
+ }
101
+ return
102
+ }
103
+
104
+ $prevWord = if ($elementCount -gt 2) { $elements[$elementCount - 2].Value } else { '' }
105
+ if ($prevWord -eq 'set' -and $elementCount -eq 4) {
106
+ Get-OasrConfigKeys | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
107
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Key: $_")
108
+ }
109
+ return
110
+ }
111
+
112
+ if ($prevWord -eq 'set' -and $elementCount -eq 5) {
113
+ $key = $elements[3].Value
114
+ switch ($key) {
115
+ 'agent' {
116
+ Get-OasrAgents | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
117
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Agent: $_")
118
+ }
119
+ return
120
+ }
121
+ 'profile' {
122
+ Get-OasrProfiles | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
123
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Profile: $_")
124
+ }
125
+ return
126
+ }
127
+ 'oasr.default_profile' {
128
+ Get-OasrProfiles | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
129
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Profile: $_")
130
+ }
131
+ return
132
+ }
133
+ 'validation.strict' {
134
+ @('true', 'false') | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
135
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
136
+ }
137
+ return
138
+ }
139
+ 'oasr.completions' {
140
+ @('true', 'false') | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
141
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
142
+ }
143
+ return
144
+ }
145
+ }
146
+ }
147
+
148
+ if ($elements[2].Value -eq 'get' -and $elementCount -eq 4) {
149
+ Get-OasrConfigKeys | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
150
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Key: $_")
151
+ }
152
+ return
153
+ }
154
+ }
155
+
96
156
  'completion' {
97
157
  if ($elementCount -eq 3) {
98
158
  @('bash', 'zsh', 'fish', 'powershell', 'install', 'uninstall') | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
@@ -108,6 +168,15 @@ $oasrCompletion = {
108
168
  }
109
169
  return
110
170
  }
171
+
172
+ 'profile' {
173
+ if ($elementCount -eq 3) {
174
+ Get-OasrProfiles | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
175
+ [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', "Profile: $_")
176
+ }
177
+ return
178
+ }
179
+ }
111
180
  }
112
181
  }
113
182
 
completions/zsh.sh CHANGED
@@ -25,7 +25,7 @@ _oasr_agents() {
25
25
 
26
26
  _oasr_profiles() {
27
27
  local profiles
28
- profiles=(${(f)"$(oasr config list 2>/dev/null | grep '^profiles\.' | sed 's/^profiles\.\([^=]*\)=.*/\1/' | sort -u)"})
28
+ profiles=(${(f)"$(oasr config profiles --names 2>/dev/null)"})
29
29
  _describe 'profile' profiles
30
30
  }
31
31
 
@@ -34,9 +34,9 @@ _oasr_config_keys() {
34
34
  keys=(
35
35
  'agent:Default agent'
36
36
  'profile:Default policy profile'
37
- 'adapter.default:Default adapter target'
37
+ 'adapter.default_targets:Default adapter targets'
38
38
  'validation.strict:Strict validation'
39
- 'validation.show_references:Show references'
39
+ 'validation.reference_max_lines:Reference max lines'
40
40
  'oasr.completions:Enable completions'
41
41
  )
42
42
  _describe 'config key' keys
@@ -48,7 +48,8 @@ _oasr_exec() {
48
48
  '--profile[Policy profile]:profile:_oasr_profiles' \
49
49
  '--agent-flags[Additional agent flags]:flags:' \
50
50
  '(-y --yes)'{-y,--yes}'[Skip confirmation]' \
51
- '--confirm[Force confirmation]' \
51
+ '--confirm[Force confirmation] \
52
+ --unsafe[Pass unsafe agent flags]' \
52
53
  '(-p --prompt)'{-p,--prompt}'[Prompt from file]:file:_files' \
53
54
  '1:skill:_oasr_skills'
54
55
  }
@@ -76,6 +77,13 @@ _oasr_config() {
76
77
  'set:Set a configuration value'
77
78
  'get:Get a configuration value'
78
79
  'list:List all configuration'
80
+ 'agent:Show agent configuration'
81
+ 'validation:Show validation settings'
82
+ 'adapter:Show adapter settings'
83
+ 'oasr:Show core settings'
84
+ 'profiles:Show profiles'
85
+ 'man:Show config reference'
86
+ 'validate:Validate config file'
79
87
  'path:Show config file path'
80
88
  )
81
89
 
@@ -97,12 +105,18 @@ _oasr_config() {
97
105
  agent)
98
106
  _oasr_agents
99
107
  ;;
100
- profile)
108
+ profile|oasr.default_profile)
101
109
  _oasr_profiles
102
110
  ;;
103
111
  validation.strict|oasr.completions)
104
112
  _values 'boolean' true false
105
113
  ;;
114
+ validation.reference_max_lines)
115
+ _message 'integer'
116
+ ;;
117
+ adapter.default_targets)
118
+ _message 'comma-separated list'
119
+ ;;
106
120
  esac
107
121
  fi
108
122
  ;;
@@ -114,6 +128,11 @@ _oasr_config() {
114
128
  esac
115
129
  }
116
130
 
131
+ _oasr_profile() {
132
+ _arguments \
133
+ '1:profile:_oasr_profiles'
134
+ }
135
+
117
136
  _oasr_registry() {
118
137
  local -a subcommands
119
138
  subcommands=(
@@ -199,6 +218,7 @@ _oasr() {
199
218
  'diff:Show tracked skill status'
200
219
  'sync:Refresh tracked skills'
201
220
  'config:Manage configuration'
221
+ 'profile:Select execution profile'
202
222
  'clone:Clone skills to directory'
203
223
  'exec:Execute a skill'
204
224
  'use:DEPRECATED - use clone instead'
@@ -230,6 +250,9 @@ _oasr() {
230
250
  exec)
231
251
  _oasr_exec
232
252
  ;;
253
+ profile)
254
+ _oasr_profile
255
+ ;;
233
256
  clone)
234
257
  _oasr_clone
235
258
  ;;
@@ -242,6 +265,9 @@ _oasr() {
242
265
  config)
243
266
  _oasr_config
244
267
  ;;
268
+ profile)
269
+ _oasr_profile
270
+ ;;
245
271
  registry)
246
272
  _oasr_registry
247
273
  ;;
config/__init__.py CHANGED
@@ -14,6 +14,7 @@ import tomli_w
14
14
  from config.defaults import DEFAULT_CONFIG
15
15
  from config.env import load_env_config, merge_configs
16
16
  from config.schema import validate_config
17
+ from profiles.loader import load_profiles
17
18
 
18
19
  OASR_DIR = Path.home() / ".oasr"
19
20
  CONFIG_FILE = OASR_DIR / "config.toml"
@@ -75,6 +76,16 @@ def load_config(config_path: Path | None = None, cli_overrides: dict[str, Any] |
75
76
  with open(path, "rb") as f:
76
77
  file_config = tomllib.load(f)
77
78
 
79
+ if not isinstance(file_config, dict):
80
+ file_config = {}
81
+
82
+ # Merge profile files with inline profiles (inline wins)
83
+ inline_profiles = file_config.get("profiles", {})
84
+ if not isinstance(inline_profiles, dict):
85
+ inline_profiles = {}
86
+ merged_profiles = load_profiles(inline_profiles=inline_profiles)
87
+ file_config["profiles"] = merged_profiles
88
+
78
89
  # Load environment variables
79
90
  env_config = load_env_config()
80
91
 
config/defaults.py CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
+ from profiles.builtins import BUILTIN_PROFILES
6
+
5
7
  DEFAULT_CONFIG: dict[str, Any] = {
6
8
  "validation": {
7
9
  "reference_max_lines": 500,
@@ -15,26 +17,7 @@ DEFAULT_CONFIG: dict[str, Any] = {
15
17
  },
16
18
  "oasr": {
17
19
  "default_profile": "safe",
20
+ "completions": True,
18
21
  },
19
- "profiles": {
20
- # Built-in safe profile (always available as fallback)
21
- "safe": {
22
- "fs_read_roots": ["./"],
23
- "fs_write_roots": ["./out", "./.oasr"],
24
- "deny_paths": [
25
- "~/.ssh",
26
- "~/.aws",
27
- "~/.gnupg",
28
- "~/.config",
29
- ".env",
30
- "~/.bashrc",
31
- "~/.zshrc",
32
- "~/.profile",
33
- ],
34
- "allowed_commands": ["rg", "fd", "jq", "cat"],
35
- "deny_shell": True,
36
- "network": False,
37
- "allow_env": False,
38
- },
39
- },
22
+ "profiles": {name: values.copy() for name, values in BUILTIN_PROFILES.items()},
40
23
  }
config/schema.py CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  from typing import Any
4
4
 
5
+ from profiles.validation import validate_profiles
6
+
5
7
  VALID_AGENTS = {"codex", "copilot", "claude", "opencode"}
6
8
 
7
9
 
@@ -80,32 +82,4 @@ def validate_config(config: dict[str, Any]) -> None:
80
82
  raise ValueError("oasr.default_profile must be a string")
81
83
 
82
84
  if "profiles" in config:
83
- if not isinstance(config["profiles"], dict):
84
- raise ValueError("profiles must be a table (dictionary)")
85
-
86
- # Validate each profile structure
87
- for profile_name, profile_data in config["profiles"].items():
88
- if not isinstance(profile_data, dict):
89
- raise ValueError(f"Profile '{profile_name}' must be a table (dictionary)")
90
-
91
- # Validate profile fields if present
92
- if "fs_read_roots" in profile_data and not isinstance(profile_data["fs_read_roots"], list):
93
- raise ValueError(f"Profile '{profile_name}': fs_read_roots must be a list")
94
-
95
- if "fs_write_roots" in profile_data and not isinstance(profile_data["fs_write_roots"], list):
96
- raise ValueError(f"Profile '{profile_name}': fs_write_roots must be a list")
97
-
98
- if "deny_paths" in profile_data and not isinstance(profile_data["deny_paths"], list):
99
- raise ValueError(f"Profile '{profile_name}': deny_paths must be a list")
100
-
101
- if "allowed_commands" in profile_data and not isinstance(profile_data["allowed_commands"], list):
102
- raise ValueError(f"Profile '{profile_name}': allowed_commands must be a list")
103
-
104
- if "deny_shell" in profile_data and not isinstance(profile_data["deny_shell"], bool):
105
- raise ValueError(f"Profile '{profile_name}': deny_shell must be a boolean")
106
-
107
- if "network" in profile_data and not isinstance(profile_data["network"], bool):
108
- raise ValueError(f"Profile '{profile_name}': network must be a boolean")
109
-
110
- if "allow_env" in profile_data and not isinstance(profile_data["allow_env"], bool):
111
- raise ValueError(f"Profile '{profile_name}': allow_env must be a boolean")
85
+ validate_profiles(config["profiles"])