monoco-toolkit 0.3.6__py3-none-any.whl → 0.3.10__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 (113) hide show
  1. monoco/cli/workspace.py +1 -1
  2. monoco/core/config.py +58 -0
  3. monoco/core/hooks/__init__.py +19 -0
  4. monoco/core/hooks/base.py +104 -0
  5. monoco/core/hooks/builtin/__init__.py +11 -0
  6. monoco/core/hooks/builtin/git_cleanup.py +266 -0
  7. monoco/core/hooks/builtin/logging_hook.py +78 -0
  8. monoco/core/hooks/context.py +131 -0
  9. monoco/core/hooks/registry.py +222 -0
  10. monoco/core/injection.py +63 -29
  11. monoco/core/integrations.py +8 -2
  12. monoco/core/output.py +5 -5
  13. monoco/core/registry.py +9 -1
  14. monoco/core/resource/__init__.py +5 -0
  15. monoco/core/resource/finder.py +98 -0
  16. monoco/core/resource/manager.py +91 -0
  17. monoco/core/resource/models.py +35 -0
  18. monoco/core/resources/en/{SKILL.md → skills/monoco_core/SKILL.md} +2 -0
  19. monoco/core/resources/zh/{SKILL.md → skills/monoco_core/SKILL.md} +2 -0
  20. monoco/core/setup.py +1 -1
  21. monoco/core/skill_framework.py +292 -0
  22. monoco/core/skills.py +538 -254
  23. monoco/core/sync.py +73 -1
  24. monoco/core/workflow_converter.py +420 -0
  25. monoco/features/{scheduler → agent}/__init__.py +5 -3
  26. monoco/features/agent/adapter.py +31 -0
  27. monoco/features/agent/apoptosis.py +44 -0
  28. monoco/features/agent/cli.py +296 -0
  29. monoco/features/agent/config.py +96 -0
  30. monoco/features/agent/defaults.py +12 -0
  31. monoco/features/{scheduler → agent}/engines.py +32 -6
  32. monoco/features/agent/flow_skills.py +281 -0
  33. monoco/features/agent/manager.py +91 -0
  34. monoco/features/{scheduler → agent}/models.py +6 -3
  35. monoco/features/agent/resources/atoms/atom-code-dev.yaml +61 -0
  36. monoco/features/agent/resources/atoms/atom-issue-lifecycle.yaml +73 -0
  37. monoco/features/agent/resources/atoms/atom-knowledge.yaml +55 -0
  38. monoco/features/agent/resources/atoms/atom-review.yaml +60 -0
  39. monoco/features/agent/resources/en/skills/flow_engineer/SKILL.md +94 -0
  40. monoco/features/agent/resources/en/skills/flow_manager/SKILL.md +93 -0
  41. monoco/features/agent/resources/en/skills/flow_planner/SKILL.md +85 -0
  42. monoco/features/agent/resources/en/skills/flow_reviewer/SKILL.md +114 -0
  43. monoco/features/agent/resources/roles/role-engineer.yaml +49 -0
  44. monoco/features/agent/resources/roles/role-manager.yaml +46 -0
  45. monoco/features/agent/resources/roles/role-planner.yaml +46 -0
  46. monoco/features/agent/resources/roles/role-reviewer.yaml +47 -0
  47. monoco/features/agent/resources/workflows/workflow-dev.yaml +83 -0
  48. monoco/features/agent/resources/workflows/workflow-issue-create.yaml +72 -0
  49. monoco/features/agent/resources/workflows/workflow-review.yaml +94 -0
  50. monoco/features/agent/resources/zh/skills/flow_engineer/SKILL.md +94 -0
  51. monoco/features/agent/resources/zh/skills/flow_manager/SKILL.md +88 -0
  52. monoco/features/agent/resources/zh/skills/flow_planner/SKILL.md +259 -0
  53. monoco/features/agent/resources/zh/skills/flow_reviewer/SKILL.md +137 -0
  54. monoco/features/{scheduler → agent}/session.py +36 -1
  55. monoco/features/{scheduler → agent}/worker.py +40 -4
  56. monoco/features/glossary/adapter.py +31 -0
  57. monoco/features/glossary/config.py +5 -0
  58. monoco/features/glossary/resources/en/AGENTS.md +29 -0
  59. monoco/features/glossary/resources/en/skills/monoco_glossary/SKILL.md +35 -0
  60. monoco/features/glossary/resources/zh/AGENTS.md +29 -0
  61. monoco/features/glossary/resources/zh/skills/monoco_glossary/SKILL.md +35 -0
  62. monoco/features/i18n/resources/en/skills/i18n_scan_workflow/SKILL.md +105 -0
  63. monoco/features/i18n/resources/en/{SKILL.md → skills/monoco_i18n/SKILL.md} +2 -0
  64. monoco/features/i18n/resources/zh/skills/i18n_scan_workflow/SKILL.md +105 -0
  65. monoco/features/i18n/resources/zh/{SKILL.md → skills/monoco_i18n/SKILL.md} +2 -0
  66. monoco/features/issue/commands.py +427 -21
  67. monoco/features/issue/core.py +140 -1
  68. monoco/features/issue/criticality.py +553 -0
  69. monoco/features/issue/domain/models.py +28 -2
  70. monoco/features/issue/engine/machine.py +75 -15
  71. monoco/features/issue/git_service.py +185 -0
  72. monoco/features/issue/linter.py +291 -62
  73. monoco/features/issue/models.py +50 -2
  74. monoco/features/issue/resources/en/skills/issue_create_workflow/SKILL.md +167 -0
  75. monoco/features/issue/resources/en/skills/issue_develop_workflow/SKILL.md +224 -0
  76. monoco/features/issue/resources/en/skills/issue_lifecycle_workflow/SKILL.md +159 -0
  77. monoco/features/issue/resources/en/skills/issue_refine_workflow/SKILL.md +203 -0
  78. monoco/features/issue/resources/en/{SKILL.md → skills/monoco_issue/SKILL.md} +50 -0
  79. monoco/features/issue/resources/zh/skills/issue_create_workflow/SKILL.md +167 -0
  80. monoco/features/issue/resources/zh/skills/issue_develop_workflow/SKILL.md +224 -0
  81. monoco/features/issue/resources/zh/skills/issue_lifecycle_workflow/SKILL.md +159 -0
  82. monoco/features/issue/resources/zh/skills/issue_refine_workflow/SKILL.md +203 -0
  83. monoco/features/issue/resources/zh/{SKILL.md → skills/monoco_issue/SKILL.md} +52 -0
  84. monoco/features/issue/validator.py +185 -65
  85. monoco/features/memo/__init__.py +2 -1
  86. monoco/features/memo/adapter.py +32 -0
  87. monoco/features/memo/cli.py +36 -14
  88. monoco/features/memo/core.py +59 -0
  89. monoco/features/memo/resources/en/skills/monoco_memo/SKILL.md +77 -0
  90. monoco/features/memo/resources/en/skills/note_processing_workflow/SKILL.md +140 -0
  91. monoco/features/memo/resources/zh/AGENTS.md +8 -0
  92. monoco/features/memo/resources/zh/skills/monoco_memo/SKILL.md +77 -0
  93. monoco/features/memo/resources/zh/skills/note_processing_workflow/SKILL.md +140 -0
  94. monoco/features/spike/resources/en/{SKILL.md → skills/monoco_spike/SKILL.md} +2 -0
  95. monoco/features/spike/resources/en/skills/research_workflow/SKILL.md +121 -0
  96. monoco/features/spike/resources/zh/{SKILL.md → skills/monoco_spike/SKILL.md} +2 -0
  97. monoco/features/spike/resources/zh/skills/research_workflow/SKILL.md +121 -0
  98. monoco/main.py +2 -3
  99. monoco_toolkit-0.3.10.dist-info/METADATA +124 -0
  100. monoco_toolkit-0.3.10.dist-info/RECORD +156 -0
  101. monoco/features/scheduler/cli.py +0 -285
  102. monoco/features/scheduler/config.py +0 -68
  103. monoco/features/scheduler/defaults.py +0 -54
  104. monoco/features/scheduler/manager.py +0 -49
  105. monoco/features/scheduler/reliability.py +0 -106
  106. monoco/features/skills/core.py +0 -102
  107. monoco_toolkit-0.3.6.dist-info/METADATA +0 -127
  108. monoco_toolkit-0.3.6.dist-info/RECORD +0 -97
  109. /monoco/core/{hooks.py → githooks.py} +0 -0
  110. /monoco/features/{skills → glossary}/__init__.py +0 -0
  111. {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.10.dist-info}/WHEEL +0 -0
  112. {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.10.dist-info}/entry_points.txt +0 -0
  113. {monoco_toolkit-0.3.6.dist-info → monoco_toolkit-0.3.10.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,296 @@
1
+ import typer
2
+ import time
3
+ from pathlib import Path
4
+ from typing import Optional
5
+ from monoco.core.output import print_output, print_error
6
+ from monoco.core.config import get_config
7
+ from monoco.features.agent import SessionManager, load_scheduler_config
8
+
9
+ app = typer.Typer(name="agent", help="Manage agent sessions and roles")
10
+ session_app = typer.Typer(name="session", help="Manage active agent sessions")
11
+ role_app = typer.Typer(name="role", help="Manage agent roles (CRUD)")
12
+ provider_app = typer.Typer(name="provider", help="Manage agent providers (Engines)")
13
+
14
+ app.add_typer(session_app, name="session")
15
+ app.add_typer(role_app, name="role")
16
+ app.add_typer(provider_app, name="provider")
17
+
18
+
19
+ @role_app.command(name="list")
20
+ def list_roles():
21
+ """
22
+ List available agent roles and their sources.
23
+ """
24
+ from monoco.features.agent.config import RoleLoader
25
+
26
+ settings = get_config()
27
+ project_root = Path(settings.paths.root).resolve()
28
+
29
+ loader = RoleLoader(project_root)
30
+ roles = loader.load_all()
31
+
32
+ output = []
33
+ for name, role in roles.items():
34
+ output.append(
35
+ {
36
+ "role": name,
37
+ "engine": role.engine,
38
+ "source": loader.sources.get(name, "unknown"),
39
+ "description": role.description,
40
+ }
41
+ )
42
+
43
+ print_output(output, title="Agent Roles")
44
+
45
+
46
+ @provider_app.command(name="list")
47
+ def list_providers():
48
+ """
49
+ List available agent providers and their status.
50
+ """
51
+ from monoco.core.integrations import get_all_integrations
52
+
53
+ # Ideally we'd pass project-specific integrations here if they existed in config objects
54
+ integrations = get_all_integrations(enabled_only=False)
55
+
56
+ output = []
57
+ for key, integration in integrations.items():
58
+ # Perform health check
59
+ health = integration.check_health()
60
+ status_icon = "✅" if health.available else "❌"
61
+
62
+ output.append(
63
+ {
64
+ "key": key,
65
+ "name": integration.name,
66
+ "status": status_icon,
67
+ "binary": integration.bin_name or "-",
68
+ "enabled": integration.enabled,
69
+ "rules": integration.system_prompt_file,
70
+ }
71
+ )
72
+
73
+ print_output(output, title="Agent Providers")
74
+
75
+
76
+ @provider_app.command(name="check")
77
+ def check_providers():
78
+ """
79
+ Run health checks on available providers.
80
+ """
81
+ from monoco.core.integrations import get_all_integrations
82
+
83
+ integrations = get_all_integrations(enabled_only=True)
84
+
85
+ output = []
86
+ for key, integration in integrations.items():
87
+ health = integration.check_health()
88
+ output.append(
89
+ {
90
+ "provider": integration.name,
91
+ "available": "✅" if health.available else "❌",
92
+ "latency": f"{health.latency_ms}ms" if health.latency_ms else "-",
93
+ "error": health.error or "-",
94
+ }
95
+ )
96
+
97
+ print_output(output, title="Provider Health Check")
98
+
99
+
100
+ @app.command()
101
+ def run(
102
+ prompt: Optional[list[str]] = typer.Argument(None, help="Instructions for the agent (e.g. 'Fix the bug')."),
103
+ issue: Optional[str] = typer.Option(
104
+ None, "--issue", "-i", help="Link to a specific Issue ID (e.g. FEAT-101)."
105
+ ),
106
+ role: str = typer.Option(
107
+ "Default", "--role", "-r", help="Specific role to use."
108
+ ),
109
+ detach: bool = typer.Option(
110
+ False, "--detach", "-d", help="Run in background (Daemon)"
111
+ ),
112
+ provider: Optional[str] = typer.Option(
113
+ None, "--provider", "-p", help="Override the default engine/provider for this session."
114
+ ),
115
+ ):
116
+ """
117
+ Start an agent session.
118
+
119
+ Usage:
120
+ monoco agent run "Check memos"
121
+ monoco agent run -i FEAT-101 "Implement feature"
122
+ """
123
+ settings = get_config()
124
+ project_root = Path(settings.paths.root).resolve()
125
+
126
+ # 1. Resolve Inputs
127
+ full_prompt = " ".join(prompt) if prompt else ""
128
+
129
+ if issue:
130
+ # User explicitly linked an issue
131
+ issue_id = issue.upper()
132
+ description = full_prompt or None
133
+ else:
134
+ # Ad-hoc task check
135
+ import re
136
+ # Heuristic: if prompt looks like an ID and is short, maybe they meant ID?
137
+ # But explicit is better. Let's assume everything in prompt is instructions.
138
+ issue_id = "NEW_TASK"
139
+ description = full_prompt
140
+
141
+ if not description and not issue:
142
+ print_error("Please provide either a PROMPT or an --issue ID.")
143
+ raise typer.Exit(code=1)
144
+
145
+ # 2. Load Roles
146
+ roles = load_scheduler_config(project_root)
147
+ selected_role = roles.get(role)
148
+
149
+ if not selected_role:
150
+ print_error(f"Role '{role}' not found. Available: {list(roles.keys())}")
151
+ raise typer.Exit(code=1)
152
+
153
+ # 3. Provider Override & Fallback Logic
154
+ target_engine = provider or selected_role.engine
155
+ from monoco.core.integrations import get_integration, get_all_integrations
156
+
157
+ integration = get_integration(target_engine)
158
+
159
+ # If integration is found, check health
160
+ is_available = False
161
+ if integration:
162
+ health = integration.check_health()
163
+ is_available = health.available
164
+ if not is_available and provider:
165
+ # If user explicitly requested this provider, fail hard
166
+ print_error(f"Requested provider '{target_engine}' is not available.")
167
+ print_error(f"Error: {health.error}")
168
+ raise typer.Exit(code=1)
169
+
170
+ # Auto-fallback if default provider is unavailable
171
+ if not is_available:
172
+ print_output(f"⚠️ Provider '{target_engine}' is not available. Searching for fallback...", style="yellow")
173
+
174
+ all_integrations = get_all_integrations(enabled_only=True)
175
+ fallback_found = None
176
+
177
+ # Priority list for fallback
178
+ priority = ["cursor", "claude", "gemini", "qwen", "kimi"]
179
+
180
+ # Try priority matches first
181
+ for key in priority:
182
+ if key in all_integrations:
183
+ if all_integrations[key].check_health().available:
184
+ fallback_found = key
185
+ break
186
+
187
+ # Determine strict fallback
188
+ if fallback_found:
189
+ print_output(f"🔄 Falling back to available provider: [bold green]{fallback_found}[/bold green]")
190
+ selected_role.engine = fallback_found
191
+ else:
192
+ # If NO CLI tools available, maybe generic agent?
193
+ if "agent" in all_integrations:
194
+ print_output("🔄 Falling back to Generic Agent (No CLI execution).", style="yellow")
195
+ selected_role.engine = "agent"
196
+ else:
197
+ print_error("❌ No available agent providers found on this system.")
198
+ print_error("Please install Cursor, Claude Code, or Gemini CLI.")
199
+ raise typer.Exit(code=1)
200
+ elif provider:
201
+ # If available and user overrode it
202
+ print_output(f"Overriding provider: {selected_role.engine} -> {provider}")
203
+ selected_role.engine = provider
204
+
205
+ display_target = issue if issue else (full_prompt[:50] + "..." if len(full_prompt) > 50 else full_prompt)
206
+ print_output(
207
+ f"Starting Agent Session for '{display_target}' as {role} (via {selected_role.engine})...",
208
+ title="Agent Framework",
209
+ )
210
+
211
+ # 4. Initialize Session
212
+ manager = SessionManager()
213
+ session = manager.create_session(issue_id, selected_role)
214
+
215
+
216
+ try:
217
+ # Pass description if it's a new task
218
+ context = {"description": description} if description else None
219
+ session.start(context=context)
220
+
221
+ if detach:
222
+ print_output(
223
+ f"Session {session.model.id} started in background (detached)."
224
+ )
225
+ return
226
+
227
+ # Monitoring Loop
228
+ while session.refresh_status() == "running":
229
+ time.sleep(1)
230
+
231
+ if session.model.status == "failed":
232
+ print_error(
233
+ f"Session {session.model.id} FAILED. Review logs for details."
234
+ )
235
+ else:
236
+ print_output(
237
+ f"Session finished with status: {session.model.status}",
238
+ title="Agent Framework",
239
+ )
240
+
241
+ except KeyboardInterrupt:
242
+ print("\nStopping...")
243
+ session.terminate()
244
+ print_output("Session terminated.")
245
+
246
+
247
+ @session_app.command(name="kill")
248
+ def kill_session(session_id: str):
249
+ """
250
+ Terminate a specific session.
251
+ """
252
+ manager = SessionManager()
253
+ session = manager.get_session(session_id)
254
+ if session:
255
+ session.terminate()
256
+ print_output(f"Session {session_id} terminated.")
257
+ else:
258
+ print_output(f"Session {session_id} not found.", style="red")
259
+
260
+
261
+ @session_app.command(name="list")
262
+ def list_sessions():
263
+ """
264
+ List active agent sessions.
265
+ """
266
+ manager = SessionManager()
267
+ sessions = manager.list_sessions()
268
+
269
+ output = []
270
+ for s in sessions:
271
+ output.append(
272
+ {
273
+ "id": s.model.id,
274
+ "issue": s.model.issue_id,
275
+ "role": s.model.role_name,
276
+ "status": s.model.status,
277
+ "branch": s.model.branch_name,
278
+ }
279
+ )
280
+
281
+ print_output(
282
+ output
283
+ or "No active sessions found (Note: Persistence not implemented in CLI list yet).",
284
+ title="Active Sessions",
285
+ )
286
+
287
+
288
+ @session_app.command(name="logs")
289
+ def session_logs(session_id: str):
290
+ """
291
+ Stream logs for a session.
292
+ """
293
+ print_output(f"Streaming logs for {session_id}...", title="Session Logs")
294
+ # Placeholder
295
+ print("[12:00:00] Session started")
296
+ print("[12:00:01] Worker initialized")
@@ -0,0 +1,96 @@
1
+ from typing import Dict, Optional
2
+ import yaml
3
+ from pathlib import Path
4
+ from .models import RoleTemplate, AgentRoleConfig as AgentConfig
5
+ from .defaults import DEFAULT_ROLES
6
+
7
+
8
+ class RoleLoader:
9
+ """
10
+ Tiered configuration loader for Agent Roles.
11
+ Level 1: Builtin Resources (monoco/features/agent/resources/roles/*.yaml)
12
+ Level 2: Global (~/.monoco/roles/*.yaml)
13
+ Level 3: Project (./.monoco/roles/*.yaml)
14
+ """
15
+
16
+ def __init__(self, project_root: Optional[Path] = None):
17
+ self.project_root = project_root
18
+ self.user_home = Path.home()
19
+ self.roles: Dict[str, RoleTemplate] = {}
20
+ self.sources: Dict[str, str] = {} # role_name -> source description
21
+
22
+ def load_all(self) -> Dict[str, RoleTemplate]:
23
+ # Level 1: Defaults (Hardcoded)
24
+ for role in DEFAULT_ROLES:
25
+ if role.name not in self.roles:
26
+ self.roles[role.name] = role
27
+ self.sources[role.name] = "builtin (default)"
28
+
29
+ # Level 2: Global
30
+ global_monoco = self.user_home / ".monoco"
31
+ self._load_from_file(global_monoco / "roles.yaml", "global")
32
+ self._load_from_dir(global_monoco / "roles", "global")
33
+
34
+ # Level 3: Project
35
+ if self.project_root:
36
+ project_monoco = self.project_root / ".monoco"
37
+ self._load_from_file(project_monoco / "roles.yaml", "project")
38
+ self._load_from_dir(project_monoco / "roles", "project")
39
+
40
+ return self.roles
41
+
42
+ def _load_from_dir(self, directory: Path, source_label: str):
43
+ if not directory.exists() or not directory.is_dir():
44
+ return
45
+
46
+ for file in directory.glob("*.yaml"):
47
+ self._load_from_file(file, source_label)
48
+
49
+ def _load_from_file(self, path: Path, source_label: str):
50
+ if not path.exists():
51
+ return
52
+
53
+ try:
54
+ with open(path, "r") as f:
55
+ data = yaml.safe_load(f) or {}
56
+
57
+ # Case A: Config object with "roles" list
58
+ if "roles" in data and isinstance(data["roles"], list):
59
+ config = AgentConfig(roles=data["roles"])
60
+ for role in config.roles:
61
+ self._upsert_role(role, str(path))
62
+
63
+ # Case B: Single Role object
64
+ elif "name" in data and "system_prompt" in data:
65
+ role = RoleTemplate(**data)
66
+ self._upsert_role(role, str(path))
67
+
68
+ except Exception as e:
69
+ import sys
70
+ print(f"Warning: Failed to load roles from {path}: {e}", file=sys.stderr)
71
+
72
+ def _upsert_role(self, role: RoleTemplate, source: str):
73
+ self.roles[role.name] = role
74
+ self.sources[role.name] = source
75
+
76
+
77
+ def load_scheduler_config(project_root: Path) -> Dict[str, RoleTemplate]:
78
+ """
79
+ Legacy compatibility wrapper for functional access.
80
+ """
81
+ loader = RoleLoader(project_root)
82
+ return loader.load_all()
83
+
84
+
85
+ def load_agent_config(project_root: Path) -> Dict[str, RoleTemplate]:
86
+ """
87
+ Load agent configuration from tiered sources.
88
+
89
+ Args:
90
+ project_root: Path to the project root directory
91
+
92
+ Returns:
93
+ Dictionary mapping role names to RoleTemplate objects
94
+ """
95
+ loader = RoleLoader(project_root)
96
+ return loader.load_all()
@@ -0,0 +1,12 @@
1
+ from .models import RoleTemplate
2
+
3
+ DEFAULT_ROLES = [
4
+ RoleTemplate(
5
+ name="Default",
6
+ description="A generic, jack-of-all-trades agent used when no specific role is configured.",
7
+ trigger="task.dispatch",
8
+ goal="Complete the assigned task.",
9
+ system_prompt="You are a helpful Agent. Complete the task assigned to you.",
10
+ engine="gemini",
11
+ )
12
+ ]
@@ -48,12 +48,13 @@ class GeminiAdapter(EngineAdapter):
48
48
  """
49
49
  Adapter for Google Gemini CLI.
50
50
 
51
- Command format: gemini -y <prompt>
51
+ Command format: gemini -p <prompt> -y
52
52
  The -y flag enables "YOLO mode" (auto-approval of actions).
53
53
  """
54
54
 
55
55
  def build_command(self, prompt: str) -> List[str]:
56
- return ["gemini", "-y", prompt]
56
+ # Based on Gemini CLI help: -p <prompt> for non-interactive
57
+ return ["gemini", "-p", prompt, "-y"]
57
58
 
58
59
  @property
59
60
  def name(self) -> str:
@@ -69,10 +70,12 @@ class ClaudeAdapter(EngineAdapter):
69
70
  Adapter for Anthropic Claude CLI.
70
71
 
71
72
  Command format: claude -p <prompt>
72
- The -p/--print flag enables non-interactive mode (print response and exit).
73
+ The -p/--print flag enables non-interactive mode.
73
74
  """
74
75
 
75
76
  def build_command(self, prompt: str) -> List[str]:
77
+ # Based on Claude CLI help: -p <prompt> is NOT standard, usually -p means print/non-interactive.
78
+ # But for one-shot execution, we do passing prompt as argument with -p flag.
76
79
  return ["claude", "-p", prompt]
77
80
 
78
81
  @property
@@ -89,12 +92,12 @@ class QwenAdapter(EngineAdapter):
89
92
  """
90
93
  Adapter for Qwen Code CLI.
91
94
 
92
- Command format: qwen -y <prompt>
93
- The -y flag enables "YOLO mode" (auto-approval of actions).
95
+ Command format: qwen -p <prompt> -y
94
96
  """
95
97
 
96
98
  def build_command(self, prompt: str) -> List[str]:
97
- return ["qwen", "-y", prompt]
99
+ # Assuming Qwen follows similar patterns (based on user feedback)
100
+ return ["qwen", "-p", prompt, "-y"]
98
101
 
99
102
  @property
100
103
  def name(self) -> str:
@@ -105,6 +108,28 @@ class QwenAdapter(EngineAdapter):
105
108
  return True
106
109
 
107
110
 
111
+ class KimiAdapter(EngineAdapter):
112
+ """
113
+ Adapter for Kimi CLI (Moonshot AI).
114
+
115
+ Command format: kimi -p <prompt> --print
116
+ Note: --print implicitly adds --yolo.
117
+ """
118
+
119
+ def build_command(self, prompt: str) -> List[str]:
120
+ # Based on Kimi CLI help: -p, --prompt TEXT.
121
+ # Also using --print for non-interactive mode (which enables yolo).
122
+ return ["kimi", "-p", prompt, "--print"]
123
+
124
+ @property
125
+ def name(self) -> str:
126
+ return "kimi"
127
+
128
+ @property
129
+ def supports_yolo_mode(self) -> bool:
130
+ return True
131
+
132
+
108
133
  class EngineFactory:
109
134
  """
110
135
  Factory for creating engine adapter instances.
@@ -118,6 +143,7 @@ class EngineFactory:
118
143
  "gemini": GeminiAdapter,
119
144
  "claude": ClaudeAdapter,
120
145
  "qwen": QwenAdapter,
146
+ "kimi": KimiAdapter,
121
147
  }
122
148
 
123
149
  @classmethod