zenus-cli 0.6.1__tar.gz

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.
@@ -0,0 +1,33 @@
1
+ Metadata-Version: 2.4
2
+ Name: zenus-cli
3
+ Version: 0.6.1
4
+ Summary: Zenus - Command Line Interface
5
+ License: MIT
6
+ Author: Zenus Team
7
+ Requires-Python: >=3.10,<4.0
8
+ Classifier: License :: OSI Approved :: MIT License
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Dist: anthropic (>=0.47.0,<0.48.0)
16
+ Requires-Dist: openai (>=1.0.0,<2.0.0)
17
+ Requires-Dist: pyyaml (>=6.0.3,<7.0.0)
18
+ Requires-Dist: rich (>=13.7.0,<14.0.0)
19
+ Requires-Dist: zenus-core (>=0.6.1,<0.7.0)
20
+ Description-Content-Type: text/markdown
21
+
22
+ # Zenus CLI
23
+
24
+ Command-line interface for Zenus.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install zenus-cli
30
+ ```
31
+
32
+ See main [Zenus documentation](../../README.md) for details.
33
+
@@ -0,0 +1,11 @@
1
+ # Zenus CLI
2
+
3
+ Command-line interface for Zenus.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install zenus-cli
9
+ ```
10
+
11
+ See main [Zenus documentation](../../README.md) for details.
@@ -0,0 +1,29 @@
1
+ [tool.poetry]
2
+ name = "zenus-cli"
3
+ version = "0.6.1"
4
+ description = "Zenus - Command Line Interface"
5
+ authors = ["Zenus Team"]
6
+ readme = "README.md"
7
+ license = "MIT"
8
+ packages = [{include = "zenus_cli", from = "src"}]
9
+
10
+ [tool.poetry.dependencies]
11
+ python = "^3.10"
12
+ zenus-core = "^0.6.1"
13
+ rich = "^13.7.0"
14
+ # LLM provider dependencies (needed for runtime)
15
+ openai = "^1.0.0"
16
+ anthropic = "^0.47.0"
17
+ pyyaml = "^6.0.3"
18
+
19
+ [tool.poetry.scripts]
20
+ zenus = "zenus_cli.main:main"
21
+
22
+ [tool.poetry.group.dev.dependencies]
23
+ pytest = "^8.0.0"
24
+ pytest-cov = "^4.1.0"
25
+ prompt-toolkit = "^3.0.52"
26
+
27
+ [build-system]
28
+ requires = ["poetry-core"]
29
+ build-backend = "poetry.core.masonry.api"
@@ -0,0 +1,9 @@
1
+ """
2
+ Zenus CLI - Command Line Interface
3
+
4
+ User-facing command-line interface for Zenus.
5
+ """
6
+
7
+ __version__ = "0.3.0"
8
+
9
+ __all__ = ["__version__"]
@@ -0,0 +1,5 @@
1
+ from zenus_cli.zenusd.main import main
2
+
3
+
4
+ if __name__ == "__main__":
5
+ main()
@@ -0,0 +1,180 @@
1
+ """
2
+ CLI Command Router
3
+
4
+ Responsible for:
5
+ - Parsing CLI arguments
6
+ - Routing to appropriate handlers (interactive/direct/help)
7
+ - Maintaining clear separation between parse, intent, execute
8
+ """
9
+
10
+ from typing import Optional, List
11
+ from dataclasses import dataclass, field
12
+
13
+
14
+ @dataclass
15
+ class CLICommand:
16
+ """Represents a parsed CLI command"""
17
+ mode: str # 'interactive', 'direct', 'help', 'version', 'status', 'model'
18
+ input_text: Optional[str] = None
19
+ flags: dict = field(default_factory=dict)
20
+
21
+
22
+ class CommandRouter:
23
+ """Routes CLI invocations to appropriate handlers"""
24
+
25
+ def __init__(self):
26
+ self.version = "0.1.0-alpha"
27
+
28
+ def parse(self, args: List[str]) -> CLICommand:
29
+ """
30
+ Parse command line arguments into a CLICommand
31
+
32
+ Modes:
33
+ - No args or 'shell' -> interactive REPL
34
+ - 'help' / '--help' / '-h' -> help message
35
+ - 'version' / '--version' / '-v' -> version info
36
+ - 'rollback' -> rollback commands
37
+ - 'history' -> history commands
38
+ - '--dry-run <text>' -> show plan but do not execute
39
+ - Direct text -> immediate intent execution
40
+ """
41
+
42
+ # Check for flags
43
+ dry_run = False
44
+ iterative = False
45
+ force_provider: Optional[str] = None
46
+ force_model: Optional[str] = None
47
+ filtered_args = []
48
+ i = 0
49
+ while i < len(args):
50
+ arg = args[i]
51
+ if arg == "--dry-run":
52
+ dry_run = True
53
+ elif arg == "--iterative":
54
+ iterative = True
55
+ elif arg in ("--provider", "-p") and i + 1 < len(args):
56
+ force_provider = args[i + 1]
57
+ i += 1
58
+ elif arg.startswith("--provider="):
59
+ force_provider = arg.split("=", 1)[1]
60
+ elif arg == "--model" and i + 1 < len(args):
61
+ force_model = args[i + 1]
62
+ i += 1
63
+ elif arg.startswith("--model="):
64
+ force_model = arg.split("=", 1)[1]
65
+ else:
66
+ filtered_args.append(arg)
67
+ i += 1
68
+
69
+ args = filtered_args
70
+
71
+ if not args or args[0] == "shell":
72
+ return CLICommand(mode="interactive")
73
+
74
+ if args[0] in ("help", "--help", "-h"):
75
+ return CLICommand(mode="help")
76
+
77
+ if args[0] in ("version", "--version", "-v"):
78
+ return CLICommand(mode="version")
79
+
80
+ if args[0] == "status":
81
+ return CLICommand(mode="status")
82
+
83
+ if args[0] == "model":
84
+ return CLICommand(
85
+ mode="model",
86
+ input_text=" ".join(args[1:]) if len(args) > 1 else "status",
87
+ )
88
+
89
+ if args[0] == "rollback":
90
+ return CLICommand(
91
+ mode="rollback",
92
+ input_text=" ".join(args[1:]) if len(args) > 1 else None,
93
+ flags={"dry_run": dry_run}
94
+ )
95
+
96
+ if args[0] == "history":
97
+ return CLICommand(
98
+ mode="history",
99
+ input_text=" ".join(args[1:]) if len(args) > 1 else None
100
+ )
101
+
102
+ # Everything else is a direct command
103
+ input_text = " ".join(args)
104
+ return CLICommand(
105
+ mode="direct",
106
+ input_text=input_text,
107
+ flags={
108
+ "dry_run": dry_run,
109
+ "iterative": iterative,
110
+ "force_provider": force_provider,
111
+ "force_model": force_model,
112
+ },
113
+ )
114
+
115
+ def show_help(self):
116
+ """Display help message"""
117
+ help_text = f"""
118
+ Zenus v{self.version}
119
+ A developer-centric, CLI-first, agent-driven operating layer
120
+
121
+ USAGE:
122
+ zenus [OPTIONS] [COMMAND]
123
+
124
+ COMMANDS:
125
+ shell Start interactive REPL (default)
126
+ status Show system status and active LLM config
127
+ model [status] Show current provider/model configuration
128
+ model list List all available models per provider
129
+ model set <p> [model] Set default provider (and optionally model)
130
+ rollback [N] Rollback last N actions (default: 1)
131
+ history Show command history and failures
132
+ help Show this help message
133
+ version Show version information
134
+ <direct command> Execute command immediately
135
+
136
+ OPTIONS:
137
+ --dry-run Show plan but do not execute
138
+ --iterative Use iterative ReAct loop (for complex tasks)
139
+ --provider <name> Use specific provider for this command only
140
+ (anthropic, openai, deepseek, ollama)
141
+ --model <id> Use specific model for this command only
142
+
143
+ EXAMPLES:
144
+ zenus # Start interactive shell
145
+ zenus "list files in ~/Documents" # Direct execution
146
+ zenus --dry-run "delete all tmp files" # Preview without executing
147
+ zenus --provider deepseek "summarize this" # Override provider once
148
+ zenus --model claude-opus-4-6 "refactor src" # Override model once
149
+ zenus model set anthropic claude-opus-4-6 # Change default
150
+ zenus --iterative "read project and improve" # Multi-step ReAct loop
151
+
152
+ INTERACTIVE SHELL SHORTCUTS:
153
+ @deepseek: your command # Use deepseek for this command
154
+ use claude: your command # Use anthropic for this command
155
+ --provider openai <cmd> # Override provider in shell mode
156
+ model set deepseek # Change default inside shell
157
+ status # Show status inside shell
158
+
159
+ PROVIDER ALIASES (for inline overrides):
160
+ anthropic, claude, sonnet, haiku, opus → anthropic
161
+ openai, gpt, chatgpt, o1, o3 → openai
162
+ deepseek → deepseek
163
+ ollama, local, llama → ollama
164
+
165
+ CREDENTIALS:
166
+ ANTHROPIC_API_KEY Anthropic (Claude) API key
167
+ OPENAI_API_KEY OpenAI API key
168
+ DEEPSEEK_API_KEY DeepSeek API key
169
+ (or set in config.yaml / .env)
170
+
171
+ LOGS:
172
+ Audit logs: ~/.zenus/logs/
173
+
174
+ For more information, visit: https://github.com/Guillhermm/zenus
175
+ """.strip()
176
+ print(help_text)
177
+
178
+ def show_version(self):
179
+ """Display version information"""
180
+ print(f"Zenus v{self.version}")
@@ -0,0 +1,159 @@
1
+ """
2
+ Zenus - Main Entry Point
3
+
4
+ CLI-first, agent-driven operating layer
5
+ Routes commands through: CLI to Intent to Plan to Execute
6
+ """
7
+
8
+ import sys
9
+ from zenus_cli.router import CommandRouter
10
+ from zenus_core.orchestrator import Orchestrator, OrchestratorError
11
+ from zenus_core.rollback import get_rollback_engine, RollbackError
12
+ from zenus_core.memory.action_tracker import get_action_tracker
13
+ from zenus_core.memory.failure_logger import get_failure_logger
14
+ from zenus_core.output.console import console
15
+
16
+
17
+ def main():
18
+ """Main entry point for Zenus"""
19
+
20
+ router = CommandRouter()
21
+ orchestrator = Orchestrator()
22
+
23
+ # Parse CLI arguments (skip program name)
24
+ args = sys.argv[1:]
25
+ command = router.parse(args)
26
+
27
+ # Route to appropriate handler
28
+ try:
29
+ if command.mode == "help":
30
+ router.show_help()
31
+
32
+ elif command.mode == "version":
33
+ router.show_version()
34
+
35
+ elif command.mode == "status":
36
+ from zenus_core.shell.commands import handle_status_command
37
+ handle_status_command(orchestrator)
38
+
39
+ elif command.mode == "model":
40
+ from zenus_core.shell.commands import handle_model_command
41
+ parts = (command.input_text or "status").split()
42
+ subcommand = parts[0]
43
+ args = parts[1:] if len(parts) > 1 else []
44
+ handle_model_command(subcommand, args)
45
+
46
+ elif command.mode == "interactive":
47
+ orchestrator.interactive_shell()
48
+
49
+ elif command.mode == "rollback":
50
+ # Rollback command
51
+ rollback_engine = get_rollback_engine()
52
+ dry_run = command.flags.get("dry_run", False)
53
+
54
+ # Parse number of actions to rollback
55
+ n = 1
56
+ if command.input_text:
57
+ try:
58
+ n = int(command.input_text)
59
+ except ValueError:
60
+ console.print("[red]Error: rollback argument must be a number[/red]")
61
+ sys.exit(1)
62
+
63
+ try:
64
+ result = rollback_engine.rollback_last_n_actions(n, dry_run=dry_run)
65
+ if result["success"]:
66
+ if result.get("dry_run"):
67
+ console.print("\n[green]Dry run complete - no changes made[/green]")
68
+ else:
69
+ console.print(f"\n[green]✓ Successfully rolled back {result['actions_rolled_back']} action(s)[/green]")
70
+ else:
71
+ console.print(f"\n[yellow]⚠ Partial rollback: {result['actions_rolled_back']} succeeded, {result['actions_failed']} failed[/yellow]")
72
+ for error in result["errors"]:
73
+ console.print(f" [red]• {error}[/red]")
74
+ except RollbackError as e:
75
+ console.print(f"[red]Rollback error: {e}[/red]")
76
+ sys.exit(1)
77
+
78
+ elif command.mode == "history":
79
+ # History command
80
+ action_tracker = get_action_tracker()
81
+ failure_logger = get_failure_logger()
82
+
83
+ # Check if --failures flag
84
+ show_failures = command.input_text == "--failures"
85
+
86
+ if show_failures:
87
+ # Show failure history
88
+ stats = failure_logger.get_failure_stats()
89
+ console.print("\n[cyan]Failure History:[/cyan]")
90
+ console.print(f" Total failures: {stats['total_failures']}")
91
+ console.print(f" Recent (7 days): {stats['recent_7_days']}")
92
+
93
+ if stats['by_tool']:
94
+ console.print("\n[cyan]By tool:[/cyan]")
95
+ for tool, count in sorted(stats['by_tool'].items(), key=lambda x: x[1], reverse=True)[:10]:
96
+ console.print(f" {tool}: {count}")
97
+
98
+ if stats['by_error_type']:
99
+ console.print("\n[cyan]By error type:[/cyan]")
100
+ for error_type, count in sorted(stats['by_error_type'].items(), key=lambda x: x[1], reverse=True):
101
+ console.print(f" {error_type}: {count}")
102
+
103
+ console.print(f"\n[cyan]Patterns with suggestions: {stats['patterns_with_suggestions']}[/cyan]")
104
+ else:
105
+ # Show transaction history
106
+ transactions = action_tracker.get_recent_transactions(limit=10)
107
+ console.print("\n[cyan]Recent Transactions:[/cyan]")
108
+ for tx in transactions:
109
+ status_color = "green" if tx["status"] == "completed" else "red"
110
+ rollback_info = " (rolled back)" if tx.get("rollback_status") else ""
111
+ console.print(f" [{status_color}]{tx['id']}[/{status_color}]: {tx['user_input']}{rollback_info}")
112
+ console.print(f" Goal: {tx['intent_goal']}")
113
+ console.print(f" Status: {tx['status']}")
114
+ console.print()
115
+
116
+ elif command.mode == "direct":
117
+ # Direct execution
118
+ dry_run = command.flags.get("dry_run", False)
119
+ iterative = command.flags.get("iterative", False)
120
+ force_provider = command.flags.get("force_provider")
121
+ force_model = command.flags.get("force_model")
122
+
123
+ # If --model given without --provider, embed it as an inline directive
124
+ # so the orchestrator's provider_override parser can infer the provider
125
+ input_text = command.input_text
126
+ if force_model and not force_provider:
127
+ input_text = f"--model {force_model} {input_text}"
128
+ force_provider = None # let the parser infer it
129
+
130
+ if iterative:
131
+ result = orchestrator.execute_iterative(
132
+ input_text,
133
+ max_iterations=10,
134
+ dry_run=dry_run,
135
+ force_provider=force_provider,
136
+ )
137
+ else:
138
+ result = orchestrator.execute_command(
139
+ input_text,
140
+ dry_run=dry_run,
141
+ force_provider=force_provider,
142
+ )
143
+
144
+ if result:
145
+ print(result)
146
+
147
+ except OrchestratorError as e:
148
+ print(f"Error: {e}", file=sys.stderr)
149
+ sys.exit(1)
150
+ except KeyboardInterrupt:
151
+ print("\nInterrupted")
152
+ sys.exit(130)
153
+ except Exception as e:
154
+ print(f"Unexpected error: {e}", file=sys.stderr)
155
+ sys.exit(1)
156
+
157
+
158
+ if __name__ == "__main__":
159
+ main()