devduck 0.2.0__py3-none-any.whl → 0.3.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,101 @@ 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
+ try:
681
+ from strands_fun_tools import (
682
+ listen,
683
+ cursor,
684
+ clipboard,
685
+ screen_reader,
686
+ yolo_vision,
687
+ )
688
+
689
+ core_tools.extend(
690
+ [listen, cursor, clipboard, screen_reader, yolo_vision]
691
+ )
692
+ except ImportError:
693
+ logger.info(
694
+ "strands-fun-tools not installed - vision/audio tools unavailable (install with: pip install devduck[all])"
695
+ )
696
+
697
+ try:
698
+ from strands_tools import (
699
+ shell,
700
+ editor,
701
+ calculator,
702
+ python_repl,
703
+ image_reader,
704
+ use_agent,
705
+ load_tool,
706
+ environment,
707
+ mcp_client,
708
+ retrieve,
709
+ )
710
+
711
+ core_tools.extend(
712
+ [
713
+ shell,
714
+ editor,
715
+ calculator,
716
+ python_repl,
717
+ image_reader,
718
+ use_agent,
719
+ load_tool,
720
+ environment,
721
+ mcp_client,
722
+ retrieve,
723
+ ]
724
+ )
725
+ except ImportError:
726
+ logger.info(
727
+ "strands-agents-tools not installed - core tools unavailable (install with: pip install devduck[all])"
728
+ )
646
729
 
647
730
  # Wrap system_prompt_tool with @tool decorator
648
731
  @tool
@@ -665,39 +748,21 @@ class DevDuck:
665
748
  """View and manage DevDuck logs."""
666
749
  return view_logs_tool(action, lines, pattern)
667
750
 
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
- ]
751
+ # Add built-in tools to the toolset
752
+ core_tools.extend([system_prompt, view_logs])
753
+
754
+ # Assign tools
755
+ self.tools = core_tools
691
756
 
692
757
  logger.info(f"Initialized {len(self.tools)} tools")
693
758
 
694
759
  # Check if MODEL_PROVIDER env variable is set
695
760
  model_provider = os.getenv("MODEL_PROVIDER")
696
761
 
697
- if model_provider:
762
+ if model_provider and create_model:
698
763
  # Use create_model utility for any provider (bedrock, anthropic, etc.)
699
764
  self.agent_model = create_model(provider=model_provider)
700
- else:
765
+ elif OllamaModel:
701
766
  # Fallback to default Ollama behavior
702
767
  self.agent_model = OllamaModel(
703
768
  host=self.ollama_host,
@@ -705,6 +770,10 @@ class DevDuck:
705
770
  temperature=1,
706
771
  keep_alive="5m",
707
772
  )
773
+ else:
774
+ raise ImportError(
775
+ "No model provider available. Install with: pip install devduck[all]"
776
+ )
708
777
 
709
778
  # Create agent with self-healing
710
779
  self.agent = Agent(
@@ -714,54 +783,59 @@ class DevDuck:
714
783
  load_tools_from_directory=True,
715
784
  )
716
785
 
717
- # 🚀 AUTO-START SERVERS: TCP (9999), WebSocket (8080), MCP HTTP (8000)
786
+ # 🚀 AUTO-START SERVERS: TCP, WebSocket, MCP HTTP
718
787
  if auto_start_servers:
719
788
  logger.info("Auto-starting servers...")
720
789
  print("🦆 Auto-starting servers...")
721
790
 
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}")
791
+ if enable_tcp:
792
+ try:
793
+ # Start TCP server on configurable port
794
+ tcp_result = self.agent.tool.tcp(
795
+ action="start_server", port=tcp_port
796
+ )
797
+ if tcp_result.get("status") == "success":
798
+ logger.info(f"TCP server started on port {tcp_port}")
799
+ print(f"🦆 TCP server: localhost:{tcp_port}")
800
+ else:
801
+ logger.warning(f"TCP server start issue: {tcp_result}")
802
+ except Exception as e:
803
+ logger.error(f"Failed to start TCP server: {e}")
804
+ print(f"🦆 ⚠ TCP server failed: {e}")
733
805
 
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}")
806
+ if enable_ws:
807
+ try:
808
+ # Start WebSocket server on configurable port
809
+ ws_result = self.agent.tool.websocket(
810
+ action="start_server", port=ws_port
811
+ )
812
+ if ws_result.get("status") == "success":
813
+ logger.info(f"✓ WebSocket server started on port {ws_port}")
814
+ print(f"🦆 ✓ WebSocket server: localhost:{ws_port}")
815
+ else:
816
+ logger.warning(f"WebSocket server start issue: {ws_result}")
817
+ except Exception as e:
818
+ logger.error(f"Failed to start WebSocket server: {e}")
819
+ print(f"🦆 ⚠ WebSocket server failed: {e}")
747
820
 
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}")
821
+ if enable_mcp:
822
+ try:
823
+ # Start MCP server with HTTP transport on configurable port
824
+ mcp_result = self.agent.tool.mcp_server(
825
+ action="start",
826
+ transport="http",
827
+ port=mcp_port,
828
+ expose_agent=True,
829
+ agent=self.agent,
830
+ )
831
+ if mcp_result.get("status") == "success":
832
+ logger.info(f"✓ MCP HTTP server started on port {mcp_port}")
833
+ print(f"🦆 ✓ MCP server: http://localhost:{mcp_port}/mcp")
834
+ else:
835
+ logger.warning(f"MCP server start issue: {mcp_result}")
836
+ except Exception as e:
837
+ logger.error(f"Failed to start MCP server: {e}")
838
+ print(f"🦆 ⚠ MCP server failed: {e}")
765
839
 
766
840
  # Start file watcher for auto hot-reload
767
841
  self._start_file_watcher()
@@ -847,6 +921,12 @@ You have full access to your own source code for self-awareness and self-modific
847
921
  - Connect from Claude Desktop, other agents, or custom clients
848
922
  - Full bidirectional communication
849
923
 
924
+ ## Knowledge Base Integration:
925
+ - **Automatic RAG** - Set STRANDS_KNOWLEDGE_BASE_ID to enable automatic retrieval/storage
926
+ - Before each query: Retrieves relevant context from knowledge base
927
+ - After each response: Stores conversation for future reference
928
+ - Seamless memory across sessions without manual tool calls
929
+
850
930
  ## Tool Creation Patterns:
851
931
 
852
932
  ### **1. @tool Decorator:**
@@ -996,7 +1076,7 @@ def weather(action: str, location: str = None) -> Dict[str, Any]:
996
1076
  self.agent = None
997
1077
 
998
1078
  def __call__(self, query):
999
- """Make the agent callable"""
1079
+ """Make the agent callable with automatic knowledge base integration"""
1000
1080
  if not self.agent:
1001
1081
  logger.warning("Agent unavailable - attempted to call with query")
1002
1082
  return "🦆 Agent unavailable - try: devduck.restart()"
@@ -1006,8 +1086,37 @@ def weather(action: str, location: str = None) -> Dict[str, Any]:
1006
1086
  # Mark agent as executing to prevent hot-reload interruption
1007
1087
  self._agent_executing = True
1008
1088
 
1089
+ # 📚 Knowledge Base Retrieval (BEFORE agent runs)
1090
+ knowledge_base_id = os.getenv("STRANDS_KNOWLEDGE_BASE_ID")
1091
+ if knowledge_base_id and hasattr(self.agent, "tool"):
1092
+ try:
1093
+ if "retrieve" in self.agent.tool_names:
1094
+ logger.info(f"Retrieving context from KB: {knowledge_base_id}")
1095
+ self.agent.tool.retrieve(
1096
+ text=query, knowledgeBaseId=knowledge_base_id
1097
+ )
1098
+ except Exception as e:
1099
+ logger.warning(f"KB retrieval failed: {e}")
1100
+
1101
+ # Run the agent
1009
1102
  result = self.agent(query)
1010
1103
 
1104
+ # 💾 Knowledge Base Storage (AFTER agent runs)
1105
+ if knowledge_base_id and hasattr(self.agent, "tool"):
1106
+ try:
1107
+ if "store_in_kb" in self.agent.tool_names:
1108
+
1109
+ conversation_content = f"Input: {query}, Result: {result!s}"
1110
+ conversation_title = f"DevDuck: {datetime.now().strftime('%Y-%m-%d')} | {query[:500]}"
1111
+ self.agent.tool.store_in_kb(
1112
+ content=conversation_content,
1113
+ title=conversation_title,
1114
+ knowledge_base_id=knowledge_base_id,
1115
+ )
1116
+ logger.info(f"Stored conversation in KB: {knowledge_base_id}")
1117
+ except Exception as e:
1118
+ logger.warning(f"KB storage failed: {e}")
1119
+
1011
1120
  # Agent finished - check if reload was pending
1012
1121
  self._agent_executing = False
1013
1122
  logger.info("Agent call completed successfully")
@@ -1157,7 +1266,24 @@ def weather(action: str, location: str = None) -> Dict[str, Any]:
1157
1266
 
1158
1267
 
1159
1268
  # 🦆 Auto-initialize when imported
1160
- devduck = DevDuck()
1269
+ # Check environment variables to control server configuration
1270
+ _auto_start = os.getenv("DEVDUCK_AUTO_START_SERVERS", "true").lower() == "true"
1271
+ _tcp_port = int(os.getenv("DEVDUCK_TCP_PORT", "9999"))
1272
+ _ws_port = int(os.getenv("DEVDUCK_WS_PORT", "8080"))
1273
+ _mcp_port = int(os.getenv("DEVDUCK_MCP_PORT", "8000"))
1274
+ _enable_tcp = os.getenv("DEVDUCK_ENABLE_TCP", "true").lower() == "true"
1275
+ _enable_ws = os.getenv("DEVDUCK_ENABLE_WS", "true").lower() == "true"
1276
+ _enable_mcp = os.getenv("DEVDUCK_ENABLE_MCP", "true").lower() == "true"
1277
+
1278
+ devduck = DevDuck(
1279
+ auto_start_servers=_auto_start,
1280
+ tcp_port=_tcp_port,
1281
+ ws_port=_ws_port,
1282
+ mcp_port=_mcp_port,
1283
+ enable_tcp=_enable_tcp,
1284
+ enable_ws=_enable_ws,
1285
+ enable_mcp=_enable_mcp,
1286
+ )
1161
1287
 
1162
1288
 
1163
1289
  # 🚀 Convenience functions
@@ -1426,9 +1552,57 @@ You have full access to your own source code for self-awareness and self-modific
1426
1552
 
1427
1553
  def cli():
1428
1554
  """CLI entry point for pip-installed devduck command"""
1555
+ import argparse
1556
+
1557
+ parser = argparse.ArgumentParser(
1558
+ description="🦆 DevDuck - Extreme minimalist self-adapting agent",
1559
+ formatter_class=argparse.RawDescriptionHelpFormatter,
1560
+ epilog="""
1561
+ Examples:
1562
+ devduck # Start interactive mode
1563
+ devduck "your query here" # One-shot query
1564
+ devduck --tcp-port 9000 # Custom TCP port
1565
+ devduck --no-tcp --no-ws # Disable TCP and WebSocket
1566
+ devduck --mcp-port 3000 # Custom MCP port
1567
+ """,
1568
+ )
1569
+
1570
+ # Query argument
1571
+ parser.add_argument("query", nargs="*", help="Query to send to the agent")
1572
+
1573
+ # Server configuration
1574
+ parser.add_argument(
1575
+ "--tcp-port", type=int, default=9999, help="TCP server port (default: 9999)"
1576
+ )
1577
+ parser.add_argument(
1578
+ "--ws-port",
1579
+ type=int,
1580
+ default=8080,
1581
+ help="WebSocket server port (default: 8080)",
1582
+ )
1583
+ parser.add_argument(
1584
+ "--mcp-port",
1585
+ type=int,
1586
+ default=8000,
1587
+ help="MCP HTTP server port (default: 8000)",
1588
+ )
1589
+
1590
+ # Server enable/disable flags
1591
+ parser.add_argument("--no-tcp", action="store_true", help="Disable TCP server")
1592
+ parser.add_argument("--no-ws", action="store_true", help="Disable WebSocket server")
1593
+ parser.add_argument("--no-mcp", action="store_true", help="Disable MCP server")
1594
+ parser.add_argument(
1595
+ "--no-servers",
1596
+ action="store_true",
1597
+ help="Disable all servers (no TCP, WebSocket, or MCP)",
1598
+ )
1599
+
1600
+ args = parser.parse_args()
1601
+
1429
1602
  logger.info("CLI mode started")
1430
- if len(sys.argv) > 1:
1431
- query = " ".join(sys.argv[1:])
1603
+
1604
+ if args.query:
1605
+ query = " ".join(args.query)
1432
1606
  logger.info(f"CLI query: {query}")
1433
1607
  result = ask(query)
1434
1608
  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.3.0'
32
+ __version_tuple__ = version_tuple = (0, 3, 0)
33
33
 
34
34
  __commit_id__ = commit_id = None