pdd-cli 0.0.57__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/setup_tool.py +549 -0
- {pdd_cli-0.0.57.dist-info → pdd_cli-0.0.59.dist-info}/METADATA +5 -3
- {pdd_cli-0.0.57.dist-info → pdd_cli-0.0.59.dist-info}/RECORD +9 -8
- {pdd_cli-0.0.57.dist-info → pdd_cli-0.0.59.dist-info}/WHEEL +0 -0
- {pdd_cli-0.0.57.dist-info → pdd_cli-0.0.59.dist-info}/entry_points.txt +0 -0
- {pdd_cli-0.0.57.dist-info → pdd_cli-0.0.59.dist-info}/licenses/LICENSE +0 -0
- {pdd_cli-0.0.57.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
|
|
pdd/setup_tool.py
ADDED
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
"""Interactive post-install setup utility for PDD."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, List, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# Global variables for non-ASCII characters and colors
|
|
12
|
+
HEAVY_HORIZONTAL = "━"
|
|
13
|
+
LIGHT_HORIZONTAL = "─"
|
|
14
|
+
HEAVY_VERTICAL = "┃"
|
|
15
|
+
LIGHT_VERTICAL = "│"
|
|
16
|
+
TOP_LEFT_CORNER = "┏"
|
|
17
|
+
TOP_RIGHT_CORNER = "┓"
|
|
18
|
+
BOTTOM_LEFT_CORNER = "┗"
|
|
19
|
+
BOTTOM_RIGHT_CORNER = "┛"
|
|
20
|
+
CROSS = "┼"
|
|
21
|
+
TEE_DOWN = "┬"
|
|
22
|
+
TEE_UP = "┴"
|
|
23
|
+
TEE_RIGHT = "├"
|
|
24
|
+
TEE_LEFT = "┤"
|
|
25
|
+
BULLET = "•"
|
|
26
|
+
ARROW_RIGHT = "→"
|
|
27
|
+
CHECK_MARK = "✓"
|
|
28
|
+
CROSS_MARK = "✗"
|
|
29
|
+
|
|
30
|
+
# Color codes
|
|
31
|
+
RESET = "\033[0m"
|
|
32
|
+
WHITE = "\033[97m"
|
|
33
|
+
CYAN = "\033[96m"
|
|
34
|
+
YELLOW = "\033[93m"
|
|
35
|
+
BOLD = "\033[1m"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Template content inline
|
|
39
|
+
HELLO_PYTHON_TEMPLATE = """Create a Python program that prints "Hello <username>" in ASCII art.
|
|
40
|
+
|
|
41
|
+
Requirements:
|
|
42
|
+
- <username> is the username of the current session, using the "whoami" command
|
|
43
|
+
- in the code, generate the full english 26 character alphabet in ascii art as a map of character values to ascii art strings, then use those to render
|
|
44
|
+
- Use only the Python standard library (no external dependencies)
|
|
45
|
+
- Create large, bold ASCII art text, using ASCII drawing characters, symbols, or any other characters that are useful
|
|
46
|
+
- The drawn text should be at least 10 rows in height
|
|
47
|
+
- Make it visually appealing with simple characters like #, *, or =
|
|
48
|
+
- Keep the code clean and readable
|
|
49
|
+
- Add a brief comment explaining what the program does
|
|
50
|
+
|
|
51
|
+
The program should be self-contained and runnable with just `python3 filename.py`."""
|
|
52
|
+
|
|
53
|
+
LLM_MODEL_CSV_TEMPLATE = """provider,model,input,output,coding_arena_elo,base_url,api_key,max_reasoning_tokens,structured_output,reasoning_type
|
|
54
|
+
OpenAI,gpt-5-nano,0.05,0.4,1249,,OPENAI_API_KEY,0,True,none
|
|
55
|
+
Google,gemini/gemini-2.5-pro,1.25,10.0,1360,,GOOGLE_API_KEY,0,True,none
|
|
56
|
+
OpenAI,gpt-5-mini,0.25,2.0,1325,,OPENAI_API_KEY,0,True,effort
|
|
57
|
+
OpenAI,gpt-5,1.25,10.0,1482,,OPENAI_API_KEY,0,True,effort
|
|
58
|
+
OpenAI,gpt-4.1,2.0,8.0,1253,,OPENAI_API_KEY,0,True,none"""
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def print_colored(text: str, color: str = WHITE, bold: bool = False) -> None:
|
|
62
|
+
"""Print colored text to console."""
|
|
63
|
+
|
|
64
|
+
style = BOLD + color if bold else color
|
|
65
|
+
print(f"{style}{text}{RESET}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def create_divider(char: str = LIGHT_HORIZONTAL, width: int = 80) -> str:
|
|
69
|
+
"""Create a horizontal divider line."""
|
|
70
|
+
|
|
71
|
+
return char * width
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def create_fat_divider(width: int = 80) -> str:
|
|
75
|
+
"""Create a fat horizontal divider line."""
|
|
76
|
+
|
|
77
|
+
return HEAVY_HORIZONTAL * width
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def print_pdd_logo() -> None:
|
|
81
|
+
"""Print the PDD logo in ASCII art."""
|
|
82
|
+
|
|
83
|
+
logo = "\n".join(
|
|
84
|
+
[
|
|
85
|
+
" +xxxxxxxxxxxxxxx+",
|
|
86
|
+
"xxxxxxxxxxxxxxxxxxxxx+",
|
|
87
|
+
"xxx +xx+ PROMPT",
|
|
88
|
+
"xxx x+ xx+ DRIVEN",
|
|
89
|
+
"xxx x+ xxx DEVELOPMENT©",
|
|
90
|
+
"xxx x+ xx+",
|
|
91
|
+
"xxx x+ xx+ COMMAND LINE INTERFACE",
|
|
92
|
+
"xxx x+ xxx",
|
|
93
|
+
"xxx +xx+ ",
|
|
94
|
+
"xxx +xxxxxxxxxxx+",
|
|
95
|
+
"xxx +xx+",
|
|
96
|
+
"xxx +xx+",
|
|
97
|
+
"xxx+xx+ WWW.PROMPTDRIVEN.AI",
|
|
98
|
+
"xxxx+",
|
|
99
|
+
"xx+",
|
|
100
|
+
]
|
|
101
|
+
)
|
|
102
|
+
print(f"{CYAN}{logo}{RESET}")
|
|
103
|
+
print_colored("Supported: OpenAI and Google Gemini (non-Vertex)", WHITE)
|
|
104
|
+
print_colored("from their respective API endpoints (no third-parties, such as Azure)", WHITE)
|
|
105
|
+
print()
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def discover_api_keys() -> Dict[str, Optional[str]]:
|
|
109
|
+
"""Discover API keys from environment variables."""
|
|
110
|
+
|
|
111
|
+
keys = {
|
|
112
|
+
"OPENAI_API_KEY": os.getenv("OPENAI_API_KEY"),
|
|
113
|
+
"GOOGLE_API_KEY": os.getenv("GOOGLE_API_KEY") or os.getenv("GEMINI_API_KEY"),
|
|
114
|
+
}
|
|
115
|
+
return keys
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_openai_key(api_key: str) -> bool:
|
|
119
|
+
"""Test OpenAI API key validity."""
|
|
120
|
+
|
|
121
|
+
if not api_key or not api_key.strip():
|
|
122
|
+
return False
|
|
123
|
+
|
|
124
|
+
try:
|
|
125
|
+
headers = {
|
|
126
|
+
"Authorization": f"Bearer {api_key.strip()}",
|
|
127
|
+
"Content-Type": "application/json",
|
|
128
|
+
}
|
|
129
|
+
response = requests.get(
|
|
130
|
+
"https://api.openai.com/v1/models",
|
|
131
|
+
headers=headers,
|
|
132
|
+
timeout=10,
|
|
133
|
+
)
|
|
134
|
+
return response.status_code == 200
|
|
135
|
+
except Exception:
|
|
136
|
+
return False
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def test_google_key(api_key: str) -> bool:
|
|
140
|
+
"""Test Google Gemini API key validity."""
|
|
141
|
+
|
|
142
|
+
if not api_key or not api_key.strip():
|
|
143
|
+
return False
|
|
144
|
+
|
|
145
|
+
try:
|
|
146
|
+
response = requests.get(
|
|
147
|
+
f"https://generativelanguage.googleapis.com/v1beta/models?key={api_key.strip()}",
|
|
148
|
+
timeout=10,
|
|
149
|
+
)
|
|
150
|
+
return response.status_code == 200
|
|
151
|
+
except Exception:
|
|
152
|
+
return False
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_api_keys(keys: Dict[str, Optional[str]]) -> Dict[str, bool]:
|
|
156
|
+
"""Test all discovered API keys."""
|
|
157
|
+
|
|
158
|
+
results: Dict[str, bool] = {}
|
|
159
|
+
|
|
160
|
+
print_colored(f"\n{LIGHT_HORIZONTAL * 40}", CYAN)
|
|
161
|
+
print_colored("Testing discovered API keys...", CYAN, bold=True)
|
|
162
|
+
print_colored(f"{LIGHT_HORIZONTAL * 40}", CYAN)
|
|
163
|
+
|
|
164
|
+
for key_name, key_value in keys.items():
|
|
165
|
+
if key_value:
|
|
166
|
+
print(f"Testing {key_name}...", end=" ", flush=True)
|
|
167
|
+
if key_name == "OPENAI_API_KEY":
|
|
168
|
+
valid = test_openai_key(key_value)
|
|
169
|
+
elif key_name in ["GOOGLE_API_KEY"]:
|
|
170
|
+
valid = test_google_key(key_value)
|
|
171
|
+
else:
|
|
172
|
+
valid = False
|
|
173
|
+
|
|
174
|
+
if valid:
|
|
175
|
+
print_colored(f"{CHECK_MARK} Valid", CYAN)
|
|
176
|
+
results[key_name] = True
|
|
177
|
+
else:
|
|
178
|
+
print_colored(f"{CROSS_MARK} Invalid", YELLOW)
|
|
179
|
+
results[key_name] = False
|
|
180
|
+
else:
|
|
181
|
+
print_colored(f"{key_name}: Not found", YELLOW)
|
|
182
|
+
results[key_name] = False
|
|
183
|
+
|
|
184
|
+
return results
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def get_user_keys(current_keys: Dict[str, Optional[str]]) -> Dict[str, Optional[str]]:
|
|
188
|
+
"""Interactive key entry/modification."""
|
|
189
|
+
|
|
190
|
+
print_colored(f"\n{create_fat_divider()}", YELLOW)
|
|
191
|
+
print_colored("API Key Configuration", YELLOW, bold=True)
|
|
192
|
+
print_colored(f"{create_fat_divider()}", YELLOW)
|
|
193
|
+
|
|
194
|
+
print_colored("You need only one API key to get started", WHITE)
|
|
195
|
+
print()
|
|
196
|
+
print_colored("Get API keys here:", WHITE)
|
|
197
|
+
print_colored(f" OpenAI {ARROW_RIGHT} https://platform.openai.com/api-keys", CYAN)
|
|
198
|
+
print_colored(f" Google Gemini {ARROW_RIGHT} https://aistudio.google.com/app/apikey", CYAN)
|
|
199
|
+
print()
|
|
200
|
+
print_colored("A free instant starter key is available from Google Gemini (above)", CYAN)
|
|
201
|
+
print()
|
|
202
|
+
|
|
203
|
+
new_keys = current_keys.copy()
|
|
204
|
+
|
|
205
|
+
for key_name in ["OPENAI_API_KEY", "GOOGLE_API_KEY"]:
|
|
206
|
+
current_value = current_keys.get(key_name, "")
|
|
207
|
+
status = "found" if current_value else "not found"
|
|
208
|
+
|
|
209
|
+
print_colored(f"{LIGHT_HORIZONTAL * 60}", CYAN)
|
|
210
|
+
print_colored(f"{key_name} (currently: {status})", WHITE, bold=True)
|
|
211
|
+
|
|
212
|
+
if current_value:
|
|
213
|
+
prompt = "Enter new key or press ENTER to keep existing: "
|
|
214
|
+
else:
|
|
215
|
+
prompt = "Enter API key (or press ENTER to skip): "
|
|
216
|
+
|
|
217
|
+
try:
|
|
218
|
+
user_input = input(f"{WHITE}{prompt}{RESET}").strip()
|
|
219
|
+
if user_input:
|
|
220
|
+
new_keys[key_name] = user_input
|
|
221
|
+
elif not current_value:
|
|
222
|
+
new_keys[key_name] = None
|
|
223
|
+
except KeyboardInterrupt:
|
|
224
|
+
print_colored("\n\nSetup cancelled.", YELLOW)
|
|
225
|
+
sys.exit(0)
|
|
226
|
+
|
|
227
|
+
return new_keys
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def detect_shell() -> str:
|
|
231
|
+
"""Detect user's default shell."""
|
|
232
|
+
|
|
233
|
+
try:
|
|
234
|
+
shell_path = os.getenv("SHELL", "/bin/bash")
|
|
235
|
+
shell_name = os.path.basename(shell_path)
|
|
236
|
+
return shell_name
|
|
237
|
+
except Exception:
|
|
238
|
+
return "bash"
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def get_shell_init_file(shell: str) -> str:
|
|
242
|
+
"""Get the appropriate shell initialization file."""
|
|
243
|
+
|
|
244
|
+
home = Path.home()
|
|
245
|
+
|
|
246
|
+
shell_files = {
|
|
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",
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return str(shell_files.get(shell, home / ".bashrc"))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def create_api_env_script(keys: Dict[str, str], shell: str) -> str:
|
|
259
|
+
"""Create shell-appropriate environment script."""
|
|
260
|
+
|
|
261
|
+
valid_keys = {k: v for k, v in keys.items() if v}
|
|
262
|
+
|
|
263
|
+
if shell == "fish":
|
|
264
|
+
lines = []
|
|
265
|
+
for key, value in valid_keys.items():
|
|
266
|
+
lines.append(f"set -gx {key} \"{value}\"")
|
|
267
|
+
return "\n".join(lines) + "\n"
|
|
268
|
+
if shell in ["csh", "tcsh"]:
|
|
269
|
+
lines = []
|
|
270
|
+
for key, value in valid_keys.items():
|
|
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
|
+
|
|
279
|
+
|
|
280
|
+
def save_configuration(valid_keys: Dict[str, str]) -> Tuple[List[str], bool]:
|
|
281
|
+
"""Save configuration to ~/.pdd/ directory."""
|
|
282
|
+
|
|
283
|
+
home = Path.home()
|
|
284
|
+
pdd_dir = home / ".pdd"
|
|
285
|
+
created_pdd_dir = False
|
|
286
|
+
saved_files: List[str] = []
|
|
287
|
+
|
|
288
|
+
if not pdd_dir.exists():
|
|
289
|
+
pdd_dir.mkdir(mode=0o755)
|
|
290
|
+
created_pdd_dir = True
|
|
291
|
+
|
|
292
|
+
shell = detect_shell()
|
|
293
|
+
api_env_content = create_api_env_script(valid_keys, shell)
|
|
294
|
+
|
|
295
|
+
api_env_file = pdd_dir / "api-env"
|
|
296
|
+
api_env_file.write_text(api_env_content)
|
|
297
|
+
api_env_file.chmod(0o755)
|
|
298
|
+
saved_files.append(str(api_env_file))
|
|
299
|
+
|
|
300
|
+
csv_lines = LLM_MODEL_CSV_TEMPLATE.strip().split("\n")
|
|
301
|
+
header = csv_lines[0]
|
|
302
|
+
valid_lines = [header]
|
|
303
|
+
|
|
304
|
+
for line in csv_lines[1:]:
|
|
305
|
+
if "OPENAI_API_KEY" in line and "OPENAI_API_KEY" in valid_keys:
|
|
306
|
+
valid_lines.append(line)
|
|
307
|
+
elif "GOOGLE_API_KEY" in line and "GOOGLE_API_KEY" in valid_keys:
|
|
308
|
+
valid_lines.append(line)
|
|
309
|
+
|
|
310
|
+
llm_model_file = pdd_dir / "llm_model.csv"
|
|
311
|
+
llm_model_file.write_text("\n".join(valid_lines) + "\n")
|
|
312
|
+
saved_files.append(str(llm_model_file))
|
|
313
|
+
|
|
314
|
+
init_file_path = get_shell_init_file(shell)
|
|
315
|
+
init_file = Path(init_file_path)
|
|
316
|
+
|
|
317
|
+
source_line = f'[ -f "{api_env_file}" ] && source "{api_env_file}"'
|
|
318
|
+
if shell == "fish":
|
|
319
|
+
source_line = f'test -f "{api_env_file}"; and source "{api_env_file}"'
|
|
320
|
+
|
|
321
|
+
if init_file.exists():
|
|
322
|
+
content = init_file.read_text()
|
|
323
|
+
if str(api_env_file) not in content:
|
|
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")
|
|
326
|
+
else:
|
|
327
|
+
init_file.write_text(f"# PDD API environment\n{source_line}\n")
|
|
328
|
+
|
|
329
|
+
return saved_files, created_pdd_dir
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def create_sample_prompt() -> str:
|
|
333
|
+
"""Create the sample prompt file."""
|
|
334
|
+
|
|
335
|
+
prompt_file = Path("hello_you_python.prompt")
|
|
336
|
+
prompt_file.write_text(HELLO_PYTHON_TEMPLATE)
|
|
337
|
+
return str(prompt_file)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def show_menu(keys: Dict[str, Optional[str]], test_results: Dict[str, bool]) -> str:
|
|
341
|
+
"""Show main menu and get user choice."""
|
|
342
|
+
|
|
343
|
+
print_colored(f"\n{create_divider()}", CYAN)
|
|
344
|
+
print_colored("Main Menu", CYAN, bold=True)
|
|
345
|
+
print_colored(f"{create_divider()}", CYAN)
|
|
346
|
+
|
|
347
|
+
print_colored("Current API Key Status:", WHITE, bold=True)
|
|
348
|
+
for key_name in ["OPENAI_API_KEY", "GOOGLE_API_KEY"]:
|
|
349
|
+
key_value = keys.get(key_name)
|
|
350
|
+
if key_value:
|
|
351
|
+
status = (
|
|
352
|
+
f"{CHECK_MARK} Valid"
|
|
353
|
+
if test_results.get(key_name)
|
|
354
|
+
else f"{CROSS_MARK} Invalid"
|
|
355
|
+
)
|
|
356
|
+
status_color = CYAN if test_results.get(key_name) else YELLOW
|
|
357
|
+
else:
|
|
358
|
+
status = "Not configured"
|
|
359
|
+
status_color = YELLOW
|
|
360
|
+
|
|
361
|
+
print(f" {key_name}: ", end="")
|
|
362
|
+
print_colored(status, status_color)
|
|
363
|
+
|
|
364
|
+
print()
|
|
365
|
+
print_colored("Options:", WHITE, bold=True)
|
|
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")
|
|
370
|
+
print()
|
|
371
|
+
|
|
372
|
+
while True:
|
|
373
|
+
try:
|
|
374
|
+
choice = input(f"{WHITE}Choose an option (1-4): {RESET}").strip()
|
|
375
|
+
if choice in ["1", "2", "3", "4"]:
|
|
376
|
+
return choice
|
|
377
|
+
print_colored("Please enter 1, 2, 3, or 4", YELLOW)
|
|
378
|
+
except KeyboardInterrupt:
|
|
379
|
+
print_colored("\n\nSetup cancelled.", YELLOW)
|
|
380
|
+
sys.exit(0)
|
|
381
|
+
|
|
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
|
+
|
|
391
|
+
summary_lines = [
|
|
392
|
+
"\n\n\n\n\n",
|
|
393
|
+
create_fat_divider(),
|
|
394
|
+
"PDD Setup Complete!",
|
|
395
|
+
create_fat_divider(),
|
|
396
|
+
"",
|
|
397
|
+
"Files created and configured:",
|
|
398
|
+
"",
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
file_descriptions: List[Tuple[str, str]] = []
|
|
402
|
+
if created_pdd_dir:
|
|
403
|
+
file_descriptions.append(("~/.pdd/", "PDD configuration directory"))
|
|
404
|
+
|
|
405
|
+
for file_path in saved_files:
|
|
406
|
+
if "api-env" in file_path:
|
|
407
|
+
file_descriptions.append((file_path, "API environment variables"))
|
|
408
|
+
elif "llm_model.csv" in file_path:
|
|
409
|
+
file_descriptions.append((file_path, "LLM model configuration"))
|
|
410
|
+
|
|
411
|
+
file_descriptions.append((sample_prompt_file, "Sample prompt for testing"))
|
|
412
|
+
file_descriptions.append(("PDD-SETUP-SUMMARY.txt", "This summary"))
|
|
413
|
+
|
|
414
|
+
max_path_len = max(len(path) for path, _ in file_descriptions)
|
|
415
|
+
|
|
416
|
+
for file_path, description in file_descriptions:
|
|
417
|
+
summary_lines.append(f"{file_path:<{max_path_len + 2}}{description}")
|
|
418
|
+
|
|
419
|
+
summary_lines.extend(
|
|
420
|
+
[
|
|
421
|
+
"",
|
|
422
|
+
create_divider(),
|
|
423
|
+
"",
|
|
424
|
+
"QUICK START:",
|
|
425
|
+
"",
|
|
426
|
+
"1. Reload your shell environment:",
|
|
427
|
+
]
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
api_env_path = f"{Path.home()}/.pdd/api-env"
|
|
431
|
+
if shell == "fish":
|
|
432
|
+
source_cmd = f"source {api_env_path}"
|
|
433
|
+
else:
|
|
434
|
+
source_cmd = f"source {api_env_path}"
|
|
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
|
+
|
|
475
|
+
print_pdd_logo()
|
|
476
|
+
|
|
477
|
+
print_colored(f"{create_divider()}", CYAN)
|
|
478
|
+
print_colored("Discovering local configuration...", CYAN, bold=True)
|
|
479
|
+
print_colored(f"{create_divider()}", CYAN)
|
|
480
|
+
|
|
481
|
+
keys = discover_api_keys()
|
|
482
|
+
test_results = test_api_keys(keys)
|
|
483
|
+
|
|
484
|
+
while True:
|
|
485
|
+
choice = show_menu(keys, test_results)
|
|
486
|
+
|
|
487
|
+
if choice == "1":
|
|
488
|
+
keys = get_user_keys(keys)
|
|
489
|
+
test_results = test_api_keys(keys)
|
|
490
|
+
elif choice == "2":
|
|
491
|
+
test_results = test_api_keys(keys)
|
|
492
|
+
elif choice == "3":
|
|
493
|
+
valid_keys = {k: v for k, v in keys.items() if v and test_results.get(k)}
|
|
494
|
+
|
|
495
|
+
if not valid_keys:
|
|
496
|
+
print_colored("\nNo valid API keys to save!", YELLOW)
|
|
497
|
+
continue
|
|
498
|
+
|
|
499
|
+
print_colored(f"\n{create_divider()}", CYAN)
|
|
500
|
+
print_colored("Saving configuration...", CYAN, bold=True)
|
|
501
|
+
print_colored(f"{create_divider()}", CYAN)
|
|
502
|
+
|
|
503
|
+
try:
|
|
504
|
+
saved_files, created_pdd_dir = save_configuration(valid_keys)
|
|
505
|
+
sample_prompt_file = create_sample_prompt()
|
|
506
|
+
shell = detect_shell()
|
|
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")
|
|
516
|
+
summary_file.write_text(summary)
|
|
517
|
+
|
|
518
|
+
for line in summary.split("\n"):
|
|
519
|
+
if line == create_fat_divider():
|
|
520
|
+
print_colored(line, YELLOW, bold=True)
|
|
521
|
+
elif line == "PDD Setup Complete!":
|
|
522
|
+
print_colored(line, YELLOW, bold=True)
|
|
523
|
+
elif line == create_divider():
|
|
524
|
+
print_colored(line, CYAN)
|
|
525
|
+
elif any(
|
|
526
|
+
line.startswith(prefix)
|
|
527
|
+
for prefix in ["QUICK START:", "LEARN MORE:", "TIPS:"]
|
|
528
|
+
):
|
|
529
|
+
print_colored(line, CYAN, bold=True)
|
|
530
|
+
elif "IMPORTANT:" in line or "Problems?" in line:
|
|
531
|
+
print_colored(line, YELLOW, bold=True)
|
|
532
|
+
else:
|
|
533
|
+
print(line)
|
|
534
|
+
|
|
535
|
+
break
|
|
536
|
+
except Exception as exc: # noqa: BLE001
|
|
537
|
+
print_colored(f"Error saving configuration: {exc}", YELLOW)
|
|
538
|
+
continue
|
|
539
|
+
elif choice == "4":
|
|
540
|
+
print_colored("\nExiting without saving configuration.", YELLOW)
|
|
541
|
+
break
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
if __name__ == "__main__":
|
|
545
|
+
try:
|
|
546
|
+
main()
|
|
547
|
+
except KeyboardInterrupt:
|
|
548
|
+
print_colored("\n\nSetup cancelled.", YELLOW)
|
|
549
|
+
sys.exit(0)
|
|
@@ -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,9 +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.
|
|
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
|