empathy-framework 4.6.2__py3-none-any.whl → 4.6.3__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 (53) hide show
  1. {empathy_framework-4.6.2.dist-info → empathy_framework-4.6.3.dist-info}/METADATA +1 -1
  2. {empathy_framework-4.6.2.dist-info → empathy_framework-4.6.3.dist-info}/RECORD +53 -20
  3. {empathy_framework-4.6.2.dist-info → empathy_framework-4.6.3.dist-info}/WHEEL +1 -1
  4. empathy_os/__init__.py +1 -1
  5. empathy_os/cli.py +361 -32
  6. empathy_os/config/xml_config.py +8 -3
  7. empathy_os/core.py +37 -4
  8. empathy_os/leverage_points.py +2 -1
  9. empathy_os/memory/short_term.py +45 -1
  10. empathy_os/meta_workflows/agent_creator 2.py +254 -0
  11. empathy_os/meta_workflows/builtin_templates 2.py +567 -0
  12. empathy_os/meta_workflows/cli_meta_workflows 2.py +1551 -0
  13. empathy_os/meta_workflows/form_engine 2.py +304 -0
  14. empathy_os/meta_workflows/intent_detector 2.py +298 -0
  15. empathy_os/meta_workflows/pattern_learner 2.py +754 -0
  16. empathy_os/meta_workflows/session_context 2.py +398 -0
  17. empathy_os/meta_workflows/template_registry 2.py +229 -0
  18. empathy_os/meta_workflows/workflow 2.py +980 -0
  19. empathy_os/models/token_estimator.py +16 -9
  20. empathy_os/models/validation.py +7 -1
  21. empathy_os/orchestration/pattern_learner 2.py +699 -0
  22. empathy_os/orchestration/real_tools 2.py +938 -0
  23. empathy_os/orchestration/real_tools.py +4 -2
  24. empathy_os/socratic/__init__ 2.py +273 -0
  25. empathy_os/socratic/ab_testing 2.py +969 -0
  26. empathy_os/socratic/blueprint 2.py +532 -0
  27. empathy_os/socratic/cli 2.py +689 -0
  28. empathy_os/socratic/collaboration 2.py +1112 -0
  29. empathy_os/socratic/domain_templates 2.py +916 -0
  30. empathy_os/socratic/embeddings 2.py +734 -0
  31. empathy_os/socratic/engine 2.py +729 -0
  32. empathy_os/socratic/explainer 2.py +663 -0
  33. empathy_os/socratic/feedback 2.py +767 -0
  34. empathy_os/socratic/forms 2.py +624 -0
  35. empathy_os/socratic/generator 2.py +716 -0
  36. empathy_os/socratic/llm_analyzer 2.py +635 -0
  37. empathy_os/socratic/mcp_server 2.py +751 -0
  38. empathy_os/socratic/session 2.py +306 -0
  39. empathy_os/socratic/storage 2.py +635 -0
  40. empathy_os/socratic/storage.py +2 -1
  41. empathy_os/socratic/success 2.py +719 -0
  42. empathy_os/socratic/visual_editor 2.py +812 -0
  43. empathy_os/socratic/web_ui 2.py +925 -0
  44. empathy_os/tier_recommender.py +5 -2
  45. empathy_os/workflow_commands.py +11 -6
  46. empathy_os/workflows/base.py +1 -1
  47. empathy_os/workflows/batch_processing 2.py +310 -0
  48. empathy_os/workflows/release_prep_crew 2.py +968 -0
  49. empathy_os/workflows/test_coverage_boost_crew 2.py +848 -0
  50. empathy_os/workflows/test_maintenance.py +3 -2
  51. {empathy_framework-4.6.2.dist-info → empathy_framework-4.6.3.dist-info}/entry_points.txt +0 -0
  52. {empathy_framework-4.6.2.dist-info → empathy_framework-4.6.3.dist-info}/licenses/LICENSE +0 -0
  53. {empathy_framework-4.6.2.dist-info → empathy_framework-4.6.3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,689 @@
1
+ """CLI for Socratic Workflow Builder
2
+
3
+ Provides command-line interface for:
4
+ - Starting interactive Socratic sessions
5
+ - Listing and resuming sessions
6
+ - Generating workflows from sessions
7
+ - Managing blueprints
8
+
9
+ Commands:
10
+ empathy socratic start [--goal "..."]
11
+ empathy socratic resume <session_id>
12
+ empathy socratic list [--state completed|in_progress]
13
+ empathy socratic generate <session_id>
14
+ empathy socratic blueprints [--domain security]
15
+ empathy socratic run <blueprint_id> [--input file.json]
16
+
17
+ Copyright 2026 Smart-AI-Memory
18
+ Licensed under Fair Source License 0.9
19
+ """
20
+
21
+ from __future__ import annotations
22
+
23
+ import argparse
24
+ import json
25
+ import sys
26
+ from typing import Any
27
+
28
+ from .engine import SocraticWorkflowBuilder
29
+ from .forms import FieldType, Form, FormField
30
+ from .session import SessionState
31
+ from .storage import get_default_storage
32
+
33
+ # =============================================================================
34
+ # CONSOLE FORMATTING
35
+ # =============================================================================
36
+
37
+
38
+ class Console:
39
+ """Simple console output formatting."""
40
+
41
+ COLORS = {
42
+ "reset": "\033[0m",
43
+ "bold": "\033[1m",
44
+ "dim": "\033[2m",
45
+ "red": "\033[91m",
46
+ "green": "\033[92m",
47
+ "yellow": "\033[93m",
48
+ "blue": "\033[94m",
49
+ "magenta": "\033[95m",
50
+ "cyan": "\033[96m",
51
+ }
52
+
53
+ def __init__(self, use_color: bool = True):
54
+ self.use_color = use_color and sys.stdout.isatty()
55
+
56
+ def _c(self, color: str, text: str) -> str:
57
+ """Apply color to text."""
58
+ if not self.use_color:
59
+ return text
60
+ return f"{self.COLORS.get(color, '')}{text}{self.COLORS['reset']}"
61
+
62
+ def header(self, text: str) -> None:
63
+ """Print a header."""
64
+ print()
65
+ print(self._c("bold", "=" * 60))
66
+ print(self._c("bold", f" {text}"))
67
+ print(self._c("bold", "=" * 60))
68
+ print()
69
+
70
+ def subheader(self, text: str) -> None:
71
+ """Print a subheader."""
72
+ print()
73
+ print(self._c("cyan", f"── {text} ──"))
74
+ print()
75
+
76
+ def success(self, text: str) -> None:
77
+ """Print success message."""
78
+ print(self._c("green", f"✓ {text}"))
79
+
80
+ def error(self, text: str) -> None:
81
+ """Print error message."""
82
+ print(self._c("red", f"✗ {text}"))
83
+
84
+ def warning(self, text: str) -> None:
85
+ """Print warning message."""
86
+ print(self._c("yellow", f"⚠ {text}"))
87
+
88
+ def info(self, text: str) -> None:
89
+ """Print info message."""
90
+ print(self._c("blue", f"ℹ {text}"))
91
+
92
+ def dim(self, text: str) -> None:
93
+ """Print dimmed text."""
94
+ print(self._c("dim", text))
95
+
96
+ def progress(self, value: float, width: int = 30) -> str:
97
+ """Generate progress bar."""
98
+ filled = int(value * width)
99
+ bar = "▓" * filled + "░" * (width - filled)
100
+ return f"[{bar}] {value:.0%}"
101
+
102
+ def table(self, headers: list[str], rows: list[list[str]]) -> None:
103
+ """Print a simple table."""
104
+ # Calculate column widths
105
+ widths = [len(h) for h in headers]
106
+ for row in rows:
107
+ for i, cell in enumerate(row):
108
+ if i < len(widths):
109
+ widths[i] = max(widths[i], len(str(cell)))
110
+
111
+ # Print header
112
+ header_line = " | ".join(
113
+ self._c("bold", h.ljust(widths[i])) for i, h in enumerate(headers)
114
+ )
115
+ print(header_line)
116
+ print("-" * (sum(widths) + len(widths) * 3 - 1))
117
+
118
+ # Print rows
119
+ for row in rows:
120
+ row_line = " | ".join(
121
+ str(cell).ljust(widths[i]) for i, cell in enumerate(row)
122
+ )
123
+ print(row_line)
124
+
125
+
126
+ console = Console()
127
+
128
+
129
+ # =============================================================================
130
+ # INTERACTIVE FORM RENDERER
131
+ # =============================================================================
132
+
133
+
134
+ def render_form_interactive(form: Form, console: Console) -> dict[str, Any]:
135
+ """Render a form and collect user input.
136
+
137
+ Args:
138
+ form: Form to render
139
+ console: Console for output
140
+
141
+ Returns:
142
+ Dictionary of answers
143
+ """
144
+ console.subheader(form.title)
145
+
146
+ if form.description:
147
+ print(form.description)
148
+ print()
149
+
150
+ print(console.progress(form.progress))
151
+ print()
152
+
153
+ answers: dict[str, Any] = {}
154
+
155
+ for field in form.fields:
156
+ # Check visibility
157
+ if not field.should_show(answers):
158
+ continue
159
+
160
+ # Render field
161
+ required = " *" if field.validation.required else ""
162
+ print(f"{console._c('bold', field.label)}{required}")
163
+
164
+ if field.help_text:
165
+ console.dim(f" {field.help_text}")
166
+
167
+ # Handle by field type
168
+ if field.field_type == FieldType.SINGLE_SELECT:
169
+ answers[field.id] = _input_single_select(field, console)
170
+
171
+ elif field.field_type == FieldType.MULTI_SELECT:
172
+ answers[field.id] = _input_multi_select(field, console)
173
+
174
+ elif field.field_type == FieldType.BOOLEAN:
175
+ answers[field.id] = _input_boolean(field, console)
176
+
177
+ elif field.field_type == FieldType.TEXT_AREA:
178
+ answers[field.id] = _input_text_area(field, console)
179
+
180
+ else: # TEXT, NUMBER, etc.
181
+ answers[field.id] = _input_text(field, console)
182
+
183
+ print()
184
+
185
+ return answers
186
+
187
+
188
+ def _input_single_select(field: FormField, console: Console) -> str | None:
189
+ """Input for single select field."""
190
+ for i, opt in enumerate(field.options, 1):
191
+ rec = console._c("green", " (Recommended)") if opt.recommended else ""
192
+ print(f" {i}. {opt.label}{rec}")
193
+ if opt.description:
194
+ console.dim(f" {opt.description}")
195
+
196
+ while True:
197
+ response = input("\n Enter number: ").strip()
198
+
199
+ if not response:
200
+ if not field.validation.required:
201
+ return None
202
+ console.error("This field is required")
203
+ continue
204
+
205
+ try:
206
+ idx = int(response) - 1
207
+ if 0 <= idx < len(field.options):
208
+ return field.options[idx].value
209
+ console.error(f"Enter a number between 1 and {len(field.options)}")
210
+ except ValueError:
211
+ console.error("Enter a valid number")
212
+
213
+
214
+ def _input_multi_select(field: FormField, console: Console) -> list[str]:
215
+ """Input for multi select field."""
216
+ for i, opt in enumerate(field.options, 1):
217
+ rec = console._c("green", " (Recommended)") if opt.recommended else ""
218
+ print(f" {i}. {opt.label}{rec}")
219
+ if opt.description:
220
+ console.dim(f" {opt.description}")
221
+
222
+ while True:
223
+ response = input("\n Enter numbers (comma-separated): ").strip()
224
+
225
+ if not response:
226
+ if not field.validation.required:
227
+ return []
228
+ console.error("Select at least one option")
229
+ continue
230
+
231
+ try:
232
+ indices = [int(x.strip()) - 1 for x in response.split(",")]
233
+ selected = []
234
+ for idx in indices:
235
+ if 0 <= idx < len(field.options):
236
+ selected.append(field.options[idx].value)
237
+
238
+ if selected:
239
+ return selected
240
+ console.error("No valid options selected")
241
+ except ValueError:
242
+ console.error("Enter valid numbers separated by commas")
243
+
244
+
245
+ def _input_boolean(field: FormField, console: Console) -> bool:
246
+ """Input for boolean field."""
247
+ while True:
248
+ response = input(" (y/n): ").strip().lower()
249
+
250
+ if response in ("y", "yes", "true", "1"):
251
+ return True
252
+ elif response in ("n", "no", "false", "0"):
253
+ return False
254
+ elif not response and not field.validation.required:
255
+ return False
256
+ else:
257
+ console.error("Enter 'y' or 'n'")
258
+
259
+
260
+ def _input_text(field: FormField, console: Console) -> str:
261
+ """Input for text field."""
262
+ prompt = f" {field.placeholder or 'Enter value'}: " if field.placeholder else " > "
263
+
264
+ while True:
265
+ response = input(prompt).strip()
266
+
267
+ if not response:
268
+ if not field.validation.required:
269
+ return ""
270
+ console.error("This field is required")
271
+ continue
272
+
273
+ # Validate
274
+ is_valid, error = field.validate(response)
275
+ if is_valid:
276
+ return response
277
+ console.error(error)
278
+
279
+
280
+ def _input_text_area(field: FormField, console: Console) -> str:
281
+ """Input for text area field."""
282
+ print(" (Enter text, then press Enter twice to finish)")
283
+
284
+ lines = []
285
+ empty_count = 0
286
+
287
+ while True:
288
+ line = input(" > " if not lines else " ")
289
+
290
+ if not line:
291
+ empty_count += 1
292
+ if empty_count >= 2:
293
+ break
294
+ lines.append("")
295
+ else:
296
+ empty_count = 0
297
+ lines.append(line)
298
+
299
+ response = "\n".join(lines).strip()
300
+
301
+ if not response and field.validation.required:
302
+ console.error("This field is required")
303
+ return _input_text_area(field, console)
304
+
305
+ return response
306
+
307
+
308
+ # =============================================================================
309
+ # CLI COMMANDS
310
+ # =============================================================================
311
+
312
+
313
+ def cmd_start(args: argparse.Namespace) -> int:
314
+ """Start a new Socratic session."""
315
+ storage = get_default_storage()
316
+ builder = SocraticWorkflowBuilder()
317
+
318
+ console.header("SOCRATIC WORKFLOW BUILDER")
319
+
320
+ # Start session
321
+ session = builder.start_session()
322
+ console.info(f"Session ID: {session.session_id[:8]}...")
323
+
324
+ # Get initial goal
325
+ if args.goal:
326
+ goal = args.goal
327
+ console.info(f"Goal: {goal}")
328
+ else:
329
+ initial_form = builder.get_initial_form()
330
+ answers = render_form_interactive(initial_form, console)
331
+ goal = answers.get("goal", "")
332
+
333
+ if not goal:
334
+ console.error("No goal provided")
335
+ return 1
336
+
337
+ # Set goal and analyze
338
+ session = builder.set_goal(session, goal)
339
+ storage.save_session(session)
340
+
341
+ # Show analysis
342
+ if session.goal_analysis:
343
+ console.subheader("Goal Analysis")
344
+ print(f" Domain: {session.goal_analysis.domain}")
345
+ print(f" Confidence: {session.goal_analysis.confidence:.0%}")
346
+
347
+ if session.goal_analysis.ambiguities:
348
+ print()
349
+ console.warning("Ambiguities detected:")
350
+ for amb in session.goal_analysis.ambiguities:
351
+ print(f" • {amb}")
352
+
353
+ # Interactive questioning loop
354
+ while not builder.is_ready_to_generate(session):
355
+ form = builder.get_next_questions(session)
356
+ if not form:
357
+ break
358
+
359
+ answers = render_form_interactive(form, console)
360
+ session = builder.submit_answers(session, answers)
361
+ storage.save_session(session)
362
+
363
+ # Show progress
364
+ summary = builder.get_session_summary(session)
365
+ console.info(f"Requirements completeness: {summary['requirements_completeness']:.0%}")
366
+
367
+ # Generate workflow
368
+ if builder.is_ready_to_generate(session):
369
+ console.subheader("Generating Workflow")
370
+
371
+ workflow = builder.generate_workflow(session)
372
+ storage.save_session(session)
373
+
374
+ if session.blueprint:
375
+ storage.save_blueprint(session.blueprint)
376
+
377
+ console.success("Workflow generated!")
378
+ print()
379
+ print(workflow.describe())
380
+
381
+ if session.blueprint:
382
+ console.info(f"Blueprint ID: {session.blueprint.id[:8]}...")
383
+
384
+ return 0
385
+
386
+
387
+ def cmd_resume(args: argparse.Namespace) -> int:
388
+ """Resume an existing session."""
389
+ storage = get_default_storage()
390
+ builder = SocraticWorkflowBuilder()
391
+
392
+ # Find session
393
+ session = storage.load_session(args.session_id)
394
+
395
+ # Try partial match
396
+ if not session:
397
+ sessions = storage.list_sessions()
398
+ matches = [s for s in sessions if s["session_id"].startswith(args.session_id)]
399
+ if len(matches) == 1:
400
+ session = storage.load_session(matches[0]["session_id"])
401
+ elif len(matches) > 1:
402
+ console.error(f"Multiple sessions match '{args.session_id}':")
403
+ for m in matches:
404
+ print(f" - {m['session_id'][:8]}... ({m['state']})")
405
+ return 1
406
+
407
+ if not session:
408
+ console.error(f"Session not found: {args.session_id}")
409
+ return 1
410
+
411
+ console.header(f"RESUMING SESSION {session.session_id[:8]}...")
412
+ console.info(f"Goal: {session.goal[:80]}...")
413
+ console.info(f"State: {session.state.value}")
414
+
415
+ # Rebuild builder state
416
+ if session.goal:
417
+ builder._sessions[session.session_id] = session
418
+
419
+ # Continue questioning or generate
420
+ if session.state == SessionState.COMPLETED:
421
+ console.success("Session already completed")
422
+ return 0
423
+
424
+ while not builder.is_ready_to_generate(session):
425
+ form = builder.get_next_questions(session)
426
+ if not form:
427
+ break
428
+
429
+ answers = render_form_interactive(form, console)
430
+ session = builder.submit_answers(session, answers)
431
+ storage.save_session(session)
432
+
433
+ if builder.is_ready_to_generate(session):
434
+ console.subheader("Generating Workflow")
435
+
436
+ workflow = builder.generate_workflow(session)
437
+ storage.save_session(session)
438
+
439
+ if session.blueprint:
440
+ storage.save_blueprint(session.blueprint)
441
+
442
+ console.success("Workflow generated!")
443
+ print()
444
+ print(workflow.describe())
445
+
446
+ return 0
447
+
448
+
449
+ def cmd_list(args: argparse.Namespace) -> int:
450
+ """List sessions."""
451
+ storage = get_default_storage()
452
+
453
+ state = None
454
+ if args.state:
455
+ try:
456
+ state = SessionState(args.state)
457
+ except ValueError:
458
+ console.error(f"Invalid state: {args.state}")
459
+ return 1
460
+
461
+ sessions = storage.list_sessions(state=state, limit=args.limit)
462
+
463
+ if not sessions:
464
+ console.info("No sessions found")
465
+ return 0
466
+
467
+ console.header("SOCRATIC SESSIONS")
468
+
469
+ headers = ["ID", "State", "Goal", "Updated"]
470
+ rows = []
471
+ for s in sessions:
472
+ rows.append([
473
+ s["session_id"][:8],
474
+ s["state"],
475
+ (s.get("goal") or "")[:40] + "..." if len(s.get("goal") or "") > 40 else s.get("goal") or "",
476
+ s.get("updated_at", "")[:16],
477
+ ])
478
+
479
+ console.table(headers, rows)
480
+ return 0
481
+
482
+
483
+ def cmd_blueprints(args: argparse.Namespace) -> int:
484
+ """List blueprints."""
485
+ storage = get_default_storage()
486
+
487
+ blueprints = storage.list_blueprints(domain=args.domain, limit=args.limit)
488
+
489
+ if not blueprints:
490
+ console.info("No blueprints found")
491
+ return 0
492
+
493
+ console.header("WORKFLOW BLUEPRINTS")
494
+
495
+ headers = ["ID", "Name", "Domain", "Agents", "Generated"]
496
+ rows = []
497
+ for b in blueprints:
498
+ rows.append([
499
+ b["id"][:8] if b.get("id") else "?",
500
+ b.get("name", "")[:30],
501
+ b.get("domain", ""),
502
+ str(b.get("agents_count", 0)),
503
+ (b.get("generated_at") or "")[:16],
504
+ ])
505
+
506
+ console.table(headers, rows)
507
+ return 0
508
+
509
+
510
+ def cmd_show(args: argparse.Namespace) -> int:
511
+ """Show details of a session or blueprint."""
512
+ storage = get_default_storage()
513
+
514
+ # Try as session first
515
+ session = storage.load_session(args.id)
516
+ if session:
517
+ console.header(f"SESSION: {session.session_id[:8]}...")
518
+
519
+ print(f"State: {session.state.value}")
520
+ print(f"Goal: {session.goal}")
521
+ print(f"Created: {session.created_at}")
522
+ print(f"Updated: {session.updated_at}")
523
+
524
+ if session.goal_analysis:
525
+ console.subheader("Analysis")
526
+ print(f"Domain: {session.goal_analysis.domain}")
527
+ print(f"Confidence: {session.goal_analysis.confidence:.0%}")
528
+ print(f"Intent: {session.goal_analysis.intent}")
529
+
530
+ if session.requirements.must_have:
531
+ console.subheader("Requirements")
532
+ for req in session.requirements.must_have:
533
+ print(f" • {req}")
534
+
535
+ return 0
536
+
537
+ # Try as blueprint
538
+ blueprint = storage.load_blueprint(args.id)
539
+ if blueprint:
540
+ console.header(f"BLUEPRINT: {blueprint.name}")
541
+
542
+ print(f"ID: {blueprint.id}")
543
+ print(f"Domain: {blueprint.domain}")
544
+ print(f"Languages: {', '.join(blueprint.supported_languages)}")
545
+ print(f"Quality Focus: {', '.join(blueprint.quality_focus)}")
546
+
547
+ console.subheader("Agents")
548
+ for agent in blueprint.agents:
549
+ print(f" • {agent.spec.name} ({agent.spec.role.value})")
550
+ print(f" Goal: {agent.spec.goal[:60]}...")
551
+
552
+ console.subheader("Stages")
553
+ for stage in blueprint.stages:
554
+ parallel = "(parallel)" if stage.parallel else "(sequential)"
555
+ print(f" • {stage.name} {parallel}")
556
+ print(f" Agents: {', '.join(stage.agent_ids)}")
557
+
558
+ return 0
559
+
560
+ console.error(f"Not found: {args.id}")
561
+ return 1
562
+
563
+
564
+ def cmd_delete(args: argparse.Namespace) -> int:
565
+ """Delete a session."""
566
+ storage = get_default_storage()
567
+
568
+ if not args.force:
569
+ response = input(f"Delete session {args.session_id}? (y/N): ").strip().lower()
570
+ if response != "y":
571
+ console.info("Cancelled")
572
+ return 0
573
+
574
+ if storage.delete_session(args.session_id):
575
+ console.success(f"Deleted session {args.session_id}")
576
+ return 0
577
+ else:
578
+ console.error(f"Session not found: {args.session_id}")
579
+ return 1
580
+
581
+
582
+ def cmd_export(args: argparse.Namespace) -> int:
583
+ """Export a blueprint to JSON."""
584
+ storage = get_default_storage()
585
+
586
+ blueprint = storage.load_blueprint(args.blueprint_id)
587
+ if not blueprint:
588
+ console.error(f"Blueprint not found: {args.blueprint_id}")
589
+ return 1
590
+
591
+ data = blueprint.to_dict()
592
+
593
+ if args.output:
594
+ with open(args.output, "w") as f:
595
+ json.dump(data, f, indent=2, default=str)
596
+ console.success(f"Exported to {args.output}")
597
+ else:
598
+ print(json.dumps(data, indent=2, default=str))
599
+
600
+ return 0
601
+
602
+
603
+ # =============================================================================
604
+ # MAIN ENTRY POINT
605
+ # =============================================================================
606
+
607
+
608
+ def create_parser() -> argparse.ArgumentParser:
609
+ """Create the argument parser."""
610
+ parser = argparse.ArgumentParser(
611
+ prog="empathy socratic",
612
+ description="Socratic Workflow Builder - Generate agent workflows through guided questioning",
613
+ )
614
+
615
+ subparsers = parser.add_subparsers(dest="command", help="Commands")
616
+
617
+ # start
618
+ start_parser = subparsers.add_parser("start", help="Start a new Socratic session")
619
+ start_parser.add_argument("--goal", "-g", help="Initial goal (skip first question)")
620
+ start_parser.add_argument("--non-interactive", action="store_true", help="Non-interactive mode")
621
+
622
+ # resume
623
+ resume_parser = subparsers.add_parser("resume", help="Resume an existing session")
624
+ resume_parser.add_argument("session_id", help="Session ID (can be partial)")
625
+
626
+ # list
627
+ list_parser = subparsers.add_parser("list", help="List sessions")
628
+ list_parser.add_argument("--state", "-s", choices=["awaiting_goal", "awaiting_answers", "ready_to_generate", "completed", "cancelled"])
629
+ list_parser.add_argument("--limit", "-n", type=int, default=20)
630
+
631
+ # blueprints
632
+ bp_parser = subparsers.add_parser("blueprints", help="List workflow blueprints")
633
+ bp_parser.add_argument("--domain", "-d", help="Filter by domain")
634
+ bp_parser.add_argument("--limit", "-n", type=int, default=20)
635
+
636
+ # show
637
+ show_parser = subparsers.add_parser("show", help="Show session or blueprint details")
638
+ show_parser.add_argument("id", help="Session or blueprint ID")
639
+
640
+ # delete
641
+ delete_parser = subparsers.add_parser("delete", help="Delete a session")
642
+ delete_parser.add_argument("session_id", help="Session ID")
643
+ delete_parser.add_argument("--force", "-f", action="store_true", help="Skip confirmation")
644
+
645
+ # export
646
+ export_parser = subparsers.add_parser("export", help="Export blueprint to JSON")
647
+ export_parser.add_argument("blueprint_id", help="Blueprint ID")
648
+ export_parser.add_argument("--output", "-o", help="Output file (default: stdout)")
649
+
650
+ return parser
651
+
652
+
653
+ def main(argv: list[str] | None = None) -> int:
654
+ """Main entry point."""
655
+ parser = create_parser()
656
+ args = parser.parse_args(argv)
657
+
658
+ if not args.command:
659
+ parser.print_help()
660
+ return 0
661
+
662
+ commands = {
663
+ "start": cmd_start,
664
+ "resume": cmd_resume,
665
+ "list": cmd_list,
666
+ "blueprints": cmd_blueprints,
667
+ "show": cmd_show,
668
+ "delete": cmd_delete,
669
+ "export": cmd_export,
670
+ }
671
+
672
+ cmd_func = commands.get(args.command)
673
+ if cmd_func:
674
+ try:
675
+ return cmd_func(args)
676
+ except KeyboardInterrupt:
677
+ print()
678
+ console.info("Interrupted")
679
+ return 130
680
+ except Exception as e:
681
+ console.error(f"Error: {e}")
682
+ return 1
683
+
684
+ parser.print_help()
685
+ return 0
686
+
687
+
688
+ if __name__ == "__main__":
689
+ sys.exit(main())