draft-board 0.1.0-beta.0
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/app/backend/.env.example +9 -0
- package/app/backend/.smartkanban/evidence/8b383839-cbec-45af-86ee-c7708d075cbe/bddf2ed5-2e21-4d46-a62b-10b87f1642a6_patch.txt +195 -0
- package/app/backend/.smartkanban/evidence/8b383839-cbec-45af-86ee-c7708d075cbe/bddf2ed5-2e21-4d46-a62b-10b87f1642a6_stat.txt +6 -0
- package/app/backend/CURL_EXAMPLES.md +335 -0
- package/app/backend/ENV_SETUP.md +65 -0
- package/app/backend/alembic/env.py +71 -0
- package/app/backend/alembic/script.py.mako +28 -0
- package/app/backend/alembic/versions/001_initial_schema.py +104 -0
- package/app/backend/alembic/versions/002_add_jobs_table.py +52 -0
- package/app/backend/alembic/versions/003_add_workspace_table.py +48 -0
- package/app/backend/alembic/versions/004_add_evidence_table.py +56 -0
- package/app/backend/alembic/versions/005_add_verification_commands.py +32 -0
- package/app/backend/alembic/versions/006_add_planner_lock_table.py +39 -0
- package/app/backend/alembic/versions/007_add_revision_review_tables.py +126 -0
- package/app/backend/alembic/versions/008_add_revision_idempotency_and_traceability.py +52 -0
- package/app/backend/alembic/versions/009_add_job_health_fields.py +46 -0
- package/app/backend/alembic/versions/010_add_review_comment_line_content.py +36 -0
- package/app/backend/alembic/versions/011_add_analysis_cache.py +47 -0
- package/app/backend/alembic/versions/012_add_boards_table.py +102 -0
- package/app/backend/alembic/versions/013_add_ticket_blocking.py +45 -0
- package/app/backend/alembic/versions/014_add_agent_sessions.py +220 -0
- package/app/backend/alembic/versions/015_add_ticket_sort_order.py +33 -0
- package/app/backend/alembic/versions/03220f0b93ae_add_pr_fields_to_ticket.py +49 -0
- package/app/backend/alembic/versions/0c2d89fff3b1_seed_board_configs_from_yaml.py +206 -0
- package/app/backend/alembic/versions/3348e5cf54c1_add_merge_checklist_table.py +67 -0
- package/app/backend/alembic/versions/357c780ee445_add_goal_status.py +34 -0
- package/app/backend/alembic/versions/553340b7e26c_add_autonomy_fields_to_goal.py +65 -0
- package/app/backend/alembic/versions/774dc335c679_merge_migration_heads.py +23 -0
- package/app/backend/alembic/versions/7b307e847cbd_merge_heads.py +23 -0
- package/app/backend/alembic/versions/82ecd978cc70_add_missing_indexes.py +48 -0
- package/app/backend/alembic/versions/8ef5054dc280_add_normalized_log_entries.py +173 -0
- package/app/backend/alembic/versions/8f3e2bd8ea3b_merge_migration_heads.py +23 -0
- package/app/backend/alembic/versions/9d17f0698d3b_add_config_column_to_boards_table.py +30 -0
- package/app/backend/alembic/versions/add_agent_conversation_history.py +72 -0
- package/app/backend/alembic/versions/add_job_variant.py +34 -0
- package/app/backend/alembic/versions/add_performance_indexes.py +95 -0
- package/app/backend/alembic/versions/add_repos_and_board_repos.py +174 -0
- package/app/backend/alembic/versions/add_session_id_to_jobs.py +27 -0
- package/app/backend/alembic/versions/add_sqlite_backend_tables.py +104 -0
- package/app/backend/alembic/versions/b10fb0b62240_add_diff_content_to_revisions.py +34 -0
- package/app/backend/alembic.ini +89 -0
- package/app/backend/app/__init__.py +3 -0
- package/app/backend/app/data_dir.py +85 -0
- package/app/backend/app/database.py +70 -0
- package/app/backend/app/database_sync.py +64 -0
- package/app/backend/app/dependencies/__init__.py +5 -0
- package/app/backend/app/dependencies/auth.py +80 -0
- package/app/backend/app/dependencies.py +43 -0
- package/app/backend/app/exceptions.py +178 -0
- package/app/backend/app/executors/__init__.py +1 -0
- package/app/backend/app/executors/adapters/__init__.py +1 -0
- package/app/backend/app/executors/adapters/aider.py +152 -0
- package/app/backend/app/executors/adapters/amazon_q.py +103 -0
- package/app/backend/app/executors/adapters/amp.py +123 -0
- package/app/backend/app/executors/adapters/claude.py +177 -0
- package/app/backend/app/executors/adapters/cline.py +127 -0
- package/app/backend/app/executors/adapters/codex.py +167 -0
- package/app/backend/app/executors/adapters/copilot.py +202 -0
- package/app/backend/app/executors/adapters/cursor.py +87 -0
- package/app/backend/app/executors/adapters/droid.py +123 -0
- package/app/backend/app/executors/adapters/gemini.py +132 -0
- package/app/backend/app/executors/adapters/goose.py +131 -0
- package/app/backend/app/executors/adapters/opencode.py +123 -0
- package/app/backend/app/executors/adapters/qwen.py +123 -0
- package/app/backend/app/executors/plugins/__init__.py +1 -0
- package/app/backend/app/executors/registry.py +202 -0
- package/app/backend/app/executors/spec.py +226 -0
- package/app/backend/app/main.py +486 -0
- package/app/backend/app/middleware/__init__.py +13 -0
- package/app/backend/app/middleware/idempotency.py +426 -0
- package/app/backend/app/middleware/rate_limit.py +312 -0
- package/app/backend/app/middleware/security_headers.py +43 -0
- package/app/backend/app/middleware/timeout.py +37 -0
- package/app/backend/app/models/__init__.py +56 -0
- package/app/backend/app/models/agent_conversation_history.py +56 -0
- package/app/backend/app/models/agent_session.py +127 -0
- package/app/backend/app/models/analysis_cache.py +49 -0
- package/app/backend/app/models/base.py +9 -0
- package/app/backend/app/models/board.py +79 -0
- package/app/backend/app/models/board_repo.py +68 -0
- package/app/backend/app/models/cost_budget.py +42 -0
- package/app/backend/app/models/enums.py +40 -0
- package/app/backend/app/models/evidence.py +132 -0
- package/app/backend/app/models/goal.py +102 -0
- package/app/backend/app/models/idempotency_entry.py +30 -0
- package/app/backend/app/models/job.py +163 -0
- package/app/backend/app/models/job_queue.py +39 -0
- package/app/backend/app/models/kv_store.py +28 -0
- package/app/backend/app/models/merge_checklist.py +87 -0
- package/app/backend/app/models/normalized_log.py +100 -0
- package/app/backend/app/models/planner_lock.py +43 -0
- package/app/backend/app/models/rate_limit_entry.py +25 -0
- package/app/backend/app/models/repo.py +66 -0
- package/app/backend/app/models/review_comment.py +91 -0
- package/app/backend/app/models/review_summary.py +69 -0
- package/app/backend/app/models/revision.py +130 -0
- package/app/backend/app/models/ticket.py +223 -0
- package/app/backend/app/models/ticket_event.py +83 -0
- package/app/backend/app/models/user.py +47 -0
- package/app/backend/app/models/workspace.py +71 -0
- package/app/backend/app/redis_client.py +119 -0
- package/app/backend/app/routers/__init__.py +29 -0
- package/app/backend/app/routers/agents.py +296 -0
- package/app/backend/app/routers/auth.py +94 -0
- package/app/backend/app/routers/board.py +885 -0
- package/app/backend/app/routers/dashboard.py +351 -0
- package/app/backend/app/routers/debug.py +528 -0
- package/app/backend/app/routers/evidence.py +96 -0
- package/app/backend/app/routers/executors.py +324 -0
- package/app/backend/app/routers/goals.py +574 -0
- package/app/backend/app/routers/jobs.py +448 -0
- package/app/backend/app/routers/maintenance.py +172 -0
- package/app/backend/app/routers/merge.py +360 -0
- package/app/backend/app/routers/planner.py +537 -0
- package/app/backend/app/routers/pull_requests.py +382 -0
- package/app/backend/app/routers/repos.py +263 -0
- package/app/backend/app/routers/revisions.py +939 -0
- package/app/backend/app/routers/settings.py +267 -0
- package/app/backend/app/routers/tickets.py +2003 -0
- package/app/backend/app/routers/webhooks.py +143 -0
- package/app/backend/app/routers/websocket.py +249 -0
- package/app/backend/app/schemas/__init__.py +109 -0
- package/app/backend/app/schemas/board.py +87 -0
- package/app/backend/app/schemas/common.py +33 -0
- package/app/backend/app/schemas/evidence.py +87 -0
- package/app/backend/app/schemas/goal.py +90 -0
- package/app/backend/app/schemas/job.py +97 -0
- package/app/backend/app/schemas/merge.py +139 -0
- package/app/backend/app/schemas/planner.py +500 -0
- package/app/backend/app/schemas/repo.py +187 -0
- package/app/backend/app/schemas/review.py +137 -0
- package/app/backend/app/schemas/revision.py +114 -0
- package/app/backend/app/schemas/ticket.py +238 -0
- package/app/backend/app/schemas/ticket_event.py +72 -0
- package/app/backend/app/schemas/workspace.py +19 -0
- package/app/backend/app/services/__init__.py +31 -0
- package/app/backend/app/services/agent_memory_service.py +223 -0
- package/app/backend/app/services/agent_registry.py +346 -0
- package/app/backend/app/services/agent_session_manager.py +318 -0
- package/app/backend/app/services/agent_session_service.py +219 -0
- package/app/backend/app/services/agent_tools.py +379 -0
- package/app/backend/app/services/auth_service.py +98 -0
- package/app/backend/app/services/autonomy_service.py +380 -0
- package/app/backend/app/services/board_repo_service.py +201 -0
- package/app/backend/app/services/board_service.py +326 -0
- package/app/backend/app/services/cleanup_service.py +1085 -0
- package/app/backend/app/services/config_service.py +908 -0
- package/app/backend/app/services/context_gatherer.py +557 -0
- package/app/backend/app/services/cost_tracking_service.py +293 -0
- package/app/backend/app/services/cursor_log_normalizer.py +536 -0
- package/app/backend/app/services/delivery_pipeline.py +440 -0
- package/app/backend/app/services/executor_service.py +634 -0
- package/app/backend/app/services/git_host/__init__.py +11 -0
- package/app/backend/app/services/git_host/factory.py +87 -0
- package/app/backend/app/services/git_host/github.py +270 -0
- package/app/backend/app/services/git_host/gitlab.py +194 -0
- package/app/backend/app/services/git_host/protocol.py +75 -0
- package/app/backend/app/services/git_merge_simple.py +346 -0
- package/app/backend/app/services/git_ops.py +384 -0
- package/app/backend/app/services/github_service.py +233 -0
- package/app/backend/app/services/goal_service.py +113 -0
- package/app/backend/app/services/job_service.py +423 -0
- package/app/backend/app/services/job_watchdog_service.py +424 -0
- package/app/backend/app/services/langchain_adapter.py +122 -0
- package/app/backend/app/services/llm_provider_clients.py +351 -0
- package/app/backend/app/services/llm_service.py +285 -0
- package/app/backend/app/services/log_normalizer.py +342 -0
- package/app/backend/app/services/log_stream_service.py +276 -0
- package/app/backend/app/services/merge_checklist_service.py +264 -0
- package/app/backend/app/services/merge_service.py +784 -0
- package/app/backend/app/services/orchestrator_log.py +84 -0
- package/app/backend/app/services/planner_service.py +1662 -0
- package/app/backend/app/services/planner_tick_sync.py +1040 -0
- package/app/backend/app/services/queued_message_service.py +156 -0
- package/app/backend/app/services/reliability_wrapper.py +389 -0
- package/app/backend/app/services/repo_discovery_service.py +318 -0
- package/app/backend/app/services/review_service.py +334 -0
- package/app/backend/app/services/revision_service.py +389 -0
- package/app/backend/app/services/safe_autopilot.py +510 -0
- package/app/backend/app/services/sqlite_worker.py +372 -0
- package/app/backend/app/services/task_dispatch.py +135 -0
- package/app/backend/app/services/ticket_generation_service.py +1781 -0
- package/app/backend/app/services/ticket_service.py +486 -0
- package/app/backend/app/services/udar_planner_service.py +1007 -0
- package/app/backend/app/services/webhook_service.py +126 -0
- package/app/backend/app/services/workspace_service.py +465 -0
- package/app/backend/app/services/worktree_file_service.py +92 -0
- package/app/backend/app/services/worktree_validator.py +213 -0
- package/app/backend/app/sqlite_kv.py +278 -0
- package/app/backend/app/state_machine.py +128 -0
- package/app/backend/app/templates/__init__.py +5 -0
- package/app/backend/app/templates/registry.py +243 -0
- package/app/backend/app/utils/__init__.py +5 -0
- package/app/backend/app/utils/artifact_reader.py +87 -0
- package/app/backend/app/utils/circuit_breaker.py +229 -0
- package/app/backend/app/utils/db_retry.py +136 -0
- package/app/backend/app/utils/ignored_fields.py +123 -0
- package/app/backend/app/utils/validators.py +54 -0
- package/app/backend/app/websocket/__init__.py +5 -0
- package/app/backend/app/websocket/manager.py +179 -0
- package/app/backend/app/websocket/state_tracker.py +113 -0
- package/app/backend/app/worker.py +3190 -0
- package/app/backend/calculator_tickets.json +40 -0
- package/app/backend/canary_tests.sh +591 -0
- package/app/backend/celerybeat-schedule +0 -0
- package/app/backend/celerybeat-schedule-shm +0 -0
- package/app/backend/celerybeat-schedule-wal +0 -0
- package/app/backend/logs/.gitkeep +3 -0
- package/app/backend/multiplication_division_implementation_tickets.json +55 -0
- package/app/backend/multiplication_division_tickets.json +42 -0
- package/app/backend/pyproject.toml +45 -0
- package/app/backend/requirements-dev.txt +8 -0
- package/app/backend/requirements.txt +20 -0
- package/app/backend/run.sh +30 -0
- package/app/backend/run_with_logs.sh +10 -0
- package/app/backend/scientific_calculator_tickets.json +40 -0
- package/app/backend/scripts/extract_openapi.py +21 -0
- package/app/backend/scripts/seed_demo.py +187 -0
- package/app/backend/setup_demo_review.py +302 -0
- package/app/backend/test_actual_parse.py +41 -0
- package/app/backend/test_agent_streaming.py +61 -0
- package/app/backend/test_parse.py +51 -0
- package/app/backend/test_streaming.py +51 -0
- package/app/backend/test_subprocess_streaming.py +50 -0
- package/app/backend/tests/__init__.py +1 -0
- package/app/backend/tests/conftest.py +46 -0
- package/app/backend/tests/test_auth.py +341 -0
- package/app/backend/tests/test_autonomy_service.py +391 -0
- package/app/backend/tests/test_cleanup_service_safety.py +417 -0
- package/app/backend/tests/test_middleware.py +279 -0
- package/app/backend/tests/test_planner_providers.py +290 -0
- package/app/backend/tests/test_planner_unblock.py +183 -0
- package/app/backend/tests/test_revision_invariants.py +618 -0
- package/app/backend/tests/test_sqlite_kv.py +290 -0
- package/app/backend/tests/test_sqlite_worker.py +353 -0
- package/app/backend/tests/test_task_dispatch.py +100 -0
- package/app/backend/tests/test_ticket_validation.py +304 -0
- package/app/backend/tests/test_udar_agent.py +693 -0
- package/app/backend/tests/test_webhook_service.py +184 -0
- package/app/backend/tickets_output.json +59 -0
- package/app/backend/user_management_tickets.json +50 -0
- package/app/backend/uvicorn.log +0 -0
- package/app/draft.yaml +313 -0
- package/app/frontend/dist/assets/index-LcjCczu5.js +155 -0
- package/app/frontend/dist/assets/index-_FP_279e.css +1 -0
- package/app/frontend/dist/index.html +14 -0
- package/app/frontend/dist/vite.svg +1 -0
- package/app/frontend/package.json +101 -0
- package/bin/cli.js +527 -0
- package/package.json +37 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"""Claude Code CLI executor adapter."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from collections.abc import AsyncIterator
|
|
7
|
+
|
|
8
|
+
from app.executors.registry import ExecutorRegistry
|
|
9
|
+
from app.executors.spec import (
|
|
10
|
+
ExecutionRequest,
|
|
11
|
+
ExecutionResult,
|
|
12
|
+
ExecutorAdapter,
|
|
13
|
+
ExecutorCapability,
|
|
14
|
+
ExecutorInvocationError,
|
|
15
|
+
ExecutorMetadata,
|
|
16
|
+
ExecutorNotFoundError,
|
|
17
|
+
ExecutorTimeoutError,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@ExecutorRegistry.register("claude")
|
|
22
|
+
class ClaudeAdapter(ExecutorAdapter):
|
|
23
|
+
"""Built-in Claude Code CLI adapter."""
|
|
24
|
+
|
|
25
|
+
def get_metadata(self) -> ExecutorMetadata:
|
|
26
|
+
return ExecutorMetadata(
|
|
27
|
+
name="claude",
|
|
28
|
+
display_name="Claude Code",
|
|
29
|
+
version="1.0.0",
|
|
30
|
+
capabilities=[
|
|
31
|
+
ExecutorCapability.STREAMING_OUTPUT,
|
|
32
|
+
ExecutorCapability.YOLO_MODE,
|
|
33
|
+
ExecutorCapability.MCP_SERVERS,
|
|
34
|
+
ExecutorCapability.COST_TRACKING,
|
|
35
|
+
],
|
|
36
|
+
config_schema={
|
|
37
|
+
"type": "object",
|
|
38
|
+
"properties": {
|
|
39
|
+
"model": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"default": "claude-sonnet-4-5",
|
|
42
|
+
"description": "Claude model to use",
|
|
43
|
+
},
|
|
44
|
+
"mcp_config": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Path to MCP config file",
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
documentation_url="https://docs.anthropic.com/claude-code",
|
|
51
|
+
author="Anthropic",
|
|
52
|
+
license="Proprietary",
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
async def is_available(self) -> bool:
|
|
56
|
+
"""Check if claude CLI is installed."""
|
|
57
|
+
return shutil.which("claude") is not None
|
|
58
|
+
|
|
59
|
+
async def check_availability(self) -> dict:
|
|
60
|
+
"""Return detailed availability diagnostics."""
|
|
61
|
+
cli_path = shutil.which("claude")
|
|
62
|
+
issues = []
|
|
63
|
+
version = None
|
|
64
|
+
|
|
65
|
+
if not cli_path:
|
|
66
|
+
issues.append("Claude Code CLI not found in PATH")
|
|
67
|
+
else:
|
|
68
|
+
try:
|
|
69
|
+
proc = await asyncio.create_subprocess_exec(
|
|
70
|
+
"claude",
|
|
71
|
+
"--version",
|
|
72
|
+
stdout=asyncio.subprocess.PIPE,
|
|
73
|
+
stderr=asyncio.subprocess.PIPE,
|
|
74
|
+
)
|
|
75
|
+
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=5)
|
|
76
|
+
version = stdout.decode().strip()
|
|
77
|
+
except Exception:
|
|
78
|
+
issues.append("Could not detect Claude Code version")
|
|
79
|
+
|
|
80
|
+
return {
|
|
81
|
+
"available": cli_path is not None,
|
|
82
|
+
"cli_found": cli_path is not None,
|
|
83
|
+
"cli_path": cli_path,
|
|
84
|
+
"version": version,
|
|
85
|
+
"issues": issues,
|
|
86
|
+
"setup_instructions": self.get_setup_instructions(),
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
def get_setup_instructions(self) -> str:
|
|
90
|
+
return (
|
|
91
|
+
"## Install Claude Code\n\n"
|
|
92
|
+
"```bash\n"
|
|
93
|
+
"npm install -g @anthropic-ai/claude-code\n"
|
|
94
|
+
"```\n\n"
|
|
95
|
+
"Then authenticate:\n"
|
|
96
|
+
"```bash\n"
|
|
97
|
+
"claude auth login\n"
|
|
98
|
+
"```\n\n"
|
|
99
|
+
"Docs: https://docs.anthropic.com/claude-code"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
async def execute(self, request: ExecutionRequest) -> ExecutionResult:
|
|
103
|
+
"""Execute using Claude Code CLI."""
|
|
104
|
+
if not await self.is_available():
|
|
105
|
+
raise ExecutorNotFoundError(
|
|
106
|
+
"Claude Code CLI not found. Install: npm install -g @anthropic-ai/claude-code"
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Build command
|
|
110
|
+
cmd = ["claude", "--print"]
|
|
111
|
+
|
|
112
|
+
if request.yolo_mode:
|
|
113
|
+
cmd.append("--dangerously-skip-permissions")
|
|
114
|
+
|
|
115
|
+
# Add MCP servers if configured
|
|
116
|
+
if request.mcp_servers:
|
|
117
|
+
for server in request.mcp_servers:
|
|
118
|
+
cmd.extend(["--mcp-server", server["name"]])
|
|
119
|
+
|
|
120
|
+
# Add the prompt
|
|
121
|
+
cmd.append(request.prompt)
|
|
122
|
+
|
|
123
|
+
# Execute
|
|
124
|
+
try:
|
|
125
|
+
process = await asyncio.create_subprocess_exec(
|
|
126
|
+
*cmd,
|
|
127
|
+
cwd=request.working_directory,
|
|
128
|
+
stdout=asyncio.subprocess.PIPE,
|
|
129
|
+
stderr=asyncio.subprocess.PIPE,
|
|
130
|
+
env={**os.environ, **request.environment},
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
stdout, stderr = await asyncio.wait_for(
|
|
134
|
+
process.communicate(), timeout=request.timeout_seconds
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return ExecutionResult(
|
|
138
|
+
exit_code=process.returncode,
|
|
139
|
+
stdout=stdout.decode("utf-8", errors="replace"),
|
|
140
|
+
stderr=stderr.decode("utf-8", errors="replace"),
|
|
141
|
+
files_changed=[], # TODO: Parse from output
|
|
142
|
+
duration_seconds=0.0, # TODO: Track duration
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
except TimeoutError:
|
|
146
|
+
process.kill()
|
|
147
|
+
raise ExecutorTimeoutError(
|
|
148
|
+
f"Claude execution timed out after {request.timeout_seconds}s"
|
|
149
|
+
)
|
|
150
|
+
except Exception as e:
|
|
151
|
+
raise ExecutorInvocationError(f"Claude execution failed: {str(e)}")
|
|
152
|
+
|
|
153
|
+
async def stream_output(self, request: ExecutionRequest) -> AsyncIterator[str]:
|
|
154
|
+
"""Stream output in real-time."""
|
|
155
|
+
if not await self.is_available():
|
|
156
|
+
raise ExecutorNotFoundError("Claude Code CLI not found")
|
|
157
|
+
|
|
158
|
+
cmd = ["claude", "--print"]
|
|
159
|
+
if request.yolo_mode:
|
|
160
|
+
cmd.append("--dangerously-skip-permissions")
|
|
161
|
+
cmd.append(request.prompt)
|
|
162
|
+
|
|
163
|
+
process = await asyncio.create_subprocess_exec(
|
|
164
|
+
*cmd,
|
|
165
|
+
cwd=request.working_directory,
|
|
166
|
+
stdout=asyncio.subprocess.PIPE,
|
|
167
|
+
stderr=asyncio.subprocess.STDOUT,
|
|
168
|
+
env={**os.environ, **request.environment},
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
while True:
|
|
172
|
+
line = await process.stdout.readline()
|
|
173
|
+
if not line:
|
|
174
|
+
break
|
|
175
|
+
yield line.decode("utf-8", errors="replace")
|
|
176
|
+
|
|
177
|
+
await process.wait()
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Cline AI assistant adapter."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from collections.abc import AsyncIterator
|
|
7
|
+
|
|
8
|
+
from app.executors.registry import ExecutorRegistry
|
|
9
|
+
from app.executors.spec import (
|
|
10
|
+
ExecutionRequest,
|
|
11
|
+
ExecutionResult,
|
|
12
|
+
ExecutorAdapter,
|
|
13
|
+
ExecutorCapability,
|
|
14
|
+
ExecutorInvocationError,
|
|
15
|
+
ExecutorMetadata,
|
|
16
|
+
ExecutorNotFoundError,
|
|
17
|
+
ExecutorTimeoutError,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@ExecutorRegistry.register("cline")
|
|
22
|
+
class ClineAdapter(ExecutorAdapter):
|
|
23
|
+
"""Cline AI assistant adapter (VS Code extension CLI)."""
|
|
24
|
+
|
|
25
|
+
def get_metadata(self) -> ExecutorMetadata:
|
|
26
|
+
return ExecutorMetadata(
|
|
27
|
+
name="cline",
|
|
28
|
+
display_name="Cline",
|
|
29
|
+
version="1.0.0",
|
|
30
|
+
capabilities=[
|
|
31
|
+
ExecutorCapability.STREAMING_OUTPUT,
|
|
32
|
+
],
|
|
33
|
+
config_schema={
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"model": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"default": "claude-3-5-sonnet-20241022",
|
|
39
|
+
"description": "LLM model to use",
|
|
40
|
+
},
|
|
41
|
+
"api_provider": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"enum": ["anthropic", "openai", "bedrock"],
|
|
44
|
+
"default": "anthropic",
|
|
45
|
+
"description": "API provider",
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
documentation_url="https://github.com/cline/cline",
|
|
50
|
+
author="Cline",
|
|
51
|
+
license="Apache-2.0",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
async def is_available(self) -> bool:
|
|
55
|
+
"""Check if cline CLI is installed."""
|
|
56
|
+
return shutil.which("cline") is not None
|
|
57
|
+
|
|
58
|
+
async def execute(self, request: ExecutionRequest) -> ExecutionResult:
|
|
59
|
+
"""Execute using Cline."""
|
|
60
|
+
if not await self.is_available():
|
|
61
|
+
raise ExecutorNotFoundError(
|
|
62
|
+
"Cline not found. Install the Cline VS Code extension with CLI support."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Build command
|
|
66
|
+
cmd = ["cline", "execute"]
|
|
67
|
+
|
|
68
|
+
# Add model and provider
|
|
69
|
+
model = request.config.get("model", "claude-3-5-sonnet-20241022")
|
|
70
|
+
provider = request.config.get("api_provider", "anthropic")
|
|
71
|
+
|
|
72
|
+
cmd.extend(["--model", model])
|
|
73
|
+
cmd.extend(["--provider", provider])
|
|
74
|
+
|
|
75
|
+
# Add the prompt
|
|
76
|
+
cmd.extend(["--prompt", request.prompt])
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
process = await asyncio.create_subprocess_exec(
|
|
80
|
+
*cmd,
|
|
81
|
+
cwd=request.working_directory,
|
|
82
|
+
stdout=asyncio.subprocess.PIPE,
|
|
83
|
+
stderr=asyncio.subprocess.PIPE,
|
|
84
|
+
env={**os.environ, **request.environment},
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
stdout, stderr = await asyncio.wait_for(
|
|
88
|
+
process.communicate(), timeout=request.timeout_seconds
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return ExecutionResult(
|
|
92
|
+
exit_code=process.returncode,
|
|
93
|
+
stdout=stdout.decode("utf-8", errors="replace"),
|
|
94
|
+
stderr=stderr.decode("utf-8", errors="replace"),
|
|
95
|
+
duration_seconds=0.0,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
except TimeoutError:
|
|
99
|
+
process.kill()
|
|
100
|
+
raise ExecutorTimeoutError(
|
|
101
|
+
f"Cline execution timed out after {request.timeout_seconds}s"
|
|
102
|
+
)
|
|
103
|
+
except Exception as e:
|
|
104
|
+
raise ExecutorInvocationError(f"Cline execution failed: {str(e)}")
|
|
105
|
+
|
|
106
|
+
async def stream_output(self, request: ExecutionRequest) -> AsyncIterator[str]:
|
|
107
|
+
"""Stream output in real-time."""
|
|
108
|
+
if not await self.is_available():
|
|
109
|
+
raise ExecutorNotFoundError("Cline not found")
|
|
110
|
+
|
|
111
|
+
cmd = ["cline", "execute", "--prompt", request.prompt]
|
|
112
|
+
|
|
113
|
+
process = await asyncio.create_subprocess_exec(
|
|
114
|
+
*cmd,
|
|
115
|
+
cwd=request.working_directory,
|
|
116
|
+
stdout=asyncio.subprocess.PIPE,
|
|
117
|
+
stderr=asyncio.subprocess.STDOUT,
|
|
118
|
+
env={**os.environ, **request.environment},
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
while True:
|
|
122
|
+
line = await process.stdout.readline()
|
|
123
|
+
if not line:
|
|
124
|
+
break
|
|
125
|
+
yield line.decode("utf-8", errors="replace")
|
|
126
|
+
|
|
127
|
+
await process.wait()
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""OpenAI Codex CLI adapter."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from collections.abc import AsyncIterator
|
|
7
|
+
|
|
8
|
+
from app.executors.registry import ExecutorRegistry
|
|
9
|
+
from app.executors.spec import (
|
|
10
|
+
ExecutionRequest,
|
|
11
|
+
ExecutionResult,
|
|
12
|
+
ExecutorAdapter,
|
|
13
|
+
ExecutorCapability,
|
|
14
|
+
ExecutorInvocationError,
|
|
15
|
+
ExecutorMetadata,
|
|
16
|
+
ExecutorNotFoundError,
|
|
17
|
+
ExecutorTimeoutError,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@ExecutorRegistry.register("codex")
|
|
22
|
+
class CodexAdapter(ExecutorAdapter):
|
|
23
|
+
"""OpenAI Codex CLI adapter."""
|
|
24
|
+
|
|
25
|
+
def get_metadata(self) -> ExecutorMetadata:
|
|
26
|
+
return ExecutorMetadata(
|
|
27
|
+
name="codex",
|
|
28
|
+
display_name="OpenAI Codex CLI",
|
|
29
|
+
version="1.0.0",
|
|
30
|
+
capabilities=[
|
|
31
|
+
ExecutorCapability.STREAMING_OUTPUT,
|
|
32
|
+
ExecutorCapability.YOLO_MODE,
|
|
33
|
+
],
|
|
34
|
+
config_schema={
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"model": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"default": "o3",
|
|
40
|
+
"description": "OpenAI model used by Codex",
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
documentation_url="https://github.com/openai/codex",
|
|
45
|
+
author="OpenAI",
|
|
46
|
+
license="Apache-2.0",
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
async def is_available(self) -> bool:
|
|
50
|
+
"""Check if codex CLI is installed."""
|
|
51
|
+
return shutil.which("codex") is not None
|
|
52
|
+
|
|
53
|
+
async def check_availability(self) -> dict:
|
|
54
|
+
"""Return detailed availability diagnostics."""
|
|
55
|
+
cli_path = shutil.which("codex")
|
|
56
|
+
issues = []
|
|
57
|
+
version = None
|
|
58
|
+
|
|
59
|
+
if not cli_path:
|
|
60
|
+
issues.append("Codex CLI not found in PATH")
|
|
61
|
+
else:
|
|
62
|
+
try:
|
|
63
|
+
proc = await asyncio.create_subprocess_exec(
|
|
64
|
+
"codex",
|
|
65
|
+
"--version",
|
|
66
|
+
stdout=asyncio.subprocess.PIPE,
|
|
67
|
+
stderr=asyncio.subprocess.PIPE,
|
|
68
|
+
)
|
|
69
|
+
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=5)
|
|
70
|
+
version = stdout.decode().strip()
|
|
71
|
+
except Exception:
|
|
72
|
+
issues.append("Could not detect Codex CLI version")
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
"available": cli_path is not None,
|
|
76
|
+
"cli_found": cli_path is not None,
|
|
77
|
+
"cli_path": cli_path,
|
|
78
|
+
"version": version,
|
|
79
|
+
"issues": issues,
|
|
80
|
+
"setup_instructions": self.get_setup_instructions(),
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
def get_setup_instructions(self) -> str:
|
|
84
|
+
return (
|
|
85
|
+
"## Install OpenAI Codex CLI\n\n"
|
|
86
|
+
"```bash\n"
|
|
87
|
+
"npm install -g @openai/codex\n"
|
|
88
|
+
"```\n\n"
|
|
89
|
+
"Then authenticate:\n"
|
|
90
|
+
"```bash\n"
|
|
91
|
+
"export OPENAI_API_KEY=your-key\n"
|
|
92
|
+
"```\n\n"
|
|
93
|
+
"Docs: https://github.com/openai/codex"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
async def execute(self, request: ExecutionRequest) -> ExecutionResult:
|
|
97
|
+
"""Execute using Codex CLI."""
|
|
98
|
+
if not await self.is_available():
|
|
99
|
+
raise ExecutorNotFoundError(
|
|
100
|
+
"Codex CLI not found. Install from https://github.com/openai/codex"
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
cmd = ["codex", "--print", "--auto-edit"]
|
|
104
|
+
|
|
105
|
+
if request.yolo_mode:
|
|
106
|
+
cmd.append("--full-auto")
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
process = await asyncio.create_subprocess_exec(
|
|
110
|
+
*cmd,
|
|
111
|
+
cwd=request.working_directory,
|
|
112
|
+
stdin=asyncio.subprocess.PIPE,
|
|
113
|
+
stdout=asyncio.subprocess.PIPE,
|
|
114
|
+
stderr=asyncio.subprocess.PIPE,
|
|
115
|
+
env={**os.environ, **request.environment},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
stdout, stderr = await asyncio.wait_for(
|
|
119
|
+
process.communicate(input=request.prompt.encode("utf-8")),
|
|
120
|
+
timeout=request.timeout_seconds,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
return ExecutionResult(
|
|
124
|
+
exit_code=process.returncode,
|
|
125
|
+
stdout=stdout.decode("utf-8", errors="replace"),
|
|
126
|
+
stderr=stderr.decode("utf-8", errors="replace"),
|
|
127
|
+
duration_seconds=0.0,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
except TimeoutError:
|
|
131
|
+
process.kill()
|
|
132
|
+
raise ExecutorTimeoutError(
|
|
133
|
+
f"Codex execution timed out after {request.timeout_seconds}s"
|
|
134
|
+
) from None
|
|
135
|
+
except Exception as e:
|
|
136
|
+
raise ExecutorInvocationError(f"Codex execution failed: {e!s}") from e
|
|
137
|
+
|
|
138
|
+
async def stream_output(self, request: ExecutionRequest) -> AsyncIterator[str]:
|
|
139
|
+
"""Stream output in real-time."""
|
|
140
|
+
if not await self.is_available():
|
|
141
|
+
raise ExecutorNotFoundError("Codex CLI not found")
|
|
142
|
+
|
|
143
|
+
cmd = ["codex", "--print", "--auto-edit"]
|
|
144
|
+
if request.yolo_mode:
|
|
145
|
+
cmd.append("--full-auto")
|
|
146
|
+
|
|
147
|
+
process = await asyncio.create_subprocess_exec(
|
|
148
|
+
*cmd,
|
|
149
|
+
cwd=request.working_directory,
|
|
150
|
+
stdin=asyncio.subprocess.PIPE,
|
|
151
|
+
stdout=asyncio.subprocess.PIPE,
|
|
152
|
+
stderr=asyncio.subprocess.STDOUT,
|
|
153
|
+
env={**os.environ, **request.environment},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Send prompt via stdin then close
|
|
157
|
+
process.stdin.write(request.prompt.encode("utf-8"))
|
|
158
|
+
await process.stdin.drain()
|
|
159
|
+
process.stdin.close()
|
|
160
|
+
|
|
161
|
+
while True:
|
|
162
|
+
line = await process.stdout.readline()
|
|
163
|
+
if not line:
|
|
164
|
+
break
|
|
165
|
+
yield line.decode("utf-8", errors="replace")
|
|
166
|
+
|
|
167
|
+
await process.wait()
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
"""GitHub Copilot CLI adapter."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
import shutil
|
|
6
|
+
from collections.abc import AsyncIterator
|
|
7
|
+
|
|
8
|
+
from app.executors.registry import ExecutorRegistry
|
|
9
|
+
from app.executors.spec import (
|
|
10
|
+
ExecutionRequest,
|
|
11
|
+
ExecutionResult,
|
|
12
|
+
ExecutorAdapter,
|
|
13
|
+
ExecutorCapability,
|
|
14
|
+
ExecutorInvocationError,
|
|
15
|
+
ExecutorMetadata,
|
|
16
|
+
ExecutorNotFoundError,
|
|
17
|
+
ExecutorTimeoutError,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@ExecutorRegistry.register("copilot")
|
|
22
|
+
class CopilotAdapter(ExecutorAdapter):
|
|
23
|
+
"""GitHub Copilot CLI adapter."""
|
|
24
|
+
|
|
25
|
+
def get_metadata(self) -> ExecutorMetadata:
|
|
26
|
+
return ExecutorMetadata(
|
|
27
|
+
name="copilot",
|
|
28
|
+
display_name="GitHub Copilot CLI",
|
|
29
|
+
version="1.0.0",
|
|
30
|
+
capabilities=[
|
|
31
|
+
ExecutorCapability.STREAMING_OUTPUT,
|
|
32
|
+
],
|
|
33
|
+
config_schema={
|
|
34
|
+
"type": "object",
|
|
35
|
+
"properties": {
|
|
36
|
+
"model": {
|
|
37
|
+
"type": "string",
|
|
38
|
+
"default": "gpt-4",
|
|
39
|
+
"description": "OpenAI model used by Copilot",
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
documentation_url="https://githubnext.com/projects/copilot-cli/",
|
|
44
|
+
author="GitHub",
|
|
45
|
+
license="Proprietary",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
async def is_available(self) -> bool:
|
|
49
|
+
"""Check if copilot CLI is installed."""
|
|
50
|
+
# GitHub Copilot CLI is accessed via `gh copilot` or dedicated `copilot` command
|
|
51
|
+
has_gh = shutil.which("gh") is not None
|
|
52
|
+
has_copilot = shutil.which("copilot") is not None
|
|
53
|
+
|
|
54
|
+
if has_gh:
|
|
55
|
+
# Check if copilot extension is installed
|
|
56
|
+
try:
|
|
57
|
+
process = await asyncio.create_subprocess_exec(
|
|
58
|
+
"gh",
|
|
59
|
+
"extension",
|
|
60
|
+
"list",
|
|
61
|
+
stdout=asyncio.subprocess.PIPE,
|
|
62
|
+
stderr=asyncio.subprocess.PIPE,
|
|
63
|
+
)
|
|
64
|
+
stdout, _ = await process.communicate()
|
|
65
|
+
return b"copilot" in stdout.lower()
|
|
66
|
+
except Exception:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
return has_copilot
|
|
70
|
+
|
|
71
|
+
async def check_availability(self) -> dict:
|
|
72
|
+
"""Return detailed availability diagnostics."""
|
|
73
|
+
has_gh = shutil.which("gh")
|
|
74
|
+
has_copilot_cli = shutil.which("copilot")
|
|
75
|
+
issues = []
|
|
76
|
+
version = None
|
|
77
|
+
copilot_ext_installed = False
|
|
78
|
+
|
|
79
|
+
if not has_gh and not has_copilot_cli:
|
|
80
|
+
issues.append("Neither 'gh' CLI nor 'copilot' CLI found in PATH")
|
|
81
|
+
elif has_gh:
|
|
82
|
+
try:
|
|
83
|
+
proc = await asyncio.create_subprocess_exec(
|
|
84
|
+
"gh",
|
|
85
|
+
"extension",
|
|
86
|
+
"list",
|
|
87
|
+
stdout=asyncio.subprocess.PIPE,
|
|
88
|
+
stderr=asyncio.subprocess.PIPE,
|
|
89
|
+
)
|
|
90
|
+
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=5)
|
|
91
|
+
if b"copilot" in stdout.lower():
|
|
92
|
+
copilot_ext_installed = True
|
|
93
|
+
else:
|
|
94
|
+
issues.append("GitHub Copilot extension not installed in gh CLI")
|
|
95
|
+
except Exception:
|
|
96
|
+
issues.append("Could not check gh extensions")
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
proc = await asyncio.create_subprocess_exec(
|
|
100
|
+
"gh",
|
|
101
|
+
"--version",
|
|
102
|
+
stdout=asyncio.subprocess.PIPE,
|
|
103
|
+
stderr=asyncio.subprocess.PIPE,
|
|
104
|
+
)
|
|
105
|
+
stdout, _ = await asyncio.wait_for(proc.communicate(), timeout=5)
|
|
106
|
+
version = stdout.decode().strip().split("\n")[0]
|
|
107
|
+
except Exception:
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
return {
|
|
111
|
+
"available": copilot_ext_installed or has_copilot_cli is not None,
|
|
112
|
+
"cli_found": has_gh is not None or has_copilot_cli is not None,
|
|
113
|
+
"cli_path": has_gh or has_copilot_cli,
|
|
114
|
+
"version": version,
|
|
115
|
+
"copilot_extension_installed": copilot_ext_installed,
|
|
116
|
+
"issues": issues,
|
|
117
|
+
"setup_instructions": self.get_setup_instructions(),
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
def get_setup_instructions(self) -> str:
|
|
121
|
+
return (
|
|
122
|
+
"## Install GitHub Copilot CLI\n\n"
|
|
123
|
+
"1. Install the GitHub CLI:\n"
|
|
124
|
+
"```bash\n"
|
|
125
|
+
"brew install gh # macOS\n"
|
|
126
|
+
"```\n\n"
|
|
127
|
+
"2. Install the Copilot extension:\n"
|
|
128
|
+
"```bash\n"
|
|
129
|
+
"gh extension install github/gh-copilot\n"
|
|
130
|
+
"```\n\n"
|
|
131
|
+
"Docs: https://githubnext.com/projects/copilot-cli/"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
async def execute(self, request: ExecutionRequest) -> ExecutionResult:
|
|
135
|
+
"""Execute using GitHub Copilot CLI."""
|
|
136
|
+
if not await self.is_available():
|
|
137
|
+
raise ExecutorNotFoundError(
|
|
138
|
+
"GitHub Copilot CLI not found. Install: gh extension install github/gh-copilot"
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Determine command structure
|
|
142
|
+
if shutil.which("gh"):
|
|
143
|
+
cmd = ["gh", "copilot", "suggest"]
|
|
144
|
+
else:
|
|
145
|
+
cmd = ["copilot", "suggest"]
|
|
146
|
+
|
|
147
|
+
# Add the prompt
|
|
148
|
+
cmd.append(request.prompt)
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
process = await asyncio.create_subprocess_exec(
|
|
152
|
+
*cmd,
|
|
153
|
+
cwd=request.working_directory,
|
|
154
|
+
stdout=asyncio.subprocess.PIPE,
|
|
155
|
+
stderr=asyncio.subprocess.PIPE,
|
|
156
|
+
env={**os.environ, **request.environment},
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
stdout, stderr = await asyncio.wait_for(
|
|
160
|
+
process.communicate(), timeout=request.timeout_seconds
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
return ExecutionResult(
|
|
164
|
+
exit_code=process.returncode,
|
|
165
|
+
stdout=stdout.decode("utf-8", errors="replace"),
|
|
166
|
+
stderr=stderr.decode("utf-8", errors="replace"),
|
|
167
|
+
duration_seconds=0.0,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
except TimeoutError:
|
|
171
|
+
process.kill()
|
|
172
|
+
raise ExecutorTimeoutError(
|
|
173
|
+
f"Copilot execution timed out after {request.timeout_seconds}s"
|
|
174
|
+
) from None
|
|
175
|
+
except Exception as e:
|
|
176
|
+
raise ExecutorInvocationError(f"Copilot execution failed: {str(e)}") from e
|
|
177
|
+
|
|
178
|
+
async def stream_output(self, request: ExecutionRequest) -> AsyncIterator[str]:
|
|
179
|
+
"""Stream output in real-time."""
|
|
180
|
+
if not await self.is_available():
|
|
181
|
+
raise ExecutorNotFoundError("GitHub Copilot CLI not found")
|
|
182
|
+
|
|
183
|
+
if shutil.which("gh"):
|
|
184
|
+
cmd = ["gh", "copilot", "suggest", request.prompt]
|
|
185
|
+
else:
|
|
186
|
+
cmd = ["copilot", "suggest", request.prompt]
|
|
187
|
+
|
|
188
|
+
process = await asyncio.create_subprocess_exec(
|
|
189
|
+
*cmd,
|
|
190
|
+
cwd=request.working_directory,
|
|
191
|
+
stdout=asyncio.subprocess.PIPE,
|
|
192
|
+
stderr=asyncio.subprocess.STDOUT,
|
|
193
|
+
env={**os.environ, **request.environment},
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
while True:
|
|
197
|
+
line = await process.stdout.readline()
|
|
198
|
+
if not line:
|
|
199
|
+
break
|
|
200
|
+
yield line.decode("utf-8", errors="replace")
|
|
201
|
+
|
|
202
|
+
await process.wait()
|