oasr 0.5.0__py3-none-any.whl → 0.5.1__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.
- cli.py +1 -1
- commands/config.py +78 -26
- commands/exec.py +22 -10
- config/__init__.py +27 -18
- config/env.py +248 -0
- config/schema.py +38 -0
- {oasr-0.5.0.dist-info → oasr-0.5.1.dist-info}/METADATA +1 -1
- {oasr-0.5.0.dist-info → oasr-0.5.1.dist-info}/RECORD +12 -11
- {oasr-0.5.0.dist-info → oasr-0.5.1.dist-info}/WHEEL +0 -0
- {oasr-0.5.0.dist-info → oasr-0.5.1.dist-info}/entry_points.txt +0 -0
- {oasr-0.5.0.dist-info → oasr-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {oasr-0.5.0.dist-info → oasr-0.5.1.dist-info}/licenses/NOTICE +0 -0
cli.py
CHANGED
|
@@ -13,7 +13,7 @@ from pathlib import Path
|
|
|
13
13
|
from commands import adapter, clean, clone, config, diff, exec, find, registry, sync, update, use, validate
|
|
14
14
|
from commands import help as help_cmd
|
|
15
15
|
|
|
16
|
-
__version__ = "0.5.
|
|
16
|
+
__version__ = "0.5.1"
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
def main(argv: list[str] | None = None) -> int:
|
commands/config.py
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
import argparse
|
|
4
4
|
import sys
|
|
5
5
|
|
|
6
|
-
from agents import detect_available_agents
|
|
6
|
+
from agents import detect_available_agents
|
|
7
7
|
from config import CONFIG_FILE, load_config, save_config
|
|
8
|
+
from config.schema import validate_agent, validate_profile_reference
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
def register(subparsers: argparse._SubParsersAction) -> None:
|
|
@@ -25,6 +26,11 @@ def register(subparsers: argparse._SubParsersAction) -> None:
|
|
|
25
26
|
)
|
|
26
27
|
set_parser.add_argument("key", help="Configuration key (e.g., 'agent')")
|
|
27
28
|
set_parser.add_argument("value", help="Configuration value")
|
|
29
|
+
set_parser.add_argument(
|
|
30
|
+
"--force",
|
|
31
|
+
action="store_true",
|
|
32
|
+
help="Skip validation (use carefully)",
|
|
33
|
+
)
|
|
28
34
|
set_parser.set_defaults(func=run_set)
|
|
29
35
|
|
|
30
36
|
# config get
|
|
@@ -57,40 +63,86 @@ def register(subparsers: argparse._SubParsersAction) -> None:
|
|
|
57
63
|
|
|
58
64
|
|
|
59
65
|
def run_set(args: argparse.Namespace) -> int:
|
|
60
|
-
"""Set a configuration value."""
|
|
66
|
+
"""Set a configuration value with validation."""
|
|
61
67
|
key = args.key.lower()
|
|
62
68
|
value = args.value
|
|
69
|
+
force = getattr(args, "force", False)
|
|
63
70
|
|
|
64
|
-
#
|
|
65
|
-
if
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
print(
|
|
70
|
-
f"Error: Invalid agent '{value}'. Must be one of: {', '.join(valid_agents)}",
|
|
71
|
-
file=sys.stderr,
|
|
72
|
-
)
|
|
71
|
+
# Parse key (support dotted notation like "validation.strict")
|
|
72
|
+
if "." in key:
|
|
73
|
+
parts = key.split(".", 1)
|
|
74
|
+
if len(parts) != 2:
|
|
75
|
+
print(f"Error: Invalid key '{key}'. Use format 'section.field' or 'agent'", file=sys.stderr)
|
|
73
76
|
return 1
|
|
77
|
+
section, field = parts
|
|
78
|
+
elif key == "agent":
|
|
79
|
+
# Special case: bare "agent" means "agent.default"
|
|
80
|
+
section, field = "agent", "default"
|
|
81
|
+
else:
|
|
82
|
+
print(f"Error: Invalid key '{key}'. Use format 'section.field' or 'agent'", file=sys.stderr)
|
|
83
|
+
return 1
|
|
74
84
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
85
|
+
# Type coercion based on field
|
|
86
|
+
original_value = value
|
|
87
|
+
if field == "strict":
|
|
88
|
+
value = value.lower() in ("true", "1", "yes", "on")
|
|
89
|
+
elif field == "reference_max_lines":
|
|
90
|
+
try:
|
|
91
|
+
value = int(value)
|
|
92
|
+
if value < 1:
|
|
93
|
+
print(f"Error: '{field}' must be a positive integer", file=sys.stderr)
|
|
94
|
+
return 1
|
|
95
|
+
except ValueError:
|
|
96
|
+
print(f"Error: '{field}' must be an integer", file=sys.stderr)
|
|
97
|
+
return 1
|
|
79
98
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
99
|
+
# Load config
|
|
100
|
+
config_path = getattr(args, "config", None)
|
|
101
|
+
config = load_config(config_path=config_path)
|
|
102
|
+
|
|
103
|
+
# Validate before setting (unless --force)
|
|
104
|
+
if not force:
|
|
105
|
+
# Validate agent
|
|
106
|
+
if section == "agent" and field == "default":
|
|
107
|
+
is_valid, error_msg = validate_agent(value)
|
|
108
|
+
if not is_valid:
|
|
109
|
+
print(f"Error: {error_msg}", file=sys.stderr)
|
|
110
|
+
print("\nTo set anyway, use: oasr config set --force agent <name>", file=sys.stderr)
|
|
111
|
+
return 1
|
|
112
|
+
|
|
113
|
+
# Validate profile reference
|
|
114
|
+
if section == "oasr" and field == "default_profile":
|
|
115
|
+
is_valid, error_msg = validate_profile_reference(value, config)
|
|
116
|
+
if not is_valid:
|
|
117
|
+
print(f"Error: {error_msg}", file=sys.stderr)
|
|
118
|
+
print("\nCreate the profile in ~/.oasr/config.toml first, or use:", file=sys.stderr)
|
|
119
|
+
print(f" oasr config set --force oasr.default_profile {value}", file=sys.stderr)
|
|
120
|
+
return 1
|
|
121
|
+
|
|
122
|
+
# Set the value
|
|
123
|
+
if section not in config:
|
|
124
|
+
config[section] = {}
|
|
125
|
+
|
|
126
|
+
config[section][field] = value
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
save_config(config, config_path=config_path)
|
|
130
|
+
|
|
131
|
+
# Show confirmation
|
|
132
|
+
if section == "agent" and field == "default":
|
|
133
|
+
# Special handling for agent - check if available
|
|
134
|
+
available = detect_available_agents()
|
|
135
|
+
if value in available:
|
|
136
|
+
print(f"✓ Default agent set to: {value}")
|
|
137
|
+
else:
|
|
138
|
+
print(f"✓ Default agent set to: {value}")
|
|
139
|
+
print(f" Warning: '{value}' binary not found in PATH. Install it to use this agent.", file=sys.stderr)
|
|
84
140
|
else:
|
|
85
|
-
print(f"✓
|
|
86
|
-
print(
|
|
87
|
-
f" Warning: '{value}' binary not found in PATH. Install it to use this agent.",
|
|
88
|
-
file=sys.stderr,
|
|
89
|
-
)
|
|
141
|
+
print(f"✓ Set {section}.{field} = {original_value}")
|
|
90
142
|
|
|
91
143
|
return 0
|
|
92
|
-
|
|
93
|
-
print(f"Error:
|
|
144
|
+
except ValueError as e:
|
|
145
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
94
146
|
return 1
|
|
95
147
|
|
|
96
148
|
|
commands/exec.py
CHANGED
|
@@ -101,18 +101,30 @@ def run(args: argparse.Namespace) -> int:
|
|
|
101
101
|
# Error already printed by _get_user_prompt
|
|
102
102
|
return 1
|
|
103
103
|
|
|
104
|
-
# Determine which agent to use
|
|
105
|
-
agent_name = _get_agent_name(args)
|
|
106
|
-
if agent_name is None:
|
|
107
|
-
# Error already printed by _get_agent_name
|
|
108
|
-
return 1
|
|
109
|
-
|
|
110
104
|
# === POLICY ENFORCEMENT ===
|
|
111
|
-
#
|
|
112
|
-
|
|
105
|
+
# Build CLI overrides for config loading
|
|
106
|
+
cli_overrides = {}
|
|
107
|
+
if args.agent:
|
|
108
|
+
cli_overrides["agent"] = {"default": args.agent}
|
|
109
|
+
if args.profile:
|
|
110
|
+
cli_overrides["oasr"] = cli_overrides.get("oasr", {})
|
|
111
|
+
cli_overrides["oasr"]["default_profile"] = args.profile
|
|
112
|
+
|
|
113
|
+
# Load configuration with precedence: CLI > env > file > defaults
|
|
114
|
+
config = load_config(cli_overrides=cli_overrides)
|
|
113
115
|
|
|
114
|
-
# Determine
|
|
115
|
-
|
|
116
|
+
# Determine agent and profile from merged config
|
|
117
|
+
agent_name = config.get("agent", {}).get("default")
|
|
118
|
+
profile_name = config.get("oasr", {}).get("default_profile", "safe")
|
|
119
|
+
|
|
120
|
+
# Validate agent is set
|
|
121
|
+
if not agent_name:
|
|
122
|
+
print(
|
|
123
|
+
"Error: No agent configured. Set OASR_AGENT, use --agent flag, or run:",
|
|
124
|
+
file=sys.stderr,
|
|
125
|
+
)
|
|
126
|
+
print(" oasr config set agent <name>", file=sys.stderr)
|
|
127
|
+
return 1
|
|
116
128
|
|
|
117
129
|
# Load the policy profile
|
|
118
130
|
profile = policy.load(config, profile_name)
|
config/__init__.py
CHANGED
|
@@ -12,6 +12,7 @@ else:
|
|
|
12
12
|
import tomli_w
|
|
13
13
|
|
|
14
14
|
from config.defaults import DEFAULT_CONFIG
|
|
15
|
+
from config.env import load_env_config, merge_configs
|
|
15
16
|
from config.schema import validate_config
|
|
16
17
|
|
|
17
18
|
OASR_DIR = Path.home() / ".oasr"
|
|
@@ -40,19 +41,27 @@ def ensure_skills_dir() -> Path:
|
|
|
40
41
|
return ensure_oasr_dir()
|
|
41
42
|
|
|
42
43
|
|
|
43
|
-
def load_config(config_path: Path | None = None) -> dict[str, Any]:
|
|
44
|
-
"""Load configuration from
|
|
44
|
+
def load_config(config_path: Path | None = None, cli_overrides: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
45
|
+
"""Load configuration from multiple sources with precedence.
|
|
46
|
+
|
|
47
|
+
Precedence order (highest to lowest):
|
|
48
|
+
1. cli_overrides - explicit CLI flags
|
|
49
|
+
2. Environment variables (OASR_*)
|
|
50
|
+
3. Config file (~/.oasr/config.toml)
|
|
51
|
+
4. Built-in defaults
|
|
45
52
|
|
|
46
53
|
Args:
|
|
47
54
|
config_path: Override config file path. Defaults to ~/.oasr/config.toml.
|
|
55
|
+
cli_overrides: Optional CLI flag overrides (highest precedence)
|
|
48
56
|
|
|
49
57
|
Returns:
|
|
50
|
-
|
|
58
|
+
Merged configuration dictionary with all sources applied.
|
|
51
59
|
"""
|
|
52
60
|
path = config_path or CONFIG_FILE
|
|
61
|
+
cli_overrides = cli_overrides or {}
|
|
53
62
|
|
|
54
63
|
# Deep copy defaults
|
|
55
|
-
|
|
64
|
+
defaults = {
|
|
56
65
|
"validation": DEFAULT_CONFIG["validation"].copy(),
|
|
57
66
|
"adapter": DEFAULT_CONFIG["adapter"].copy(),
|
|
58
67
|
"agent": DEFAULT_CONFIG["agent"].copy(),
|
|
@@ -60,22 +69,22 @@ def load_config(config_path: Path | None = None) -> dict[str, Any]:
|
|
|
60
69
|
"profiles": {k: v.copy() for k, v in DEFAULT_CONFIG["profiles"].items()},
|
|
61
70
|
}
|
|
62
71
|
|
|
72
|
+
# Load config file
|
|
73
|
+
file_config = {}
|
|
63
74
|
if path.exists():
|
|
64
75
|
with open(path, "rb") as f:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
for profile_name, profile_data in loaded["profiles"].items():
|
|
78
|
-
config["profiles"][profile_name] = profile_data
|
|
76
|
+
file_config = tomllib.load(f)
|
|
77
|
+
|
|
78
|
+
# Load environment variables
|
|
79
|
+
env_config = load_env_config()
|
|
80
|
+
|
|
81
|
+
# Merge all sources with precedence
|
|
82
|
+
config = merge_configs(
|
|
83
|
+
cli_overrides=cli_overrides,
|
|
84
|
+
env_config=env_config,
|
|
85
|
+
file_config=file_config,
|
|
86
|
+
defaults=defaults,
|
|
87
|
+
)
|
|
79
88
|
|
|
80
89
|
return config
|
|
81
90
|
|
config/env.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""Environment variable configuration support.
|
|
2
|
+
|
|
3
|
+
This module provides functionality to load OASR configuration from environment
|
|
4
|
+
variables, following the pattern: OASR_<SECTION>_<KEY>
|
|
5
|
+
|
|
6
|
+
Precedence order:
|
|
7
|
+
1. CLI flags (highest)
|
|
8
|
+
2. Environment variables
|
|
9
|
+
3. Config file
|
|
10
|
+
4. Built-in defaults (lowest)
|
|
11
|
+
|
|
12
|
+
Environment variable naming:
|
|
13
|
+
- Prefix: OASR_
|
|
14
|
+
- Format: OASR_<SECTION>_<KEY> (uppercase, underscore-separated)
|
|
15
|
+
- Examples:
|
|
16
|
+
OASR_AGENT=codex → agent.default = "codex"
|
|
17
|
+
OASR_PROFILE=dev → oasr.default_profile = "dev"
|
|
18
|
+
OASR_VALIDATION_STRICT=true → validation.strict = true
|
|
19
|
+
|
|
20
|
+
Type handling:
|
|
21
|
+
- Strings: as-is
|
|
22
|
+
- Booleans: true/false, 1/0, yes/no, on/off (case-insensitive)
|
|
23
|
+
- Integers: parsed with int()
|
|
24
|
+
- Lists: comma-separated values
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
import os
|
|
28
|
+
from typing import Any
|
|
29
|
+
|
|
30
|
+
# Mapping of environment variable names to config paths
|
|
31
|
+
ENV_VAR_MAP = {
|
|
32
|
+
"OASR_AGENT": ("agent", "default"),
|
|
33
|
+
"OASR_PROFILE": ("oasr", "default_profile"),
|
|
34
|
+
"OASR_VALIDATION_STRICT": ("validation", "strict"),
|
|
35
|
+
"OASR_VALIDATION_MAX_LINES": ("validation", "reference_max_lines"),
|
|
36
|
+
"OASR_ADAPTER_TARGETS": ("adapter", "default_targets"),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def parse_bool(value: str) -> bool:
|
|
41
|
+
"""Parse boolean from string.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
value: String value to parse
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Boolean value
|
|
48
|
+
|
|
49
|
+
Raises:
|
|
50
|
+
ValueError: If value cannot be parsed as boolean
|
|
51
|
+
"""
|
|
52
|
+
value_lower = value.lower()
|
|
53
|
+
if value_lower in ("true", "1", "yes", "on"):
|
|
54
|
+
return True
|
|
55
|
+
if value_lower in ("false", "0", "no", "off"):
|
|
56
|
+
return False
|
|
57
|
+
raise ValueError(f"Cannot parse '{value}' as boolean")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def parse_int(value: str) -> int:
|
|
61
|
+
"""Parse integer from string.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
value: String value to parse
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
Integer value
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
ValueError: If value cannot be parsed as integer
|
|
71
|
+
"""
|
|
72
|
+
return int(value)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def parse_list(value: str) -> list[str]:
|
|
76
|
+
"""Parse list from comma-separated string.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
value: Comma-separated string
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
List of strings (trimmed)
|
|
83
|
+
"""
|
|
84
|
+
return [item.strip() for item in value.split(",") if item.strip()]
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def parse_value(value: str, expected_type: type) -> Any:
|
|
88
|
+
"""Parse environment variable value based on expected type.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
value: String value from environment
|
|
92
|
+
expected_type: Expected Python type
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Parsed value of appropriate type
|
|
96
|
+
|
|
97
|
+
Raises:
|
|
98
|
+
ValueError: If value cannot be parsed to expected type
|
|
99
|
+
"""
|
|
100
|
+
if expected_type is bool:
|
|
101
|
+
return parse_bool(value)
|
|
102
|
+
elif expected_type is int:
|
|
103
|
+
return parse_int(value)
|
|
104
|
+
elif expected_type is list:
|
|
105
|
+
return parse_list(value)
|
|
106
|
+
else:
|
|
107
|
+
return value # String, return as-is
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def load_env_config() -> dict[str, dict[str, Any]]:
|
|
111
|
+
"""Load configuration from environment variables.
|
|
112
|
+
|
|
113
|
+
Reads all OASR_* environment variables and converts them to a nested
|
|
114
|
+
dictionary structure matching the config file format.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Nested dictionary with config values from environment variables
|
|
118
|
+
Example: {"agent": {"default": "codex"}, "oasr": {"default_profile": "safe"}}
|
|
119
|
+
|
|
120
|
+
Notes:
|
|
121
|
+
- Only processes variables defined in ENV_VAR_MAP
|
|
122
|
+
- Silently skips variables with invalid values (logs warning)
|
|
123
|
+
- Returns empty sections if no relevant env vars set
|
|
124
|
+
"""
|
|
125
|
+
config = {}
|
|
126
|
+
|
|
127
|
+
for env_var, (section, key) in ENV_VAR_MAP.items():
|
|
128
|
+
value = os.getenv(env_var)
|
|
129
|
+
if value is None:
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
# Determine expected type based on key
|
|
133
|
+
# This is a heuristic - we infer type from key name
|
|
134
|
+
expected_type = str # Default
|
|
135
|
+
if key == "strict":
|
|
136
|
+
expected_type = bool
|
|
137
|
+
elif key == "reference_max_lines":
|
|
138
|
+
expected_type = int
|
|
139
|
+
elif key == "default_targets":
|
|
140
|
+
expected_type = list
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
parsed_value = parse_value(value, expected_type)
|
|
144
|
+
|
|
145
|
+
# Create section if it doesn't exist
|
|
146
|
+
if section not in config:
|
|
147
|
+
config[section] = {}
|
|
148
|
+
|
|
149
|
+
config[section][key] = parsed_value
|
|
150
|
+
|
|
151
|
+
except (ValueError, TypeError) as e:
|
|
152
|
+
# Log warning but continue (fail-safe)
|
|
153
|
+
import sys
|
|
154
|
+
|
|
155
|
+
print(
|
|
156
|
+
f"⚠ Warning: Invalid value for {env_var}='{value}': {e}. Skipping.",
|
|
157
|
+
file=sys.stderr,
|
|
158
|
+
)
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
return config
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def merge_configs(
|
|
165
|
+
cli_overrides: dict[str, Any],
|
|
166
|
+
env_config: dict[str, dict[str, Any]],
|
|
167
|
+
file_config: dict[str, dict[str, Any]],
|
|
168
|
+
defaults: dict[str, dict[str, Any]],
|
|
169
|
+
) -> dict[str, dict[str, Any]]:
|
|
170
|
+
"""Merge configurations from multiple sources with correct precedence.
|
|
171
|
+
|
|
172
|
+
Precedence order (highest to lowest):
|
|
173
|
+
1. cli_overrides - explicit CLI flags
|
|
174
|
+
2. env_config - environment variables
|
|
175
|
+
3. file_config - config file values
|
|
176
|
+
4. defaults - built-in defaults
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
cli_overrides: Values explicitly set via CLI flags
|
|
180
|
+
env_config: Values from environment variables
|
|
181
|
+
file_config: Values from config file
|
|
182
|
+
defaults: Built-in default values
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Merged configuration dictionary
|
|
186
|
+
|
|
187
|
+
Notes:
|
|
188
|
+
- CLI overrides take precedence over everything
|
|
189
|
+
- Environment variables override config file
|
|
190
|
+
- Config file overrides defaults
|
|
191
|
+
- Sections are merged independently
|
|
192
|
+
"""
|
|
193
|
+
result = {}
|
|
194
|
+
|
|
195
|
+
# Get all sections from all sources
|
|
196
|
+
all_sections = set()
|
|
197
|
+
for config in [defaults, file_config, env_config, cli_overrides]:
|
|
198
|
+
all_sections.update(config.keys())
|
|
199
|
+
|
|
200
|
+
# Merge each section with precedence
|
|
201
|
+
for section in all_sections:
|
|
202
|
+
result[section] = {}
|
|
203
|
+
|
|
204
|
+
# Start with defaults
|
|
205
|
+
if section in defaults:
|
|
206
|
+
result[section].update(defaults[section])
|
|
207
|
+
|
|
208
|
+
# Override with file config
|
|
209
|
+
if section in file_config:
|
|
210
|
+
result[section].update(file_config[section])
|
|
211
|
+
|
|
212
|
+
# Override with env config
|
|
213
|
+
if section in env_config:
|
|
214
|
+
result[section].update(env_config[section])
|
|
215
|
+
|
|
216
|
+
# Override with CLI (if present)
|
|
217
|
+
if section in cli_overrides:
|
|
218
|
+
result[section].update(cli_overrides[section])
|
|
219
|
+
|
|
220
|
+
return result
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def get_config_source(
|
|
224
|
+
section: str,
|
|
225
|
+
key: str,
|
|
226
|
+
cli_overrides: dict[str, Any],
|
|
227
|
+
env_config: dict[str, dict[str, Any]],
|
|
228
|
+
file_config: dict[str, dict[str, Any]],
|
|
229
|
+
) -> str:
|
|
230
|
+
"""Determine the source of a config value.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
section: Config section name
|
|
234
|
+
key: Config key name
|
|
235
|
+
cli_overrides: CLI flag overrides
|
|
236
|
+
env_config: Environment variable config
|
|
237
|
+
file_config: Config file values
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Source string: "cli flag", "env var", "config file", or "default"
|
|
241
|
+
"""
|
|
242
|
+
if section in cli_overrides and key in cli_overrides[section]:
|
|
243
|
+
return "cli flag"
|
|
244
|
+
if section in env_config and key in env_config.get(section, {}):
|
|
245
|
+
return "env var"
|
|
246
|
+
if section in file_config and key in file_config.get(section, {}):
|
|
247
|
+
return "config file"
|
|
248
|
+
return "default"
|
config/schema.py
CHANGED
|
@@ -5,6 +5,44 @@ from typing import Any
|
|
|
5
5
|
VALID_AGENTS = {"codex", "copilot", "claude", "opencode"}
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
def validate_agent(agent: str | None) -> tuple[bool, str | None]:
|
|
9
|
+
"""Validate agent configuration value.
|
|
10
|
+
|
|
11
|
+
Args:
|
|
12
|
+
agent: Agent name to validate
|
|
13
|
+
|
|
14
|
+
Returns:
|
|
15
|
+
Tuple of (is_valid, error_message)
|
|
16
|
+
"""
|
|
17
|
+
if agent is None:
|
|
18
|
+
return (True, None)
|
|
19
|
+
|
|
20
|
+
if agent not in VALID_AGENTS:
|
|
21
|
+
sorted_agents = ", ".join(sorted(VALID_AGENTS))
|
|
22
|
+
return (False, f"Invalid agent '{agent}'. Valid agents: {sorted_agents}")
|
|
23
|
+
|
|
24
|
+
return (True, None)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def validate_profile_reference(profile_name: str, config: dict[str, Any]) -> tuple[bool, str | None]:
|
|
28
|
+
"""Validate that a profile reference exists in config.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
profile_name: Profile name to validate
|
|
32
|
+
config: Full config dictionary
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Tuple of (is_valid, error_message)
|
|
36
|
+
"""
|
|
37
|
+
profiles = config.get("profiles", {})
|
|
38
|
+
|
|
39
|
+
if profile_name not in profiles:
|
|
40
|
+
available = ", ".join(sorted(profiles.keys()))
|
|
41
|
+
return (False, f"Profile '{profile_name}' not found. Available profiles: {available}")
|
|
42
|
+
|
|
43
|
+
return (True, None)
|
|
44
|
+
|
|
45
|
+
|
|
8
46
|
def validate_config(config: dict[str, Any]) -> None:
|
|
9
47
|
"""Validate configuration dictionary.
|
|
10
48
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
__init__.py,sha256=cYuwXNht5J2GDPEbHz57rmXRyWzaUgAaCXz8okR0rKE,84
|
|
2
2
|
__main__.py,sha256=Due_Us-4KNlLZhf8MkmoP1hWS5qMWmpZvz2ZaCqPHT4,120
|
|
3
3
|
adapter.py,sha256=WEpYkKDTb7We0zU9i6Z-r5ydtUdghNhxTZ5Eq58h4fU,10027
|
|
4
|
-
cli.py,sha256=
|
|
4
|
+
cli.py,sha256=xghN20mgfEeh3vR_Vaiv0i2f1MBN6gk8J0uU3kXRK8g,2672
|
|
5
5
|
discovery.py,sha256=WWF8SN2LH88mOUBJLavM7rvXcxi6uDQGpqRK20GysxA,3298
|
|
6
6
|
manifest.py,sha256=feNCjkFWfhoVubevKjLtKoIEuzT1YGQn6wWgs9XM8_o,12229
|
|
7
7
|
registry.py,sha256=zGutwVP39xaYqc3KDEXMWCV1tORYpqc5JISO8OaWP1Q,4470
|
|
@@ -27,9 +27,9 @@ commands/adapter.py,sha256=_68v3t-dRU0mszzL4udKs1bKennyg7RfBTaK2fDGTsE,3215
|
|
|
27
27
|
commands/add.py,sha256=NJLQ-8-3zy7o6S9VLfL_wauP-Vz0oNGwN3nvtiwxNYM,15255
|
|
28
28
|
commands/clean.py,sha256=RQBAfe6iCLsjMqUyVR55JdYX9MBqgrUuIrA8rFKs1J0,1102
|
|
29
29
|
commands/clone.py,sha256=4APH34-yHjiXQIQwBnKOSEQ_sxV24_GKypcOJMfncvs,5912
|
|
30
|
-
commands/config.py,sha256=
|
|
30
|
+
commands/config.py,sha256=4kzDEjVpwrmMPK_DPYePdQe2lGh_b8waYORZDHCDYZw,6976
|
|
31
31
|
commands/diff.py,sha256=37JMjvfAEfvK7-4X5iFbD-IGkS8ae4YSY7ZDIZF5B9E,5766
|
|
32
|
-
commands/exec.py,sha256=
|
|
32
|
+
commands/exec.py,sha256=zFmxxclpHQF39sqDpR5436XQiEYo334BGcQ5a8gbR9I,8711
|
|
33
33
|
commands/find.py,sha256=zgqwUnaG5aLX6gJIU2ZeQzxsFh2s7oDNNtmV-e-62Jg,1663
|
|
34
34
|
commands/help.py,sha256=5yhIpgGs1xPs2f39lg-ELE7D0tV_uUTjxQsgkWusIwo,1449
|
|
35
35
|
commands/info.py,sha256=zywaUQsrvcPXcX8W49P7Jqnr90pX8nBPqnH1XcIs0Uk,4396
|
|
@@ -41,9 +41,10 @@ commands/sync.py,sha256=ZQoB5hBqrzvM6LUQVlKqHQVJib4dB5qe5M-pVG2vtGM,4946
|
|
|
41
41
|
commands/update.py,sha256=bOWjdTNyeYg-hvXv5GfUzEtsTA7gU9JLM592GI9Oq68,11939
|
|
42
42
|
commands/use.py,sha256=ggB28g2BDg3Lv3nF40wnDAJ7p0mo6C1pc1KgahvQYXM,1452
|
|
43
43
|
commands/validate.py,sha256=Y8TLHxW4Z98onmzu-h-kDIET-48lVaIdQXOvuyBemLw,2361
|
|
44
|
-
config/__init__.py,sha256=
|
|
44
|
+
config/__init__.py,sha256=glSjT1_y4aOfhZ8odrUWCGF1hBbY_huTjVp6suepHDY,3647
|
|
45
45
|
config/defaults.py,sha256=JfCltQYoE7EqBYlxsNrSITLmwifTvRrJe5lqL0Ys7Cs,986
|
|
46
|
-
config/
|
|
46
|
+
config/env.py,sha256=WgnQXjhfvV7m1oxZCK9WdIX_rqLy_-BOSuPjbpjdI1c,7163
|
|
47
|
+
config/schema.py,sha256=VlvmiYWjU2hExBJfME90Oyqp-H4OHcUs_hvvp54K9jA,4498
|
|
47
48
|
policy/__init__.py,sha256=0sPJaruOyc9ioNyIcrTW72RgpaE64FgibS0h5mQELb8,1353
|
|
48
49
|
policy/defaults.py,sha256=9GMQM2l2OKTmhXlKwyTfcICR5vD9qEvyvqaR5KrN7ZI,620
|
|
49
50
|
policy/enforcement.py,sha256=djsosjjfdyr0SjnHF2kz4u3glvMNgd1CJztN6yZE-fM,2749
|
|
@@ -51,9 +52,9 @@ policy/profile.py,sha256=WDKaUagsWnBPGz5a_OOcxTsdZ66WjaIaR0R7ITVqy8g,6790
|
|
|
51
52
|
skillcopy/__init__.py,sha256=YUglUkDzKfnCt4ar_DU33ksI9fGyn2UYbV7qn2c_BcU,2322
|
|
52
53
|
skillcopy/local.py,sha256=QH6484dCenjg8pfNOyTRbQQBklEWhkkTnfQok5ssf_4,1049
|
|
53
54
|
skillcopy/remote.py,sha256=83jRA2VfjtSDGO-YM1x3WGJjKvWzK1RmSTL7SdUOz8s,3155
|
|
54
|
-
oasr-0.5.
|
|
55
|
-
oasr-0.5.
|
|
56
|
-
oasr-0.5.
|
|
57
|
-
oasr-0.5.
|
|
58
|
-
oasr-0.5.
|
|
59
|
-
oasr-0.5.
|
|
55
|
+
oasr-0.5.1.dist-info/METADATA,sha256=fAfdWjbFnZGfRpQ7gJx1IgpKLbaXgpFZz6ddjdzumxc,17924
|
|
56
|
+
oasr-0.5.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
57
|
+
oasr-0.5.1.dist-info/entry_points.txt,sha256=VnMuOi6XYMbzAD2bP0X5uV1sQXjOqoDWJ33Lsxwq8u8,52
|
|
58
|
+
oasr-0.5.1.dist-info/licenses/LICENSE,sha256=nQ1j9Ldb8FlJ-z7y2WuXPIlyfnYC7YPasjGdOBgcfP4,10561
|
|
59
|
+
oasr-0.5.1.dist-info/licenses/NOTICE,sha256=EsfkCN0ZRDS0oj3ADvMKeKrAXaPlC8YfpSjvjGVv9jE,207
|
|
60
|
+
oasr-0.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|