pdd-cli 0.0.58__py3-none-any.whl → 0.0.59__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.
Potentially problematic release.
This version of pdd-cli might be problematic. Click here for more details.
- pdd/__init__.py +1 -1
- pdd/cli.py +1 -5
- pdd_cli-0.0.58.data/data/utils/pdd-setup.py → pdd/setup_tool.py +246 -221
- {pdd_cli-0.0.58.dist-info → pdd_cli-0.0.59.dist-info}/METADATA +5 -3
- {pdd_cli-0.0.58.dist-info → pdd_cli-0.0.59.dist-info}/RECORD +9 -9
- {pdd_cli-0.0.58.dist-info → pdd_cli-0.0.59.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.58.dist-info → pdd_cli-0.0.59.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.58.dist-info → pdd_cli-0.0.59.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.58.dist-info → pdd_cli-0.0.59.dist-info}/top_level.txt +0 -0
pdd/__init__.py
CHANGED
pdd/cli.py
CHANGED
|
@@ -155,11 +155,7 @@ def _should_show_onboarding_reminder(ctx: click.Context) -> bool:
|
|
|
155
155
|
|
|
156
156
|
def _run_setup_utility() -> None:
|
|
157
157
|
"""Execute the interactive setup utility script."""
|
|
158
|
-
|
|
159
|
-
if not setup_script.exists():
|
|
160
|
-
raise FileNotFoundError(f"Setup utility not found at {setup_script}")
|
|
161
|
-
|
|
162
|
-
result = subprocess.run([sys.executable, str(setup_script)])
|
|
158
|
+
result = subprocess.run([sys.executable, "-m", "pdd.setup_tool"])
|
|
163
159
|
if result.returncode not in (0, None):
|
|
164
160
|
raise RuntimeError(f"Setup utility exited with status {result.returncode}")
|
|
165
161
|
|
|
@@ -1,20 +1,16 @@
|
|
|
1
|
-
|
|
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
|
-
"""
|
|
1
|
+
"""Interactive post-install setup utility for PDD."""
|
|
6
2
|
|
|
7
3
|
import os
|
|
8
4
|
import sys
|
|
9
|
-
import subprocess
|
|
10
|
-
import json
|
|
11
|
-
import requests
|
|
12
5
|
from pathlib import Path
|
|
13
|
-
from typing import Dict, Optional, Tuple
|
|
6
|
+
from typing import Dict, List, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
14
10
|
|
|
15
11
|
# Global variables for non-ASCII characters and colors
|
|
16
12
|
HEAVY_HORIZONTAL = "━"
|
|
17
|
-
LIGHT_HORIZONTAL = "─"
|
|
13
|
+
LIGHT_HORIZONTAL = "─"
|
|
18
14
|
HEAVY_VERTICAL = "┃"
|
|
19
15
|
LIGHT_VERTICAL = "│"
|
|
20
16
|
TOP_LEFT_CORNER = "┏"
|
|
@@ -38,6 +34,7 @@ CYAN = "\033[96m"
|
|
|
38
34
|
YELLOW = "\033[93m"
|
|
39
35
|
BOLD = "\033[1m"
|
|
40
36
|
|
|
37
|
+
|
|
41
38
|
# Template content inline
|
|
42
39
|
HELLO_PYTHON_TEMPLATE = """Create a Python program that prints "Hello <username>" in ASCII art.
|
|
43
40
|
|
|
@@ -60,21 +57,29 @@ OpenAI,gpt-5-mini,0.25,2.0,1325,,OPENAI_API_KEY,0,True,effort
|
|
|
60
57
|
OpenAI,gpt-5,1.25,10.0,1482,,OPENAI_API_KEY,0,True,effort
|
|
61
58
|
OpenAI,gpt-4.1,2.0,8.0,1253,,OPENAI_API_KEY,0,True,none"""
|
|
62
59
|
|
|
60
|
+
|
|
63
61
|
def print_colored(text: str, color: str = WHITE, bold: bool = False) -> None:
|
|
64
|
-
"""Print colored text to console"""
|
|
62
|
+
"""Print colored text to console."""
|
|
63
|
+
|
|
65
64
|
style = BOLD + color if bold else color
|
|
66
65
|
print(f"{style}{text}{RESET}")
|
|
67
66
|
|
|
67
|
+
|
|
68
68
|
def create_divider(char: str = LIGHT_HORIZONTAL, width: int = 80) -> str:
|
|
69
|
-
"""Create a horizontal divider line"""
|
|
69
|
+
"""Create a horizontal divider line."""
|
|
70
|
+
|
|
70
71
|
return char * width
|
|
71
72
|
|
|
73
|
+
|
|
72
74
|
def create_fat_divider(width: int = 80) -> str:
|
|
73
|
-
"""Create a fat horizontal divider line"""
|
|
75
|
+
"""Create a fat horizontal divider line."""
|
|
76
|
+
|
|
74
77
|
return HEAVY_HORIZONTAL * width
|
|
75
78
|
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
|
|
80
|
+
def print_pdd_logo() -> None:
|
|
81
|
+
"""Print the PDD logo in ASCII art."""
|
|
82
|
+
|
|
78
83
|
logo = "\n".join(
|
|
79
84
|
[
|
|
80
85
|
" +xxxxxxxxxxxxxxx+",
|
|
@@ -99,65 +104,73 @@ def print_pdd_logo():
|
|
|
99
104
|
print_colored("from their respective API endpoints (no third-parties, such as Azure)", WHITE)
|
|
100
105
|
print()
|
|
101
106
|
|
|
107
|
+
|
|
102
108
|
def discover_api_keys() -> Dict[str, Optional[str]]:
|
|
103
|
-
"""Discover API keys from environment variables"""
|
|
109
|
+
"""Discover API keys from environment variables."""
|
|
110
|
+
|
|
104
111
|
keys = {
|
|
105
|
-
|
|
106
|
-
|
|
112
|
+
"OPENAI_API_KEY": os.getenv("OPENAI_API_KEY"),
|
|
113
|
+
"GOOGLE_API_KEY": os.getenv("GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY"),
|
|
107
114
|
}
|
|
108
115
|
return keys
|
|
109
116
|
|
|
117
|
+
|
|
110
118
|
def test_openai_key(api_key: str) -> bool:
|
|
111
|
-
"""Test OpenAI API key validity"""
|
|
119
|
+
"""Test OpenAI API key validity."""
|
|
120
|
+
|
|
112
121
|
if not api_key or not api_key.strip():
|
|
113
122
|
return False
|
|
114
|
-
|
|
123
|
+
|
|
115
124
|
try:
|
|
116
125
|
headers = {
|
|
117
|
-
|
|
118
|
-
|
|
126
|
+
"Authorization": f"Bearer {api_key.strip()}",
|
|
127
|
+
"Content-Type": "application/json",
|
|
119
128
|
}
|
|
120
129
|
response = requests.get(
|
|
121
|
-
|
|
130
|
+
"https://api.openai.com/v1/models",
|
|
122
131
|
headers=headers,
|
|
123
|
-
timeout=10
|
|
132
|
+
timeout=10,
|
|
124
133
|
)
|
|
125
134
|
return response.status_code == 200
|
|
126
135
|
except Exception:
|
|
127
136
|
return False
|
|
128
137
|
|
|
138
|
+
|
|
129
139
|
def test_google_key(api_key: str) -> bool:
|
|
130
|
-
"""Test Google Gemini API key validity"""
|
|
140
|
+
"""Test Google Gemini API key validity."""
|
|
141
|
+
|
|
131
142
|
if not api_key or not api_key.strip():
|
|
132
143
|
return False
|
|
133
|
-
|
|
144
|
+
|
|
134
145
|
try:
|
|
135
146
|
response = requests.get(
|
|
136
|
-
f
|
|
137
|
-
timeout=10
|
|
147
|
+
f"https://generativelanguage.googleapis.com/v1beta/models?key={api_key.strip()}",
|
|
148
|
+
timeout=10,
|
|
138
149
|
)
|
|
139
150
|
return response.status_code == 200
|
|
140
151
|
except Exception:
|
|
141
152
|
return False
|
|
142
153
|
|
|
154
|
+
|
|
143
155
|
def test_api_keys(keys: Dict[str, Optional[str]]) -> Dict[str, bool]:
|
|
144
|
-
"""Test all discovered API keys"""
|
|
145
|
-
|
|
146
|
-
|
|
156
|
+
"""Test all discovered API keys."""
|
|
157
|
+
|
|
158
|
+
results: Dict[str, bool] = {}
|
|
159
|
+
|
|
147
160
|
print_colored(f"\n{LIGHT_HORIZONTAL * 40}", CYAN)
|
|
148
161
|
print_colored("Testing discovered API keys...", CYAN, bold=True)
|
|
149
162
|
print_colored(f"{LIGHT_HORIZONTAL * 40}", CYAN)
|
|
150
|
-
|
|
163
|
+
|
|
151
164
|
for key_name, key_value in keys.items():
|
|
152
165
|
if key_value:
|
|
153
166
|
print(f"Testing {key_name}...", end=" ", flush=True)
|
|
154
|
-
if key_name ==
|
|
167
|
+
if key_name == "OPENAI_API_KEY":
|
|
155
168
|
valid = test_openai_key(key_value)
|
|
156
|
-
elif key_name in [
|
|
169
|
+
elif key_name in ["GOOGLE_API_KEY"]:
|
|
157
170
|
valid = test_google_key(key_value)
|
|
158
171
|
else:
|
|
159
172
|
valid = False
|
|
160
|
-
|
|
173
|
+
|
|
161
174
|
if valid:
|
|
162
175
|
print_colored(f"{CHECK_MARK} Valid", CYAN)
|
|
163
176
|
results[key_name] = True
|
|
@@ -167,15 +180,17 @@ def test_api_keys(keys: Dict[str, Optional[str]]) -> Dict[str, bool]:
|
|
|
167
180
|
else:
|
|
168
181
|
print_colored(f"{key_name}: Not found", YELLOW)
|
|
169
182
|
results[key_name] = False
|
|
170
|
-
|
|
183
|
+
|
|
171
184
|
return results
|
|
172
185
|
|
|
186
|
+
|
|
173
187
|
def get_user_keys(current_keys: Dict[str, Optional[str]]) -> Dict[str, Optional[str]]:
|
|
174
|
-
"""Interactive key entry/modification"""
|
|
188
|
+
"""Interactive key entry/modification."""
|
|
189
|
+
|
|
175
190
|
print_colored(f"\n{create_fat_divider()}", YELLOW)
|
|
176
191
|
print_colored("API Key Configuration", YELLOW, bold=True)
|
|
177
192
|
print_colored(f"{create_fat_divider()}", YELLOW)
|
|
178
|
-
|
|
193
|
+
|
|
179
194
|
print_colored("You need only one API key to get started", WHITE)
|
|
180
195
|
print()
|
|
181
196
|
print_colored("Get API keys here:", WHITE)
|
|
@@ -184,21 +199,21 @@ def get_user_keys(current_keys: Dict[str, Optional[str]]) -> Dict[str, Optional[
|
|
|
184
199
|
print()
|
|
185
200
|
print_colored("A free instant starter key is available from Google Gemini (above)", CYAN)
|
|
186
201
|
print()
|
|
187
|
-
|
|
202
|
+
|
|
188
203
|
new_keys = current_keys.copy()
|
|
189
|
-
|
|
190
|
-
for key_name in [
|
|
204
|
+
|
|
205
|
+
for key_name in ["OPENAI_API_KEY", "GOOGLE_API_KEY"]:
|
|
191
206
|
current_value = current_keys.get(key_name, "")
|
|
192
207
|
status = "found" if current_value else "not found"
|
|
193
|
-
|
|
208
|
+
|
|
194
209
|
print_colored(f"{LIGHT_HORIZONTAL * 60}", CYAN)
|
|
195
210
|
print_colored(f"{key_name} (currently: {status})", WHITE, bold=True)
|
|
196
|
-
|
|
211
|
+
|
|
197
212
|
if current_value:
|
|
198
|
-
prompt =
|
|
213
|
+
prompt = "Enter new key or press ENTER to keep existing: "
|
|
199
214
|
else:
|
|
200
|
-
prompt =
|
|
201
|
-
|
|
215
|
+
prompt = "Enter API key (or press ENTER to skip): "
|
|
216
|
+
|
|
202
217
|
try:
|
|
203
218
|
user_input = input(f"{WHITE}{prompt}{RESET}").strip()
|
|
204
219
|
if user_input:
|
|
@@ -208,156 +223,171 @@ def get_user_keys(current_keys: Dict[str, Optional[str]]) -> Dict[str, Optional[
|
|
|
208
223
|
except KeyboardInterrupt:
|
|
209
224
|
print_colored("\n\nSetup cancelled.", YELLOW)
|
|
210
225
|
sys.exit(0)
|
|
211
|
-
|
|
226
|
+
|
|
212
227
|
return new_keys
|
|
213
228
|
|
|
229
|
+
|
|
214
230
|
def detect_shell() -> str:
|
|
215
|
-
"""Detect user's default shell"""
|
|
231
|
+
"""Detect user's default shell."""
|
|
232
|
+
|
|
216
233
|
try:
|
|
217
|
-
shell_path = os.getenv(
|
|
234
|
+
shell_path = os.getenv("SHELL", "/bin/bash")
|
|
218
235
|
shell_name = os.path.basename(shell_path)
|
|
219
236
|
return shell_name
|
|
220
|
-
except:
|
|
221
|
-
return
|
|
237
|
+
except Exception:
|
|
238
|
+
return "bash"
|
|
239
|
+
|
|
222
240
|
|
|
223
241
|
def get_shell_init_file(shell: str) -> str:
|
|
224
|
-
"""Get the appropriate shell initialization file"""
|
|
242
|
+
"""Get the appropriate shell initialization file."""
|
|
243
|
+
|
|
225
244
|
home = Path.home()
|
|
226
|
-
|
|
245
|
+
|
|
227
246
|
shell_files = {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
247
|
+
"bash": home / ".bashrc",
|
|
248
|
+
"zsh": home / ".zshrc",
|
|
249
|
+
"fish": home / ".config/fish/config.fish",
|
|
250
|
+
"csh": home / ".cshrc",
|
|
251
|
+
"tcsh": home / ".tcshrc",
|
|
252
|
+
"ksh": home / ".kshrc",
|
|
234
253
|
}
|
|
235
|
-
|
|
236
|
-
return str(shell_files.get(shell, home /
|
|
254
|
+
|
|
255
|
+
return str(shell_files.get(shell, home / ".bashrc"))
|
|
256
|
+
|
|
237
257
|
|
|
238
258
|
def create_api_env_script(keys: Dict[str, str], shell: str) -> str:
|
|
239
|
-
"""Create shell-appropriate environment script"""
|
|
259
|
+
"""Create shell-appropriate environment script."""
|
|
260
|
+
|
|
240
261
|
valid_keys = {k: v for k, v in keys.items() if v}
|
|
241
|
-
|
|
242
|
-
if shell ==
|
|
243
|
-
lines = []
|
|
244
|
-
for key, value in valid_keys.items():
|
|
245
|
-
lines.append(f'set -gx {key} "{value}"')
|
|
246
|
-
return '\n'.join(lines) + '\n'
|
|
247
|
-
elif shell in ['csh', 'tcsh']:
|
|
262
|
+
|
|
263
|
+
if shell == "fish":
|
|
248
264
|
lines = []
|
|
249
265
|
for key, value in valid_keys.items():
|
|
250
|
-
lines.append(f
|
|
251
|
-
return
|
|
252
|
-
|
|
266
|
+
lines.append(f"set -gx {key} \"{value}\"")
|
|
267
|
+
return "\n".join(lines) + "\n"
|
|
268
|
+
if shell in ["csh", "tcsh"]:
|
|
253
269
|
lines = []
|
|
254
270
|
for key, value in valid_keys.items():
|
|
255
|
-
lines.append(f
|
|
256
|
-
return
|
|
271
|
+
lines.append(f"setenv {key} \"{value}\"")
|
|
272
|
+
return "\n".join(lines) + "\n"
|
|
273
|
+
|
|
274
|
+
lines = []
|
|
275
|
+
for key, value in valid_keys.items():
|
|
276
|
+
lines.append(f"export {key}=\"{value}\"")
|
|
277
|
+
return "\n".join(lines) + "\n"
|
|
278
|
+
|
|
257
279
|
|
|
258
280
|
def save_configuration(valid_keys: Dict[str, str]) -> Tuple[List[str], bool]:
|
|
259
|
-
"""Save configuration to ~/.pdd/ directory"""
|
|
281
|
+
"""Save configuration to ~/.pdd/ directory."""
|
|
282
|
+
|
|
260
283
|
home = Path.home()
|
|
261
|
-
pdd_dir = home /
|
|
284
|
+
pdd_dir = home / ".pdd"
|
|
262
285
|
created_pdd_dir = False
|
|
263
|
-
saved_files = []
|
|
264
|
-
|
|
265
|
-
# Create .pdd directory if it doesn't exist
|
|
286
|
+
saved_files: List[str] = []
|
|
287
|
+
|
|
266
288
|
if not pdd_dir.exists():
|
|
267
289
|
pdd_dir.mkdir(mode=0o755)
|
|
268
290
|
created_pdd_dir = True
|
|
269
|
-
|
|
270
|
-
# Detect shell and create api-env script
|
|
291
|
+
|
|
271
292
|
shell = detect_shell()
|
|
272
293
|
api_env_content = create_api_env_script(valid_keys, shell)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
api_env_file = pdd_dir / 'api-env'
|
|
294
|
+
|
|
295
|
+
api_env_file = pdd_dir / "api-env"
|
|
276
296
|
api_env_file.write_text(api_env_content)
|
|
277
297
|
api_env_file.chmod(0o755)
|
|
278
298
|
saved_files.append(str(api_env_file))
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
csv_lines = LLM_MODEL_CSV_TEMPLATE.strip().split('\n')
|
|
299
|
+
|
|
300
|
+
csv_lines = LLM_MODEL_CSV_TEMPLATE.strip().split("\n")
|
|
282
301
|
header = csv_lines[0]
|
|
283
302
|
valid_lines = [header]
|
|
284
|
-
|
|
303
|
+
|
|
285
304
|
for line in csv_lines[1:]:
|
|
286
|
-
if
|
|
305
|
+
if "OPENAI_API_KEY" in line and "OPENAI_API_KEY" in valid_keys:
|
|
287
306
|
valid_lines.append(line)
|
|
288
|
-
elif
|
|
307
|
+
elif "GOOGLE_API_KEY" in line and "GOOGLE_API_KEY" in valid_keys:
|
|
289
308
|
valid_lines.append(line)
|
|
290
|
-
|
|
291
|
-
llm_model_file = pdd_dir /
|
|
292
|
-
llm_model_file.write_text(
|
|
309
|
+
|
|
310
|
+
llm_model_file = pdd_dir / "llm_model.csv"
|
|
311
|
+
llm_model_file.write_text("\n".join(valid_lines) + "\n")
|
|
293
312
|
saved_files.append(str(llm_model_file))
|
|
294
|
-
|
|
295
|
-
# Update shell init file
|
|
313
|
+
|
|
296
314
|
init_file_path = get_shell_init_file(shell)
|
|
297
315
|
init_file = Path(init_file_path)
|
|
298
|
-
|
|
316
|
+
|
|
299
317
|
source_line = f'[ -f "{api_env_file}" ] && source "{api_env_file}"'
|
|
300
|
-
if shell ==
|
|
318
|
+
if shell == "fish":
|
|
301
319
|
source_line = f'test -f "{api_env_file}"; and source "{api_env_file}"'
|
|
302
|
-
|
|
303
|
-
# Check if source line already exists
|
|
320
|
+
|
|
304
321
|
if init_file.exists():
|
|
305
322
|
content = init_file.read_text()
|
|
306
323
|
if str(api_env_file) not in content:
|
|
307
|
-
with init_file.open(
|
|
308
|
-
|
|
324
|
+
with init_file.open("a", encoding="utf-8") as file_handle:
|
|
325
|
+
file_handle.write(f"\n# PDD API environment\n{source_line}\n")
|
|
309
326
|
else:
|
|
310
|
-
init_file.write_text(f
|
|
311
|
-
|
|
327
|
+
init_file.write_text(f"# PDD API environment\n{source_line}\n")
|
|
328
|
+
|
|
312
329
|
return saved_files, created_pdd_dir
|
|
313
330
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
331
|
+
|
|
332
|
+
def create_sample_prompt() -> str:
|
|
333
|
+
"""Create the sample prompt file."""
|
|
334
|
+
|
|
335
|
+
prompt_file = Path("hello_you_python.prompt")
|
|
317
336
|
prompt_file.write_text(HELLO_PYTHON_TEMPLATE)
|
|
318
337
|
return str(prompt_file)
|
|
319
338
|
|
|
339
|
+
|
|
320
340
|
def show_menu(keys: Dict[str, Optional[str]], test_results: Dict[str, bool]) -> str:
|
|
321
|
-
"""Show main menu and get user choice"""
|
|
341
|
+
"""Show main menu and get user choice."""
|
|
342
|
+
|
|
322
343
|
print_colored(f"\n{create_divider()}", CYAN)
|
|
323
344
|
print_colored("Main Menu", CYAN, bold=True)
|
|
324
345
|
print_colored(f"{create_divider()}", CYAN)
|
|
325
|
-
|
|
326
|
-
# Show current status
|
|
346
|
+
|
|
327
347
|
print_colored("Current API Key Status:", WHITE, bold=True)
|
|
328
|
-
for key_name in [
|
|
348
|
+
for key_name in ["OPENAI_API_KEY", "GOOGLE_API_KEY"]:
|
|
329
349
|
key_value = keys.get(key_name)
|
|
330
350
|
if key_value:
|
|
331
|
-
status =
|
|
351
|
+
status = (
|
|
352
|
+
f"{CHECK_MARK} Valid"
|
|
353
|
+
if test_results.get(key_name)
|
|
354
|
+
else f"{CROSS_MARK} Invalid"
|
|
355
|
+
)
|
|
332
356
|
status_color = CYAN if test_results.get(key_name) else YELLOW
|
|
333
357
|
else:
|
|
334
358
|
status = "Not configured"
|
|
335
359
|
status_color = YELLOW
|
|
336
|
-
|
|
360
|
+
|
|
337
361
|
print(f" {key_name}: ", end="")
|
|
338
362
|
print_colored(status, status_color)
|
|
339
|
-
|
|
363
|
+
|
|
340
364
|
print()
|
|
341
365
|
print_colored("Options:", WHITE, bold=True)
|
|
342
|
-
print(
|
|
343
|
-
print(
|
|
344
|
-
print(
|
|
345
|
-
print(
|
|
366
|
+
print(" 1. Re-enter API keys")
|
|
367
|
+
print(" 2. Re-test current keys")
|
|
368
|
+
print(" 3. Save configuration and exit")
|
|
369
|
+
print(" 4. Exit without saving")
|
|
346
370
|
print()
|
|
347
|
-
|
|
371
|
+
|
|
348
372
|
while True:
|
|
349
373
|
try:
|
|
350
374
|
choice = input(f"{WHITE}Choose an option (1-4): {RESET}").strip()
|
|
351
|
-
if choice in [
|
|
375
|
+
if choice in ["1", "2", "3", "4"]:
|
|
352
376
|
return choice
|
|
353
|
-
|
|
354
|
-
print_colored("Please enter 1, 2, 3, or 4", YELLOW)
|
|
377
|
+
print_colored("Please enter 1, 2, 3, or 4", YELLOW)
|
|
355
378
|
except KeyboardInterrupt:
|
|
356
379
|
print_colored("\n\nSetup cancelled.", YELLOW)
|
|
357
380
|
sys.exit(0)
|
|
358
381
|
|
|
359
|
-
|
|
360
|
-
|
|
382
|
+
|
|
383
|
+
def create_exit_summary(
|
|
384
|
+
saved_files: List[str],
|
|
385
|
+
created_pdd_dir: bool,
|
|
386
|
+
sample_prompt_file: str,
|
|
387
|
+
shell: str,
|
|
388
|
+
) -> str:
|
|
389
|
+
"""Create comprehensive exit summary."""
|
|
390
|
+
|
|
361
391
|
summary_lines = [
|
|
362
392
|
"\n\n\n\n\n",
|
|
363
393
|
create_fat_divider(),
|
|
@@ -365,158 +395,153 @@ def create_exit_summary(saved_files: List[str], created_pdd_dir: bool, sample_pr
|
|
|
365
395
|
create_fat_divider(),
|
|
366
396
|
"",
|
|
367
397
|
"Files created and configured:",
|
|
368
|
-
""
|
|
398
|
+
"",
|
|
369
399
|
]
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
file_descriptions = []
|
|
400
|
+
|
|
401
|
+
file_descriptions: List[Tuple[str, str]] = []
|
|
373
402
|
if created_pdd_dir:
|
|
374
403
|
file_descriptions.append(("~/.pdd/", "PDD configuration directory"))
|
|
375
|
-
|
|
404
|
+
|
|
376
405
|
for file_path in saved_files:
|
|
377
|
-
if
|
|
406
|
+
if "api-env" in file_path:
|
|
378
407
|
file_descriptions.append((file_path, "API environment variables"))
|
|
379
|
-
elif
|
|
408
|
+
elif "llm_model.csv" in file_path:
|
|
380
409
|
file_descriptions.append((file_path, "LLM model configuration"))
|
|
381
|
-
|
|
410
|
+
|
|
382
411
|
file_descriptions.append((sample_prompt_file, "Sample prompt for testing"))
|
|
383
412
|
file_descriptions.append(("PDD-SETUP-SUMMARY.txt", "This summary"))
|
|
384
|
-
|
|
385
|
-
# Find max file path length for alignment
|
|
413
|
+
|
|
386
414
|
max_path_len = max(len(path) for path, _ in file_descriptions)
|
|
387
|
-
|
|
415
|
+
|
|
388
416
|
for file_path, description in file_descriptions:
|
|
389
417
|
summary_lines.append(f"{file_path:<{max_path_len + 2}}{description}")
|
|
390
|
-
|
|
391
|
-
summary_lines.extend(
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
418
|
+
|
|
419
|
+
summary_lines.extend(
|
|
420
|
+
[
|
|
421
|
+
"",
|
|
422
|
+
create_divider(),
|
|
423
|
+
"",
|
|
424
|
+
"QUICK START:",
|
|
425
|
+
"",
|
|
426
|
+
"1. Reload your shell environment:",
|
|
427
|
+
]
|
|
428
|
+
)
|
|
429
|
+
|
|
401
430
|
api_env_path = f"{Path.home()}/.pdd/api-env"
|
|
402
|
-
if shell ==
|
|
431
|
+
if shell == "fish":
|
|
403
432
|
source_cmd = f"source {api_env_path}"
|
|
404
433
|
else:
|
|
405
434
|
source_cmd = f"source {api_env_path}"
|
|
406
|
-
|
|
407
|
-
summary_lines.extend(
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
435
|
+
|
|
436
|
+
summary_lines.extend(
|
|
437
|
+
[
|
|
438
|
+
f" {source_cmd}",
|
|
439
|
+
"",
|
|
440
|
+
"2. Generate code from the sample prompt:",
|
|
441
|
+
" pdd generate hello_you_python.prompt",
|
|
442
|
+
"",
|
|
443
|
+
create_divider(),
|
|
444
|
+
"",
|
|
445
|
+
"LEARN MORE:",
|
|
446
|
+
"",
|
|
447
|
+
f"{BULLET} PDD documentation: pdd --help",
|
|
448
|
+
f"{BULLET} PDD website: https://promptdriven.ai/",
|
|
449
|
+
f"{BULLET} Discord community: https://discord.gg/Yp4RTh8bG7",
|
|
450
|
+
"",
|
|
451
|
+
"TIPS:",
|
|
452
|
+
"",
|
|
453
|
+
f"{BULLET} IMPORTANT: Reload your shell environment using the source command above",
|
|
454
|
+
"",
|
|
455
|
+
f"{BULLET} Start with simple prompts and gradually increase complexity",
|
|
456
|
+
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",
|
|
457
|
+
f"{BULLET} Try out 'pdd example' with your prompt+code to create examples which help pdd do better",
|
|
458
|
+
"",
|
|
459
|
+
f"{BULLET} As you get comfortable, learn configuration settings, including the .pddrc file, PDD_GENERATE_OUTPUT_PATH, and PDD_TEST_OUTPUT_PATH",
|
|
460
|
+
f"{BULLET} For larger projects, use Makefiles and/or 'pdd sync'",
|
|
461
|
+
f"{BULLET} For ongoing substantial projects, learn about llm_model.csv to optimize model cost, latency, and output quality",
|
|
462
|
+
"",
|
|
463
|
+
f"{BULLET} Use 'pdd --help' to explore all available commands",
|
|
464
|
+
"",
|
|
465
|
+
"Problems? Shout out on our Discord for help! https://discord.gg/Yp4RTh8bG7",
|
|
466
|
+
]
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
return "\n".join(summary_lines)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def main() -> None:
|
|
473
|
+
"""Main setup workflow."""
|
|
474
|
+
|
|
443
475
|
print_pdd_logo()
|
|
444
|
-
|
|
445
|
-
# Discover environment
|
|
476
|
+
|
|
446
477
|
print_colored(f"{create_divider()}", CYAN)
|
|
447
478
|
print_colored("Discovering local configuration...", CYAN, bold=True)
|
|
448
479
|
print_colored(f"{create_divider()}", CYAN)
|
|
449
|
-
|
|
480
|
+
|
|
450
481
|
keys = discover_api_keys()
|
|
451
|
-
|
|
452
|
-
# Test discovered keys
|
|
453
482
|
test_results = test_api_keys(keys)
|
|
454
|
-
|
|
455
|
-
# Main interaction loop
|
|
483
|
+
|
|
456
484
|
while True:
|
|
457
485
|
choice = show_menu(keys, test_results)
|
|
458
|
-
|
|
459
|
-
if choice ==
|
|
460
|
-
# Re-enter keys
|
|
486
|
+
|
|
487
|
+
if choice == "1":
|
|
461
488
|
keys = get_user_keys(keys)
|
|
462
489
|
test_results = test_api_keys(keys)
|
|
463
|
-
|
|
464
|
-
elif choice == '2':
|
|
465
|
-
# Re-test keys
|
|
490
|
+
elif choice == "2":
|
|
466
491
|
test_results = test_api_keys(keys)
|
|
467
|
-
|
|
468
|
-
elif choice == '3':
|
|
469
|
-
# Save and exit
|
|
492
|
+
elif choice == "3":
|
|
470
493
|
valid_keys = {k: v for k, v in keys.items() if v and test_results.get(k)}
|
|
471
|
-
|
|
494
|
+
|
|
472
495
|
if not valid_keys:
|
|
473
496
|
print_colored("\nNo valid API keys to save!", YELLOW)
|
|
474
497
|
continue
|
|
475
|
-
|
|
498
|
+
|
|
476
499
|
print_colored(f"\n{create_divider()}", CYAN)
|
|
477
500
|
print_colored("Saving configuration...", CYAN, bold=True)
|
|
478
501
|
print_colored(f"{create_divider()}", CYAN)
|
|
479
|
-
|
|
502
|
+
|
|
480
503
|
try:
|
|
481
504
|
saved_files, created_pdd_dir = save_configuration(valid_keys)
|
|
482
505
|
sample_prompt_file = create_sample_prompt()
|
|
483
506
|
shell = detect_shell()
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
507
|
+
|
|
508
|
+
summary = create_exit_summary(
|
|
509
|
+
saved_files,
|
|
510
|
+
created_pdd_dir,
|
|
511
|
+
sample_prompt_file,
|
|
512
|
+
shell,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
summary_file = Path("PDD-SETUP-SUMMARY.txt")
|
|
490
516
|
summary_file.write_text(summary)
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
lines = summary.split('\n')
|
|
494
|
-
for line in lines:
|
|
517
|
+
|
|
518
|
+
for line in summary.split("\n"):
|
|
495
519
|
if line == create_fat_divider():
|
|
496
520
|
print_colored(line, YELLOW, bold=True)
|
|
497
521
|
elif line == "PDD Setup Complete!":
|
|
498
522
|
print_colored(line, YELLOW, bold=True)
|
|
499
523
|
elif line == create_divider():
|
|
500
524
|
print_colored(line, CYAN)
|
|
501
|
-
elif
|
|
525
|
+
elif any(
|
|
526
|
+
line.startswith(prefix)
|
|
527
|
+
for prefix in ["QUICK START:", "LEARN MORE:", "TIPS:"]
|
|
528
|
+
):
|
|
502
529
|
print_colored(line, CYAN, bold=True)
|
|
503
530
|
elif "IMPORTANT:" in line or "Problems?" in line:
|
|
504
531
|
print_colored(line, YELLOW, bold=True)
|
|
505
532
|
else:
|
|
506
533
|
print(line)
|
|
507
|
-
|
|
534
|
+
|
|
508
535
|
break
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
print_colored(f"Error saving configuration: {e}", YELLOW)
|
|
536
|
+
except Exception as exc: # noqa: BLE001
|
|
537
|
+
print_colored(f"Error saving configuration: {exc}", YELLOW)
|
|
512
538
|
continue
|
|
513
|
-
|
|
514
|
-
elif choice == '4':
|
|
515
|
-
# Exit without saving
|
|
539
|
+
elif choice == "4":
|
|
516
540
|
print_colored("\nExiting without saving configuration.", YELLOW)
|
|
517
541
|
break
|
|
518
542
|
|
|
519
|
-
|
|
543
|
+
|
|
544
|
+
if __name__ == "__main__":
|
|
520
545
|
try:
|
|
521
546
|
main()
|
|
522
547
|
except KeyboardInterrupt:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pdd-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.59
|
|
4
4
|
Summary: PDD (Prompt-Driven Development) Command Line Interface
|
|
5
5
|
Author: Greg Tanaka
|
|
6
6
|
Author-email: glt@alumni.caltech.edu
|
|
@@ -44,6 +44,8 @@ Requires-Dist: openai>=1.99.5
|
|
|
44
44
|
Provides-Extra: dev
|
|
45
45
|
Requires-Dist: commitizen; extra == "dev"
|
|
46
46
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
47
|
+
Requires-Dist: pytest-testmon; extra == "dev"
|
|
48
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
|
47
49
|
Requires-Dist: pytest-mock; extra == "dev"
|
|
48
50
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
49
51
|
Requires-Dist: z3-solver; extra == "dev"
|
|
@@ -51,7 +53,7 @@ Requires-Dist: build; extra == "dev"
|
|
|
51
53
|
Requires-Dist: twine; extra == "dev"
|
|
52
54
|
Dynamic: license-file
|
|
53
55
|
|
|
54
|
-
.. image:: https://img.shields.io/badge/pdd--cli-v0.0.
|
|
56
|
+
.. image:: https://img.shields.io/badge/pdd--cli-v0.0.59-blue
|
|
55
57
|
:alt: PDD-CLI Version
|
|
56
58
|
|
|
57
59
|
.. image:: https://img.shields.io/badge/Discord-join%20chat-7289DA.svg?logo=discord&logoColor=white&link=https://discord.gg/Yp4RTh8bG7
|
|
@@ -128,7 +130,7 @@ After installation, verify:
|
|
|
128
130
|
|
|
129
131
|
pdd --version
|
|
130
132
|
|
|
131
|
-
You'll see the current PDD version (e.g., 0.0.
|
|
133
|
+
You'll see the current PDD version (e.g., 0.0.59).
|
|
132
134
|
|
|
133
135
|
Getting Started with Examples
|
|
134
136
|
-----------------------------
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
pdd/__init__.py,sha256=
|
|
1
|
+
pdd/__init__.py,sha256=6ca93PkB5BfPH4LcpAo6xz5hCmdLn8B0P6cgmY8IqyY,633
|
|
2
2
|
pdd/auto_deps_main.py,sha256=cpP3bbzVL3jomrGinpzTxzIDIC8tmDDYOwUAC1TKRaw,3970
|
|
3
3
|
pdd/auto_include.py,sha256=OJcdcwTwJNqHPHKG9P4m9Ij-PiLex0EbuwJP0uiQi_Y,7484
|
|
4
4
|
pdd/auto_update.py,sha256=w6jzTnMiYRNpwQHQxWNiIAwQ0d6xh1iOB3xgDsabWtc,5236
|
|
@@ -6,7 +6,7 @@ pdd/bug_main.py,sha256=EtaGTuucQ7VgqOhyg4o6GFG7_QtTsDPTrRdGJWT648M,4841
|
|
|
6
6
|
pdd/bug_to_unit_test.py,sha256=BoQqNyKQpBQDW8-JwBH_RX4RHRSiU8Kk3EplFrkECt0,6665
|
|
7
7
|
pdd/change.py,sha256=Hg_x0pa370-e6oDiczaTgFAy3Am9ReCPkqFrvqv4U38,6114
|
|
8
8
|
pdd/change_main.py,sha256=04VHiO_D-jlfeRn6rrVH7ZTA5agXPoJGm1StGI8--XY,27804
|
|
9
|
-
pdd/cli.py,sha256=
|
|
9
|
+
pdd/cli.py,sha256=c5Gco_Ra1ZCZf1MtxrNZdySDhZqICHBQMSH5VBqVPEw,55162
|
|
10
10
|
pdd/cmd_test_main.py,sha256=M-i5x26ORXurt_pu8x1sgLAyVIItbuRThiux4wBg3Ls,7768
|
|
11
11
|
pdd/code_generator.py,sha256=AxMRZKGIlLh9xWdn2FA6b3zSoZ-5TIZNIAzqjFboAQs,4718
|
|
12
12
|
pdd/code_generator_main.py,sha256=UtoskalEPpMAvCO-zd6xmr1lbQqSWQ7BvYgNJCybqok,35151
|
|
@@ -55,6 +55,7 @@ pdd/preprocess_main.py,sha256=WGhOB9qEu7MmFoyXNml_AmqGii73LJWngx4kTlZ526k,3262
|
|
|
55
55
|
pdd/process_csv_change.py,sha256=ckNqVPRooWVyIvmqjdEgo2PDLnpoQ6Taa2dUaWGRlzU,27926
|
|
56
56
|
pdd/pytest_output.py,sha256=IrRKYneW_F6zv9WaJwKFGnOBLFBFjk1CnhO_EVAjb9E,6612
|
|
57
57
|
pdd/python_env_detector.py,sha256=y-QESoPNiKaD821uz8okX-9qA-oqvH9cQHY2_MwFHzU,5194
|
|
58
|
+
pdd/setup_tool.py,sha256=sksHVgSLSzlMVSX-WoAZwLRqQhKYPhuCBoq6XbOLhTI,18171
|
|
58
59
|
pdd/split.py,sha256=9lWrh-JOjOpxRp4-s1VL7bqJMVWlsmY5LxONT7sYM8A,5288
|
|
59
60
|
pdd/split_main.py,sha256=52rcZoeS_wpYRiqbqMUgr_hUY7GS62otwzDfuAGi6YA,4845
|
|
60
61
|
pdd/summarize_directory.py,sha256=BR3-yGcmUdDT26vWLGokBo6mAZzaT7PzoY_qZriH3cc,10061
|
|
@@ -110,10 +111,9 @@ pdd/prompts/unfinished_prompt_LLM.prompt,sha256=vud_G9PlVv9Ig64uBC-hPEVFRk5lwpc8
|
|
|
110
111
|
pdd/prompts/update_prompt_LLM.prompt,sha256=prIc8uLp2jqnLTHt6JvWDZGanPZipivhhYeXe0lVaYw,1328
|
|
111
112
|
pdd/prompts/xml_convertor_LLM.prompt,sha256=YGRGXJeg6EhM9690f-SKqQrKqSJjLFD51UrPOlO0Frg,2786
|
|
112
113
|
pdd/templates/architecture/architecture_json.prompt,sha256=uSNSsKTL-cuMMhi5a4GSpC94DKkOFAlXh7R0CUlo-hg,8126
|
|
113
|
-
pdd_cli-0.0.
|
|
114
|
-
pdd_cli-0.0.
|
|
115
|
-
pdd_cli-0.0.
|
|
116
|
-
pdd_cli-0.0.
|
|
117
|
-
pdd_cli-0.0.
|
|
118
|
-
pdd_cli-0.0.
|
|
119
|
-
pdd_cli-0.0.58.dist-info/RECORD,,
|
|
114
|
+
pdd_cli-0.0.59.dist-info/licenses/LICENSE,sha256=kvTJnnxPVTYlGKSY4ZN1kzdmJ0lxRdNWxgupaB27zsU,1066
|
|
115
|
+
pdd_cli-0.0.59.dist-info/METADATA,sha256=wiXNwvDbviSCgnEaJwSbfWDv-BgkaZEjm3lpfHudbcM,12687
|
|
116
|
+
pdd_cli-0.0.59.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
117
|
+
pdd_cli-0.0.59.dist-info/entry_points.txt,sha256=Kr8HtNVb8uHZtQJNH4DnF8j7WNgWQbb7_Pw5hECSR-I,36
|
|
118
|
+
pdd_cli-0.0.59.dist-info/top_level.txt,sha256=xjnhIACeMcMeDfVNREgQZl4EbTni2T11QkL5r7E-sbE,4
|
|
119
|
+
pdd_cli-0.0.59.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|