devduck 0.1.1766644714__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of devduck might be problematic. Click here for more details.
- devduck/__init__.py +591 -1092
- devduck/_version.py +2 -2
- devduck/install.sh +42 -0
- devduck/test_redduck.py +1 -0
- devduck/tools/__init__.py +4 -44
- devduck/tools/install_tools.py +2 -103
- devduck/tools/mcp_server.py +6 -34
- devduck/tools/tcp.py +7 -6
- devduck/tools/websocket.py +2 -8
- devduck-0.2.0.dist-info/METADATA +143 -0
- devduck-0.2.0.dist-info/RECORD +16 -0
- {devduck-0.1.1766644714.dist-info → devduck-0.2.0.dist-info}/entry_points.txt +0 -1
- devduck-0.2.0.dist-info/licenses/LICENSE +21 -0
- devduck/agentcore_handler.py +0 -76
- devduck/tools/_ambient_input.py +0 -423
- devduck/tools/_tray_app.py +0 -530
- devduck/tools/agentcore_agents.py +0 -197
- devduck/tools/agentcore_config.py +0 -441
- devduck/tools/agentcore_invoke.py +0 -423
- devduck/tools/agentcore_logs.py +0 -320
- devduck/tools/ambient.py +0 -157
- devduck/tools/create_subagent.py +0 -659
- devduck/tools/fetch_github_tool.py +0 -201
- devduck/tools/ipc.py +0 -546
- devduck/tools/scraper.py +0 -935
- devduck/tools/speech_to_speech.py +0 -850
- devduck/tools/state_manager.py +0 -292
- devduck/tools/store_in_kb.py +0 -187
- devduck/tools/system_prompt.py +0 -608
- devduck/tools/tray.py +0 -247
- devduck/tools/use_github.py +0 -438
- devduck-0.1.1766644714.dist-info/METADATA +0 -717
- devduck-0.1.1766644714.dist-info/RECORD +0 -33
- devduck-0.1.1766644714.dist-info/licenses/LICENSE +0 -201
- {devduck-0.1.1766644714.dist-info → devduck-0.2.0.dist-info}/WHEEL +0 -0
- {devduck-0.1.1766644714.dist-info → devduck-0.2.0.dist-info}/top_level.txt +0 -0
devduck/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.
|
|
32
|
-
__version_tuple__ = version_tuple = (0,
|
|
31
|
+
__version__ = version = '0.2.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 2, 0)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
devduck/install.sh
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# 🦆 DevDuck installer - Extreme minimalist agent
|
|
3
|
+
|
|
4
|
+
echo "🦆 Installing Devduck..."
|
|
5
|
+
|
|
6
|
+
# Check Python
|
|
7
|
+
if ! command -v python3 &> /dev/null; then
|
|
8
|
+
echo "❌ Python 3 not found. Please install Python 3.8+"
|
|
9
|
+
exit 1
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
# Check Ollama
|
|
13
|
+
if ! command -v ollama &> /dev/null; then
|
|
14
|
+
echo "⚠️ Ollama not found. Installing..."
|
|
15
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
16
|
+
# macOS
|
|
17
|
+
if command -v brew &> /dev/null; then
|
|
18
|
+
brew install ollama
|
|
19
|
+
else
|
|
20
|
+
curl -fsSL https://ollama.ai/install.sh | sh
|
|
21
|
+
fi
|
|
22
|
+
else
|
|
23
|
+
# Linux
|
|
24
|
+
curl -fsSL https://ollama.ai/install.sh | sh
|
|
25
|
+
fi
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
# Start ollama service
|
|
29
|
+
echo "🦆 Starting Ollama service..."
|
|
30
|
+
ollama serve &
|
|
31
|
+
sleep 2
|
|
32
|
+
|
|
33
|
+
# Pull a basic model
|
|
34
|
+
echo "🦆 Pulling basic model..."
|
|
35
|
+
ollama pull qwen3:1.7b
|
|
36
|
+
|
|
37
|
+
# Test devduck
|
|
38
|
+
echo "🦆 Testing Devduck..."
|
|
39
|
+
python3 __init__.py "what's 5*7?"
|
|
40
|
+
|
|
41
|
+
echo "✅ Devduck installed successfully!"
|
|
42
|
+
echo "Usage: python3 __init__.py 'your question'"
|
devduck/test_redduck.py
CHANGED
devduck/tools/__init__.py
CHANGED
|
@@ -1,47 +1,7 @@
|
|
|
1
|
-
"""
|
|
2
|
-
DevDuck Tools Package
|
|
1
|
+
"""DevDuck tools package."""
|
|
3
2
|
|
|
4
|
-
This module exports all available tools for devduck.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
from .agentcore_agents import agentcore_agents
|
|
8
|
-
from .agentcore_config import agentcore_config
|
|
9
|
-
from .agentcore_invoke import agentcore_invoke
|
|
10
|
-
from .agentcore_logs import agentcore_logs
|
|
11
|
-
from .ambient import ambient
|
|
12
|
-
from .create_subagent import create_subagent
|
|
13
|
-
from .fetch_github_tool import fetch_github_tool
|
|
14
|
-
from .install_tools import install_tools
|
|
15
|
-
from .ipc import ipc
|
|
16
|
-
from .mcp_server import mcp_server
|
|
17
|
-
from .scraper import scraper
|
|
18
|
-
from .speech_to_speech import speech_to_speech
|
|
19
|
-
from .state_manager import state_manager
|
|
20
|
-
from .store_in_kb import store_in_kb
|
|
21
|
-
from .system_prompt import system_prompt
|
|
22
3
|
from .tcp import tcp
|
|
23
|
-
from .
|
|
24
|
-
from .
|
|
25
|
-
from .websocket import websocket
|
|
4
|
+
from .mcp_server import mcp_server
|
|
5
|
+
from .install_tools import install_tools
|
|
26
6
|
|
|
27
|
-
__all__ = [
|
|
28
|
-
"agentcore_agents",
|
|
29
|
-
"agentcore_config",
|
|
30
|
-
"agentcore_invoke",
|
|
31
|
-
"agentcore_logs",
|
|
32
|
-
"ambient",
|
|
33
|
-
"create_subagent",
|
|
34
|
-
"fetch_github_tool",
|
|
35
|
-
"install_tools",
|
|
36
|
-
"ipc",
|
|
37
|
-
"mcp_server",
|
|
38
|
-
"scraper",
|
|
39
|
-
"speech_to_speech",
|
|
40
|
-
"state_manager",
|
|
41
|
-
"store_in_kb",
|
|
42
|
-
"system_prompt",
|
|
43
|
-
"tcp",
|
|
44
|
-
"tray",
|
|
45
|
-
"use_github",
|
|
46
|
-
"websocket",
|
|
47
|
-
]
|
|
7
|
+
__all__ = ["tcp", "mcp_server", "install_tools"]
|
devduck/tools/install_tools.py
CHANGED
|
@@ -29,7 +29,7 @@ def install_tools(
|
|
|
29
29
|
and loading their tools into the agent's registry at runtime.
|
|
30
30
|
|
|
31
31
|
Args:
|
|
32
|
-
action: Action to perform - "install", "load", "install_and_load", "list_loaded"
|
|
32
|
+
action: Action to perform - "install", "load", "install_and_load", "list_loaded"
|
|
33
33
|
package: Python package to install (e.g., "strands-agents-tools", "strands-fun-tools")
|
|
34
34
|
module: Module to import tools from (e.g., "strands_tools", "strands_fun_tools")
|
|
35
35
|
tool_names: Optional list of specific tools to load. If None, loads all available tools
|
|
@@ -39,13 +39,6 @@ def install_tools(
|
|
|
39
39
|
Result dictionary with status and content
|
|
40
40
|
|
|
41
41
|
Examples:
|
|
42
|
-
# List available tools in a package (without loading)
|
|
43
|
-
install_tools(
|
|
44
|
-
action="list_available",
|
|
45
|
-
package="strands-fun-tools",
|
|
46
|
-
module="strands_fun_tools"
|
|
47
|
-
)
|
|
48
|
-
|
|
49
42
|
# Install and load all tools from strands-agents-tools
|
|
50
43
|
install_tools(
|
|
51
44
|
action="install_and_load",
|
|
@@ -86,15 +79,13 @@ def install_tools(
|
|
|
86
79
|
return _load_tools_from_module(module, tool_names, agent)
|
|
87
80
|
elif action == "list_loaded":
|
|
88
81
|
return _list_loaded_tools(agent)
|
|
89
|
-
elif action == "list_available":
|
|
90
|
-
return _list_available_tools(package, module)
|
|
91
82
|
else:
|
|
92
83
|
return {
|
|
93
84
|
"status": "error",
|
|
94
85
|
"content": [
|
|
95
86
|
{
|
|
96
87
|
"text": f"❌ Unknown action: {action}\n\n"
|
|
97
|
-
f"Valid actions: install, load, install_and_load, list_loaded
|
|
88
|
+
f"Valid actions: install, load, install_and_load, list_loaded"
|
|
98
89
|
}
|
|
99
90
|
],
|
|
100
91
|
}
|
|
@@ -315,95 +306,3 @@ def _list_loaded_tools(agent: Any) -> Dict[str, Any]:
|
|
|
315
306
|
"status": "error",
|
|
316
307
|
"content": [{"text": f"❌ Failed to list tools: {str(e)}"}],
|
|
317
308
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
def _list_available_tools(package: Optional[str], module: str) -> Dict[str, Any]:
|
|
321
|
-
"""List available tools in a package without loading them."""
|
|
322
|
-
if not module:
|
|
323
|
-
return {
|
|
324
|
-
"status": "error",
|
|
325
|
-
"content": [
|
|
326
|
-
{"text": "❌ module parameter is required for list_available action"}
|
|
327
|
-
],
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
try:
|
|
331
|
-
# Try to import the module
|
|
332
|
-
try:
|
|
333
|
-
imported_module = importlib.import_module(module)
|
|
334
|
-
logger.info(f"Module {module} already installed")
|
|
335
|
-
except ImportError:
|
|
336
|
-
# Module not installed - try to install package first
|
|
337
|
-
if not package:
|
|
338
|
-
return {
|
|
339
|
-
"status": "error",
|
|
340
|
-
"content": [
|
|
341
|
-
{
|
|
342
|
-
"text": f"❌ Module {module} not found and no package specified to install.\n\n"
|
|
343
|
-
f"Please provide the 'package' parameter to install first."
|
|
344
|
-
}
|
|
345
|
-
],
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
logger.info(f"Module {module} not found, installing package {package}")
|
|
349
|
-
install_result = _install_package(package)
|
|
350
|
-
if install_result["status"] == "error":
|
|
351
|
-
return install_result
|
|
352
|
-
|
|
353
|
-
# Try importing again after installation
|
|
354
|
-
try:
|
|
355
|
-
imported_module = importlib.import_module(module)
|
|
356
|
-
except ImportError as e:
|
|
357
|
-
return {
|
|
358
|
-
"status": "error",
|
|
359
|
-
"content": [
|
|
360
|
-
{
|
|
361
|
-
"text": f"❌ Failed to import {module} even after installing {package}: {str(e)}"
|
|
362
|
-
}
|
|
363
|
-
],
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
# Discover tools in the module
|
|
367
|
-
available_tools = {}
|
|
368
|
-
for attr_name in dir(imported_module):
|
|
369
|
-
attr = getattr(imported_module, attr_name)
|
|
370
|
-
# Check if it's a tool (has tool_name and tool_spec attributes)
|
|
371
|
-
if hasattr(attr, "tool_name") and hasattr(attr, "tool_spec"):
|
|
372
|
-
tool_spec = attr.tool_spec
|
|
373
|
-
description = tool_spec.get("description", "No description available")
|
|
374
|
-
available_tools[attr.tool_name] = description
|
|
375
|
-
|
|
376
|
-
if not available_tools:
|
|
377
|
-
return {
|
|
378
|
-
"status": "success",
|
|
379
|
-
"content": [{"text": f"⚠️ No tools found in module: {module}"}],
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
# Build result message
|
|
383
|
-
result_lines = [
|
|
384
|
-
f"📦 **Available Tools in {module} ({len(available_tools)})**\n"
|
|
385
|
-
]
|
|
386
|
-
|
|
387
|
-
for tool_name, description in sorted(available_tools.items()):
|
|
388
|
-
# Truncate long descriptions
|
|
389
|
-
if len(description) > 100:
|
|
390
|
-
description = description[:97] + "..."
|
|
391
|
-
|
|
392
|
-
result_lines.append(f"**{tool_name}**")
|
|
393
|
-
result_lines.append(f" {description}\n")
|
|
394
|
-
|
|
395
|
-
result_lines.append(f"\n💡 To load these tools, use:")
|
|
396
|
-
result_lines.append(f" install_tools(action='load', module='{module}')")
|
|
397
|
-
result_lines.append(f" # Or load specific tools:")
|
|
398
|
-
result_lines.append(
|
|
399
|
-
f" install_tools(action='load', module='{module}', tool_names=['tool1', 'tool2'])"
|
|
400
|
-
)
|
|
401
|
-
|
|
402
|
-
return {"status": "success", "content": [{"text": "\n".join(result_lines)}]}
|
|
403
|
-
|
|
404
|
-
except Exception as e:
|
|
405
|
-
logger.exception(f"Error listing available tools from {module}")
|
|
406
|
-
return {
|
|
407
|
-
"status": "error",
|
|
408
|
-
"content": [{"text": f"❌ Failed to list available tools: {str(e)}"}],
|
|
409
|
-
}
|
devduck/tools/mcp_server.py
CHANGED
|
@@ -190,11 +190,9 @@ def _start_mcp_server(
|
|
|
190
190
|
agent_invoke_tool = types.Tool(
|
|
191
191
|
name="devduck",
|
|
192
192
|
description=(
|
|
193
|
-
"Invoke
|
|
194
|
-
"
|
|
195
|
-
"
|
|
196
|
-
"Use this for complex queries requiring reasoning, multi-tool orchestration, "
|
|
197
|
-
"or when you need the complete DevDuck experience via MCP."
|
|
193
|
+
"Invoke the full DevDuck agent with a natural language prompt. "
|
|
194
|
+
"Use this for complex queries that require reasoning across multiple tools "
|
|
195
|
+
"or when you need a conversational response from the agent."
|
|
198
196
|
),
|
|
199
197
|
inputSchema={
|
|
200
198
|
"type": "object",
|
|
@@ -230,7 +228,7 @@ def _start_mcp_server(
|
|
|
230
228
|
try:
|
|
231
229
|
logger.debug(f"call_tool: name={name}, arguments={arguments}")
|
|
232
230
|
|
|
233
|
-
# Handle agent invocation tool -
|
|
231
|
+
# Handle agent invocation tool - use the devduck instance directly
|
|
234
232
|
if name == "devduck" and expose_agent:
|
|
235
233
|
prompt = arguments.get("prompt")
|
|
236
234
|
if not prompt:
|
|
@@ -243,34 +241,8 @@ def _start_mcp_server(
|
|
|
243
241
|
|
|
244
242
|
logger.debug(f"Invoking devduck with prompt: {prompt[:100]}...")
|
|
245
243
|
|
|
246
|
-
#
|
|
247
|
-
|
|
248
|
-
try:
|
|
249
|
-
from devduck import DevDuck
|
|
250
|
-
|
|
251
|
-
# Create fresh DevDuck instance (no auto-start to avoid recursion)
|
|
252
|
-
mcp_devduck = DevDuck(auto_start_servers=False)
|
|
253
|
-
mcp_agent = mcp_devduck.agent
|
|
254
|
-
|
|
255
|
-
if not mcp_agent:
|
|
256
|
-
return [
|
|
257
|
-
types.TextContent(
|
|
258
|
-
type="text",
|
|
259
|
-
text="❌ Error: Failed to create DevDuck instance",
|
|
260
|
-
)
|
|
261
|
-
]
|
|
262
|
-
|
|
263
|
-
# Execute with full DevDuck capabilities
|
|
264
|
-
result = mcp_agent(prompt)
|
|
265
|
-
|
|
266
|
-
except Exception as e:
|
|
267
|
-
logger.error(f"DevDuck creation failed: {e}", exc_info=True)
|
|
268
|
-
return [
|
|
269
|
-
types.TextContent(
|
|
270
|
-
type="text",
|
|
271
|
-
text=f"❌ Error creating DevDuck instance: {str(e)}",
|
|
272
|
-
)
|
|
273
|
-
]
|
|
244
|
+
# Use the devduck agent directly (don't create a new instance)
|
|
245
|
+
result = agent(prompt)
|
|
274
246
|
|
|
275
247
|
# Extract text response from agent result
|
|
276
248
|
response_text = str(result)
|
devduck/tools/tcp.py
CHANGED
|
@@ -216,6 +216,13 @@ def handle_client(
|
|
|
216
216
|
)
|
|
217
217
|
|
|
218
218
|
try:
|
|
219
|
+
# Send welcome message
|
|
220
|
+
welcome_msg = "🦆 Welcome to DevDuck TCP Server!\n"
|
|
221
|
+
welcome_msg += (
|
|
222
|
+
"Real-time streaming enabled - responses stream as they're generated.\n"
|
|
223
|
+
)
|
|
224
|
+
welcome_msg += "Send a message or 'exit' to close the connection.\n\n"
|
|
225
|
+
streaming_handler._send(welcome_msg)
|
|
219
226
|
|
|
220
227
|
while True:
|
|
221
228
|
# Receive data from the client
|
|
@@ -316,12 +323,6 @@ def run_server(
|
|
|
316
323
|
if SERVER_THREADS[port]["running"]:
|
|
317
324
|
logger.error(f"Error accepting connection: {e}")
|
|
318
325
|
|
|
319
|
-
except OSError as e:
|
|
320
|
-
# Port conflict - handled upstream, no need for scary errors
|
|
321
|
-
if "Address already in use" in str(e):
|
|
322
|
-
logger.debug(f"Port {port} unavailable (handled upstream)")
|
|
323
|
-
else:
|
|
324
|
-
logger.error(f"Server error on {host}:{port}: {e}")
|
|
325
326
|
except Exception as e:
|
|
326
327
|
logger.error(f"Server error on {host}:{port}: {e}")
|
|
327
328
|
finally:
|
devduck/tools/websocket.py
CHANGED
|
@@ -261,7 +261,7 @@ async def handle_websocket_client(websocket, system_prompt: str):
|
|
|
261
261
|
# Send welcome message
|
|
262
262
|
welcome = {
|
|
263
263
|
"type": "connected",
|
|
264
|
-
"data": "🦆 Welcome to DevDuck!",
|
|
264
|
+
"data": "🦆 Welcome to DevDuck WebSocket Server! Real-time streaming enabled.",
|
|
265
265
|
"timestamp": time.time(),
|
|
266
266
|
}
|
|
267
267
|
await websocket.send(json.dumps(welcome))
|
|
@@ -347,14 +347,8 @@ def run_websocket_server(
|
|
|
347
347
|
asyncio.set_event_loop(loop)
|
|
348
348
|
WS_SERVER_THREADS[port]["loop"] = loop
|
|
349
349
|
loop.run_until_complete(start_server())
|
|
350
|
-
except OSError as e:
|
|
351
|
-
# Port conflict - handled upstream, no need for scary errors
|
|
352
|
-
if "Address already in use" in str(e) or "address already in use" in str(e):
|
|
353
|
-
logger.debug(f"Port {port} unavailable (handled upstream)")
|
|
354
|
-
else:
|
|
355
|
-
logger.error(f"WebSocket server error on {host}:{port}: {e}")
|
|
356
350
|
except Exception as e:
|
|
357
|
-
logger.error(f"WebSocket server error on {host}:{port}: {e}")
|
|
351
|
+
logger.error(f"WebSocket server error on {host}:{port}: {e}", exc_info=True)
|
|
358
352
|
finally:
|
|
359
353
|
logger.info(f"WebSocket Server on {host}:{port} stopped")
|
|
360
354
|
WS_SERVER_THREADS[port]["running"] = False
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: devduck
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: 🦆 Extreme minimalist self-adapting AI agent - one file, self-healing, runtime dependencies
|
|
5
|
+
Author-email: duck <hey@devduck.dev>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/cagataycali/devduck
|
|
8
|
+
Project-URL: Repository, https://github.com/cagataycali/devduck.git
|
|
9
|
+
Project-URL: Documentation, https://github.com/cagataycali/devduck#readme
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/cagataycali/devduck/issues
|
|
11
|
+
Keywords: ai,agent,minimalist,self-healing,ollama,strands-agents
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: System Administrators
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: System :: Systems Administration
|
|
25
|
+
Classifier: Topic :: Utilities
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: strands-agents
|
|
30
|
+
Requires-Dist: strands-agents[ollama]
|
|
31
|
+
Requires-Dist: strands-agents[openai]
|
|
32
|
+
Requires-Dist: strands-agents[anthropic]
|
|
33
|
+
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]
|
|
37
|
+
Requires-Dist: websockets
|
|
38
|
+
Requires-Dist: prompt_toolkit
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# 🦆 DevDuck
|
|
42
|
+
|
|
43
|
+
**One file. Self-healing. Adaptive.**
|
|
44
|
+
|
|
45
|
+
Minimalist AI agent that fixes itself when things break.
|
|
46
|
+
|
|
47
|
+
## Install
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pipx install devduck
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Requires: Python 3.10+, Ollama running
|
|
54
|
+
|
|
55
|
+
## Use
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
# Start DevDuck (auto-starts TCP, WebSocket, MCP servers)
|
|
59
|
+
devduck
|
|
60
|
+
|
|
61
|
+
# CLI mode
|
|
62
|
+
devduck "what's the time?"
|
|
63
|
+
|
|
64
|
+
# Python
|
|
65
|
+
import devduck
|
|
66
|
+
devduck("calculate 2+2")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Auto-Started Servers
|
|
70
|
+
|
|
71
|
+
When you run `devduck`, three servers start automatically:
|
|
72
|
+
|
|
73
|
+
- **🌐 Web UI**: [http://cagataycali.github.io/devduck](http://cagataycali.github.io/devduck) (auto-connects)
|
|
74
|
+
- **🔌 TCP**: `nc localhost 9999` (raw socket)
|
|
75
|
+
- **🌊 WebSocket**: `ws://localhost:8080` (structured JSON)
|
|
76
|
+
- **🔗 MCP**: `http://localhost:8000/mcp` (Model Context Protocol)
|
|
77
|
+
|
|
78
|
+
### Connect via MCP
|
|
79
|
+
|
|
80
|
+
Add to your MCP client (e.g., Claude Desktop):
|
|
81
|
+
|
|
82
|
+
```json
|
|
83
|
+
{
|
|
84
|
+
"mcpServers": {
|
|
85
|
+
"devduck": {
|
|
86
|
+
"command": "uvx",
|
|
87
|
+
"args": [
|
|
88
|
+
"strands-mcp-server",
|
|
89
|
+
"--upstream-url",
|
|
90
|
+
"http://localhost:8000/mcp/"
|
|
91
|
+
],
|
|
92
|
+
"disabled": false
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Connect via Terminal
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Direct TCP connection
|
|
102
|
+
nc localhost 9999
|
|
103
|
+
> what's the time?
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Features
|
|
107
|
+
|
|
108
|
+
- **Self-healing** - Auto-fixes deps, models, errors
|
|
109
|
+
- **Hot-reload** - Create tools in `./tools/*.py`, use instantly
|
|
110
|
+
- **Adaptive** - Picks model based on OS (macOS: 1.7b, Linux: 30b)
|
|
111
|
+
- **14 tools** - shell, editor, files, python, calculator, tcp, etc.
|
|
112
|
+
- **History aware** - Remembers shell/conversation context
|
|
113
|
+
- **Multi-protocol** - TCP, WebSocket, MCP, CLI, Python
|
|
114
|
+
|
|
115
|
+
## Create Tool
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# ./tools/greet.py
|
|
119
|
+
from strands import tool
|
|
120
|
+
|
|
121
|
+
@tool
|
|
122
|
+
def greet(name: str) -> str:
|
|
123
|
+
return f"Hello {name}!"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Save. Done. Use immediately.
|
|
127
|
+
|
|
128
|
+
## Multi-Model
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
export MODEL_PROVIDER="bedrock"
|
|
132
|
+
export STRANDS_MODEL_ID="us.anthropic.claude-sonnet-4-5-20250929-v1:0"
|
|
133
|
+
export STRANDS_ADDITIONAL_REQUEST_FIELDS='{"anthropic_beta": ["interleaved-thinking-2025-05-14", "context-1m-2025-08-07"], "thinking": {"type": "enabled", "budget_tokens": 2048}}'
|
|
134
|
+
export STRANDS_MAX_TOKENS="64000"
|
|
135
|
+
|
|
136
|
+
devduck "analyze data"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
**Quack.** 🦆
|
|
142
|
+
|
|
143
|
+
*Built with [Strands Agents SDK](https://github.com/strands-agents/sdk-python)*
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
devduck/__init__.py,sha256=w7H7wKWkeD0fKUhLMJ4dxxrWCkRH_R7w3maxjJI0WgU,51881
|
|
2
|
+
devduck/__main__.py,sha256=aeF2RR4k7lzSR2X1QKV9XQPCKhtsH0JYUv2etBBqmL0,145
|
|
3
|
+
devduck/_version.py,sha256=Dg8AmJomLVpjKL6prJylOONZAPRtB86LOce7dorQS_A,704
|
|
4
|
+
devduck/install.sh,sha256=tYq2WWZFCBEMbxCneKAw3GSNAG1zNhpd-kzW1l5ZISw,990
|
|
5
|
+
devduck/test_redduck.py,sha256=nqRchR7d54jWGx7JN5tji2ZV4Ek4L9s-P7hp0mKjA0Y,1773
|
|
6
|
+
devduck/tools/__init__.py,sha256=mu3V4jL2ACN4f-pnUID_A2p6o3Yc_-V_y9071PduCR0,177
|
|
7
|
+
devduck/tools/install_tools.py,sha256=wm_67b9IfY-2wRuWgxuEKhaSIV5vNfbGmZL3G9dGi2A,10348
|
|
8
|
+
devduck/tools/mcp_server.py,sha256=oyF1gb7K-OlxyJLUO3L-vNo2ajKzIrcnT1crwKMOkhU,20118
|
|
9
|
+
devduck/tools/tcp.py,sha256=4KtyRlgaOLKXT3PU0yFRM79FoOkn3-S10dVL4L5iG80,22063
|
|
10
|
+
devduck/tools/websocket.py,sha256=ryKE1WbfaTFguwp-AzJlyCOifHE5uXJAVdHD8jecJgQ,16681
|
|
11
|
+
devduck-0.2.0.dist-info/licenses/LICENSE,sha256=CVGEiNh6cW1mgAKW83Q0P4xrQEXvqc6W-rb789W_IHM,1060
|
|
12
|
+
devduck-0.2.0.dist-info/METADATA,sha256=yl_DpWxBYGIWkbehwUFH-jyD7W1O945cRCJsE9Ufzew,3902
|
|
13
|
+
devduck-0.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
14
|
+
devduck-0.2.0.dist-info/entry_points.txt,sha256=BAMQaIg_BLZQOTk12bT7hy1dE9oGPLt-_dTbI4cnBnQ,40
|
|
15
|
+
devduck-0.2.0.dist-info/top_level.txt,sha256=ySXWlVronp8xHYfQ_Hdfr463e0EnbWuqyuxs94EU7yk,8
|
|
16
|
+
devduck-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 maxs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
devduck/agentcore_handler.py
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""DevDuck AgentCore Handler"""
|
|
3
|
-
import json
|
|
4
|
-
import os
|
|
5
|
-
import threading
|
|
6
|
-
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
|
7
|
-
|
|
8
|
-
# Configure for AgentCore deployment
|
|
9
|
-
os.environ["DEVDUCK_AUTO_START_SERVERS"] = "false"
|
|
10
|
-
os.environ["MODEL_PROVIDER"] = "bedrock"
|
|
11
|
-
|
|
12
|
-
from devduck import devduck
|
|
13
|
-
|
|
14
|
-
app = BedrockAgentCoreApp()
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@app.entrypoint
|
|
18
|
-
async def invoke(payload, context):
|
|
19
|
-
"""AgentCore entrypoint - streaming by default with async generator"""
|
|
20
|
-
mode = payload.get("mode", "streaming") # streaming (default), sync, async
|
|
21
|
-
|
|
22
|
-
query = payload.get("prompt", payload.get("text", ""))
|
|
23
|
-
if not query:
|
|
24
|
-
yield {"error": "No query provided"}
|
|
25
|
-
return
|
|
26
|
-
|
|
27
|
-
print(f"Mode: {mode}, Query: {query}")
|
|
28
|
-
|
|
29
|
-
agent = devduck.agent
|
|
30
|
-
|
|
31
|
-
if mode == "sync":
|
|
32
|
-
# Sync mode - return result directly (blocking)
|
|
33
|
-
try:
|
|
34
|
-
result = agent(query)
|
|
35
|
-
yield {"statusCode": 200, "response": str(result)}
|
|
36
|
-
except Exception as e:
|
|
37
|
-
print(f"Error in sync: {str(e)}")
|
|
38
|
-
yield {"statusCode": 500, "error": str(e)}
|
|
39
|
-
|
|
40
|
-
elif mode == "async":
|
|
41
|
-
# Async mode - fire and forget in background thread
|
|
42
|
-
task_id = app.add_async_task("devduck_processing", payload)
|
|
43
|
-
thread = threading.Thread(
|
|
44
|
-
target=lambda: _run_in_thread(agent, query, task_id), daemon=True
|
|
45
|
-
)
|
|
46
|
-
thread.start()
|
|
47
|
-
yield {"statusCode": 200, "task_id": task_id}
|
|
48
|
-
|
|
49
|
-
else:
|
|
50
|
-
# Streaming mode (default) - stream events as they happen
|
|
51
|
-
try:
|
|
52
|
-
stream = agent.stream_async(query)
|
|
53
|
-
async for event in stream:
|
|
54
|
-
print(event)
|
|
55
|
-
yield event
|
|
56
|
-
except Exception as e:
|
|
57
|
-
print(f"Error in streaming: {str(e)}")
|
|
58
|
-
yield {"error": str(e)}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def _run_in_thread(agent, query, task_id):
|
|
62
|
-
"""Run agent in background thread for async mode"""
|
|
63
|
-
try:
|
|
64
|
-
result = agent(query)
|
|
65
|
-
print(f"DevDuck result: {result}")
|
|
66
|
-
app.complete_async_task(task_id)
|
|
67
|
-
except Exception as e:
|
|
68
|
-
print(f"Error in async thread: {str(e)}")
|
|
69
|
-
try:
|
|
70
|
-
app.complete_async_task(task_id)
|
|
71
|
-
except:
|
|
72
|
-
pass
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if __name__ == "__main__":
|
|
76
|
-
app.run()
|