devduck 0.2.0__tar.gz → 0.3.0__tar.gz

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.

Potentially problematic release.


This version of devduck might be problematic. Click here for more details.

Files changed (42) hide show
  1. devduck-0.3.0/.github/workflows/agent.yml +163 -0
  2. {devduck-0.2.0 → devduck-0.3.0}/.gitignore +3 -1
  3. {devduck-0.2.0/devduck.egg-info → devduck-0.3.0}/PKG-INFO +17 -8
  4. {devduck-0.2.0 → devduck-0.3.0}/README.md +5 -1
  5. devduck-0.3.0/action.yml +212 -0
  6. devduck-0.3.0/agent_runner.py +206 -0
  7. {devduck-0.2.0 → devduck-0.3.0}/devduck/__init__.py +273 -99
  8. {devduck-0.2.0 → devduck-0.3.0}/devduck/_version.py +3 -3
  9. devduck-0.3.0/devduck/tools/create_subagent.py +659 -0
  10. devduck-0.3.0/devduck/tools/store_in_kb.py +187 -0
  11. {devduck-0.2.0 → devduck-0.3.0}/devduck/tools/tcp.py +0 -3
  12. devduck-0.3.0/devduck/tools/use_github.py +438 -0
  13. {devduck-0.2.0 → devduck-0.3.0}/devduck/tools/websocket.py +1 -1
  14. {devduck-0.2.0 → devduck-0.3.0/devduck.egg-info}/PKG-INFO +17 -8
  15. {devduck-0.2.0 → devduck-0.3.0}/devduck.egg-info/SOURCES.txt +17 -2
  16. {devduck-0.2.0 → devduck-0.3.0}/devduck.egg-info/requires.txt +9 -3
  17. devduck-0.3.0/mcp.json +10 -0
  18. {devduck-0.2.0 → devduck-0.3.0}/pyproject.toml +11 -3
  19. devduck-0.3.0/requirements.txt +4 -0
  20. devduck-0.3.0/setup-aws-oidc.sh +145 -0
  21. devduck-0.3.0/tools/__init__.py +0 -0
  22. devduck-0.3.0/tools/fetch_github_tool.py +201 -0
  23. devduck-0.3.0/tools/gist.py +708 -0
  24. devduck-0.3.0/tools/github_tools.py +134 -0
  25. devduck-0.3.0/tools/scraper.py +935 -0
  26. devduck-0.3.0/tools/store_in_kb.py +187 -0
  27. devduck-0.3.0/tools/system_prompt.py +485 -0
  28. devduck-0.2.0/devduck/install.sh +0 -42
  29. {devduck-0.2.0 → devduck-0.3.0}/LICENSE +0 -0
  30. {devduck-0.2.0 → devduck-0.3.0}/MANIFEST.in +0 -0
  31. {devduck-0.2.0 → devduck-0.3.0}/devduck/__main__.py +0 -0
  32. {devduck-0.2.0 → devduck-0.3.0}/devduck/test_redduck.py +0 -0
  33. {devduck-0.2.0 → devduck-0.3.0}/devduck/tools/__init__.py +0 -0
  34. {devduck-0.2.0 → devduck-0.3.0}/devduck/tools/install_tools.py +0 -0
  35. {devduck-0.2.0 → devduck-0.3.0}/devduck/tools/mcp_server.py +0 -0
  36. {devduck-0.2.0 → devduck-0.3.0}/devduck.egg-info/dependency_links.txt +0 -0
  37. {devduck-0.2.0 → devduck-0.3.0}/devduck.egg-info/entry_points.txt +0 -0
  38. {devduck-0.2.0 → devduck-0.3.0}/devduck.egg-info/top_level.txt +0 -0
  39. {devduck-0.2.0 → devduck-0.3.0}/docs/index.html +0 -0
  40. {devduck-0.2.0 → devduck-0.3.0}/install.sh +0 -0
  41. {devduck-0.2.0 → devduck-0.3.0}/setup.cfg +0 -0
  42. {devduck-0.2.0 → devduck-0.3.0}/test.py +0 -0
@@ -0,0 +1,163 @@
1
+ name: DevDuck
2
+
3
+ on:
4
+ schedule:
5
+ - cron: '0 8 * * *'
6
+ issues:
7
+ types: [opened, edited, closed, reopened, assigned, unassigned, labeled, unlabeled]
8
+ issue_comment:
9
+ types: [created, edited, deleted]
10
+ pull_request:
11
+ types: [opened, closed, edited, reopened, synchronize, ready_for_review]
12
+ pull_request_review:
13
+ types: [submitted, edited]
14
+ discussion:
15
+ types: [created, edited, answered, unanswered, category_changed, labeled, unlabeled, transferred, pinned, unpinned, locked, unlocked]
16
+ discussion_comment:
17
+ types: [created, edited, deleted]
18
+ pull_request_review_comment:
19
+ types: [created, edited]
20
+ workflow_dispatch:
21
+ inputs:
22
+ task:
23
+ description: 'Task'
24
+ required: false
25
+ type: string
26
+ system_prompt:
27
+ description: 'System prompt'
28
+ required: false
29
+ type: string
30
+ tools:
31
+ description: 'Comma-separated list of tools to enable for the agent'
32
+ required: false
33
+ type: string
34
+ provider:
35
+ description: 'Provider'
36
+ default: "bedrock"
37
+ required: false
38
+ type: choice
39
+ options:
40
+ - bedrock
41
+ - openai
42
+ - github
43
+ - anthropic
44
+ - litellm
45
+ - llamaapi
46
+ model:
47
+ description: 'Model ID'
48
+ default: "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
49
+ required: false
50
+ type: string
51
+ max_tokens:
52
+ description: 'Max tokens'
53
+ default: "60000"
54
+ required: false
55
+ type: string
56
+ temperature:
57
+ description: 'Temperature (0.0-1.0)'
58
+ default: "1"
59
+ required: false
60
+ type: string
61
+ mcp_servers:
62
+ description: 'MCP servers configuration (JSON string)'
63
+ required: false
64
+ type: string
65
+ default: '{"mcpServers":{"strands-docs":{"command":"uvx","args":["strands-agents-mcp-server"],"timeout":30000}}}'
66
+ additional_request_fields:
67
+ description: 'Additional request fields JSON (e.g., thinking, anthropic_beta)'
68
+ required: false
69
+ type: string
70
+
71
+ permissions: write-all
72
+
73
+ jobs:
74
+ check-permissions:
75
+ runs-on: ubuntu-latest
76
+ outputs:
77
+ authorized: ${{ steps.check.outputs.authorized }}
78
+ steps:
79
+ - name: Check user authorization
80
+ id: check
81
+ run: |
82
+ # 🔐 CONFIGURE AUTHORIZED USERS HERE
83
+ # Add your authorized usernames (comma-separated)
84
+ AUTHORIZED_USERS="${{ secrets.AUTHORIZED_USERS }}"
85
+
86
+ echo "Checking authorization for user: ${{ github.actor }}"
87
+
88
+ # Check if the triggering user is in the authorized list
89
+ if [[ ",$AUTHORIZED_USERS," == *",${{ github.actor }},"* ]]; then
90
+ echo "✅ User ${{ github.actor }} is authorized"
91
+ echo "authorized=true" >> $GITHUB_OUTPUT
92
+ else
93
+ echo "❌ User ${{ github.actor }} is NOT authorized"
94
+ echo "Authorized users: $AUTHORIZED_USERS"
95
+ echo "authorized=false" >> $GITHUB_OUTPUT
96
+ fi
97
+
98
+ agent:
99
+ needs: check-permissions
100
+ runs-on: ubuntu-latest
101
+ # Only run if user is authorized
102
+ if: needs.check-permissions.outputs.authorized == 'true'
103
+ steps:
104
+ - name: Checkout code
105
+ uses: actions/checkout@v4
106
+ with:
107
+ token: ${{ secrets.PAT_TOKEN }}
108
+
109
+ - name: Run Strands Agent
110
+ uses: ./
111
+ env:
112
+ PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
113
+ MODEL_PROVIDER: "bedrock"
114
+ with:
115
+ task: |
116
+ ${{
117
+ github.event.inputs.task ||
118
+ 'I received a GitHub event and am running autonomously. I will analyze the context and take appropriate action. I have full GitHub event details available.'
119
+ }}
120
+
121
+ # Model configuration
122
+ provider: ${{ github.event.inputs.provider || vars.STRANDS_PROVIDER || 'bedrock' }}
123
+ model: ${{ github.event.inputs.model || vars.STRANDS_MODEL_ID || 'us.anthropic.claude-sonnet-4-5-20250929-v1:0' }}
124
+ max_tokens: ${{ github.event.inputs.max_tokens || vars.STRANDS_MAX_TOKENS || '60000' }}
125
+ temperature: ${{ github.event.inputs.temperature || vars.STRANDS_TEMPERATURE || '1' }}
126
+
127
+ # System prompt configuration
128
+ system_prompt: ${{ github.event.inputs.system_prompt || vars.SYSTEM_PROMPT || vars.INPUT_SYSTEM_PROMPT || 'You are a restricted GitHub agent for this repository, powered by Strands Agents SDK. Only authorized users can trigger your execution.' }}
129
+
130
+ # Tool configuration
131
+ tools: ${{ github.event.inputs.tools || vars.STRANDS_TOOLS}}
132
+
133
+ # AWS configuration
134
+ aws_role_arn: ${{ secrets.AWS_ROLE_ARN }}
135
+ aws_region: ${{ secrets.AWS_REGION || 'us-west-2' }}
136
+
137
+ # Knowledge base
138
+ knowledge_base_id: ${{ secrets.STRANDS_KNOWLEDGE_BASE_ID }}
139
+
140
+ # Advanced configuration
141
+ additional_request_fields: ${{ github.event.inputs.additional_request_fields || vars.STRANDS_ADDITIONAL_REQUEST_FIELDS }}
142
+ bypass_tool_consent: "true"
143
+
144
+ # MCP configuration
145
+ mcp_servers: ${{ github.event.inputs.mcp_servers || vars.MCP_SERVERS }}
146
+
147
+ unauthorized-notice:
148
+ needs: check-permissions
149
+ runs-on: ubuntu-latest
150
+ # Only run if user is NOT authorized
151
+ if: needs.check-permissions.outputs.authorized == 'false'
152
+ steps:
153
+ - name: Unauthorized access attempt
154
+ run: |
155
+ echo "🚫 UNAUTHORIZED ACCESS ATTEMPT"
156
+ echo "User: ${{ github.actor }}"
157
+ echo "Repository: ${{ github.repository }}"
158
+ echo "Event: ${{ github.event_name }}"
159
+ echo "Time: $(date)"
160
+ echo ""
161
+ echo "This incident has been logged."
162
+ echo "Contact repository administrators for access."
163
+ exit 1
@@ -3,4 +3,6 @@
3
3
  *.egg-info
4
4
  __pycache__
5
5
  *.bak
6
- dist/
6
+ dist/
7
+ build
8
+ _version.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: devduck
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: 🦆 Extreme minimalist self-adapting AI agent - one file, self-healing, runtime dependencies
5
5
  Author-email: duck <hey@devduck.dev>
6
6
  License-Expression: MIT
@@ -27,15 +27,20 @@ Requires-Python: >=3.10
27
27
  Description-Content-Type: text/markdown
28
28
  License-File: LICENSE
29
29
  Requires-Dist: strands-agents
30
+ Requires-Dist: prompt_toolkit
30
31
  Requires-Dist: strands-agents[ollama]
31
- Requires-Dist: strands-agents[openai]
32
- Requires-Dist: strands-agents[anthropic]
33
32
  Requires-Dist: strands-agents-tools
34
- Requires-Dist: strands-fun-tools[audio]
35
- Requires-Dist: strands-fun-tools[vision]
36
- Requires-Dist: strands-fun-tools[all]
33
+ Requires-Dist: strands-agentcore-tools
34
+ Requires-Dist: beautifulsoup4
35
+ Requires-Dist: colorama
37
36
  Requires-Dist: websockets
38
- Requires-Dist: prompt_toolkit
37
+ Provides-Extra: all
38
+ Requires-Dist: strands-agents[openai]; extra == "all"
39
+ Requires-Dist: strands-agents[anthropic]; extra == "all"
40
+ Requires-Dist: strands-fun-tools[audio]; extra == "all"
41
+ Requires-Dist: strands-fun-tools[vision]; extra == "all"
42
+ Requires-Dist: strands-fun-tools[all]; extra == "all"
43
+ Requires-Dist: strands-mcp-server; extra == "all"
39
44
  Dynamic: license-file
40
45
 
41
46
  # 🦆 DevDuck
@@ -47,10 +52,14 @@ Minimalist AI agent that fixes itself when things break.
47
52
  ## Install
48
53
 
49
54
  ```bash
55
+ # Minimal install
50
56
  pipx install devduck
57
+
58
+ # Full install (all tools)
59
+ pipx install "devduck[all]"
51
60
  ```
52
61
 
53
- Requires: Python 3.10+, Ollama running
62
+ Requires: Python 3.10+, Ollama running (or set MODEL_PROVIDER)
54
63
 
55
64
  ## Use
56
65
 
@@ -7,10 +7,14 @@ Minimalist AI agent that fixes itself when things break.
7
7
  ## Install
8
8
 
9
9
  ```bash
10
+ # Minimal install
10
11
  pipx install devduck
12
+
13
+ # Full install (all tools)
14
+ pipx install "devduck[all]"
11
15
  ```
12
16
 
13
- Requires: Python 3.10+, Ollama running
17
+ Requires: Python 3.10+, Ollama running (or set MODEL_PROVIDER)
14
18
 
15
19
  ## Use
16
20
 
@@ -0,0 +1,212 @@
1
+ name: 'DevDuck'
2
+ description: 'Quack.'
3
+ author: 'Cagatay Cali'
4
+
5
+ branding:
6
+ icon: 'bot'
7
+ color: 'blue'
8
+
9
+ inputs:
10
+ # Core Task Configuration
11
+ task:
12
+ description: 'Specific task for the agent to perform'
13
+ required: true
14
+
15
+ system_prompt:
16
+ description: 'Additional system prompt instructions for the agent'
17
+ required: false
18
+ default: ''
19
+
20
+ # Model Configuration
21
+ provider:
22
+ description: 'Model provider (bedrock, openai, github, anthropic, litellm, llamaapi)'
23
+ required: false
24
+ default: 'bedrock'
25
+
26
+ model:
27
+ description: 'Model ID to use'
28
+ required: false
29
+ default: 'us.anthropic.claude-sonnet-4-20250514-v1:0'
30
+
31
+ max_tokens:
32
+ description: 'Maximum tokens for the model'
33
+ required: false
34
+ default: '32768'
35
+
36
+ temperature:
37
+ description: 'Temperature for model generation (0.0-1.0)'
38
+ required: false
39
+ default: '1'
40
+
41
+ # Tool Configuration
42
+ tools:
43
+ description: 'Comma-separated list of tools to enable'
44
+ required: false
45
+ default: 'current_time'
46
+
47
+ # Authentication & Access
48
+ github_token:
49
+ description: 'GitHub token for API access'
50
+ required: false
51
+ default: ${{ github.token }}
52
+
53
+ # AWS Configuration (for Bedrock)
54
+ aws_region:
55
+ description: 'AWS region for Bedrock'
56
+ required: false
57
+ default: 'us-west-2'
58
+
59
+ aws_role_arn:
60
+ description: 'AWS role ARN for OIDC authentication'
61
+ required: false
62
+
63
+ # Knowledge Base Configuration
64
+ knowledge_base_id:
65
+ description: 'Strands Knowledge Base ID for memory'
66
+ required: false
67
+
68
+ # Advanced Configuration
69
+ additional_request_fields:
70
+ description: 'Additional request fields JSON (e.g., thinking, anthropic_beta)'
71
+ required: false
72
+ default: ''
73
+
74
+ bypass_tool_consent:
75
+ description: 'Skip tool consent prompts'
76
+ required: false
77
+ default: 'true'
78
+
79
+ # MCP Configuration
80
+ mcp_servers:
81
+ description: 'MCP servers configuration (JSON string)'
82
+ required: false
83
+ # default: '{"mcpServers":{"strands-docs":{"command":"uvx","args":["strands-agents-mcp-server"],"timeout":30000}}}'
84
+
85
+ # Agent Runner Configuration
86
+ agent_runner:
87
+ description: 'URL to custom agent_runner.py script. Falls back to repository variable AGENT_RUNNER, then default gist'
88
+ required: false
89
+
90
+ outputs:
91
+ result:
92
+ description: 'Result from the agent execution'
93
+
94
+ success:
95
+ description: 'Whether the agent execution was successful'
96
+
97
+ error:
98
+ description: 'Error message if execution failed'
99
+
100
+ runs:
101
+ using: 'composite'
102
+ steps:
103
+ - name: Use Homebrew Python 3.13
104
+ if: runner.os == 'macOS'
105
+ shell: bash
106
+ run: |
107
+ echo "/opt/homebrew/opt/python@3.13/libexec/bin" >> $GITHUB_PATH
108
+ echo "/opt/homebrew/bin" >> $GITHUB_PATH
109
+
110
+ - name: Set up Python
111
+ if: runner.os != 'macOS'
112
+ uses: actions/setup-python@v4
113
+ with:
114
+ python-version: '3.13'
115
+
116
+ - name: Cache UV dependencies
117
+ uses: actions/cache@v4
118
+ with:
119
+ path: |
120
+ ~/.cache/uv
121
+ key: ${{ runner.os }}-python${{ matrix.python-version || '3.13' }}-uv-${{ hashFiles('requirements.txt', 'requirements-local.txt') }}-v2
122
+ restore-keys: |
123
+ ${{ runner.os }}-python${{ matrix.python-version || '3.13' }}-uv-
124
+
125
+ - name: Clean UV cache on failure
126
+ if: failure()
127
+ shell: bash
128
+ run: |
129
+ uv cache clean || true
130
+ rm -rf ~/.cache/uv/* || true
131
+
132
+ - name: Install UV
133
+ shell: bash
134
+ run: |
135
+ curl -LsSf https://astral.sh/uv/install.sh | sh
136
+ echo "$HOME/.cargo/bin" >> $GITHUB_PATH
137
+
138
+ - name: Install Strands Agents
139
+ shell: bash
140
+ run: |
141
+ uv pip install --system -r requirements.txt --quiet
142
+
143
+ - name: Install Local Dependencies
144
+ if: runner.os == 'macOS'
145
+ shell: bash
146
+ run: |
147
+ uv pip install --system -r requirements-local.txt --quiet
148
+
149
+ - name: Configure Git
150
+ shell: bash
151
+ run: |
152
+ git config --global user.name "DevDuck" 2>/dev/null
153
+ git config --global user.email "hey@strands.my" 2>/dev/null
154
+
155
+ - name: Configure AWS credentials
156
+ if: inputs.aws_role_arn != ''
157
+ uses: aws-actions/configure-aws-credentials@v4
158
+ with:
159
+ role-to-assume: ${{ inputs.aws_role_arn }}
160
+ role-session-name: GitHubActions-StrandsAgent-${{ github.run_id }}
161
+ aws-region: ${{ inputs.aws_region }}
162
+ mask-aws-account-id: true
163
+
164
+ - name: Download agent script
165
+ shell: bash
166
+ env:
167
+ # GitHub Configuration - Priority: Input > Repository Variable > Default Gist
168
+ AGENT_RUNNER: ${{ inputs.agent_runner || 'https://gist.githubusercontent.com/cagataycali/f67df59c34c0d7ff13adbba3b0f8d3d9/raw/bb324e37d19948f0d36d0ca9ce721ca64deb13a1/agent_runner.py' }}
169
+ run: |
170
+ curl -s -o agent_runner.py $AGENT_RUNNER
171
+
172
+ - name: Run Strands Agent
173
+ shell: bash
174
+ env:
175
+ # GitHub Configuration
176
+ GITHUB_TOKEN: ${{ inputs.github_token }}
177
+ GITHUB_CONTEXT: ${{ toJson(github) }}
178
+ GITHUB_REPOSITORY: ${{ github.repository }}
179
+ GITHUB_EVENT_NAME: ${{ github.event_name }}
180
+ GITHUB_ACTOR: ${{ github.actor }}
181
+
182
+ # Model Configuration
183
+ STRANDS_PROVIDER: ${{ inputs.provider }}
184
+ STRANDS_MODEL_ID: ${{ inputs.model }}
185
+ STRANDS_MAX_TOKENS: ${{ inputs.max_tokens }}
186
+ STRANDS_TEMPERATURE: ${{ inputs.temperature }}
187
+ STRANDS_ADDITIONAL_REQUEST_FIELDS: ${{ inputs.additional_request_fields }}
188
+
189
+ # Tool Configuration
190
+ STRANDS_TOOLS: ${{ inputs.tools }}
191
+ BYPASS_TOOL_CONSENT: ${{ inputs.bypass_tool_consent }}
192
+ STRANDS_TOOL_CONSOLE_MODE: "enabled"
193
+
194
+ # Task Configuration
195
+ INPUT_TASK: ${{ inputs.task }}
196
+ INPUT_SYSTEM_PROMPT: ${{ inputs.system_prompt }}
197
+
198
+ # AWS Configuration
199
+ AWS_REGION: ${{ inputs.aws_region }}
200
+
201
+ # Knowledge Base
202
+ STRANDS_KNOWLEDGE_BASE_ID: ${{ inputs.knowledge_base_id }}
203
+
204
+ # MCP Configuration
205
+ MCP_SERVERS: ${{ inputs.mcp_servers }}
206
+ run: |
207
+ AGENT_INPUT="$INPUT_TASK"
208
+
209
+ echo "🤖 Starting Strands Agent..."
210
+
211
+ # Run the agent with UV
212
+ uv run agent_runner.py "$AGENT_INPUT"
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Minimal DevDuck GitHub Agent Runner
4
+ Uses DevDuck with GitHub context, MCP tools, and knowledge base integration.
5
+
6
+ Examples:
7
+ echo "analyze this data" | python agent_runner.py
8
+ python agent_runner.py "what can you do"
9
+ INPUT_TASK="search papers" python agent_runner.py
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import sys
15
+ import datetime
16
+
17
+ # Disable auto-start servers for DevDuck singleton before import
18
+ os.environ["DEVDUCK_AUTO_START_SERVERS"] = "false"
19
+ os.environ["BYPASS_TOOL_CONSENT"] = "true"
20
+ os.environ["STRANDS_TOOL_CONSOLE_MODE"] = "enabled"
21
+
22
+ from mcp import StdioServerParameters, stdio_client
23
+ from strands.tools.mcp import MCPClient
24
+
25
+ # HTTP transport imports
26
+ try:
27
+ from mcp.client.sse import sse_client
28
+ from mcp.client.streamable_http import streamablehttp_client
29
+
30
+ HTTP_TRANSPORT_AVAILABLE = True
31
+ except ImportError:
32
+ HTTP_TRANSPORT_AVAILABLE = False
33
+
34
+ # DevDuck is required
35
+ from devduck import DevDuck
36
+
37
+
38
+ def load_mcp_config() -> dict:
39
+ """Load MCP server configuration from JSON file or environment variable."""
40
+ mcp_servers_env = os.getenv("MCP_SERVERS")
41
+ if mcp_servers_env:
42
+ try:
43
+ config = json.loads(mcp_servers_env)
44
+ return config.get("mcpServers", {})
45
+ except json.JSONDecodeError:
46
+ pass
47
+
48
+ # Fallback: hardcoded strands-agents-mcp-server
49
+ return {"strands-agents": {"command": "uvx", "args": ["strands-agents-mcp-server"]}}
50
+
51
+
52
+ def create_mcp_clients() -> list:
53
+ """Create MCP clients from configuration."""
54
+ mcp_config = load_mcp_config()
55
+ clients = []
56
+
57
+ for server_name, server_config in mcp_config.items():
58
+ try:
59
+ command = server_config.get("command")
60
+ if command:
61
+ args = server_config.get("args", [])
62
+ env = server_config.get("env", {})
63
+
64
+ client = MCPClient(
65
+ lambda cmd=command, arguments=args, environment=env: stdio_client(
66
+ StdioServerParameters(
67
+ command=cmd,
68
+ args=arguments,
69
+ env=environment if environment else None,
70
+ )
71
+ )
72
+ )
73
+ clients.append((server_name, client))
74
+
75
+ elif server_config.get("url") and HTTP_TRANSPORT_AVAILABLE:
76
+ url = server_config.get("url")
77
+ headers = server_config.get("headers", {})
78
+
79
+ try:
80
+ client = MCPClient(lambda: sse_client(url, headers=headers))
81
+ clients.append((server_name, client))
82
+ except Exception:
83
+ try:
84
+ client = MCPClient(
85
+ lambda: streamablehttp_client(url, headers=headers)
86
+ )
87
+ clients.append((server_name, client))
88
+ except Exception:
89
+ continue
90
+ except Exception:
91
+ continue
92
+
93
+ return clients
94
+
95
+
96
+ def build_system_prompt() -> str:
97
+ """Build system prompt from environment variables."""
98
+ base_prompt = os.getenv("SYSTEM_PROMPT", "")
99
+ if not base_prompt:
100
+ base_prompt = "You are an autonomous GitHub agent powered by DevDuck."
101
+
102
+ input_system_prompt = os.getenv("INPUT_SYSTEM_PROMPT", "")
103
+ if input_system_prompt:
104
+ base_prompt = f"{base_prompt}\n\n{input_system_prompt}"
105
+
106
+ github_context = os.environ.get("GITHUB_CONTEXT", "")
107
+ if github_context:
108
+ base_prompt = f"{base_prompt}\n\nGitHub Context:\n{github_context}"
109
+
110
+ return base_prompt
111
+
112
+
113
+ def run_agent(query: str) -> str:
114
+ """Run DevDuck agent with GitHub context and MCP tools."""
115
+
116
+ print("🦆 Creating DevDuck agent...")
117
+
118
+ # Create a new DevDuck instance without auto-starting servers
119
+ duck = DevDuck(auto_start_servers=False)
120
+ agent = duck.agent
121
+
122
+ # Build and append GitHub context to DevDuck's system prompt
123
+ github_system_prompt = build_system_prompt()
124
+ if agent and github_system_prompt:
125
+ agent.system_prompt += "\n\nCustom system prompt:" + github_system_prompt
126
+ print("✅ GitHub context appended to DevDuck's system prompt")
127
+
128
+ # Get MCP clients
129
+ mcp_clients = create_mcp_clients()
130
+
131
+ # Run agent with MCP context
132
+ # NOTE: KB retrieval/storage now handled automatically in DevDuck.__call__
133
+ result = None
134
+ if mcp_clients:
135
+ print(f"🔗 Loading {len(mcp_clients)} MCP servers...")
136
+
137
+ def run_with_mcp(clients, current_agent):
138
+ if not clients:
139
+ return current_agent(query)
140
+
141
+ server_name, client = clients[0]
142
+ remaining = clients[1:]
143
+
144
+ with client:
145
+ try:
146
+ mcp_tools = client.list_tools_sync()
147
+ print(f"✓ {server_name}: {len(mcp_tools)} tools loaded")
148
+ except Exception as e:
149
+ print(f"⚠️ {server_name} failed: {e}")
150
+
151
+ return run_with_mcp(remaining, current_agent)
152
+
153
+ result = run_with_mcp(mcp_clients, agent)
154
+ else:
155
+ result = agent(query)
156
+
157
+ return result
158
+
159
+
160
+ def main():
161
+ """Main entry point."""
162
+
163
+ # Multi-input task collection (priority-based)
164
+ tasks = {}
165
+
166
+ # Priority 1: Piped input (stdin)
167
+ if not sys.stdin.isatty():
168
+ try:
169
+ pipe_task = sys.stdin.read().strip()
170
+ if pipe_task:
171
+ tasks["pipe"] = pipe_task
172
+ except Exception:
173
+ pass
174
+
175
+ # Priority 2: Command line arguments
176
+ if len(sys.argv) > 1:
177
+ cmd_task = " ".join(sys.argv[1:])
178
+ if cmd_task:
179
+ tasks["command_line"] = cmd_task
180
+
181
+ # Priority 3: Environment variable
182
+ env_task = os.getenv("INPUT_TASK")
183
+ if env_task:
184
+ tasks["environment"] = env_task
185
+
186
+ # No tasks found
187
+ if not tasks:
188
+ print(
189
+ "❌ Error: No task provided. Use command line args, stdin, or INPUT_TASK env var."
190
+ )
191
+ sys.exit(1)
192
+
193
+ # Combine all tasks
194
+ combined_task = "\n\n".join(tasks.values())
195
+
196
+ try:
197
+ result = run_agent(combined_task)
198
+ print(f"\n✅ Agent Execution Complete!")
199
+ print(f"📝 Result: {result}")
200
+ except Exception as e:
201
+ print(f"❌ Fatal error: {e}")
202
+ sys.exit(1)
203
+
204
+
205
+ if __name__ == "__main__":
206
+ main()