devduck 0.3.0__py3-none-any.whl → 0.4.1__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
@@ -126,168 +126,6 @@ def get_own_source_code():
126
126
  except Exception as e:
127
127
  return f"Error reading own source code: {e}"
128
128
 
129
-
130
- # 🛠️ System prompt tool (with .prompt file persistence)
131
- def system_prompt_tool(
132
- action: str,
133
- prompt: str | None = None,
134
- context: str | None = None,
135
- variable_name: str = "SYSTEM_PROMPT",
136
- ) -> Dict[str, Any]:
137
- """
138
- Manage the agent's system prompt dynamically with file persistence.
139
-
140
- Args:
141
- action: "view", "update", "add_context", or "reset"
142
- prompt: New system prompt text (required for "update")
143
- context: Additional context to prepend (for "add_context")
144
- variable_name: Environment variable name (default: SYSTEM_PROMPT)
145
-
146
- Returns:
147
- Dict with status and content
148
- """
149
- from pathlib import Path
150
- import tempfile
151
-
152
- def _get_prompt_file_path() -> Path:
153
- """Get the .prompt file path in temp directory."""
154
- temp_dir = Path(tempfile.gettempdir()) / ".devduck"
155
- temp_dir.mkdir(exist_ok=True, mode=0o700) # Create with restrictive permissions
156
- return temp_dir / ".prompt"
157
-
158
- def _write_prompt_file(prompt_text: str) -> None:
159
- """Write prompt to .prompt file in temp directory."""
160
- prompt_file = _get_prompt_file_path()
161
- try:
162
- # Create file with restrictive permissions
163
- with open(
164
- prompt_file,
165
- "w",
166
- encoding="utf-8",
167
- opener=lambda path, flags: os.open(path, flags, 0o600),
168
- ) as f:
169
- f.write(prompt_text)
170
- except (OSError, PermissionError):
171
- try:
172
- prompt_file.write_text(prompt_text, encoding="utf-8")
173
- prompt_file.chmod(0o600)
174
- except (OSError, PermissionError):
175
- prompt_file.write_text(prompt_text, encoding="utf-8")
176
-
177
- def _get_system_prompt(var_name: str) -> str:
178
- """Get current system prompt from environment variable."""
179
- return os.environ.get(var_name, "")
180
-
181
- def _update_system_prompt(new_prompt: str, var_name: str) -> None:
182
- """Update system prompt in both environment and .prompt file."""
183
- os.environ[var_name] = new_prompt
184
- if var_name == "SYSTEM_PROMPT":
185
- _write_prompt_file(new_prompt)
186
-
187
- try:
188
- if action == "view":
189
- current = _get_system_prompt(variable_name)
190
- return {
191
- "status": "success",
192
- "content": [
193
- {"text": f"Current system prompt from {variable_name}:{current}"}
194
- ],
195
- }
196
-
197
- elif action == "update":
198
- if not prompt:
199
- return {
200
- "status": "error",
201
- "content": [
202
- {"text": "Error: prompt parameter required for update action"}
203
- ],
204
- }
205
-
206
- _update_system_prompt(prompt, variable_name)
207
-
208
- if variable_name == "SYSTEM_PROMPT":
209
- message = f"System prompt updated (env: {variable_name}, file: .prompt)"
210
- else:
211
- message = f"System prompt updated (env: {variable_name})"
212
-
213
- return {"status": "success", "content": [{"text": message}]}
214
-
215
- elif action == "add_context":
216
- if not context:
217
- return {
218
- "status": "error",
219
- "content": [
220
- {
221
- "text": "Error: context parameter required for add_context action"
222
- }
223
- ],
224
- }
225
-
226
- current = _get_system_prompt(variable_name)
227
- new_prompt = f"{current} {context}" if current else context
228
- _update_system_prompt(new_prompt, variable_name)
229
-
230
- if variable_name == "SYSTEM_PROMPT":
231
- message = f"Context added to system prompt (env: {variable_name}, file: .prompt)"
232
- else:
233
- message = f"Context added to system prompt (env: {variable_name})"
234
-
235
- return {"status": "success", "content": [{"text": message}]}
236
-
237
- elif action == "reset":
238
- os.environ.pop(variable_name, None)
239
-
240
- if variable_name == "SYSTEM_PROMPT":
241
- prompt_file = _get_prompt_file_path()
242
- if prompt_file.exists():
243
- try:
244
- prompt_file.unlink()
245
- except (OSError, PermissionError):
246
- pass
247
- message = (
248
- f"System prompt reset (env: {variable_name}, file: .prompt cleared)"
249
- )
250
- else:
251
- message = f"System prompt reset (env: {variable_name})"
252
-
253
- return {"status": "success", "content": [{"text": message}]}
254
-
255
- elif action == "get":
256
- # Backward compatibility
257
- current = _get_system_prompt(variable_name)
258
- return {
259
- "status": "success",
260
- "content": [{"text": f"System prompt: {current}"}],
261
- }
262
-
263
- elif action == "set":
264
- # Backward compatibility
265
- if prompt is None:
266
- return {"status": "error", "content": [{"text": "No prompt provided"}]}
267
-
268
- if context:
269
- prompt = f"{context} {prompt}"
270
-
271
- _update_system_prompt(prompt, variable_name)
272
- return {
273
- "status": "success",
274
- "content": [{"text": "System prompt updated successfully"}],
275
- }
276
-
277
- else:
278
- return {
279
- "status": "error",
280
- "content": [
281
- {
282
- "text": f"Unknown action '{action}'. Valid: view, update, add_context, reset"
283
- }
284
- ],
285
- }
286
-
287
- except Exception as e:
288
- return {"status": "error", "content": [{"text": f"Error: {str(e)}"}]}
289
-
290
-
291
129
  def view_logs_tool(
292
130
  action: str = "view",
293
131
  lines: int = 100,
@@ -661,6 +499,7 @@ class DevDuck:
661
499
  use_github,
662
500
  create_subagent,
663
501
  store_in_kb,
502
+ system_prompt,
664
503
  )
665
504
 
666
505
  core_tools.extend(
@@ -672,34 +511,39 @@ class DevDuck:
672
511
  use_github,
673
512
  create_subagent,
674
513
  store_in_kb,
514
+ system_prompt
675
515
  ]
676
516
  )
677
517
  except ImportError as e:
678
518
  logger.warning(f"devduck.tools import failed: {e}")
679
519
 
680
- try:
681
- from strands_fun_tools import (
682
- listen,
683
- cursor,
684
- clipboard,
685
- screen_reader,
686
- yolo_vision,
687
- )
520
+ # Skip fun tools in --mcp mode (they're not needed for MCP server)
521
+ if "--mcp" not in sys.argv:
522
+ try:
523
+ from strands_fun_tools import (
524
+ listen,
525
+ cursor,
526
+ clipboard,
527
+ screen_reader,
528
+ yolo_vision,
529
+ )
688
530
 
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
- )
531
+ core_tools.extend(
532
+ [listen, cursor, clipboard, screen_reader, yolo_vision]
533
+ )
534
+ except ImportError:
535
+ logger.info(
536
+ "strands-fun-tools not installed - vision/audio tools unavailable (install with: pip install devduck[all])"
537
+ )
538
+ else:
539
+ logger.info("--mcp mode: skipping vision/audio tools")
696
540
 
697
541
  try:
698
542
  from strands_tools import (
699
543
  shell,
700
544
  editor,
701
545
  calculator,
702
- python_repl,
546
+ # python_repl,
703
547
  image_reader,
704
548
  use_agent,
705
549
  load_tool,
@@ -713,7 +557,7 @@ class DevDuck:
713
557
  shell,
714
558
  editor,
715
559
  calculator,
716
- python_repl,
560
+ # python_repl,
717
561
  image_reader,
718
562
  use_agent,
719
563
  load_tool,
@@ -727,17 +571,6 @@ class DevDuck:
727
571
  "strands-agents-tools not installed - core tools unavailable (install with: pip install devduck[all])"
728
572
  )
729
573
 
730
- # Wrap system_prompt_tool with @tool decorator
731
- @tool
732
- def system_prompt(
733
- action: str,
734
- prompt: str = None,
735
- context: str = None,
736
- variable_name: str = "SYSTEM_PROMPT",
737
- ) -> Dict[str, Any]:
738
- """Manage agent system prompt dynamically."""
739
- return system_prompt_tool(action, prompt, context, variable_name)
740
-
741
574
  # Wrap view_logs_tool with @tool decorator
742
575
  @tool
743
576
  def view_logs(
@@ -749,7 +582,7 @@ class DevDuck:
749
582
  return view_logs_tool(action, lines, pattern)
750
583
 
751
584
  # Add built-in tools to the toolset
752
- core_tools.extend([system_prompt, view_logs])
585
+ core_tools.extend([view_logs])
753
586
 
754
587
  # Assign tools
755
588
  self.tools = core_tools
@@ -976,9 +809,20 @@ def weather(action: str, location: str = None) -> Dict[str, Any]:
976
809
  ```
977
810
 
978
811
  ## System Prompt Management:
979
- - Use system_prompt(action='get') to view current prompt
980
- - Use system_prompt(action='set', prompt='new text') to update
981
- - Changes persist in SYSTEM_PROMPT environment variable
812
+ - **View**: system_prompt(action='view') - See current prompt
813
+ - **Update Local**: system_prompt(action='update', prompt='new text') - Updates env var + .prompt file
814
+ - **Update GitHub**: system_prompt(action='update', prompt='text', repository='cagataycali/devduck') - Syncs to repo variables
815
+ - **Variable Name**: system_prompt(action='update', prompt='text', variable_name='CUSTOM_PROMPT') - Use custom var
816
+ - **Add Context**: system_prompt(action='add_context', context='new learning') - Append without replacing
817
+
818
+ ### 🧠 Self-Improvement Pattern:
819
+ When you learn something valuable during conversations:
820
+ 1. Identify the new insight or pattern
821
+ 2. Use system_prompt(action='add_context', context='...') to append it
822
+ 3. Sync to GitHub: system_prompt(action='update', prompt=new_full_prompt, repository='owner/repo')
823
+ 4. New learnings persist across sessions via SYSTEM_PROMPT env var
824
+
825
+ **Repository Integration**: Set repository='cagataycali/devduck' to sync prompts across deployments
982
826
 
983
827
  ## Shell Commands:
984
828
  - Prefix with ! to execute shell commands directly
@@ -1267,7 +1111,13 @@ def weather(action: str, location: str = None) -> Dict[str, Any]:
1267
1111
 
1268
1112
  # 🦆 Auto-initialize when imported
1269
1113
  # Check environment variables to control server configuration
1114
+ # Also check if --mcp flag is present to skip auto-starting servers
1270
1115
  _auto_start = os.getenv("DEVDUCK_AUTO_START_SERVERS", "true").lower() == "true"
1116
+
1117
+ # Disable auto-start if --mcp flag is present (stdio mode)
1118
+ if "--mcp" in sys.argv:
1119
+ _auto_start = False
1120
+
1271
1121
  _tcp_port = int(os.getenv("DEVDUCK_TCP_PORT", "9999"))
1272
1122
  _ws_port = int(os.getenv("DEVDUCK_WS_PORT", "8080"))
1273
1123
  _mcp_port = int(os.getenv("DEVDUCK_MCP_PORT", "8000"))
@@ -1510,9 +1360,20 @@ You have full access to your own source code for self-awareness and self-modific
1510
1360
  - Full bidirectional communication
1511
1361
 
1512
1362
  ## System Prompt Management:
1513
- - Use system_prompt(action='get') to view current prompt
1514
- - Use system_prompt(action='set', prompt='new text') to update
1515
- - Changes persist in SYSTEM_PROMPT environment variable
1363
+ - **View**: system_prompt(action='view') - See current prompt
1364
+ - **Update Local**: system_prompt(action='update', prompt='new text') - Updates env var + .prompt file
1365
+ - **Update GitHub**: system_prompt(action='update', prompt='text', repository='cagataycali/devduck') - Syncs to repo variables
1366
+ - **Variable Name**: system_prompt(action='update', prompt='text', variable_name='CUSTOM_PROMPT') - Use custom var
1367
+ - **Add Context**: system_prompt(action='add_context', context='new learning') - Append without replacing
1368
+
1369
+ ### 🧠 Self-Improvement Pattern:
1370
+ When you learn something valuable during conversations:
1371
+ 1. Identify the new insight or pattern
1372
+ 2. Use system_prompt(action='add_context', context='...') to append it
1373
+ 3. Optionally sync to GitHub: system_prompt(action='update', prompt=new_full_prompt, repository='owner/repo')
1374
+ 4. New learnings persist across sessions via SYSTEM_PROMPT env var
1375
+
1376
+ **Repository Integration**: Set repository='cagataycali/devduck' to sync prompts across deployments
1516
1377
 
1517
1378
  ## Shell Commands:
1518
1379
  - Prefix with ! to execute shell commands directly
@@ -1561,15 +1422,33 @@ def cli():
1561
1422
  Examples:
1562
1423
  devduck # Start interactive mode
1563
1424
  devduck "your query here" # One-shot query
1425
+ devduck --mcp # MCP stdio mode (for Claude Desktop)
1564
1426
  devduck --tcp-port 9000 # Custom TCP port
1565
1427
  devduck --no-tcp --no-ws # Disable TCP and WebSocket
1566
1428
  devduck --mcp-port 3000 # Custom MCP port
1429
+
1430
+ Claude Desktop Config:
1431
+ {
1432
+ "mcpServers": {
1433
+ "devduck": {
1434
+ "command": "uvx",
1435
+ "args": ["devduck", "--mcp"]
1436
+ }
1437
+ }
1438
+ }
1567
1439
  """,
1568
1440
  )
1569
1441
 
1570
1442
  # Query argument
1571
1443
  parser.add_argument("query", nargs="*", help="Query to send to the agent")
1572
1444
 
1445
+ # MCP stdio mode flag
1446
+ parser.add_argument(
1447
+ "--mcp",
1448
+ action="store_true",
1449
+ help="Start MCP server in stdio mode (for Claude Desktop integration)",
1450
+ )
1451
+
1573
1452
  # Server configuration
1574
1453
  parser.add_argument(
1575
1454
  "--tcp-port", type=int, default=9999, help="TCP server port (default: 9999)"
@@ -1601,6 +1480,30 @@ Examples:
1601
1480
 
1602
1481
  logger.info("CLI mode started")
1603
1482
 
1483
+ # Handle --mcp flag for stdio mode
1484
+ if args.mcp:
1485
+ logger.info("Starting MCP server in stdio mode (blocking, foreground)")
1486
+ print("🦆 Starting MCP stdio server...", file=sys.stderr)
1487
+
1488
+ # Don't auto-start HTTP/TCP/WS servers for stdio mode
1489
+ if devduck.agent:
1490
+ try:
1491
+ # Start MCP server in stdio mode - this BLOCKS until terminated
1492
+ devduck.agent.tool.mcp_server(
1493
+ action="start",
1494
+ transport="stdio",
1495
+ expose_agent=True,
1496
+ agent=devduck.agent,
1497
+ )
1498
+ except Exception as e:
1499
+ logger.error(f"Failed to start MCP stdio server: {e}")
1500
+ print(f"🦆 Error: {e}", file=sys.stderr)
1501
+ sys.exit(1)
1502
+ else:
1503
+ print("🦆 Agent not available", file=sys.stderr)
1504
+ sys.exit(1)
1505
+ return
1506
+
1604
1507
  if args.query:
1605
1508
  query = " ".join(args.query)
1606
1509
  logger.info(f"CLI query: {query}")
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.3.0'
32
- __version_tuple__ = version_tuple = (0, 3, 0)
31
+ __version__ = version = '0.4.1'
32
+ __version_tuple__ = version_tuple = (0, 4, 1)
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -190,9 +190,11 @@ def _start_mcp_server(
190
190
  agent_invoke_tool = types.Tool(
191
191
  name="devduck",
192
192
  description=(
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."
193
+ "Invoke a FULL DevDuck instance with complete capabilities. "
194
+ "Each invocation creates a fresh DevDuck agent with self-healing, "
195
+ "hot-reload, all tools, knowledge base integration, and system prompt building. "
196
+ "Use this for complex queries requiring reasoning, multi-tool orchestration, "
197
+ "or when you need the complete DevDuck experience via MCP."
196
198
  ),
197
199
  inputSchema={
198
200
  "type": "object",
@@ -228,7 +230,7 @@ def _start_mcp_server(
228
230
  try:
229
231
  logger.debug(f"call_tool: name={name}, arguments={arguments}")
230
232
 
231
- # Handle agent invocation tool - use the devduck instance directly
233
+ # Handle agent invocation tool - create a full DevDuck instance
232
234
  if name == "devduck" and expose_agent:
233
235
  prompt = arguments.get("prompt")
234
236
  if not prompt:
@@ -241,8 +243,34 @@ def _start_mcp_server(
241
243
 
242
244
  logger.debug(f"Invoking devduck with prompt: {prompt[:100]}...")
243
245
 
244
- # Use the devduck agent directly (don't create a new instance)
245
- result = agent(prompt)
246
+ # Create a NEW DevDuck instance for this MCP invocation
247
+ # This gives full DevDuck power: self-healing, hot-reload, all tools, etc.
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
+ ]
246
274
 
247
275
  # Extract text response from agent result
248
276
  response_text = str(result)