ralphx 0.3.4__py3-none-any.whl → 0.3.5__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 (38) hide show
  1. ralphx/__init__.py +1 -1
  2. ralphx/api/routes/auth.py +703 -94
  3. ralphx/api/routes/config.py +3 -56
  4. ralphx/api/routes/export_import.py +6 -9
  5. ralphx/api/routes/loops.py +4 -4
  6. ralphx/api/routes/planning.py +19 -5
  7. ralphx/api/routes/templates.py +2 -2
  8. ralphx/api/routes/workflows.py +1 -22
  9. ralphx/cli.py +4 -1
  10. ralphx/core/auth.py +346 -171
  11. ralphx/core/database.py +588 -164
  12. ralphx/core/executor.py +0 -3
  13. ralphx/core/loop.py +15 -2
  14. ralphx/core/loop_templates.py +3 -3
  15. ralphx/core/planning_service.py +109 -21
  16. ralphx/core/preview.py +9 -25
  17. ralphx/core/project_db.py +124 -72
  18. ralphx/core/project_export.py +1 -5
  19. ralphx/core/project_import.py +14 -29
  20. ralphx/core/sample_project.py +1 -5
  21. ralphx/core/templates.py +9 -9
  22. ralphx/core/workflow_export.py +4 -7
  23. ralphx/core/workflow_import.py +3 -27
  24. ralphx/mcp/__init__.py +6 -2
  25. ralphx/mcp/registry.py +3 -3
  26. ralphx/mcp/tools/workflows.py +114 -32
  27. ralphx/mcp_server.py +6 -2
  28. ralphx/static/assets/index-0ovNnfOq.css +1 -0
  29. ralphx/static/assets/index-CY9s08ZB.js +251 -0
  30. ralphx/static/assets/index-CY9s08ZB.js.map +1 -0
  31. ralphx/static/index.html +2 -2
  32. {ralphx-0.3.4.dist-info → ralphx-0.3.5.dist-info}/METADATA +33 -12
  33. {ralphx-0.3.4.dist-info → ralphx-0.3.5.dist-info}/RECORD +35 -35
  34. ralphx/static/assets/index-CcRDyY3b.css +0 -1
  35. ralphx/static/assets/index-CcxfTosc.js +0 -251
  36. ralphx/static/assets/index-CcxfTosc.js.map +0 -1
  37. {ralphx-0.3.4.dist-info → ralphx-0.3.5.dist-info}/WHEEL +0 -0
  38. {ralphx-0.3.4.dist-info → ralphx-0.3.5.dist-info}/entry_points.txt +0 -0
@@ -187,12 +187,12 @@ async def check_loop_type_requirements_status(slug: str, loop_type: str):
187
187
  detail=f"No requirements found for loop type: {loop_type}",
188
188
  )
189
189
 
190
- # Check credentials for auth requirement
190
+ # Check for authenticated account
191
191
  from ralphx.core.database import Database
192
192
 
193
193
  global_db = Database()
194
- credentials = global_db.get_credentials()
195
- has_auth = credentials is not None
194
+ accounts = global_db.list_accounts(include_inactive=False, include_deleted=False)
195
+ has_auth = len(accounts) > 0
196
196
 
197
197
  # Get resource counts by type
198
198
  resources = project_db.list_resources(enabled=True)
@@ -425,59 +425,6 @@ async def import_jsonl_with_format(
425
425
  tmp_path.unlink(missing_ok=True)
426
426
 
427
427
 
428
- # ============================================================================
429
- # Namespace Endpoints
430
- # ============================================================================
431
-
432
-
433
- class NamespaceInfo(BaseModel):
434
- """Information about a namespace."""
435
-
436
- namespace: str
437
- item_count: int
438
- pending_count: int
439
- completed_count: int
440
- processed_count: int
441
-
442
-
443
- @router.get("/{slug}/namespaces", response_model=list[NamespaceInfo])
444
- async def list_namespaces(slug: str):
445
- """List all namespaces with item counts.
446
-
447
- Returns namespaces that have work items, along with counts by status.
448
- """
449
- manager, project, project_db = get_project(slug)
450
-
451
- # Get all items grouped by namespace
452
- items, _ = project_db.list_work_items(limit=10000)
453
-
454
- namespace_stats = {}
455
- for item in items:
456
- ns = item.get("namespace")
457
- if not ns:
458
- continue
459
-
460
- if ns not in namespace_stats:
461
- namespace_stats[ns] = {
462
- "namespace": ns,
463
- "item_count": 0,
464
- "pending_count": 0,
465
- "completed_count": 0,
466
- "processed_count": 0,
467
- }
468
-
469
- namespace_stats[ns]["item_count"] += 1
470
- status = item.get("status", "pending")
471
- if status == "pending":
472
- namespace_stats[ns]["pending_count"] += 1
473
- elif status == "completed":
474
- namespace_stats[ns]["completed_count"] += 1
475
- elif status == "processed":
476
- namespace_stats[ns]["processed_count"] += 1
477
-
478
- return [NamespaceInfo(**ns) for ns in namespace_stats.values()]
479
-
480
-
481
428
  # ============================================================================
482
429
  # Project Resources (Shared Library) Endpoints
483
430
  # ============================================================================
@@ -70,7 +70,7 @@ async def _read_upload_with_limit(file: UploadFile, max_size: int = MAX_UPLOAD_S
70
70
  class ExportPreviewResponse(BaseModel):
71
71
  """Response for export preview."""
72
72
  workflow_name: str
73
- workflow_namespace: str
73
+ workflow_id: str
74
74
  steps_count: int
75
75
  items_total: int
76
76
  resources_count: int
@@ -108,7 +108,7 @@ class ResourcePreviewResponse(BaseModel):
108
108
  class ImportPreviewResponse(BaseModel):
109
109
  """Response for import preview."""
110
110
  workflow_name: str
111
- workflow_namespace: str
111
+ workflow_id: str
112
112
  exported_at: str
113
113
  ralphx_version: str
114
114
  schema_version: int
@@ -138,7 +138,7 @@ class ConflictInfo(BaseModel):
138
138
  class MergePreviewResponse(BaseModel):
139
139
  """Response for merge preview."""
140
140
  workflow_name: str
141
- workflow_namespace: str
141
+ workflow_id: str
142
142
  items_count: int
143
143
  resources_count: int
144
144
  conflicts: list[ConflictInfo]
@@ -194,7 +194,6 @@ class WorkflowSummaryResponse(BaseModel):
194
194
  """Summary of a workflow in project export."""
195
195
  id: str
196
196
  name: str
197
- namespace: str
198
197
  steps_count: int
199
198
  items_count: int
200
199
  resources_count: int
@@ -323,7 +322,7 @@ async def preview_workflow_export(slug: str, workflow_id: str):
323
322
 
324
323
  return ExportPreviewResponse(
325
324
  workflow_name=preview.workflow_name,
326
- workflow_namespace=preview.workflow_namespace,
325
+ workflow_id=preview.workflow_id,
327
326
  steps_count=preview.steps_count,
328
327
  items_total=preview.items_total,
329
328
  resources_count=preview.resources_count,
@@ -404,7 +403,7 @@ async def preview_workflow_import(
404
403
 
405
404
  return ImportPreviewResponse(
406
405
  workflow_name=preview.workflow_name,
407
- workflow_namespace=preview.workflow_namespace,
406
+ workflow_id=preview.workflow_id,
408
407
  exported_at=preview.exported_at,
409
408
  ralphx_version=preview.ralphx_version,
410
409
  schema_version=preview.schema_version,
@@ -551,7 +550,7 @@ async def preview_workflow_merge(
551
550
 
552
551
  return MergePreviewResponse(
553
552
  workflow_name=preview.workflow_name,
554
- workflow_namespace=preview.workflow_namespace,
553
+ workflow_id=preview.workflow_id,
555
554
  items_count=preview.items_count,
556
555
  resources_count=preview.resources_count,
557
556
  conflicts=conflicts,
@@ -639,7 +638,6 @@ async def preview_project_export(
639
638
  WorkflowSummaryResponse(
640
639
  id=w.id,
641
640
  name=w.name,
642
- namespace=w.namespace,
643
641
  steps_count=w.steps_count,
644
642
  items_count=w.items_count,
645
643
  resources_count=w.resources_count,
@@ -727,7 +725,6 @@ async def preview_project_import(
727
725
  WorkflowSummaryResponse(
728
726
  id=w.id,
729
727
  name=w.name,
730
- namespace=w.namespace,
731
728
  steps_count=w.steps_count,
732
729
  items_count=w.items_count,
733
730
  resources_count=w.resources_count,
@@ -849,14 +849,14 @@ async def create_simple_loop(slug: str, request: CreateSimpleLoopRequest):
849
849
  status_code=status.HTTP_400_BAD_REQUEST,
850
850
  detail=f"Source loop '{source_loop}' not found in this project.",
851
851
  )
852
- namespace = source_loop # Use source loop name as namespace
852
+ source_loop_name = source_loop # Use source loop name
853
853
  elif request.stories_source and request.stories_source.type == "content":
854
- # For JSONL uploads, derive namespace from request or loop_id
855
- namespace = request.stories_source.namespace or loop_id.replace("-", "_")
854
+ # For JSONL uploads, derive source from request or loop_id
855
+ source_loop_name = request.stories_source.namespace or loop_id.replace("-", "_")
856
856
 
857
857
  config_yaml = generate_simple_implementation_config(
858
858
  name=loop_id,
859
- namespace=namespace,
859
+ source_loop=source_loop_name,
860
860
  display_name=display_name,
861
861
  description=description,
862
862
  )
@@ -269,9 +269,18 @@ async def stream_planning_response(slug: str, workflow_id: str):
269
269
  detail="Planning session is not active",
270
270
  )
271
271
 
272
- # Get workflow for context
272
+ # Get workflow and current step for context
273
273
  workflow = pdb.get_workflow(workflow_id)
274
274
 
275
+ # Get the step to access its config (tools, model, timeout)
276
+ step = pdb.get_workflow_step(session["step_id"])
277
+ step_config = step.get("config", {}) if step else {}
278
+
279
+ # Extract configuration from step
280
+ allowed_tools = step_config.get("allowedTools", [])
281
+ model = step_config.get("model", "opus") # Default to opus for design docs
282
+ timeout = step_config.get("timeout", 180)
283
+
275
284
  async def generate_response():
276
285
  """Generate streaming response from Claude."""
277
286
  import json
@@ -290,7 +299,12 @@ async def stream_planning_response(slug: str, workflow_id: str):
290
299
  accumulated = ""
291
300
 
292
301
  try:
293
- async for event in service.stream_response(messages):
302
+ async for event in service.stream_response(
303
+ messages,
304
+ model=model,
305
+ tools=allowed_tools if allowed_tools else None,
306
+ timeout=timeout,
307
+ ):
294
308
  if event.type == AdapterEvent.TEXT:
295
309
  text = event.text or ""
296
310
  accumulated += text
@@ -405,9 +419,9 @@ async def complete_planning_session(
405
419
 
406
420
  # Get workflow info
407
421
  workflow = pdb.get_workflow(workflow_id)
408
- namespace = workflow["namespace"]
409
422
 
410
423
  # Save artifacts as project resources
424
+ # Use workflow_id for unique filenames (namespace was removed in schema v16)
411
425
  from pathlib import Path
412
426
  from datetime import datetime
413
427
 
@@ -416,7 +430,7 @@ async def complete_planning_session(
416
430
  resource_path = Path(project["path"]) / ".ralphx" / "resources"
417
431
  resource_path.mkdir(parents=True, exist_ok=True)
418
432
 
419
- doc_filename = f"design-doc-{namespace}.md"
433
+ doc_filename = f"design-doc-{workflow_id}.md"
420
434
  doc_path = resource_path / doc_filename
421
435
  doc_path.write_text(artifacts["design_doc"])
422
436
 
@@ -442,7 +456,7 @@ async def complete_planning_session(
442
456
  resource_path = Path(project["path"]) / ".ralphx" / "resources"
443
457
  resource_path.mkdir(parents=True, exist_ok=True)
444
458
 
445
- guardrails_filename = f"guardrails-{namespace}.md"
459
+ guardrails_filename = f"guardrails-{workflow_id}.md"
446
460
  guardrails_path = resource_path / guardrails_filename
447
461
  guardrails_path.write_text(artifacts["guardrails"])
448
462
 
@@ -4,7 +4,7 @@ Templates are global, read-only, and shipped with RalphX.
4
4
  No authentication required - templates are public.
5
5
 
6
6
  Includes:
7
- - Loop templates (research, implementation, etc.)
7
+ - Loop templates (extractgen_requirements, implementation, etc.)
8
8
  - Loop builder templates (planning, implementation with Phase 1)
9
9
  - Permission templates (planning, implementation, read_only, etc.)
10
10
  """
@@ -87,7 +87,7 @@ async def get_template_by_name(name: str) -> TemplateDetail:
87
87
  No authentication required - templates are public.
88
88
 
89
89
  Args:
90
- name: Template name (e.g., 'research', 'implementation')
90
+ name: Template name (e.g., 'extractgen_requirements', 'implementation')
91
91
 
92
92
  Returns:
93
93
  Full template with config and YAML representation
@@ -62,7 +62,6 @@ class WorkflowResponse(BaseModel):
62
62
  id: str
63
63
  template_id: Optional[str] = None
64
64
  name: str
65
- namespace: str
66
65
  status: str
67
66
  current_step: int
68
67
  created_at: str
@@ -222,23 +221,6 @@ def _get_project_db(slug: str) -> ProjectDatabase:
222
221
  return ProjectDatabase(project["path"])
223
222
 
224
223
 
225
- def _generate_namespace(name: str) -> str:
226
- """Generate a valid namespace from a workflow name."""
227
- import re
228
-
229
- # Convert to lowercase, replace spaces with dashes
230
- ns = name.lower().replace(" ", "-")
231
- # Remove invalid characters
232
- ns = re.sub(r"[^a-z0-9_-]", "", ns)
233
- # Ensure it starts with a letter
234
- if not ns or not ns[0].isalpha():
235
- ns = "w" + ns
236
- # Truncate to 64 chars and add unique suffix
237
- ns = ns[:56]
238
- suffix = uuid.uuid4().hex[:7]
239
- return f"{ns}-{suffix}"
240
-
241
-
242
224
  def _workflow_to_response(
243
225
  workflow: dict, steps: list[dict], pdb: Optional[ProjectDatabase] = None
244
226
  ) -> WorkflowResponse:
@@ -366,7 +348,6 @@ def _workflow_to_response(
366
348
  id=workflow["id"],
367
349
  template_id=workflow.get("template_id"),
368
350
  name=workflow["name"],
369
- namespace=workflow["namespace"],
370
351
  status=workflow["status"],
371
352
  current_step=workflow["current_step"],
372
353
  created_at=workflow["created_at"],
@@ -511,9 +492,8 @@ async def create_workflow(slug: str, request: CreateWorkflowRequest):
511
492
  """
512
493
  pdb = _get_project_db(slug)
513
494
 
514
- # Generate unique ID and namespace
495
+ # Generate unique ID
515
496
  workflow_id = f"wf-{uuid.uuid4().hex[:12]}"
516
- namespace = _generate_namespace(request.name)
517
497
 
518
498
  # Get template steps if template specified (templates still use "phases" internally)
519
499
  template_steps = []
@@ -531,7 +511,6 @@ async def create_workflow(slug: str, request: CreateWorkflowRequest):
531
511
  workflow = pdb.create_workflow(
532
512
  id=workflow_id,
533
513
  name=request.name,
534
- namespace=namespace,
535
514
  template_id=request.template_id,
536
515
  status="draft",
537
516
  )
ralphx/cli.py CHANGED
@@ -1319,7 +1319,10 @@ def mcp_server() -> None:
1319
1319
  67 tools for full RalphX management through Claude Code.
1320
1320
 
1321
1321
  To add RalphX to Claude Code, run:
1322
- claude mcp add ralphx -- ralphx mcp
1322
+ Linux/Mac: claude mcp add ralphx -e PYTHONDONTWRITEBYTECODE=1 -- "$(which ralphx)" mcp
1323
+ Mac (zsh): if "which" fails, first run: conda init zsh && source ~/.zshrc
1324
+ Windows: find path with "where.exe ralphx", then:
1325
+ claude mcp add ralphx -e PYTHONDONTWRITEBYTECODE=1 -- C:\\path\\to\\ralphx.exe mcp
1323
1326
 
1324
1327
  Then in Claude Code, you can ask Claude to:
1325
1328
  - Manage projects (add, remove, list, diagnose)