Skip to content

Commit 09e4f74

Browse files
committed
adding function to print colored terminal output
1 parent 7e56471 commit 09e4f74

File tree

4 files changed

+95
-36
lines changed

4 files changed

+95
-36
lines changed

packages/agent-os/src/agent_os/policies/cli.py

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,37 @@
2424
import sys
2525
from pathlib import Path
2626
from typing import Any
27+
from rich import print
2728

2829
# ---------------------------------------------------------------------------
29-
# Lazy imports — keep startup fast and give clear messages when deps missing.
30+
# function for colored output
3031
# ---------------------------------------------------------------------------
3132

33+
def error(msg):
34+
print(f"[red]{msg}[/red]", file=sys.stderr)
35+
36+
def success(msg):
37+
print(f"[green]{msg}[/green]", file=sys.stderr)
38+
39+
def warn(msg):
40+
print(f"[yellow]{msg}[/yellow]", file=sys.stderr)
41+
42+
def policy_violation(msg):
43+
print(f"[bold red]{msg}[/bold red]", file=sys.stderr)
44+
45+
def passed_check(msg):
46+
print(f"[green]✔ {msg}[/green]", file=sys.stderr)
47+
48+
# ---------------------------------------------------------------------------
49+
# Lazy imports
50+
# ---------------------------------------------------------------------------
3251

3352
def _import_yaml() -> Any:
3453
try:
3554
import yaml
36-
3755
return yaml
3856
except ImportError:
39-
print("ERROR: pyyaml is required — pip install pyyaml", file=sys.stderr)
57+
error("ERROR: pyyaml is required — pip install pyyaml")
4058
sys.exit(2)
4159

4260

@@ -49,35 +67,33 @@ def _load_file(path: Path) -> dict[str, Any]:
4967
elif path.suffix == ".json":
5068
data = json.loads(text)
5169
else:
52-
# Try YAML first, fall back to JSON
5370
yaml = _import_yaml()
5471
try:
5572
data = yaml.safe_load(text)
5673
except Exception:
5774
data = json.loads(text)
75+
5876
if not isinstance(data, dict):
5977
raise ValueError(f"Expected a mapping at top level, got {type(data).__name__}")
6078
return data
6179

62-
6380
# ============================================================================
6481
# validate
6582
# ============================================================================
6683

67-
6884
def cmd_validate(args: argparse.Namespace) -> int:
6985
"""Validate a policy YAML/JSON file against the PolicyDocument schema."""
7086
from .schema import PolicyDocument # noqa: E402
7187

7288
path = Path(args.path)
7389
if not path.exists():
74-
print(f"ERROR: file not found: {path}", file=sys.stderr)
90+
error(f"ERROR: file not found: {path}")
7591
return 2
7692

7793
try:
7894
data = _load_file(path)
7995
except Exception as exc:
80-
print(f"ERROR: failed to parse {path}: {exc}", file=sys.stderr)
96+
error(f"ERROR: failed to parse {path}: {exc}")
8197
return 2
8298

8399
# --- Optional JSON-Schema validation (best-effort) --------------------
@@ -89,32 +105,30 @@ def cmd_validate(args: argparse.Namespace) -> int:
89105
schema = json.loads(schema_path.read_text(encoding="utf-8"))
90106
jsonschema.validate(instance=data, schema=schema)
91107
except ImportError:
92-
pass # jsonschema not installed — skip, rely on Pydantic
108+
pass
93109
except jsonschema.ValidationError as ve:
94-
print(f"FAIL: {path}")
95-
print(f" JSON-Schema error: {ve.message}")
110+
policy_violation(f"FAIL: {path}")
111+
error(f" JSON-Schema error: {ve.message}")
96112
if ve.absolute_path:
97-
print(f" Location: {' -> '.join(str(p) for p in ve.absolute_path)}")
113+
warn(f" Location: {' -> '.join(str(p) for p in ve.absolute_path)}")
98114
return 1
99115

100116
# --- Pydantic validation (authoritative) ------------------------------
101117
try:
102118
PolicyDocument.model_validate(data)
103119
except Exception as exc:
104-
print(f"FAIL: {path}")
120+
policy_violation(f"FAIL: {path}")
105121
for line in str(exc).splitlines():
106-
print(f" {line}")
122+
error(f" {line}")
107123
return 1
108124

109-
print(f"OK: {path}")
125+
success(f"OK: {path}")
110126
return 0
111127

112-
113128
# ============================================================================
114129
# test
115130
# ============================================================================
116131

117-
118132
def cmd_test(args: argparse.Namespace) -> int:
119133
"""Test a policy against a set of scenarios."""
120134
from .evaluator import PolicyEvaluator # noqa: E402
@@ -125,7 +139,7 @@ def cmd_test(args: argparse.Namespace) -> int:
125139

126140
for p in (policy_path, scenarios_path):
127141
if not p.exists():
128-
print(f"ERROR: file not found: {p}", file=sys.stderr)
142+
error(f"ERROR: file not found: {p}")
129143
return 2
130144

131145
# Load the policy
@@ -135,19 +149,19 @@ def cmd_test(args: argparse.Namespace) -> int:
135149
try:
136150
doc = PolicyDocument.from_json(policy_path)
137151
except Exception:
138-
print(f"ERROR: failed to load policy {policy_path}: {exc}", file=sys.stderr)
152+
error(f"ERROR: failed to load policy {policy_path}: {exc}")
139153
return 2
140154

141155
# Load scenarios
142156
try:
143157
scenarios_data = _load_file(scenarios_path)
144158
except Exception as exc:
145-
print(f"ERROR: failed to parse scenarios {scenarios_path}: {exc}", file=sys.stderr)
159+
error(f"ERROR: failed to parse scenarios {scenarios_path}: {exc}")
146160
return 2
147161

148162
scenarios = scenarios_data.get("scenarios", [])
149163
if not scenarios:
150-
print("ERROR: no scenarios found in test file", file=sys.stderr)
164+
error("ERROR: no scenarios found in test file")
151165
return 2
152166

153167
evaluator = PolicyEvaluator(policies=[doc])
@@ -175,22 +189,20 @@ def cmd_test(args: argparse.Namespace) -> int:
175189

176190
if errors:
177191
failed += 1
178-
print(f" FAIL: {name}")
192+
policy_violation(f"FAIL: {name}")
179193
for err in errors:
180-
print(f" - {err}")
194+
error(f" - {err}")
181195
else:
182196
passed += 1
183-
print(f" PASS: {name}")
197+
passed_check(name)
184198

185-
print(f"\n{passed}/{total} scenarios passed.")
199+
success(f"\n{passed}/{total} scenarios passed.")
186200
return 1 if failed > 0 else 0
187201

188-
189202
# ============================================================================
190203
# diff
191204
# ============================================================================
192205

193-
194206
def cmd_diff(args: argparse.Namespace) -> int:
195207
"""Show differences between two policy files."""
196208
from .schema import PolicyDocument # noqa: E402
@@ -200,14 +212,14 @@ def cmd_diff(args: argparse.Namespace) -> int:
200212

201213
for p in (path1, path2):
202214
if not p.exists():
203-
print(f"ERROR: file not found: {p}", file=sys.stderr)
215+
error(f"ERROR: file not found: {p}")
204216
return 2
205217

206218
try:
207219
doc1 = PolicyDocument.model_validate(_load_file(path1))
208220
doc2 = PolicyDocument.model_validate(_load_file(path2))
209221
except Exception as exc:
210-
print(f"ERROR: failed to load policies: {exc}", file=sys.stderr)
222+
error(f"ERROR: failed to load policies: {exc}")
211223
return 2
212224

213225
differences: list[str] = []
@@ -271,20 +283,18 @@ def cmd_diff(args: argparse.Namespace) -> int:
271283
differences.append(f" rule '{name}' message changed")
272284

273285
if differences:
274-
print(f"Differences between {path1} and {path2}:")
286+
success(f"Differences between {path1} and {path2}:")
275287
for diff in differences:
276288
print(diff)
277289
return 1
278290
else:
279-
print(f"No differences between {path1} and {path2}.")
291+
success(f"No differences between {path1} and {path2}.")
280292
return 0
281293

282-
283294
# ============================================================================
284295
# Main entry point
285296
# ============================================================================
286297

287-
288298
def main(argv: list[str] | None = None) -> int:
289299
"""Parse arguments and dispatch to the appropriate subcommand."""
290300
parser = argparse.ArgumentParser(
@@ -293,22 +303,22 @@ def main(argv: list[str] | None = None) -> int:
293303
)
294304
subparsers = parser.add_subparsers(dest="command", help="Available commands")
295305

296-
# -- validate ----------------------------------------------------------
306+
# -- validate
297307
p_validate = subparsers.add_parser(
298308
"validate",
299309
help="Validate a policy YAML/JSON file against the schema.",
300310
)
301311
p_validate.add_argument("path", help="Path to the policy file to validate.")
302312

303-
# -- test --------------------------------------------------------------
313+
# -- test
304314
p_test = subparsers.add_parser(
305315
"test",
306316
help="Test a policy against a set of scenarios.",
307317
)
308318
p_test.add_argument("policy_path", help="Path to the policy file.")
309319
p_test.add_argument("test_scenarios_path", help="Path to the test scenarios YAML.")
310320

311-
# -- diff --------------------------------------------------------------
321+
# -- diff
312322
p_diff = subparsers.add_parser(
313323
"diff",
314324
help="Show differences between two policy files.",
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
name: Test Policy Messages
2+
version: "1.0"
3+
rules:
4+
- name: allow_rule
5+
action: allow
6+
condition:
7+
type: always_true
8+
- name: audit_rule
9+
action: audit
10+
condition:
11+
type: always_true
12+
- name: block_rule
13+
action: block
14+
condition:
15+
type: always_true
16+
- name: deny_rule
17+
action: deny
18+
condition:
19+
type: always_true
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
scenarios:
2+
- name: Scenario Allow
3+
context: {"type": "allow"}
4+
expected_allowed: true
5+
expected_action: allow
6+
7+
- name: Scenario Audit
8+
context: {"type": "audit"}
9+
expected_allowed: true
10+
expected_action: audit
11+
12+
- name: Scenario Block
13+
context: {"type": "block"}
14+
expected_allowed: false
15+
expected_action: block
16+
17+
- name: Scenario Deny
18+
context: {"type": "deny"}
19+
expected_allowed: false
20+
expected_action: deny
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# test_policies/test_success.yaml
2+
name: Test Success
3+
version: "1.0"
4+
description: Minimal valid policy
5+
defaults:
6+
action: allow
7+
max_tokens: 10
8+
max_tool_calls: 2
9+
confidence_threshold: 0.5
10+
rules: []

0 commit comments

Comments
 (0)