cliops 3.2.2__py3-none-any.whl → 4.0.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.
- {cliops-3.2.2.dist-info → cliops-4.0.0.dist-info}/METADATA +9 -6
- cliops-4.0.0.dist-info/RECORD +18 -0
- core/branding.py +34 -0
- core/cache.py +51 -0
- core/optimizer.py +39 -8
- core/patterns.py +71 -15
- core/setup.py +77 -0
- core/state.py +1 -1
- core/validation.py +49 -0
- main.py +15 -0
- cliops-3.2.2.dist-info/RECORD +0 -14
- {cliops-3.2.2.dist-info → cliops-4.0.0.dist-info}/WHEEL +0 -0
- {cliops-3.2.2.dist-info → cliops-4.0.0.dist-info}/entry_points.txt +0 -0
- {cliops-3.2.2.dist-info → cliops-4.0.0.dist-info}/licenses/LICENSE +0 -0
- {cliops-3.2.2.dist-info → cliops-4.0.0.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,13 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cliops
|
3
|
-
Version:
|
3
|
+
Version: 4.0.0
|
4
4
|
Summary: Advanced CLI tool for structured, pattern-based prompt optimization and state management
|
5
|
-
Home-page: https://github.com/
|
5
|
+
Home-page: https://github.com/iammabd/cliops
|
6
6
|
Author: cliops by mabd
|
7
7
|
Author-email: iammabd@substack.com
|
8
|
-
Project-URL: Bug Reports, https://github.com/
|
9
|
-
Project-URL: Source, https://github.com/
|
10
|
-
Project-URL: Documentation, https://cliops.
|
8
|
+
Project-URL: Bug Reports, https://github.com/iammabd/cliops/issues
|
9
|
+
Project-URL: Source, https://github.com/iammabd/cliops
|
10
|
+
Project-URL: Documentation, https://cliops.hashnode.space
|
11
11
|
Keywords: cli prompt optimization ai llm prompt-engineering patterns state-management
|
12
12
|
Classifier: Development Status :: 5 - Production/Stable
|
13
13
|
Classifier: Intended Audience :: Developers
|
@@ -29,6 +29,9 @@ Requires-Python: >=3.8
|
|
29
29
|
Description-Content-Type: text/markdown
|
30
30
|
License-File: LICENSE
|
31
31
|
Requires-Dist: rich>=13.0.0
|
32
|
+
Requires-Dist: jinja2>=3.0.0
|
33
|
+
Requires-Dist: pydantic>=2.0.0
|
34
|
+
Requires-Dist: typing-extensions>=4.0.0
|
32
35
|
Provides-Extra: dev
|
33
36
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
34
37
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
@@ -51,7 +54,7 @@ Dynamic: requires-dist
|
|
51
54
|
Dynamic: requires-python
|
52
55
|
Dynamic: summary
|
53
56
|
|
54
|
-
#
|
57
|
+
# Cliops - Command Line Interface for Prompt Optimization
|
55
58
|
|
56
59
|
A powerful CLI tool for structured, pattern-based prompt optimization and state management.
|
57
60
|
|
@@ -0,0 +1,18 @@
|
|
1
|
+
main.py,sha256=eLhDnSUOgWM0-wQ1UkfLduEw6NXMs8b5JuVxzmg0QIk,9828
|
2
|
+
presets.py,sha256=DKIfhwxtBZEC5fYHgXqhuBKou7KYQks_2M8cR3IeWao,3172
|
3
|
+
cliops-4.0.0.dist-info/licenses/LICENSE,sha256=2J5KKebeJ2AdMaxuYA1fX0ZuDs9MmWp3cpjZX-JqrZs,1079
|
4
|
+
core/__init__.py,sha256=aWl7MZaubJNqrafNCM5VRYg4SZ7sdifrxTWrJC8d-_s,24
|
5
|
+
core/analyzer.py,sha256=mwMmrlV-Pk31mMfKs0NPqBqY3cpNwCq_DWwGaurjPzI,4328
|
6
|
+
core/branding.py,sha256=B8afUp8mxh1yNzM3cTFLZ9uA_iwKy6wu3N1ZbK6ahss,989
|
7
|
+
core/cache.py,sha256=uDUPRr7Q5gLHl_sUzsybt2nXBl9TSsbo92K8stbs1Pk,1658
|
8
|
+
core/config.py,sha256=aoYCLt-EFM7WiR3_QN049_cfz9_SODuqnErte07DTVM,1388
|
9
|
+
core/optimizer.py,sha256=n72DSf4pc4dqW7g8KPH1GOw8W8qpdi6q45tI02miuok,7762
|
10
|
+
core/patterns.py,sha256=CW2uFm1z8pCeQCio2oT39vkEJ9z_ifF-oRJtoufcyEM,9310
|
11
|
+
core/setup.py,sha256=gP6IuoAbrUKpwHQIwlHHufZyd7F3ZUtg-B3TJgWmCAE,2865
|
12
|
+
core/state.py,sha256=pWAdv085KSc6V7Idy3DdVCL35QbICNcjO3chi09yvOU,2654
|
13
|
+
core/validation.py,sha256=2wTVQE9MsHeN_3WhV0EiixHmFd3EXzV5t67TS_auT9I,1896
|
14
|
+
cliops-4.0.0.dist-info/METADATA,sha256=95m4etwpCcUoBn9IAebd9hZCdNkJ0vchO6H4f8s4afM,4182
|
15
|
+
cliops-4.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
16
|
+
cliops-4.0.0.dist-info/entry_points.txt,sha256=F8ZncVlnk83ELj0TGXUYS-qYvmaP-owQyU20I7jRefg,37
|
17
|
+
cliops-4.0.0.dist-info/top_level.txt,sha256=dV-NRp_bb_IkCFfEXDbA1mHA5SshVNbUsxaF809itG8,18
|
18
|
+
cliops-4.0.0.dist-info/RECORD,,
|
core/branding.py
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
from rich.console import Console
|
2
|
+
from rich.panel import Panel
|
3
|
+
from rich.text import Text
|
4
|
+
|
5
|
+
console = Console()
|
6
|
+
|
7
|
+
ASCII_LOGO = r"""
|
8
|
+
_____ _ _____ ____ _____ _____
|
9
|
+
/ ____| | |_ _/ __ \| __ \ / ____|
|
10
|
+
| | | | | || | | | |__) | (___
|
11
|
+
| | | | | || | | | ___/ \___ \
|
12
|
+
| |____| |____ _| || |__| | | ____) |
|
13
|
+
\_____|______|_____\____/|_| |_____/
|
14
|
+
"""
|
15
|
+
|
16
|
+
def show_banner():
|
17
|
+
"""Display CLIOPS ASCII art banner"""
|
18
|
+
logo_text = Text(ASCII_LOGO, style="bold cyan")
|
19
|
+
tagline = Text("Command Line Interface for Prompt Optimization", style="dim italic")
|
20
|
+
|
21
|
+
console.print()
|
22
|
+
console.print(logo_text, justify="center")
|
23
|
+
console.print(tagline, justify="center")
|
24
|
+
console.print()
|
25
|
+
|
26
|
+
def show_input_frame(prompt_text: str) -> str:
|
27
|
+
"""Display framed input prompt"""
|
28
|
+
panel = Panel(
|
29
|
+
f"[bold white]{prompt_text}[/bold white]",
|
30
|
+
border_style="white",
|
31
|
+
padding=(0, 1)
|
32
|
+
)
|
33
|
+
console.print(panel)
|
34
|
+
return input(">>> ")
|
core/cache.py
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
import json
|
2
|
+
import hashlib
|
3
|
+
from pathlib import Path
|
4
|
+
from typing import Any, Optional
|
5
|
+
from .config import Config
|
6
|
+
|
7
|
+
class Cache:
|
8
|
+
def __init__(self):
|
9
|
+
self.cache_dir = Config.get_app_data_dir() / "cache"
|
10
|
+
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
11
|
+
self._memory_cache = {}
|
12
|
+
|
13
|
+
def _get_cache_key(self, key: str) -> str:
|
14
|
+
"""Generate cache key hash"""
|
15
|
+
return hashlib.md5(key.encode()).hexdigest()
|
16
|
+
|
17
|
+
def get(self, key: str) -> Optional[Any]:
|
18
|
+
"""Get cached value"""
|
19
|
+
# Check memory cache first
|
20
|
+
if key in self._memory_cache:
|
21
|
+
return self._memory_cache[key]
|
22
|
+
|
23
|
+
# Check disk cache
|
24
|
+
cache_file = self.cache_dir / f"{self._get_cache_key(key)}.json"
|
25
|
+
if cache_file.exists():
|
26
|
+
try:
|
27
|
+
with open(cache_file, 'r') as f:
|
28
|
+
data = json.load(f)
|
29
|
+
self._memory_cache[key] = data
|
30
|
+
return data
|
31
|
+
except (json.JSONDecodeError, IOError):
|
32
|
+
cache_file.unlink(missing_ok=True)
|
33
|
+
|
34
|
+
return None
|
35
|
+
|
36
|
+
def set(self, key: str, value: Any):
|
37
|
+
"""Set cached value"""
|
38
|
+
self._memory_cache[key] = value
|
39
|
+
|
40
|
+
cache_file = self.cache_dir / f"{self._get_cache_key(key)}.json"
|
41
|
+
try:
|
42
|
+
with open(cache_file, 'w') as f:
|
43
|
+
json.dump(value, f)
|
44
|
+
except (IOError, TypeError):
|
45
|
+
pass # Fail silently for caching
|
46
|
+
|
47
|
+
def clear(self):
|
48
|
+
"""Clear all cache"""
|
49
|
+
self._memory_cache.clear()
|
50
|
+
for cache_file in self.cache_dir.glob("*.json"):
|
51
|
+
cache_file.unlink(missing_ok=True)
|
core/optimizer.py
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
import re
|
2
|
+
from jinja2 import Template, TemplateError
|
2
3
|
from rich.console import Console
|
3
4
|
from rich.panel import Panel
|
4
5
|
from rich.table import Table
|
5
6
|
from rich.syntax import Syntax
|
6
7
|
from rich.text import Text
|
7
8
|
from rich import box
|
9
|
+
from .validation import PromptValidator
|
10
|
+
from .cache import Cache
|
8
11
|
|
9
12
|
console = Console()
|
10
13
|
|
@@ -14,6 +17,8 @@ class PromptOptimizer:
|
|
14
17
|
self.pattern_registry = pattern_registry
|
15
18
|
self.cli_state = cli_state
|
16
19
|
self.verbose = verbose
|
20
|
+
self.cache = Cache()
|
21
|
+
self.validator = PromptValidator()
|
17
22
|
|
18
23
|
def _parse_prompt_into_sections(self, raw_prompt: str) -> dict:
|
19
24
|
"""Parses a raw prompt into a dictionary of sections based on '## SECTION:' headers and <TAG>...</TAG> blocks."""
|
@@ -89,12 +94,28 @@ class PromptOptimizer:
|
|
89
94
|
|
90
95
|
def optimize_prompt(self, raw_prompt: str, pattern_name: str, overrides: dict, dry_run: bool = False) -> str:
|
91
96
|
"""Applies an optimization pattern to a raw prompt."""
|
92
|
-
|
93
|
-
|
94
|
-
|
97
|
+
try:
|
98
|
+
# Validate and sanitize input
|
99
|
+
raw_prompt = self.validator.sanitize_input(raw_prompt)
|
100
|
+
pattern_name = self.validator.validate_pattern_name(pattern_name)
|
101
|
+
|
102
|
+
# Check cache first
|
103
|
+
cache_key = f"{pattern_name}:{hash(raw_prompt)}:{hash(str(overrides))}"
|
104
|
+
cached_result = self.cache.get(cache_key)
|
105
|
+
if cached_result and not dry_run:
|
106
|
+
return cached_result
|
107
|
+
|
108
|
+
pattern = self.pattern_registry.get_pattern(pattern_name)
|
109
|
+
if not pattern:
|
110
|
+
available = list(self.pattern_registry.patterns.keys())
|
111
|
+
raise ValueError(f"Pattern '{pattern_name}' not found. Available: {', '.join(available)}")
|
95
112
|
|
96
|
-
|
97
|
-
|
113
|
+
# Extract components from raw_prompt
|
114
|
+
extracted_fields = self._extract_components(raw_prompt, pattern)
|
115
|
+
|
116
|
+
except Exception as e:
|
117
|
+
console.print(f"[bold red]Error:[/bold red] {str(e)}", style="red")
|
118
|
+
raise ValueError(f"Input validation failed: {str(e)}")
|
98
119
|
|
99
120
|
# Prepare fields for template formatting
|
100
121
|
template_fields = {}
|
@@ -144,7 +165,17 @@ class PromptOptimizer:
|
|
144
165
|
return "Dry run complete. No prompt generated."
|
145
166
|
|
146
167
|
try:
|
147
|
-
|
168
|
+
# Use Jinja2 template rendering
|
169
|
+
template = Template(pattern.template)
|
170
|
+
optimized_prompt = template.render(**template_fields)
|
171
|
+
|
172
|
+
# Cache the result
|
173
|
+
if not dry_run:
|
174
|
+
self.cache.set(cache_key, optimized_prompt)
|
175
|
+
|
148
176
|
return optimized_prompt
|
149
|
-
|
150
|
-
|
177
|
+
|
178
|
+
except TemplateError as e:
|
179
|
+
raise ValueError(f"Template rendering failed for pattern '{pattern_name}': {str(e)}")
|
180
|
+
except Exception as e:
|
181
|
+
raise ValueError(f"Optimization failed: {str(e)}")
|
core/patterns.py
CHANGED
@@ -1,11 +1,14 @@
|
|
1
1
|
import json
|
2
2
|
import re
|
3
3
|
from pathlib import Path
|
4
|
+
from jinja2 import Template, TemplateError
|
4
5
|
from rich.console import Console
|
5
6
|
from rich.table import Table
|
6
7
|
from rich.syntax import Syntax
|
7
8
|
from rich import box
|
8
9
|
from .config import Config
|
10
|
+
from .cache import Cache
|
11
|
+
from .validation import PromptValidator
|
9
12
|
|
10
13
|
console = Console()
|
11
14
|
|
@@ -43,6 +46,7 @@ class PatternRegistry:
|
|
43
46
|
def __init__(self, cli_state):
|
44
47
|
self.patterns: dict[str, OptimizationPattern] = {}
|
45
48
|
self.cli_state = cli_state
|
49
|
+
self.cache = Cache()
|
46
50
|
self._load_default_patterns()
|
47
51
|
self._load_user_patterns()
|
48
52
|
|
@@ -77,28 +81,80 @@ class PatternRegistry:
|
|
77
81
|
default_patterns_data = [
|
78
82
|
{"name": "context_aware_generation",
|
79
83
|
"description": "Guides generation based on specific context, mindset, and current focus.",
|
80
|
-
"template": "# DIRECTIVE: {directive}\n\n"
|
84
|
+
"template": "# DIRECTIVE: {{ directive }}\n\n"
|
81
85
|
"## CONTEXT:\n"
|
82
|
-
"<CONTEXT>{context}</CONTEXT>\n\n"
|
86
|
+
"<CONTEXT>{{ context }}</CONTEXT>\n\n"
|
83
87
|
"## CURRENT FOCUS:\n"
|
84
|
-
"{current_focus}\n\n"
|
88
|
+
"{{ current_focus }}\n\n"
|
85
89
|
"## MINDSET:\n"
|
86
|
-
"{mindset}\n\n"
|
90
|
+
"{{ mindset }}\n\n"
|
87
91
|
"## CONSTRAINTS:\n"
|
88
|
-
"{constraints}\n\n"
|
92
|
+
"{{ constraints }}\n\n"
|
89
93
|
"## OUTPUT FORMAT:\n"
|
90
|
-
"{output_format}\n\n"
|
91
|
-
"## EXAMPLES:\n"
|
92
|
-
"{examples}\n\n"
|
94
|
+
"{{ output_format }}\n\n"
|
93
95
|
"## SUCCESS CRITERIA:\n"
|
94
|
-
"{success_criteria}\n\n"
|
96
|
+
"{{ success_criteria }}\n\n"
|
95
97
|
"## STATE:\n"
|
96
|
-
"Project Architecture: {STATE.ARCHITECTURE}\n"
|
97
|
-
"Common Patterns: {STATE.PATTERNS}\n"
|
98
|
-
"Current Project Focus: {STATE.FOCUS}\n\n"
|
99
|
-
"{code_here}",
|
98
|
+
"Project Architecture: {{ STATE.ARCHITECTURE }}\n"
|
99
|
+
"Common Patterns: {{ STATE.PATTERNS }}\n"
|
100
|
+
"Current Project Focus: {{ STATE.FOCUS }}\n\n"
|
101
|
+
"{{ code_here }}",
|
100
102
|
"principles": ["Context-Aware Generation", "Adaptive Nuance", "State Anchoring"],
|
101
|
-
"specific_extract_func": specific_extract_context_aware}
|
103
|
+
"specific_extract_func": specific_extract_context_aware},
|
104
|
+
|
105
|
+
{"name": "bug_fix_precision",
|
106
|
+
"description": "Structured approach for precise bug identification and resolution.",
|
107
|
+
"template": "# BUG FIX REQUEST\n\n"
|
108
|
+
"## PROBLEM DESCRIPTION:\n"
|
109
|
+
"{{ directive }}\n\n"
|
110
|
+
"## CURRENT CODE:\n"
|
111
|
+
"```\n{{ code_here }}\n```\n\n"
|
112
|
+
"## EXPECTED BEHAVIOR:\n"
|
113
|
+
"{{ success_criteria }}\n\n"
|
114
|
+
"## CONSTRAINTS:\n"
|
115
|
+
"{{ constraints }}\n\n"
|
116
|
+
"## ARCHITECTURE CONTEXT:\n"
|
117
|
+
"{{ STATE.ARCHITECTURE }}\n\n"
|
118
|
+
"## OUTPUT FORMAT:\n"
|
119
|
+
"{{ output_format }}",
|
120
|
+
"principles": ["Precision", "Root Cause Analysis", "Minimal Changes"]},
|
121
|
+
|
122
|
+
{"name": "code_review",
|
123
|
+
"description": "Comprehensive code review with security and performance focus.",
|
124
|
+
"template": "# CODE REVIEW REQUEST\n\n"
|
125
|
+
"## CODE TO REVIEW:\n"
|
126
|
+
"```\n{{ code_here }}\n```\n\n"
|
127
|
+
"## REVIEW FOCUS:\n"
|
128
|
+
"{{ directive }}\n\n"
|
129
|
+
"## ARCHITECTURE:\n"
|
130
|
+
"{{ STATE.ARCHITECTURE }}\n\n"
|
131
|
+
"## REVIEW CRITERIA:\n"
|
132
|
+
"- Security vulnerabilities\n"
|
133
|
+
"- Performance issues\n"
|
134
|
+
"- Code maintainability\n"
|
135
|
+
"- Best practices adherence\n\n"
|
136
|
+
"## CONSTRAINTS:\n"
|
137
|
+
"{{ constraints }}\n\n"
|
138
|
+
"## OUTPUT FORMAT:\n"
|
139
|
+
"{{ output_format }}",
|
140
|
+
"principles": ["Security First", "Performance", "Maintainability"]},
|
141
|
+
|
142
|
+
{"name": "api_design",
|
143
|
+
"description": "RESTful API design with OpenAPI specification focus.",
|
144
|
+
"template": "# API DESIGN REQUEST\n\n"
|
145
|
+
"## API REQUIREMENT:\n"
|
146
|
+
"{{ directive }}\n\n"
|
147
|
+
"## ARCHITECTURE:\n"
|
148
|
+
"{{ STATE.ARCHITECTURE }}\n\n"
|
149
|
+
"## ENDPOINTS NEEDED:\n"
|
150
|
+
"{{ scope }}\n\n"
|
151
|
+
"## CONSTRAINTS:\n"
|
152
|
+
"{{ constraints }}\n\n"
|
153
|
+
"## SUCCESS CRITERIA:\n"
|
154
|
+
"{{ success_criteria }}\n\n"
|
155
|
+
"## OUTPUT FORMAT:\n"
|
156
|
+
"{{ output_format }}",
|
157
|
+
"principles": ["RESTful Design", "OpenAPI Standard", "Scalability"]}
|
102
158
|
]
|
103
159
|
|
104
160
|
for p_data in default_patterns_data:
|
@@ -124,7 +180,7 @@ class PatternRegistry:
|
|
124
180
|
except Exception as e:
|
125
181
|
console.print(f"[bold red]Error:[/bold red] loading user patterns from {patterns_file}: {e}", style="red")
|
126
182
|
|
127
|
-
def get_pattern(self, name: str)
|
183
|
+
def get_pattern(self, name: str):
|
128
184
|
"""Retrieves a pattern by name."""
|
129
185
|
return self.patterns.get(name)
|
130
186
|
|
core/setup.py
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
from rich.console import Console
|
2
|
+
from rich.panel import Panel
|
3
|
+
|
4
|
+
from .validation import StateSchema
|
5
|
+
from .branding import show_input_frame
|
6
|
+
from pydantic import ValidationError
|
7
|
+
|
8
|
+
console = Console()
|
9
|
+
|
10
|
+
class StateSetup:
|
11
|
+
def __init__(self, cli_state):
|
12
|
+
self.cli_state = cli_state
|
13
|
+
self.required_fields = ["ARCHITECTURE", "FOCUS", "PATTERNS"]
|
14
|
+
|
15
|
+
def is_setup_complete(self) -> bool:
|
16
|
+
"""Check if all required state fields are configured"""
|
17
|
+
for field in self.required_fields:
|
18
|
+
if not self.cli_state.get(field):
|
19
|
+
return False
|
20
|
+
return True
|
21
|
+
|
22
|
+
def run_interactive_setup(self):
|
23
|
+
"""Run interactive state setup"""
|
24
|
+
console.print(Panel(
|
25
|
+
"[bold yellow]Initial Setup Required[/bold yellow]\n\n"
|
26
|
+
"Before using CLIOPS, please configure your project settings:",
|
27
|
+
border_style="yellow"
|
28
|
+
))
|
29
|
+
|
30
|
+
setup_data = {}
|
31
|
+
|
32
|
+
# Architecture setup
|
33
|
+
architecture = show_input_frame("Enter your project architecture (e.g., 'React + Node.js', 'Django + PostgreSQL'):")
|
34
|
+
setup_data["ARCHITECTURE"] = architecture
|
35
|
+
|
36
|
+
# Focus setup
|
37
|
+
focus = show_input_frame("Enter your current project focus (e.g., 'API development', 'UI components'):")
|
38
|
+
setup_data["FOCUS"] = focus
|
39
|
+
|
40
|
+
# Patterns setup
|
41
|
+
console.print(Panel(
|
42
|
+
"Available patterns: context_aware_generation, bug_fix_precision, code_review, api_design",
|
43
|
+
border_style="dim"
|
44
|
+
))
|
45
|
+
patterns = show_input_frame("Enter preferred patterns (comma-separated):")
|
46
|
+
setup_data["PATTERNS"] = patterns
|
47
|
+
|
48
|
+
# Default pattern
|
49
|
+
default_pattern = show_input_frame("Enter default pattern (optional, press Enter for 'context_aware_generation'):")
|
50
|
+
if default_pattern:
|
51
|
+
setup_data["DEFAULT_PATTERN"] = default_pattern
|
52
|
+
else:
|
53
|
+
setup_data["DEFAULT_PATTERN"] = "context_aware_generation"
|
54
|
+
|
55
|
+
# Validate and save
|
56
|
+
try:
|
57
|
+
validated_data = StateSchema(**setup_data)
|
58
|
+
for key, value in validated_data.model_dump().items():
|
59
|
+
if value: # Only set non-None values
|
60
|
+
self.cli_state.set(key, value)
|
61
|
+
|
62
|
+
console.print(Panel(
|
63
|
+
"[bold green]Setup Complete![/bold green]\n\n"
|
64
|
+
"You can now use CLIOPS to optimize and analyze prompts.",
|
65
|
+
border_style="green"
|
66
|
+
))
|
67
|
+
|
68
|
+
except ValidationError as e:
|
69
|
+
console.print(f"[bold red]Setup Error:[/bold red] {e}", style="red")
|
70
|
+
raise ValueError("Setup validation failed")
|
71
|
+
|
72
|
+
def check_and_setup(self):
|
73
|
+
"""Check setup status and run setup if needed"""
|
74
|
+
if not self.is_setup_complete():
|
75
|
+
self.run_interactive_setup()
|
76
|
+
return True
|
77
|
+
return False
|
core/state.py
CHANGED
@@ -41,7 +41,7 @@ class CLIState:
|
|
41
41
|
if not str(self.file_path).startswith('/tmp') and 'tmp' not in str(self.file_path):
|
42
42
|
console.print(f"State '[bold green]{key.upper()}[/bold green]' set to '[cyan]{value}[/cyan]'.")
|
43
43
|
|
44
|
-
def get(self, key: str)
|
44
|
+
def get(self, key: str):
|
45
45
|
"""Gets a value from the state."""
|
46
46
|
return self.state.get(key.upper())
|
47
47
|
|
core/validation.py
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
from pydantic import BaseModel, Field, field_validator
|
2
|
+
from typing import Optional, Dict, Any, Union
|
3
|
+
import re
|
4
|
+
|
5
|
+
class StateSchema(BaseModel):
|
6
|
+
ARCHITECTURE: str = Field(..., min_length=1, description="Project architecture")
|
7
|
+
FOCUS: str = Field(..., min_length=1, description="Current project focus")
|
8
|
+
PATTERNS: str = Field(..., min_length=1, description="Preferred patterns")
|
9
|
+
DEFAULT_PATTERN: Optional[str] = Field(None, description="Default optimization pattern")
|
10
|
+
|
11
|
+
@field_validator('ARCHITECTURE')
|
12
|
+
@classmethod
|
13
|
+
def validate_architecture(cls, v):
|
14
|
+
if len(v.strip()) < 3:
|
15
|
+
raise ValueError('Architecture must be at least 3 characters')
|
16
|
+
return v.strip()
|
17
|
+
|
18
|
+
@field_validator('PATTERNS')
|
19
|
+
@classmethod
|
20
|
+
def validate_patterns(cls, v):
|
21
|
+
# Basic pattern name validation
|
22
|
+
pattern_names = [p.strip() for p in v.split(',')]
|
23
|
+
for name in pattern_names:
|
24
|
+
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', name):
|
25
|
+
raise ValueError(f'Invalid pattern name: {name}')
|
26
|
+
return v
|
27
|
+
|
28
|
+
class PromptValidator:
|
29
|
+
@staticmethod
|
30
|
+
def sanitize_input(text: str) -> str:
|
31
|
+
"""Sanitize user input"""
|
32
|
+
if not text or not isinstance(text, str):
|
33
|
+
raise ValueError("Input must be a non-empty string")
|
34
|
+
|
35
|
+
# Remove potentially dangerous characters
|
36
|
+
sanitized = re.sub(r'<[^>]*>', '', text.strip()) # Remove HTML tags
|
37
|
+
sanitized = re.sub(r'[{}]', '', sanitized) # Remove braces
|
38
|
+
|
39
|
+
if len(sanitized) < 3:
|
40
|
+
raise ValueError("Input too short (minimum 3 characters)")
|
41
|
+
|
42
|
+
return sanitized
|
43
|
+
|
44
|
+
@staticmethod
|
45
|
+
def validate_pattern_name(name: str) -> str:
|
46
|
+
"""Validate pattern name"""
|
47
|
+
if not re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*$', name):
|
48
|
+
raise ValueError(f"Invalid pattern name: {name}")
|
49
|
+
return name
|
main.py
CHANGED
@@ -11,6 +11,9 @@ from core.state import CLIState
|
|
11
11
|
from core.patterns import PatternRegistry
|
12
12
|
from core.analyzer import PromptAnalyzer
|
13
13
|
from core.optimizer import PromptOptimizer
|
14
|
+
from core.branding import show_banner, show_input_frame
|
15
|
+
from core.setup import StateSetup
|
16
|
+
from core.validation import PromptValidator
|
14
17
|
from presets import suggest_preset_from_prompt, apply_preset_interactive
|
15
18
|
|
16
19
|
console = Console()
|
@@ -70,6 +73,9 @@ def _run_init_command(cli_state: CLIState, pattern_registry: PatternRegistry):
|
|
70
73
|
console.print(Panel("[bold green]Initialization complete![/bold green]", expand=False, border_style="green"))
|
71
74
|
|
72
75
|
def main():
|
76
|
+
# Show banner on startup
|
77
|
+
show_banner()
|
78
|
+
|
73
79
|
# Create a dummy parser to peek at the arguments
|
74
80
|
temp_parser = argparse.ArgumentParser(add_help=False)
|
75
81
|
temp_parser.add_argument("first_arg", nargs='?', help=argparse.SUPPRESS)
|
@@ -106,6 +112,15 @@ def main():
|
|
106
112
|
|
107
113
|
cli_state = CLIState(Config.get_state_file_path())
|
108
114
|
pattern_registry = PatternRegistry(cli_state)
|
115
|
+
|
116
|
+
# Check and run setup if needed (except for init command or tests)
|
117
|
+
if args.command != "init" and not os.environ.get('CLIOPS_SKIP_SETUP'):
|
118
|
+
state_setup = StateSetup(cli_state)
|
119
|
+
try:
|
120
|
+
state_setup.check_and_setup()
|
121
|
+
except ValueError as e:
|
122
|
+
console.print(f"[bold red]Setup Error:[/bold red] {e}", style="red")
|
123
|
+
sys.exit(1)
|
109
124
|
|
110
125
|
# Handle cases where prompt might be piped via stdin
|
111
126
|
if hasattr(args, 'prompt') and args.prompt is None and not sys.stdin.isatty():
|
cliops-3.2.2.dist-info/RECORD
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
main.py,sha256=YwzFPWB6QDtjK7r9gKA9tixEgD_7_b7gXoj6Q2ETCqk,9255
|
2
|
-
presets.py,sha256=DKIfhwxtBZEC5fYHgXqhuBKou7KYQks_2M8cR3IeWao,3172
|
3
|
-
cliops-3.2.2.dist-info/licenses/LICENSE,sha256=2J5KKebeJ2AdMaxuYA1fX0ZuDs9MmWp3cpjZX-JqrZs,1079
|
4
|
-
core/__init__.py,sha256=aWl7MZaubJNqrafNCM5VRYg4SZ7sdifrxTWrJC8d-_s,24
|
5
|
-
core/analyzer.py,sha256=mwMmrlV-Pk31mMfKs0NPqBqY3cpNwCq_DWwGaurjPzI,4328
|
6
|
-
core/config.py,sha256=aoYCLt-EFM7WiR3_QN049_cfz9_SODuqnErte07DTVM,1388
|
7
|
-
core/optimizer.py,sha256=MK-C0wpvdUfTu0a1OiKFTPqc9WptoMNYZc7pziwm3gQ,6435
|
8
|
-
core/patterns.py,sha256=BWiwb5fjQbWHLsJ68p0QbtSddFqzhYx3AeLkSY-WKbU,6464
|
9
|
-
core/state.py,sha256=BeosHqAwT3tcicaQs-gJabz2jrtwQp8t7J0ziGXv_QI,2668
|
10
|
-
cliops-3.2.2.dist-info/METADATA,sha256=JnwFSYAm-g_CIVQsjM-ta5CQL1LukpS8kT04XLQrBZA,4076
|
11
|
-
cliops-3.2.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
12
|
-
cliops-3.2.2.dist-info/entry_points.txt,sha256=F8ZncVlnk83ELj0TGXUYS-qYvmaP-owQyU20I7jRefg,37
|
13
|
-
cliops-3.2.2.dist-info/top_level.txt,sha256=dV-NRp_bb_IkCFfEXDbA1mHA5SshVNbUsxaF809itG8,18
|
14
|
-
cliops-3.2.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|