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.

Files changed (36) hide show
  1. devduck/__init__.py +591 -1092
  2. devduck/_version.py +2 -2
  3. devduck/install.sh +42 -0
  4. devduck/test_redduck.py +1 -0
  5. devduck/tools/__init__.py +4 -44
  6. devduck/tools/install_tools.py +2 -103
  7. devduck/tools/mcp_server.py +6 -34
  8. devduck/tools/tcp.py +7 -6
  9. devduck/tools/websocket.py +2 -8
  10. devduck-0.2.0.dist-info/METADATA +143 -0
  11. devduck-0.2.0.dist-info/RECORD +16 -0
  12. {devduck-0.1.1766644714.dist-info → devduck-0.2.0.dist-info}/entry_points.txt +0 -1
  13. devduck-0.2.0.dist-info/licenses/LICENSE +21 -0
  14. devduck/agentcore_handler.py +0 -76
  15. devduck/tools/_ambient_input.py +0 -423
  16. devduck/tools/_tray_app.py +0 -530
  17. devduck/tools/agentcore_agents.py +0 -197
  18. devduck/tools/agentcore_config.py +0 -441
  19. devduck/tools/agentcore_invoke.py +0 -423
  20. devduck/tools/agentcore_logs.py +0 -320
  21. devduck/tools/ambient.py +0 -157
  22. devduck/tools/create_subagent.py +0 -659
  23. devduck/tools/fetch_github_tool.py +0 -201
  24. devduck/tools/ipc.py +0 -546
  25. devduck/tools/scraper.py +0 -935
  26. devduck/tools/speech_to_speech.py +0 -850
  27. devduck/tools/state_manager.py +0 -292
  28. devduck/tools/store_in_kb.py +0 -187
  29. devduck/tools/system_prompt.py +0 -608
  30. devduck/tools/tray.py +0 -247
  31. devduck/tools/use_github.py +0 -438
  32. devduck-0.1.1766644714.dist-info/METADATA +0 -717
  33. devduck-0.1.1766644714.dist-info/RECORD +0 -33
  34. devduck-0.1.1766644714.dist-info/licenses/LICENSE +0 -201
  35. {devduck-0.1.1766644714.dist-info → devduck-0.2.0.dist-info}/WHEEL +0 -0
  36. {devduck-0.1.1766644714.dist-info → devduck-0.2.0.dist-info}/top_level.txt +0 -0
@@ -1,530 +0,0 @@
1
- """
2
- DevDuck Tray - Modern tray app with server controls & agent capabilities
3
- """
4
-
5
- import rumps
6
- import socket
7
- import threading
8
- import json
9
- import os
10
- import tempfile
11
- import webbrowser
12
- from queue import Queue
13
- from datetime import datetime
14
- from pathlib import Path
15
- import uuid
16
-
17
-
18
- class DevDuckTray(rumps.App):
19
- def __init__(self):
20
- super().__init__("🦆", quit_button=None)
21
-
22
- # State
23
- self.command_queue = Queue()
24
- self.command_responses = {}
25
- self.ui_update_queue = Queue()
26
- self.state = {
27
- "status": "idle",
28
- "tcp_enabled": True,
29
- "ws_enabled": True,
30
- "mcp_enabled": True,
31
- }
32
-
33
- self.base_icon = "🦆"
34
-
35
- # Multi-stream state
36
- self.active_streams = {}
37
- self.recent_results = []
38
- self.max_recent = 5
39
-
40
- # Persistent menu items
41
- self.saved_menu_items = []
42
-
43
- # Build initial menu
44
- self._build_menu()
45
-
46
- # Start IPC listener
47
- self.socket_path = os.path.join(tempfile.gettempdir(), "devduck_tray.sock")
48
- self._start_ipc_listener()
49
-
50
- # Start command processor
51
- self.command_timer = rumps.Timer(self._process_commands, 0.1)
52
- self.command_timer.start()
53
-
54
- # Start UI update processor (main thread only)
55
- self.ui_timer = rumps.Timer(self._process_ui_updates, 0.2)
56
- self.ui_timer.start()
57
-
58
- # Import devduck
59
- try:
60
- os.environ["DEVDUCK_AUTO_START_SERVERS"] = "false"
61
- import devduck
62
-
63
- self.devduck = devduck
64
- except ImportError:
65
- self.devduck = None
66
-
67
- def _process_ui_updates(self, _):
68
- """Process UI updates on main thread only"""
69
- updated = False
70
- while not self.ui_update_queue.empty():
71
- update_type = self.ui_update_queue.get()
72
- if update_type == "menu":
73
- updated = True
74
-
75
- if updated:
76
- self._build_menu()
77
-
78
- def _request_menu_update(self):
79
- """Request menu update (thread-safe)"""
80
- self.ui_update_queue.put("menu")
81
-
82
- def _build_menu(self):
83
- """Build menu with server controls and agent capabilities"""
84
- self.menu.clear()
85
-
86
- # Test Button - First item for easy testing
87
- self.menu.add(
88
- rumps.MenuItem(
89
- "🧪 Test Agent", callback=self._create_callback("what time is it?")
90
- )
91
- )
92
- self.menu.add(rumps.separator)
93
-
94
- # Active Streams Section
95
- if self.active_streams:
96
- self.menu.add(rumps.MenuItem("🌊 Active Streams", callback=None))
97
- for stream_id, stream_data in list(self.active_streams.items()):
98
- icon = {
99
- "thinking": "🤔",
100
- "processing": "💡",
101
- "complete": "✅",
102
- "error": "❌",
103
- }.get(stream_data["status"], "💭")
104
- query_short = (
105
- stream_data["query"][:30] + "..."
106
- if len(stream_data["query"]) > 30
107
- else stream_data["query"]
108
- )
109
- text_short = (
110
- stream_data["text"][:40] + "..."
111
- if len(stream_data["text"]) > 40
112
- else stream_data["text"]
113
- )
114
- menu_text = f" {icon} {query_short}: {text_short}"
115
- self.menu.add(rumps.MenuItem(menu_text, callback=None))
116
- self.menu.add(rumps.separator)
117
-
118
- # User-defined menu items
119
- if self.saved_menu_items:
120
- for item in self.saved_menu_items:
121
- if item.get("type") == "separator":
122
- self.menu.add(rumps.separator)
123
- else:
124
- label = item.get("title") or item.get("label", "Item")
125
- query = item.get("query") or item.get("action", label)
126
- self.menu.add(
127
- rumps.MenuItem(label, callback=self._create_callback(query))
128
- )
129
- self.menu.add(rumps.separator)
130
-
131
- # Recent Results Section
132
- if self.recent_results:
133
- self.menu.add(rumps.MenuItem("📝 Recent Results", callback=None))
134
- for query, result, timestamp in self.recent_results[: self.max_recent]:
135
- query_short = query[:25] + "..." if len(query) > 25 else query
136
- result_short = result[:35] + "..." if len(result) > 35 else result
137
- time_str = timestamp.strftime("%H:%M")
138
- menu_text = f" [{time_str}] {query_short} → {result_short}"
139
- self.menu.add(
140
- rumps.MenuItem(
141
- menu_text,
142
- callback=self._create_show_result_callback(query, result),
143
- )
144
- )
145
- self.menu.add(rumps.separator)
146
-
147
- # Status
148
- self.menu.add(rumps.MenuItem(f"Status: {self.state['status']}", callback=None))
149
- self.menu.add(rumps.separator)
150
-
151
- # Server controls
152
- self.menu.add(rumps.MenuItem("🌐 Servers", callback=None))
153
-
154
- tcp_status = "✅" if self.state["tcp_enabled"] else "❌"
155
- self.menu.add(
156
- rumps.MenuItem(f" {tcp_status} TCP (9999)", callback=self.toggle_tcp)
157
- )
158
-
159
- ws_status = "✅" if self.state["ws_enabled"] else "❌"
160
- self.menu.add(
161
- rumps.MenuItem(f" {ws_status} WebSocket (8080)", callback=self.toggle_ws)
162
- )
163
-
164
- mcp_status = "✅" if self.state["mcp_enabled"] else "❌"
165
- self.menu.add(
166
- rumps.MenuItem(f" {mcp_status} MCP (8000)", callback=self.toggle_mcp)
167
- )
168
-
169
- self.menu.add(rumps.separator)
170
-
171
- # Agent Capabilities
172
- self.menu.add(rumps.MenuItem("🤖 Agent Capabilities", callback=None))
173
- self.menu.add(
174
- rumps.MenuItem(
175
- " 👂 Start Clipboard Listening",
176
- callback=self._create_callback(
177
- "start clipboard monitoring in background"
178
- ),
179
- )
180
- )
181
- self.menu.add(
182
- rumps.MenuItem(
183
- " 🎤 Start Background Listening",
184
- callback=self._create_callback("start background audio listening"),
185
- )
186
- )
187
- self.menu.add(
188
- rumps.MenuItem(
189
- " 📺 Start Screen Reader",
190
- callback=self._create_callback("start screen reader monitoring"),
191
- )
192
- )
193
- self.menu.add(
194
- rumps.MenuItem(
195
- " 👁️ Start YOLO Vision",
196
- callback=self._create_callback("start yolo vision detection"),
197
- )
198
- )
199
-
200
- self.menu.add(rumps.separator)
201
-
202
- # Actions
203
- self.menu.add(rumps.MenuItem("💻 Show Input", callback=self.show_input))
204
- self.menu.add(rumps.MenuItem("🌐 Web Dashboard", callback=self.open_dashboard))
205
-
206
- self.menu.add(rumps.separator)
207
- self.menu.add(rumps.MenuItem("Quit", callback=self.quit_app))
208
-
209
- def _create_show_result_callback(self, query, result):
210
- """Create callback to show full result in notification"""
211
-
212
- def callback(sender):
213
- rumps.notification("DevDuck Result", query, result)
214
-
215
- return callback
216
-
217
- def toggle_tcp(self, sender):
218
- """Toggle TCP server"""
219
- if self.devduck and hasattr(self.devduck.devduck.agent, "tool"):
220
- try:
221
- action = "stop_server" if self.state["tcp_enabled"] else "start_server"
222
- self.devduck.devduck.agent.tool.tcp(action=action, port=9999)
223
- self.state["tcp_enabled"] = not self.state["tcp_enabled"]
224
- self._request_menu_update()
225
- rumps.notification(
226
- "DevDuck",
227
- "",
228
- f"TCP: {'ON' if self.state['tcp_enabled'] else 'OFF'}",
229
- )
230
- except Exception as e:
231
- rumps.notification("DevDuck Error", "", str(e))
232
-
233
- def toggle_ws(self, sender):
234
- """Toggle WebSocket server"""
235
- if self.devduck and hasattr(self.devduck.devduck.agent, "tool"):
236
- try:
237
- action = "stop_server" if self.state["ws_enabled"] else "start_server"
238
- self.devduck.devduck.agent.tool.websocket(action=action, port=8080)
239
- self.state["ws_enabled"] = not self.state["ws_enabled"]
240
- self._request_menu_update()
241
- rumps.notification(
242
- "DevDuck",
243
- "",
244
- f"WebSocket: {'ON' if self.state['ws_enabled'] else 'OFF'}",
245
- )
246
- except Exception as e:
247
- rumps.notification("DevDuck Error", "", str(e))
248
-
249
- def toggle_mcp(self, sender):
250
- """Toggle MCP server"""
251
- if self.devduck and hasattr(self.devduck.devduck.agent, "tool"):
252
- try:
253
- action = "stop" if self.state["mcp_enabled"] else "start"
254
- self.devduck.devduck.agent.tool.mcp_server(action=action, port=8000)
255
- self.state["mcp_enabled"] = not self.state["mcp_enabled"]
256
- self._request_menu_update()
257
- rumps.notification(
258
- "DevDuck",
259
- "",
260
- f"MCP: {'ON' if self.state['mcp_enabled'] else 'OFF'}",
261
- )
262
- except Exception as e:
263
- rumps.notification("DevDuck Error", "", str(e))
264
-
265
- def show_input(self, sender):
266
- """Show ambient input overlay"""
267
- ambient_socket = os.path.join(tempfile.gettempdir(), "devduck_ambient.sock")
268
-
269
- try:
270
- client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
271
- client.settimeout(2.0)
272
- client.connect(ambient_socket)
273
- client.send(json.dumps({"action": "show"}).encode("utf-8"))
274
- client.close()
275
- except:
276
- # Ambient not running - try to start via devduck
277
- if self.devduck and hasattr(self.devduck.devduck.agent, "tool"):
278
- try:
279
- self.devduck.devduck.agent.tool.ambient(action="start")
280
- rumps.notification("DevDuck", "", "Input overlay started! 💻")
281
- except Exception as e:
282
- rumps.notification("DevDuck Error", "", f"Failed: {e}")
283
-
284
- def open_dashboard(self, sender):
285
- """Open web dashboard"""
286
- webbrowser.open("https://cagataycali.github.io/devduck")
287
-
288
- def quit_app(self, sender):
289
- """Quit application"""
290
- rumps.quit_application()
291
-
292
- def _start_ipc_listener(self):
293
- """Start Unix socket listener"""
294
-
295
- def listener():
296
- try:
297
- if os.path.exists(self.socket_path):
298
- os.unlink(self.socket_path)
299
-
300
- server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
301
- server.bind(self.socket_path)
302
- server.listen(5)
303
-
304
- while True:
305
- conn, _ = server.accept()
306
- threading.Thread(
307
- target=self._handle_connection, args=(conn,), daemon=True
308
- ).start()
309
- except Exception as e:
310
- print(f"IPC error: {e}")
311
-
312
- threading.Thread(target=listener, daemon=True).start()
313
-
314
- def _handle_connection(self, conn):
315
- """Handle IPC connection"""
316
- try:
317
- data = conn.recv(4096)
318
- if data:
319
- command = json.loads(data.decode("utf-8"))
320
- request_id = command.get("request_id", id(command))
321
-
322
- self.command_queue.put((request_id, command))
323
-
324
- # Wait for response
325
- import time
326
-
327
- timeout = 5.0
328
- start = time.time()
329
- while time.time() - start < timeout:
330
- if request_id in self.command_responses:
331
- response = self.command_responses.pop(request_id)
332
- conn.send(json.dumps(response).encode("utf-8"))
333
- break
334
- time.sleep(0.05)
335
- else:
336
- conn.send(
337
- json.dumps({"status": "error", "message": "timeout"}).encode(
338
- "utf-8"
339
- )
340
- )
341
- except Exception as e:
342
- try:
343
- conn.send(
344
- json.dumps({"status": "error", "message": str(e)}).encode("utf-8")
345
- )
346
- except:
347
- pass
348
- finally:
349
- conn.close()
350
-
351
- def _process_commands(self, _):
352
- """Process queued commands on main thread"""
353
- while not self.command_queue.empty():
354
- request_id, cmd = self.command_queue.get()
355
- response = self._handle_command(cmd)
356
- self.command_responses[request_id] = response
357
-
358
- def _handle_command(self, cmd):
359
- """Handle IPC commands"""
360
- try:
361
- action = cmd.get("action")
362
-
363
- if action == "update_title":
364
- new_icon = cmd.get("title", "🦆")
365
- self.base_icon = new_icon
366
- self.title = self.base_icon
367
- return {"status": "success"}
368
-
369
- elif action == "set_progress":
370
- progress = cmd.get("progress", "idle")
371
- icons = {
372
- "idle": "🦆",
373
- "thinking": "🤔",
374
- "processing": "💡",
375
- "complete": "✅",
376
- "error": "❌",
377
- }
378
- self.base_icon = icons.get(progress, "🦆")
379
- self.title = self.base_icon
380
- return {"status": "success"}
381
-
382
- elif action == "update_menu":
383
- items = cmd.get("items", [])
384
- self.saved_menu_items = items
385
- self._request_menu_update()
386
- return {"status": "success"}
387
-
388
- elif action == "notify":
389
- msg = cmd.get("message", {})
390
- rumps.notification(
391
- msg.get("title", "DevDuck"),
392
- msg.get("subtitle", ""),
393
- msg.get("message", ""),
394
- )
395
- return {"status": "success"}
396
-
397
- elif action == "stream_text":
398
- text = cmd.get("text", "")
399
- stream_id = cmd.get("stream_id", "default")
400
- status = cmd.get("status", "processing")
401
- query = cmd.get("query", "")
402
-
403
- self.active_streams[stream_id] = {
404
- "query": query,
405
- "status": status,
406
- "text": text,
407
- }
408
-
409
- self._request_menu_update()
410
-
411
- return {"status": "success"}
412
-
413
- elif action == "stream_complete":
414
- stream_id = cmd.get("stream_id", "default")
415
- if stream_id in self.active_streams:
416
- stream_data = self.active_streams.pop(stream_id)
417
- self.recent_results.insert(
418
- 0, (stream_data["query"], stream_data["text"], datetime.now())
419
- )
420
- self.recent_results = self.recent_results[: self.max_recent]
421
- self._request_menu_update()
422
- return {"status": "success"}
423
-
424
- elif action == "show_input":
425
- self.show_input(None)
426
- return {"status": "success"}
427
-
428
- elif action in ["toggle_tcp", "toggle_ws", "toggle_mcp"]:
429
- if action == "toggle_tcp":
430
- self.toggle_tcp(None)
431
- elif action == "toggle_ws":
432
- self.toggle_ws(None)
433
- elif action == "toggle_mcp":
434
- self.toggle_mcp(None)
435
- return {"status": "success"}
436
-
437
- return {"status": "error", "message": "Unknown action"}
438
- except Exception as e:
439
- return {"status": "error", "message": str(e)}
440
-
441
- def _create_callback(self, query):
442
- """Create callback for menu item"""
443
-
444
- def callback(sender):
445
- if self.devduck:
446
- stream_id = str(uuid.uuid4())[:8]
447
-
448
- self.active_streams[stream_id] = {
449
- "query": query,
450
- "status": "thinking",
451
- "text": "Starting...",
452
- }
453
- self._request_menu_update()
454
-
455
- self.base_icon = "🤔"
456
- self.title = self.base_icon
457
-
458
- def run():
459
- try:
460
- self.active_streams[stream_id]["status"] = "processing"
461
- self.active_streams[stream_id]["text"] = "Processing query..."
462
- self._request_menu_update()
463
-
464
- self.base_icon = "💡"
465
- self.title = self.base_icon
466
-
467
- result = self.devduck.ask(query)
468
- result_str = str(result)
469
-
470
- self.active_streams[stream_id]["status"] = "complete"
471
- self.active_streams[stream_id]["text"] = result_str
472
- self._request_menu_update()
473
-
474
- self.base_icon = "✅"
475
- self.title = self.base_icon
476
-
477
- rumps.notification("DevDuck Result", query, result_str)
478
-
479
- def move_to_recent():
480
- if stream_id in self.active_streams:
481
- stream_data = self.active_streams.pop(stream_id)
482
- self.recent_results.insert(
483
- 0,
484
- (
485
- stream_data["query"],
486
- stream_data["text"],
487
- datetime.now(),
488
- ),
489
- )
490
- self.recent_results = self.recent_results[
491
- : self.max_recent
492
- ]
493
- self._request_menu_update()
494
- self._reset_icon()
495
-
496
- threading.Timer(3.0, move_to_recent).start()
497
-
498
- except Exception as e:
499
- self.active_streams[stream_id]["status"] = "error"
500
- self.active_streams[stream_id]["text"] = str(e)
501
- self._request_menu_update()
502
-
503
- self.base_icon = "❌"
504
- self.title = self.base_icon
505
- rumps.notification("DevDuck Error", query, str(e))
506
-
507
- threading.Timer(
508
- 3.0, lambda: self._cleanup_stream(stream_id)
509
- ).start()
510
-
511
- threading.Thread(target=run, daemon=True).start()
512
-
513
- return callback
514
-
515
- def _cleanup_stream(self, stream_id):
516
- """Remove stream and reset icon"""
517
- if stream_id in self.active_streams:
518
- self.active_streams.pop(stream_id)
519
- self._request_menu_update()
520
- self._reset_icon()
521
-
522
- def _reset_icon(self):
523
- """Reset icon to default"""
524
- self.base_icon = "🦆"
525
- self.title = self.base_icon
526
-
527
-
528
- if __name__ == "__main__":
529
- app = DevDuckTray()
530
- app.run()