connectonion 0.5.8__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.
Files changed (113) hide show
  1. connectonion/__init__.py +78 -0
  2. connectonion/address.py +320 -0
  3. connectonion/agent.py +450 -0
  4. connectonion/announce.py +84 -0
  5. connectonion/asgi.py +287 -0
  6. connectonion/auto_debug_exception.py +181 -0
  7. connectonion/cli/__init__.py +3 -0
  8. connectonion/cli/browser_agent/__init__.py +5 -0
  9. connectonion/cli/browser_agent/browser.py +243 -0
  10. connectonion/cli/browser_agent/prompt.md +107 -0
  11. connectonion/cli/commands/__init__.py +1 -0
  12. connectonion/cli/commands/auth_commands.py +527 -0
  13. connectonion/cli/commands/browser_commands.py +27 -0
  14. connectonion/cli/commands/create.py +511 -0
  15. connectonion/cli/commands/deploy_commands.py +220 -0
  16. connectonion/cli/commands/doctor_commands.py +173 -0
  17. connectonion/cli/commands/init.py +469 -0
  18. connectonion/cli/commands/project_cmd_lib.py +828 -0
  19. connectonion/cli/commands/reset_commands.py +149 -0
  20. connectonion/cli/commands/status_commands.py +168 -0
  21. connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +2010 -0
  22. connectonion/cli/docs/connectonion.md +1256 -0
  23. connectonion/cli/docs.md +123 -0
  24. connectonion/cli/main.py +148 -0
  25. connectonion/cli/templates/meta-agent/README.md +287 -0
  26. connectonion/cli/templates/meta-agent/agent.py +196 -0
  27. connectonion/cli/templates/meta-agent/prompts/answer_prompt.md +9 -0
  28. connectonion/cli/templates/meta-agent/prompts/docs_retrieve_prompt.md +15 -0
  29. connectonion/cli/templates/meta-agent/prompts/metagent.md +71 -0
  30. connectonion/cli/templates/meta-agent/prompts/think_prompt.md +18 -0
  31. connectonion/cli/templates/minimal/README.md +56 -0
  32. connectonion/cli/templates/minimal/agent.py +40 -0
  33. connectonion/cli/templates/playwright/README.md +118 -0
  34. connectonion/cli/templates/playwright/agent.py +336 -0
  35. connectonion/cli/templates/playwright/prompt.md +102 -0
  36. connectonion/cli/templates/playwright/requirements.txt +3 -0
  37. connectonion/cli/templates/web-research/agent.py +122 -0
  38. connectonion/connect.py +128 -0
  39. connectonion/console.py +539 -0
  40. connectonion/debug_agent/__init__.py +13 -0
  41. connectonion/debug_agent/agent.py +45 -0
  42. connectonion/debug_agent/prompts/debug_assistant.md +72 -0
  43. connectonion/debug_agent/runtime_inspector.py +406 -0
  44. connectonion/debug_explainer/__init__.py +10 -0
  45. connectonion/debug_explainer/explain_agent.py +114 -0
  46. connectonion/debug_explainer/explain_context.py +263 -0
  47. connectonion/debug_explainer/explainer_prompt.md +29 -0
  48. connectonion/debug_explainer/root_cause_analysis_prompt.md +43 -0
  49. connectonion/debugger_ui.py +1039 -0
  50. connectonion/decorators.py +208 -0
  51. connectonion/events.py +248 -0
  52. connectonion/execution_analyzer/__init__.py +9 -0
  53. connectonion/execution_analyzer/execution_analysis.py +93 -0
  54. connectonion/execution_analyzer/execution_analysis_prompt.md +47 -0
  55. connectonion/host.py +579 -0
  56. connectonion/interactive_debugger.py +342 -0
  57. connectonion/llm.py +801 -0
  58. connectonion/llm_do.py +307 -0
  59. connectonion/logger.py +300 -0
  60. connectonion/prompt_files/__init__.py +1 -0
  61. connectonion/prompt_files/analyze_contact.md +62 -0
  62. connectonion/prompt_files/eval_expected.md +12 -0
  63. connectonion/prompt_files/react_evaluate.md +11 -0
  64. connectonion/prompt_files/react_plan.md +16 -0
  65. connectonion/prompt_files/reflect.md +22 -0
  66. connectonion/prompts.py +144 -0
  67. connectonion/relay.py +200 -0
  68. connectonion/static/docs.html +688 -0
  69. connectonion/tool_executor.py +279 -0
  70. connectonion/tool_factory.py +186 -0
  71. connectonion/tool_registry.py +105 -0
  72. connectonion/trust.py +166 -0
  73. connectonion/trust_agents.py +71 -0
  74. connectonion/trust_functions.py +88 -0
  75. connectonion/tui/__init__.py +57 -0
  76. connectonion/tui/divider.py +39 -0
  77. connectonion/tui/dropdown.py +251 -0
  78. connectonion/tui/footer.py +31 -0
  79. connectonion/tui/fuzzy.py +56 -0
  80. connectonion/tui/input.py +278 -0
  81. connectonion/tui/keys.py +35 -0
  82. connectonion/tui/pick.py +130 -0
  83. connectonion/tui/providers.py +155 -0
  84. connectonion/tui/status_bar.py +163 -0
  85. connectonion/usage.py +161 -0
  86. connectonion/useful_events_handlers/__init__.py +16 -0
  87. connectonion/useful_events_handlers/reflect.py +116 -0
  88. connectonion/useful_plugins/__init__.py +20 -0
  89. connectonion/useful_plugins/calendar_plugin.py +163 -0
  90. connectonion/useful_plugins/eval.py +139 -0
  91. connectonion/useful_plugins/gmail_plugin.py +162 -0
  92. connectonion/useful_plugins/image_result_formatter.py +127 -0
  93. connectonion/useful_plugins/re_act.py +78 -0
  94. connectonion/useful_plugins/shell_approval.py +159 -0
  95. connectonion/useful_tools/__init__.py +44 -0
  96. connectonion/useful_tools/diff_writer.py +192 -0
  97. connectonion/useful_tools/get_emails.py +183 -0
  98. connectonion/useful_tools/gmail.py +1596 -0
  99. connectonion/useful_tools/google_calendar.py +613 -0
  100. connectonion/useful_tools/memory.py +380 -0
  101. connectonion/useful_tools/microsoft_calendar.py +604 -0
  102. connectonion/useful_tools/outlook.py +488 -0
  103. connectonion/useful_tools/send_email.py +205 -0
  104. connectonion/useful_tools/shell.py +97 -0
  105. connectonion/useful_tools/slash_command.py +201 -0
  106. connectonion/useful_tools/terminal.py +285 -0
  107. connectonion/useful_tools/todo_list.py +241 -0
  108. connectonion/useful_tools/web_fetch.py +216 -0
  109. connectonion/xray.py +467 -0
  110. connectonion-0.5.8.dist-info/METADATA +741 -0
  111. connectonion-0.5.8.dist-info/RECORD +113 -0
  112. connectonion-0.5.8.dist-info/WHEEL +4 -0
  113. connectonion-0.5.8.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,44 @@
1
+ """
2
+ Purpose: Export all useful tools and utilities for ConnectOnion agents
3
+ LLM-Note:
4
+ Dependencies: imports from [send_email, get_emails, memory, gmail, google_calendar, outlook, microsoft_calendar, web_fetch, shell, diff_writer, tui.pick, terminal, todo_list, slash_command] | imported by [__init__.py main package] | re-exports tools for agent consumption
5
+ Data flow: agent imports from useful_tools → accesses tool functions/classes directly
6
+ State/Effects: no state | pure re-exports | lazy loading for heavy dependencies
7
+ Integration: exposes send_email, get_emails, mark_read, mark_unread (email functions) | Memory, Gmail, GoogleCalendar, Outlook, MicrosoftCalendar, WebFetch, Shell, DiffWriter, TodoList (tool classes) | pick, yes_no, autocomplete (TUI helpers) | SlashCommand (extension point)
8
+ Errors: ImportError if dependency not installed (e.g., google-auth for GoogleCalendar, httpx for Outlook/MicrosoftCalendar)
9
+ """
10
+
11
+ from .send_email import send_email
12
+ from .get_emails import get_emails, mark_read, mark_unread
13
+ from .memory import Memory
14
+ from .gmail import Gmail
15
+ from .google_calendar import GoogleCalendar
16
+ from .outlook import Outlook
17
+ from .microsoft_calendar import MicrosoftCalendar
18
+ from .web_fetch import WebFetch
19
+ from .shell import Shell
20
+ from .diff_writer import DiffWriter
21
+ from ..tui import pick
22
+ from .terminal import yes_no, autocomplete
23
+ from .todo_list import TodoList
24
+ from .slash_command import SlashCommand
25
+
26
+ __all__ = [
27
+ "send_email",
28
+ "get_emails",
29
+ "mark_read",
30
+ "mark_unread",
31
+ "Memory",
32
+ "Gmail",
33
+ "GoogleCalendar",
34
+ "Outlook",
35
+ "MicrosoftCalendar",
36
+ "WebFetch",
37
+ "Shell",
38
+ "DiffWriter",
39
+ "pick",
40
+ "yes_no",
41
+ "autocomplete",
42
+ "TodoList",
43
+ "SlashCommand"
44
+ ]
@@ -0,0 +1,192 @@
1
+ """
2
+ Purpose: Human-in-the-loop file writing tool with diff preview and approval workflow
3
+ LLM-Note:
4
+ Dependencies: imports from [difflib, pathlib, rich.console, rich.panel, rich.text, connectonion.tui.pick] | imported by [useful_tools/__init__.py] | tested by [tests/unit/test_diff_writer.py, tests/unit/test_diff_writer_tool.py]
5
+ Data flow: Agent calls DiffWriter.write(path, content) → reads existing file → generates unified diff → displays via Rich panel → pick() prompts for approval → writes if approved → returns status string
6
+ State/Effects: reads and writes files on filesystem | displays Rich-formatted diff in terminal | blocks for user input (unless auto_approve=True) | creates new files if path doesn't exist
7
+ Integration: exposes DiffWriter class with write(path, content), diff(path, content), read(path) | used as agent tool via Agent(tools=[DiffWriter()]) | auto_approve=True for automation
8
+ Performance: file I/O per operation | difflib is O(n) for diff generation | Rich rendering is fast | blocks on user input
9
+ Errors: returns error string if file unreadable | returns "Cancelled" if user rejects | no exceptions raised
10
+
11
+ DiffWriter - Human-in-the-loop file writing with diff display.
12
+
13
+ Usage:
14
+ from connectonion import Agent, DiffWriter
15
+
16
+ writer = DiffWriter()
17
+ agent = Agent("coder", tools=[writer])
18
+
19
+ # Agent can now use:
20
+ # - write(path, content) - Write with diff display and approval
21
+ # - diff(path, content) - Preview diff without writing
22
+ # - read(path) - Read file contents
23
+ """
24
+
25
+ import difflib
26
+ from pathlib import Path
27
+
28
+ from rich.console import Console
29
+ from rich.panel import Panel
30
+ from rich.text import Text
31
+
32
+ from connectonion.tui import pick
33
+
34
+
35
+ class DiffWriter:
36
+ """File writer with diff display and human approval."""
37
+
38
+ def __init__(self, auto_approve: bool = False):
39
+ """Initialize DiffWriter.
40
+
41
+ Args:
42
+ auto_approve: If True, skip approval prompts (for automation)
43
+ """
44
+ self.auto_approve = auto_approve
45
+ self._console = Console()
46
+
47
+ def write(self, path: str, content: str) -> str:
48
+ """Write content to a file with diff display and approval.
49
+
50
+ Args:
51
+ path: File path to write to
52
+ content: Content to write
53
+
54
+ Returns:
55
+ Success message, rejection message, or user feedback for agent
56
+ """
57
+ file_path = Path(path)
58
+
59
+ if not self.auto_approve:
60
+ diff_text = self._generate_diff(path, content)
61
+ if diff_text:
62
+ self._display_diff(diff_text, path)
63
+ else:
64
+ self._display_new_file(path, content)
65
+
66
+ choice = self._ask_approval(path)
67
+
68
+ if choice == "reject":
69
+ self._console.print()
70
+ self._console.print("[bold yellow]What should the agent do instead?[/]")
71
+ feedback = input("> ")
72
+ return f"User rejected changes to {path}. Feedback: {feedback}"
73
+
74
+ if choice == "approve_all":
75
+ self.auto_approve = True
76
+
77
+ file_path.parent.mkdir(parents=True, exist_ok=True)
78
+ file_path.write_text(content, encoding='utf-8')
79
+ return f"Wrote {len(content)} bytes to {path}"
80
+
81
+ def diff(self, path: str, content: str) -> str:
82
+ """Show diff without writing (preview mode).
83
+
84
+ Args:
85
+ path: File path to compare against
86
+ content: New content to compare
87
+
88
+ Returns:
89
+ Diff string in unified format
90
+ """
91
+ diff_text = self._generate_diff(path, content)
92
+ if diff_text:
93
+ self._display_diff(diff_text, path)
94
+ return diff_text
95
+ return f"No changes to {path}"
96
+
97
+ def read(self, path: str) -> str:
98
+ """Read file contents.
99
+
100
+ Args:
101
+ path: File path to read
102
+
103
+ Returns:
104
+ File contents or error message
105
+ """
106
+ file_path = Path(path)
107
+ if not file_path.exists():
108
+ return f"Error: File {path} not found"
109
+ return file_path.read_text(encoding='utf-8')
110
+
111
+ def _ask_approval(self, path: str) -> str:
112
+ """Ask user for approval with single keypress.
113
+
114
+ Returns:
115
+ 'approve', 'approve_all', or 'reject'
116
+ """
117
+ self._console.print()
118
+ self._console.print(f"[bold cyan]File:[/] {path}")
119
+
120
+ choice = pick(
121
+ f"Apply changes to {path}?",
122
+ {
123
+ "1": "Yes, apply this change",
124
+ "2": "Yes to all (auto-approve for session)",
125
+ "3": "No, and tell agent what to do instead",
126
+ },
127
+ console=self._console,
128
+ )
129
+ return {"1": "approve", "2": "approve_all", "3": "reject"}[choice]
130
+
131
+ def _generate_diff(self, path: str, new_content: str) -> str:
132
+ """Generate unified diff between existing file and new content."""
133
+ file_path = Path(path)
134
+
135
+ if file_path.exists():
136
+ original_lines = file_path.read_text(encoding='utf-8').splitlines(keepends=True)
137
+ else:
138
+ return "" # New file, no diff
139
+
140
+ new_lines = new_content.splitlines(keepends=True)
141
+
142
+ diff = difflib.unified_diff(
143
+ original_lines,
144
+ new_lines,
145
+ fromfile=f"a/{path}",
146
+ tofile=f"b/{path}",
147
+ )
148
+
149
+ return ''.join(diff)
150
+
151
+ def _display_diff(self, diff_text: str, path: str):
152
+ """Display colorized diff."""
153
+ styled = Text()
154
+
155
+ for line in diff_text.splitlines():
156
+ if line.startswith('+++') or line.startswith('---'):
157
+ styled.append(line + '\n', style="bold white")
158
+ elif line.startswith('@@'):
159
+ styled.append(line + '\n', style="cyan")
160
+ elif line.startswith('+'):
161
+ styled.append(line + '\n', style="green")
162
+ elif line.startswith('-'):
163
+ styled.append(line + '\n', style="red")
164
+ else:
165
+ styled.append(line + '\n', style="dim")
166
+
167
+ panel = Panel(
168
+ styled,
169
+ title=f"[bold yellow]Changes to {path}[/]",
170
+ border_style="yellow",
171
+ padding=(0, 1)
172
+ )
173
+ self._console.print(panel)
174
+
175
+ def _display_new_file(self, path: str, content: str):
176
+ """Display preview of new file."""
177
+ styled = Text()
178
+
179
+ lines = content.splitlines()
180
+ for line in lines[:50]: # Limit preview to 50 lines
181
+ styled.append(f"+ {line}\n", style="green")
182
+
183
+ if len(lines) > 50:
184
+ styled.append(f"... ({len(lines) - 50} more lines)\n", style="dim")
185
+
186
+ panel = Panel(
187
+ styled,
188
+ title=f"[bold yellow]New file: {path}[/]",
189
+ border_style="yellow",
190
+ padding=(0, 1)
191
+ )
192
+ self._console.print(panel)
@@ -0,0 +1,183 @@
1
+ """
2
+ Purpose: Retrieve emails from agent's inbox via OpenOnion API with filtering options
3
+ LLM-Note:
4
+ Dependencies: imports from [os, json, toml, requests, pathlib, typing, dotenv] | imported by [__init__.py, useful_tools/__init__.py] | tested by [tests/test_email_functions.py, tests/test_real_email.py]
5
+ Data flow: Agent calls get_emails(last=10, unread=False) → searches for .env file → loads OPENONION_API_KEY → GET to oo.openonion.ai/api/email with query params → returns List[Dict] with emails: {id, from, to, subject, body, html_body, date, read} | mark_read(email_id) PUTs to /api/email/{id}/read
6
+ State/Effects: reads .env files | makes HTTP GET/PUT requests | no local caching | mark_read() modifies server-side read status
7
+ Integration: exposes get_emails(last, unread), mark_read(email_id) | used as agent tool functions | requires 'co auth' setup | API endpoints: GET /api/email?last=N&unread=true, PUT /api/email/{id}/read
8
+ Performance: one HTTP request per call | no pagination (uses 'last' param) | synchronous blocking | no local cache
9
+ Errors: returns empty list [] on failure | HTTP errors caught and wrapped in error dict | missing auth returns error | let-it-crash pattern for API failures
10
+ """
11
+
12
+ import os
13
+ import json
14
+ import toml
15
+ import requests
16
+ from pathlib import Path
17
+ from typing import List, Dict, Optional, Union
18
+
19
+
20
+ def get_emails(last: int = 10, unread: bool = False) -> List[Dict]:
21
+ """Get emails sent to the agent's address.
22
+
23
+ Args:
24
+ last: Number of emails to retrieve (default: 10)
25
+ unread: Only get unread emails (default: False)
26
+
27
+ Returns:
28
+ List of email dictionaries containing:
29
+ - id: Unique message ID
30
+ - from: Sender's email address
31
+ - subject: Email subject
32
+ - message: Email body content
33
+ - timestamp: ISO format timestamp
34
+ - read: Boolean read status
35
+ """
36
+ # Get authentication token from environment
37
+ # Emails are hosted by OpenOnion and require OPENONION_API_KEY for authentication
38
+ token = os.getenv("OPENONION_API_KEY")
39
+
40
+ if not token:
41
+ raise ValueError(
42
+ "OPENONION_API_KEY not found in .env file. "
43
+ "Agent emails are hosted by OpenOnion and require authentication. "
44
+ "Check your .env file for OPENONION_API_KEY, or run 'co init' to copy "
45
+ "OPENONION_API_KEY from ~/.co/keys.env to your project."
46
+ )
47
+
48
+ # Fetch emails from backend API
49
+ backend_url = os.getenv("CONNECTONION_BACKEND_URL", "https://oo.openonion.ai")
50
+ endpoint = f"{backend_url}/api/v1/email/received"
51
+
52
+ headers = {
53
+ "Authorization": f"Bearer {token}",
54
+ "Content-Type": "application/json"
55
+ }
56
+
57
+ params = {
58
+ "limit": last,
59
+ "unread_only": unread
60
+ }
61
+
62
+ response = requests.get(
63
+ endpoint,
64
+ params=params,
65
+ headers=headers,
66
+ timeout=10
67
+ )
68
+
69
+ # Raise error if API call failed
70
+ response.raise_for_status()
71
+
72
+ data = response.json()
73
+ emails = data.get("emails", [])
74
+
75
+ # Ensure consistent format
76
+ formatted_emails = []
77
+ for email in emails:
78
+ formatted_emails.append({
79
+ "id": email.get("id", ""),
80
+ "from": email.get("from_email", email.get("from", "")),
81
+ "subject": email.get("subject", ""),
82
+ "message": email.get("text_body", email.get("html_body", "")),
83
+ "timestamp": email.get("received_at", ""),
84
+ "read": email.get("is_read", False)
85
+ })
86
+
87
+ return formatted_emails
88
+
89
+
90
+ def mark_read(email_ids: Union[str, List[str]]) -> bool:
91
+ """Mark email(s) as read.
92
+
93
+ Args:
94
+ email_ids: Single email ID or list of IDs to mark as read
95
+
96
+ Returns:
97
+ True if successful, False otherwise
98
+ """
99
+ # Normalize to list
100
+ if isinstance(email_ids, str):
101
+ email_ids = [email_ids]
102
+
103
+ if not email_ids:
104
+ raise ValueError("No email IDs provided to mark as read")
105
+
106
+ # Get authentication token from environment
107
+ token = os.getenv("OPENONION_API_KEY")
108
+
109
+ if not token:
110
+ raise ValueError(
111
+ "OPENONION_API_KEY not found in .env file. "
112
+ "Check your .env file for OPENONION_API_KEY, or run 'co init' to copy "
113
+ "OPENONION_API_KEY from ~/.co/keys.env to your project."
114
+ )
115
+
116
+ # Mark emails as read via backend API
117
+ backend_url = os.getenv("CONNECTONION_BACKEND_URL", "https://oo.openonion.ai")
118
+ endpoint = f"{backend_url}/api/v1/email/s/mark-read"
119
+
120
+ headers = {
121
+ "Authorization": f"Bearer {token}",
122
+ "Content-Type": "application/json"
123
+ }
124
+
125
+ # Mark each email as read individually
126
+ for email_id in email_ids:
127
+ response = requests.post(
128
+ f"{endpoint}?email_id={email_id}",
129
+ headers=headers,
130
+ timeout=10
131
+ )
132
+ # Raise error if API call failed
133
+ response.raise_for_status()
134
+
135
+ return True
136
+
137
+
138
+ def mark_unread(email_ids: Union[str, List[str]]) -> bool:
139
+ """Mark email(s) as unread.
140
+
141
+ Args:
142
+ email_ids: Single email ID or list of IDs to mark as unread
143
+
144
+ Returns:
145
+ True if successful, False otherwise
146
+ """
147
+ # Normalize to list
148
+ if isinstance(email_ids, str):
149
+ email_ids = [email_ids]
150
+
151
+ if not email_ids:
152
+ raise ValueError("No email IDs provided to mark as unread")
153
+
154
+ # Get authentication token from environment
155
+ token = os.getenv("OPENONION_API_KEY")
156
+
157
+ if not token:
158
+ raise ValueError(
159
+ "OPENONION_API_KEY not found in .env file. "
160
+ "Check your .env file for OPENONION_API_KEY, or run 'co init' to copy "
161
+ "OPENONION_API_KEY from ~/.co/keys.env to your project."
162
+ )
163
+
164
+ # Mark emails as unread via backend API
165
+ backend_url = os.getenv("CONNECTONION_BACKEND_URL", "https://oo.openonion.ai")
166
+ endpoint = f"{backend_url}/api/v1/email/s/mark-unread"
167
+
168
+ headers = {
169
+ "Authorization": f"Bearer {token}",
170
+ "Content-Type": "application/json"
171
+ }
172
+
173
+ # Mark each email as unread individually
174
+ for email_id in email_ids:
175
+ response = requests.post(
176
+ f"{endpoint}?email_id={email_id}",
177
+ headers=headers,
178
+ timeout=10
179
+ )
180
+ # Raise error if API call failed
181
+ response.raise_for_status()
182
+
183
+ return True