connectonion 0.5.3__tar.gz → 0.5.6__tar.gz
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-0.5.3 → connectonion-0.5.6}/PKG-INFO +1 -1
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/__init__.py +1 -1
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/asgi.py +47 -1
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/commands/deploy_commands.py +110 -16
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/host.py +101 -81
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/usage.py +10 -5
- {connectonion-0.5.3 → connectonion-0.5.6}/pyproject.toml +1 -1
- {connectonion-0.5.3 → connectonion-0.5.6}/.gitignore +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/address.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/agent.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/announce.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/auto_debug_exception.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/__init__.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/browser_agent/__init__.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/browser_agent/browser.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/browser_agent/prompt.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/commands/__init__.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/commands/auth_commands.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/commands/browser_commands.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/commands/create.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/commands/doctor_commands.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/commands/init.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/commands/project_cmd_lib.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/commands/reset_commands.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/commands/status_commands.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/docs/co-vibecoding-principles-docs-contexts-all-in-one.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/docs/connectonion.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/docs.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/main.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/meta-agent/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/meta-agent/agent.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/meta-agent/prompts/answer_prompt.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/meta-agent/prompts/docs_retrieve_prompt.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/meta-agent/prompts/metagent.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/meta-agent/prompts/think_prompt.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/minimal/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/minimal/agent.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/playwright/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/playwright/agent.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/playwright/prompt.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/playwright/requirements.txt +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/web-research/agent.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/connect.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/console.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/debug_agent/__init__.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/debug_agent/agent.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/debug_agent/prompts/debug_assistant.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/debug_agent/runtime_inspector.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/debug_explainer/__init__.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/debug_explainer/explain_agent.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/debug_explainer/explain_context.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/debug_explainer/explainer_prompt.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/debug_explainer/root_cause_analysis_prompt.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/debugger_ui.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/decorators.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/events.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/execution_analyzer/__init__.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/execution_analyzer/execution_analysis.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/execution_analyzer/execution_analysis_prompt.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/interactive_debugger.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/llm.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/llm_do.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/logger.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/prompt_files/__init__.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/prompt_files/analyze_contact.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/prompt_files/eval_expected.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/prompt_files/react_evaluate.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/prompt_files/react_plan.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/prompt_files/reflect.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/prompts.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/relay.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/static/docs.html +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tool_executor.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tool_factory.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tool_registry.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/trust.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/trust_agents.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/trust_functions.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tui/__init__.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tui/divider.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tui/dropdown.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tui/footer.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tui/fuzzy.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tui/input.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tui/keys.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tui/pick.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tui/providers.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/tui/status_bar.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_events_handlers/__init__.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_events_handlers/reflect.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_plugins/__init__.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_plugins/calendar_plugin.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_plugins/eval.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_plugins/gmail_plugin.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_plugins/image_result_formatter.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_plugins/re_act.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_plugins/shell_approval.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/__init__.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/diff_writer.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/get_emails.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/gmail.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/google_calendar.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/memory.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/microsoft_calendar.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/outlook.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/send_email.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/shell.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/slash_command.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/terminal.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/todo_list.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_tools/web_fetch.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/connectonion/xray.py +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/docs/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/docs/cli/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/docs/debug/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/docs/integrations/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/docs/network/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/docs/templates/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/docs/tui/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/docs/useful_plugins/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/docs/useful_tools/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/examples/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/examples/browser-agent/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/examples/email-agent/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/examples/simple-agent/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/prompts/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/prompts/formats/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/tests/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/tests/cli/README.md +0 -0
- {connectonion-0.5.3 → connectonion-0.5.6}/tests/cli/aws/README.md +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: connectonion
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.6
|
|
4
4
|
Summary: A simple Python framework for creating AI agents with behavior tracking
|
|
5
5
|
Project-URL: Homepage, https://github.com/openonion/connectonion
|
|
6
6
|
Project-URL: Documentation, https://docs.connectonion.com
|
|
@@ -6,9 +6,24 @@ requests. Separated from host.py for better testing and smaller file size.
|
|
|
6
6
|
Design decision: Raw ASGI instead of Starlette/FastAPI for full protocol control.
|
|
7
7
|
See: docs/design-decisions/022-raw-asgi-implementation.md
|
|
8
8
|
"""
|
|
9
|
+
import hmac
|
|
9
10
|
import json
|
|
11
|
+
import os
|
|
10
12
|
from pathlib import Path
|
|
11
13
|
|
|
14
|
+
from pydantic import BaseModel
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _json_default(obj):
|
|
18
|
+
"""Handle non-serializable objects like Pydantic models.
|
|
19
|
+
|
|
20
|
+
This enables native JSON serialization for Pydantic BaseModel instances
|
|
21
|
+
nested in API response dicts, following FastAPI's pattern.
|
|
22
|
+
"""
|
|
23
|
+
if isinstance(obj, BaseModel):
|
|
24
|
+
return obj.model_dump()
|
|
25
|
+
raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")
|
|
26
|
+
|
|
12
27
|
|
|
13
28
|
async def read_body(receive) -> bytes:
|
|
14
29
|
"""Read complete request body from ASGI receive."""
|
|
@@ -23,7 +38,7 @@ async def read_body(receive) -> bytes:
|
|
|
23
38
|
|
|
24
39
|
async def send_json(send, data: dict, status: int = 200):
|
|
25
40
|
"""Send JSON response via ASGI send."""
|
|
26
|
-
body = json.dumps(data).encode()
|
|
41
|
+
body = json.dumps(data, default=_json_default).encode()
|
|
27
42
|
await send({"type": "http.response.start", "status": status,
|
|
28
43
|
"headers": [[b"content-type", b"application/json"]]})
|
|
29
44
|
await send({"type": "http.response.body", "body": body})
|
|
@@ -39,6 +54,13 @@ async def send_html(send, html: bytes, status: int = 200):
|
|
|
39
54
|
await send({"type": "http.response.body", "body": html})
|
|
40
55
|
|
|
41
56
|
|
|
57
|
+
async def send_text(send, text: str, status: int = 200):
|
|
58
|
+
"""Send plain text response via ASGI send."""
|
|
59
|
+
await send({"type": "http.response.start", "status": status,
|
|
60
|
+
"headers": [[b"content-type", b"text/plain; charset=utf-8"]]})
|
|
61
|
+
await send({"type": "http.response.body", "body": text.encode()})
|
|
62
|
+
|
|
63
|
+
|
|
42
64
|
async def handle_http(
|
|
43
65
|
scope,
|
|
44
66
|
receive,
|
|
@@ -68,6 +90,30 @@ async def handle_http(
|
|
|
68
90
|
"""
|
|
69
91
|
method, path = scope["method"], scope["path"]
|
|
70
92
|
|
|
93
|
+
# Admin endpoints require API key auth
|
|
94
|
+
if path.startswith("/admin"):
|
|
95
|
+
headers = dict(scope.get("headers", []))
|
|
96
|
+
auth = headers.get(b"authorization", b"").decode()
|
|
97
|
+
expected = os.environ.get("OPENONION_API_KEY", "")
|
|
98
|
+
if not expected or not auth.startswith("Bearer ") or not hmac.compare_digest(auth[7:], expected):
|
|
99
|
+
await send_json(send, {"error": "unauthorized"}, 401)
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
if method == "GET" and path == "/admin/logs":
|
|
103
|
+
result = handlers["admin_logs"]()
|
|
104
|
+
if "error" in result:
|
|
105
|
+
await send_json(send, result, 404)
|
|
106
|
+
else:
|
|
107
|
+
await send_text(send, result["content"])
|
|
108
|
+
return
|
|
109
|
+
|
|
110
|
+
if method == "GET" and path == "/admin/sessions":
|
|
111
|
+
await send_json(send, handlers["admin_sessions"]())
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
await send_json(send, {"error": "not found"}, 404)
|
|
115
|
+
return
|
|
116
|
+
|
|
71
117
|
if method == "POST" and path == "/input":
|
|
72
118
|
body = await read_body(receive)
|
|
73
119
|
try:
|
|
@@ -5,11 +5,13 @@ LLM-Note:
|
|
|
5
5
|
Data flow: handle_deploy() → validates git repo and .co/config.toml → _get_api_key() loads OPENONION_API_KEY → reads config.toml for project name and secrets path → dotenv_values() loads secrets from .env → git archive creates tarball of HEAD → POST to /api/v1/deploy with tarball + project_name + secrets → polls /api/v1/deploy/{id}/status until running/error → displays agent URL
|
|
6
6
|
State/Effects: creates temporary tarball file in tempdir | reads .co/config.toml, .env files | makes network POST request | prints progress to stdout via rich.Console | does not modify project files
|
|
7
7
|
Integration: exposes handle_deploy() for CLI | expects git repo with .co/config.toml containing project.name, project.secrets, deploy.entrypoint | uses Bearer token auth | returns void (prints results)
|
|
8
|
-
Performance: git archive is fast | network timeout
|
|
8
|
+
Performance: git archive is fast | network timeout 600s for upload+build, 10s for status checks | polls every 3s up to 100 times (~5 min)
|
|
9
9
|
Errors: fails if not git repo | fails if not ConnectOnion project (.co/config.toml missing) | fails if no API key | prints backend error messages
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
import json
|
|
12
13
|
import os
|
|
14
|
+
import re
|
|
13
15
|
import subprocess
|
|
14
16
|
import tempfile
|
|
15
17
|
import time
|
|
@@ -24,6 +26,30 @@ console = Console()
|
|
|
24
26
|
API_BASE = "https://oo.openonion.ai"
|
|
25
27
|
|
|
26
28
|
|
|
29
|
+
def _check_host_export(entrypoint: str) -> bool:
|
|
30
|
+
"""Check if entrypoint file exports an ASGI app via host().
|
|
31
|
+
|
|
32
|
+
Looks for patterns like:
|
|
33
|
+
- host(agent)
|
|
34
|
+
- host(my_agent)
|
|
35
|
+
- from connectonion import host
|
|
36
|
+
"""
|
|
37
|
+
entrypoint_path = Path(entrypoint)
|
|
38
|
+
if not entrypoint_path.exists():
|
|
39
|
+
return False
|
|
40
|
+
|
|
41
|
+
content = entrypoint_path.read_text()
|
|
42
|
+
|
|
43
|
+
# Check for host() call pattern
|
|
44
|
+
# Matches: host(agent), host(my_agent), host( agent ), etc.
|
|
45
|
+
host_call_pattern = r'\bhost\s*\([^)]+\)'
|
|
46
|
+
|
|
47
|
+
if re.search(host_call_pattern, content):
|
|
48
|
+
return True
|
|
49
|
+
|
|
50
|
+
return False
|
|
51
|
+
|
|
52
|
+
|
|
27
53
|
def _get_api_key() -> str:
|
|
28
54
|
"""Get OPENONION_API_KEY from env or .env files."""
|
|
29
55
|
if api_key := os.getenv("OPENONION_API_KEY"):
|
|
@@ -65,6 +91,26 @@ def handle_deploy():
|
|
|
65
91
|
secrets_path = config.get("project", {}).get("secrets", ".env")
|
|
66
92
|
entrypoint = config.get("deploy", {}).get("entrypoint", "agent.py")
|
|
67
93
|
|
|
94
|
+
# Validate entrypoint exists
|
|
95
|
+
if not Path(entrypoint).exists():
|
|
96
|
+
console.print(f"[red]Entrypoint not found: {entrypoint}[/red]")
|
|
97
|
+
console.print("[dim]Set entrypoint in .co/config.toml under [deploy][/dim]")
|
|
98
|
+
return
|
|
99
|
+
|
|
100
|
+
# Validate entrypoint exports ASGI app via host()
|
|
101
|
+
if not _check_host_export(entrypoint):
|
|
102
|
+
console.print(f"[red]Entrypoint '{entrypoint}' does not export an ASGI app.[/red]")
|
|
103
|
+
console.print()
|
|
104
|
+
console.print("[yellow]To deploy, your agent must call host():[/yellow]")
|
|
105
|
+
console.print()
|
|
106
|
+
console.print(" [cyan]from connectonion import Agent, host[/cyan]")
|
|
107
|
+
console.print()
|
|
108
|
+
console.print(" [cyan]agent = Agent('my-agent', ...)[/cyan]")
|
|
109
|
+
console.print(" [cyan]host(agent) # Starts HTTP server[/cyan]")
|
|
110
|
+
console.print()
|
|
111
|
+
console.print("[dim]See: https://docs.connectonion.com/deploy[/dim]")
|
|
112
|
+
return
|
|
113
|
+
|
|
68
114
|
# Load secrets from .env
|
|
69
115
|
secrets = dotenv_values(secrets_path) if Path(secrets_path).exists() else {}
|
|
70
116
|
|
|
@@ -76,7 +122,17 @@ def handle_deploy():
|
|
|
76
122
|
check=True,
|
|
77
123
|
)
|
|
78
124
|
|
|
125
|
+
# Show package size
|
|
126
|
+
tarball_size = tarball_path.stat().st_size
|
|
127
|
+
if tarball_size < 1024:
|
|
128
|
+
size_str = f"{tarball_size} B"
|
|
129
|
+
elif tarball_size < 1024 * 1024:
|
|
130
|
+
size_str = f"{tarball_size / 1024:.1f} KB"
|
|
131
|
+
else:
|
|
132
|
+
size_str = f"{tarball_size / (1024 * 1024):.2f} MB"
|
|
133
|
+
|
|
79
134
|
console.print(f" Project: {project_name}")
|
|
135
|
+
console.print(f" Package: {size_str}")
|
|
80
136
|
console.print(f" Secrets: {len(secrets)} keys")
|
|
81
137
|
console.print()
|
|
82
138
|
|
|
@@ -88,39 +144,77 @@ def handle_deploy():
|
|
|
88
144
|
files={"package": ("agent.tar.gz", f, "application/gzip")},
|
|
89
145
|
data={
|
|
90
146
|
"project_name": project_name,
|
|
91
|
-
"secrets":
|
|
147
|
+
"secrets": json.dumps(secrets),
|
|
92
148
|
"entrypoint": entrypoint,
|
|
93
149
|
},
|
|
94
150
|
headers={"Authorization": f"Bearer {api_key}"},
|
|
95
|
-
timeout=
|
|
151
|
+
timeout=600, # 10 minutes for docker build
|
|
96
152
|
)
|
|
97
153
|
|
|
98
154
|
if response.status_code != 200:
|
|
99
155
|
console.print(f"[red]Deploy failed: {response.text}[/red]")
|
|
100
156
|
return
|
|
101
157
|
|
|
102
|
-
|
|
158
|
+
result = response.json()
|
|
159
|
+
|
|
160
|
+
# Check for error response (backend returns 200 with error dict)
|
|
161
|
+
if "error" in result:
|
|
162
|
+
console.print(f"[red]Deploy failed: {result['error']}[/red]")
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
deployment_id = result.get("id")
|
|
166
|
+
url = result.get("url", "")
|
|
103
167
|
|
|
104
168
|
# Wait for deployment
|
|
105
169
|
console.print("Building...")
|
|
170
|
+
deploy_success = False
|
|
171
|
+
final_status = "unknown"
|
|
172
|
+
timeout_count = 0
|
|
173
|
+
|
|
106
174
|
for _ in range(100):
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
175
|
+
try:
|
|
176
|
+
status_resp = requests.get(
|
|
177
|
+
f"{API_BASE}/api/v1/deploy/{deployment_id}/status",
|
|
178
|
+
headers={"Authorization": f"Bearer {api_key}"},
|
|
179
|
+
timeout=30, # Increased timeout for slow SSH
|
|
180
|
+
)
|
|
181
|
+
except requests.exceptions.Timeout:
|
|
182
|
+
timeout_count += 1
|
|
183
|
+
if timeout_count >= 3:
|
|
184
|
+
console.print("[yellow]Status checks timing out, but deploy may still succeed.[/yellow]")
|
|
185
|
+
break
|
|
186
|
+
time.sleep(3)
|
|
187
|
+
continue
|
|
188
|
+
except requests.exceptions.RequestException as e:
|
|
189
|
+
console.print(f"[yellow]Network error: {e}[/yellow]")
|
|
190
|
+
time.sleep(3)
|
|
191
|
+
continue
|
|
192
|
+
|
|
112
193
|
if status_resp.status_code != 200:
|
|
194
|
+
console.print(f"[red]Status check failed: {status_resp.status_code}[/red]")
|
|
113
195
|
break
|
|
114
|
-
|
|
115
|
-
|
|
196
|
+
|
|
197
|
+
result = status_resp.json()
|
|
198
|
+
final_status = result.get("status", "unknown")
|
|
199
|
+
|
|
200
|
+
if final_status == "running":
|
|
201
|
+
deploy_success = True
|
|
202
|
+
# Update URL from status response (may be more up-to-date)
|
|
203
|
+
url = result.get("url") or url
|
|
116
204
|
break
|
|
117
|
-
if
|
|
118
|
-
console.print(f"[red]{
|
|
205
|
+
if final_status in ("error", "failed"):
|
|
206
|
+
console.print(f"[red]Deploy failed: {result.get('error_message', 'Unknown error')}[/red]")
|
|
119
207
|
return
|
|
120
208
|
time.sleep(3)
|
|
121
209
|
|
|
122
|
-
url = response.json().get("url", "")
|
|
123
210
|
console.print()
|
|
124
|
-
|
|
125
|
-
|
|
211
|
+
if deploy_success:
|
|
212
|
+
console.print("[bold green]Deployed![/bold green]")
|
|
213
|
+
else:
|
|
214
|
+
console.print(f"[yellow]Deploy status: {final_status}[/yellow]")
|
|
215
|
+
console.print("[yellow]Check status with 'co deployments' or try again.[/yellow]")
|
|
216
|
+
|
|
217
|
+
# Always show URL if we have one
|
|
218
|
+
if url:
|
|
219
|
+
console.print(f"Agent URL: {url}")
|
|
126
220
|
console.print()
|
|
@@ -16,10 +16,10 @@ import logging
|
|
|
16
16
|
import os
|
|
17
17
|
import time
|
|
18
18
|
import uuid
|
|
19
|
-
from dataclasses import dataclass, asdict
|
|
20
19
|
from pathlib import Path
|
|
21
|
-
from typing import Union
|
|
20
|
+
from typing import Optional, Union
|
|
22
21
|
|
|
22
|
+
from pydantic import BaseModel
|
|
23
23
|
from rich.console import Console
|
|
24
24
|
from rich.panel import Panel
|
|
25
25
|
|
|
@@ -38,15 +38,21 @@ def get_default_trust() -> str:
|
|
|
38
38
|
|
|
39
39
|
# === Types ===
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
class Session(BaseModel):
|
|
42
|
+
"""Session record for tracking agent requests.
|
|
43
|
+
|
|
44
|
+
Uses Pydantic BaseModel for:
|
|
45
|
+
- Native JSON serialization via .model_dump()
|
|
46
|
+
- Type validation
|
|
47
|
+
- API response compatibility
|
|
48
|
+
"""
|
|
43
49
|
session_id: str
|
|
44
50
|
status: str
|
|
45
51
|
prompt: str
|
|
46
|
-
result: str = None
|
|
47
|
-
created: float = None
|
|
48
|
-
expires: float = None
|
|
49
|
-
duration_ms: int = None
|
|
52
|
+
result: Optional[str] = None
|
|
53
|
+
created: Optional[float] = None
|
|
54
|
+
expires: Optional[float] = None
|
|
55
|
+
duration_ms: Optional[int] = None
|
|
50
56
|
|
|
51
57
|
|
|
52
58
|
# === Storage ===
|
|
@@ -60,7 +66,7 @@ class SessionStorage:
|
|
|
60
66
|
|
|
61
67
|
def save(self, session: Session):
|
|
62
68
|
with open(self.path, "a") as f:
|
|
63
|
-
f.write(
|
|
69
|
+
f.write(session.model_dump_json() + "\n")
|
|
64
70
|
|
|
65
71
|
def get(self, session_id: str) -> Session | None:
|
|
66
72
|
if not self.path.exists():
|
|
@@ -149,12 +155,12 @@ def input_handler(agent, storage: SessionStorage, prompt: str, result_ttl: int,
|
|
|
149
155
|
def session_handler(storage: SessionStorage, session_id: str) -> dict | None:
|
|
150
156
|
"""GET /sessions/{id}"""
|
|
151
157
|
session = storage.get(session_id)
|
|
152
|
-
return
|
|
158
|
+
return session.model_dump() if session else None
|
|
153
159
|
|
|
154
160
|
|
|
155
161
|
def sessions_handler(storage: SessionStorage) -> dict:
|
|
156
162
|
"""GET /sessions"""
|
|
157
|
-
return {"sessions": [
|
|
163
|
+
return {"sessions": [s.model_dump() for s in storage.list()]}
|
|
158
164
|
|
|
159
165
|
|
|
160
166
|
def health_handler(agent, start_time: float) -> dict:
|
|
@@ -215,79 +221,55 @@ def verify_signature(payload: dict, signature: str, public_key: str) -> bool:
|
|
|
215
221
|
def extract_and_authenticate(data: dict, trust, *, blacklist=None, whitelist=None, agent_address=None):
|
|
216
222
|
"""Extract prompt and authenticate request.
|
|
217
223
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
Simple (unsigned):
|
|
221
|
-
{"prompt": "...", "from": "0x..."}
|
|
224
|
+
ALL requests must be signed - this is a protocol requirement.
|
|
222
225
|
|
|
223
|
-
|
|
226
|
+
Required format (Ed25519 signed):
|
|
224
227
|
{
|
|
225
|
-
"payload": {"prompt": "...", "to": "
|
|
226
|
-
"from": "
|
|
227
|
-
"signature": "
|
|
228
|
+
"payload": {"prompt": "...", "to": "0xAgentAddress", "timestamp": 123},
|
|
229
|
+
"from": "0xCallerPublicKey",
|
|
230
|
+
"signature": "0xEd25519Signature..."
|
|
228
231
|
}
|
|
229
232
|
|
|
230
|
-
Trust
|
|
231
|
-
-
|
|
232
|
-
-
|
|
233
|
-
-
|
|
233
|
+
Trust levels control additional policies AFTER signature verification:
|
|
234
|
+
- "open": Any valid signer allowed
|
|
235
|
+
- "careful": Warnings for unknown signers (default)
|
|
236
|
+
- "strict": Whitelist only
|
|
237
|
+
- Custom policy/Agent: LLM evaluation
|
|
234
238
|
|
|
235
239
|
Returns: (prompt, identity, sig_valid, error)
|
|
236
240
|
"""
|
|
237
|
-
#
|
|
238
|
-
if
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
data, trust_level, blacklist=blacklist, whitelist=whitelist, agent_address=agent_address
|
|
246
|
-
)
|
|
247
|
-
else:
|
|
248
|
-
prompt, identity, sig_valid, error = _authenticate_simple(
|
|
249
|
-
data, trust_level, blacklist=blacklist, whitelist=whitelist
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
# If basic auth failed, return error
|
|
241
|
+
# Protocol requirement: ALL requests must be signed
|
|
242
|
+
if "payload" not in data or "signature" not in data:
|
|
243
|
+
return None, None, False, "unauthorized: signed request required"
|
|
244
|
+
|
|
245
|
+
# Verify signature (protocol level - always required)
|
|
246
|
+
prompt, identity, error = _authenticate_signed(
|
|
247
|
+
data, blacklist=blacklist, whitelist=whitelist, agent_address=agent_address
|
|
248
|
+
)
|
|
253
249
|
if error:
|
|
254
|
-
return prompt, identity,
|
|
250
|
+
return prompt, identity, False, error
|
|
251
|
+
|
|
252
|
+
# Trust level: additional policies AFTER signature verification
|
|
253
|
+
if trust == "strict" and whitelist and identity not in whitelist:
|
|
254
|
+
return None, identity, True, "forbidden: not in whitelist"
|
|
255
255
|
|
|
256
|
-
#
|
|
257
|
-
|
|
258
|
-
if is_custom_trust(trust) and not (isinstance(trust, str) and trust not in TRUST_LEVELS and trust_level in TRUST_LEVELS):
|
|
256
|
+
# Custom trust policy/agent evaluation
|
|
257
|
+
if is_custom_trust(trust):
|
|
259
258
|
trust_agent = create_trust_agent(trust)
|
|
260
|
-
accepted, reason = evaluate_with_trust_agent(trust_agent, prompt, identity,
|
|
259
|
+
accepted, reason = evaluate_with_trust_agent(trust_agent, prompt, identity, True)
|
|
261
260
|
if not accepted:
|
|
262
|
-
return None, identity,
|
|
263
|
-
|
|
264
|
-
return prompt, identity, sig_valid, None
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
def _authenticate_simple(data: dict, trust: str, *, blacklist=None, whitelist=None):
|
|
268
|
-
"""Authenticate simple unsigned request."""
|
|
269
|
-
prompt = data.get("prompt", "")
|
|
270
|
-
identity = data.get("from")
|
|
271
|
-
|
|
272
|
-
# Check blacklist
|
|
273
|
-
if blacklist and identity in blacklist:
|
|
274
|
-
return None, identity, False, "forbidden: blacklisted"
|
|
261
|
+
return None, identity, True, f"rejected: {reason}"
|
|
275
262
|
|
|
276
|
-
|
|
277
|
-
if whitelist and identity in whitelist:
|
|
278
|
-
return prompt, identity, True, None
|
|
263
|
+
return prompt, identity, True, None
|
|
279
264
|
|
|
280
|
-
# Trust level enforcement
|
|
281
|
-
if trust == "strict" and not identity:
|
|
282
|
-
return None, None, False, "unauthorized: identity required"
|
|
283
265
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
return prompt, identity, sig_valid, None
|
|
266
|
+
def _authenticate_signed(data: dict, *, blacklist=None, whitelist=None, agent_address=None):
|
|
267
|
+
"""Authenticate signed request with Ed25519 - ALWAYS REQUIRED.
|
|
287
268
|
|
|
269
|
+
Protocol-level signature verification. All requests must be signed.
|
|
288
270
|
|
|
289
|
-
|
|
290
|
-
"""
|
|
271
|
+
Returns: (prompt, identity, error) - error is None on success
|
|
272
|
+
"""
|
|
291
273
|
payload = data.get("payload", {})
|
|
292
274
|
identity = data.get("from")
|
|
293
275
|
signature = data.get("signature")
|
|
@@ -298,34 +280,34 @@ def _authenticate_signed(data: dict, trust: str, *, blacklist=None, whitelist=No
|
|
|
298
280
|
|
|
299
281
|
# Check blacklist first
|
|
300
282
|
if blacklist and identity in blacklist:
|
|
301
|
-
return None, identity,
|
|
283
|
+
return None, identity, "forbidden: blacklisted"
|
|
302
284
|
|
|
303
|
-
# Check whitelist (bypass signature check)
|
|
285
|
+
# Check whitelist (bypass signature check - trusted caller)
|
|
304
286
|
if whitelist and identity in whitelist:
|
|
305
|
-
return prompt, identity,
|
|
287
|
+
return prompt, identity, None
|
|
306
288
|
|
|
307
|
-
# Validate required fields
|
|
289
|
+
# Validate required fields
|
|
308
290
|
if not identity:
|
|
309
|
-
return None, None,
|
|
291
|
+
return None, None, "unauthorized: 'from' field required"
|
|
310
292
|
if not signature:
|
|
311
|
-
return None, identity,
|
|
293
|
+
return None, identity, "unauthorized: signature required"
|
|
312
294
|
if not timestamp:
|
|
313
|
-
return None, identity,
|
|
295
|
+
return None, identity, "unauthorized: timestamp required in payload"
|
|
314
296
|
|
|
315
297
|
# Check timestamp expiry (5 minute window)
|
|
316
298
|
now = time.time()
|
|
317
299
|
if abs(now - timestamp) > SIGNATURE_EXPIRY_SECONDS:
|
|
318
|
-
return None, identity,
|
|
300
|
+
return None, identity, "unauthorized: signature expired"
|
|
319
301
|
|
|
320
302
|
# Optionally verify 'to' matches agent address
|
|
321
303
|
if agent_address and to_address and to_address != agent_address:
|
|
322
|
-
return None, identity,
|
|
304
|
+
return None, identity, "unauthorized: wrong recipient"
|
|
323
305
|
|
|
324
306
|
# Verify signature
|
|
325
307
|
if not verify_signature(payload, signature, identity):
|
|
326
|
-
return None, identity,
|
|
308
|
+
return None, identity, "unauthorized: invalid signature"
|
|
327
309
|
|
|
328
|
-
return prompt, identity,
|
|
310
|
+
return prompt, identity, None
|
|
329
311
|
|
|
330
312
|
|
|
331
313
|
def get_agent_address(agent) -> str:
|
|
@@ -375,6 +357,35 @@ def is_custom_trust(trust) -> bool:
|
|
|
375
357
|
return trust not in TRUST_LEVELS # It's a policy string
|
|
376
358
|
|
|
377
359
|
|
|
360
|
+
# === Admin Handlers ===
|
|
361
|
+
|
|
362
|
+
def admin_logs_handler(agent_name: str) -> dict:
|
|
363
|
+
"""GET /admin/logs - return plain text activity log file."""
|
|
364
|
+
log_path = Path(f".co/logs/{agent_name}.log")
|
|
365
|
+
if log_path.exists():
|
|
366
|
+
return {"content": log_path.read_text()}
|
|
367
|
+
return {"error": "No logs found"}
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def admin_sessions_handler() -> dict:
|
|
371
|
+
"""GET /admin/sessions - return all activity sessions as JSON array."""
|
|
372
|
+
import yaml
|
|
373
|
+
sessions_dir = Path(".co/sessions")
|
|
374
|
+
if not sessions_dir.exists():
|
|
375
|
+
return {"sessions": []}
|
|
376
|
+
|
|
377
|
+
sessions = []
|
|
378
|
+
for session_file in sessions_dir.glob("*.yaml"):
|
|
379
|
+
with open(session_file) as f:
|
|
380
|
+
session_data = yaml.safe_load(f)
|
|
381
|
+
if session_data:
|
|
382
|
+
sessions.append(session_data)
|
|
383
|
+
|
|
384
|
+
# Sort by created date descending (newest first)
|
|
385
|
+
sessions.sort(key=lambda s: s.get("created", ""), reverse=True)
|
|
386
|
+
return {"sessions": sessions}
|
|
387
|
+
|
|
388
|
+
|
|
378
389
|
# === Entry Point ===
|
|
379
390
|
|
|
380
391
|
def _create_handlers(agent, result_ttl: int):
|
|
@@ -387,6 +398,9 @@ def _create_handlers(agent, result_ttl: int):
|
|
|
387
398
|
"info": lambda trust: info_handler(agent, trust),
|
|
388
399
|
"auth": extract_and_authenticate,
|
|
389
400
|
"ws_input": agent.input,
|
|
401
|
+
# Admin endpoints (auth required via OPENONION_API_KEY)
|
|
402
|
+
"admin_logs": lambda: admin_logs_handler(agent.name),
|
|
403
|
+
"admin_sessions": admin_sessions_handler,
|
|
390
404
|
}
|
|
391
405
|
|
|
392
406
|
|
|
@@ -422,7 +436,7 @@ def _start_relay_background(agent, relay_url: str, addr_data: dict):
|
|
|
422
436
|
|
|
423
437
|
def host(
|
|
424
438
|
agent,
|
|
425
|
-
port: int =
|
|
439
|
+
port: int = None,
|
|
426
440
|
trust: Union[str, "Agent"] = "careful",
|
|
427
441
|
result_ttl: int = 86400,
|
|
428
442
|
workers: int = 1,
|
|
@@ -437,7 +451,7 @@ def host(
|
|
|
437
451
|
|
|
438
452
|
Args:
|
|
439
453
|
agent: Agent to host
|
|
440
|
-
port: HTTP port (default 8000)
|
|
454
|
+
port: HTTP port (default: PORT env var or 8000)
|
|
441
455
|
trust: Trust level, policy, or Agent:
|
|
442
456
|
- Level: "open", "careful", "strict"
|
|
443
457
|
- Policy: Natural language or file path
|
|
@@ -457,10 +471,16 @@ def host(
|
|
|
457
471
|
GET /health - Health check
|
|
458
472
|
GET /info - Agent info
|
|
459
473
|
WS /ws - WebSocket
|
|
474
|
+
GET /logs - Activity log (requires OPENONION_API_KEY)
|
|
475
|
+
GET /logs/sessions - Activity sessions (requires OPENONION_API_KEY)
|
|
460
476
|
"""
|
|
461
477
|
import uvicorn
|
|
462
478
|
from . import address
|
|
463
479
|
|
|
480
|
+
# Use PORT env var if port not specified (for container deployments)
|
|
481
|
+
if port is None:
|
|
482
|
+
port = int(os.environ.get("PORT", 8000))
|
|
483
|
+
|
|
464
484
|
# Load or generate agent identity
|
|
465
485
|
co_dir = Path.cwd() / '.co'
|
|
466
486
|
addr_data = address.load(co_dir)
|
|
@@ -1,17 +1,22 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Purpose: Token usage tracking and cost calculation for LLM calls
|
|
3
3
|
LLM-Note:
|
|
4
|
-
Dependencies:
|
|
4
|
+
Dependencies: pydantic | imported by [llm.py, agent.py]
|
|
5
5
|
Data flow: receives model name + token counts → returns cost in USD
|
|
6
6
|
Integration: exposes TokenUsage, MODEL_PRICING, MODEL_CONTEXT_LIMITS, calculate_cost(), get_context_limit()
|
|
7
7
|
"""
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from pydantic import BaseModel
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
class TokenUsage(BaseModel):
|
|
13
|
+
"""Token usage from a single LLM call.
|
|
14
|
+
|
|
15
|
+
Uses Pydantic BaseModel for:
|
|
16
|
+
- Native JSON serialization via .model_dump()
|
|
17
|
+
- Type validation at runtime
|
|
18
|
+
- Future-proof API response compatibility
|
|
19
|
+
"""
|
|
15
20
|
input_tokens: int = 0
|
|
16
21
|
output_tokens: int = 0
|
|
17
22
|
cached_tokens: int = 0 # Tokens read from cache (subset of input_tokens)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/meta-agent/prompts/metagent.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{connectonion-0.5.3 → connectonion-0.5.6}/connectonion/cli/templates/playwright/requirements.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{connectonion-0.5.3 → connectonion-0.5.6}/connectonion/debug_agent/prompts/debug_assistant.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{connectonion-0.5.3 → connectonion-0.5.6}/connectonion/debug_explainer/root_cause_analysis_prompt.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{connectonion-0.5.3 → connectonion-0.5.6}/connectonion/execution_analyzer/execution_analysis.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{connectonion-0.5.3 → connectonion-0.5.6}/connectonion/useful_plugins/image_result_formatter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|