netra-zen 1.0.2__py3-none-any.whl → 1.0.3__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.
- {netra_zen-1.0.2.dist-info → netra_zen-1.0.3.dist-info}/METADATA +1 -1
- {netra_zen-1.0.2.dist-info → netra_zen-1.0.3.dist-info}/RECORD +7 -7
- zen_orchestrator.py +120 -93
- {netra_zen-1.0.2.dist-info → netra_zen-1.0.3.dist-info}/WHEEL +0 -0
- {netra_zen-1.0.2.dist-info → netra_zen-1.0.3.dist-info}/entry_points.txt +0 -0
- {netra_zen-1.0.2.dist-info → netra_zen-1.0.3.dist-info}/licenses/LICENSE.md +0 -0
- {netra_zen-1.0.2.dist-info → netra_zen-1.0.3.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,15 @@
|
|
1
|
-
zen_orchestrator.py,sha256=
|
1
|
+
zen_orchestrator.py,sha256=eUiA8T08QYMfx5kM84LUloDUxscCUzv_k3O0WN3nFpo,148941
|
2
2
|
agent_interface/__init__.py,sha256=OsbOKzElHsxhVgak87oOx_u46QNgKmz-Reis-plAMwk,525
|
3
3
|
agent_interface/base_agent.py,sha256=GNskG9VaZgno7X24lQTpFdxUoQE0yJHLh0UPFJvOPn4,11098
|
4
|
-
netra_zen-1.0.
|
4
|
+
netra_zen-1.0.3.dist-info/licenses/LICENSE.md,sha256=t6LtOzAE2hgIIv5WbaN0wOcU3QCnGtAkMGNclHrKTOs,79
|
5
5
|
token_budget/__init__.py,sha256=_2tmi72DGNtbYcZ-rGIxVKMytdkHFjzJaWz8bDhYACc,33
|
6
6
|
token_budget/budget_manager.py,sha256=VRWxKcGDtgJfIRh-ztYQ4-wuhBvddVJJnyoGfxCBlv0,9567
|
7
7
|
token_budget/models.py,sha256=14xFTk2-R1Ql0F9WLDof7vADrKC_5Fj7EE7UmZdoG00,2384
|
8
8
|
token_budget/visualization.py,sha256=SaNnZ15edHXtjDCA5Yfu7w3AztCZRYsYCPGBqzapupY,719
|
9
9
|
token_transparency/__init__.py,sha256=go86Rg4_VYAPLw3myVpLe1s1PbMu1llDTw1wfomP1lA,453
|
10
10
|
token_transparency/claude_pricing_engine.py,sha256=9zWQJS3HJEs_lljil-xT1cUvG-Jf3ykNAninJFyfNSM,12630
|
11
|
-
netra_zen-1.0.
|
12
|
-
netra_zen-1.0.
|
13
|
-
netra_zen-1.0.
|
14
|
-
netra_zen-1.0.
|
15
|
-
netra_zen-1.0.
|
11
|
+
netra_zen-1.0.3.dist-info/METADATA,sha256=WQb-6CiZG9JqJrkW8fuGc5UmejBT4-ZimVnNVH6ehLw,29442
|
12
|
+
netra_zen-1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
13
|
+
netra_zen-1.0.3.dist-info/entry_points.txt,sha256=oDehCnPGZezG0m9ZWspxjHLHyQ3eERX87eojR4ljaRo,45
|
14
|
+
netra_zen-1.0.3.dist-info/top_level.txt,sha256=dHz-hgh_dfiiOUrPf8wW80fA31rfEYDFmA6fpu67Yjk,65
|
15
|
+
netra_zen-1.0.3.dist-info/RECORD,,
|
zen_orchestrator.py
CHANGED
@@ -2,41 +2,43 @@
|
|
2
2
|
"""
|
3
3
|
Usage Examples:
|
4
4
|
|
5
|
+
zen -h # Help
|
6
|
+
|
5
7
|
Direct Command Execution:
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
zen "/single-command-in-claude-commands" # Execute single command directly
|
9
|
+
zen "/analyze-code" --workspace ~/my-project
|
10
|
+
zen "/debug-issue" --instance-name "debug-session"
|
11
|
+
zen "/optimize-performance" --session-id "perf-1"
|
12
|
+
zen "/generate-docs" --clear-history --compact-history
|
11
13
|
|
12
14
|
Configuration File Mode:
|
13
|
-
|
14
|
-
|
15
|
+
zen --config config.json
|
16
|
+
zen --config config.json --workspace ~/my-project
|
15
17
|
|
16
18
|
Default Instances Mode:
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
zen --dry-run # Auto-detects workspace from project root
|
20
|
+
zen --workspace ~/my-project --dry-run # Override workspace
|
21
|
+
zen --startup-delay 2.0 # 2 second delay between launches
|
22
|
+
zen --startup-delay 0.5 # 0.5 second delay between launches
|
23
|
+
zen --max-line-length 1000 # Longer output lines
|
24
|
+
zen --status-report-interval 60 # Status reports every 60s
|
25
|
+
zen --quiet # Minimal output, errors only
|
24
26
|
|
25
27
|
Command Discovery:
|
26
|
-
|
27
|
-
|
28
|
+
zen --list-commands # Show all available commands
|
29
|
+
zen --inspect-command "/analyze-code" # Inspect specific command
|
28
30
|
|
29
31
|
Scheduling:
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
zen "/analyze-code" --start-at "2h" # Start 2 hours from now
|
33
|
+
zen "/debug-issue" --start-at "30m" # Start in 30 minutes
|
34
|
+
zen "/optimize" --start-at "1am" # Start at 1 AM (today or tomorrow)
|
35
|
+
zen "/review-code" --start-at "14:30" # Start at 2:30 PM (today or tomorrow)
|
36
|
+
zen "/generate-docs" --start-at "10:30pm" # Start at 10:30 PM (today or tomorrow)
|
35
37
|
|
36
38
|
Precedence Rules:
|
37
|
-
1. Direct command (highest) -
|
38
|
-
2. Config file (medium) -
|
39
|
-
3. Default instances (lowest) -
|
39
|
+
1. Direct command (highest) - zen "/command"
|
40
|
+
2. Config file (medium) - zen --config file.json # expected default usage pattern
|
41
|
+
3. Default instances (lowest) - zen
|
40
42
|
"""
|
41
43
|
|
42
44
|
import asyncio
|
@@ -58,6 +60,11 @@ import re
|
|
58
60
|
from uuid import uuid4, UUID
|
59
61
|
from enum import Enum
|
60
62
|
|
63
|
+
try:
|
64
|
+
from zen.telemetry import telemetry_manager
|
65
|
+
except Exception: # pragma: no cover - telemetry optional
|
66
|
+
telemetry_manager = None
|
67
|
+
|
61
68
|
# Add token budget imports with proper path handling
|
62
69
|
sys.path.insert(0, str(Path(__file__).parent))
|
63
70
|
try:
|
@@ -77,15 +84,6 @@ except ImportError as e:
|
|
77
84
|
ClaudePricingEngine = None
|
78
85
|
TokenUsageData = None
|
79
86
|
|
80
|
-
# Add CLI extensions imports
|
81
|
-
try:
|
82
|
-
from cli_extensions import handle_example_commands
|
83
|
-
except ImportError as e:
|
84
|
-
# Graceful fallback if CLI extensions are not available
|
85
|
-
handle_example_commands = None
|
86
|
-
|
87
|
-
# NetraOptimizer database functionality has been removed for security
|
88
|
-
# Local token metrics are preserved without database persistence
|
89
87
|
|
90
88
|
# Setup logging
|
91
89
|
logging.basicConfig(
|
@@ -197,6 +195,7 @@ class InstanceStatus:
|
|
197
195
|
tool_details: Dict[str, int] = None # Tool name -> usage count
|
198
196
|
tool_tokens: Dict[str, int] = None # Tool name -> token usage
|
199
197
|
tool_id_mapping: Dict[str, str] = field(default_factory=dict) # tool_use_id -> tool name mapping
|
198
|
+
telemetry_recorded: bool = False
|
200
199
|
|
201
200
|
def __post_init__(self):
|
202
201
|
"""Initialize fields that need special handling"""
|
@@ -214,7 +213,7 @@ class ClaudeInstanceOrchestrator:
|
|
214
213
|
|
215
214
|
def __init__(self, workspace_dir: Path, max_console_lines: int = 5, startup_delay: float = 1.0,
|
216
215
|
max_line_length: int = 500, status_report_interval: int = 30,
|
217
|
-
|
216
|
+
quiet: bool = False,
|
218
217
|
overall_token_budget: Optional[int] = None,
|
219
218
|
overall_cost_budget: Optional[float] = None,
|
220
219
|
budget_type: str = "tokens",
|
@@ -233,11 +232,10 @@ class ClaudeInstanceOrchestrator:
|
|
233
232
|
self.status_report_interval = status_report_interval # Seconds between status reports
|
234
233
|
self.last_status_report = time.time()
|
235
234
|
self.status_report_task = None # For the rolling status report task
|
236
|
-
self.use_cloud_sql = use_cloud_sql
|
237
235
|
self.quiet = quiet
|
238
236
|
self.log_level = log_level
|
239
237
|
self.batch_id = str(uuid4()) # Generate batch ID for this orchestration run
|
240
|
-
|
238
|
+
|
241
239
|
self.optimizer = None
|
242
240
|
|
243
241
|
# Initialize budget manager if any budget settings are provided
|
@@ -278,12 +276,6 @@ class ClaudeInstanceOrchestrator:
|
|
278
276
|
else:
|
279
277
|
logger.debug("Token budget tracking disabled (no budget specified)")
|
280
278
|
|
281
|
-
# Configure CloudSQL if requested
|
282
|
-
# CloudSQL functionality available with Netra Apex
|
283
|
-
# Token metrics are now handled locally without database persistence
|
284
|
-
if use_cloud_sql:
|
285
|
-
logger.warning("CloudSQL functionality has been disabled. Token metrics will be displayed locally only.")
|
286
|
-
logger.info("For data persistence, consider upgrading to Netra Apex.")
|
287
279
|
|
288
280
|
def log_at_level(self, level: LogLevel, message: str, log_func=None):
|
289
281
|
"""Log message only if current log level permits."""
|
@@ -338,36 +330,36 @@ class ClaudeInstanceOrchestrator:
|
|
338
330
|
command_string = "; ".join(full_command)
|
339
331
|
|
340
332
|
# Find the claude executable with Mac-specific paths
|
341
|
-
|
333
|
+
# IMPORTANT: Use direct paths to avoid shell functions that may have database dependencies
|
334
|
+
possible_paths = [
|
335
|
+
"/opt/homebrew/bin/claude", # Mac Homebrew ARM - prefer direct path
|
336
|
+
"/usr/local/bin/claude", # Mac Homebrew Intel
|
337
|
+
"~/.local/bin/claude", # User local install
|
338
|
+
"/usr/bin/claude", # System install
|
339
|
+
"claude.cmd", # Windows
|
340
|
+
"claude.exe", # Windows
|
341
|
+
]
|
342
|
+
|
343
|
+
claude_cmd = None
|
344
|
+
for path in possible_paths:
|
345
|
+
# Expand user path if needed
|
346
|
+
expanded_path = Path(path).expanduser()
|
347
|
+
if expanded_path.exists() and expanded_path.is_file():
|
348
|
+
claude_cmd = str(expanded_path)
|
349
|
+
logger.info(f"Found Claude executable at: {claude_cmd}")
|
350
|
+
break
|
351
|
+
|
352
|
+
# Only use shutil.which as fallback if no direct path found
|
342
353
|
if not claude_cmd:
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
"claude.exe", # Windows
|
347
|
-
"/opt/homebrew/bin/claude", # Mac Homebrew ARM
|
348
|
-
"/usr/local/bin/claude", # Mac Homebrew Intel
|
349
|
-
"~/.local/bin/claude", # User local install
|
350
|
-
"/usr/bin/claude", # System install
|
351
|
-
"claude" # Final fallback
|
352
|
-
]
|
353
|
-
|
354
|
-
for path in possible_paths:
|
355
|
-
# Expand user path if needed
|
356
|
-
expanded_path = Path(path).expanduser()
|
357
|
-
if expanded_path.exists():
|
358
|
-
claude_cmd = str(expanded_path)
|
359
|
-
logger.info(f"Found Claude executable at: {claude_cmd}")
|
360
|
-
break
|
361
|
-
elif shutil.which(path):
|
362
|
-
claude_cmd = path
|
363
|
-
logger.info(f"Found Claude executable via which: {claude_cmd}")
|
364
|
-
break
|
354
|
+
claude_cmd = shutil.which("claude")
|
355
|
+
if claude_cmd:
|
356
|
+
logger.info(f"Found Claude executable via which: {claude_cmd}")
|
365
357
|
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
358
|
+
if not claude_cmd:
|
359
|
+
logger.warning("Claude command not found in PATH or common locations")
|
360
|
+
logger.warning("Please ensure Claude Code is installed and in your PATH")
|
361
|
+
logger.warning("Install with: npm install -g @anthropic/claude-code")
|
362
|
+
claude_cmd = "/opt/homebrew/bin/claude" # Default fallback to most likely location
|
371
363
|
|
372
364
|
# New approach: slash commands can be included directly in prompt
|
373
365
|
cmd = [
|
@@ -494,6 +486,11 @@ class ClaudeInstanceOrchestrator:
|
|
494
486
|
logger.error(f"🚫 BLOCK MODE: {message}")
|
495
487
|
status.status = "failed"
|
496
488
|
status.error = f"Blocked by budget limit - {reason}"
|
489
|
+
timestamp = time.time()
|
490
|
+
if status.start_time is None:
|
491
|
+
status.start_time = timestamp
|
492
|
+
status.end_time = timestamp
|
493
|
+
self._emit_instance_telemetry(name, config, status)
|
497
494
|
return False
|
498
495
|
else: # warn mode
|
499
496
|
logger.warning(f"⚠️ WARN MODE: {message}")
|
@@ -571,18 +568,22 @@ class ClaudeInstanceOrchestrator:
|
|
571
568
|
if returncode == 0:
|
572
569
|
status.status = "completed"
|
573
570
|
logger.info(f"Instance {name} completed successfully")
|
571
|
+
self._emit_instance_telemetry(name, config, status)
|
574
572
|
return True
|
575
573
|
else:
|
576
574
|
status.status = "failed"
|
577
575
|
logger.error(f"Instance {name} failed with return code {returncode}")
|
578
576
|
if status.error:
|
579
577
|
logger.error(f"Error output: {status.error}")
|
578
|
+
self._emit_instance_telemetry(name, config, status)
|
580
579
|
return False
|
581
580
|
|
582
581
|
except Exception as e:
|
583
582
|
status.status = "failed"
|
584
583
|
status.error = str(e)
|
585
584
|
logger.error(f"Exception running instance {name}: {e}")
|
585
|
+
status.end_time = status.end_time or time.time()
|
586
|
+
self._emit_instance_telemetry(name, config, status)
|
586
587
|
return False
|
587
588
|
|
588
589
|
async def _save_metrics_to_database(self, name: str, config: InstanceConfig, status: InstanceStatus):
|
@@ -635,6 +636,38 @@ class ClaudeInstanceOrchestrator:
|
|
635
636
|
|
636
637
|
return input_cost + output_cost + cache_read_cost + cache_creation_cost + tool_cost
|
637
638
|
|
639
|
+
def _emit_instance_telemetry(self, name: str, config: InstanceConfig, status: InstanceStatus) -> None:
|
640
|
+
"""Send telemetry span with token usage and cost metadata."""
|
641
|
+
|
642
|
+
if telemetry_manager is None or not hasattr(telemetry_manager, "is_enabled"):
|
643
|
+
return
|
644
|
+
|
645
|
+
if getattr(status, "telemetry_recorded", False):
|
646
|
+
return
|
647
|
+
|
648
|
+
if not telemetry_manager.is_enabled():
|
649
|
+
return
|
650
|
+
|
651
|
+
cost_usd: Optional[float]
|
652
|
+
try:
|
653
|
+
cost_usd = self._calculate_cost(status)
|
654
|
+
except Exception as exc: # pragma: no cover - defensive guard
|
655
|
+
logger.debug(f"Cost calculation failed for telemetry span ({name}): {exc}")
|
656
|
+
cost_usd = None
|
657
|
+
|
658
|
+
try:
|
659
|
+
telemetry_manager.record_instance_span(
|
660
|
+
batch_id=self.batch_id,
|
661
|
+
instance_name=name,
|
662
|
+
status=status,
|
663
|
+
config=config,
|
664
|
+
cost_usd=cost_usd,
|
665
|
+
workspace=str(self.workspace_dir),
|
666
|
+
)
|
667
|
+
status.telemetry_recorded = True
|
668
|
+
except Exception as exc: # pragma: no cover - Network/export errors
|
669
|
+
logger.debug(f"Telemetry emission failed for {name}: {exc}")
|
670
|
+
|
638
671
|
async def _stream_output(self, name: str, process):
|
639
672
|
"""Stream output in real-time for stream-json format (DEPRECATED - use _stream_output_parallel)"""
|
640
673
|
status = self.statuses[name]
|
@@ -810,13 +843,19 @@ class ClaudeInstanceOrchestrator:
|
|
810
843
|
for name, result in zip(self.instances.keys(), results):
|
811
844
|
if isinstance(result, asyncio.TimeoutError):
|
812
845
|
logger.error(f"Instance {name} timed out after {timeout}s")
|
813
|
-
self.statuses[name]
|
814
|
-
|
846
|
+
status = self.statuses[name]
|
847
|
+
status.status = "failed"
|
848
|
+
status.error = f"Timeout after {timeout}s"
|
849
|
+
status.end_time = time.time()
|
850
|
+
self._emit_instance_telemetry(name, self.instances[name], status)
|
815
851
|
final_results[name] = False
|
816
852
|
elif isinstance(result, Exception):
|
817
853
|
logger.error(f"Instance {name} failed with exception: {result}")
|
818
|
-
self.statuses[name]
|
819
|
-
|
854
|
+
status = self.statuses[name]
|
855
|
+
status.status = "failed"
|
856
|
+
status.error = str(result)
|
857
|
+
status.end_time = time.time()
|
858
|
+
self._emit_instance_telemetry(name, self.instances[name], status)
|
820
859
|
final_results[name] = False
|
821
860
|
else:
|
822
861
|
final_results[name] = result
|
@@ -2370,8 +2409,6 @@ async def main():
|
|
2370
2409
|
help="Seconds between rolling status reports (default: 5)")
|
2371
2410
|
parser.add_argument("--start-at", type=str, default=None,
|
2372
2411
|
help="Schedule orchestration to start at specific time. Examples: '2h' (2 hours from now), '30m' (30 minutes), '14:30' (2:30 PM today), '1am' (1 AM today/tomorrow)")
|
2373
|
-
parser.add_argument("--use-cloud-sql", action="store_true",
|
2374
|
-
help="Save metrics to CloudSQL database (NetraOptimizer integration)")
|
2375
2412
|
|
2376
2413
|
# Direct command options
|
2377
2414
|
parser.add_argument("--instance-name", type=str, help="Instance name for direct command execution")
|
@@ -2454,10 +2491,6 @@ async def main():
|
|
2454
2491
|
|
2455
2492
|
logger.info(f"Using workspace: {workspace}")
|
2456
2493
|
|
2457
|
-
# Handle CLI extension commands
|
2458
|
-
if handle_example_commands and handle_example_commands(args):
|
2459
|
-
return
|
2460
|
-
|
2461
2494
|
|
2462
2495
|
# Load instance configurations with direct command precedence
|
2463
2496
|
direct_instance = create_direct_instance(args, workspace)
|
@@ -2547,7 +2580,6 @@ async def main():
|
|
2547
2580
|
startup_delay=args.startup_delay,
|
2548
2581
|
max_line_length=args.max_line_length,
|
2549
2582
|
status_report_interval=args.status_report_interval,
|
2550
|
-
use_cloud_sql=args.use_cloud_sql,
|
2551
2583
|
quiet=args.quiet,
|
2552
2584
|
overall_token_budget=final_overall_budget,
|
2553
2585
|
overall_cost_budget=final_overall_cost_budget,
|
@@ -2775,9 +2807,6 @@ async def main():
|
|
2775
2807
|
|
2776
2808
|
# Run all instances
|
2777
2809
|
logger.info("Starting Claude Code instance orchestration")
|
2778
|
-
if args.use_cloud_sql:
|
2779
|
-
logger.info(f"Batch ID: {orchestrator.batch_id}")
|
2780
|
-
logger.info("Metrics will be saved to CloudSQL")
|
2781
2810
|
start_time = time.time()
|
2782
2811
|
|
2783
2812
|
results = await orchestrator.run_all_instances(args.timeout)
|
@@ -2909,19 +2938,17 @@ async def main():
|
|
2909
2938
|
|
2910
2939
|
# For detailed data access
|
2911
2940
|
print("\n" + "="*80)
|
2912
|
-
print("🚀
|
2941
|
+
print("🚀 Looking for more?")
|
2913
2942
|
print("="*80)
|
2914
|
-
print("
|
2915
|
-
print("and advanced reporting features, upgrade to Netra Apex.")
|
2943
|
+
print("Explore Zen with Apex for the most effective AI Ops value for production AI.")
|
2916
2944
|
print("")
|
2917
2945
|
print("🌐 Learn more: https://netrasystems.ai/")
|
2918
2946
|
print("="*80)
|
2919
2947
|
|
2920
|
-
|
2921
|
-
|
2922
|
-
|
2923
|
-
|
2924
|
-
print(f" Database persistence disabled for security")
|
2948
|
+
|
2949
|
+
# Flush telemetry before exit
|
2950
|
+
if telemetry_manager is not None and hasattr(telemetry_manager, "shutdown"):
|
2951
|
+
telemetry_manager.shutdown()
|
2925
2952
|
|
2926
2953
|
# Exit with appropriate code
|
2927
2954
|
sys.exit(0 if summary['failed'] == 0 else 1)
|
@@ -2931,4 +2958,4 @@ def run():
|
|
2931
2958
|
asyncio.run(main())
|
2932
2959
|
|
2933
2960
|
if __name__ == "__main__":
|
2934
|
-
run()
|
2961
|
+
run()
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|