pdd-cli 0.0.45__py3-none-any.whl → 0.0.118__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.
- pdd/__init__.py +40 -8
- pdd/agentic_bug.py +323 -0
- pdd/agentic_bug_orchestrator.py +497 -0
- pdd/agentic_change.py +231 -0
- pdd/agentic_change_orchestrator.py +526 -0
- pdd/agentic_common.py +598 -0
- pdd/agentic_crash.py +534 -0
- pdd/agentic_e2e_fix.py +319 -0
- pdd/agentic_e2e_fix_orchestrator.py +426 -0
- pdd/agentic_fix.py +1294 -0
- pdd/agentic_langtest.py +162 -0
- pdd/agentic_update.py +387 -0
- pdd/agentic_verify.py +183 -0
- pdd/architecture_sync.py +565 -0
- pdd/auth_service.py +210 -0
- pdd/auto_deps_main.py +71 -51
- pdd/auto_include.py +245 -5
- pdd/auto_update.py +125 -47
- pdd/bug_main.py +196 -23
- pdd/bug_to_unit_test.py +2 -0
- pdd/change_main.py +11 -4
- pdd/cli.py +22 -1181
- pdd/cmd_test_main.py +350 -150
- pdd/code_generator.py +60 -18
- pdd/code_generator_main.py +790 -57
- pdd/commands/__init__.py +48 -0
- pdd/commands/analysis.py +306 -0
- pdd/commands/auth.py +309 -0
- pdd/commands/connect.py +290 -0
- pdd/commands/fix.py +163 -0
- pdd/commands/generate.py +257 -0
- pdd/commands/maintenance.py +175 -0
- pdd/commands/misc.py +87 -0
- pdd/commands/modify.py +256 -0
- pdd/commands/report.py +144 -0
- pdd/commands/sessions.py +284 -0
- pdd/commands/templates.py +215 -0
- pdd/commands/utility.py +110 -0
- pdd/config_resolution.py +58 -0
- pdd/conflicts_main.py +8 -3
- pdd/construct_paths.py +589 -111
- pdd/context_generator.py +10 -2
- pdd/context_generator_main.py +175 -76
- pdd/continue_generation.py +53 -10
- pdd/core/__init__.py +33 -0
- pdd/core/cli.py +527 -0
- pdd/core/cloud.py +237 -0
- pdd/core/dump.py +554 -0
- pdd/core/errors.py +67 -0
- pdd/core/remote_session.py +61 -0
- pdd/core/utils.py +90 -0
- pdd/crash_main.py +262 -33
- pdd/data/language_format.csv +71 -63
- pdd/data/llm_model.csv +20 -18
- pdd/detect_change_main.py +5 -4
- pdd/docs/prompting_guide.md +864 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/benchmark_analysis.py +495 -0
- pdd/docs/whitepaper_with_benchmarks/data_and_functions/creation_compare.py +528 -0
- pdd/fix_code_loop.py +523 -95
- pdd/fix_code_module_errors.py +6 -2
- pdd/fix_error_loop.py +491 -92
- pdd/fix_errors_from_unit_tests.py +4 -3
- pdd/fix_main.py +278 -21
- pdd/fix_verification_errors.py +12 -100
- pdd/fix_verification_errors_loop.py +529 -286
- pdd/fix_verification_main.py +294 -89
- pdd/frontend/dist/assets/index-B5DZHykP.css +1 -0
- pdd/frontend/dist/assets/index-DQ3wkeQ2.js +449 -0
- pdd/frontend/dist/index.html +376 -0
- pdd/frontend/dist/logo.svg +33 -0
- pdd/generate_output_paths.py +139 -15
- pdd/generate_test.py +218 -146
- pdd/get_comment.py +19 -44
- pdd/get_extension.py +8 -9
- pdd/get_jwt_token.py +318 -22
- pdd/get_language.py +8 -7
- pdd/get_run_command.py +75 -0
- pdd/get_test_command.py +68 -0
- pdd/git_update.py +70 -19
- pdd/incremental_code_generator.py +2 -2
- pdd/insert_includes.py +13 -4
- pdd/llm_invoke.py +1711 -181
- pdd/load_prompt_template.py +19 -12
- pdd/path_resolution.py +140 -0
- pdd/pdd_completion.fish +25 -2
- pdd/pdd_completion.sh +30 -4
- pdd/pdd_completion.zsh +79 -4
- pdd/postprocess.py +14 -4
- pdd/preprocess.py +293 -24
- pdd/preprocess_main.py +41 -6
- pdd/prompts/agentic_bug_step10_pr_LLM.prompt +182 -0
- pdd/prompts/agentic_bug_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_bug_step2_docs_LLM.prompt +129 -0
- pdd/prompts/agentic_bug_step3_triage_LLM.prompt +95 -0
- pdd/prompts/agentic_bug_step4_reproduce_LLM.prompt +97 -0
- pdd/prompts/agentic_bug_step5_root_cause_LLM.prompt +123 -0
- pdd/prompts/agentic_bug_step6_test_plan_LLM.prompt +107 -0
- pdd/prompts/agentic_bug_step7_generate_LLM.prompt +172 -0
- pdd/prompts/agentic_bug_step8_verify_LLM.prompt +119 -0
- pdd/prompts/agentic_bug_step9_e2e_test_LLM.prompt +289 -0
- pdd/prompts/agentic_change_step10_identify_issues_LLM.prompt +1006 -0
- pdd/prompts/agentic_change_step11_fix_issues_LLM.prompt +984 -0
- pdd/prompts/agentic_change_step12_create_pr_LLM.prompt +131 -0
- pdd/prompts/agentic_change_step1_duplicate_LLM.prompt +73 -0
- pdd/prompts/agentic_change_step2_docs_LLM.prompt +101 -0
- pdd/prompts/agentic_change_step3_research_LLM.prompt +126 -0
- pdd/prompts/agentic_change_step4_clarify_LLM.prompt +164 -0
- pdd/prompts/agentic_change_step5_docs_change_LLM.prompt +981 -0
- pdd/prompts/agentic_change_step6_devunits_LLM.prompt +1005 -0
- pdd/prompts/agentic_change_step7_architecture_LLM.prompt +1044 -0
- pdd/prompts/agentic_change_step8_analyze_LLM.prompt +1027 -0
- pdd/prompts/agentic_change_step9_implement_LLM.prompt +1077 -0
- pdd/prompts/agentic_crash_explore_LLM.prompt +49 -0
- pdd/prompts/agentic_e2e_fix_step1_unit_tests_LLM.prompt +90 -0
- pdd/prompts/agentic_e2e_fix_step2_e2e_tests_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step3_root_cause_LLM.prompt +89 -0
- pdd/prompts/agentic_e2e_fix_step4_fix_e2e_tests_LLM.prompt +96 -0
- pdd/prompts/agentic_e2e_fix_step5_identify_devunits_LLM.prompt +91 -0
- pdd/prompts/agentic_e2e_fix_step6_create_unit_tests_LLM.prompt +106 -0
- pdd/prompts/agentic_e2e_fix_step7_verify_tests_LLM.prompt +116 -0
- pdd/prompts/agentic_e2e_fix_step8_run_pdd_fix_LLM.prompt +120 -0
- pdd/prompts/agentic_e2e_fix_step9_verify_all_LLM.prompt +146 -0
- pdd/prompts/agentic_fix_explore_LLM.prompt +45 -0
- pdd/prompts/agentic_fix_harvest_only_LLM.prompt +48 -0
- pdd/prompts/agentic_fix_primary_LLM.prompt +85 -0
- pdd/prompts/agentic_update_LLM.prompt +925 -0
- pdd/prompts/agentic_verify_explore_LLM.prompt +45 -0
- pdd/prompts/auto_include_LLM.prompt +122 -905
- pdd/prompts/change_LLM.prompt +3093 -1
- pdd/prompts/detect_change_LLM.prompt +686 -27
- pdd/prompts/example_generator_LLM.prompt +22 -1
- pdd/prompts/extract_code_LLM.prompt +5 -1
- pdd/prompts/extract_program_code_fix_LLM.prompt +7 -1
- pdd/prompts/extract_prompt_update_LLM.prompt +7 -8
- pdd/prompts/extract_promptline_LLM.prompt +17 -11
- pdd/prompts/find_verification_errors_LLM.prompt +6 -0
- pdd/prompts/fix_code_module_errors_LLM.prompt +12 -2
- pdd/prompts/fix_errors_from_unit_tests_LLM.prompt +9 -0
- pdd/prompts/fix_verification_errors_LLM.prompt +22 -0
- pdd/prompts/generate_test_LLM.prompt +41 -7
- pdd/prompts/generate_test_from_example_LLM.prompt +115 -0
- pdd/prompts/increase_tests_LLM.prompt +1 -5
- pdd/prompts/insert_includes_LLM.prompt +316 -186
- pdd/prompts/prompt_code_diff_LLM.prompt +119 -0
- pdd/prompts/prompt_diff_LLM.prompt +82 -0
- pdd/prompts/trace_LLM.prompt +25 -22
- pdd/prompts/unfinished_prompt_LLM.prompt +85 -1
- pdd/prompts/update_prompt_LLM.prompt +22 -1
- pdd/pytest_output.py +127 -12
- pdd/remote_session.py +876 -0
- pdd/render_mermaid.py +236 -0
- pdd/server/__init__.py +52 -0
- pdd/server/app.py +335 -0
- pdd/server/click_executor.py +587 -0
- pdd/server/executor.py +338 -0
- pdd/server/jobs.py +661 -0
- pdd/server/models.py +241 -0
- pdd/server/routes/__init__.py +31 -0
- pdd/server/routes/architecture.py +451 -0
- pdd/server/routes/auth.py +364 -0
- pdd/server/routes/commands.py +929 -0
- pdd/server/routes/config.py +42 -0
- pdd/server/routes/files.py +603 -0
- pdd/server/routes/prompts.py +1322 -0
- pdd/server/routes/websocket.py +473 -0
- pdd/server/security.py +243 -0
- pdd/server/terminal_spawner.py +209 -0
- pdd/server/token_counter.py +222 -0
- pdd/setup_tool.py +648 -0
- pdd/simple_math.py +2 -0
- pdd/split_main.py +3 -2
- pdd/summarize_directory.py +237 -195
- pdd/sync_animation.py +8 -4
- pdd/sync_determine_operation.py +839 -112
- pdd/sync_main.py +351 -57
- pdd/sync_orchestration.py +1400 -756
- pdd/sync_tui.py +848 -0
- pdd/template_expander.py +161 -0
- pdd/template_registry.py +264 -0
- pdd/templates/architecture/architecture_json.prompt +237 -0
- pdd/templates/generic/generate_prompt.prompt +174 -0
- pdd/trace.py +168 -12
- pdd/trace_main.py +4 -3
- pdd/track_cost.py +140 -63
- pdd/unfinished_prompt.py +51 -4
- pdd/update_main.py +567 -67
- pdd/update_model_costs.py +2 -2
- pdd/update_prompt.py +19 -4
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/METADATA +29 -11
- pdd_cli-0.0.118.dist-info/RECORD +227 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/licenses/LICENSE +1 -1
- pdd_cli-0.0.45.dist-info/RECORD +0 -116
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.45.dist-info → pdd_cli-0.0.118.dist-info}/top_level.txt +0 -0
pdd/setup_tool.py
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
PDD Setup Script - Post-install configuration tool for PDD (Prompt Driven Development)
|
|
4
|
+
Helps new users bootstrap their PDD configuration with LLM API keys and basic settings.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import sys
|
|
9
|
+
import subprocess
|
|
10
|
+
import json
|
|
11
|
+
import requests
|
|
12
|
+
import csv
|
|
13
|
+
import importlib.resources
|
|
14
|
+
import shlex
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from typing import Dict, Optional, Tuple, List
|
|
17
|
+
|
|
18
|
+
# Global variables for non-ASCII characters and colors
|
|
19
|
+
HEAVY_HORIZONTAL = "━"
|
|
20
|
+
LIGHT_HORIZONTAL = "─"
|
|
21
|
+
HEAVY_VERTICAL = "┃"
|
|
22
|
+
LIGHT_VERTICAL = "│"
|
|
23
|
+
TOP_LEFT_CORNER = "┏"
|
|
24
|
+
TOP_RIGHT_CORNER = "┓"
|
|
25
|
+
BOTTOM_LEFT_CORNER = "┗"
|
|
26
|
+
BOTTOM_RIGHT_CORNER = "┛"
|
|
27
|
+
CROSS = "┼"
|
|
28
|
+
TEE_DOWN = "┬"
|
|
29
|
+
TEE_UP = "┴"
|
|
30
|
+
TEE_RIGHT = "├"
|
|
31
|
+
TEE_LEFT = "┤"
|
|
32
|
+
BULLET = "•"
|
|
33
|
+
ARROW_RIGHT = "→"
|
|
34
|
+
CHECK_MARK = "✓"
|
|
35
|
+
CROSS_MARK = "✗"
|
|
36
|
+
|
|
37
|
+
# Color codes
|
|
38
|
+
RESET = "\033[0m"
|
|
39
|
+
WHITE = "\033[97m"
|
|
40
|
+
CYAN = "\033[96m"
|
|
41
|
+
YELLOW = "\033[93m"
|
|
42
|
+
BOLD = "\033[1m"
|
|
43
|
+
|
|
44
|
+
# Template content inline
|
|
45
|
+
SUCCESS_PYTHON_TEMPLATE = """
|
|
46
|
+
Write a python script to print "You did it, <Username>!!!" to the console.
|
|
47
|
+
Do not write anything except that message.
|
|
48
|
+
Capitalize the username."""
|
|
49
|
+
|
|
50
|
+
def _read_packaged_llm_model_csv() -> Tuple[List[str], List[Dict[str, str]]]:
|
|
51
|
+
"""Load the packaged CSV (pdd/data/llm_model.csv) and return header + rows.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
(header_fields, rows) where header_fields is the list of column names
|
|
55
|
+
and rows is a list of dictionaries for each CSV row.
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
csv_text = importlib.resources.files('pdd').joinpath('data/llm_model.csv').read_text()
|
|
59
|
+
except Exception as e:
|
|
60
|
+
raise FileNotFoundError(f"Failed to load default LLM model CSV from package: {e}")
|
|
61
|
+
|
|
62
|
+
reader = csv.DictReader(csv_text.splitlines())
|
|
63
|
+
header = reader.fieldnames or []
|
|
64
|
+
rows = [row for row in reader]
|
|
65
|
+
return header, rows
|
|
66
|
+
|
|
67
|
+
def print_colored(text: str, color: str = WHITE, bold: bool = False) -> None:
|
|
68
|
+
"""Print colored text to console"""
|
|
69
|
+
style = BOLD + color if bold else color
|
|
70
|
+
print(f"{style}{text}{RESET}")
|
|
71
|
+
|
|
72
|
+
def create_divider(char: str = LIGHT_HORIZONTAL, width: int = 80) -> str:
|
|
73
|
+
"""Create a horizontal divider line"""
|
|
74
|
+
return char * width
|
|
75
|
+
|
|
76
|
+
def create_fat_divider(width: int = 80) -> str:
|
|
77
|
+
"""Create a fat horizontal divider line"""
|
|
78
|
+
return HEAVY_HORIZONTAL * width
|
|
79
|
+
|
|
80
|
+
def print_pdd_logo():
|
|
81
|
+
"""Print the PDD logo in ASCII art"""
|
|
82
|
+
logo = "\n".join(
|
|
83
|
+
[
|
|
84
|
+
" +xxxxxxxxxxxxxxx+",
|
|
85
|
+
"xxxxxxxxxxxxxxxxxxxxx+",
|
|
86
|
+
"xxx +xx+ PROMPT",
|
|
87
|
+
"xxx x+ xx+ DRIVEN",
|
|
88
|
+
"xxx x+ xxx DEVELOPMENT©",
|
|
89
|
+
"xxx x+ xx+",
|
|
90
|
+
"xxx x+ xx+ COMMAND LINE INTERFACE",
|
|
91
|
+
"xxx x+ xxx",
|
|
92
|
+
"xxx +xx+ ",
|
|
93
|
+
"xxx +xxxxxxxxxxx+",
|
|
94
|
+
"xxx +xx+",
|
|
95
|
+
"xxx +xx+",
|
|
96
|
+
"xxx+xx+ WWW.PROMPTDRIVEN.AI",
|
|
97
|
+
"xxxx+",
|
|
98
|
+
"xx+",
|
|
99
|
+
]
|
|
100
|
+
)
|
|
101
|
+
print(f"{CYAN}{logo}{RESET}")
|
|
102
|
+
print()
|
|
103
|
+
print_colored("Let's get set up quickly with a solid basic configuration!", WHITE, bold=True)
|
|
104
|
+
print()
|
|
105
|
+
print_colored("Supported: OpenAI, Google Gemini, and Anthropic Claude", WHITE)
|
|
106
|
+
print_colored("from their respective API endpoints (no third-parties, such as Azure)", WHITE)
|
|
107
|
+
print()
|
|
108
|
+
|
|
109
|
+
def get_csv_variable_names() -> Dict[str, str]:
|
|
110
|
+
"""Inspect packaged CSV to determine API key variable names per provider.
|
|
111
|
+
|
|
112
|
+
Focus on direct providers only: OpenAI GPT models (model startswith 'gpt-'),
|
|
113
|
+
Google Gemini (model startswith 'gemini/'), and Anthropic (model startswith 'anthropic/').
|
|
114
|
+
"""
|
|
115
|
+
header, rows = _read_packaged_llm_model_csv()
|
|
116
|
+
variable_names: Dict[str, str] = {}
|
|
117
|
+
|
|
118
|
+
for row in rows:
|
|
119
|
+
model = (row.get('model') or '').strip()
|
|
120
|
+
api_key = (row.get('api_key') or '').strip()
|
|
121
|
+
provider = (row.get('provider') or '').strip().upper()
|
|
122
|
+
|
|
123
|
+
if not api_key:
|
|
124
|
+
continue
|
|
125
|
+
|
|
126
|
+
if model.startswith('gpt-') and provider == 'OPENAI':
|
|
127
|
+
variable_names['OPENAI'] = api_key
|
|
128
|
+
elif model.startswith('gemini/') and provider == 'GOOGLE':
|
|
129
|
+
# Prefer direct Gemini key, not Vertex
|
|
130
|
+
variable_names['GOOGLE'] = api_key
|
|
131
|
+
elif model.startswith('anthropic/') and provider == 'ANTHROPIC':
|
|
132
|
+
variable_names['ANTHROPIC'] = api_key
|
|
133
|
+
|
|
134
|
+
# Fallbacks if not detected (keep prior behavior)
|
|
135
|
+
variable_names.setdefault('OPENAI', 'OPENAI_API_KEY')
|
|
136
|
+
# Prefer GEMINI_API_KEY name for Google if present
|
|
137
|
+
variable_names.setdefault('GOOGLE', 'GEMINI_API_KEY')
|
|
138
|
+
variable_names.setdefault('ANTHROPIC', 'ANTHROPIC_API_KEY')
|
|
139
|
+
return variable_names
|
|
140
|
+
|
|
141
|
+
def discover_api_keys() -> Dict[str, Optional[str]]:
|
|
142
|
+
"""Discover API keys from environment variables"""
|
|
143
|
+
# Get the variable names actually used in CSV template
|
|
144
|
+
csv_vars = get_csv_variable_names()
|
|
145
|
+
|
|
146
|
+
keys = {
|
|
147
|
+
'OPENAI_API_KEY': os.getenv('OPENAI_API_KEY'),
|
|
148
|
+
'ANTHROPIC_API_KEY': os.getenv('ANTHROPIC_API_KEY'),
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# For Google, check both possible environment variables but use CSV template's variable name
|
|
152
|
+
google_var_name = csv_vars.get('GOOGLE', 'GEMINI_API_KEY') # Default to GEMINI_API_KEY
|
|
153
|
+
google_api_key = os.getenv('GEMINI_API_KEY') or os.getenv('GOOGLE_API_KEY')
|
|
154
|
+
keys[google_var_name] = google_api_key
|
|
155
|
+
|
|
156
|
+
return keys
|
|
157
|
+
|
|
158
|
+
def test_openai_key(api_key: str) -> bool:
|
|
159
|
+
"""Test OpenAI API key validity"""
|
|
160
|
+
if not api_key or not api_key.strip():
|
|
161
|
+
return False
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
headers = {
|
|
165
|
+
'Authorization': f'Bearer {api_key.strip()}',
|
|
166
|
+
'Content-Type': 'application/json'
|
|
167
|
+
}
|
|
168
|
+
response = requests.get(
|
|
169
|
+
'https://api.openai.com/v1/models',
|
|
170
|
+
headers=headers,
|
|
171
|
+
timeout=10
|
|
172
|
+
)
|
|
173
|
+
return response.status_code == 200
|
|
174
|
+
except Exception:
|
|
175
|
+
return False
|
|
176
|
+
|
|
177
|
+
def test_google_key(api_key: str) -> bool:
|
|
178
|
+
"""Test Google Gemini API key validity"""
|
|
179
|
+
if not api_key or not api_key.strip():
|
|
180
|
+
return False
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
response = requests.get(
|
|
184
|
+
f'https://generativelanguage.googleapis.com/v1beta/models?key={api_key.strip()}',
|
|
185
|
+
timeout=10
|
|
186
|
+
)
|
|
187
|
+
return response.status_code == 200
|
|
188
|
+
except Exception:
|
|
189
|
+
return False
|
|
190
|
+
|
|
191
|
+
def test_anthropic_key(api_key: str) -> bool:
|
|
192
|
+
"""Test Anthropic API key validity"""
|
|
193
|
+
if not api_key or not api_key.strip():
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
headers = {
|
|
198
|
+
'x-api-key': api_key.strip(),
|
|
199
|
+
'Content-Type': 'application/json'
|
|
200
|
+
}
|
|
201
|
+
response = requests.get(
|
|
202
|
+
'https://api.anthropic.com/v1/messages',
|
|
203
|
+
headers=headers,
|
|
204
|
+
timeout=10
|
|
205
|
+
)
|
|
206
|
+
# Anthropic returns 400 for invalid request structure but 401/403 for bad keys
|
|
207
|
+
return response.status_code != 401 and response.status_code != 403
|
|
208
|
+
except Exception:
|
|
209
|
+
return False
|
|
210
|
+
|
|
211
|
+
def test_api_keys(keys: Dict[str, Optional[str]]) -> Dict[str, bool]:
|
|
212
|
+
"""Test all discovered API keys"""
|
|
213
|
+
results = {}
|
|
214
|
+
|
|
215
|
+
print_colored(f"\n{LIGHT_HORIZONTAL * 40}", CYAN)
|
|
216
|
+
print_colored("Testing discovered API keys...", CYAN, bold=True)
|
|
217
|
+
print_colored(f"{LIGHT_HORIZONTAL * 40}", CYAN)
|
|
218
|
+
|
|
219
|
+
for key_name, key_value in keys.items():
|
|
220
|
+
if key_value:
|
|
221
|
+
print(f"Testing {key_name}...", end=" ", flush=True)
|
|
222
|
+
if key_name == 'OPENAI_API_KEY':
|
|
223
|
+
valid = test_openai_key(key_value)
|
|
224
|
+
elif key_name in ['GEMINI_API_KEY', 'GOOGLE_API_KEY']:
|
|
225
|
+
valid = test_google_key(key_value)
|
|
226
|
+
elif key_name == 'ANTHROPIC_API_KEY':
|
|
227
|
+
valid = test_anthropic_key(key_value)
|
|
228
|
+
else:
|
|
229
|
+
valid = False
|
|
230
|
+
|
|
231
|
+
if valid:
|
|
232
|
+
print_colored(f"{CHECK_MARK} Valid", CYAN)
|
|
233
|
+
results[key_name] = True
|
|
234
|
+
else:
|
|
235
|
+
print_colored(f"{CROSS_MARK} Invalid", YELLOW)
|
|
236
|
+
results[key_name] = False
|
|
237
|
+
else:
|
|
238
|
+
print_colored(f"{key_name}: Not found", YELLOW)
|
|
239
|
+
results[key_name] = False
|
|
240
|
+
|
|
241
|
+
return results
|
|
242
|
+
|
|
243
|
+
def get_user_keys(current_keys: Dict[str, Optional[str]]) -> Dict[str, Optional[str]]:
|
|
244
|
+
"""Interactive key entry/modification"""
|
|
245
|
+
print_colored(f"\n{create_fat_divider()}", YELLOW)
|
|
246
|
+
print_colored("API Key Configuration", YELLOW, bold=True)
|
|
247
|
+
print_colored(f"{create_fat_divider()}", YELLOW)
|
|
248
|
+
|
|
249
|
+
print_colored("You need only one API key to get started", WHITE)
|
|
250
|
+
print()
|
|
251
|
+
print_colored("Get API keys here:", WHITE)
|
|
252
|
+
print_colored(f" OpenAI {ARROW_RIGHT} https://platform.openai.com/api-keys", CYAN)
|
|
253
|
+
print_colored(f" Google Gemini {ARROW_RIGHT} https://aistudio.google.com/app/apikey", CYAN)
|
|
254
|
+
print_colored(f" Anthropic {ARROW_RIGHT} https://console.anthropic.com/settings/keys", CYAN)
|
|
255
|
+
print()
|
|
256
|
+
print_colored("A free instant starter key is available from Google Gemini (above)", CYAN)
|
|
257
|
+
print()
|
|
258
|
+
|
|
259
|
+
new_keys = current_keys.copy()
|
|
260
|
+
|
|
261
|
+
# Get the actual key names from discovered keys
|
|
262
|
+
key_names = list(current_keys.keys())
|
|
263
|
+
for key_name in key_names:
|
|
264
|
+
current_value = current_keys.get(key_name, "")
|
|
265
|
+
status = "found" if current_value else "not found"
|
|
266
|
+
|
|
267
|
+
print_colored(f"{LIGHT_HORIZONTAL * 60}", CYAN)
|
|
268
|
+
print_colored(f"{key_name} (currently: {status})", WHITE, bold=True)
|
|
269
|
+
|
|
270
|
+
if current_value:
|
|
271
|
+
prompt = f"Enter new key or press ENTER to keep existing: "
|
|
272
|
+
else:
|
|
273
|
+
prompt = f"Enter API key (or press ENTER to skip): "
|
|
274
|
+
|
|
275
|
+
try:
|
|
276
|
+
user_input = input(f"{WHITE}{prompt}{RESET}").strip()
|
|
277
|
+
if user_input:
|
|
278
|
+
new_keys[key_name] = user_input
|
|
279
|
+
elif not current_value:
|
|
280
|
+
new_keys[key_name] = None
|
|
281
|
+
except KeyboardInterrupt:
|
|
282
|
+
print_colored("\n\nSetup cancelled.", YELLOW)
|
|
283
|
+
sys.exit(0)
|
|
284
|
+
|
|
285
|
+
return new_keys
|
|
286
|
+
|
|
287
|
+
def detect_shell() -> str:
|
|
288
|
+
"""Detect user's default shell"""
|
|
289
|
+
try:
|
|
290
|
+
shell_path = os.getenv('SHELL', '/bin/bash')
|
|
291
|
+
shell_name = os.path.basename(shell_path)
|
|
292
|
+
return shell_name
|
|
293
|
+
except:
|
|
294
|
+
return 'bash'
|
|
295
|
+
|
|
296
|
+
def get_shell_init_file(shell: str) -> str:
|
|
297
|
+
"""Get the appropriate shell initialization file"""
|
|
298
|
+
home = Path.home()
|
|
299
|
+
|
|
300
|
+
shell_files = {
|
|
301
|
+
'bash': home / '.bashrc',
|
|
302
|
+
'zsh': home / '.zshrc',
|
|
303
|
+
'fish': home / '.config/fish/config.fish',
|
|
304
|
+
'csh': home / '.cshrc',
|
|
305
|
+
'tcsh': home / '.tcshrc',
|
|
306
|
+
'ksh': home / '.kshrc',
|
|
307
|
+
'sh': home / '.profile'
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
return str(shell_files.get(shell, home / '.bashrc'))
|
|
311
|
+
|
|
312
|
+
def create_api_env_script(keys: Dict[str, str], shell: str) -> str:
|
|
313
|
+
"""Create shell-appropriate environment script with proper escaping"""
|
|
314
|
+
valid_keys = {k: v for k, v in keys.items() if v}
|
|
315
|
+
lines = []
|
|
316
|
+
|
|
317
|
+
for key, value in valid_keys.items():
|
|
318
|
+
# shlex.quote is designed for POSIX shells (sh, bash, zsh, ksh)
|
|
319
|
+
# It also works reasonably well for fish and csh for simple assignments
|
|
320
|
+
quoted_val = shlex.quote(value)
|
|
321
|
+
|
|
322
|
+
if shell == 'fish':
|
|
323
|
+
lines.append(f'set -gx {key} {quoted_val}')
|
|
324
|
+
elif shell in ['csh', 'tcsh']:
|
|
325
|
+
lines.append(f'setenv {key} {quoted_val}')
|
|
326
|
+
else: # bash, zsh, ksh, sh and others
|
|
327
|
+
lines.append(f'export {key}={quoted_val}')
|
|
328
|
+
|
|
329
|
+
return '\n'.join(lines) + '\n'
|
|
330
|
+
|
|
331
|
+
def save_configuration(valid_keys: Dict[str, str]) -> Tuple[List[str], bool, Optional[str]]:
|
|
332
|
+
"""Save configuration to ~/.pdd/ directory"""
|
|
333
|
+
home = Path.home()
|
|
334
|
+
pdd_dir = home / '.pdd'
|
|
335
|
+
created_pdd_dir = False
|
|
336
|
+
saved_files = []
|
|
337
|
+
|
|
338
|
+
# Create .pdd directory if it doesn't exist
|
|
339
|
+
if not pdd_dir.exists():
|
|
340
|
+
pdd_dir.mkdir(mode=0o755)
|
|
341
|
+
created_pdd_dir = True
|
|
342
|
+
|
|
343
|
+
# Detect shell and create api-env script
|
|
344
|
+
shell = detect_shell()
|
|
345
|
+
api_env_content = create_api_env_script(valid_keys, shell)
|
|
346
|
+
|
|
347
|
+
# Write shell-specific api-env file
|
|
348
|
+
api_env_file = pdd_dir / f'api-env.{shell}'
|
|
349
|
+
api_env_file.write_text(api_env_content)
|
|
350
|
+
api_env_file.chmod(0o755)
|
|
351
|
+
saved_files.append(str(api_env_file))
|
|
352
|
+
|
|
353
|
+
# Create llm_model.csv with models from packaged CSV filtered by provider and available keys
|
|
354
|
+
header_fields, rows = _read_packaged_llm_model_csv()
|
|
355
|
+
|
|
356
|
+
# Keep only direct Google Gemini (model startswith 'gemini/'), OpenAI GPT (gpt-*) and Anthropic (anthropic/*)
|
|
357
|
+
def _is_supported_model(row: Dict[str, str]) -> bool:
|
|
358
|
+
model = (row.get('model') or '').strip()
|
|
359
|
+
if model.startswith('gpt-'):
|
|
360
|
+
return True
|
|
361
|
+
if model.startswith('gemini/'):
|
|
362
|
+
return True
|
|
363
|
+
if model.startswith('anthropic/'):
|
|
364
|
+
return True
|
|
365
|
+
return False
|
|
366
|
+
|
|
367
|
+
# Filter rows by supported models and by api_key presence in valid_keys
|
|
368
|
+
filtered_rows: List[Dict[str, str]] = []
|
|
369
|
+
for row in rows:
|
|
370
|
+
if not _is_supported_model(row):
|
|
371
|
+
continue
|
|
372
|
+
api_key_name = (row.get('api_key') or '').strip()
|
|
373
|
+
# Include only if we have a validated key for this row
|
|
374
|
+
if api_key_name and api_key_name in valid_keys:
|
|
375
|
+
filtered_rows.append(row)
|
|
376
|
+
|
|
377
|
+
# Write out the filtered CSV to ~/.pdd/llm_model.csv preserving column order
|
|
378
|
+
llm_model_file = pdd_dir / 'llm_model.csv'
|
|
379
|
+
with llm_model_file.open('w', newline='') as f:
|
|
380
|
+
writer = csv.DictWriter(f, fieldnames=header_fields)
|
|
381
|
+
writer.writeheader()
|
|
382
|
+
for row in filtered_rows:
|
|
383
|
+
writer.writerow({k: row.get(k, '') for k in header_fields})
|
|
384
|
+
saved_files.append(str(llm_model_file))
|
|
385
|
+
|
|
386
|
+
# Update shell init file
|
|
387
|
+
init_file_path = get_shell_init_file(shell)
|
|
388
|
+
init_file = Path(init_file_path)
|
|
389
|
+
init_file_updated = None
|
|
390
|
+
|
|
391
|
+
source_line = f'[ -f "{api_env_file}" ] && source "{api_env_file}"'
|
|
392
|
+
if shell == 'fish':
|
|
393
|
+
source_line = f'test -f "{api_env_file}"; and source "{api_env_file}"'
|
|
394
|
+
elif shell in ['csh', 'tcsh']:
|
|
395
|
+
source_line = f'if ( -f "{api_env_file}" ) source "{api_env_file}"'
|
|
396
|
+
elif shell == 'sh':
|
|
397
|
+
source_line = f'[ -f "{api_env_file}" ] && . "{api_env_file}"'
|
|
398
|
+
|
|
399
|
+
# Ensure parent directory exists (important for fish shell)
|
|
400
|
+
init_file.parent.mkdir(parents=True, exist_ok=True)
|
|
401
|
+
|
|
402
|
+
# Check if source line already exists
|
|
403
|
+
if init_file.exists():
|
|
404
|
+
content = init_file.read_text()
|
|
405
|
+
if str(api_env_file) not in content:
|
|
406
|
+
with init_file.open('a') as f:
|
|
407
|
+
f.write(f'\n# PDD API environment\n{source_line}\n')
|
|
408
|
+
init_file_updated = str(init_file)
|
|
409
|
+
else:
|
|
410
|
+
init_file.write_text(f'# PDD API environment\n{source_line}\n')
|
|
411
|
+
init_file_updated = str(init_file)
|
|
412
|
+
|
|
413
|
+
return saved_files, created_pdd_dir, init_file_updated
|
|
414
|
+
|
|
415
|
+
def create_sample_prompt():
|
|
416
|
+
"""Create the sample prompt file"""
|
|
417
|
+
prompt_file = Path('success_python.prompt')
|
|
418
|
+
prompt_file.write_text(SUCCESS_PYTHON_TEMPLATE)
|
|
419
|
+
return str(prompt_file)
|
|
420
|
+
|
|
421
|
+
def show_menu(keys: Dict[str, Optional[str]], test_results: Dict[str, bool]) -> str:
|
|
422
|
+
"""Show main menu and get user choice"""
|
|
423
|
+
print_colored(f"\n{create_divider()}", CYAN)
|
|
424
|
+
print_colored("Main Menu", CYAN, bold=True)
|
|
425
|
+
print_colored(f"{create_divider()}", CYAN)
|
|
426
|
+
|
|
427
|
+
# Show current status
|
|
428
|
+
print_colored("Current API Key Status:", WHITE, bold=True)
|
|
429
|
+
# Get the actual key names from discovered keys
|
|
430
|
+
key_names = list(keys.keys())
|
|
431
|
+
for key_name in key_names:
|
|
432
|
+
key_value = keys.get(key_name)
|
|
433
|
+
if key_value:
|
|
434
|
+
status = f"{CHECK_MARK} Valid" if test_results.get(key_name) else f"{CROSS_MARK} Invalid"
|
|
435
|
+
status_color = CYAN if test_results.get(key_name) else YELLOW
|
|
436
|
+
else:
|
|
437
|
+
status = "Not configured"
|
|
438
|
+
status_color = YELLOW
|
|
439
|
+
|
|
440
|
+
print(f" {key_name}: ", end="")
|
|
441
|
+
print_colored(status, status_color)
|
|
442
|
+
|
|
443
|
+
print()
|
|
444
|
+
print_colored("Options:", WHITE, bold=True)
|
|
445
|
+
print(f" 1. Re-enter API keys")
|
|
446
|
+
print(f" 2. Re-test current keys")
|
|
447
|
+
print(f" 3. Save configuration and exit")
|
|
448
|
+
print(f" 4. Exit without saving")
|
|
449
|
+
print()
|
|
450
|
+
|
|
451
|
+
while True:
|
|
452
|
+
try:
|
|
453
|
+
choice = input(f"{WHITE}Choose an option (1-4): {RESET}").strip()
|
|
454
|
+
if choice in ['1', '2', '3', '4']:
|
|
455
|
+
return choice
|
|
456
|
+
else:
|
|
457
|
+
print_colored("Please enter 1, 2, 3, or 4", YELLOW)
|
|
458
|
+
except KeyboardInterrupt:
|
|
459
|
+
print_colored("\n\nSetup cancelled.", YELLOW)
|
|
460
|
+
sys.exit(0)
|
|
461
|
+
|
|
462
|
+
def create_exit_summary(saved_files: List[str], created_pdd_dir: bool, sample_prompt_file: str, shell: str, valid_keys: Dict[str, str], init_file_updated: Optional[str] = None) -> str:
|
|
463
|
+
"""Create comprehensive exit summary"""
|
|
464
|
+
summary_lines = [
|
|
465
|
+
"\n\n\n\n\n",
|
|
466
|
+
create_fat_divider(),
|
|
467
|
+
"PDD Setup Complete!",
|
|
468
|
+
create_fat_divider(),
|
|
469
|
+
"",
|
|
470
|
+
"API Keys Configured:",
|
|
471
|
+
""
|
|
472
|
+
]
|
|
473
|
+
|
|
474
|
+
# Add configured API keys information
|
|
475
|
+
if valid_keys:
|
|
476
|
+
for key_name, key_value in valid_keys.items():
|
|
477
|
+
# Show just the first and last few characters for security
|
|
478
|
+
masked_key = f"{key_value[:8]}...{key_value[-4:]}" if len(key_value) > 12 else "***"
|
|
479
|
+
summary_lines.append(f" {key_name}: {masked_key}")
|
|
480
|
+
summary_lines.extend(["", "Files created and configured:", ""])
|
|
481
|
+
else:
|
|
482
|
+
summary_lines.extend([" None", "", "Files created and configured:", ""])
|
|
483
|
+
|
|
484
|
+
# File descriptions with alignment
|
|
485
|
+
file_descriptions = []
|
|
486
|
+
if created_pdd_dir:
|
|
487
|
+
file_descriptions.append(("~/.pdd/", "PDD configuration directory"))
|
|
488
|
+
|
|
489
|
+
for file_path in saved_files:
|
|
490
|
+
if 'api-env.' in file_path:
|
|
491
|
+
file_descriptions.append((file_path, f"API environment variables ({shell} shell)"))
|
|
492
|
+
elif 'llm_model.csv' in file_path:
|
|
493
|
+
file_descriptions.append((file_path, "LLM model configuration"))
|
|
494
|
+
|
|
495
|
+
file_descriptions.append((sample_prompt_file, "Sample prompt for testing"))
|
|
496
|
+
|
|
497
|
+
# Add shell init file if it was updated
|
|
498
|
+
if init_file_updated:
|
|
499
|
+
file_descriptions.append((init_file_updated, f"Shell startup file (updated to source API environment)"))
|
|
500
|
+
|
|
501
|
+
file_descriptions.append(("PDD-SETUP-SUMMARY.txt", "This summary"))
|
|
502
|
+
|
|
503
|
+
# Find max file path length for alignment
|
|
504
|
+
max_path_len = max(len(path) for path, _ in file_descriptions)
|
|
505
|
+
|
|
506
|
+
for file_path, description in file_descriptions:
|
|
507
|
+
summary_lines.append(f"{file_path:<{max_path_len + 2}}{description}")
|
|
508
|
+
|
|
509
|
+
summary_lines.extend([
|
|
510
|
+
"",
|
|
511
|
+
create_divider(),
|
|
512
|
+
"",
|
|
513
|
+
"QUICK START:",
|
|
514
|
+
"",
|
|
515
|
+
f"1. Reload your shell environment:"
|
|
516
|
+
])
|
|
517
|
+
|
|
518
|
+
# Shell-specific source command for manual reloading
|
|
519
|
+
api_env_path = f"{Path.home()}/.pdd/api-env.{shell}"
|
|
520
|
+
# Use dot command for sh shell, source for others
|
|
521
|
+
if shell == 'sh':
|
|
522
|
+
source_cmd = f". {api_env_path}"
|
|
523
|
+
else:
|
|
524
|
+
source_cmd = f"source {api_env_path}"
|
|
525
|
+
|
|
526
|
+
summary_lines.extend([
|
|
527
|
+
f" {source_cmd}",
|
|
528
|
+
"",
|
|
529
|
+
f"2. Generate code from the sample prompt:",
|
|
530
|
+
f" pdd generate success_python.prompt",
|
|
531
|
+
"",
|
|
532
|
+
create_divider(),
|
|
533
|
+
"",
|
|
534
|
+
"LEARN MORE:",
|
|
535
|
+
"",
|
|
536
|
+
f"{BULLET} PDD documentation: pdd --help",
|
|
537
|
+
f"{BULLET} PDD website: https://promptdriven.ai/",
|
|
538
|
+
f"{BULLET} Discord community: https://discord.gg/Yp4RTh8bG7",
|
|
539
|
+
"",
|
|
540
|
+
"TIPS:",
|
|
541
|
+
"",
|
|
542
|
+
f"{BULLET} IMPORTANT: Reload your shell environment using the source command above",
|
|
543
|
+
"",
|
|
544
|
+
f"{BULLET} Start with simple prompts and gradually increase complexity",
|
|
545
|
+
f"{BULLET} Try out 'pdd test' with your prompt+code to create test(s) pdd can use to automatically verify and fix your output code",
|
|
546
|
+
f"{BULLET} Try out 'pdd example' with your prompt+code to create examples which help pdd do better",
|
|
547
|
+
"",
|
|
548
|
+
f"{BULLET} As you get comfortable, learn configuration settings, including the .pddrc file, PDD_GENERATE_OUTPUT_PATH, and PDD_TEST_OUTPUT_PATH",
|
|
549
|
+
f"{BULLET} For larger projects, use Makefiles and/or 'pdd sync'",
|
|
550
|
+
f"{BULLET} For ongoing substantial projects, learn about llm_model.csv and the --strength,",
|
|
551
|
+
f" --temperature, and --time options to optimize model cost, latency, and output quality",
|
|
552
|
+
"",
|
|
553
|
+
f"{BULLET} Use 'pdd --help' to explore all available commands",
|
|
554
|
+
"",
|
|
555
|
+
"Problems? Shout out on our Discord for help! https://discord.gg/Yp4RTh8bG7"
|
|
556
|
+
])
|
|
557
|
+
|
|
558
|
+
return '\n'.join(summary_lines)
|
|
559
|
+
|
|
560
|
+
def main():
|
|
561
|
+
"""Main setup workflow"""
|
|
562
|
+
# Initial greeting
|
|
563
|
+
print_pdd_logo()
|
|
564
|
+
|
|
565
|
+
# Discover environment
|
|
566
|
+
print_colored(f"{create_divider()}", CYAN)
|
|
567
|
+
print_colored("Discovering local configuration...", CYAN, bold=True)
|
|
568
|
+
print_colored(f"{create_divider()}", CYAN)
|
|
569
|
+
|
|
570
|
+
keys = discover_api_keys()
|
|
571
|
+
|
|
572
|
+
# Test discovered keys
|
|
573
|
+
test_results = test_api_keys(keys)
|
|
574
|
+
|
|
575
|
+
# Main interaction loop
|
|
576
|
+
while True:
|
|
577
|
+
choice = show_menu(keys, test_results)
|
|
578
|
+
|
|
579
|
+
if choice == '1':
|
|
580
|
+
# Re-enter keys
|
|
581
|
+
keys = get_user_keys(keys)
|
|
582
|
+
test_results = test_api_keys(keys)
|
|
583
|
+
|
|
584
|
+
elif choice == '2':
|
|
585
|
+
# Re-test keys
|
|
586
|
+
test_results = test_api_keys(keys)
|
|
587
|
+
|
|
588
|
+
elif choice == '3':
|
|
589
|
+
# Save and exit
|
|
590
|
+
valid_keys = {k: v for k, v in keys.items() if v and test_results.get(k)}
|
|
591
|
+
|
|
592
|
+
if not valid_keys:
|
|
593
|
+
print_colored("\nNo valid API keys to save!", YELLOW)
|
|
594
|
+
continue
|
|
595
|
+
|
|
596
|
+
print_colored(f"\n{create_divider()}", CYAN)
|
|
597
|
+
print_colored("Saving configuration...", CYAN, bold=True)
|
|
598
|
+
print_colored(f"{create_divider()}", CYAN)
|
|
599
|
+
|
|
600
|
+
try:
|
|
601
|
+
saved_files, created_pdd_dir, init_file_updated = save_configuration(valid_keys)
|
|
602
|
+
sample_prompt_file = create_sample_prompt()
|
|
603
|
+
shell = detect_shell()
|
|
604
|
+
|
|
605
|
+
# Create and display summary
|
|
606
|
+
summary = create_exit_summary(saved_files, created_pdd_dir, sample_prompt_file, shell, valid_keys, init_file_updated)
|
|
607
|
+
|
|
608
|
+
# Write summary to file
|
|
609
|
+
summary_file = Path('PDD-SETUP-SUMMARY.txt')
|
|
610
|
+
summary_file.write_text(summary)
|
|
611
|
+
|
|
612
|
+
# Display summary with colors
|
|
613
|
+
lines = summary.split('\n')
|
|
614
|
+
for line in lines:
|
|
615
|
+
if line == create_fat_divider():
|
|
616
|
+
print_colored(line, YELLOW, bold=True)
|
|
617
|
+
elif line == "PDD Setup Complete!":
|
|
618
|
+
print_colored(line, YELLOW, bold=True)
|
|
619
|
+
elif line == create_divider():
|
|
620
|
+
print_colored(line, CYAN)
|
|
621
|
+
elif line.startswith("API Keys Configured:") or line.startswith("Files created and configured:"):
|
|
622
|
+
print_colored(line, CYAN, bold=True)
|
|
623
|
+
elif line.startswith("QUICK START:"):
|
|
624
|
+
print_colored(line, YELLOW, bold=True)
|
|
625
|
+
elif line.startswith("LEARN MORE:") or line.startswith("TIPS:"):
|
|
626
|
+
print_colored(line, CYAN, bold=True)
|
|
627
|
+
elif "IMPORTANT:" in line or "Problems?" in line:
|
|
628
|
+
print_colored(line, YELLOW, bold=True)
|
|
629
|
+
else:
|
|
630
|
+
print(line)
|
|
631
|
+
|
|
632
|
+
break
|
|
633
|
+
|
|
634
|
+
except Exception as e:
|
|
635
|
+
print_colored(f"Error saving configuration: {e}", YELLOW)
|
|
636
|
+
continue
|
|
637
|
+
|
|
638
|
+
elif choice == '4':
|
|
639
|
+
# Exit without saving
|
|
640
|
+
print_colored("\nExiting without saving configuration.", YELLOW)
|
|
641
|
+
break
|
|
642
|
+
|
|
643
|
+
if __name__ == '__main__':
|
|
644
|
+
try:
|
|
645
|
+
main()
|
|
646
|
+
except KeyboardInterrupt:
|
|
647
|
+
print_colored("\n\nSetup cancelled.", YELLOW)
|
|
648
|
+
sys.exit(0)
|
pdd/simple_math.py
ADDED
pdd/split_main.py
CHANGED
|
@@ -53,7 +53,8 @@ def split_main(
|
|
|
53
53
|
force=ctx.obj.get('force', False),
|
|
54
54
|
quiet=ctx.obj.get('quiet', False),
|
|
55
55
|
command="split",
|
|
56
|
-
command_options=command_options
|
|
56
|
+
command_options=command_options,
|
|
57
|
+
context_override=ctx.obj.get('context')
|
|
57
58
|
)
|
|
58
59
|
|
|
59
60
|
# Get parameters from context
|
|
@@ -113,4 +114,4 @@ def split_main(
|
|
|
113
114
|
elif isinstance(e, ValueError):
|
|
114
115
|
rprint("[yellow]Hint: Check if input files have valid content.[/yellow]")
|
|
115
116
|
|
|
116
|
-
sys.exit(1)
|
|
117
|
+
sys.exit(1)
|