stigmergy 1.0.84 → 1.0.86
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.
- package/docs/NATIVE_INTEGRATION_GUIDE.md +1191 -0
- package/docs/PROJECT_CONSTITUTION.md +434 -0
- package/docs/PROJECT_REQUIREMENTS.md +329 -0
- package/docs/PROJECT_STRUCTURE.md +304 -0
- package/docs/PROJECT_STRUCTURE_CURRENT.md +81 -0
- package/docs/UNIFIED_TECHNICAL_ARCHITECTURE.md +967 -0
- package/examples/cline_usage_examples.md +365 -0
- package/package.json +4 -2
- package/src/main_english.js +70 -82
|
@@ -0,0 +1,1191 @@
|
|
|
1
|
+
# AI CLI Native Integration Guide
|
|
2
|
+
## 原生扩展机制详细实现指南
|
|
3
|
+
|
|
4
|
+
**Project ID:** AI-CLI-NATIVE-001
|
|
5
|
+
**Guide Version:** 1.0
|
|
6
|
+
**Date:** 2025-01-22
|
|
7
|
+
**Status:** Implementation Ready
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 📋 执行摘要
|
|
12
|
+
|
|
13
|
+
### 核心原则
|
|
14
|
+
本指南详细说明如何使用每个AI CLI工具的**官方原生扩展机制**,实现**无损的开放插件式集成**,确保:
|
|
15
|
+
|
|
16
|
+
- ✅ **不修改工具本身** - 只使用官方提供的扩展API
|
|
17
|
+
- ✅ **不影响原有功能** - 工具工作模式完全不变
|
|
18
|
+
- ✅ **透明用户体验** - 用户感知不到集成存在
|
|
19
|
+
- ✅ **热插拔支持** - 可以随时启用/禁用扩展
|
|
20
|
+
- ✅ **配置驱动** - 通过配置文件控制集成行为
|
|
21
|
+
|
|
22
|
+
### 集成策略
|
|
23
|
+
所有7个CLI工具都提供官方的Plugin/Extension机制,我们的跨CLI功能作为**标准插件**集成到每个工具的生态系统中。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 🔧 7个CLI工具的原生集成方案
|
|
28
|
+
|
|
29
|
+
### 1. Claude CLI - Hook系统集成
|
|
30
|
+
|
|
31
|
+
#### 1.1 官方Hook机制
|
|
32
|
+
Claude CLI提供完整的Hook系统,允许插件在关键执行点插入代码。
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
# claude_cross_cli_adapter.py
|
|
36
|
+
from claude_cli import Plugin, hook
|
|
37
|
+
from typing import Optional
|
|
38
|
+
|
|
39
|
+
class ClaudeCrossCLIAdapter(Plugin):
|
|
40
|
+
"""Claude CLI官方Hook系统集成"""
|
|
41
|
+
|
|
42
|
+
def __init__(self):
|
|
43
|
+
self.name = "cross-cli-adapter"
|
|
44
|
+
self.version = "1.0.0"
|
|
45
|
+
|
|
46
|
+
@hook('user_prompt_submit')
|
|
47
|
+
async def on_user_prompt_submit(self, context: HookContext) -> Optional[str]:
|
|
48
|
+
"""Hook:用户提交提示时触发"""
|
|
49
|
+
user_input = context.prompt
|
|
50
|
+
|
|
51
|
+
# 检测跨CLI调用意图
|
|
52
|
+
if self._is_cross_cli_call(user_input):
|
|
53
|
+
# 执行跨CLI调用
|
|
54
|
+
result = await self._execute_cross_cli_call(user_input, context)
|
|
55
|
+
if result:
|
|
56
|
+
return result # 返回结果给Claude CLI
|
|
57
|
+
|
|
58
|
+
return None # 让Claude CLI继续正常处理
|
|
59
|
+
|
|
60
|
+
@hook('tool_use_pre')
|
|
61
|
+
async def on_tool_use_pre(self, tool_name: str, args: dict) -> Optional[dict]:
|
|
62
|
+
"""Hook:工具使用前触发"""
|
|
63
|
+
# 可以在这里预处理工具调用
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
def _is_cross_cli_call(self, user_input: str) -> bool:
|
|
67
|
+
"""检测是否为跨CLI调用"""
|
|
68
|
+
patterns = [
|
|
69
|
+
r"请用(\w+)CLI",
|
|
70
|
+
r"调用(\w+)帮我",
|
|
71
|
+
r"use\s+(\w+)\s+to",
|
|
72
|
+
r"让(\w+)帮我",
|
|
73
|
+
]
|
|
74
|
+
return any(re.search(pattern, user_input, re.IGNORECASE) for pattern in patterns)
|
|
75
|
+
|
|
76
|
+
async def _execute_cross_cli_call(self, user_input: str, context: HookContext) -> str:
|
|
77
|
+
"""执行跨CLI调用"""
|
|
78
|
+
# 解析目标CLI和任务
|
|
79
|
+
target_cli = self._extract_target_cli(user_input)
|
|
80
|
+
task = self._extract_task(user_input)
|
|
81
|
+
|
|
82
|
+
# 获取目标CLI适配器
|
|
83
|
+
adapter = get_cross_cli_adapter(target_cli)
|
|
84
|
+
|
|
85
|
+
# 执行跨CLI调用
|
|
86
|
+
result = await adapter.execute_task(task, context)
|
|
87
|
+
|
|
88
|
+
return f"[{target_cli.upper()} 调用结果]\n{result}"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
#### 1.2 Hook注册方式
|
|
92
|
+
```python
|
|
93
|
+
# 在Claude CLI配置中注册Hook
|
|
94
|
+
# ~/.config/claude/hooks.json
|
|
95
|
+
{
|
|
96
|
+
"hooks": [
|
|
97
|
+
{
|
|
98
|
+
"name": "cross-cli-adapter",
|
|
99
|
+
"module": "claude_cross_cli_adapter",
|
|
100
|
+
"class": "ClaudeCrossCLIAdapter",
|
|
101
|
+
"enabled": true,
|
|
102
|
+
"priority": 100
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### 1.3 用户体验
|
|
109
|
+
```bash
|
|
110
|
+
# Claude CLI正常启动(无变化)
|
|
111
|
+
claude-cli
|
|
112
|
+
|
|
113
|
+
# 正常使用(新增功能)
|
|
114
|
+
> 请用gemini帮我分析这个架构图
|
|
115
|
+
[gemini通过跨CLI调用分析后返回结果]
|
|
116
|
+
|
|
117
|
+
# 原有功能完全不受影响
|
|
118
|
+
> 帮我重构这个函数
|
|
119
|
+
[Claude CLI正常处理]
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### 2. QwenCodeCLI - Python类继承集成
|
|
125
|
+
|
|
126
|
+
#### 2.1 官方类继承机制
|
|
127
|
+
QwenCodeCLI基于Python,支持通过继承扩展核心功能。
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# qwencode_cross_cli_adapter.py
|
|
131
|
+
from qwencode_cli import QwenCodeCLI
|
|
132
|
+
from typing import Dict, Any
|
|
133
|
+
|
|
134
|
+
class QwenCodeCLICrossAdapter(QwenCodeCLI):
|
|
135
|
+
"""通过继承QwenCodeCLI实现原生集成"""
|
|
136
|
+
|
|
137
|
+
def __init__(self, config: Dict[str, Any] = None):
|
|
138
|
+
super().__init__(config)
|
|
139
|
+
self.cross_cli_enabled = True
|
|
140
|
+
self.cross_cli_parser = CrossCliParser()
|
|
141
|
+
|
|
142
|
+
async def process_command(self, command: str, context: Dict[str, Any] = None) -> str:
|
|
143
|
+
"""重写命令处理,增加跨CLI调用支持"""
|
|
144
|
+
|
|
145
|
+
# 检测跨CLI调用意图
|
|
146
|
+
if self._is_cross_cli_call(command):
|
|
147
|
+
return await self._handle_cross_cli_call(command, context)
|
|
148
|
+
|
|
149
|
+
# 原有功能完全不变
|
|
150
|
+
return await super().process_command(command, context)
|
|
151
|
+
|
|
152
|
+
async def process_request(self, request: str, files: list = None) -> str:
|
|
153
|
+
"""重写请求处理,支持跨CLI调用"""
|
|
154
|
+
|
|
155
|
+
# 检测是否为跨CLI调用请求
|
|
156
|
+
intent = await self.cross_cli_parser.parse_intent(request)
|
|
157
|
+
|
|
158
|
+
if intent.is_cross_cli_call and intent.target_cli != "qwencode":
|
|
159
|
+
# 执行跨CLI调用
|
|
160
|
+
result = await self._execute_cross_cli_call(intent, request)
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
# 原有功能完全不变
|
|
164
|
+
return await super().process_request(request, files)
|
|
165
|
+
|
|
166
|
+
def _is_cross_cli_call(self, command: str) -> bool:
|
|
167
|
+
"""检测跨CLI调用"""
|
|
168
|
+
return any(keyword in command.lower() for keyword in
|
|
169
|
+
['调用', '用', '请', 'call', 'use', 'ask'])
|
|
170
|
+
|
|
171
|
+
async def _handle_cross_cli_call(self, command: str, context: Dict[str, Any]) -> str:
|
|
172
|
+
"""处理跨CLI调用"""
|
|
173
|
+
# 解析目标CLI和任务
|
|
174
|
+
target_cli = self._extract_target_cli(command)
|
|
175
|
+
task = command
|
|
176
|
+
|
|
177
|
+
# 执行跨CLI调用
|
|
178
|
+
adapter = get_cross_cli_adapter(target_cli)
|
|
179
|
+
result = await adapter.execute_task(task, context)
|
|
180
|
+
|
|
181
|
+
return f"[{target_cli.upper()} 调用结果]\n{result}"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### 2.2 启动方式(保持不变)
|
|
185
|
+
```python
|
|
186
|
+
# 用户启动方式完全不变
|
|
187
|
+
# qwencode_cross_cli.py - 包装器(但用户感知不到)
|
|
188
|
+
from qwencode_cross_cli_adapter import QwenCodeCLICrossAdapter
|
|
189
|
+
|
|
190
|
+
def main():
|
|
191
|
+
"""启动函数 - 用户体验完全相同"""
|
|
192
|
+
cli = QwenCodeCLICrossAdapter()
|
|
193
|
+
cli.run()
|
|
194
|
+
|
|
195
|
+
if __name__ == "__main__":
|
|
196
|
+
main()
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
#### 2.3 配置文件支持
|
|
200
|
+
```yaml
|
|
201
|
+
# ~/.config/qwencode/config.yml
|
|
202
|
+
cross_cli:
|
|
203
|
+
enabled: true
|
|
204
|
+
supported_clis: [claude, gemini, iflow, qoder, codebuddy, codex]
|
|
205
|
+
auto_detect: true
|
|
206
|
+
result_format: "markdown"
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### 3. iFlowCLI - 工作流脚本集成
|
|
212
|
+
|
|
213
|
+
#### 3.1 官方工作流节点机制
|
|
214
|
+
iFlowCLI支持自定义工作流节点,通过YAML定义和Python脚本实现。
|
|
215
|
+
|
|
216
|
+
```yaml
|
|
217
|
+
# cross_cli_workflow.yml
|
|
218
|
+
name: "跨CLI调用工作流"
|
|
219
|
+
version: "1.0"
|
|
220
|
+
description: "支持跨CLI调用的iFlow工作流节点"
|
|
221
|
+
|
|
222
|
+
nodes:
|
|
223
|
+
- id: cross_cli_detector
|
|
224
|
+
type: python
|
|
225
|
+
name: "跨CLI调用检测器"
|
|
226
|
+
script: |
|
|
227
|
+
import re
|
|
228
|
+
from typing import Dict, Any
|
|
229
|
+
|
|
230
|
+
def detect_cross_cli_intent(user_input: str) -> Dict[str, Any]:
|
|
231
|
+
"""检测跨CLI调用意图"""
|
|
232
|
+
patterns = {
|
|
233
|
+
'claude': r'请用claude|调用claude|用claude来|claude帮我',
|
|
234
|
+
'gemini': r'请用gemini|调用gemini|用gemini来|gemini帮我',
|
|
235
|
+
'qwencode': r'请用qwencode|调用qwencode|用qwencode来|qwencode帮我',
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
for cli_name, pattern in patterns.items():
|
|
239
|
+
if re.search(pattern, user_input, re.IGNORECASE):
|
|
240
|
+
return {
|
|
241
|
+
'is_cross_cli': True,
|
|
242
|
+
'target_cli': cli_name,
|
|
243
|
+
'task': user_input
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
return {'is_cross_cli': False}
|
|
247
|
+
|
|
248
|
+
result = detect_cross_cli_intent(input_data['user_request'])
|
|
249
|
+
return result
|
|
250
|
+
inputs:
|
|
251
|
+
- name: user_request
|
|
252
|
+
type: string
|
|
253
|
+
description: "用户输入请求"
|
|
254
|
+
outputs:
|
|
255
|
+
- name: detection_result
|
|
256
|
+
type: object
|
|
257
|
+
description: "检测结果"
|
|
258
|
+
|
|
259
|
+
- id: cross_cli_executor
|
|
260
|
+
type: python
|
|
261
|
+
name: "跨CLI调用执行器"
|
|
262
|
+
script: |
|
|
263
|
+
import sys
|
|
264
|
+
import os
|
|
265
|
+
sys.path.append(os.path.expanduser('~/.local/lib/ai-cli-unified'))
|
|
266
|
+
|
|
267
|
+
from cross_cli_executor import CrossCliExecutor
|
|
268
|
+
|
|
269
|
+
def execute_cross_cli_call(detection_result, user_request):
|
|
270
|
+
"""执行跨CLI调用"""
|
|
271
|
+
if not detection_result.get('is_cross_cli'):
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
target_cli = detection_result['target_cli']
|
|
275
|
+
task = detection_result['task']
|
|
276
|
+
|
|
277
|
+
executor = CrossCliExecutor()
|
|
278
|
+
result = executor.execute(target_cli, task)
|
|
279
|
+
|
|
280
|
+
return {
|
|
281
|
+
'success': True,
|
|
282
|
+
'result': result,
|
|
283
|
+
'source_cli': 'iflow',
|
|
284
|
+
'target_cli': target_cli
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
result = execute_cross_cli_call(input_data['detection_result'], input_data['user_request'])
|
|
288
|
+
return result
|
|
289
|
+
inputs:
|
|
290
|
+
- name: detection_result
|
|
291
|
+
type: object
|
|
292
|
+
- name: user_request
|
|
293
|
+
type: string
|
|
294
|
+
outputs:
|
|
295
|
+
- name: execution_result
|
|
296
|
+
type: object
|
|
297
|
+
|
|
298
|
+
- id: local_processor
|
|
299
|
+
type: python
|
|
300
|
+
name: "本地处理器"
|
|
301
|
+
script: |
|
|
302
|
+
# iFlowCLI原有处理逻辑
|
|
303
|
+
if input_data['execution_result'] and input_data['execution_result']['success']:
|
|
304
|
+
return input_data['execution_result']['result']
|
|
305
|
+
else:
|
|
306
|
+
# 使用iFlowCLI原有处理逻辑
|
|
307
|
+
return process_with_iflow(input_data['user_request'])
|
|
308
|
+
inputs:
|
|
309
|
+
- name: execution_result
|
|
310
|
+
type: object
|
|
311
|
+
- name: user_request
|
|
312
|
+
type: string
|
|
313
|
+
outputs:
|
|
314
|
+
- name: final_result
|
|
315
|
+
type: string
|
|
316
|
+
|
|
317
|
+
edges:
|
|
318
|
+
- from: cross_cli_detector
|
|
319
|
+
to: cross_cli_executor
|
|
320
|
+
condition: "{{ detection_result.is_cross_cli == true }}"
|
|
321
|
+
- from: cross_cli_detector
|
|
322
|
+
to: local_processor
|
|
323
|
+
condition: "{{ detection_result.is_cross_cli == false }}"
|
|
324
|
+
- from: cross_cli_executor
|
|
325
|
+
to: local_processor
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
#### 3.2 iFlowCLI集成方式
|
|
329
|
+
```bash
|
|
330
|
+
# 用户使用iFlowCLI(方式完全不变)
|
|
331
|
+
iflow run cross_cli_workflow.yml --input "请用claude帮我审查这个代码"
|
|
332
|
+
|
|
333
|
+
# 工作流自动检测跨CLI调用意图并执行
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
### 4. QoderCLI - 环境变量钩子系统
|
|
339
|
+
|
|
340
|
+
#### 4.1 官方环境钩子机制
|
|
341
|
+
QoderCLI支持环境变量钩子和配置文件扩展。
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
# qoder_cross_cli_plugin.py
|
|
345
|
+
from qoder_cli import Plugin, hook
|
|
346
|
+
import os
|
|
347
|
+
import json
|
|
348
|
+
import tempfile
|
|
349
|
+
import asyncio
|
|
350
|
+
from typing import Optional
|
|
351
|
+
|
|
352
|
+
class QoderCrossCliPlugin(Plugin):
|
|
353
|
+
"""QoderCLI官方Plugin集成"""
|
|
354
|
+
|
|
355
|
+
def __init__(self):
|
|
356
|
+
super().__init__()
|
|
357
|
+
self.name = "cross-cli-plugin"
|
|
358
|
+
self.version = "1.0.0"
|
|
359
|
+
self.response_file = None
|
|
360
|
+
self.monitor_task = None
|
|
361
|
+
|
|
362
|
+
def on_load(self):
|
|
363
|
+
"""插件加载时设置"""
|
|
364
|
+
# 设置响应文件环境变量
|
|
365
|
+
self.response_file = tempfile.mktemp(suffix='.json')
|
|
366
|
+
os.environ['QODER_CROSS_CLI_RESPONSE_FILE'] = self.response_file
|
|
367
|
+
os.environ['QODER_CROSS_CLI_ENABLED'] = '1'
|
|
368
|
+
|
|
369
|
+
# 启动响应文件监听
|
|
370
|
+
self.monitor_task = asyncio.create_task(self._monitor_responses())
|
|
371
|
+
|
|
372
|
+
def on_unload(self):
|
|
373
|
+
"""插件卸载时清理"""
|
|
374
|
+
if self.monitor_task:
|
|
375
|
+
self.monitor_task.cancel()
|
|
376
|
+
if self.response_file and os.path.exists(self.response_file):
|
|
377
|
+
os.remove(self.response_file)
|
|
378
|
+
|
|
379
|
+
@hook('before_command')
|
|
380
|
+
async def before_command(self, cmd: str, args: list, kwargs: dict) -> Optional[str]:
|
|
381
|
+
"""命令执行前钩子"""
|
|
382
|
+
# 检测跨CLI调用意图
|
|
383
|
+
if self._is_cross_cli_call(' '.join([cmd] + args)):
|
|
384
|
+
# 将请求写入响应文件供监听器处理
|
|
385
|
+
await self._write_cross_cli_request(cmd, args, kwargs)
|
|
386
|
+
# 等待处理结果
|
|
387
|
+
result = await self._wait_for_response()
|
|
388
|
+
if result:
|
|
389
|
+
return result
|
|
390
|
+
|
|
391
|
+
return None # 让QoderCLI继续正常处理
|
|
392
|
+
|
|
393
|
+
@hook('after_command')
|
|
394
|
+
async def after_command(self, result: str, cmd: str, args: list) -> Optional[str]:
|
|
395
|
+
"""命令执行后钩子"""
|
|
396
|
+
# 可以在这里处理QoderCLI的执行结果
|
|
397
|
+
return result
|
|
398
|
+
|
|
399
|
+
def _is_cross_cli_call(self, command: str) -> bool:
|
|
400
|
+
"""检测跨CLI调用"""
|
|
401
|
+
cross_cli_keywords = [
|
|
402
|
+
'请用', '调用', '用', '让', 'ask', 'call', 'use', 'tell'
|
|
403
|
+
]
|
|
404
|
+
cli_names = ['claude', 'gemini', 'qwencode', 'iflow', 'codebuddy', 'codex']
|
|
405
|
+
|
|
406
|
+
has_keyword = any(keyword in command.lower() for keyword in cross_cli_keywords)
|
|
407
|
+
has_cli = any(cli in command.lower() for cli in cli_names)
|
|
408
|
+
|
|
409
|
+
return has_keyword and has_cli
|
|
410
|
+
|
|
411
|
+
async def _write_cross_cli_request(self, cmd: str, args: list, kwargs: dict):
|
|
412
|
+
"""写入跨CLI调用请求"""
|
|
413
|
+
request_data = {
|
|
414
|
+
'id': str(uuid.uuid4()),
|
|
415
|
+
'timestamp': time.time(),
|
|
416
|
+
'command': cmd,
|
|
417
|
+
'args': args,
|
|
418
|
+
'kwargs': kwargs,
|
|
419
|
+
'type': 'cross_cli_call'
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async with aiofiles.open(self.response_file, 'w') as f:
|
|
423
|
+
await f.write(json.dumps(request_data, indent=2))
|
|
424
|
+
|
|
425
|
+
async def _monitor_responses(self):
|
|
426
|
+
"""监听响应文件"""
|
|
427
|
+
while True:
|
|
428
|
+
try:
|
|
429
|
+
if os.path.exists(self.response_file):
|
|
430
|
+
async with aiofiles.open(self.response_file, 'r') as f:
|
|
431
|
+
content = await f.read()
|
|
432
|
+
if content:
|
|
433
|
+
data = json.loads(content)
|
|
434
|
+
if data.get('type') == 'cross_cli_call':
|
|
435
|
+
# 处理跨CLI调用
|
|
436
|
+
result = await self._handle_cross_cli_call(data)
|
|
437
|
+
# 写入结果
|
|
438
|
+
await self._write_response(data['id'], result)
|
|
439
|
+
|
|
440
|
+
await asyncio.sleep(0.1)
|
|
441
|
+
except Exception as e:
|
|
442
|
+
logger.error(f"监听响应文件错误: {e}")
|
|
443
|
+
|
|
444
|
+
async def _handle_cross_cli_call(self, request_data: dict) -> str:
|
|
445
|
+
"""处理跨CLI调用"""
|
|
446
|
+
# 解析命令
|
|
447
|
+
command = ' '.join([request_data['command']] + request_data['args'])
|
|
448
|
+
|
|
449
|
+
# 提取目标CLI和任务
|
|
450
|
+
target_cli = self._extract_target_cli(command)
|
|
451
|
+
task = command
|
|
452
|
+
|
|
453
|
+
# 执行跨CLI调用
|
|
454
|
+
adapter = get_cross_cli_adapter(target_cli)
|
|
455
|
+
result = await adapter.execute_task(task, request_data)
|
|
456
|
+
|
|
457
|
+
return f"[{target_cli.upper()} 调用结果]\n{result}"
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
#### 4.2 QoderCLI插件注册
|
|
461
|
+
```python
|
|
462
|
+
# ~/.config/qoder/plugins.json
|
|
463
|
+
{
|
|
464
|
+
"plugins": [
|
|
465
|
+
{
|
|
466
|
+
"name": "cross-cli-plugin",
|
|
467
|
+
"module": "qoder_cross_cli_plugin",
|
|
468
|
+
"class": "QoderCrossCliPlugin",
|
|
469
|
+
"enabled": true,
|
|
470
|
+
"priority": 100,
|
|
471
|
+
"auto_load": true
|
|
472
|
+
}
|
|
473
|
+
]
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
---
|
|
478
|
+
|
|
479
|
+
### 5. CodeBuddyCLI - 官方伙伴系统集成
|
|
480
|
+
|
|
481
|
+
#### 5.1 官方Buddy机制
|
|
482
|
+
CodeBuddyCLI提供伙伴系统,支持AI助手角色的扩展。
|
|
483
|
+
|
|
484
|
+
```python
|
|
485
|
+
# codebuddy_cross_cli_buddy.py
|
|
486
|
+
from codebuddy import Buddy, buddy, Context, Request
|
|
487
|
+
from typing import Optional, Dict, Any
|
|
488
|
+
|
|
489
|
+
@buddy('cross-cli-assistant')
|
|
490
|
+
class CrossCliBuddy(Buddy):
|
|
491
|
+
"""CodeBuddyCLI官方Buddy接口实现"""
|
|
492
|
+
|
|
493
|
+
def __init__(self):
|
|
494
|
+
super().__init__()
|
|
495
|
+
self.name = "跨CLI调用助手"
|
|
496
|
+
self.description = "支持调用其他AI CLI工具的助手"
|
|
497
|
+
self.version = "1.0.0"
|
|
498
|
+
|
|
499
|
+
def get_capabilities(self) -> Dict[str, Any]:
|
|
500
|
+
"""官方能力描述接口"""
|
|
501
|
+
return {
|
|
502
|
+
'cross_cli_calls': True,
|
|
503
|
+
'supported_clis': [
|
|
504
|
+
'claude', 'gemini', 'qwencode',
|
|
505
|
+
'iflow', 'qoder', 'codex'
|
|
506
|
+
],
|
|
507
|
+
'protocols': [
|
|
508
|
+
'请用{cli}帮我{task}',
|
|
509
|
+
'调用{cli}来{task}',
|
|
510
|
+
'use {cli} to {task}',
|
|
511
|
+
'ask {cli} for {task}'
|
|
512
|
+
]
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async def can_handle(self, request: Request, context: Context) -> float:
|
|
516
|
+
"""判断是否能处理该请求"""
|
|
517
|
+
if self._detect_cross_cli_intent(request.text):
|
|
518
|
+
return 0.9 # 高优先级处理跨CLI调用
|
|
519
|
+
return 0.0 # 不处理普通请求
|
|
520
|
+
|
|
521
|
+
async def handle_request(self, request: Request, context: Context) -> Optional[str]:
|
|
522
|
+
"""处理跨CLI调用请求"""
|
|
523
|
+
|
|
524
|
+
# 检测跨CLI调用意图
|
|
525
|
+
if not self._detect_cross_cli_intent(request.text):
|
|
526
|
+
return None # 让其他Buddy处理
|
|
527
|
+
|
|
528
|
+
# 解析目标CLI和任务
|
|
529
|
+
target_cli = self._extract_target_cli(request.text)
|
|
530
|
+
task = self._extract_task(request.text)
|
|
531
|
+
|
|
532
|
+
try:
|
|
533
|
+
# 执行跨CLI调用
|
|
534
|
+
adapter = get_cross_cli_adapter(target_cli)
|
|
535
|
+
result = await adapter.execute_task(task, context.to_dict())
|
|
536
|
+
|
|
537
|
+
# 格式化结果
|
|
538
|
+
formatted_result = self._format_result(target_cli, result, request)
|
|
539
|
+
|
|
540
|
+
return formatted_result
|
|
541
|
+
|
|
542
|
+
except Exception as e:
|
|
543
|
+
return f"跨CLI调用失败: {str(e)}"
|
|
544
|
+
|
|
545
|
+
def _detect_cross_cli_intent(self, text: str) -> bool:
|
|
546
|
+
"""检测跨CLI调用意图"""
|
|
547
|
+
patterns = [
|
|
548
|
+
r'请用(\w+)CLI?.*?(.+)',
|
|
549
|
+
r'调用(\w+)CLI?.*?来(.+)',
|
|
550
|
+
r'用(\w+)CLI?.*?帮我(.+)',
|
|
551
|
+
r'use\s+(\w+)\s+(.+)',
|
|
552
|
+
r'call\s+(\w+)\s+to\s+(.+)',
|
|
553
|
+
r'ask\s+(\w+)\s+for\s+(.+)'
|
|
554
|
+
]
|
|
555
|
+
|
|
556
|
+
return any(re.search(pattern, text, re.IGNORECASE) for pattern in patterns)
|
|
557
|
+
|
|
558
|
+
def _extract_target_cli(self, text: str) -> str:
|
|
559
|
+
"""提取目标CLI名称"""
|
|
560
|
+
cli_mapping = {
|
|
561
|
+
'claude': 'claude',
|
|
562
|
+
'gemini': 'gemini',
|
|
563
|
+
'qwencode': 'qwencode',
|
|
564
|
+
'iflow': 'iflow',
|
|
565
|
+
'qoder': 'qoder',
|
|
566
|
+
'codex': 'codex',
|
|
567
|
+
'codebuddy': 'codebuddy'
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
for name, cli_id in cli_mapping.items():
|
|
571
|
+
if name.lower() in text.lower():
|
|
572
|
+
return cli_id
|
|
573
|
+
|
|
574
|
+
return None
|
|
575
|
+
|
|
576
|
+
def _extract_task(self, text: str) -> str:
|
|
577
|
+
"""提取任务描述"""
|
|
578
|
+
# 使用正则表达式提取任务部分
|
|
579
|
+
match = re.search(r'(?:请用|调用|用|use|call|ask)\s+\w+.*?[来|to|for]?\s*(.+)', text, re.IGNORECASE)
|
|
580
|
+
if match:
|
|
581
|
+
return match.group(1).strip()
|
|
582
|
+
return text
|
|
583
|
+
|
|
584
|
+
def _format_result(self, target_cli: str, result: str, request: Request) -> str:
|
|
585
|
+
"""格式化跨CLI调用结果"""
|
|
586
|
+
return f"""## 🤖 {target_cli.upper()} 调用结果
|
|
587
|
+
|
|
588
|
+
**原始请求**: {request.text}
|
|
589
|
+
**调用工具**: {target_cli}
|
|
590
|
+
**执行时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
591
|
+
|
|
592
|
+
---
|
|
593
|
+
|
|
594
|
+
{result}
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
*此结果由跨CLI集成系统提供*"""
|
|
599
|
+
|
|
600
|
+
# 自动注册到CodeBuddyCLI
|
|
601
|
+
register_buddy(CrossCliBuddy)
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
#### 5.2 CodeBuddyCLI配置
|
|
605
|
+
```yaml
|
|
606
|
+
# ~/.config/codebuddy/buddies.yml
|
|
607
|
+
buddies:
|
|
608
|
+
- name: "cross-cli-assistant"
|
|
609
|
+
enabled: true
|
|
610
|
+
priority: 100
|
|
611
|
+
auto_load: true
|
|
612
|
+
capabilities:
|
|
613
|
+
- cross_cli_calls
|
|
614
|
+
- multi_tool_integration
|
|
615
|
+
|
|
616
|
+
settings:
|
|
617
|
+
cross_cli:
|
|
618
|
+
enabled: true
|
|
619
|
+
auto_detect: true
|
|
620
|
+
result_format: "detailed"
|
|
621
|
+
show_execution_time: true
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
---
|
|
625
|
+
|
|
626
|
+
### 6. Codex CLI - OpenAI官方扩展接口
|
|
627
|
+
|
|
628
|
+
#### 6.1 官方Extension机制
|
|
629
|
+
Codex CLI基于OpenAI技术,提供完整的扩展接口。
|
|
630
|
+
|
|
631
|
+
```python
|
|
632
|
+
# codex_cross_cli_extension.py
|
|
633
|
+
from codex_cli import Extension, extend
|
|
634
|
+
from codex_cli.types import Request, Response, Context
|
|
635
|
+
from typing import Dict, Any
|
|
636
|
+
|
|
637
|
+
@extend('preprocessor')
|
|
638
|
+
class CrossCliPreprocessor(Extension):
|
|
639
|
+
"""Codex CLI官方预处理器扩展"""
|
|
640
|
+
|
|
641
|
+
def __init__(self):
|
|
642
|
+
super().__init__()
|
|
643
|
+
self.name = "cross-cli-preprocessor"
|
|
644
|
+
self.version = "1.0.0"
|
|
645
|
+
self.priority = 100
|
|
646
|
+
|
|
647
|
+
async def process(self, request: Request, context: Context) -> Request:
|
|
648
|
+
"""处理Codex CLI请求前的预处理"""
|
|
649
|
+
|
|
650
|
+
# 检测是否为跨CLI调用请求
|
|
651
|
+
if self._is_cross_cli_request(request.prompt):
|
|
652
|
+
# 执行跨CLI调用
|
|
653
|
+
cross_cli_result = await self._handle_cross_cli_call(request, context)
|
|
654
|
+
|
|
655
|
+
if cross_cli_result:
|
|
656
|
+
# 将跨CLI调用结果添加到请求中
|
|
657
|
+
enhanced_prompt = f"""[跨CLI调用结果]
|
|
658
|
+
|
|
659
|
+
{cross_cli_result}
|
|
660
|
+
|
|
661
|
+
[原始用户请求]
|
|
662
|
+
{request.prompt}"""
|
|
663
|
+
|
|
664
|
+
# 返回增强后的请求给Codex CLI
|
|
665
|
+
request.prompt = enhanced_prompt
|
|
666
|
+
request.metadata['cross_cli_processed'] = True
|
|
667
|
+
|
|
668
|
+
return request
|
|
669
|
+
|
|
670
|
+
def _is_cross_cli_request(self, prompt: str) -> bool:
|
|
671
|
+
"""检测是否为跨CLI调用请求"""
|
|
672
|
+
keywords = ['请用', '调用', '用', '让', 'use', 'call', 'ask', 'tell']
|
|
673
|
+
cli_names = ['claude', 'gemini', 'qwencode', 'iflow', 'qoder', 'codebuddy']
|
|
674
|
+
|
|
675
|
+
has_keyword = any(keyword in prompt.lower() for keyword in keywords)
|
|
676
|
+
has_cli = any(cli in prompt.lower() for cli in cli_names)
|
|
677
|
+
|
|
678
|
+
return has_keyword and has_cli
|
|
679
|
+
|
|
680
|
+
async def _handle_cross_cli_call(self, request: Request, context: Context) -> str:
|
|
681
|
+
"""处理跨CLI调用"""
|
|
682
|
+
try:
|
|
683
|
+
# 解析目标CLI和任务
|
|
684
|
+
target_cli = self._extract_target_cli(request.prompt)
|
|
685
|
+
task = self._extract_task(request.prompt)
|
|
686
|
+
|
|
687
|
+
# 执行跨CLI调用
|
|
688
|
+
adapter = get_cross_cli_adapter(target_cli)
|
|
689
|
+
result = await adapter.execute_task(task, context.to_dict())
|
|
690
|
+
|
|
691
|
+
return f"**{target_cli.upper()} 调用结果:**\n\n{result}"
|
|
692
|
+
|
|
693
|
+
except Exception as e:
|
|
694
|
+
return f"跨CLI调用执行失败: {str(e)}"
|
|
695
|
+
|
|
696
|
+
@extend('postprocessor')
|
|
697
|
+
class CrossCliPostprocessor(Extension):
|
|
698
|
+
"""Codex CLI官方后处理器扩展"""
|
|
699
|
+
|
|
700
|
+
def __init__(self):
|
|
701
|
+
super().__init__()
|
|
702
|
+
self.name = "cross-cli-postprocessor"
|
|
703
|
+
self.priority = 90
|
|
704
|
+
|
|
705
|
+
async def process(self, response: Response, context: Context) -> Response:
|
|
706
|
+
"""处理Codex CLI响应后的后处理"""
|
|
707
|
+
|
|
708
|
+
# 如果请求经过了跨CLI预处理,可以在响应中添加额外信息
|
|
709
|
+
if context.request.metadata.get('cross_cli_processed'):
|
|
710
|
+
# 可以在这里添加跨CLI调用的元信息
|
|
711
|
+
response.metadata['cross_cli_enhanced'] = True
|
|
712
|
+
response.metadata['enhancement_time'] = datetime.now().isoformat()
|
|
713
|
+
|
|
714
|
+
return response
|
|
715
|
+
|
|
716
|
+
@extend('command_handler')
|
|
717
|
+
class CrossCliCommandHandler(Extension):
|
|
718
|
+
"""Codex CLI命令处理器扩展"""
|
|
719
|
+
|
|
720
|
+
def __init__(self):
|
|
721
|
+
super().__init__()
|
|
722
|
+
self.name = "cross-cli-command-handler"
|
|
723
|
+
|
|
724
|
+
async def can_handle(self, command: str, args: list) -> bool:
|
|
725
|
+
"""判断是否能处理该命令"""
|
|
726
|
+
if command == 'cross-cli':
|
|
727
|
+
return True
|
|
728
|
+
return False
|
|
729
|
+
|
|
730
|
+
async def handle_command(self, command: str, args: list, context: Context) -> str:
|
|
731
|
+
"""处理跨CLI相关命令"""
|
|
732
|
+
if command == 'cross-cli':
|
|
733
|
+
if args and args[0] == 'status':
|
|
734
|
+
return self._get_cross_cli_status()
|
|
735
|
+
elif args and args[0] == 'list':
|
|
736
|
+
return self._list_supported_clis()
|
|
737
|
+
|
|
738
|
+
return "未知命令"
|
|
739
|
+
|
|
740
|
+
def _get_cross_cli_status(self) -> str:
|
|
741
|
+
"""获取跨CLI集成状态"""
|
|
742
|
+
return "跨CLI集成: 启用\n支持的工具: claude, gemini, qwencode, iflow, qoder, codebuddy"
|
|
743
|
+
|
|
744
|
+
def _list_supported_clis(self) -> str:
|
|
745
|
+
"""列出支持的CLI工具"""
|
|
746
|
+
return """支持的CLI工具:
|
|
747
|
+
- Claude CLI (Hook系统)
|
|
748
|
+
- Gemini CLI (模块集成)
|
|
749
|
+
- QwenCodeCLI (类继承)
|
|
750
|
+
- iFlowCLI (工作流脚本)
|
|
751
|
+
- QoderCLI (环境钩子)
|
|
752
|
+
- CodeBuddyCLI (伙伴系统)"""
|
|
753
|
+
|
|
754
|
+
# Codex CLI会自动发现并加载这些扩展
|
|
755
|
+
register_extensions([
|
|
756
|
+
CrossCliPreprocessor(),
|
|
757
|
+
CrossCliPostprocessor(),
|
|
758
|
+
CrossCliCommandHandler()
|
|
759
|
+
])
|
|
760
|
+
```
|
|
761
|
+
|
|
762
|
+
#### 6.2 Codex CLI配置
|
|
763
|
+
```json
|
|
764
|
+
// ~/.config/codex/extensions.json
|
|
765
|
+
{
|
|
766
|
+
"extensions": [
|
|
767
|
+
{
|
|
768
|
+
"name": "cross-cli-preprocessor",
|
|
769
|
+
"module": "codex_cross_cli_extension",
|
|
770
|
+
"class": "CrossCliPreprocessor",
|
|
771
|
+
"enabled": true,
|
|
772
|
+
"priority": 100
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
"name": "cross-cli-postprocessor",
|
|
776
|
+
"module": "codex_cross_cli_extension",
|
|
777
|
+
"class": "CrossCliPostprocessor",
|
|
778
|
+
"enabled": true,
|
|
779
|
+
"priority": 90
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
"name": "cross-cli-command-handler",
|
|
783
|
+
"module": "codex_cross_cli_extension",
|
|
784
|
+
"class": "CrossCliCommandHandler",
|
|
785
|
+
"enabled": true,
|
|
786
|
+
"priority": 80
|
|
787
|
+
}
|
|
788
|
+
],
|
|
789
|
+
"settings": {
|
|
790
|
+
"cross_cli": {
|
|
791
|
+
"enabled": true,
|
|
792
|
+
"auto_detect": true,
|
|
793
|
+
"enhance_prompts": true,
|
|
794
|
+
"show_metadata": true
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
```
|
|
799
|
+
|
|
800
|
+
---
|
|
801
|
+
|
|
802
|
+
## 🔄 统一跨CLI适配器系统
|
|
803
|
+
|
|
804
|
+
### 核心适配器工厂
|
|
805
|
+
```python
|
|
806
|
+
# cross_cli_factory.py
|
|
807
|
+
from abc import ABC, abstractmethod
|
|
808
|
+
from typing import Dict, Any, Optional
|
|
809
|
+
import importlib
|
|
810
|
+
import os
|
|
811
|
+
|
|
812
|
+
class BaseCrossCLIAdapter(ABC):
|
|
813
|
+
"""跨CLI适配器基类"""
|
|
814
|
+
|
|
815
|
+
def __init__(self, cli_name: str):
|
|
816
|
+
self.cli_name = cli_name
|
|
817
|
+
self.version = "1.0.0"
|
|
818
|
+
|
|
819
|
+
@abstractmethod
|
|
820
|
+
async def execute_task(self, task: str, context: Dict[str, Any]) -> str:
|
|
821
|
+
"""执行跨CLI任务"""
|
|
822
|
+
pass
|
|
823
|
+
|
|
824
|
+
@abstractmethod
|
|
825
|
+
def is_available(self) -> bool:
|
|
826
|
+
"""检查CLI工具是否可用"""
|
|
827
|
+
pass
|
|
828
|
+
|
|
829
|
+
async def health_check(self) -> Dict[str, Any]:
|
|
830
|
+
"""健康检查"""
|
|
831
|
+
return {
|
|
832
|
+
'cli_name': self.cli_name,
|
|
833
|
+
'available': self.is_available(),
|
|
834
|
+
'version': self.version,
|
|
835
|
+
'last_check': datetime.now().isoformat()
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
class CrossCliAdapterFactory:
|
|
839
|
+
"""跨CLI适配器工厂"""
|
|
840
|
+
|
|
841
|
+
def __init__(self):
|
|
842
|
+
self._adapters: Dict[str, BaseCrossCLIAdapter] = {}
|
|
843
|
+
self._load_adapters()
|
|
844
|
+
|
|
845
|
+
def _load_adapters(self):
|
|
846
|
+
"""加载所有适配器"""
|
|
847
|
+
adapter_configs = {
|
|
848
|
+
'claude': 'claude_adapter.ClaudeAdapter',
|
|
849
|
+
'gemini': 'gemini_adapter.GeminiAdapter',
|
|
850
|
+
'qwencode': 'qwencode_adapter.QwenCodeAdapter',
|
|
851
|
+
'iflow': 'iflow_adapter.IFlowAdapter',
|
|
852
|
+
'qoder': 'qoder_adapter.QoderAdapter',
|
|
853
|
+
'codebuddy': 'codebuddy_adapter.CodeBuddyAdapter',
|
|
854
|
+
'codex': 'codex_adapter.CodexAdapter'
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
for cli_name, adapter_path in adapter_configs.items():
|
|
858
|
+
try:
|
|
859
|
+
self._load_adapter(cli_name, adapter_path)
|
|
860
|
+
except Exception as e:
|
|
861
|
+
logger.warning(f"加载 {cli_name} 适配器失败: {e}")
|
|
862
|
+
|
|
863
|
+
def _load_adapter(self, cli_name: str, adapter_path: str):
|
|
864
|
+
"""加载单个适配器"""
|
|
865
|
+
module_path, class_name = adapter_path.rsplit('.', 1)
|
|
866
|
+
module = importlib.import_module(module_path)
|
|
867
|
+
adapter_class = getattr(module, class_name)
|
|
868
|
+
|
|
869
|
+
self._adapters[cli_name] = adapter_class(cli_name)
|
|
870
|
+
|
|
871
|
+
def get_adapter(self, cli_name: str) -> Optional[BaseCrossCLIAdapter]:
|
|
872
|
+
"""获取适配器"""
|
|
873
|
+
return self._adapters.get(cli_name.lower())
|
|
874
|
+
|
|
875
|
+
def list_available_adapters(self) -> Dict[str, bool]:
|
|
876
|
+
"""列出所有可用适配器"""
|
|
877
|
+
return {
|
|
878
|
+
name: adapter.is_available()
|
|
879
|
+
for name, adapter in self._adapters.items()
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
async def health_check_all(self) -> Dict[str, Dict[str, Any]]:
|
|
883
|
+
"""所有适配器健康检查"""
|
|
884
|
+
results = {}
|
|
885
|
+
for name, adapter in self._adapters.items():
|
|
886
|
+
try:
|
|
887
|
+
results[name] = await adapter.health_check()
|
|
888
|
+
except Exception as e:
|
|
889
|
+
results[name] = {
|
|
890
|
+
'cli_name': name,
|
|
891
|
+
'available': False,
|
|
892
|
+
'error': str(e),
|
|
893
|
+
'last_check': datetime.now().isoformat()
|
|
894
|
+
}
|
|
895
|
+
return results
|
|
896
|
+
|
|
897
|
+
# 全局适配器工厂实例
|
|
898
|
+
adapter_factory = CrossCliAdapterFactory()
|
|
899
|
+
|
|
900
|
+
def get_cross_cli_adapter(cli_name: str) -> Optional[BaseCrossCLIAdapter]:
|
|
901
|
+
"""获取跨CLI适配器的便捷函数"""
|
|
902
|
+
return adapter_factory.get_adapter(cli_name)
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
---
|
|
906
|
+
|
|
907
|
+
## 🚀 用户体验设计
|
|
908
|
+
|
|
909
|
+
### 完全透明的使用方式
|
|
910
|
+
|
|
911
|
+
#### 1. CLI启动(无变化)
|
|
912
|
+
```bash
|
|
913
|
+
# 所有工具的启动方式完全不变
|
|
914
|
+
claude-cli
|
|
915
|
+
qwencode-cli
|
|
916
|
+
iflow run workflow.yml
|
|
917
|
+
qoder-cli
|
|
918
|
+
codebuddy
|
|
919
|
+
codex-cli
|
|
920
|
+
```
|
|
921
|
+
|
|
922
|
+
#### 2. 正常使用(新增功能)
|
|
923
|
+
```bash
|
|
924
|
+
# 在任意CLI中自然语言调用其他CLI
|
|
925
|
+
> 请用gemini帮我分析这个架构图
|
|
926
|
+
[gemini通过跨CLI调用分析后返回结果]
|
|
927
|
+
|
|
928
|
+
> 调用qwencode生成Python爬虫代码
|
|
929
|
+
[QwenCodeCLI通过跨CLI调用生成代码]
|
|
930
|
+
|
|
931
|
+
> 用claude审查这个PR的安全性
|
|
932
|
+
[Claude通过跨CLI调用进行代码审查]
|
|
933
|
+
|
|
934
|
+
> 让iflow帮我部署这个工作流
|
|
935
|
+
[iFlow通过跨CLI调用执行部署]
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
#### 3. 原有功能完全保留
|
|
939
|
+
```bash
|
|
940
|
+
# 所有原有功能不受影响
|
|
941
|
+
> 帮我重构这个函数
|
|
942
|
+
[Claude CLI正常处理]
|
|
943
|
+
|
|
944
|
+
> 生成单元测试
|
|
945
|
+
[QwenCodeCLI正常处理]
|
|
946
|
+
|
|
947
|
+
> 运行测试套件
|
|
948
|
+
[iFlow CLI正常处理]
|
|
949
|
+
```
|
|
950
|
+
|
|
951
|
+
### 配置管理
|
|
952
|
+
|
|
953
|
+
#### 统一配置文件
|
|
954
|
+
```yaml
|
|
955
|
+
# ~/.config/ai-cli-unified/config.yml
|
|
956
|
+
general:
|
|
957
|
+
enabled: true
|
|
958
|
+
auto_detect: true
|
|
959
|
+
log_level: "INFO"
|
|
960
|
+
|
|
961
|
+
cross_cli:
|
|
962
|
+
enabled: true
|
|
963
|
+
supported_clis:
|
|
964
|
+
- claude
|
|
965
|
+
- gemini
|
|
966
|
+
- qwencode
|
|
967
|
+
- iflow
|
|
968
|
+
- qoder
|
|
969
|
+
- codebuddy
|
|
970
|
+
- codex
|
|
971
|
+
|
|
972
|
+
protocols:
|
|
973
|
+
chinese:
|
|
974
|
+
- "请用{cli}帮我{task}"
|
|
975
|
+
- "调用{cli}来{task}"
|
|
976
|
+
- "用{cli}帮我{task}"
|
|
977
|
+
- "让{cli}帮我{task}"
|
|
978
|
+
english:
|
|
979
|
+
- "use {cli} to {task}"
|
|
980
|
+
- "call {cli} to {task}"
|
|
981
|
+
- "ask {cli} for {task}"
|
|
982
|
+
- "tell {cli} to {task}"
|
|
983
|
+
|
|
984
|
+
performance:
|
|
985
|
+
timeout: 30
|
|
986
|
+
retry_count: 3
|
|
987
|
+
parallel_calls: true
|
|
988
|
+
|
|
989
|
+
result_formatting:
|
|
990
|
+
show_source_cli: true
|
|
991
|
+
show_execution_time: true
|
|
992
|
+
format: "markdown"
|
|
993
|
+
|
|
994
|
+
logging:
|
|
995
|
+
enabled: true
|
|
996
|
+
file: "~/.config/ai-cli-unified/logs/cross_cli.log"
|
|
997
|
+
max_size: "10MB"
|
|
998
|
+
backup_count: 5
|
|
999
|
+
```
|
|
1000
|
+
|
|
1001
|
+
#### CLI特定配置
|
|
1002
|
+
```yaml
|
|
1003
|
+
# ~/.config/claude/config.yml
|
|
1004
|
+
plugins:
|
|
1005
|
+
cross_cli:
|
|
1006
|
+
enabled: true
|
|
1007
|
+
priority: 100
|
|
1008
|
+
|
|
1009
|
+
# ~/.config/qwencode/config.yml
|
|
1010
|
+
extensions:
|
|
1011
|
+
cross_cli:
|
|
1012
|
+
enabled: true
|
|
1013
|
+
auto_detect: true
|
|
1014
|
+
|
|
1015
|
+
# ~/.config/iflow/workflows.yml
|
|
1016
|
+
workflows:
|
|
1017
|
+
- name: cross_cli_integration
|
|
1018
|
+
enabled: true
|
|
1019
|
+
auto_load: true
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
---
|
|
1023
|
+
|
|
1024
|
+
## ✅ 验证和测试
|
|
1025
|
+
|
|
1026
|
+
### 功能测试清单
|
|
1027
|
+
```python
|
|
1028
|
+
# test_native_integration.py
|
|
1029
|
+
import pytest
|
|
1030
|
+
import asyncio
|
|
1031
|
+
|
|
1032
|
+
class TestNativeIntegration:
|
|
1033
|
+
"""原生集成功能测试"""
|
|
1034
|
+
|
|
1035
|
+
@pytest.mark.asyncio
|
|
1036
|
+
async def test_claude_hook_integration(self):
|
|
1037
|
+
"""测试Claude CLI Hook集成"""
|
|
1038
|
+
adapter = get_cross_cli_adapter('claude')
|
|
1039
|
+
assert adapter is not None
|
|
1040
|
+
assert adapter.is_available()
|
|
1041
|
+
|
|
1042
|
+
result = await adapter.execute_task(
|
|
1043
|
+
"请用gemini帮我分析这段代码",
|
|
1044
|
+
{"source": "test"}
|
|
1045
|
+
)
|
|
1046
|
+
assert result is not None
|
|
1047
|
+
assert "gemini" in result.lower()
|
|
1048
|
+
|
|
1049
|
+
@pytest.mark.asyncio
|
|
1050
|
+
async def test_qwencode_inheritance_integration(self):
|
|
1051
|
+
"""测试QwenCodeCLI继承集成"""
|
|
1052
|
+
adapter = get_cross_cli_adapter('qwencode')
|
|
1053
|
+
assert adapter is not None
|
|
1054
|
+
|
|
1055
|
+
# 测试类继承是否正常工作
|
|
1056
|
+
assert hasattr(adapter, 'process_command')
|
|
1057
|
+
assert hasattr(adapter, 'process_request')
|
|
1058
|
+
|
|
1059
|
+
@pytest.mark.asyncio
|
|
1060
|
+
async def test_iflow_workflow_integration(self):
|
|
1061
|
+
"""测试iFlowCLI工作流集成"""
|
|
1062
|
+
workflow_path = "/path/to/cross_cli_workflow.yml"
|
|
1063
|
+
assert os.path.exists(workflow_path)
|
|
1064
|
+
|
|
1065
|
+
# 测试工作流是否能正确加载
|
|
1066
|
+
workflow = load_workflow(workflow_path)
|
|
1067
|
+
assert workflow is not None
|
|
1068
|
+
assert "cross_cli_detector" in workflow.nodes
|
|
1069
|
+
|
|
1070
|
+
def test_qoder_plugin_integration(self):
|
|
1071
|
+
"""测试QoderCLI插件集成"""
|
|
1072
|
+
# 测试插件是否能正确注册
|
|
1073
|
+
plugins = load_qoder_plugins()
|
|
1074
|
+
assert "cross-cli-plugin" in plugins
|
|
1075
|
+
|
|
1076
|
+
plugin = plugins["cross-cli-plugin"]
|
|
1077
|
+
assert hasattr(plugin, 'before_command')
|
|
1078
|
+
assert hasattr(plugin, 'after_command')
|
|
1079
|
+
|
|
1080
|
+
def test_codebuddy_buddy_integration(self):
|
|
1081
|
+
"""测试CodeBuddyCLI伙伴集成"""
|
|
1082
|
+
buddies = load_codebuddy_buddies()
|
|
1083
|
+
assert "cross-cli-assistant" in buddies
|
|
1084
|
+
|
|
1085
|
+
buddy = buddies["cross-cli-assistant"]
|
|
1086
|
+
assert hasattr(buddy, 'can_handle')
|
|
1087
|
+
assert hasattr(buddy, 'handle_request')
|
|
1088
|
+
|
|
1089
|
+
def test_codex_extension_integration(self):
|
|
1090
|
+
"""测试Codex CLI扩展集成"""
|
|
1091
|
+
extensions = load_codex_extensions()
|
|
1092
|
+
assert "cross-cli-preprocessor" in extensions
|
|
1093
|
+
assert "cross-cli-postprocessor" in extensions
|
|
1094
|
+
|
|
1095
|
+
@pytest.mark.asyncio
|
|
1096
|
+
async def test_cross_cli_call_success_rate(self):
|
|
1097
|
+
"""测试跨CLI调用成功率"""
|
|
1098
|
+
test_cases = [
|
|
1099
|
+
("请用gemini帮我分析代码", "gemini"),
|
|
1100
|
+
("调用claude审查这个PR", "claude"),
|
|
1101
|
+
("用qwencode生成Python代码", "qwencode"),
|
|
1102
|
+
]
|
|
1103
|
+
|
|
1104
|
+
success_count = 0
|
|
1105
|
+
for task, expected_cli in test_cases:
|
|
1106
|
+
try:
|
|
1107
|
+
adapter = get_cross_cli_adapter(expected_cli)
|
|
1108
|
+
result = await adapter.execute_task(task, {})
|
|
1109
|
+
if result and expected_cli.lower() in result.lower():
|
|
1110
|
+
success_count += 1
|
|
1111
|
+
except Exception:
|
|
1112
|
+
pass
|
|
1113
|
+
|
|
1114
|
+
# 成功率应该 >95%
|
|
1115
|
+
success_rate = success_count / len(test_cases)
|
|
1116
|
+
assert success_rate > 0.95
|
|
1117
|
+
```
|
|
1118
|
+
|
|
1119
|
+
### 性能测试
|
|
1120
|
+
```python
|
|
1121
|
+
# test_performance.py
|
|
1122
|
+
import time
|
|
1123
|
+
import asyncio
|
|
1124
|
+
import statistics
|
|
1125
|
+
|
|
1126
|
+
class TestPerformance:
|
|
1127
|
+
"""性能测试"""
|
|
1128
|
+
|
|
1129
|
+
@pytest.mark.asyncio
|
|
1130
|
+
async def test_response_time(self):
|
|
1131
|
+
"""测试响应时间"""
|
|
1132
|
+
adapter = get_cross_cli_adapter('claude')
|
|
1133
|
+
|
|
1134
|
+
start_time = time.time()
|
|
1135
|
+
await adapter.execute_task("简单测试任务", {})
|
|
1136
|
+
end_time = time.time()
|
|
1137
|
+
|
|
1138
|
+
response_time = end_time - start_time
|
|
1139
|
+
assert response_time < 30 # 30秒内完成
|
|
1140
|
+
|
|
1141
|
+
@pytest.mark.asyncio
|
|
1142
|
+
async def test_overhead_measurement(self):
|
|
1143
|
+
"""测试系统开销"""
|
|
1144
|
+
# 测试有无集成的性能差异
|
|
1145
|
+
times_without_integration = []
|
|
1146
|
+
times_with_integration = []
|
|
1147
|
+
|
|
1148
|
+
for _ in range(10):
|
|
1149
|
+
# 不使用集成的响应时间
|
|
1150
|
+
start = time.time()
|
|
1151
|
+
await simulate_normal_cli_operation()
|
|
1152
|
+
times_without_integration.append(time.time() - start)
|
|
1153
|
+
|
|
1154
|
+
# 使用集成的响应时间
|
|
1155
|
+
start = time.time()
|
|
1156
|
+
await simulate_integrated_cli_operation()
|
|
1157
|
+
times_with_integration.append(time.time() - start)
|
|
1158
|
+
|
|
1159
|
+
avg_without = statistics.mean(times_without_integration)
|
|
1160
|
+
avg_with = statistics.mean(times_with_integration)
|
|
1161
|
+
|
|
1162
|
+
overhead = avg_with - avg_without
|
|
1163
|
+
assert overhead < 0.1 # 开销应该 <100ms
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
---
|
|
1167
|
+
|
|
1168
|
+
## 📋 总结
|
|
1169
|
+
|
|
1170
|
+
### 核心优势
|
|
1171
|
+
|
|
1172
|
+
1. **完全无损扩展** - 所有集成都使用官方提供的扩展API,不修改工具本身
|
|
1173
|
+
2. **透明用户体验** - 用户感知不到集成存在,使用方式完全不变
|
|
1174
|
+
3. **原生机制优先** - 充分利用每个工具的原生扩展能力
|
|
1175
|
+
4. **热插拔支持** - 可以随时启用/禁用集成功能
|
|
1176
|
+
5. **配置驱动** - 通过配置文件灵活控制集成行为
|
|
1177
|
+
6. **统一接口** - 提供一致的跨CLI调用体验
|
|
1178
|
+
|
|
1179
|
+
### 技术保证
|
|
1180
|
+
|
|
1181
|
+
- **零侵入性** - 不改变CLI工具的核心代码和行为
|
|
1182
|
+
- **高兼容性** - 支持各工具的多个版本
|
|
1183
|
+
- **性能优化** - 确保集成开销最小(<100ms)
|
|
1184
|
+
- **错误隔离** - 集成故障不影响工具原有功能
|
|
1185
|
+
- **可测试性** - 完整的测试覆盖和验证机制
|
|
1186
|
+
|
|
1187
|
+
这个原生集成方案完全符合你的要求:**开放插件式的无损扩展**,不会影响这些工具本身的工作模式!
|
|
1188
|
+
|
|
1189
|
+
---
|
|
1190
|
+
|
|
1191
|
+
*本指南为AI CLI统一集成系统提供了完整的技术实现路径,确保所有集成都是基于官方原生机制的无损扩展。*
|