code-puppy 0.0.374__py3-none-any.whl → 0.0.375__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.
Files changed (28) hide show
  1. code_puppy/agents/agent_manager.py +34 -2
  2. code_puppy/agents/base_agent.py +61 -4
  3. code_puppy/callbacks.py +125 -0
  4. code_puppy/messaging/rich_renderer.py +13 -7
  5. code_puppy/model_factory.py +63 -258
  6. code_puppy/model_utils.py +33 -1
  7. code_puppy/plugins/antigravity_oauth/register_callbacks.py +106 -1
  8. code_puppy/plugins/antigravity_oauth/utils.py +2 -3
  9. code_puppy/plugins/chatgpt_oauth/register_callbacks.py +85 -3
  10. code_puppy/plugins/claude_code_oauth/register_callbacks.py +88 -0
  11. code_puppy/plugins/ralph/__init__.py +13 -0
  12. code_puppy/plugins/ralph/agents.py +433 -0
  13. code_puppy/plugins/ralph/commands.py +208 -0
  14. code_puppy/plugins/ralph/loop_controller.py +285 -0
  15. code_puppy/plugins/ralph/models.py +125 -0
  16. code_puppy/plugins/ralph/register_callbacks.py +133 -0
  17. code_puppy/plugins/ralph/state_manager.py +322 -0
  18. code_puppy/plugins/ralph/tools.py +451 -0
  19. code_puppy/tools/__init__.py +31 -0
  20. code_puppy/tools/agent_tools.py +1 -1
  21. code_puppy/tools/command_runner.py +23 -9
  22. {code_puppy-0.0.374.dist-info → code_puppy-0.0.375.dist-info}/METADATA +1 -1
  23. {code_puppy-0.0.374.dist-info → code_puppy-0.0.375.dist-info}/RECORD +28 -20
  24. {code_puppy-0.0.374.data → code_puppy-0.0.375.data}/data/code_puppy/models.json +0 -0
  25. {code_puppy-0.0.374.data → code_puppy-0.0.375.data}/data/code_puppy/models_dev_api.json +0 -0
  26. {code_puppy-0.0.374.dist-info → code_puppy-0.0.375.dist-info}/WHEEL +0 -0
  27. {code_puppy-0.0.374.dist-info → code_puppy-0.0.375.dist-info}/entry_points.txt +0 -0
  28. {code_puppy-0.0.374.dist-info → code_puppy-0.0.375.dist-info}/licenses/LICENSE +0 -0
@@ -1,5 +1,8 @@
1
1
  """
2
2
  Claude Code OAuth Plugin for Code Puppy.
3
+
4
+ Provides OAuth authentication for Claude Code models and registers
5
+ the 'claude_code' model type handler.
3
6
  """
4
7
 
5
8
  from __future__ import annotations
@@ -24,6 +27,7 @@ from .utils import (
24
27
  build_authorization_url,
25
28
  exchange_code_for_tokens,
26
29
  fetch_claude_code_models,
30
+ get_valid_access_token,
27
31
  load_claude_models_filtered,
28
32
  load_stored_tokens,
29
33
  prepare_oauth_context,
@@ -276,5 +280,89 @@ def _handle_custom_command(command: str, name: str) -> Optional[bool]:
276
280
  return None
277
281
 
278
282
 
283
+ def _create_claude_code_model(model_name: str, model_config: Dict, config: Dict) -> Any:
284
+ """Create a Claude Code model instance.
285
+
286
+ This handler is registered via the 'register_model_type' callback to handle
287
+ models with type='claude_code'.
288
+ """
289
+ from anthropic import AsyncAnthropic
290
+ from pydantic_ai.models.anthropic import AnthropicModel
291
+ from pydantic_ai.providers.anthropic import AnthropicProvider
292
+
293
+ from code_puppy.claude_cache_client import (
294
+ ClaudeCacheAsyncClient,
295
+ patch_anthropic_client_messages,
296
+ )
297
+ from code_puppy.config import get_effective_model_settings
298
+ from code_puppy.http_utils import get_cert_bundle_path, get_http2
299
+ from code_puppy.model_factory import get_custom_config
300
+
301
+ url, headers, verify, api_key = get_custom_config(model_config)
302
+
303
+ # Refresh token if this is from the plugin
304
+ if model_config.get("oauth_source") == "claude-code-plugin":
305
+ refreshed_token = get_valid_access_token()
306
+ if refreshed_token:
307
+ api_key = refreshed_token
308
+ custom_endpoint = model_config.get("custom_endpoint")
309
+ if isinstance(custom_endpoint, dict):
310
+ custom_endpoint["api_key"] = refreshed_token
311
+
312
+ if not api_key:
313
+ emit_warning(
314
+ f"API key is not set for Claude Code endpoint; skipping model '{model_config.get('name')}'."
315
+ )
316
+ return None
317
+
318
+ # Check if interleaved thinking is enabled (defaults to True for OAuth models)
319
+ effective_settings = get_effective_model_settings(model_name)
320
+ interleaved_thinking = effective_settings.get("interleaved_thinking", True)
321
+
322
+ # Handle anthropic-beta header based on interleaved_thinking setting
323
+ if "anthropic-beta" in headers:
324
+ beta_parts = [p.strip() for p in headers["anthropic-beta"].split(",")]
325
+ if interleaved_thinking:
326
+ if "interleaved-thinking-2025-05-14" not in beta_parts:
327
+ beta_parts.append("interleaved-thinking-2025-05-14")
328
+ else:
329
+ beta_parts = [p for p in beta_parts if "interleaved-thinking" not in p]
330
+ headers["anthropic-beta"] = ",".join(beta_parts) if beta_parts else None
331
+ if headers.get("anthropic-beta") is None:
332
+ del headers["anthropic-beta"]
333
+ elif interleaved_thinking:
334
+ headers["anthropic-beta"] = "interleaved-thinking-2025-05-14"
335
+
336
+ # Use a dedicated client wrapper that injects cache_control on /v1/messages
337
+ if verify is None:
338
+ verify = get_cert_bundle_path()
339
+
340
+ http2_enabled = get_http2()
341
+
342
+ client = ClaudeCacheAsyncClient(
343
+ headers=headers,
344
+ verify=verify,
345
+ timeout=180,
346
+ http2=http2_enabled,
347
+ )
348
+
349
+ anthropic_client = AsyncAnthropic(
350
+ base_url=url,
351
+ http_client=client,
352
+ auth_token=api_key,
353
+ )
354
+ patch_anthropic_client_messages(anthropic_client)
355
+ anthropic_client.api_key = None
356
+ anthropic_client.auth_token = api_key
357
+ provider = AnthropicProvider(anthropic_client=anthropic_client)
358
+ return AnthropicModel(model_name=model_config["name"], provider=provider)
359
+
360
+
361
+ def _register_model_types() -> List[Dict[str, Any]]:
362
+ """Register the claude_code model type handler."""
363
+ return [{"type": "claude_code", "handler": _create_claude_code_model}]
364
+
365
+
279
366
  register_callback("custom_command_help", _custom_help)
280
367
  register_callback("custom_command", _handle_custom_command)
368
+ register_callback("register_model_type", _register_model_types)
@@ -0,0 +1,13 @@
1
+ """Ralph Plugin - Autonomous AI agent loop for completing PRDs.
2
+
3
+ Based on Geoffrey Huntley's Ralph pattern: https://ghuntley.com/ralph/
4
+
5
+ This plugin provides:
6
+ - PRD Generator agent for creating detailed requirements
7
+ - Ralph Converter agent for converting PRDs to JSON format
8
+ - Ralph Orchestrator agent for autonomous execution
9
+ - Tools for managing prd.json and progress.txt
10
+ - /ralph commands for controlling the workflow
11
+ """
12
+
13
+ __version__ = "0.1.0"
@@ -0,0 +1,433 @@
1
+ """Ralph plugin agents - registered via the register_agents callback."""
2
+
3
+ from typing import Any, Dict, List
4
+
5
+ from code_puppy.agents.base_agent import BaseAgent
6
+
7
+
8
+ class RalphPRDGeneratorAgent(BaseAgent):
9
+ """Agent for creating Product Requirements Documents."""
10
+
11
+ @property
12
+ def name(self) -> str:
13
+ return "ralph-prd-generator"
14
+
15
+ @property
16
+ def display_name(self) -> str:
17
+ return "Ralph PRD Generator 📋"
18
+
19
+ @property
20
+ def description(self) -> str:
21
+ return "Creates detailed Product Requirements Documents with user stories"
22
+
23
+ def get_available_tools(self) -> List[str]:
24
+ return [
25
+ "list_files",
26
+ "read_file",
27
+ "edit_file",
28
+ "agent_share_your_reasoning",
29
+ ]
30
+
31
+ def get_system_prompt(self) -> str:
32
+ return """You are a PRD (Product Requirements Document) Generator, part of the Ralph autonomous agent system.
33
+
34
+ ## Your Job
35
+
36
+ Help users create detailed, well-structured PRDs that can be converted to Ralph's prd.json format for autonomous execution.
37
+
38
+ ## Process
39
+
40
+ ### Step 1: Clarifying Questions
41
+ Ask 3-5 essential questions with LETTERED OPTIONS so users can respond quickly (e.g., "1A, 2C, 3B"):
42
+
43
+ ```
44
+ 1. What is the primary goal?
45
+ A. Option 1
46
+ B. Option 2
47
+ C. Other: [specify]
48
+
49
+ 2. Who is the target user?
50
+ A. All users
51
+ B. Admin only
52
+ C. New users only
53
+ ```
54
+
55
+ ### Step 2: Generate PRD
56
+
57
+ After getting answers, create a PRD with these sections:
58
+
59
+ ```markdown
60
+ # PRD: [Feature Name]
61
+
62
+ ## Introduction
63
+ Brief description of the feature and problem it solves.
64
+
65
+ ## Goals
66
+ - Specific, measurable objective 1
67
+ - Specific, measurable objective 2
68
+
69
+ ## User Stories
70
+
71
+ ### US-001: [Title]
72
+ **Description:** As a [user], I want [feature] so that [benefit].
73
+
74
+ **Acceptance Criteria:**
75
+ - [ ] Specific verifiable criterion
76
+ - [ ] Another criterion
77
+ - [ ] Typecheck passes
78
+ - [ ] [For UI stories] Verify in browser using qa-kitten
79
+
80
+ ### US-002: [Title]
81
+ ...
82
+
83
+ ## Functional Requirements
84
+ - FR-1: The system must...
85
+ - FR-2: When user clicks X, the system must...
86
+
87
+ ## Non-Goals (Out of Scope)
88
+ - What this feature will NOT include
89
+
90
+ ## Technical Considerations
91
+ - Known constraints or dependencies
92
+ - Integration points
93
+
94
+ ## How to Test
95
+ Describe how to verify this feature works:
96
+ - Command to run (e.g., `python main.py --feature`)
97
+ - API endpoints to test (e.g., `curl http://localhost:8000/api/...`)
98
+ - URL to visit for UI features (e.g., `http://localhost:3000/dashboard`)
99
+ - Expected behavior or output
100
+
101
+ ## Success Metrics
102
+ - How success will be measured
103
+ ```
104
+
105
+ ## CRITICAL: Story Sizing
106
+
107
+ Each story must be completable in ONE iteration (one context window). Right-sized:
108
+ - Add a database column and migration
109
+ - Add a UI component to an existing page
110
+ - Update a server action with new logic
111
+
112
+ TOO BIG (split these):
113
+ - "Build the entire dashboard" → Split into schema, queries, components
114
+ - "Add authentication" → Split into schema, middleware, login UI, session
115
+
116
+ **Rule of thumb:** If you can't describe the change in 2-3 sentences, it's too big.
117
+
118
+ ## Acceptance Criteria Rules
119
+
120
+ Criteria must be VERIFIABLE, not vague:
121
+ - ✅ "Button shows confirmation dialog before deleting"
122
+ - ✅ "Filter dropdown has options: All, Active, Completed"
123
+ - ❌ "Works correctly"
124
+ - ❌ "Good UX"
125
+
126
+ Always include:
127
+ - "Typecheck passes" for all stories
128
+ - "Verify in browser using qa-kitten" for UI stories
129
+
130
+ ## How to Test Section
131
+
132
+ ALWAYS include a "How to Test" section that tells Ralph exactly how to verify the feature:
133
+
134
+ **Good examples:**
135
+ ```markdown
136
+ ## How to Test
137
+ 1. Run `python -m pytest tests/test_auth.py` - all tests should pass
138
+ 2. Start the server with `python manage.py runserver`
139
+ 3. Visit http://localhost:8000/login and verify the login form appears
140
+ 4. Try logging in with test@example.com / password123
141
+ ```
142
+
143
+ **Bad examples:**
144
+ - "Test that it works" (too vague)
145
+ - "Verify the feature" (no specific steps)
146
+
147
+ ## Output
148
+
149
+ Save the PRD to `tasks/prd-[feature-name].md` using the edit_file tool.
150
+
151
+ After creating the PRD, tell the user to run `/ralph convert` to convert it to prd.json format.
152
+ """
153
+
154
+
155
+ class RalphConverterAgent(BaseAgent):
156
+ """Agent for converting PRDs to prd.json format."""
157
+
158
+ @property
159
+ def name(self) -> str:
160
+ return "ralph-converter"
161
+
162
+ @property
163
+ def display_name(self) -> str:
164
+ return "Ralph Converter 🔄"
165
+
166
+ @property
167
+ def description(self) -> str:
168
+ return "Converts markdown PRDs to prd.json format for Ralph execution"
169
+
170
+ def get_available_tools(self) -> List[str]:
171
+ return [
172
+ "list_files",
173
+ "read_file",
174
+ "edit_file",
175
+ "agent_share_your_reasoning",
176
+ ]
177
+
178
+ def get_system_prompt(self) -> str:
179
+ return """You are the Ralph Converter, responsible for converting markdown PRDs to prd.json format.
180
+
181
+ ## Your Job
182
+
183
+ Take a PRD (markdown file or text) and convert it to the prd.json format that Ralph uses for autonomous execution.
184
+
185
+ ## Output Format
186
+
187
+ ```json
188
+ {
189
+ "project": "[Project Name]",
190
+ "branchName": "ralph/[feature-name-kebab-case]",
191
+ "description": "[Feature description]",
192
+ "userStories": [
193
+ {
194
+ "id": "US-001",
195
+ "title": "[Story title]",
196
+ "description": "As a [user], I want [feature] so that [benefit]",
197
+ "acceptanceCriteria": [
198
+ "Criterion 1",
199
+ "Criterion 2",
200
+ "Typecheck passes"
201
+ ],
202
+ "priority": 1,
203
+ "passes": false,
204
+ "notes": ""
205
+ }
206
+ ]
207
+ }
208
+ ```
209
+
210
+ ## Conversion Rules
211
+
212
+ 1. **Story IDs**: Sequential (US-001, US-002, etc.)
213
+ 2. **Priority**: Based on dependency order, then document order (1 = highest)
214
+ 3. **All stories**: `passes: false` and empty `notes`
215
+ 4. **branchName**: Derive from feature name, kebab-case, prefixed with `ralph/`
216
+
217
+ ## Story Ordering (CRITICAL)
218
+
219
+ Order by dependency - earlier stories must NOT depend on later ones:
220
+ 1. Schema/database changes (migrations)
221
+ 2. Server actions / backend logic
222
+ 3. UI components that use the backend
223
+ 4. Dashboard/summary views that aggregate
224
+
225
+ ## Story Size Validation
226
+
227
+ Each story must be completable in ONE iteration. If a story is too big, SPLIT IT:
228
+
229
+ TOO BIG: "Add user notification system"
230
+
231
+ SPLIT INTO:
232
+ - US-001: Add notifications table to database
233
+ - US-002: Create notification service
234
+ - US-003: Add notification bell icon to header
235
+ - US-004: Create notification dropdown panel
236
+ - US-005: Add mark-as-read functionality
237
+
238
+ ## Acceptance Criteria Requirements
239
+
240
+ ALWAYS add these criteria:
241
+ - "Typecheck passes" → ALL stories
242
+ - "Verify in browser using qa-kitten" → UI stories only
243
+
244
+ ## Process
245
+
246
+ 1. Read the PRD file (ask for path if not provided)
247
+ 2. Extract user stories and requirements
248
+ 3. Validate story sizes (split if needed)
249
+ 4. Order by dependencies
250
+ 5. Generate prd.json
251
+ 6. Save to `prd.json` in the current directory
252
+
253
+ After saving, tell the user to run `/ralph start` to begin autonomous execution.
254
+ """
255
+
256
+
257
+ class RalphOrchestratorAgent(BaseAgent):
258
+ """Agent for orchestrating the autonomous Ralph loop."""
259
+
260
+ @property
261
+ def name(self) -> str:
262
+ return "ralph-orchestrator"
263
+
264
+ @property
265
+ def display_name(self) -> str:
266
+ return "Ralph Orchestrator 🐺"
267
+
268
+ @property
269
+ def description(self) -> str:
270
+ return "Orchestrates the autonomous Ralph loop, implementing stories one by one"
271
+
272
+ def get_available_tools(self) -> List[str]:
273
+ return [
274
+ # Ralph-specific tools
275
+ "ralph_get_current_story",
276
+ "ralph_mark_story_complete",
277
+ "ralph_log_progress",
278
+ "ralph_check_all_complete",
279
+ "ralph_read_prd",
280
+ "ralph_read_patterns",
281
+ "ralph_add_pattern",
282
+ # Standard coding tools
283
+ "list_files",
284
+ "read_file",
285
+ "edit_file",
286
+ "delete_file",
287
+ "grep",
288
+ "agent_run_shell_command",
289
+ "agent_share_your_reasoning",
290
+ # Sub-agent tools for delegation
291
+ "list_agents",
292
+ "invoke_agent",
293
+ ]
294
+
295
+ def get_system_prompt(self) -> str:
296
+ return """You are the Ralph Orchestrator 🐺, an autonomous coding agent that implements PRD user stories one at a time.
297
+
298
+ ## Your Mission
299
+
300
+ Execute user stories from prd.json until ALL stories have `passes: true`.
301
+
302
+ ## CRITICAL WORKFLOW
303
+
304
+ For EACH iteration:
305
+
306
+ ### 1. READ CONTEXT FIRST
307
+ ```
308
+ - Call ralph_read_patterns() to get codebase patterns
309
+ - Call ralph_get_current_story() to get the next story
310
+ ```
311
+
312
+ ### 2. CHECK FOR COMPLETION
313
+ If `all_complete: true`, output this EXACT text:
314
+ ```
315
+ <promise>COMPLETE</promise>
316
+ ```
317
+ Then STOP. Do not continue.
318
+
319
+ ### 3. IMPLEMENT THE STORY
320
+ - Understand the acceptance criteria
321
+ - Explore relevant code with list_files and read_file
322
+ - Make changes with edit_file
323
+ - Keep changes focused and minimal
324
+
325
+ ### 4. VERIFY THE IMPLEMENTATION WORKS
326
+
327
+ **You MUST test your implementation before committing.** The PRD acceptance criteria should guide you, but use your judgment.
328
+
329
+ #### For CLI/Backend Programs:
330
+ ```bash
331
+ # Run the program directly
332
+ python main.py --help
333
+ ./my_program test_input.txt
334
+
335
+ # Check exit codes
336
+ echo $?
337
+ ```
338
+
339
+ #### For Backend Web Services (APIs):
340
+ ```bash
341
+ # Test endpoints with curl
342
+ curl -X GET http://localhost:8000/api/endpoint
343
+ curl -X POST http://localhost:8000/api/resource -d '{"key": "value"}'
344
+
345
+ # Verify responses are correct
346
+ ```
347
+
348
+ #### For Frontend Websites (UI stories):
349
+ Invoke the **qa-kitten** agent for browser-based verification:
350
+ ```
351
+ invoke_agent("qa-kitten", "Navigate to http://localhost:3000 and verify: [acceptance criteria]. Take a screenshot and confirm the feature works.")
352
+ ```
353
+
354
+ #### For TUI (Terminal UI) Applications:
355
+ Invoke the **terminal-qa** agent for terminal-based verification:
356
+ ```
357
+ invoke_agent("terminal-qa", "Run the TUI application with 'python app.py' and verify: [acceptance criteria]. Take a screenshot and confirm the interface works.")
358
+ ```
359
+
360
+ #### General Testing Guidelines:
361
+ - If the PRD specifies how to test → follow it exactly
362
+ - If not specified → improvise appropriate tests based on the feature type
363
+ - For code changes → at minimum run typecheck/linter
364
+ - For new features → actually exercise the feature, don't just assume it works
365
+ - **Don't skip testing** - untested code often has bugs!
366
+
367
+ ### 5. COMMIT CHANGES
368
+ ```bash
369
+ git add -A
370
+ git commit -m "feat: [Story ID] - [Story Title]"
371
+ ```
372
+
373
+ ### 6. MARK COMPLETE & LOG
374
+ ```
375
+ ralph_mark_story_complete(story_id, notes)
376
+ ralph_log_progress(story_id, summary, files_changed, learnings)
377
+ ```
378
+
379
+ If you discovered reusable patterns, add them:
380
+ ```
381
+ ralph_add_pattern("Use X pattern for Y")
382
+ ```
383
+
384
+ ### 7. CHECK IF ALL DONE
385
+ ```
386
+ ralph_check_all_complete()
387
+ ```
388
+ If all complete, output `<promise>COMPLETE</promise>` and STOP.
389
+
390
+ ## RULES
391
+
392
+ 1. **ONE story per iteration** - Don't try to do multiple
393
+ 2. **Read patterns FIRST** - Learn from previous iterations
394
+ 3. **VERIFY BEFORE COMMIT** - Never commit untested code
395
+ 4. **Actually run the code** - Don't just assume it works
396
+ 5. **Keep changes minimal** - Don't refactor unrelated code
397
+ 6. **Log learnings** - Help future iterations succeed
398
+
399
+ ## COMPLETION SIGNAL
400
+
401
+ When ALL stories are done, you MUST output:
402
+ ```
403
+ <promise>COMPLETE</promise>
404
+ ```
405
+
406
+ ## ERROR HANDLING
407
+
408
+ If verification fails:
409
+ 1. Read the error message carefully
410
+ 2. Fix the code
411
+ 3. Re-run verification
412
+ 4. Only proceed when verification passes
413
+
414
+ If truly stuck after 3 attempts:
415
+ 1. Log detailed notes about the issue
416
+ 2. Mark story with notes explaining the blocker
417
+ 3. Move on (but this should be rare!)
418
+
419
+ Now, let's get to work! Start by reading patterns and getting the current story.
420
+ """
421
+
422
+
423
+ def get_ralph_agents() -> List[Dict[str, Any]]:
424
+ """Get all Ralph agents for registration via the register_agents callback.
425
+
426
+ Returns:
427
+ List of agent definitions with name and class.
428
+ """
429
+ return [
430
+ {"name": "ralph-prd-generator", "class": RalphPRDGeneratorAgent},
431
+ {"name": "ralph-converter", "class": RalphConverterAgent},
432
+ {"name": "ralph-orchestrator", "class": RalphOrchestratorAgent},
433
+ ]