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
connectonion/trust.py
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Factory and coordinator for creating trust verification agents with policies
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [os, pathlib, typing, trust_agents.py, trust_functions.py] | imported by [agent.py] | tested by [tests/test_trust.py]
|
|
5
|
+
Data flow: receives trust param from Agent.__init__ → get_default_trust_level() checks CONNECTONION_ENV → create_trust_agent() validates trust param → returns Agent with trust verification tools OR None | trust param can be: None (no trust), str ("open"/"careful"/"strict" levels OR markdown policy OR file path), Path (markdown file), Agent (custom trust agent)
|
|
6
|
+
State/Effects: reads markdown files if Path/file path provided | checks os.environ for CONNECTONION_ENV and CONNECTONION_TRUST | creates Agent instances with trust_verification_tools from trust_functions.py | no writes or global state
|
|
7
|
+
Integration: exposes create_trust_agent(trust, api_key, model), get_default_trust_level(), validate_trust_level(level), TRUST_LEVELS constant | used by Agent.__init__ to create self.trust | trust agents get tools from get_trust_verification_tools() and prompts from get_trust_prompt()
|
|
8
|
+
Performance: lazy creation (only when trust param provided) | environment checks are fast | file I/O only for custom policies
|
|
9
|
+
Errors: raises TypeError for invalid trust type | raises ValueError for invalid trust level string | raises FileNotFoundError for missing policy files | heuristic detection for ambiguous strings (checks if file exists before treating as inline policy)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Union, Optional
|
|
15
|
+
|
|
16
|
+
from .trust_agents import TRUST_PROMPTS, get_trust_prompt
|
|
17
|
+
from .trust_functions import get_trust_verification_tools
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Trust level constants
|
|
21
|
+
TRUST_LEVELS = ["open", "careful", "strict"]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_default_trust_level() -> Optional[str]:
|
|
25
|
+
"""
|
|
26
|
+
Get default trust level based on environment.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Default trust level or None
|
|
30
|
+
"""
|
|
31
|
+
env = os.environ.get('CONNECTONION_ENV', '').lower()
|
|
32
|
+
|
|
33
|
+
if env == 'development':
|
|
34
|
+
return 'open'
|
|
35
|
+
elif env == 'production':
|
|
36
|
+
return 'strict'
|
|
37
|
+
elif env in ['staging', 'test']:
|
|
38
|
+
return 'careful'
|
|
39
|
+
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def create_trust_agent(trust: Union[str, Path, 'Agent', None], api_key: Optional[str] = None, model: str = "gpt-5-mini") -> Optional['Agent']:
|
|
44
|
+
"""
|
|
45
|
+
Create or return a trust agent based on the trust parameter.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
trust: Can be:
|
|
49
|
+
- None: No trust agent (returns None)
|
|
50
|
+
- str: Trust level ("open", "careful", "strict") or markdown policy/file path
|
|
51
|
+
- Path: Path to markdown policy file
|
|
52
|
+
- Agent: Custom trust agent (returned as-is)
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
An Agent configured for trust verification, or None
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
TypeError: If trust is not a valid type
|
|
59
|
+
ValueError: If trust level is invalid
|
|
60
|
+
FileNotFoundError: If trust policy file doesn't exist
|
|
61
|
+
"""
|
|
62
|
+
from .agent import Agent # Import here to avoid circular dependency
|
|
63
|
+
|
|
64
|
+
# If None, check for environment default
|
|
65
|
+
if trust is None:
|
|
66
|
+
env_trust = os.environ.get('CONNECTONION_TRUST')
|
|
67
|
+
if env_trust:
|
|
68
|
+
trust = env_trust
|
|
69
|
+
else:
|
|
70
|
+
return None # No trust agent
|
|
71
|
+
|
|
72
|
+
# If it's already an Agent, validate and return it
|
|
73
|
+
if isinstance(trust, Agent):
|
|
74
|
+
if not trust.tools:
|
|
75
|
+
raise ValueError("Trust agent must have verification tools")
|
|
76
|
+
return trust
|
|
77
|
+
|
|
78
|
+
# Get trust verification tools
|
|
79
|
+
trust_tools = get_trust_verification_tools()
|
|
80
|
+
|
|
81
|
+
# Handle Path object
|
|
82
|
+
if isinstance(trust, Path):
|
|
83
|
+
if not trust.exists():
|
|
84
|
+
raise FileNotFoundError(f"Trust policy file not found: {trust}")
|
|
85
|
+
policy = trust.read_text(encoding='utf-8')
|
|
86
|
+
return Agent(
|
|
87
|
+
name="trust_agent_custom",
|
|
88
|
+
tools=trust_tools,
|
|
89
|
+
system_prompt=policy,
|
|
90
|
+
api_key=api_key,
|
|
91
|
+
model=model
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Handle string parameter
|
|
95
|
+
if isinstance(trust, str):
|
|
96
|
+
trust_lower = trust.lower()
|
|
97
|
+
|
|
98
|
+
# Check if it's a trust level
|
|
99
|
+
if trust_lower in TRUST_LEVELS:
|
|
100
|
+
return Agent(
|
|
101
|
+
name=f"trust_agent_{trust_lower}",
|
|
102
|
+
tools=trust_tools,
|
|
103
|
+
system_prompt=get_trust_prompt(trust_lower),
|
|
104
|
+
api_key=api_key,
|
|
105
|
+
model=model
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Check if it looks like a trust level but isn't valid
|
|
109
|
+
# (single word without path separators or newlines)
|
|
110
|
+
if '\n' not in trust and '/' not in trust and '\\' not in trust and ' ' not in trust and len(trust) < 20:
|
|
111
|
+
# It's a single word, probably meant to be a trust level
|
|
112
|
+
if not os.path.exists(trust): # And it's not a file
|
|
113
|
+
raise ValueError(f"Invalid trust level: {trust}. Must be one of: {', '.join(TRUST_LEVELS)}")
|
|
114
|
+
|
|
115
|
+
# Check if it's a file path (contains path separators or ends with .md)
|
|
116
|
+
if '/' in trust or '\\' in trust or trust.endswith('.md'):
|
|
117
|
+
path = Path(trust)
|
|
118
|
+
if not path.exists():
|
|
119
|
+
raise FileNotFoundError(f"Trust policy file not found: {trust}")
|
|
120
|
+
policy = path.read_text(encoding='utf-8')
|
|
121
|
+
return Agent(
|
|
122
|
+
name="trust_agent_custom",
|
|
123
|
+
tools=trust_tools,
|
|
124
|
+
system_prompt=policy,
|
|
125
|
+
api_key=api_key,
|
|
126
|
+
model=model
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
# Check if it's a single-line string that could be a file
|
|
130
|
+
if '\n' not in trust and len(trust) < 100:
|
|
131
|
+
# Could be a file path without obvious markers
|
|
132
|
+
path = Path(trust)
|
|
133
|
+
if path.exists():
|
|
134
|
+
policy = path.read_text(encoding='utf-8')
|
|
135
|
+
return Agent(
|
|
136
|
+
name="trust_agent_custom",
|
|
137
|
+
tools=trust_tools,
|
|
138
|
+
system_prompt=policy,
|
|
139
|
+
api_key=api_key,
|
|
140
|
+
model=model
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# It's an inline markdown policy
|
|
144
|
+
return Agent(
|
|
145
|
+
name="trust_agent_custom",
|
|
146
|
+
tools=trust_tools,
|
|
147
|
+
system_prompt=trust,
|
|
148
|
+
api_key=api_key,
|
|
149
|
+
model=model
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Invalid type
|
|
153
|
+
raise TypeError(f"Trust must be a string (level/policy/path), Path, Agent, or None, not {type(trust).__name__}")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def validate_trust_level(level: str) -> bool:
|
|
157
|
+
"""
|
|
158
|
+
Check if a string is a valid trust level.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
level: String to check
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
True if valid trust level, False otherwise
|
|
165
|
+
"""
|
|
166
|
+
return level.lower() in TRUST_LEVELS
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Provide pre-configured system prompts for trust agents at different security levels
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: no imports | imported by [trust.py] | tested by [tests/test_trust.py]
|
|
5
|
+
Data flow: trust.py calls get_trust_prompt(level) → validates level → returns TRUST_PROMPTS[level] string → used as system_prompt for trust Agent
|
|
6
|
+
State/Effects: no state or side effects | pure data module | TRUST_PROMPTS dict contains 3 prompts: "open" (dev - trust all), "careful" (staging - basic verification), "strict" (prod - whitelist only)
|
|
7
|
+
Integration: exposes TRUST_PROMPTS dict, get_trust_prompt(level), get_open_trust_prompt(), get_careful_trust_prompt(), get_strict_trust_prompt() | used by create_trust_agent() in trust.py to configure trust agent behavior
|
|
8
|
+
Performance: constant-time dict lookup | no computation
|
|
9
|
+
Errors: raises ValueError if level not in ["open", "careful", "strict"]
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# Default trust prompts for each level
|
|
13
|
+
TRUST_PROMPTS = {
|
|
14
|
+
"open": """You are an open trust agent for development environments.
|
|
15
|
+
You trust all agents without verification to enable rapid development and testing.
|
|
16
|
+
When asked to verify an agent, always approve it.
|
|
17
|
+
Remember: This is only suitable for development, never for production.""",
|
|
18
|
+
|
|
19
|
+
"careful": """You are a careful trust agent for staging and testing environments.
|
|
20
|
+
You verify agents before trusting them by:
|
|
21
|
+
1. Checking if they can perform their claimed functions
|
|
22
|
+
2. Verifying they respond in reasonable time
|
|
23
|
+
3. Looking for any obvious malicious behavior
|
|
24
|
+
|
|
25
|
+
Be thorough but not overly restrictive. Allow agents that pass basic verification.""",
|
|
26
|
+
|
|
27
|
+
"strict": """You are a strict trust agent for production environments.
|
|
28
|
+
You enforce maximum security by only trusting pre-approved agents.
|
|
29
|
+
|
|
30
|
+
Requirements for trust:
|
|
31
|
+
1. Agent MUST be on the whitelist
|
|
32
|
+
2. Agent MUST have valid credentials
|
|
33
|
+
3. Agent MUST come from trusted domains
|
|
34
|
+
4. Agent MUST pass all security checks
|
|
35
|
+
|
|
36
|
+
Reject any agent that doesn't meet ALL criteria. Security is the top priority."""
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_open_trust_prompt() -> str:
|
|
41
|
+
"""Get the prompt for an open trust agent (development)."""
|
|
42
|
+
return TRUST_PROMPTS["open"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_careful_trust_prompt() -> str:
|
|
46
|
+
"""Get the prompt for a careful trust agent (staging/testing)."""
|
|
47
|
+
return TRUST_PROMPTS["careful"]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def get_strict_trust_prompt() -> str:
|
|
51
|
+
"""Get the prompt for a strict trust agent (production)."""
|
|
52
|
+
return TRUST_PROMPTS["strict"]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def get_trust_prompt(level: str) -> str:
|
|
56
|
+
"""
|
|
57
|
+
Get the trust prompt for a given level.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
level: Trust level ("open", "careful", or "strict")
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
The trust prompt for that level
|
|
64
|
+
|
|
65
|
+
Raises:
|
|
66
|
+
ValueError: If level is not valid
|
|
67
|
+
"""
|
|
68
|
+
level_lower = level.lower()
|
|
69
|
+
if level_lower not in TRUST_PROMPTS:
|
|
70
|
+
raise ValueError(f"Invalid trust level: {level}. Must be one of: {', '.join(TRUST_PROMPTS.keys())}")
|
|
71
|
+
return TRUST_PROMPTS[level_lower]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Provide tool functions for trust agents to verify other agents
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [pathlib, typing] | imported by [trust.py] | tested by [tests/test_trust.py]
|
|
5
|
+
Data flow: create_trust_agent() calls get_trust_verification_tools() → returns list of [check_whitelist, test_capability, verify_agent] functions → these become tools for trust Agent → trust agent calls tools with agent_id → functions return descriptive strings → AI interprets results per trust policy
|
|
6
|
+
State/Effects: check_whitelist() reads ~/.connectonion/trusted.txt file if exists | supports wildcard patterns with * | test_capability() and verify_agent() are pure (no I/O, just return descriptive strings for AI)
|
|
7
|
+
Integration: exposes check_whitelist(agent_id), test_capability(agent_id, test, expected), verify_agent(agent_id, agent_info), get_trust_verification_tools() | used by trust.py to equip trust agents with verification capabilities
|
|
8
|
+
Performance: file read only for check_whitelist | simple string operations | no network calls
|
|
9
|
+
Errors: check_whitelist() catches file read exceptions and returns error string | missing whitelist file returns helpful message | no exceptions raised (errors returned as strings for AI interpretation)
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import List, Callable
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def check_whitelist(agent_id: str) -> str:
|
|
17
|
+
"""
|
|
18
|
+
Check if an agent is on the whitelist.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
agent_id: Identifier of the agent to check
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
String indicating if agent is whitelisted or not
|
|
25
|
+
"""
|
|
26
|
+
whitelist_path = Path.home() / ".connectonion" / "trusted.txt"
|
|
27
|
+
if whitelist_path.exists():
|
|
28
|
+
try:
|
|
29
|
+
whitelist = whitelist_path.read_text(encoding='utf-8')
|
|
30
|
+
lines = whitelist.strip().split('\n')
|
|
31
|
+
for line in lines:
|
|
32
|
+
line = line.strip()
|
|
33
|
+
if not line or line.startswith('#'):
|
|
34
|
+
continue
|
|
35
|
+
if line == agent_id:
|
|
36
|
+
return f"{agent_id} is on the whitelist"
|
|
37
|
+
# Simple wildcard support
|
|
38
|
+
if '*' in line:
|
|
39
|
+
pattern = line.replace('*', '')
|
|
40
|
+
if pattern in agent_id:
|
|
41
|
+
return f"{agent_id} matches whitelist pattern: {line}"
|
|
42
|
+
return f"{agent_id} is NOT on the whitelist"
|
|
43
|
+
except Exception as e:
|
|
44
|
+
return f"Error reading whitelist: {e}"
|
|
45
|
+
return "No whitelist file found at ~/.connectonion/trusted.txt"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_capability(agent_id: str, test: str, expected: str) -> str:
|
|
49
|
+
"""
|
|
50
|
+
Test an agent's capability with a specific test.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
agent_id: Identifier of the agent to test
|
|
54
|
+
test: The test to perform
|
|
55
|
+
expected: The expected result
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Test description for the trust agent to evaluate
|
|
59
|
+
"""
|
|
60
|
+
return f"Testing {agent_id} with: {test}, expecting: {expected}"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def verify_agent(agent_id: str, agent_info: str = "") -> str:
|
|
64
|
+
"""
|
|
65
|
+
General verification of an agent.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
agent_id: Identifier of the agent
|
|
69
|
+
agent_info: Additional information about the agent
|
|
70
|
+
|
|
71
|
+
Returns:
|
|
72
|
+
Verification context for the trust agent
|
|
73
|
+
"""
|
|
74
|
+
return f"Verifying agent: {agent_id}. Info: {agent_info}"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def get_trust_verification_tools() -> List[Callable]:
|
|
78
|
+
"""
|
|
79
|
+
Get the list of trust verification tools.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
List of trust verification functions
|
|
83
|
+
"""
|
|
84
|
+
return [
|
|
85
|
+
check_whitelist,
|
|
86
|
+
test_capability,
|
|
87
|
+
verify_agent
|
|
88
|
+
]
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Purpose: Terminal UI components for interactive agent interfaces with powerline-style rendering
|
|
3
|
+
LLM-Note:
|
|
4
|
+
Dependencies: imports from [input, dropdown, providers, keys, fuzzy, status_bar, divider, pick, footer] | imported by [useful_tools/__init__.py, useful_tools/terminal.py] | requires rich library
|
|
5
|
+
Data flow: agent/CLI imports TUI components → components render to terminal via Rich → user interacts via keyboard → components return selected values
|
|
6
|
+
State/Effects: manages terminal state during interaction | raw mode for keyboard input | restores terminal on exit
|
|
7
|
+
Integration: exposes Input (text with autocomplete), Dropdown, FileProvider/StaticProvider (autocomplete sources), StatusBar/SimpleStatusBar/ProgressSegment, Divider, pick (single-select), Footer | keyboard handling via getch/read_key | fuzzy matching via fuzzy_match/highlight_match
|
|
8
|
+
Performance: renders to terminal in real-time | fuzzy matching is O(n*m) | file provider reads filesystem
|
|
9
|
+
Errors: KeyboardInterrupt handled for clean exit | terminal restoration on exception
|
|
10
|
+
|
|
11
|
+
Powerline-style terminal components inspired by powerlevel10k.
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
from connectonion.tui import Input, FileProvider, StatusBar, Divider
|
|
15
|
+
|
|
16
|
+
# Status bar with model/context/git info
|
|
17
|
+
status = StatusBar([
|
|
18
|
+
("🤖", "co/gemini-2.5-pro", "magenta"),
|
|
19
|
+
("📊", "50%", "green"),
|
|
20
|
+
("", "main", "blue"),
|
|
21
|
+
])
|
|
22
|
+
console.print(status.render())
|
|
23
|
+
|
|
24
|
+
# Minimal input with @ file autocomplete
|
|
25
|
+
text = Input(triggers={"@": FileProvider()}).run()
|
|
26
|
+
|
|
27
|
+
# Divider line
|
|
28
|
+
console.print(Divider().render())
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
from .input import Input
|
|
32
|
+
from .dropdown import Dropdown, DropdownItem
|
|
33
|
+
from .providers import FileProvider, StaticProvider
|
|
34
|
+
from .keys import getch, read_key
|
|
35
|
+
from .fuzzy import fuzzy_match, highlight_match
|
|
36
|
+
from .status_bar import StatusBar, SimpleStatusBar, ProgressSegment
|
|
37
|
+
from .divider import Divider
|
|
38
|
+
from .pick import pick
|
|
39
|
+
from .footer import Footer
|
|
40
|
+
|
|
41
|
+
__all__ = [
|
|
42
|
+
"Input",
|
|
43
|
+
"Dropdown",
|
|
44
|
+
"DropdownItem",
|
|
45
|
+
"FileProvider",
|
|
46
|
+
"StaticProvider",
|
|
47
|
+
"getch",
|
|
48
|
+
"read_key",
|
|
49
|
+
"fuzzy_match",
|
|
50
|
+
"highlight_match",
|
|
51
|
+
"StatusBar",
|
|
52
|
+
"SimpleStatusBar",
|
|
53
|
+
"ProgressSegment",
|
|
54
|
+
"Divider",
|
|
55
|
+
"pick",
|
|
56
|
+
"Footer",
|
|
57
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Divider - Simple horizontal line separator.
|
|
2
|
+
|
|
3
|
+
A minimal line to separate sections in terminal UI.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from rich.text import Text
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Divider:
|
|
10
|
+
"""Simple horizontal divider line.
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
from connectonion.tui import Divider
|
|
14
|
+
|
|
15
|
+
divider = Divider()
|
|
16
|
+
console.print(divider.render())
|
|
17
|
+
|
|
18
|
+
# Custom width
|
|
19
|
+
divider = Divider(width=40)
|
|
20
|
+
console.print(divider.render())
|
|
21
|
+
|
|
22
|
+
Output:
|
|
23
|
+
────────────────────────────────────
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, width: int = 40, char: str = "─", style: str = "dim"):
|
|
27
|
+
"""
|
|
28
|
+
Args:
|
|
29
|
+
width: Width of the divider line
|
|
30
|
+
char: Character to use for the line
|
|
31
|
+
style: Rich style for the line
|
|
32
|
+
"""
|
|
33
|
+
self.width = width
|
|
34
|
+
self.char = char
|
|
35
|
+
self.style = style
|
|
36
|
+
|
|
37
|
+
def render(self) -> Text:
|
|
38
|
+
"""Render the divider line."""
|
|
39
|
+
return Text(self.char * self.width, style=self.style)
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Dropdown - reusable selection list component.
|
|
2
|
+
|
|
3
|
+
Modern zsh-style dropdown with icons and highlighting.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from rich.text import Text
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from rich.console import Group
|
|
12
|
+
|
|
13
|
+
from .fuzzy import highlight_match
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class DropdownItem:
|
|
18
|
+
"""Structured item for dropdown display.
|
|
19
|
+
|
|
20
|
+
Supports rich metadata for different display styles.
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
# Simple item
|
|
24
|
+
item = DropdownItem(display="/today", value="/today")
|
|
25
|
+
|
|
26
|
+
# With description (shows on second line or right side)
|
|
27
|
+
item = DropdownItem(
|
|
28
|
+
display="/today",
|
|
29
|
+
value="/today",
|
|
30
|
+
description="Daily email briefing",
|
|
31
|
+
icon="📅"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
# Contact style
|
|
35
|
+
item = DropdownItem(
|
|
36
|
+
display="Davis Baer",
|
|
37
|
+
value="davis@oneupapp.io",
|
|
38
|
+
description="davis@oneupapp.io",
|
|
39
|
+
subtitle="OneUp · founder",
|
|
40
|
+
icon="👤"
|
|
41
|
+
)
|
|
42
|
+
"""
|
|
43
|
+
display: str # Main text to show
|
|
44
|
+
value: Any # Value to return when selected
|
|
45
|
+
score: int = 0 # Match score for sorting
|
|
46
|
+
positions: list[int] = field(default_factory=list) # Matched char positions
|
|
47
|
+
description: str = "" # Secondary text (right side or second line)
|
|
48
|
+
subtitle: str = "" # Third line or additional context
|
|
49
|
+
icon: str = "" # Left icon (emoji or nerd font)
|
|
50
|
+
style: str = "" # Rich style for the display text
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
def from_tuple(cls, item: tuple) -> "DropdownItem":
|
|
54
|
+
"""Convert old tuple format to DropdownItem for backward compatibility.
|
|
55
|
+
|
|
56
|
+
Supports:
|
|
57
|
+
(display, value, score, positions)
|
|
58
|
+
(display, value, score, positions, metadata_dict)
|
|
59
|
+
"""
|
|
60
|
+
if isinstance(item, DropdownItem):
|
|
61
|
+
return item
|
|
62
|
+
|
|
63
|
+
display, value, score, positions = item[:4]
|
|
64
|
+
metadata = item[4] if len(item) > 4 else {}
|
|
65
|
+
|
|
66
|
+
return cls(
|
|
67
|
+
display=display,
|
|
68
|
+
value=value,
|
|
69
|
+
score=score,
|
|
70
|
+
positions=positions or [],
|
|
71
|
+
description=metadata.get("description", ""),
|
|
72
|
+
subtitle=metadata.get("subtitle", ""),
|
|
73
|
+
icon=metadata.get("icon", ""),
|
|
74
|
+
style=metadata.get("style", ""),
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# File type icons (requires Nerd Font for best results, fallback to unicode)
|
|
79
|
+
ICONS = {
|
|
80
|
+
"folder": "📁",
|
|
81
|
+
"python": "🐍",
|
|
82
|
+
"javascript": "📜",
|
|
83
|
+
"typescript": "📘",
|
|
84
|
+
"json": "📋",
|
|
85
|
+
"markdown": "📝",
|
|
86
|
+
"yaml": "⚙️",
|
|
87
|
+
"default": "📄",
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_file_icon(name: str) -> str:
|
|
92
|
+
"""Get icon for file based on extension."""
|
|
93
|
+
if name.endswith('/'):
|
|
94
|
+
return ICONS["folder"]
|
|
95
|
+
ext = name.rsplit('.', 1)[-1].lower() if '.' in name else ""
|
|
96
|
+
if ext == "py":
|
|
97
|
+
return ICONS["python"]
|
|
98
|
+
elif ext in ("js", "jsx"):
|
|
99
|
+
return ICONS["javascript"]
|
|
100
|
+
elif ext in ("ts", "tsx"):
|
|
101
|
+
return ICONS["typescript"]
|
|
102
|
+
elif ext == "json":
|
|
103
|
+
return ICONS["json"]
|
|
104
|
+
elif ext in ("md", "mdx"):
|
|
105
|
+
return ICONS["markdown"]
|
|
106
|
+
elif ext in ("yml", "yaml"):
|
|
107
|
+
return ICONS["yaml"]
|
|
108
|
+
return ICONS["default"]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class Dropdown:
|
|
112
|
+
"""Dropdown selection list with keyboard navigation.
|
|
113
|
+
|
|
114
|
+
Modern zsh-style with icons and fuzzy match highlighting.
|
|
115
|
+
Supports DropdownItem for rich metadata display.
|
|
116
|
+
|
|
117
|
+
Usage:
|
|
118
|
+
dropdown = Dropdown(max_visible=8)
|
|
119
|
+
|
|
120
|
+
# Old tuple format (backward compatible)
|
|
121
|
+
dropdown.set_items([
|
|
122
|
+
("agent.py", "agent.py", 10, [0,1,2]),
|
|
123
|
+
("main.py", "main.py", 5, [0,1]),
|
|
124
|
+
])
|
|
125
|
+
|
|
126
|
+
# New DropdownItem format
|
|
127
|
+
dropdown.set_items([
|
|
128
|
+
DropdownItem("/today", "/today", description="Daily briefing", icon="📅"),
|
|
129
|
+
DropdownItem("/inbox", "/inbox", description="Show emails", icon="📥"),
|
|
130
|
+
])
|
|
131
|
+
|
|
132
|
+
dropdown.down() # Move selection
|
|
133
|
+
selected = dropdown.selected_value # Get current selection
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
def __init__(self, max_visible: int = 8, show_icons: bool = True):
|
|
137
|
+
self.max_visible = max_visible
|
|
138
|
+
self.show_icons = show_icons
|
|
139
|
+
self.items: list[DropdownItem] = []
|
|
140
|
+
self.selected_index = 0
|
|
141
|
+
|
|
142
|
+
def set_items(self, items: list):
|
|
143
|
+
"""Set items. Accepts DropdownItem or old tuple format."""
|
|
144
|
+
converted = []
|
|
145
|
+
for item in items[:self.max_visible]:
|
|
146
|
+
if isinstance(item, DropdownItem):
|
|
147
|
+
converted.append(item)
|
|
148
|
+
else:
|
|
149
|
+
converted.append(DropdownItem.from_tuple(item))
|
|
150
|
+
self.items = converted
|
|
151
|
+
self.selected_index = 0
|
|
152
|
+
|
|
153
|
+
def clear(self):
|
|
154
|
+
"""Clear all items."""
|
|
155
|
+
self.items = []
|
|
156
|
+
self.selected_index = 0
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def is_empty(self) -> bool:
|
|
160
|
+
return len(self.items) == 0
|
|
161
|
+
|
|
162
|
+
@property
|
|
163
|
+
def selected_value(self):
|
|
164
|
+
"""Get currently selected value."""
|
|
165
|
+
if self.items and self.selected_index < len(self.items):
|
|
166
|
+
return self.items[self.selected_index].value
|
|
167
|
+
return None
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def selected_display(self) -> str:
|
|
171
|
+
"""Get currently selected display text."""
|
|
172
|
+
if self.items and self.selected_index < len(self.items):
|
|
173
|
+
return self.items[self.selected_index].display
|
|
174
|
+
return ""
|
|
175
|
+
|
|
176
|
+
def up(self):
|
|
177
|
+
"""Move selection up."""
|
|
178
|
+
if self.items:
|
|
179
|
+
self.selected_index = (self.selected_index - 1) % len(self.items)
|
|
180
|
+
|
|
181
|
+
def down(self):
|
|
182
|
+
"""Move selection down."""
|
|
183
|
+
if self.items:
|
|
184
|
+
self.selected_index = (self.selected_index + 1) % len(self.items)
|
|
185
|
+
|
|
186
|
+
def _get_icon(self, item: DropdownItem) -> str:
|
|
187
|
+
"""Get icon for item - use item.icon if set, otherwise infer from filename."""
|
|
188
|
+
if item.icon:
|
|
189
|
+
return item.icon
|
|
190
|
+
if self.show_icons:
|
|
191
|
+
return get_file_icon(item.display)
|
|
192
|
+
return ""
|
|
193
|
+
|
|
194
|
+
def render(self) -> Table:
|
|
195
|
+
"""Render dropdown as Rich Table.
|
|
196
|
+
|
|
197
|
+
Supports multiple display modes based on DropdownItem fields:
|
|
198
|
+
- Simple: just display text with icon
|
|
199
|
+
- With description: display + description on right
|
|
200
|
+
- With subtitle: two-line display
|
|
201
|
+
|
|
202
|
+
Selected item has light background for visibility on both light/dark terminals.
|
|
203
|
+
"""
|
|
204
|
+
table = Table(show_header=False, box=None, padding=(0, 0), show_edge=False)
|
|
205
|
+
|
|
206
|
+
for i, item in enumerate(self.items):
|
|
207
|
+
is_selected = i == self.selected_index
|
|
208
|
+
row = Text()
|
|
209
|
+
|
|
210
|
+
# Selection indicator
|
|
211
|
+
if is_selected:
|
|
212
|
+
row.append(" ❯ ", style="bold green")
|
|
213
|
+
else:
|
|
214
|
+
row.append(" ", style="dim")
|
|
215
|
+
|
|
216
|
+
# Icon
|
|
217
|
+
icon = self._get_icon(item)
|
|
218
|
+
if icon:
|
|
219
|
+
if is_selected:
|
|
220
|
+
row.append(f"{icon} ", style="bold")
|
|
221
|
+
else:
|
|
222
|
+
row.append(f"{icon} ", style="dim")
|
|
223
|
+
|
|
224
|
+
# Main display text with highlighting
|
|
225
|
+
display_style = item.style if item.style else ""
|
|
226
|
+
highlighted = highlight_match(item.display, item.positions)
|
|
227
|
+
if display_style:
|
|
228
|
+
highlighted.stylize(display_style)
|
|
229
|
+
row.append_text(highlighted)
|
|
230
|
+
|
|
231
|
+
# Description (right side, dimmed)
|
|
232
|
+
if item.description:
|
|
233
|
+
row.append(" ", style="dim")
|
|
234
|
+
row.append(item.description, style="dim italic")
|
|
235
|
+
|
|
236
|
+
# Add background to selected row
|
|
237
|
+
if is_selected:
|
|
238
|
+
row.stylize("on bright_black")
|
|
239
|
+
|
|
240
|
+
table.add_row(row)
|
|
241
|
+
|
|
242
|
+
# Subtitle as second line (only for selected or all items with subtitle)
|
|
243
|
+
if item.subtitle and is_selected:
|
|
244
|
+
subtitle_row = Text()
|
|
245
|
+
subtitle_row.append(" ", style="dim") # Indent to align with text
|
|
246
|
+
subtitle_row.append(item.subtitle, style="dim")
|
|
247
|
+
if is_selected:
|
|
248
|
+
subtitle_row.stylize("on bright_black")
|
|
249
|
+
table.add_row(subtitle_row)
|
|
250
|
+
|
|
251
|
+
return table
|