pdd-cli 0.0.58__py3-none-any.whl → 0.0.60__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.60"
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
 
pdd/pdd_completion.fish CHANGED
@@ -10,6 +10,8 @@ complete -c pdd -n "__fish_use_subcommand" -l quiet -d "Decrease output verbosit
10
10
  complete -c pdd -n "__fish_use_subcommand" -l output-cost -r -d "Enable cost tracking and output CSV file"
11
11
  complete -c pdd -n "__fish_use_subcommand" -l review-examples -d "Review few-shot examples before execution"
12
12
  complete -c pdd -n "__fish_use_subcommand" -l local -d "Run commands locally"
13
+ complete -c pdd -n "__fish_use_subcommand" -l context -r -d "Override .pddrc context"
14
+ complete -c pdd -n "__fish_use_subcommand" -l list-contexts -d ".pddrc contexts and exit"
13
15
  complete -c pdd -n "__fish_use_subcommand" -l help -d "Show help message"
14
16
  complete -c pdd -n "__fish_use_subcommand" -l version -d "Show version information"
15
17
 
@@ -29,12 +31,16 @@ complete -c pdd -n "__fish_use_subcommand" -a trace -d "Trace code line to promp
29
31
  complete -c pdd -n "__fish_use_subcommand" -a bug -d "Generate unit test from bug report"
30
32
  complete -c pdd -n "__fish_use_subcommand" -a auto-deps -d "Analyze and insert dependencies from directory or glob"
31
33
  complete -c pdd -n "__fish_use_subcommand" -a verify -d "Verify functional correctness using LLM judgment"
34
+ complete -c pdd -n "__fish_use_subcommand" -a sync -d "Synchronize prompt, code, examples, tests"
35
+ complete -c pdd -n "__fish_use_subcommand" -a setup -d "Interactive setup and completion install"
36
+ complete -c pdd -n "__fish_use_subcommand" -a install_completion -d "Install shell completion"
37
+ complete -c pdd -n "__fish_use_subcommand" -a pytest-output -d "Run pytest and capture structured output"
32
38
 
33
39
  # Command-specific completions
34
40
  complete -c pdd -n "__fish_seen_subcommand_from generate" -l output -r -d "Output location for generated code"
35
41
  complete -c pdd -n "__fish_seen_subcommand_from generate" -l original-prompt -r -d "Original prompt file for incremental generation"
36
42
  complete -c pdd -n "__fish_seen_subcommand_from generate" -l incremental -d "Force incremental patching"
37
- complete -c pdd -n "__fish_seen_subcommand_from generate" -s e -l env -xa "(env | cut -d= -f1 | sed 's/$/=/' | sort -u)" -d "Set template variable (KEY=VALUE) or read KEY from env"
43
+ complete -c pdd -n "__fish_seen_subcommand_from generate" -s e -l env -xa "(env | cut -d= -f1 | sed 's/.*/&=/' | sort -u)" -d "Set template variable (KEY=VALUE) or read KEY from env"
38
44
  complete -c pdd -n "__fish_seen_subcommand_from generate" -a "(__fish_complete_suffix .prompt)"
39
45
 
40
46
  complete -c pdd -n "__fish_seen_subcommand_from example" -l output -r -d "Output location for example code"
@@ -126,10 +132,26 @@ complete -c pdd -n "__fish_seen_subcommand_from verify" -l budget -x -d "Max bud
126
132
  complete -c pdd -n "__fish_seen_subcommand_from verify" -a "(__fish_complete_suffix .prompt)"
127
133
  complete -c pdd -n "__fish_seen_subcommand_from verify" -a "(__fish_complete_suffix .py .js .java .cpp .rb .go)" # For CODE_FILE and PROGRAM_FILE
128
134
 
135
+ # sync command
136
+ complete -c pdd -n "__fish_seen_subcommand_from sync" -l max-attempts -x -d "Max attempts for loops"
137
+ complete -c pdd -n "__fish_seen_subcommand_from sync" -l budget -x -d "Total budget for sync"
138
+ complete -c pdd -n "__fish_seen_subcommand_from sync" -l skip-verify -d "Skip functional verification"
139
+ complete -c pdd -n "__fish_seen_subcommand_from sync" -l skip-tests -d "Skip unit test generation"
140
+ complete -c pdd -n "__fish_seen_subcommand_from sync" -l target-coverage -x -d "Desired coverage percentage"
141
+ complete -c pdd -n "__fish_seen_subcommand_from sync" -l log -d "Show analysis instead of running"
142
+
143
+ # setup and install_completion have no options
144
+ complete -c pdd -n "__fish_seen_subcommand_from setup" -d "Run interactive setup"
145
+ complete -c pdd -n "__fish_seen_subcommand_from install_completion" -d "Install shell completion"
146
+
147
+ # pytest-output command
148
+ complete -c pdd -n "__fish_seen_subcommand_from pytest-output" -l json-only -d "Print only JSON"
149
+ complete -c pdd -n "__fish_seen_subcommand_from pytest-output" -a "(__fish_complete_suffix .py)"
150
+
129
151
  # File completion for all commands
130
152
  complete -c pdd -n "__fish_seen_subcommand_from generate example test preprocess fix split change update detect conflicts crash trace bug auto-deps verify" -a "(__fish_complete_suffix .prompt)"
131
153
  complete -c pdd -n "__fish_seen_subcommand_from generate example test preprocess fix split change update detect conflicts crash trace bug auto-deps verify" -a "(__fish_complete_suffix .py .js .java .cpp .rb .go)"
132
154
  complete -c pdd -n "__fish_seen_subcommand_from generate example test preprocess fix split change update detect conflicts crash trace bug auto-deps verify" -a "(__fish_complete_suffix .log .txt .csv)"
133
155
 
134
156
  # Help completion
135
- complete -c pdd -n "__fish_seen_subcommand_from help" -a "generate example test preprocess fix split change update detect conflicts crash trace bug auto-deps verify" -d "Show help for specific command"
157
+ complete -c pdd -n "__fish_seen_subcommand_from help" -a "generate example test preprocess fix split change update detect conflicts crash trace bug auto-deps verify sync setup install_completion pytest-output" -d "Show help for specific command"
pdd/pdd_completion.sh CHANGED
@@ -15,10 +15,10 @@ _pdd() {
15
15
  cword=$COMP_CWORD
16
16
 
17
17
  # Global options
18
- local global_opts="--force --strength --time --temperature --verbose --quiet --output-cost --review-examples --local --help --version"
18
+ local global_opts="--force --strength --time --temperature --verbose --quiet --output-cost --review-examples --local --context --list-contexts --help --version"
19
19
 
20
20
  # Commands
21
- local commands="generate example test preprocess fix split change update detect conflicts crash trace bug auto-deps verify"
21
+ local commands="generate example test preprocess fix split change update detect conflicts crash trace bug auto-deps verify sync setup install_completion pytest-output"
22
22
 
23
23
  # Command-specific options
24
24
  local generate_opts="--output --original-prompt --incremental --env -e"
@@ -36,6 +36,8 @@ _pdd() {
36
36
  local bug_opts="--output --language"
37
37
  local auto_deps_opts="--output --csv --force-scan"
38
38
  local verify_opts="--output-results --output-code --output-program --max-attempts --budget"
39
+ local sync_opts="--max-attempts --budget --skip-verify --skip-tests --target-coverage --log"
40
+ local pytest_output_opts="--json-only"
39
41
 
40
42
  # Complete global options before command
41
43
  if [[ $cword -eq 1 ]]; then
@@ -141,6 +143,20 @@ _pdd() {
141
143
  _complete_files
142
144
  COMPREPLY+=($(compgen -W "$verify_opts" -- "$cur"))
143
145
  ;;
146
+ sync)
147
+ # BASENAME (not a file), offer options
148
+ COMPREPLY+=($(compgen -W "$sync_opts" -- "$cur"))
149
+ ;;
150
+ setup)
151
+ # no command-specific options
152
+ ;;
153
+ install_completion)
154
+ # no command-specific options
155
+ ;;
156
+ pytest-output)
157
+ _complete_files
158
+ COMPREPLY+=($(compgen -W "$pytest_output_opts" -- "$cur"))
159
+ ;;
144
160
  *)
145
161
  COMPREPLY=($(compgen -W "$global_opts" -- "$cur"))
146
162
  ;;
pdd/pdd_completion.zsh CHANGED
@@ -57,6 +57,8 @@ _pdd_global_opts=(
57
57
  '--output-cost[Enable cost tracking and output a CSV file with usage details.]:filename:_files'
58
58
  '--review-examples[Review and optionally exclude few-shot examples before command execution.]'
59
59
  '--local[Run commands locally instead of in the cloud.]'
60
+ '--context[Override automatic .pddrc context]:context-name:_guard'
61
+ '--list-contexts[List available .pddrc contexts and exit]'
60
62
  '--help[Show help message and exit.]'
61
63
  '--version[Show version and exit.]'
62
64
  )
@@ -396,6 +398,54 @@ _pdd_verify() {
396
398
  '*:filename:_files'
397
399
  }
398
400
 
401
+ # sync
402
+ # Usage: pdd [GLOBAL OPTIONS] sync [OPTIONS] BASENAME
403
+ # Options:
404
+ # --max-attempts [INT]
405
+ # --budget [FLOAT]
406
+ # --skip-verify
407
+ # --skip-tests
408
+ # --target-coverage [FLOAT]
409
+ # --log
410
+ # Arg:
411
+ # 1: BASENAME
412
+ _pdd_sync() {
413
+ _arguments -s \
414
+ $_pdd_global_opts \
415
+ '--max-attempts=[Maximum attempts for iterative loops (default 3)]:int' \
416
+ '--budget=[Maximum total cost for sync (default 10.0)]:float' \
417
+ '--skip-verify[Skip the functional verification step]' \
418
+ '--skip-tests[Skip unit test generation and fixing]' \
419
+ '--target-coverage=[Desired code coverage percentage]:float' \
420
+ '--log[Show analysis instead of executing operations]' \
421
+ '1:basename: ' \
422
+ '*: :'
423
+ }
424
+
425
+ # setup (no options)
426
+ _pdd_setup() {
427
+ _arguments -s $_pdd_global_opts
428
+ }
429
+
430
+ # install_completion (no options)
431
+ _pdd_install_completion() {
432
+ _arguments -s $_pdd_global_opts
433
+ }
434
+
435
+ # pytest-output
436
+ # Usage: pdd [GLOBAL OPTIONS] pytest-output [OPTIONS] TEST_FILE
437
+ # Options:
438
+ # --json-only
439
+ # Arg:
440
+ # 1: TEST_FILE
441
+ _pdd_pytest_output() {
442
+ _arguments -s \
443
+ $_pdd_global_opts \
444
+ '--json-only[Print only JSON to stdout]' \
445
+ '1:test-file:_files' \
446
+ '*:filename:_files'
447
+ }
448
+
399
449
  ##
400
450
  # Main PDD completion dispatcher
401
451
  ##
@@ -421,6 +471,10 @@ _pdd() {
421
471
  'bug:Generate a unit test based on incorrect vs desired outputs'
422
472
  'auto-deps:Analyze a prompt and include deps from a directory or glob'
423
473
  'verify:Verify functional correctness using LLM judgment and iteratively fix'
474
+ 'sync:Synchronize prompt, code, examples, tests with analysis'
475
+ 'setup:Interactive setup and completion install'
476
+ 'install_completion:Install shell completion for current shell'
477
+ 'pytest-output:Run pytest and capture structured output'
424
478
  )
425
479
 
426
480
  # If there's no subcommand yet (i.e., user typed only "pdd " or "pdd -<Tab>"), offer global opts or subcommands.
@@ -480,6 +534,18 @@ _pdd() {
480
534
  verify)
481
535
  _pdd_verify
482
536
  ;;
537
+ sync)
538
+ _pdd_sync
539
+ ;;
540
+ setup)
541
+ _pdd_setup
542
+ ;;
543
+ install_completion)
544
+ _pdd_install_completion
545
+ ;;
546
+ pytest-output)
547
+ _pdd_pytest_output
548
+ ;;
483
549
  # If the subcommand is unknown or not typed yet, fall back to showing the list of subcommands.
484
550
  *)
485
551
  _describe -t subcommands 'pdd subcommand' _pdd_subcommands
@@ -9,6 +9,8 @@ import sys
9
9
  import subprocess
10
10
  import json
11
11
  import requests
12
+ import csv
13
+ import importlib.resources
12
14
  from pathlib import Path
13
15
  from typing import Dict, Optional, Tuple, List
14
16
 
@@ -39,26 +41,27 @@ YELLOW = "\033[93m"
39
41
  BOLD = "\033[1m"
40
42
 
41
43
  # 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"""
44
+ SUCCESS_PYTHON_TEMPLATE = """
45
+ Write a python script to print "You did it, <Username>!!!" to the console.
46
+ Do not write anything except that message.
47
+ Capitalize the username."""
48
+
49
+ def _read_packaged_llm_model_csv() -> Tuple[List[str], List[Dict[str, str]]]:
50
+ """Load the packaged CSV (pdd/data/llm_model.csv) and return header + rows.
51
+
52
+ Returns:
53
+ (header_fields, rows) where header_fields is the list of column names
54
+ and rows is a list of dictionaries for each CSV row.
55
+ """
56
+ try:
57
+ csv_text = importlib.resources.files('pdd').joinpath('data/llm_model.csv').read_text()
58
+ except Exception as e:
59
+ raise FileNotFoundError(f"Failed to load default LLM model CSV from package: {e}")
60
+
61
+ reader = csv.DictReader(csv_text.splitlines())
62
+ header = reader.fieldnames or []
63
+ rows = [row for row in reader]
64
+ return header, rows
62
65
 
63
66
  def print_colored(text: str, color: str = WHITE, bold: bool = False) -> None:
64
67
  """Print colored text to console"""
@@ -95,16 +98,60 @@ def print_pdd_logo():
95
98
  ]
96
99
  )
97
100
  print(f"{CYAN}{logo}{RESET}")
98
- print_colored("Supported: OpenAI and Google Gemini (non-Vertex)", WHITE)
101
+ print()
102
+ print_colored("Let's get set up quickly with a solid basic configuration!", WHITE, bold=True)
103
+ print()
104
+ print_colored("Supported: OpenAI, Google Gemini, and Anthropic Claude", WHITE)
99
105
  print_colored("from their respective API endpoints (no third-parties, such as Azure)", WHITE)
100
106
  print()
101
107
 
108
+ def get_csv_variable_names() -> Dict[str, str]:
109
+ """Inspect packaged CSV to determine API key variable names per provider.
110
+
111
+ Focus on direct providers only: OpenAI GPT models (model startswith 'gpt-'),
112
+ Google Gemini (model startswith 'gemini/'), and Anthropic (model startswith 'anthropic/').
113
+ """
114
+ header, rows = _read_packaged_llm_model_csv()
115
+ variable_names: Dict[str, str] = {}
116
+
117
+ for row in rows:
118
+ model = (row.get('model') or '').strip()
119
+ api_key = (row.get('api_key') or '').strip()
120
+ provider = (row.get('provider') or '').strip().upper()
121
+
122
+ if not api_key:
123
+ continue
124
+
125
+ if model.startswith('gpt-') and provider == 'OPENAI':
126
+ variable_names['OPENAI'] = api_key
127
+ elif model.startswith('gemini/') and provider == 'GOOGLE':
128
+ # Prefer direct Gemini key, not Vertex
129
+ variable_names['GOOGLE'] = api_key
130
+ elif model.startswith('anthropic/') and provider == 'ANTHROPIC':
131
+ variable_names['ANTHROPIC'] = api_key
132
+
133
+ # Fallbacks if not detected (keep prior behavior)
134
+ variable_names.setdefault('OPENAI', 'OPENAI_API_KEY')
135
+ # Prefer GEMINI_API_KEY name for Google if present
136
+ variable_names.setdefault('GOOGLE', 'GEMINI_API_KEY')
137
+ variable_names.setdefault('ANTHROPIC', 'ANTHROPIC_API_KEY')
138
+ return variable_names
139
+
102
140
  def discover_api_keys() -> Dict[str, Optional[str]]:
103
141
  """Discover API keys from environment variables"""
142
+ # Get the variable names actually used in CSV template
143
+ csv_vars = get_csv_variable_names()
144
+
104
145
  keys = {
105
146
  'OPENAI_API_KEY': os.getenv('OPENAI_API_KEY'),
106
- 'GOOGLE_API_KEY': os.getenv('GOOGLE_API_KEY') or os.getenv('GEMINI_API_KEY'),
147
+ 'ANTHROPIC_API_KEY': os.getenv('ANTHROPIC_API_KEY'),
107
148
  }
149
+
150
+ # For Google, check both possible environment variables but use CSV template's variable name
151
+ google_var_name = csv_vars.get('GOOGLE', 'GEMINI_API_KEY') # Default to GEMINI_API_KEY
152
+ google_api_key = os.getenv('GEMINI_API_KEY') or os.getenv('GOOGLE_API_KEY')
153
+ keys[google_var_name] = google_api_key
154
+
108
155
  return keys
109
156
 
110
157
  def test_openai_key(api_key: str) -> bool:
@@ -140,6 +187,26 @@ def test_google_key(api_key: str) -> bool:
140
187
  except Exception:
141
188
  return False
142
189
 
190
+ def test_anthropic_key(api_key: str) -> bool:
191
+ """Test Anthropic API key validity"""
192
+ if not api_key or not api_key.strip():
193
+ return False
194
+
195
+ try:
196
+ headers = {
197
+ 'x-api-key': api_key.strip(),
198
+ 'Content-Type': 'application/json'
199
+ }
200
+ response = requests.get(
201
+ 'https://api.anthropic.com/v1/messages',
202
+ headers=headers,
203
+ timeout=10
204
+ )
205
+ # Anthropic returns 400 for invalid request structure but 401/403 for bad keys
206
+ return response.status_code != 401 and response.status_code != 403
207
+ except Exception:
208
+ return False
209
+
143
210
  def test_api_keys(keys: Dict[str, Optional[str]]) -> Dict[str, bool]:
144
211
  """Test all discovered API keys"""
145
212
  results = {}
@@ -153,8 +220,10 @@ def test_api_keys(keys: Dict[str, Optional[str]]) -> Dict[str, bool]:
153
220
  print(f"Testing {key_name}...", end=" ", flush=True)
154
221
  if key_name == 'OPENAI_API_KEY':
155
222
  valid = test_openai_key(key_value)
156
- elif key_name in ['GOOGLE_API_KEY']:
223
+ elif key_name in ['GEMINI_API_KEY', 'GOOGLE_API_KEY']:
157
224
  valid = test_google_key(key_value)
225
+ elif key_name == 'ANTHROPIC_API_KEY':
226
+ valid = test_anthropic_key(key_value)
158
227
  else:
159
228
  valid = False
160
229
 
@@ -181,13 +250,16 @@ def get_user_keys(current_keys: Dict[str, Optional[str]]) -> Dict[str, Optional[
181
250
  print_colored("Get API keys here:", WHITE)
182
251
  print_colored(f" OpenAI {ARROW_RIGHT} https://platform.openai.com/api-keys", CYAN)
183
252
  print_colored(f" Google Gemini {ARROW_RIGHT} https://aistudio.google.com/app/apikey", CYAN)
253
+ print_colored(f" Anthropic {ARROW_RIGHT} https://console.anthropic.com/settings/keys", CYAN)
184
254
  print()
185
255
  print_colored("A free instant starter key is available from Google Gemini (above)", CYAN)
186
256
  print()
187
257
 
188
258
  new_keys = current_keys.copy()
189
259
 
190
- for key_name in ['OPENAI_API_KEY', 'GOOGLE_API_KEY']:
260
+ # Get the actual key names from discovered keys
261
+ key_names = list(current_keys.keys())
262
+ for key_name in key_names:
191
263
  current_value = current_keys.get(key_name, "")
192
264
  status = "found" if current_value else "not found"
193
265
 
@@ -230,7 +302,8 @@ def get_shell_init_file(shell: str) -> str:
230
302
  'fish': home / '.config/fish/config.fish',
231
303
  'csh': home / '.cshrc',
232
304
  'tcsh': home / '.tcshrc',
233
- 'ksh': home / '.kshrc'
305
+ 'ksh': home / '.kshrc',
306
+ 'sh': home / '.profile'
234
307
  }
235
308
 
236
309
  return str(shell_files.get(shell, home / '.bashrc'))
@@ -249,13 +322,13 @@ def create_api_env_script(keys: Dict[str, str], shell: str) -> str:
249
322
  for key, value in valid_keys.items():
250
323
  lines.append(f'setenv {key} "{value}"')
251
324
  return '\n'.join(lines) + '\n'
252
- else: # bash, zsh, ksh and others
325
+ else: # bash, zsh, ksh, sh and others
253
326
  lines = []
254
327
  for key, value in valid_keys.items():
255
328
  lines.append(f'export {key}="{value}"')
256
329
  return '\n'.join(lines) + '\n'
257
330
 
258
- def save_configuration(valid_keys: Dict[str, str]) -> Tuple[List[str], bool]:
331
+ def save_configuration(valid_keys: Dict[str, str]) -> Tuple[List[str], bool, Optional[str]]:
259
332
  """Save configuration to ~/.pdd/ directory"""
260
333
  home = Path.home()
261
334
  pdd_dir = home / '.pdd'
@@ -271,34 +344,60 @@ def save_configuration(valid_keys: Dict[str, str]) -> Tuple[List[str], bool]:
271
344
  shell = detect_shell()
272
345
  api_env_content = create_api_env_script(valid_keys, shell)
273
346
 
274
- # Write api-env file
275
- api_env_file = pdd_dir / 'api-env'
347
+ # Write shell-specific api-env file
348
+ api_env_file = pdd_dir / f'api-env.{shell}'
276
349
  api_env_file.write_text(api_env_content)
277
350
  api_env_file.chmod(0o755)
278
351
  saved_files.append(str(api_env_file))
279
352
 
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
-
353
+ # Create llm_model.csv with models from packaged CSV filtered by provider and available keys
354
+ header_fields, rows = _read_packaged_llm_model_csv()
355
+
356
+ # Keep only direct Google Gemini (model startswith 'gemini/'), OpenAI GPT (gpt-*) and Anthropic (anthropic/*)
357
+ def _is_supported_model(row: Dict[str, str]) -> bool:
358
+ model = (row.get('model') or '').strip()
359
+ if model.startswith('gpt-'):
360
+ return True
361
+ if model.startswith('gemini/'):
362
+ return True
363
+ if model.startswith('anthropic/'):
364
+ return True
365
+ return False
366
+
367
+ # Filter rows by supported models and by api_key presence in valid_keys
368
+ filtered_rows: List[Dict[str, str]] = []
369
+ for row in rows:
370
+ if not _is_supported_model(row):
371
+ continue
372
+ api_key_name = (row.get('api_key') or '').strip()
373
+ # Include only if we have a validated key for this row
374
+ if api_key_name and api_key_name in valid_keys:
375
+ filtered_rows.append(row)
376
+
377
+ # Write out the filtered CSV to ~/.pdd/llm_model.csv preserving column order
291
378
  llm_model_file = pdd_dir / 'llm_model.csv'
292
- llm_model_file.write_text('\n'.join(valid_lines) + '\n')
379
+ with llm_model_file.open('w', newline='') as f:
380
+ writer = csv.DictWriter(f, fieldnames=header_fields)
381
+ writer.writeheader()
382
+ for row in filtered_rows:
383
+ writer.writerow({k: row.get(k, '') for k in header_fields})
293
384
  saved_files.append(str(llm_model_file))
294
385
 
295
386
  # Update shell init file
296
387
  init_file_path = get_shell_init_file(shell)
297
388
  init_file = Path(init_file_path)
389
+ init_file_updated = None
298
390
 
299
391
  source_line = f'[ -f "{api_env_file}" ] && source "{api_env_file}"'
300
392
  if shell == 'fish':
301
393
  source_line = f'test -f "{api_env_file}"; and source "{api_env_file}"'
394
+ elif shell in ['csh', 'tcsh']:
395
+ source_line = f'if ( -f "{api_env_file}" ) source "{api_env_file}"'
396
+ elif shell == 'sh':
397
+ source_line = f'[ -f "{api_env_file}" ] && . "{api_env_file}"'
398
+
399
+ # Ensure parent directory exists (important for fish shell)
400
+ init_file.parent.mkdir(parents=True, exist_ok=True)
302
401
 
303
402
  # Check if source line already exists
304
403
  if init_file.exists():
@@ -306,15 +405,17 @@ def save_configuration(valid_keys: Dict[str, str]) -> Tuple[List[str], bool]:
306
405
  if str(api_env_file) not in content:
307
406
  with init_file.open('a') as f:
308
407
  f.write(f'\n# PDD API environment\n{source_line}\n')
408
+ init_file_updated = str(init_file)
309
409
  else:
310
410
  init_file.write_text(f'# PDD API environment\n{source_line}\n')
411
+ init_file_updated = str(init_file)
311
412
 
312
- return saved_files, created_pdd_dir
413
+ return saved_files, created_pdd_dir, init_file_updated
313
414
 
314
415
  def create_sample_prompt():
315
416
  """Create the sample prompt file"""
316
- prompt_file = Path('hello_you_python.prompt')
317
- prompt_file.write_text(HELLO_PYTHON_TEMPLATE)
417
+ prompt_file = Path('success_python.prompt')
418
+ prompt_file.write_text(SUCCESS_PYTHON_TEMPLATE)
318
419
  return str(prompt_file)
319
420
 
320
421
  def show_menu(keys: Dict[str, Optional[str]], test_results: Dict[str, bool]) -> str:
@@ -325,7 +426,9 @@ def show_menu(keys: Dict[str, Optional[str]], test_results: Dict[str, bool]) ->
325
426
 
326
427
  # Show current status
327
428
  print_colored("Current API Key Status:", WHITE, bold=True)
328
- for key_name in ['OPENAI_API_KEY', 'GOOGLE_API_KEY']:
429
+ # Get the actual key names from discovered keys
430
+ key_names = list(keys.keys())
431
+ for key_name in key_names:
329
432
  key_value = keys.get(key_name)
330
433
  if key_value:
331
434
  status = f"{CHECK_MARK} Valid" if test_results.get(key_name) else f"{CROSS_MARK} Invalid"
@@ -356,7 +459,7 @@ def show_menu(keys: Dict[str, Optional[str]], test_results: Dict[str, bool]) ->
356
459
  print_colored("\n\nSetup cancelled.", YELLOW)
357
460
  sys.exit(0)
358
461
 
359
- def create_exit_summary(saved_files: List[str], created_pdd_dir: bool, sample_prompt_file: str, shell: str) -> str:
462
+ def create_exit_summary(saved_files: List[str], created_pdd_dir: bool, sample_prompt_file: str, shell: str, valid_keys: Dict[str, str], init_file_updated: Optional[str] = None) -> str:
360
463
  """Create comprehensive exit summary"""
361
464
  summary_lines = [
362
465
  "\n\n\n\n\n",
@@ -364,22 +467,37 @@ def create_exit_summary(saved_files: List[str], created_pdd_dir: bool, sample_pr
364
467
  "PDD Setup Complete!",
365
468
  create_fat_divider(),
366
469
  "",
367
- "Files created and configured:",
470
+ "API Keys Configured:",
368
471
  ""
369
472
  ]
370
473
 
474
+ # Add configured API keys information
475
+ if valid_keys:
476
+ for key_name, key_value in valid_keys.items():
477
+ # Show just the first and last few characters for security
478
+ masked_key = f"{key_value[:8]}...{key_value[-4:]}" if len(key_value) > 12 else "***"
479
+ summary_lines.append(f" {key_name}: {masked_key}")
480
+ summary_lines.extend(["", "Files created and configured:", ""])
481
+ else:
482
+ summary_lines.extend([" None", "", "Files created and configured:", ""])
483
+
371
484
  # File descriptions with alignment
372
485
  file_descriptions = []
373
486
  if created_pdd_dir:
374
487
  file_descriptions.append(("~/.pdd/", "PDD configuration directory"))
375
488
 
376
489
  for file_path in saved_files:
377
- if 'api-env' in file_path:
378
- file_descriptions.append((file_path, "API environment variables"))
490
+ if 'api-env.' in file_path:
491
+ file_descriptions.append((file_path, f"API environment variables ({shell} shell)"))
379
492
  elif 'llm_model.csv' in file_path:
380
493
  file_descriptions.append((file_path, "LLM model configuration"))
381
494
 
382
495
  file_descriptions.append((sample_prompt_file, "Sample prompt for testing"))
496
+
497
+ # Add shell init file if it was updated
498
+ if init_file_updated:
499
+ file_descriptions.append((init_file_updated, f"Shell startup file (updated to source API environment)"))
500
+
383
501
  file_descriptions.append(("PDD-SETUP-SUMMARY.txt", "This summary"))
384
502
 
385
503
  # Find max file path length for alignment
@@ -397,10 +515,11 @@ def create_exit_summary(saved_files: List[str], created_pdd_dir: bool, sample_pr
397
515
  f"1. Reload your shell environment:"
398
516
  ])
399
517
 
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}"
518
+ # Shell-specific source command for manual reloading
519
+ api_env_path = f"{Path.home()}/.pdd/api-env.{shell}"
520
+ # Use dot command for sh shell, source for others
521
+ if shell == 'sh':
522
+ source_cmd = f". {api_env_path}"
404
523
  else:
405
524
  source_cmd = f"source {api_env_path}"
406
525
 
@@ -408,7 +527,7 @@ def create_exit_summary(saved_files: List[str], created_pdd_dir: bool, sample_pr
408
527
  f" {source_cmd}",
409
528
  "",
410
529
  f"2. Generate code from the sample prompt:",
411
- f" pdd generate hello_you_python.prompt",
530
+ f" pdd generate success_python.prompt",
412
531
  "",
413
532
  create_divider(),
414
533
  "",
@@ -428,7 +547,8 @@ def create_exit_summary(saved_files: List[str], created_pdd_dir: bool, sample_pr
428
547
  "",
429
548
  f"{BULLET} As you get comfortable, learn configuration settings, including the .pddrc file, PDD_GENERATE_OUTPUT_PATH, and PDD_TEST_OUTPUT_PATH",
430
549
  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",
550
+ f"{BULLET} For ongoing substantial projects, learn about llm_model.csv and the --strength,",
551
+ f" --temperature, and --time options to optimize model cost, latency, and output quality",
432
552
  "",
433
553
  f"{BULLET} Use 'pdd --help' to explore all available commands",
434
554
  "",
@@ -478,12 +598,12 @@ def main():
478
598
  print_colored(f"{create_divider()}", CYAN)
479
599
 
480
600
  try:
481
- saved_files, created_pdd_dir = save_configuration(valid_keys)
601
+ saved_files, created_pdd_dir, init_file_updated = save_configuration(valid_keys)
482
602
  sample_prompt_file = create_sample_prompt()
483
603
  shell = detect_shell()
484
604
 
485
605
  # Create and display summary
486
- summary = create_exit_summary(saved_files, created_pdd_dir, sample_prompt_file, shell)
606
+ summary = create_exit_summary(saved_files, created_pdd_dir, sample_prompt_file, shell, valid_keys, init_file_updated)
487
607
 
488
608
  # Write summary to file
489
609
  summary_file = Path('PDD-SETUP-SUMMARY.txt')
@@ -498,7 +618,11 @@ def main():
498
618
  print_colored(line, YELLOW, bold=True)
499
619
  elif line == create_divider():
500
620
  print_colored(line, CYAN)
501
- elif line.startswith("QUICK START:") or line.startswith("LEARN MORE:") or line.startswith("TIPS:"):
621
+ elif line.startswith("API Keys Configured:") or line.startswith("Files created and configured:"):
622
+ print_colored(line, CYAN, bold=True)
623
+ elif line.startswith("QUICK START:"):
624
+ print_colored(line, YELLOW, bold=True)
625
+ elif line.startswith("LEARN MORE:") or line.startswith("TIPS:"):
502
626
  print_colored(line, CYAN, bold=True)
503
627
  elif "IMPORTANT:" in line or "Problems?" in line:
504
628
  print_colored(line, YELLOW, bold=True)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pdd-cli
3
- Version: 0.0.58
3
+ Version: 0.0.60
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.60-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.60).
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=DkLNT664TDBz1q2bKJ1z7tY7zRdVTF6dTex1j5DokZU,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
@@ -45,9 +45,9 @@ pdd/llm_invoke.py,sha256=US8J9oZZdVgLIjcmOI-YyHu6z2un43ZN811IcQBmAIA,96728
45
45
  pdd/load_prompt_template.py,sha256=stt42Og0PzXy0N_bsaivk5e2l5z_BnHiXIJZM14oVWw,2673
46
46
  pdd/logo_animation.py,sha256=n6HJWzuFze2csAAW2-zbxfjvWFYRI4hIdwVBtHBOkj4,20782
47
47
  pdd/mcp_config.json,sha256=D3ctWHlShvltbtH37zbYb6smVE0V80_lGjDKDIqsSBE,124
48
- pdd/pdd_completion.fish,sha256=K4qT52-9LG6apKVH0Bgfqk1Kqfketb47ge_6mcacVFE,12089
49
- pdd/pdd_completion.sh,sha256=DI2gJc7IxqjgKyEgjmoi8ev3f9Gs3LJrRgSQ-u2T6ms,5844
50
- pdd/pdd_completion.zsh,sha256=f4BxXrLZUEcFeIUmXWdnFOTnTpJBFZ8KGpnIdjDdKFo,15353
48
+ pdd/pdd_completion.fish,sha256=pfEyBWzFp3matC_SW2BTDNaBJybYKUEi3YpPE-D6AKU,13776
49
+ pdd/pdd_completion.sh,sha256=xgx-g6aeCCrlh6loeLyJN5jCsX15YXrWyT1U499p3C0,6490
50
+ pdd/pdd_completion.zsh,sha256=V9-V8jqw3osjlXNOvjYMJf0E771-_EQe-Cboo1xzPvY,17090
51
51
  pdd/postprocess.py,sha256=mNw3iSDxE-eTYo3QwJCj_EmdEnnB5ysUN62YPapC_IM,4433
52
52
  pdd/postprocess_0.py,sha256=OW17GyCFLYErCyWh2tL4syuho3q2yFf2wyekQ4BLdPM,2168
53
53
  pdd/preprocess.py,sha256=75-J1smdi1Uq7gRQRLtVdkIfwltkeIvIZE-TkxxxCz0,12326
@@ -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=h3MW7yr5tSlreq3u26aUfQqFTneQ-wNCoq0vwIYxqMY,24087
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.60.dist-info/licenses/LICENSE,sha256=kvTJnnxPVTYlGKSY4ZN1kzdmJ0lxRdNWxgupaB27zsU,1066
115
+ pdd_cli-0.0.60.dist-info/METADATA,sha256=4vSMytmX5LPnADjAlOVkZPCSf--g5FWbATtu9zo7Qqs,12687
116
+ pdd_cli-0.0.60.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
117
+ pdd_cli-0.0.60.dist-info/entry_points.txt,sha256=Kr8HtNVb8uHZtQJNH4DnF8j7WNgWQbb7_Pw5hECSR-I,36
118
+ pdd_cli-0.0.60.dist-info/top_level.txt,sha256=xjnhIACeMcMeDfVNREgQZl4EbTni2T11QkL5r7E-sbE,4
119
+ pdd_cli-0.0.60.dist-info/RECORD,,