devduck 0.2.0__py3-none-any.whl → 0.4.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 CHANGED
@@ -10,6 +10,7 @@ import platform
10
10
  import socket
11
11
  import logging
12
12
  import tempfile
13
+ from datetime import datetime
13
14
  from pathlib import Path
14
15
  from datetime import datetime
15
16
  from typing import Dict, Any
@@ -51,19 +52,18 @@ logger.info("DevDuck logging system initialized")
51
52
 
52
53
  # 🔧 Self-healing dependency installer
53
54
  def ensure_deps():
54
- """Install dependencies at runtime if missing"""
55
+ """Install core dependencies at runtime if missing"""
55
56
  import importlib.metadata
56
57
 
57
- deps = [
58
+ # Only ensure core deps - everything else is optional
59
+ core_deps = [
58
60
  "strands-agents",
59
- "strands-agents[ollama]",
60
- "strands-agents[openai]",
61
- "strands-agents[anthropic]",
61
+ "prompt_toolkit",
62
62
  "strands-agents-tools",
63
63
  ]
64
64
 
65
65
  # Check each package individually using importlib.metadata
66
- for dep in deps:
66
+ for dep in core_deps:
67
67
  pkg_name = dep.split("[")[0] # Get base package name (strip extras)
68
68
  try:
69
69
  # Check if package is installed using metadata (checks PyPI package name)
@@ -606,7 +606,16 @@ def append_to_shell_history(query, response):
606
606
 
607
607
  # 🦆 The devduck agent
608
608
  class DevDuck:
609
- def __init__(self, auto_start_servers=True):
609
+ def __init__(
610
+ self,
611
+ auto_start_servers=True,
612
+ tcp_port=9999,
613
+ ws_port=8080,
614
+ mcp_port=8000,
615
+ enable_tcp=True,
616
+ enable_ws=True,
617
+ enable_mcp=True,
618
+ ):
610
619
  """Initialize the minimalist adaptive agent"""
611
620
  logger.info("Initializing DevDuck agent...")
612
621
  try:
@@ -622,27 +631,105 @@ class DevDuck:
622
631
 
623
632
  # Import after ensuring deps
624
633
  from strands import Agent, tool
625
- from strands.models.ollama import OllamaModel
626
- from strands_tools.utils.models.model import create_model
627
- from .tools import tcp, websocket, mcp_server, install_tools
628
- from strands_fun_tools import (
629
- listen,
630
- cursor,
631
- clipboard,
632
- screen_reader,
633
- yolo_vision,
634
- )
635
- from strands_tools import (
636
- shell,
637
- editor,
638
- calculator,
639
- python_repl,
640
- image_reader,
641
- use_agent,
642
- load_tool,
643
- environment,
644
- mcp_client,
645
- )
634
+
635
+ # Core tools (always available)
636
+ core_tools = []
637
+
638
+ # Try importing optional tools gracefully
639
+ try:
640
+ from strands.models.ollama import OllamaModel
641
+ except ImportError:
642
+ logger.warning(
643
+ "strands-agents[ollama] not installed - Ollama model unavailable"
644
+ )
645
+ OllamaModel = None
646
+
647
+ try:
648
+ from strands_tools.utils.models.model import create_model
649
+ except ImportError:
650
+ logger.warning(
651
+ "strands-agents-tools not installed - create_model unavailable"
652
+ )
653
+ create_model = None
654
+
655
+ try:
656
+ from .tools import (
657
+ tcp,
658
+ websocket,
659
+ mcp_server,
660
+ install_tools,
661
+ use_github,
662
+ create_subagent,
663
+ store_in_kb,
664
+ )
665
+
666
+ core_tools.extend(
667
+ [
668
+ tcp,
669
+ websocket,
670
+ mcp_server,
671
+ install_tools,
672
+ use_github,
673
+ create_subagent,
674
+ store_in_kb,
675
+ ]
676
+ )
677
+ except ImportError as e:
678
+ logger.warning(f"devduck.tools import failed: {e}")
679
+
680
+ # Skip fun tools in --mcp mode (they're not needed for MCP server)
681
+ if "--mcp" not in sys.argv:
682
+ try:
683
+ from strands_fun_tools import (
684
+ listen,
685
+ cursor,
686
+ clipboard,
687
+ screen_reader,
688
+ yolo_vision,
689
+ )
690
+
691
+ core_tools.extend(
692
+ [listen, cursor, clipboard, screen_reader, yolo_vision]
693
+ )
694
+ except ImportError:
695
+ logger.info(
696
+ "strands-fun-tools not installed - vision/audio tools unavailable (install with: pip install devduck[all])"
697
+ )
698
+ else:
699
+ logger.info("--mcp mode: skipping vision/audio tools")
700
+
701
+ try:
702
+ from strands_tools import (
703
+ shell,
704
+ editor,
705
+ calculator,
706
+ # python_repl,
707
+ image_reader,
708
+ use_agent,
709
+ load_tool,
710
+ environment,
711
+ mcp_client,
712
+ retrieve,
713
+ )
714
+
715
+ core_tools.extend(
716
+ [
717
+ shell,
718
+ editor,
719
+ calculator,
720
+ # python_repl,
721
+ image_reader,
722
+ use_agent,
723
+ load_tool,
724
+ environment,
725
+ mcp_client,
726
+ retrieve,
727
+ ]
728
+ )
729
+ except ImportError:
730
+ logger.info(
731
+ "strands-agents-tools not installed - core tools unavailable (install with: pip install devduck[all])"
732
+ )
646
733
 
647
734
  # Wrap system_prompt_tool with @tool decorator
648
735
  @tool
@@ -665,39 +752,21 @@ class DevDuck:
665
752
  """View and manage DevDuck logs."""
666
753
  return view_logs_tool(action, lines, pattern)
667
754
 
668
- # Minimal but functional toolset including system_prompt and view_logs
669
- self.tools = [
670
- shell,
671
- editor,
672
- calculator,
673
- python_repl,
674
- image_reader,
675
- use_agent,
676
- load_tool,
677
- environment,
678
- system_prompt,
679
- view_logs,
680
- tcp,
681
- websocket,
682
- mcp_server,
683
- install_tools,
684
- mcp_client,
685
- listen,
686
- cursor,
687
- clipboard,
688
- screen_reader,
689
- yolo_vision,
690
- ]
755
+ # Add built-in tools to the toolset
756
+ core_tools.extend([system_prompt, view_logs])
757
+
758
+ # Assign tools
759
+ self.tools = core_tools
691
760
 
692
761
  logger.info(f"Initialized {len(self.tools)} tools")
693
762
 
694
763
  # Check if MODEL_PROVIDER env variable is set
695
764
  model_provider = os.getenv("MODEL_PROVIDER")
696
765
 
697
- if model_provider:
766
+ if model_provider and create_model:
698
767
  # Use create_model utility for any provider (bedrock, anthropic, etc.)
699
768
  self.agent_model = create_model(provider=model_provider)
700
- else:
769
+ elif OllamaModel:
701
770
  # Fallback to default Ollama behavior
702
771
  self.agent_model = OllamaModel(
703
772
  host=self.ollama_host,
@@ -705,6 +774,10 @@ class DevDuck:
705
774
  temperature=1,
706
775
  keep_alive="5m",
707
776
  )
777
+ else:
778
+ raise ImportError(
779
+ "No model provider available. Install with: pip install devduck[all]"
780
+ )
708
781
 
709
782
  # Create agent with self-healing
710
783
  self.agent = Agent(
@@ -714,54 +787,59 @@ class DevDuck:
714
787
  load_tools_from_directory=True,
715
788
  )
716
789
 
717
- # 🚀 AUTO-START SERVERS: TCP (9999), WebSocket (8080), MCP HTTP (8000)
790
+ # 🚀 AUTO-START SERVERS: TCP, WebSocket, MCP HTTP
718
791
  if auto_start_servers:
719
792
  logger.info("Auto-starting servers...")
720
793
  print("🦆 Auto-starting servers...")
721
794
 
722
- try:
723
- # Start TCP server on port 9999
724
- tcp_result = self.agent.tool.tcp(action="start_server", port=9999)
725
- if tcp_result.get("status") == "success":
726
- logger.info(" TCP server started on port 9999")
727
- print("🦆 ✓ TCP server: localhost:9999")
728
- else:
729
- logger.warning(f"TCP server start issue: {tcp_result}")
730
- except Exception as e:
731
- logger.error(f"Failed to start TCP server: {e}")
732
- print(f"🦆 ⚠ TCP server failed: {e}")
795
+ if enable_tcp:
796
+ try:
797
+ # Start TCP server on configurable port
798
+ tcp_result = self.agent.tool.tcp(
799
+ action="start_server", port=tcp_port
800
+ )
801
+ if tcp_result.get("status") == "success":
802
+ logger.info(f"TCP server started on port {tcp_port}")
803
+ print(f"🦆 TCP server: localhost:{tcp_port}")
804
+ else:
805
+ logger.warning(f"TCP server start issue: {tcp_result}")
806
+ except Exception as e:
807
+ logger.error(f"Failed to start TCP server: {e}")
808
+ print(f"🦆 ⚠ TCP server failed: {e}")
733
809
 
734
- try:
735
- # Start WebSocket server on port 8080
736
- ws_result = self.agent.tool.websocket(
737
- action="start_server", port=8080
738
- )
739
- if ws_result.get("status") == "success":
740
- logger.info(" WebSocket server started on port 8080")
741
- print("🦆 ✓ WebSocket server: localhost:8080")
742
- else:
743
- logger.warning(f"WebSocket server start issue: {ws_result}")
744
- except Exception as e:
745
- logger.error(f"Failed to start WebSocket server: {e}")
746
- print(f"🦆 WebSocket server failed: {e}")
810
+ if enable_ws:
811
+ try:
812
+ # Start WebSocket server on configurable port
813
+ ws_result = self.agent.tool.websocket(
814
+ action="start_server", port=ws_port
815
+ )
816
+ if ws_result.get("status") == "success":
817
+ logger.info(f"✓ WebSocket server started on port {ws_port}")
818
+ print(f"🦆 ✓ WebSocket server: localhost:{ws_port}")
819
+ else:
820
+ logger.warning(f"WebSocket server start issue: {ws_result}")
821
+ except Exception as e:
822
+ logger.error(f"Failed to start WebSocket server: {e}")
823
+ print(f"🦆 ⚠ WebSocket server failed: {e}")
747
824
 
748
- try:
749
- # Start MCP server with HTTP transport on port 8000
750
- mcp_result = self.agent.tool.mcp_server(
751
- action="start",
752
- transport="http",
753
- port=8000,
754
- expose_agent=True,
755
- agent=self.agent,
756
- )
757
- if mcp_result.get("status") == "success":
758
- logger.info(" MCP HTTP server started on port 8000")
759
- print("🦆 ✓ MCP server: http://localhost:8000/mcp")
760
- else:
761
- logger.warning(f"MCP server start issue: {mcp_result}")
762
- except Exception as e:
763
- logger.error(f"Failed to start MCP server: {e}")
764
- print(f"🦆 MCP server failed: {e}")
825
+ if enable_mcp:
826
+ try:
827
+ # Start MCP server with HTTP transport on configurable port
828
+ mcp_result = self.agent.tool.mcp_server(
829
+ action="start",
830
+ transport="http",
831
+ port=mcp_port,
832
+ expose_agent=True,
833
+ agent=self.agent,
834
+ )
835
+ if mcp_result.get("status") == "success":
836
+ logger.info(f"✓ MCP HTTP server started on port {mcp_port}")
837
+ print(f"🦆 ✓ MCP server: http://localhost:{mcp_port}/mcp")
838
+ else:
839
+ logger.warning(f"MCP server start issue: {mcp_result}")
840
+ except Exception as e:
841
+ logger.error(f"Failed to start MCP server: {e}")
842
+ print(f"🦆 ⚠ MCP server failed: {e}")
765
843
 
766
844
  # Start file watcher for auto hot-reload
767
845
  self._start_file_watcher()
@@ -847,6 +925,12 @@ You have full access to your own source code for self-awareness and self-modific
847
925
  - Connect from Claude Desktop, other agents, or custom clients
848
926
  - Full bidirectional communication
849
927
 
928
+ ## Knowledge Base Integration:
929
+ - **Automatic RAG** - Set STRANDS_KNOWLEDGE_BASE_ID to enable automatic retrieval/storage
930
+ - Before each query: Retrieves relevant context from knowledge base
931
+ - After each response: Stores conversation for future reference
932
+ - Seamless memory across sessions without manual tool calls
933
+
850
934
  ## Tool Creation Patterns:
851
935
 
852
936
  ### **1. @tool Decorator:**
@@ -996,7 +1080,7 @@ def weather(action: str, location: str = None) -> Dict[str, Any]:
996
1080
  self.agent = None
997
1081
 
998
1082
  def __call__(self, query):
999
- """Make the agent callable"""
1083
+ """Make the agent callable with automatic knowledge base integration"""
1000
1084
  if not self.agent:
1001
1085
  logger.warning("Agent unavailable - attempted to call with query")
1002
1086
  return "🦆 Agent unavailable - try: devduck.restart()"
@@ -1006,8 +1090,37 @@ def weather(action: str, location: str = None) -> Dict[str, Any]:
1006
1090
  # Mark agent as executing to prevent hot-reload interruption
1007
1091
  self._agent_executing = True
1008
1092
 
1093
+ # 📚 Knowledge Base Retrieval (BEFORE agent runs)
1094
+ knowledge_base_id = os.getenv("STRANDS_KNOWLEDGE_BASE_ID")
1095
+ if knowledge_base_id and hasattr(self.agent, "tool"):
1096
+ try:
1097
+ if "retrieve" in self.agent.tool_names:
1098
+ logger.info(f"Retrieving context from KB: {knowledge_base_id}")
1099
+ self.agent.tool.retrieve(
1100
+ text=query, knowledgeBaseId=knowledge_base_id
1101
+ )
1102
+ except Exception as e:
1103
+ logger.warning(f"KB retrieval failed: {e}")
1104
+
1105
+ # Run the agent
1009
1106
  result = self.agent(query)
1010
1107
 
1108
+ # 💾 Knowledge Base Storage (AFTER agent runs)
1109
+ if knowledge_base_id and hasattr(self.agent, "tool"):
1110
+ try:
1111
+ if "store_in_kb" in self.agent.tool_names:
1112
+
1113
+ conversation_content = f"Input: {query}, Result: {result!s}"
1114
+ conversation_title = f"DevDuck: {datetime.now().strftime('%Y-%m-%d')} | {query[:500]}"
1115
+ self.agent.tool.store_in_kb(
1116
+ content=conversation_content,
1117
+ title=conversation_title,
1118
+ knowledge_base_id=knowledge_base_id,
1119
+ )
1120
+ logger.info(f"Stored conversation in KB: {knowledge_base_id}")
1121
+ except Exception as e:
1122
+ logger.warning(f"KB storage failed: {e}")
1123
+
1011
1124
  # Agent finished - check if reload was pending
1012
1125
  self._agent_executing = False
1013
1126
  logger.info("Agent call completed successfully")
@@ -1157,7 +1270,30 @@ def weather(action: str, location: str = None) -> Dict[str, Any]:
1157
1270
 
1158
1271
 
1159
1272
  # 🦆 Auto-initialize when imported
1160
- devduck = DevDuck()
1273
+ # Check environment variables to control server configuration
1274
+ # Also check if --mcp flag is present to skip auto-starting servers
1275
+ _auto_start = os.getenv("DEVDUCK_AUTO_START_SERVERS", "true").lower() == "true"
1276
+
1277
+ # Disable auto-start if --mcp flag is present (stdio mode)
1278
+ if "--mcp" in sys.argv:
1279
+ _auto_start = False
1280
+
1281
+ _tcp_port = int(os.getenv("DEVDUCK_TCP_PORT", "9999"))
1282
+ _ws_port = int(os.getenv("DEVDUCK_WS_PORT", "8080"))
1283
+ _mcp_port = int(os.getenv("DEVDUCK_MCP_PORT", "8000"))
1284
+ _enable_tcp = os.getenv("DEVDUCK_ENABLE_TCP", "true").lower() == "true"
1285
+ _enable_ws = os.getenv("DEVDUCK_ENABLE_WS", "true").lower() == "true"
1286
+ _enable_mcp = os.getenv("DEVDUCK_ENABLE_MCP", "true").lower() == "true"
1287
+
1288
+ devduck = DevDuck(
1289
+ auto_start_servers=_auto_start,
1290
+ tcp_port=_tcp_port,
1291
+ ws_port=_ws_port,
1292
+ mcp_port=_mcp_port,
1293
+ enable_tcp=_enable_tcp,
1294
+ enable_ws=_enable_ws,
1295
+ enable_mcp=_enable_mcp,
1296
+ )
1161
1297
 
1162
1298
 
1163
1299
  # 🚀 Convenience functions
@@ -1426,9 +1562,99 @@ You have full access to your own source code for self-awareness and self-modific
1426
1562
 
1427
1563
  def cli():
1428
1564
  """CLI entry point for pip-installed devduck command"""
1565
+ import argparse
1566
+
1567
+ parser = argparse.ArgumentParser(
1568
+ description="🦆 DevDuck - Extreme minimalist self-adapting agent",
1569
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1570
+ epilog="""
1571
+ Examples:
1572
+ devduck # Start interactive mode
1573
+ devduck "your query here" # One-shot query
1574
+ devduck --mcp # MCP stdio mode (for Claude Desktop)
1575
+ devduck --tcp-port 9000 # Custom TCP port
1576
+ devduck --no-tcp --no-ws # Disable TCP and WebSocket
1577
+ devduck --mcp-port 3000 # Custom MCP port
1578
+
1579
+ Claude Desktop Config:
1580
+ {
1581
+ "mcpServers": {
1582
+ "devduck": {
1583
+ "command": "uvx",
1584
+ "args": ["devduck", "--mcp"]
1585
+ }
1586
+ }
1587
+ }
1588
+ """,
1589
+ )
1590
+
1591
+ # Query argument
1592
+ parser.add_argument("query", nargs="*", help="Query to send to the agent")
1593
+
1594
+ # MCP stdio mode flag
1595
+ parser.add_argument(
1596
+ "--mcp",
1597
+ action="store_true",
1598
+ help="Start MCP server in stdio mode (for Claude Desktop integration)",
1599
+ )
1600
+
1601
+ # Server configuration
1602
+ parser.add_argument(
1603
+ "--tcp-port", type=int, default=9999, help="TCP server port (default: 9999)"
1604
+ )
1605
+ parser.add_argument(
1606
+ "--ws-port",
1607
+ type=int,
1608
+ default=8080,
1609
+ help="WebSocket server port (default: 8080)",
1610
+ )
1611
+ parser.add_argument(
1612
+ "--mcp-port",
1613
+ type=int,
1614
+ default=8000,
1615
+ help="MCP HTTP server port (default: 8000)",
1616
+ )
1617
+
1618
+ # Server enable/disable flags
1619
+ parser.add_argument("--no-tcp", action="store_true", help="Disable TCP server")
1620
+ parser.add_argument("--no-ws", action="store_true", help="Disable WebSocket server")
1621
+ parser.add_argument("--no-mcp", action="store_true", help="Disable MCP server")
1622
+ parser.add_argument(
1623
+ "--no-servers",
1624
+ action="store_true",
1625
+ help="Disable all servers (no TCP, WebSocket, or MCP)",
1626
+ )
1627
+
1628
+ args = parser.parse_args()
1629
+
1429
1630
  logger.info("CLI mode started")
1430
- if len(sys.argv) > 1:
1431
- query = " ".join(sys.argv[1:])
1631
+
1632
+ # Handle --mcp flag for stdio mode
1633
+ if args.mcp:
1634
+ logger.info("Starting MCP server in stdio mode (blocking, foreground)")
1635
+ print("🦆 Starting MCP stdio server...", file=sys.stderr)
1636
+
1637
+ # Don't auto-start HTTP/TCP/WS servers for stdio mode
1638
+ if devduck.agent:
1639
+ try:
1640
+ # Start MCP server in stdio mode - this BLOCKS until terminated
1641
+ devduck.agent.tool.mcp_server(
1642
+ action="start",
1643
+ transport="stdio",
1644
+ expose_agent=True,
1645
+ agent=devduck.agent,
1646
+ )
1647
+ except Exception as e:
1648
+ logger.error(f"Failed to start MCP stdio server: {e}")
1649
+ print(f"🦆 Error: {e}", file=sys.stderr)
1650
+ sys.exit(1)
1651
+ else:
1652
+ print("🦆 Agent not available", file=sys.stderr)
1653
+ sys.exit(1)
1654
+ return
1655
+
1656
+ if args.query:
1657
+ query = " ".join(args.query)
1432
1658
  logger.info(f"CLI query: {query}")
1433
1659
  result = ask(query)
1434
1660
  print(result)
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.2.0'
32
- __version_tuple__ = version_tuple = (0, 2, 0)
31
+ __version__ = version = '0.4.0'
32
+ __version_tuple__ = version_tuple = (0, 4, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None