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 CHANGED
@@ -1,6 +1,6 @@
1
1
  """PDD - Prompt Driven Development"""
2
2
 
3
- __version__ = "0.0.58"
3
+ __version__ = "0.0.59"
4
4
 
5
5
  # Strength parameter used for LLM extraction across the codebase
6
6
  # Used in postprocessing, XML tagging, code generation, and other extraction
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
- setup_script = Path(__file__).resolve().parent.parent / "utils" / "pdd-setup.py"
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
- #!/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
- """
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, List
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
- def print_pdd_logo():
77
- """Print the PDD logo in ASCII art"""
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
- 'OPENAI_API_KEY': os.getenv('OPENAI_API_KEY'),
106
- 'GOOGLE_API_KEY': os.getenv('GOOGLE_API_KEY') or os.getenv('GEMINI_API_KEY'),
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
- 'Authorization': f'Bearer {api_key.strip()}',
118
- 'Content-Type': 'application/json'
126
+ "Authorization": f"Bearer {api_key.strip()}",
127
+ "Content-Type": "application/json",
119
128
  }
120
129
  response = requests.get(
121
- 'https://api.openai.com/v1/models',
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'https://generativelanguage.googleapis.com/v1beta/models?key={api_key.strip()}',
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
- results = {}
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 == 'OPENAI_API_KEY':
167
+ if key_name == "OPENAI_API_KEY":
155
168
  valid = test_openai_key(key_value)
156
- elif key_name in ['GOOGLE_API_KEY']:
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 ['OPENAI_API_KEY', 'GOOGLE_API_KEY']:
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 = f"Enter new key or press ENTER to keep existing: "
213
+ prompt = "Enter new key or press ENTER to keep existing: "
199
214
  else:
200
- prompt = f"Enter API key (or press ENTER to skip): "
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('SHELL', '/bin/bash')
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 'bash'
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
- 'bash': home / '.bashrc',
229
- 'zsh': home / '.zshrc',
230
- 'fish': home / '.config/fish/config.fish',
231
- 'csh': home / '.cshrc',
232
- 'tcsh': home / '.tcshrc',
233
- 'ksh': home / '.kshrc'
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 / '.bashrc'))
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 == 'fish':
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'setenv {key} "{value}"')
251
- return '\n'.join(lines) + '\n'
252
- else: # bash, zsh, ksh and others
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'export {key}="{value}"')
256
- return '\n'.join(lines) + '\n'
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 / '.pdd'
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
- # Write api-env file
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
- # Create llm_model.csv with only valid providers
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 'OPENAI_API_KEY' in line and 'OPENAI_API_KEY' in valid_keys:
305
+ if "OPENAI_API_KEY" in line and "OPENAI_API_KEY" in valid_keys:
287
306
  valid_lines.append(line)
288
- elif 'GOOGLE_API_KEY' in line and 'GOOGLE_API_KEY' in valid_keys:
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 / 'llm_model.csv'
292
- llm_model_file.write_text('\n'.join(valid_lines) + '\n')
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 == 'fish':
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('a') as f:
308
- f.write(f'\n# PDD API environment\n{source_line}\n')
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'# PDD API environment\n{source_line}\n')
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
- def create_sample_prompt():
315
- """Create the sample prompt file"""
316
- prompt_file = Path('hello_you_python.prompt')
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 ['OPENAI_API_KEY', 'GOOGLE_API_KEY']:
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 = f"{CHECK_MARK} Valid" if test_results.get(key_name) else f"{CROSS_MARK} Invalid"
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(f" 1. Re-enter API keys")
343
- print(f" 2. Re-test current keys")
344
- print(f" 3. Save configuration and exit")
345
- print(f" 4. Exit without saving")
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 ['1', '2', '3', '4']:
375
+ if choice in ["1", "2", "3", "4"]:
352
376
  return choice
353
- else:
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
- def create_exit_summary(saved_files: List[str], created_pdd_dir: bool, sample_prompt_file: str, shell: str) -> str:
360
- """Create comprehensive exit summary"""
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
- # File descriptions with alignment
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 'api-env' in file_path:
406
+ if "api-env" in file_path:
378
407
  file_descriptions.append((file_path, "API environment variables"))
379
- elif 'llm_model.csv' in file_path:
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
- create_divider(),
394
- "",
395
- "QUICK START:",
396
- "",
397
- f"1. Reload your shell environment:"
398
- ])
399
-
400
- # Shell-specific source command
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 == 'fish':
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
- f" {source_cmd}",
409
- "",
410
- f"2. Generate code from the sample prompt:",
411
- f" pdd generate hello_you_python.prompt",
412
- "",
413
- create_divider(),
414
- "",
415
- "LEARN MORE:",
416
- "",
417
- f"{BULLET} PDD documentation: pdd --help",
418
- f"{BULLET} PDD website: https://promptdriven.ai/",
419
- f"{BULLET} Discord community: https://discord.gg/Yp4RTh8bG7",
420
- "",
421
- "TIPS:",
422
- "",
423
- f"{BULLET} IMPORTANT: Reload your shell environment using the source command above",
424
- "",
425
- f"{BULLET} Start with simple prompts and gradually increase complexity",
426
- 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",
427
- f"{BULLET} Try out 'pdd example' with your prompt+code to create examples which help pdd do better",
428
- "",
429
- f"{BULLET} As you get comfortable, learn configuration settings, including the .pddrc file, PDD_GENERATE_OUTPUT_PATH, and PDD_TEST_OUTPUT_PATH",
430
- f"{BULLET} For larger projects, use Makefiles and/or 'pdd sync'",
431
- f"{BULLET} For ongoing substantial projects, learn about llm_model.csv to optimize model cost, latency, and output quality",
432
- "",
433
- f"{BULLET} Use 'pdd --help' to explore all available commands",
434
- "",
435
- "Problems? Shout out on our Discord for help! https://discord.gg/Yp4RTh8bG7"
436
- ])
437
-
438
- return '\n'.join(summary_lines)
439
-
440
- def main():
441
- """Main setup workflow"""
442
- # Initial greeting
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 == '1':
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
- # Create and display summary
486
- summary = create_exit_summary(saved_files, created_pdd_dir, sample_prompt_file, shell)
487
-
488
- # Write summary to file
489
- summary_file = Path('PDD-SETUP-SUMMARY.txt')
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
- # Display summary with colors
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 line.startswith("QUICK START:") or line.startswith("LEARN MORE:") or line.startswith("TIPS:"):
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
- except Exception as e:
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
- if __name__ == '__main__':
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.58
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.58-blue
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.58).
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=PcN6Adk2Ea2dffgFJe0eyB538DRmYKrwUXjuu7IrSPQ,633
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=kLmJZ-2ETIbbpclwM7cQuaVfAiUNTBxJ9RtB0CsF2h0,55355
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.58.data/data/utils/pdd-setup.py,sha256=sQ-HwwSN-ER4xz77EZqfxH8W0gG-kDtqj7LaR7SomA4,18955
114
- pdd_cli-0.0.58.dist-info/licenses/LICENSE,sha256=kvTJnnxPVTYlGKSY4ZN1kzdmJ0lxRdNWxgupaB27zsU,1066
115
- pdd_cli-0.0.58.dist-info/METADATA,sha256=TIrlo_oc6NbiHyi_WqFxqsRSOXrhEkYcuFoN5eCaD3s,12597
116
- pdd_cli-0.0.58.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
117
- pdd_cli-0.0.58.dist-info/entry_points.txt,sha256=Kr8HtNVb8uHZtQJNH4DnF8j7WNgWQbb7_Pw5hECSR-I,36
118
- pdd_cli-0.0.58.dist-info/top_level.txt,sha256=xjnhIACeMcMeDfVNREgQZl4EbTni2T11QkL5r7E-sbE,4
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,,