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.
- connectonion/__init__.py +78 -0
- connectonion/address.py +320 -0
- connectonion/agent.py +450 -0
- connectonion/announce.py +84 -0
- connectonion/asgi.py +287 -0
- connectonion/auto_debug_exception.py +181 -0
- connectonion/cli/__init__.py +3 -0
- connectonion/cli/browser_agent/__init__.py +5 -0
- connectonion/cli/browser_agent/browser.py +243 -0
- connectonion/cli/browser_agent/prompt.md +107 -0
- connectonion/cli/commands/__init__.py +1 -0
- connectonion/cli/commands/auth_commands.py +527 -0
- connectonion/cli/commands/browser_commands.py +27 -0
- connectonion/cli/commands/create.py +511 -0
- connectonion/cli/commands/deploy_commands.py +220 -0
- connectonion/cli/commands/doctor_commands.py +173 -0
- connectonion/cli/commands/init.py +469 -0
- connectonion/cli/commands/project_cmd_lib.py +828 -0
- connectonion/cli/commands/reset_commands.py +149 -0
- connectonion/cli/commands/status_commands.py +168 -0
- connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +2010 -0
- connectonion/cli/docs/connectonion.md +1256 -0
- connectonion/cli/docs.md +123 -0
- connectonion/cli/main.py +148 -0
- connectonion/cli/templates/meta-agent/README.md +287 -0
- connectonion/cli/templates/meta-agent/agent.py +196 -0
- connectonion/cli/templates/meta-agent/prompts/answer_prompt.md +9 -0
- connectonion/cli/templates/meta-agent/prompts/docs_retrieve_prompt.md +15 -0
- connectonion/cli/templates/meta-agent/prompts/metagent.md +71 -0
- connectonion/cli/templates/meta-agent/prompts/think_prompt.md +18 -0
- connectonion/cli/templates/minimal/README.md +56 -0
- connectonion/cli/templates/minimal/agent.py +40 -0
- connectonion/cli/templates/playwright/README.md +118 -0
- connectonion/cli/templates/playwright/agent.py +336 -0
- connectonion/cli/templates/playwright/prompt.md +102 -0
- connectonion/cli/templates/playwright/requirements.txt +3 -0
- connectonion/cli/templates/web-research/agent.py +122 -0
- connectonion/connect.py +128 -0
- connectonion/console.py +539 -0
- connectonion/debug_agent/__init__.py +13 -0
- connectonion/debug_agent/agent.py +45 -0
- connectonion/debug_agent/prompts/debug_assistant.md +72 -0
- connectonion/debug_agent/runtime_inspector.py +406 -0
- connectonion/debug_explainer/__init__.py +10 -0
- connectonion/debug_explainer/explain_agent.py +114 -0
- connectonion/debug_explainer/explain_context.py +263 -0
- connectonion/debug_explainer/explainer_prompt.md +29 -0
- connectonion/debug_explainer/root_cause_analysis_prompt.md +43 -0
- connectonion/debugger_ui.py +1039 -0
- connectonion/decorators.py +208 -0
- connectonion/events.py +248 -0
- connectonion/execution_analyzer/__init__.py +9 -0
- connectonion/execution_analyzer/execution_analysis.py +93 -0
- connectonion/execution_analyzer/execution_analysis_prompt.md +47 -0
- connectonion/host.py +579 -0
- connectonion/interactive_debugger.py +342 -0
- connectonion/llm.py +801 -0
- connectonion/llm_do.py +307 -0
- connectonion/logger.py +300 -0
- connectonion/prompt_files/__init__.py +1 -0
- connectonion/prompt_files/analyze_contact.md +62 -0
- connectonion/prompt_files/eval_expected.md +12 -0
- connectonion/prompt_files/react_evaluate.md +11 -0
- connectonion/prompt_files/react_plan.md +16 -0
- connectonion/prompt_files/reflect.md +22 -0
- connectonion/prompts.py +144 -0
- connectonion/relay.py +200 -0
- connectonion/static/docs.html +688 -0
- connectonion/tool_executor.py +279 -0
- connectonion/tool_factory.py +186 -0
- connectonion/tool_registry.py +105 -0
- connectonion/trust.py +166 -0
- connectonion/trust_agents.py +71 -0
- connectonion/trust_functions.py +88 -0
- connectonion/tui/__init__.py +57 -0
- connectonion/tui/divider.py +39 -0
- connectonion/tui/dropdown.py +251 -0
- connectonion/tui/footer.py +31 -0
- connectonion/tui/fuzzy.py +56 -0
- connectonion/tui/input.py +278 -0
- connectonion/tui/keys.py +35 -0
- connectonion/tui/pick.py +130 -0
- connectonion/tui/providers.py +155 -0
- connectonion/tui/status_bar.py +163 -0
- connectonion/usage.py +161 -0
- connectonion/useful_events_handlers/__init__.py +16 -0
- connectonion/useful_events_handlers/reflect.py +116 -0
- connectonion/useful_plugins/__init__.py +20 -0
- connectonion/useful_plugins/calendar_plugin.py +163 -0
- connectonion/useful_plugins/eval.py +139 -0
- connectonion/useful_plugins/gmail_plugin.py +162 -0
- connectonion/useful_plugins/image_result_formatter.py +127 -0
- connectonion/useful_plugins/re_act.py +78 -0
- connectonion/useful_plugins/shell_approval.py +159 -0
- connectonion/useful_tools/__init__.py +44 -0
- connectonion/useful_tools/diff_writer.py +192 -0
- connectonion/useful_tools/get_emails.py +183 -0
- connectonion/useful_tools/gmail.py +1596 -0
- connectonion/useful_tools/google_calendar.py +613 -0
- connectonion/useful_tools/memory.py +380 -0
- connectonion/useful_tools/microsoft_calendar.py +604 -0
- connectonion/useful_tools/outlook.py +488 -0
- connectonion/useful_tools/send_email.py +205 -0
- connectonion/useful_tools/shell.py +97 -0
- connectonion/useful_tools/slash_command.py +201 -0
- connectonion/useful_tools/terminal.py +285 -0
- connectonion/useful_tools/todo_list.py +241 -0
- connectonion/useful_tools/web_fetch.py +216 -0
- connectonion/xray.py +467 -0
- connectonion-0.5.8.dist-info/METADATA +741 -0
- connectonion-0.5.8.dist-info/RECORD +113 -0
- connectonion-0.5.8.dist-info/WHEEL +4 -0
- 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
|