pdd-cli 0.0.56__py3-none-any.whl → 0.0.58__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/trace.py CHANGED
@@ -1,4 +1,4 @@
1
- from typing import Tuple, Optional
1
+ from typing import Tuple, Optional, List
2
2
  from rich import print
3
3
  from rich.console import Console
4
4
  from pydantic import BaseModel, Field
@@ -10,6 +10,39 @@ from .llm_invoke import llm_invoke
10
10
  from . import DEFAULT_TIME, DEFAULT_STRENGTH
11
11
  console = Console()
12
12
 
13
+
14
+ def _normalize_text(value: str) -> str:
15
+ if value is None:
16
+ return ""
17
+ value = value.replace("\u201c", '"').replace("\u201d", '"')
18
+ value = value.replace("\u2018", "'").replace("\u2019", "'")
19
+ value = value.replace("\u00A0", " ")
20
+ value = re.sub(r"\s+", " ", value.strip())
21
+ return value
22
+
23
+
24
+ def _fallback_prompt_line(prompt_lines: List[str], code_str: str) -> int:
25
+ """Best-effort deterministic fallback to select a prompt line."""
26
+ normalized_code = _normalize_text(code_str).casefold()
27
+ tokens = [tok for tok in re.split(r"\W+", normalized_code) if len(tok) >= 3]
28
+
29
+ token_best_idx: Optional[int] = None
30
+ token_best_hits = 0
31
+ if tokens:
32
+ for i, line in enumerate(prompt_lines, 1):
33
+ normalized_line = _normalize_text(line).casefold()
34
+ hits = sum(1 for tok in tokens if tok in normalized_line)
35
+ if hits > token_best_hits:
36
+ token_best_hits = hits
37
+ token_best_idx = i
38
+ if token_best_idx is not None and token_best_hits > 0:
39
+ return token_best_idx
40
+
41
+ for i, line in enumerate(prompt_lines, 1):
42
+ if _normalize_text(line):
43
+ return i
44
+ return 1
45
+
13
46
  class PromptLineOutput(BaseModel):
14
47
  prompt_line: str = Field(description="The line from the prompt file that matches the code")
15
48
 
@@ -109,15 +142,6 @@ def trace(
109
142
  console.print(f"Searching for line: {prompt_line_str}")
110
143
 
111
144
  # Robust normalization for comparison
112
- def normalize_text(s: str) -> str:
113
- if s is None:
114
- return ""
115
- s = s.replace("\u201c", '"').replace("\u201d", '"') # smart double quotes → straight
116
- s = s.replace("\u2018", "'").replace("\u2019", "'") # smart single quotes → straight
117
- s = s.replace("\u00A0", " ") # non-breaking space → space
118
- s = re.sub(r"\s+", " ", s.strip()) # collapse whitespace
119
- return s
120
-
121
145
  # If the model echoed wrapper tags like <llm_output>...</llm_output>, extract inner text
122
146
  raw_search = prompt_line_str
123
147
  try:
@@ -127,12 +151,12 @@ def trace(
127
151
  except Exception:
128
152
  pass
129
153
 
130
- normalized_search = normalize_text(raw_search).casefold()
154
+ normalized_search = _normalize_text(raw_search).casefold()
131
155
  best_candidate_idx = None
132
156
  best_candidate_len = 0
133
157
 
134
158
  for i, line in enumerate(prompt_lines, 1):
135
- normalized_line = normalize_text(line).casefold()
159
+ normalized_line = _normalize_text(line).casefold()
136
160
  line_len = len(normalized_line)
137
161
 
138
162
  # Base similarity
@@ -246,14 +270,26 @@ def trace(
246
270
  f"[yellow]Low-confidence multi-line match selected (ratio={win_best_ratio:.3f}).[/yellow]"
247
271
  )
248
272
 
273
+ # Step 7c: Deterministic fallback when LLM output cannot be matched reliably
274
+ fallback_used = False
275
+ if best_match is None:
276
+ best_match = _fallback_prompt_line(prompt_lines, code_str)
277
+ fallback_used = True
278
+
249
279
  # Step 8: Return results
250
280
  if verbose:
251
281
  console.print(f"[green]Found matching line: {best_match}[/green]")
252
282
  console.print(f"[green]Total cost: ${total_cost:.6f}[/green]")
253
283
  console.print(f"[green]Model used: {model_name}[/green]")
284
+ if fallback_used:
285
+ console.print("[yellow]Fallback matching heuristic was used.[/yellow]")
254
286
 
255
287
  return best_match, total_cost, model_name
256
288
 
257
289
  except Exception as e:
258
290
  console.print(f"[bold red]Error in trace function: {str(e)}[/bold red]")
259
- return None, 0.0, ""
291
+ try:
292
+ fallback_line = _fallback_prompt_line(prompt_file.splitlines(), code_file.splitlines()[code_line - 1] if 0 < code_line <= len(code_file.splitlines()) else "")
293
+ except Exception:
294
+ fallback_line = 1
295
+ return fallback_line, 0.0, "fallback"
@@ -0,0 +1,524 @@
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
+ from pathlib import Path
13
+ from typing import Dict, Optional, Tuple, List
14
+
15
+ # Global variables for non-ASCII characters and colors
16
+ HEAVY_HORIZONTAL = "━"
17
+ LIGHT_HORIZONTAL = "─"
18
+ HEAVY_VERTICAL = "┃"
19
+ LIGHT_VERTICAL = "│"
20
+ TOP_LEFT_CORNER = "┏"
21
+ TOP_RIGHT_CORNER = "┓"
22
+ BOTTOM_LEFT_CORNER = "┗"
23
+ BOTTOM_RIGHT_CORNER = "┛"
24
+ CROSS = "┼"
25
+ TEE_DOWN = "┬"
26
+ TEE_UP = "┴"
27
+ TEE_RIGHT = "├"
28
+ TEE_LEFT = "┤"
29
+ BULLET = "•"
30
+ ARROW_RIGHT = "→"
31
+ CHECK_MARK = "✓"
32
+ CROSS_MARK = "✗"
33
+
34
+ # Color codes
35
+ RESET = "\033[0m"
36
+ WHITE = "\033[97m"
37
+ CYAN = "\033[96m"
38
+ YELLOW = "\033[93m"
39
+ BOLD = "\033[1m"
40
+
41
+ # Template content inline
42
+ HELLO_PYTHON_TEMPLATE = """Create a Python program that prints "Hello <username>" in ASCII art.
43
+
44
+ Requirements:
45
+ - <username> is the username of the current session, using the "whoami" command
46
+ - 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
47
+ - Use only the Python standard library (no external dependencies)
48
+ - Create large, bold ASCII art text, using ASCII drawing characters, symbols, or any other characters that are useful
49
+ - The drawn text should be at least 10 rows in height
50
+ - Make it visually appealing with simple characters like #, *, or =
51
+ - Keep the code clean and readable
52
+ - Add a brief comment explaining what the program does
53
+
54
+ The program should be self-contained and runnable with just `python3 filename.py`."""
55
+
56
+ LLM_MODEL_CSV_TEMPLATE = """provider,model,input,output,coding_arena_elo,base_url,api_key,max_reasoning_tokens,structured_output,reasoning_type
57
+ OpenAI,gpt-5-nano,0.05,0.4,1249,,OPENAI_API_KEY,0,True,none
58
+ Google,gemini/gemini-2.5-pro,1.25,10.0,1360,,GOOGLE_API_KEY,0,True,none
59
+ OpenAI,gpt-5-mini,0.25,2.0,1325,,OPENAI_API_KEY,0,True,effort
60
+ OpenAI,gpt-5,1.25,10.0,1482,,OPENAI_API_KEY,0,True,effort
61
+ OpenAI,gpt-4.1,2.0,8.0,1253,,OPENAI_API_KEY,0,True,none"""
62
+
63
+ def print_colored(text: str, color: str = WHITE, bold: bool = False) -> None:
64
+ """Print colored text to console"""
65
+ style = BOLD + color if bold else color
66
+ print(f"{style}{text}{RESET}")
67
+
68
+ def create_divider(char: str = LIGHT_HORIZONTAL, width: int = 80) -> str:
69
+ """Create a horizontal divider line"""
70
+ return char * width
71
+
72
+ def create_fat_divider(width: int = 80) -> str:
73
+ """Create a fat horizontal divider line"""
74
+ return HEAVY_HORIZONTAL * width
75
+
76
+ def print_pdd_logo():
77
+ """Print the PDD logo in ASCII art"""
78
+ logo = "\n".join(
79
+ [
80
+ " +xxxxxxxxxxxxxxx+",
81
+ "xxxxxxxxxxxxxxxxxxxxx+",
82
+ "xxx +xx+ PROMPT",
83
+ "xxx x+ xx+ DRIVEN",
84
+ "xxx x+ xxx DEVELOPMENT©",
85
+ "xxx x+ xx+",
86
+ "xxx x+ xx+ COMMAND LINE INTERFACE",
87
+ "xxx x+ xxx",
88
+ "xxx +xx+ ",
89
+ "xxx +xxxxxxxxxxx+",
90
+ "xxx +xx+",
91
+ "xxx +xx+",
92
+ "xxx+xx+ WWW.PROMPTDRIVEN.AI",
93
+ "xxxx+",
94
+ "xx+",
95
+ ]
96
+ )
97
+ print(f"{CYAN}{logo}{RESET}")
98
+ print_colored("Supported: OpenAI and Google Gemini (non-Vertex)", WHITE)
99
+ print_colored("from their respective API endpoints (no third-parties, such as Azure)", WHITE)
100
+ print()
101
+
102
+ def discover_api_keys() -> Dict[str, Optional[str]]:
103
+ """Discover API keys from environment variables"""
104
+ 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'),
107
+ }
108
+ return keys
109
+
110
+ def test_openai_key(api_key: str) -> bool:
111
+ """Test OpenAI API key validity"""
112
+ if not api_key or not api_key.strip():
113
+ return False
114
+
115
+ try:
116
+ headers = {
117
+ 'Authorization': f'Bearer {api_key.strip()}',
118
+ 'Content-Type': 'application/json'
119
+ }
120
+ response = requests.get(
121
+ 'https://api.openai.com/v1/models',
122
+ headers=headers,
123
+ timeout=10
124
+ )
125
+ return response.status_code == 200
126
+ except Exception:
127
+ return False
128
+
129
+ def test_google_key(api_key: str) -> bool:
130
+ """Test Google Gemini API key validity"""
131
+ if not api_key or not api_key.strip():
132
+ return False
133
+
134
+ try:
135
+ response = requests.get(
136
+ f'https://generativelanguage.googleapis.com/v1beta/models?key={api_key.strip()}',
137
+ timeout=10
138
+ )
139
+ return response.status_code == 200
140
+ except Exception:
141
+ return False
142
+
143
+ def test_api_keys(keys: Dict[str, Optional[str]]) -> Dict[str, bool]:
144
+ """Test all discovered API keys"""
145
+ results = {}
146
+
147
+ print_colored(f"\n{LIGHT_HORIZONTAL * 40}", CYAN)
148
+ print_colored("Testing discovered API keys...", CYAN, bold=True)
149
+ print_colored(f"{LIGHT_HORIZONTAL * 40}", CYAN)
150
+
151
+ for key_name, key_value in keys.items():
152
+ if key_value:
153
+ print(f"Testing {key_name}...", end=" ", flush=True)
154
+ if key_name == 'OPENAI_API_KEY':
155
+ valid = test_openai_key(key_value)
156
+ elif key_name in ['GOOGLE_API_KEY']:
157
+ valid = test_google_key(key_value)
158
+ else:
159
+ valid = False
160
+
161
+ if valid:
162
+ print_colored(f"{CHECK_MARK} Valid", CYAN)
163
+ results[key_name] = True
164
+ else:
165
+ print_colored(f"{CROSS_MARK} Invalid", YELLOW)
166
+ results[key_name] = False
167
+ else:
168
+ print_colored(f"{key_name}: Not found", YELLOW)
169
+ results[key_name] = False
170
+
171
+ return results
172
+
173
+ def get_user_keys(current_keys: Dict[str, Optional[str]]) -> Dict[str, Optional[str]]:
174
+ """Interactive key entry/modification"""
175
+ print_colored(f"\n{create_fat_divider()}", YELLOW)
176
+ print_colored("API Key Configuration", YELLOW, bold=True)
177
+ print_colored(f"{create_fat_divider()}", YELLOW)
178
+
179
+ print_colored("You need only one API key to get started", WHITE)
180
+ print()
181
+ print_colored("Get API keys here:", WHITE)
182
+ print_colored(f" OpenAI {ARROW_RIGHT} https://platform.openai.com/api-keys", CYAN)
183
+ print_colored(f" Google Gemini {ARROW_RIGHT} https://aistudio.google.com/app/apikey", CYAN)
184
+ print()
185
+ print_colored("A free instant starter key is available from Google Gemini (above)", CYAN)
186
+ print()
187
+
188
+ new_keys = current_keys.copy()
189
+
190
+ for key_name in ['OPENAI_API_KEY', 'GOOGLE_API_KEY']:
191
+ current_value = current_keys.get(key_name, "")
192
+ status = "found" if current_value else "not found"
193
+
194
+ print_colored(f"{LIGHT_HORIZONTAL * 60}", CYAN)
195
+ print_colored(f"{key_name} (currently: {status})", WHITE, bold=True)
196
+
197
+ if current_value:
198
+ prompt = f"Enter new key or press ENTER to keep existing: "
199
+ else:
200
+ prompt = f"Enter API key (or press ENTER to skip): "
201
+
202
+ try:
203
+ user_input = input(f"{WHITE}{prompt}{RESET}").strip()
204
+ if user_input:
205
+ new_keys[key_name] = user_input
206
+ elif not current_value:
207
+ new_keys[key_name] = None
208
+ except KeyboardInterrupt:
209
+ print_colored("\n\nSetup cancelled.", YELLOW)
210
+ sys.exit(0)
211
+
212
+ return new_keys
213
+
214
+ def detect_shell() -> str:
215
+ """Detect user's default shell"""
216
+ try:
217
+ shell_path = os.getenv('SHELL', '/bin/bash')
218
+ shell_name = os.path.basename(shell_path)
219
+ return shell_name
220
+ except:
221
+ return 'bash'
222
+
223
+ def get_shell_init_file(shell: str) -> str:
224
+ """Get the appropriate shell initialization file"""
225
+ home = Path.home()
226
+
227
+ 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'
234
+ }
235
+
236
+ return str(shell_files.get(shell, home / '.bashrc'))
237
+
238
+ def create_api_env_script(keys: Dict[str, str], shell: str) -> str:
239
+ """Create shell-appropriate environment script"""
240
+ 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']:
248
+ lines = []
249
+ 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
253
+ lines = []
254
+ for key, value in valid_keys.items():
255
+ lines.append(f'export {key}="{value}"')
256
+ return '\n'.join(lines) + '\n'
257
+
258
+ def save_configuration(valid_keys: Dict[str, str]) -> Tuple[List[str], bool]:
259
+ """Save configuration to ~/.pdd/ directory"""
260
+ home = Path.home()
261
+ pdd_dir = home / '.pdd'
262
+ created_pdd_dir = False
263
+ saved_files = []
264
+
265
+ # Create .pdd directory if it doesn't exist
266
+ if not pdd_dir.exists():
267
+ pdd_dir.mkdir(mode=0o755)
268
+ created_pdd_dir = True
269
+
270
+ # Detect shell and create api-env script
271
+ shell = detect_shell()
272
+ api_env_content = create_api_env_script(valid_keys, shell)
273
+
274
+ # Write api-env file
275
+ api_env_file = pdd_dir / 'api-env'
276
+ api_env_file.write_text(api_env_content)
277
+ api_env_file.chmod(0o755)
278
+ 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')
282
+ header = csv_lines[0]
283
+ valid_lines = [header]
284
+
285
+ for line in csv_lines[1:]:
286
+ if 'OPENAI_API_KEY' in line and 'OPENAI_API_KEY' in valid_keys:
287
+ valid_lines.append(line)
288
+ elif 'GOOGLE_API_KEY' in line and 'GOOGLE_API_KEY' in valid_keys:
289
+ 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')
293
+ saved_files.append(str(llm_model_file))
294
+
295
+ # Update shell init file
296
+ init_file_path = get_shell_init_file(shell)
297
+ init_file = Path(init_file_path)
298
+
299
+ source_line = f'[ -f "{api_env_file}" ] && source "{api_env_file}"'
300
+ if shell == 'fish':
301
+ source_line = f'test -f "{api_env_file}"; and source "{api_env_file}"'
302
+
303
+ # Check if source line already exists
304
+ if init_file.exists():
305
+ content = init_file.read_text()
306
+ 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')
309
+ else:
310
+ init_file.write_text(f'# PDD API environment\n{source_line}\n')
311
+
312
+ return saved_files, created_pdd_dir
313
+
314
+ def create_sample_prompt():
315
+ """Create the sample prompt file"""
316
+ prompt_file = Path('hello_you_python.prompt')
317
+ prompt_file.write_text(HELLO_PYTHON_TEMPLATE)
318
+ return str(prompt_file)
319
+
320
+ def show_menu(keys: Dict[str, Optional[str]], test_results: Dict[str, bool]) -> str:
321
+ """Show main menu and get user choice"""
322
+ print_colored(f"\n{create_divider()}", CYAN)
323
+ print_colored("Main Menu", CYAN, bold=True)
324
+ print_colored(f"{create_divider()}", CYAN)
325
+
326
+ # Show current status
327
+ print_colored("Current API Key Status:", WHITE, bold=True)
328
+ for key_name in ['OPENAI_API_KEY', 'GOOGLE_API_KEY']:
329
+ key_value = keys.get(key_name)
330
+ if key_value:
331
+ status = f"{CHECK_MARK} Valid" if test_results.get(key_name) else f"{CROSS_MARK} Invalid"
332
+ status_color = CYAN if test_results.get(key_name) else YELLOW
333
+ else:
334
+ status = "Not configured"
335
+ status_color = YELLOW
336
+
337
+ print(f" {key_name}: ", end="")
338
+ print_colored(status, status_color)
339
+
340
+ print()
341
+ 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")
346
+ print()
347
+
348
+ while True:
349
+ try:
350
+ choice = input(f"{WHITE}Choose an option (1-4): {RESET}").strip()
351
+ if choice in ['1', '2', '3', '4']:
352
+ return choice
353
+ else:
354
+ print_colored("Please enter 1, 2, 3, or 4", YELLOW)
355
+ except KeyboardInterrupt:
356
+ print_colored("\n\nSetup cancelled.", YELLOW)
357
+ sys.exit(0)
358
+
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"""
361
+ summary_lines = [
362
+ "\n\n\n\n\n",
363
+ create_fat_divider(),
364
+ "PDD Setup Complete!",
365
+ create_fat_divider(),
366
+ "",
367
+ "Files created and configured:",
368
+ ""
369
+ ]
370
+
371
+ # File descriptions with alignment
372
+ file_descriptions = []
373
+ if created_pdd_dir:
374
+ file_descriptions.append(("~/.pdd/", "PDD configuration directory"))
375
+
376
+ for file_path in saved_files:
377
+ if 'api-env' in file_path:
378
+ file_descriptions.append((file_path, "API environment variables"))
379
+ elif 'llm_model.csv' in file_path:
380
+ file_descriptions.append((file_path, "LLM model configuration"))
381
+
382
+ file_descriptions.append((sample_prompt_file, "Sample prompt for testing"))
383
+ file_descriptions.append(("PDD-SETUP-SUMMARY.txt", "This summary"))
384
+
385
+ # Find max file path length for alignment
386
+ max_path_len = max(len(path) for path, _ in file_descriptions)
387
+
388
+ for file_path, description in file_descriptions:
389
+ 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
401
+ api_env_path = f"{Path.home()}/.pdd/api-env"
402
+ if shell == 'fish':
403
+ source_cmd = f"source {api_env_path}"
404
+ else:
405
+ 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
443
+ print_pdd_logo()
444
+
445
+ # Discover environment
446
+ print_colored(f"{create_divider()}", CYAN)
447
+ print_colored("Discovering local configuration...", CYAN, bold=True)
448
+ print_colored(f"{create_divider()}", CYAN)
449
+
450
+ keys = discover_api_keys()
451
+
452
+ # Test discovered keys
453
+ test_results = test_api_keys(keys)
454
+
455
+ # Main interaction loop
456
+ while True:
457
+ choice = show_menu(keys, test_results)
458
+
459
+ if choice == '1':
460
+ # Re-enter keys
461
+ keys = get_user_keys(keys)
462
+ test_results = test_api_keys(keys)
463
+
464
+ elif choice == '2':
465
+ # Re-test keys
466
+ test_results = test_api_keys(keys)
467
+
468
+ elif choice == '3':
469
+ # Save and exit
470
+ valid_keys = {k: v for k, v in keys.items() if v and test_results.get(k)}
471
+
472
+ if not valid_keys:
473
+ print_colored("\nNo valid API keys to save!", YELLOW)
474
+ continue
475
+
476
+ print_colored(f"\n{create_divider()}", CYAN)
477
+ print_colored("Saving configuration...", CYAN, bold=True)
478
+ print_colored(f"{create_divider()}", CYAN)
479
+
480
+ try:
481
+ saved_files, created_pdd_dir = save_configuration(valid_keys)
482
+ sample_prompt_file = create_sample_prompt()
483
+ 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')
490
+ summary_file.write_text(summary)
491
+
492
+ # Display summary with colors
493
+ lines = summary.split('\n')
494
+ for line in lines:
495
+ if line == create_fat_divider():
496
+ print_colored(line, YELLOW, bold=True)
497
+ elif line == "PDD Setup Complete!":
498
+ print_colored(line, YELLOW, bold=True)
499
+ elif line == create_divider():
500
+ print_colored(line, CYAN)
501
+ elif line.startswith("QUICK START:") or line.startswith("LEARN MORE:") or line.startswith("TIPS:"):
502
+ print_colored(line, CYAN, bold=True)
503
+ elif "IMPORTANT:" in line or "Problems?" in line:
504
+ print_colored(line, YELLOW, bold=True)
505
+ else:
506
+ print(line)
507
+
508
+ break
509
+
510
+ except Exception as e:
511
+ print_colored(f"Error saving configuration: {e}", YELLOW)
512
+ continue
513
+
514
+ elif choice == '4':
515
+ # Exit without saving
516
+ print_colored("\nExiting without saving configuration.", YELLOW)
517
+ break
518
+
519
+ if __name__ == '__main__':
520
+ try:
521
+ main()
522
+ except KeyboardInterrupt:
523
+ print_colored("\n\nSetup cancelled.", YELLOW)
524
+ sys.exit(0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdd-cli
3
- Version: 0.0.56
3
+ Version: 0.0.58
4
4
  Summary: PDD (Prompt-Driven Development) Command Line Interface
5
5
  Author: Greg Tanaka
6
6
  Author-email: glt@alumni.caltech.edu
@@ -51,7 +51,7 @@ Requires-Dist: build; extra == "dev"
51
51
  Requires-Dist: twine; extra == "dev"
52
52
  Dynamic: license-file
53
53
 
54
- .. image:: https://img.shields.io/badge/pdd--cli-v0.0.56-blue
54
+ .. image:: https://img.shields.io/badge/pdd--cli-v0.0.58-blue
55
55
  :alt: PDD-CLI Version
56
56
 
57
57
  .. image:: https://img.shields.io/badge/Discord-join%20chat-7289DA.svg?logo=discord&logoColor=white&link=https://discord.gg/Yp4RTh8bG7
@@ -128,7 +128,7 @@ After installation, verify:
128
128
 
129
129
  pdd --version
130
130
 
131
- You'll see the current PDD version (e.g., 0.0.56).
131
+ You'll see the current PDD version (e.g., 0.0.58).
132
132
 
133
133
  Getting Started with Examples
134
134
  -----------------------------