interagent-framework 0.1.0__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.
interagent/cli.py ADDED
@@ -0,0 +1,982 @@
1
+ #!/usr/bin/env python3
2
+ """Command-line interface for InterAgent."""
3
+
4
+ import argparse
5
+ import sys
6
+ from datetime import date
7
+ from pathlib import Path
8
+ from typing import List, Optional
9
+
10
+ from . import __version__
11
+ from .constants import VALID_AGENTS, VALID_MODES, INTERAGENT_DIR
12
+ from .session import Session
13
+ from .task import Task, TaskStatus
14
+ from .messaging import Message, MessageBus
15
+ from .locking import acquire_lock, release_lock, LockError
16
+ from .validator import validate_task, validate_message
17
+ from .templates import get_template
18
+ from .utils import (
19
+ ensure_dirs,
20
+ print_success,
21
+ print_warning,
22
+ print_error,
23
+ print_info,
24
+ )
25
+
26
+
27
+ def cmd_init(args: argparse.Namespace) -> int:
28
+ """Initialize a new session."""
29
+ if INTERAGENT_DIR.exists() and not args.force:
30
+ print_warning(".interagent/ already exists. Use --force to overwrite.")
31
+ return 1
32
+
33
+ ensure_dirs()
34
+
35
+ try:
36
+ session = Session.create(
37
+ name=args.project or "Unnamed Project",
38
+ principal=args.principal or "claude",
39
+ mode=args.mode or "hierarchical",
40
+ )
41
+ session.save()
42
+
43
+ # Create README
44
+ readme_path = INTERAGENT_DIR / "README.md"
45
+ with open(readme_path, "w", encoding="utf-8") as f:
46
+ f.write(f"""# InterAgent Session: {session.name}
47
+
48
+ **ID:** {session.id}
49
+ **Mode:** {session.mode}
50
+ **Principal:** {session.principal}
51
+
52
+ ## Quick Commands
53
+
54
+ ```bash
55
+ # Check status
56
+ interagent status
57
+
58
+ # Create task
59
+ interagent task create --title "Task name" --assignee kimi
60
+
61
+ # List tasks
62
+ interagent task list
63
+
64
+ # Quick delegation
65
+ interagent quick --to kimi "Implement auth"
66
+
67
+ # Check inbox
68
+ interagent inbox --agent kimi
69
+
70
+ # Get relay prompt
71
+ interagent relay --to kimi
72
+
73
+ # Summary
74
+ interagent summary
75
+ ```
76
+
77
+ ## Files
78
+
79
+ - `session.json` - Session configuration
80
+ - `agents/` - Agent status
81
+ - `tasks/active/` - Active tasks
82
+ - `tasks/completed/` - Completed tasks
83
+ - `messages/pending/` - Unread messages
84
+ - `messages/archive/` - Message history
85
+ - `shared/` - Shared context and decisions
86
+ """)
87
+
88
+ print_success(f"Initialized session: {session.name}")
89
+ print(f" ID: {session.id}")
90
+ print(f" Mode: {session.mode}")
91
+ print(f" Principal: {session.principal}")
92
+ print(f"\n[DIR] Created .interagent/ directory")
93
+ print("\nNext steps:")
94
+ print("1. Edit .interagent/shared/context.md with project details")
95
+ print("2. Quick start: interagent quick --to kimi \"Your task\"")
96
+ return 0
97
+
98
+ except ValueError as e:
99
+ print_error(str(e))
100
+ return 1
101
+
102
+
103
+ def cmd_status(args: argparse.Namespace) -> int:
104
+ """Show session status."""
105
+ session = Session.load()
106
+ if not session:
107
+ print_error("No session found. Run: interagent init")
108
+ return 1
109
+
110
+ print(f"[STAT] Session: {session.name}")
111
+ print(f" ID: {session.id}")
112
+ print(f" Mode: {session.mode}")
113
+ print(f" Principal: {session.principal}")
114
+
115
+ print(f"\n[AGENTS] Agents:")
116
+ for agent, info in session.agents.items():
117
+ print(f" {agent}: {info.get('role', 'unknown')}")
118
+
119
+ # Count tasks
120
+ active_tasks = Task.list_all(active_only=True)
121
+ completed_tasks = Task.list_all()
122
+ completed_tasks = [t for t in completed_tasks if t.status in ["completed", "approved"]]
123
+
124
+ print(f"\n[TASK] Tasks:")
125
+ print(f" Active: {len(active_tasks)}")
126
+ print(f" Completed: {len(completed_tasks)}")
127
+
128
+ # Count messages
129
+ pending = MessageBus.get_inbox("claude") + MessageBus.get_inbox("kimi")
130
+ print(f"\n[MSG] Pending Messages: {len(pending)}")
131
+
132
+ return 0
133
+
134
+
135
+ def cmd_summary(args: argparse.Namespace) -> int:
136
+ """Show quick summary for relay decisions."""
137
+ session = Session.load()
138
+ if not session:
139
+ print_error("No session found. Run: interagent init")
140
+ return 1
141
+
142
+ print("=" * 60)
143
+ print("INTERAGENT SUMMARY")
144
+ print("=" * 60)
145
+ print()
146
+
147
+ # Session info
148
+ print(f"Session: {session.name} ({session.mode} mode)")
149
+ print(f"Principal: {session.principal}")
150
+ print()
151
+
152
+ # Tasks by status
153
+ all_tasks = Task.list_all()
154
+
155
+ pending_claude = [t for t in all_tasks if t.assignee == "claude" and t.status in ["pending", "assigned"]]
156
+ pending_kimi = [t for t in all_tasks if t.assignee == "kimi" and t.status in ["pending", "assigned"]]
157
+ in_progress_claude = [t for t in all_tasks if t.assignee == "claude" and t.status == "in_progress"]
158
+ in_progress_kimi = [t for t in all_tasks if t.assignee == "kimi" and t.status == "in_progress"]
159
+ ready_for_review = [t for t in all_tasks if t.status in ["completed", "under_review"]]
160
+ approved = [t for t in all_tasks if t.status == "approved"]
161
+
162
+ print("[TASKS]")
163
+ if pending_claude:
164
+ print(f" [WAIT] Claude: {len(pending_claude)} task(s) waiting to start")
165
+ if pending_kimi:
166
+ print(f" [WAIT] Kimi: {len(pending_kimi)} task(s) waiting to start")
167
+ if in_progress_claude:
168
+ print(f" [WORK] Claude: {len(in_progress_claude)} task(s) in progress")
169
+ if in_progress_kimi:
170
+ print(f" [WORK] Kimi: {len(in_progress_kimi)} task(s) in progress")
171
+ if ready_for_review:
172
+ print(f" [REVIEW] {len(ready_for_review)} task(s) ready for review")
173
+ if approved:
174
+ print(f" [OK] {len(approved)} task(s) approved")
175
+
176
+ if not any([pending_claude, pending_kimi, in_progress_claude, in_progress_kimi, ready_for_review, approved]):
177
+ print(" No active tasks")
178
+ print()
179
+
180
+ # Messages
181
+ claude_msgs = MessageBus.get_inbox("claude")
182
+ kimi_msgs = MessageBus.get_inbox("kimi")
183
+
184
+ print("[MESSAGES]")
185
+ if claude_msgs:
186
+ print(f" [MSG] Claude: {len(claude_msgs)} unread message(s)")
187
+ for msg in claude_msgs:
188
+ print(f" - From {msg.sender}: {msg.subject or '(no subject)'}")
189
+ if kimi_msgs:
190
+ print(f" [MSG] Kimi: {len(kimi_msgs)} unread message(s)")
191
+ for msg in kimi_msgs:
192
+ print(f" - From {msg.sender}: {msg.subject or '(no subject)'}")
193
+ if not claude_msgs and not kimi_msgs:
194
+ print(" No unread messages")
195
+ print()
196
+
197
+ # Action items
198
+ print("[ACTION ITEMS]")
199
+ if ready_for_review:
200
+ print(f" -> Tell {session.principal} to review {len(ready_for_review)} completed task(s)")
201
+ if pending_kimi and session.principal == "claude":
202
+ print(f" -> Tell Kimi to check inbox ({len(pending_kimi)} new task(s))")
203
+ if pending_claude and session.principal == "kimi":
204
+ print(f" -> Tell Claude to check inbox ({len(pending_claude)} new task(s))")
205
+ if claude_msgs:
206
+ print(" -> Tell Claude to check messages")
207
+ if kimi_msgs:
208
+ print(" -> Tell Kimi to check messages")
209
+ if not any([ready_for_review, pending_kimi, pending_claude, claude_msgs, kimi_msgs]):
210
+ print(" All caught up! No action needed.")
211
+ print()
212
+
213
+ # Quick commands
214
+ print("[QUICK COMMANDS]")
215
+ if ready_for_review:
216
+ task_id = ready_for_review[0].id
217
+ print(f" interagent task show {task_id}")
218
+ if kimi_msgs:
219
+ print(f" interagent relay --to kimi")
220
+ if claude_msgs:
221
+ print(f" interagent relay --to claude")
222
+ print()
223
+
224
+ return 0
225
+
226
+
227
+ def cmd_relay(args: argparse.Namespace) -> int:
228
+ """Generate relay prompt for an agent."""
229
+ agent = args.agent
230
+
231
+ # Get pending tasks for this agent
232
+ pending_tasks = Task.list_all(assignee=agent, status="assigned")
233
+ pending_tasks.extend(Task.list_all(assignee=agent, status="pending"))
234
+
235
+ # Get messages for this agent
236
+ messages = MessageBus.get_inbox(agent)
237
+
238
+ # Get session
239
+ session = Session.load()
240
+ role = session.get_agent_role(agent) if session else "delegate"
241
+
242
+ print("=" * 60)
243
+ print(f"RELAY PROMPT FOR {agent.upper()}")
244
+ print("=" * 60)
245
+ print()
246
+ print("Copy and paste this to the agent:")
247
+ print()
248
+ print("-" * 60)
249
+ print()
250
+
251
+ # Generate the prompt
252
+ print(f"@{agent} - You have work in the InterAgent collaboration system.")
253
+ print()
254
+ print(f"Your role: {role}")
255
+ print()
256
+
257
+ if pending_tasks:
258
+ print(f"[TASK] You have {len(pending_tasks)} new task(s):")
259
+ for task in pending_tasks:
260
+ print(f" - {task.title} ({task.id})")
261
+ print()
262
+ print("Please:")
263
+ print("1. Check .interagent/tasks/active/ for details")
264
+ print("2. Run: interagent task update <task_id> --status in_progress")
265
+ print("3. Do the work")
266
+ print("4. Run: interagent task update <task_id> --status completed")
267
+ print("5. Send a message when done: interagent msg send --to <other> --message 'Done!'")
268
+ print()
269
+
270
+ if messages:
271
+ print(f"[MSG] You have {len(messages)} unread message(s):")
272
+ for msg in messages[:3]: # Show first 3
273
+ print(f" From {msg.sender}: {msg.subject or '(no subject)'}")
274
+ print()
275
+ print("Check your inbox:")
276
+ print(f" interagent inbox --agent {agent}")
277
+ print()
278
+
279
+ if not pending_tasks and not messages:
280
+ print("No pending tasks or messages.")
281
+ print()
282
+ print("Useful commands:")
283
+ print(f" interagent status # Check overall status")
284
+ print(f" interagent summary # Quick summary")
285
+ print()
286
+
287
+ print("-" * 60)
288
+ print()
289
+
290
+ return 0
291
+
292
+
293
+ def cmd_quick(args: argparse.Namespace) -> int:
294
+ """Quick mode - single command for task delegation."""
295
+ ensure_dirs()
296
+
297
+ session = Session.load()
298
+ if not session:
299
+ print_error("No session found. Run: interagent init")
300
+ return 1
301
+
302
+ sender = args.from_agent or session.principal
303
+ recipient = args.to
304
+ task_desc = args.task
305
+
306
+ try:
307
+ # Create task with lock
308
+ task = Task.create(
309
+ title=task_desc[:100], # Limit title length
310
+ description=task_desc if len(task_desc) > 100 else "",
311
+ assignee=recipient,
312
+ assigner=sender,
313
+ priority=args.priority or "medium",
314
+ )
315
+
316
+ # Validate before saving
317
+ is_valid, errors = validate_task(task.to_dict())
318
+ if not is_valid:
319
+ print_error("Task validation failed:")
320
+ for err in errors:
321
+ print(f" - {err}")
322
+ return 1
323
+
324
+ # Try to acquire lock
325
+ try:
326
+ if not acquire_lock(f"task_{task.id}", timeout=5):
327
+ print_error("Could not create task - another process is working")
328
+ return 1
329
+
330
+ task.update(status="assigned")
331
+ task.save()
332
+
333
+ # Update session
334
+ session.add_task(task.id)
335
+ session.save()
336
+
337
+ finally:
338
+ release_lock(f"task_{task.id}")
339
+
340
+ # Create message
341
+ msg = Message.create(
342
+ sender=sender,
343
+ recipient=recipient,
344
+ subject=f"Task: {task.title}",
345
+ content=f"You have been assigned a task: {task_desc}\n\n"
346
+ f"Task ID: {task.id}\n"
347
+ f"Priority: {task.priority}\n\n"
348
+ f"To start: interagent task update {task.id} --status in_progress",
349
+ message_type="delegation",
350
+ task_id=task.id,
351
+ )
352
+
353
+ # Validate message
354
+ is_valid, errors = validate_message(msg.to_dict())
355
+ if is_valid:
356
+ MessageBus.send(msg)
357
+
358
+ print_success("Quick delegation complete!")
359
+ print(f" Task: {task.id}")
360
+ print(f" Assigned to: {recipient}")
361
+ print()
362
+ print("Next step:")
363
+ print(f" interagent relay --to {recipient}")
364
+ print()
365
+ print("This will generate the prompt to copy to the agent.")
366
+
367
+ return 0
368
+
369
+ except Exception as e:
370
+ print_error(f"Failed: {e}")
371
+ return 1
372
+
373
+
374
+ def cmd_task_create(args: argparse.Namespace) -> int:
375
+ """Create a new task."""
376
+ ensure_dirs()
377
+
378
+ try:
379
+ task = Task.create(
380
+ title=args.title,
381
+ description=args.description or "",
382
+ assignee=args.assignee,
383
+ assigner=args.assigner,
384
+ priority=args.priority or "medium",
385
+ requirements=args.requirements,
386
+ acceptance_criteria=args.criteria,
387
+ )
388
+
389
+ # Validate
390
+ is_valid, errors = validate_task(task.to_dict())
391
+ if not is_valid:
392
+ print_error("Task validation failed:")
393
+ for err in errors:
394
+ print(f" - {err}")
395
+ return 1
396
+
397
+ # Lock and save
398
+ if not acquire_lock(f"task_{task.id}", timeout=5):
399
+ print_error("Could not create task - another process is working")
400
+ return 1
401
+
402
+ try:
403
+ task.save()
404
+
405
+ # Update session
406
+ session = Session.load()
407
+ if session:
408
+ session.add_task(task.id)
409
+ session.save()
410
+ finally:
411
+ release_lock(f"task_{task.id}")
412
+
413
+ print_success(f"Created task: {task.id}")
414
+ print(f" Title: {task.title}")
415
+ print(f" Assignee: {task.assignee or 'Unassigned'}")
416
+ print(f" Priority: {task.priority}")
417
+ print(f"\n File: {INTERAGENT_DIR}/tasks/active/{task.id}.json")
418
+ return 0
419
+
420
+ except Exception as e:
421
+ print_error(f"Failed to create task: {e}")
422
+ return 1
423
+
424
+
425
+ def cmd_task_list(args: argparse.Namespace) -> int:
426
+ """List tasks."""
427
+ tasks = Task.list_all(
428
+ status=args.status,
429
+ assignee=args.assignee,
430
+ active_only=args.active_only,
431
+ )
432
+
433
+ if not tasks:
434
+ print_info("No tasks found.")
435
+ return 0
436
+
437
+ print(f"[TASK] Tasks ({len(tasks)}):")
438
+ print("-" * 80)
439
+ for task in tasks:
440
+ print(f"[{task.status:12}] {task.id}: {task.title}")
441
+ print(f" Assignee: {task.assignee or 'Unassigned'}")
442
+ print(f" Priority: {task.priority}")
443
+ print()
444
+
445
+ return 0
446
+
447
+
448
+ def cmd_task_show(args: argparse.Namespace) -> int:
449
+ """Show task details."""
450
+ task = Task.load(args.task_id)
451
+ if not task:
452
+ print_error(f"Task not found: {args.task_id}")
453
+ return 1
454
+
455
+ print(task.to_markdown())
456
+ return 0
457
+
458
+
459
+ def cmd_task_update(args: argparse.Namespace) -> int:
460
+ """Update task status."""
461
+ # Try to acquire lock
462
+ if not acquire_lock(f"task_{args.task_id}", timeout=10):
463
+ print_error("Task is currently being edited by another process")
464
+ return 1
465
+
466
+ try:
467
+ task = Task.load(args.task_id)
468
+ if not task:
469
+ print_error(f"Task not found: {args.task_id}")
470
+ return 1
471
+
472
+ if args.status:
473
+ old_status = task.status
474
+ task.update(status=args.status)
475
+ print(f"Status: {old_status} -> {args.status}")
476
+
477
+ # Move to completed if appropriate
478
+ if args.status in ["completed", "approved"]:
479
+ task.move_to_completed()
480
+
481
+ # Update session
482
+ session = Session.load()
483
+ if session:
484
+ session.complete_task(task.id)
485
+ session.save()
486
+
487
+ print("Moved to completed/")
488
+
489
+ if args.note:
490
+ notes = task.to_dict().get("notes", [])
491
+ from .utils import now_iso
492
+ notes.append({
493
+ "timestamp": now_iso(),
494
+ "note": args.note,
495
+ })
496
+ task.update(notes=notes)
497
+ print("Added note")
498
+
499
+ # Validate before saving
500
+ is_valid, errors = validate_task(task.to_dict())
501
+ if not is_valid:
502
+ print_error("Validation failed:")
503
+ for err in errors:
504
+ print(f" - {err}")
505
+ return 1
506
+
507
+ task.save()
508
+ print_success(f"Updated task: {args.task_id}")
509
+ return 0
510
+
511
+ finally:
512
+ release_lock(f"task_{args.task_id}")
513
+
514
+
515
+ def cmd_msg_send(args: argparse.Namespace) -> int:
516
+ """Send a message."""
517
+ ensure_dirs()
518
+
519
+ try:
520
+ message = Message.create(
521
+ sender=args.from_agent or "unknown",
522
+ recipient=args.to,
523
+ content=args.message,
524
+ subject=args.subject or "",
525
+ message_type=args.type or "message",
526
+ task_id=args.task_id,
527
+ )
528
+
529
+ # Validate
530
+ is_valid, errors = validate_message(message.to_dict())
531
+ if not is_valid:
532
+ print_error("Message validation failed:")
533
+ for err in errors:
534
+ print(f" - {err}")
535
+ return 1
536
+
537
+ MessageBus.send(message)
538
+
539
+ print_success(f"Message sent: {message.id}")
540
+ print(f" To: {args.to}")
541
+ print(f" Subject: {args.subject or '(no subject)'}")
542
+ print(f"\n @{args.to} - Check your inbox: interagent inbox --agent {args.to}")
543
+ return 0
544
+
545
+ except Exception as e:
546
+ print_error(f"Failed to send message: {e}")
547
+ return 1
548
+
549
+
550
+ def cmd_inbox(args: argparse.Namespace) -> int:
551
+ """Check inbox."""
552
+ agent = args.agent
553
+ if not agent:
554
+ # Try to determine current agent from session
555
+ session = Session.load()
556
+ if session:
557
+ print_info("Checking inbox for all agents...")
558
+
559
+ if agent:
560
+ messages = MessageBus.get_inbox(agent)
561
+ else:
562
+ messages = (
563
+ MessageBus.get_inbox("claude") +
564
+ MessageBus.get_inbox("kimi")
565
+ )
566
+
567
+ if not messages:
568
+ print_info(f"No messages for {agent or 'anyone'}")
569
+ return 0
570
+
571
+ print(f"[IN] Messages ({len(messages)}):")
572
+ print("-" * 80)
573
+ for msg in messages:
574
+ print(f"From: {msg.sender}")
575
+ print(f"To: {msg.recipient}")
576
+ print(f"Subject: {msg.subject or '(no subject)'}")
577
+ print(f"Time: {msg.timestamp}")
578
+ print(f"\n{msg.content}")
579
+ print("-" * 80)
580
+
581
+ return 0
582
+
583
+
584
+ def cmd_msg_read(args: argparse.Namespace) -> int:
585
+ """Mark message as read."""
586
+ if MessageBus.mark_read(args.msg_id):
587
+ print_success(f"Message archived: {args.msg_id}")
588
+ return 0
589
+ else:
590
+ print_error(f"Message not found: {args.msg_id}")
591
+ return 1
592
+
593
+
594
+ def cmd_delegate(args: argparse.Namespace) -> int:
595
+ """Quick delegation command."""
596
+ # Create task
597
+ task_id = f"task-xxx"
598
+ task = Task.create(
599
+ title=args.task,
600
+ description=args.description or "",
601
+ assignee=args.to,
602
+ assigner=args.from_agent or "claude",
603
+ priority=args.priority or "medium",
604
+ )
605
+
606
+ # Use quick command internally
607
+ class QuickArgs:
608
+ pass
609
+
610
+ quick_args = QuickArgs()
611
+ quick_args.from_agent = args.from_agent
612
+ quick_args.to = args.to
613
+ quick_args.task = args.task
614
+ quick_args.priority = args.priority
615
+
616
+ return cmd_quick(quick_args)
617
+
618
+
619
+ def cmd_update_template(args: argparse.Namespace) -> int:
620
+ """Generate a prompt instructing an agent to update the kickoff template."""
621
+ # Resolve template path
622
+ template_path = getattr(args, "template_path", None)
623
+ if not template_path:
624
+ # Walk up to 4 parent directories looking for template.txt
625
+ search = Path(__file__).parent
626
+ for _ in range(6):
627
+ candidate = search / "template.txt"
628
+ if candidate.exists():
629
+ template_path = str(candidate)
630
+ break
631
+ search = search.parent
632
+ if not template_path:
633
+ template_path = (
634
+ "~/Documents/projects/template.txt"
635
+ " (path not auto-detected - please verify)"
636
+ )
637
+
638
+ focus = getattr(args, "focus", None) or (
639
+ "all areas: new sub-agent capabilities, collaboration patterns, "
640
+ "updated Claude Code / Kimi Code features"
641
+ )
642
+ agent = args.agent
643
+ today = date.today().isoformat()
644
+ year = str(date.today().year)
645
+
646
+ try:
647
+ template = get_template("update_prompt")
648
+ except FileNotFoundError:
649
+ print_error("Template 'update_prompt' not found in src/interagent/templates/")
650
+ return 1
651
+
652
+ prompt = (
653
+ template
654
+ .replace("{agent}", agent.capitalize())
655
+ .replace("{template_path}", str(template_path))
656
+ .replace("{focus}", focus)
657
+ .replace("{date}", today)
658
+ .replace("{year}", year)
659
+ )
660
+
661
+ separator = "=" * 70
662
+ print(separator)
663
+ print(f"[PROMPT] Copy and paste the following into {agent.capitalize()} Code:")
664
+ print(separator)
665
+ print(prompt)
666
+ print(separator)
667
+ return 0
668
+
669
+
670
+ def create_parser() -> argparse.ArgumentParser:
671
+ """Create argument parser."""
672
+ parser = argparse.ArgumentParser(
673
+ prog="interagent",
674
+ description="InterAgent - Framework for Claude and Kimi collaboration",
675
+ formatter_class=argparse.RawDescriptionHelpFormatter,
676
+ epilog="""
677
+ Examples:
678
+ interagent init --project "My API" --principal claude
679
+ interagent quick --to kimi "Implement authentication"
680
+ interagent relay --to kimi
681
+ interagent summary
682
+ interagent task list
683
+ interagent inbox --agent kimi
684
+
685
+ For more help: https://github.com/yourusername/interagent
686
+ """,
687
+ )
688
+
689
+ parser.add_argument(
690
+ "--version",
691
+ action="version",
692
+ version=f"%(prog)s {__version__}",
693
+ )
694
+
695
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
696
+
697
+ # Init
698
+ init_parser = subparsers.add_parser("init", help="Initialize session")
699
+ init_parser.add_argument("--project", "-p", help="Project name")
700
+ init_parser.add_argument(
701
+ "--principal",
702
+ choices=VALID_AGENTS,
703
+ default="claude",
704
+ help="Principal agent (default: claude)",
705
+ )
706
+ init_parser.add_argument(
707
+ "--mode",
708
+ choices=VALID_MODES,
709
+ default="hierarchical",
710
+ help="Collaboration mode (default: hierarchical)",
711
+ )
712
+ init_parser.add_argument(
713
+ "--force",
714
+ action="store_true",
715
+ help="Force overwrite existing session",
716
+ )
717
+
718
+ # Status
719
+ subparsers.add_parser("status", help="Show session status")
720
+
721
+ # Summary (NEW)
722
+ subparsers.add_parser("summary", help="Quick summary for relay decisions")
723
+
724
+ # Relay (NEW)
725
+ relay_parser = subparsers.add_parser("relay", help="Generate relay prompt for agent")
726
+ relay_parser.add_argument(
727
+ "--agent", "-a",
728
+ required=True,
729
+ choices=VALID_AGENTS,
730
+ help="Agent to generate prompt for",
731
+ )
732
+
733
+ # Quick (NEW)
734
+ quick_parser = subparsers.add_parser("quick", help="Quick task delegation (single command)")
735
+ quick_parser.add_argument(
736
+ "--to", "-t",
737
+ required=True,
738
+ choices=VALID_AGENTS,
739
+ help="Delegate to",
740
+ )
741
+ quick_parser.add_argument(
742
+ "--from-agent", "-f",
743
+ choices=VALID_AGENTS,
744
+ help="Delegate from",
745
+ )
746
+ quick_parser.add_argument(
747
+ "--priority",
748
+ choices=["low", "medium", "high", "critical"],
749
+ default="medium",
750
+ help="Task priority",
751
+ )
752
+ quick_parser.add_argument(
753
+ "task",
754
+ help="Task description",
755
+ )
756
+
757
+ # Task commands
758
+ task_parser = subparsers.add_parser("task", help="Task management")
759
+ task_subparsers = task_parser.add_subparsers(dest="task_command")
760
+
761
+ # Task create
762
+ task_create = task_subparsers.add_parser("create", help="Create task")
763
+ task_create.add_argument("--title", "-t", required=True, help="Task title")
764
+ task_create.add_argument("--description", "-d", help="Task description")
765
+ task_create.add_argument(
766
+ "--assignee", "-a",
767
+ choices=VALID_AGENTS,
768
+ help="Assign to agent",
769
+ )
770
+ task_create.add_argument(
771
+ "--assigner",
772
+ help="Assigned by agent",
773
+ )
774
+ task_create.add_argument(
775
+ "--priority",
776
+ choices=["low", "medium", "high", "critical"],
777
+ default="medium",
778
+ help="Task priority",
779
+ )
780
+ task_create.add_argument(
781
+ "--requirements",
782
+ nargs="+",
783
+ help="Task requirements",
784
+ )
785
+ task_create.add_argument(
786
+ "--criteria",
787
+ nargs="+",
788
+ help="Acceptance criteria",
789
+ )
790
+
791
+ # Task list
792
+ task_list = task_subparsers.add_parser("list", help="List tasks")
793
+ task_list.add_argument(
794
+ "--assignee",
795
+ choices=VALID_AGENTS,
796
+ help="Filter by assignee",
797
+ )
798
+ task_list.add_argument(
799
+ "--status",
800
+ help="Filter by status",
801
+ )
802
+ task_list.add_argument(
803
+ "--active-only",
804
+ action="store_true",
805
+ help="Show only active tasks",
806
+ )
807
+
808
+ # Task show
809
+ task_show = task_subparsers.add_parser("show", help="Show task details")
810
+ task_show.add_argument("task_id", help="Task ID")
811
+
812
+ # Task update
813
+ task_update = task_subparsers.add_parser("update", help="Update task")
814
+ task_update.add_argument("task_id", help="Task ID")
815
+ task_update.add_argument(
816
+ "--status",
817
+ choices=[
818
+ "pending", "assigned", "in_progress", "completed",
819
+ "under_review", "revision_needed", "approved", "rejected",
820
+ ],
821
+ help="New status",
822
+ )
823
+ task_update.add_argument("--note", help="Add a note")
824
+
825
+ # Message commands
826
+ msg_parser = subparsers.add_parser("msg", help="Message management")
827
+ msg_subparsers = msg_parser.add_subparsers(dest="msg_command")
828
+
829
+ # Send message
830
+ msg_send = msg_subparsers.add_parser("send", help="Send a message")
831
+ msg_send.add_argument(
832
+ "--to", "-t",
833
+ required=True,
834
+ choices=VALID_AGENTS,
835
+ help="Recipient",
836
+ )
837
+ msg_send.add_argument(
838
+ "--from-agent", "-f",
839
+ choices=VALID_AGENTS,
840
+ help="Sender",
841
+ )
842
+ msg_send.add_argument("--subject", "-s", help="Message subject")
843
+ msg_send.add_argument(
844
+ "--message", "-m",
845
+ required=True,
846
+ help="Message content",
847
+ )
848
+ msg_send.add_argument(
849
+ "--type",
850
+ choices=["message", "delegation", "review", "discussion"],
851
+ default="message",
852
+ help="Message type",
853
+ )
854
+ msg_send.add_argument("--task-id", help="Related task ID")
855
+
856
+ # Read message
857
+ msg_read = msg_subparsers.add_parser("read", help="Mark message as read")
858
+ msg_read.add_argument("msg_id", help="Message ID")
859
+
860
+ # Inbox
861
+ inbox_parser = subparsers.add_parser("inbox", help="Check inbox")
862
+ inbox_parser.add_argument(
863
+ "--agent", "-a",
864
+ choices=VALID_AGENTS,
865
+ help="Check for specific agent",
866
+ )
867
+
868
+ # Delegate shortcut
869
+ delegate_parser = subparsers.add_parser("delegate", help="Quick task delegation")
870
+ delegate_parser.add_argument(
871
+ "--to", "-t",
872
+ required=True,
873
+ choices=VALID_AGENTS,
874
+ help="Delegate to",
875
+ )
876
+ delegate_parser.add_argument(
877
+ "--from-agent", "-f",
878
+ choices=VALID_AGENTS,
879
+ help="Delegate from",
880
+ )
881
+ delegate_parser.add_argument(
882
+ "--task",
883
+ required=True,
884
+ help="Task description",
885
+ )
886
+ delegate_parser.add_argument(
887
+ "--description", "-d",
888
+ help="Detailed description",
889
+ )
890
+ delegate_parser.add_argument(
891
+ "--priority",
892
+ choices=["low", "medium", "high", "critical"],
893
+ default="medium",
894
+ help="Task priority",
895
+ )
896
+
897
+ # update-template
898
+ update_tmpl_parser = subparsers.add_parser(
899
+ "update-template",
900
+ help="Generate a prompt to update the kickoff template with new AI best practices",
901
+ )
902
+ update_tmpl_parser.add_argument(
903
+ "--agent", "-a",
904
+ required=True,
905
+ choices=VALID_AGENTS,
906
+ help="Which agent receives and executes the update prompt (claude or kimi)",
907
+ )
908
+ update_tmpl_parser.add_argument(
909
+ "--template-path", "-p",
910
+ default=None,
911
+ dest="template_path",
912
+ help="Path to the template file (default: searches parent dirs for template.txt)",
913
+ )
914
+ update_tmpl_parser.add_argument(
915
+ "--focus", "-f",
916
+ default=None,
917
+ help="Optional focus area e.g. 'sub-agents', 'security', 'kimi-capabilities'",
918
+ )
919
+
920
+ return parser
921
+
922
+
923
+ def main(args: Optional[List[str]] = None) -> int:
924
+ """Main entry point."""
925
+ parser = create_parser()
926
+ parsed_args = parser.parse_args(args)
927
+
928
+ if not parsed_args.command:
929
+ parser.print_help()
930
+ return 0
931
+
932
+ # Route commands
933
+ try:
934
+ if parsed_args.command == "init":
935
+ return cmd_init(parsed_args)
936
+ elif parsed_args.command == "status":
937
+ return cmd_status(parsed_args)
938
+ elif parsed_args.command == "summary":
939
+ return cmd_summary(parsed_args)
940
+ elif parsed_args.command == "relay":
941
+ return cmd_relay(parsed_args)
942
+ elif parsed_args.command == "quick":
943
+ return cmd_quick(parsed_args)
944
+ elif parsed_args.command == "task":
945
+ if parsed_args.task_command == "create":
946
+ return cmd_task_create(parsed_args)
947
+ elif parsed_args.task_command == "list":
948
+ return cmd_task_list(parsed_args)
949
+ elif parsed_args.task_command == "show":
950
+ return cmd_task_show(parsed_args)
951
+ elif parsed_args.task_command == "update":
952
+ return cmd_task_update(parsed_args)
953
+ else:
954
+ parser.parse_args(["task", "--help"])
955
+ return 0
956
+ elif parsed_args.command == "msg":
957
+ if parsed_args.msg_command == "send":
958
+ return cmd_msg_send(parsed_args)
959
+ elif parsed_args.msg_command == "read":
960
+ return cmd_msg_read(parsed_args)
961
+ else:
962
+ parser.parse_args(["msg", "--help"])
963
+ return 0
964
+ elif parsed_args.command == "inbox":
965
+ return cmd_inbox(parsed_args)
966
+ elif parsed_args.command == "delegate":
967
+ return cmd_delegate(parsed_args)
968
+ elif parsed_args.command == "update-template":
969
+ return cmd_update_template(parsed_args)
970
+ else:
971
+ parser.print_help()
972
+ return 0
973
+ except KeyboardInterrupt:
974
+ print("\nInterrupted.")
975
+ return 130
976
+ except Exception as e:
977
+ print_error(f"Unexpected error: {e}")
978
+ return 1
979
+
980
+
981
+ if __name__ == "__main__":
982
+ sys.exit(main())