connectonion 0.6.4__py3-none-any.whl → 0.6.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. connectonion/__init__.py +1 -1
  2. connectonion/cli/co_ai/main.py +2 -2
  3. connectonion/cli/co_ai/prompts/connectonion/concepts/trust.md +166 -208
  4. connectonion/cli/commands/copy_commands.py +21 -0
  5. connectonion/cli/commands/trust_commands.py +152 -0
  6. connectonion/cli/main.py +82 -0
  7. connectonion/core/llm.py +2 -2
  8. connectonion/docs/concepts/fast_rules.md +237 -0
  9. connectonion/docs/concepts/onboarding.md +465 -0
  10. connectonion/docs/concepts/trust.md +933 -192
  11. connectonion/docs/design-decisions/023-trust-policy-system-design.md +323 -0
  12. connectonion/docs/network/README.md +23 -1
  13. connectonion/docs/network/connect.md +135 -0
  14. connectonion/docs/network/host.md +73 -4
  15. connectonion/network/__init__.py +7 -6
  16. connectonion/network/asgi/__init__.py +3 -0
  17. connectonion/network/asgi/http.py +125 -19
  18. connectonion/network/asgi/websocket.py +276 -15
  19. connectonion/network/connect.py +145 -29
  20. connectonion/network/host/auth.py +70 -67
  21. connectonion/network/host/routes.py +88 -3
  22. connectonion/network/host/server.py +100 -17
  23. connectonion/network/trust/__init__.py +27 -19
  24. connectonion/network/trust/factory.py +51 -24
  25. connectonion/network/trust/fast_rules.py +100 -0
  26. connectonion/network/trust/tools.py +316 -32
  27. connectonion/network/trust/trust_agent.py +403 -0
  28. connectonion/transcribe.py +1 -1
  29. {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/METADATA +1 -1
  30. {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/RECORD +32 -27
  31. connectonion/network/trust/prompts.py +0 -71
  32. {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/WHEEL +0 -0
  33. {connectonion-0.6.4.dist-info → connectonion-0.6.5.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,152 @@
1
+ """
2
+ CLI commands for managing trust lists (contacts, whitelist, blocklist, admins).
3
+
4
+ Addresses are shown in full (not truncated) so users can easily copy them
5
+ for use in other commands or configuration files.
6
+
7
+ Usage:
8
+ co trust list # List all trust lists
9
+ co trust level <address> # Check trust level of address
10
+ co trust add <address> # Add to contacts (default)
11
+ co trust add <address> -w # Add to whitelist
12
+ co trust remove <address> # Remove from all lists
13
+ co trust block <address> # Block an address
14
+ co trust unblock <address> # Unblock an address
15
+ co trust admin add <address> # Add admin (super admin only)
16
+ co trust admin remove <address> # Remove admin (super admin only)
17
+ """
18
+
19
+ from pathlib import Path
20
+ from rich.console import Console
21
+ from rich.table import Table
22
+
23
+ from ...network.trust.tools import (
24
+ CO_DIR,
25
+ get_level,
26
+ promote_to_contact,
27
+ promote_to_whitelist,
28
+ demote_to_stranger,
29
+ block,
30
+ unblock,
31
+ add_admin,
32
+ remove_admin,
33
+ load_admins,
34
+ get_self_address,
35
+ )
36
+
37
+ console = Console()
38
+
39
+
40
+ def _read_list(list_name: str) -> list[str]:
41
+ """Read entries from a list file."""
42
+ list_path = CO_DIR / f"{list_name}.txt"
43
+ if not list_path.exists():
44
+ return []
45
+ content = list_path.read_text(encoding='utf-8')
46
+ return [line.strip() for line in content.splitlines()
47
+ if line.strip() and not line.startswith('#')]
48
+
49
+
50
+ def handle_trust_list():
51
+ """List all trust lists."""
52
+ contacts = _read_list("contacts")
53
+ whitelist = _read_list("whitelist")
54
+ blocklist = _read_list("blocklist")
55
+ admins = load_admins()
56
+ self_addr = get_self_address()
57
+
58
+ console.print()
59
+
60
+ # Admins
61
+ console.print("[bold]Admins[/bold]")
62
+ if admins:
63
+ for addr in admins:
64
+ label = " [dim](self)[/dim]" if addr == self_addr else ""
65
+ console.print(f" {addr}{label}")
66
+ else:
67
+ console.print(" [dim]No admins configured[/dim]")
68
+ console.print()
69
+
70
+ # Whitelist
71
+ console.print("[bold]Whitelist[/bold] [dim](full trust)[/dim]")
72
+ if whitelist:
73
+ for addr in whitelist:
74
+ console.print(f" {addr}")
75
+ else:
76
+ console.print(" [dim]Empty[/dim]")
77
+ console.print()
78
+
79
+ # Contacts
80
+ console.print("[bold]Contacts[/bold] [dim](verified via invite/payment)[/dim]")
81
+ if contacts:
82
+ for addr in contacts:
83
+ console.print(f" {addr}")
84
+ else:
85
+ console.print(" [dim]Empty[/dim]")
86
+ console.print()
87
+
88
+ # Blocklist
89
+ console.print("[bold]Blocklist[/bold] [dim](denied access)[/dim]")
90
+ if blocklist:
91
+ for addr in blocklist:
92
+ console.print(f" [red]{addr}[/red]")
93
+ else:
94
+ console.print(" [dim]Empty[/dim]")
95
+ console.print()
96
+
97
+ console.print(f"[dim]Lists stored in: {CO_DIR}[/dim]")
98
+
99
+
100
+ def handle_trust_level(address: str):
101
+ """Check trust level of an address."""
102
+ level = get_level(address)
103
+
104
+ level_colors = {
105
+ "stranger": "dim",
106
+ "contact": "cyan",
107
+ "whitelist": "green",
108
+ "blocked": "red",
109
+ }
110
+ color = level_colors.get(level, "white")
111
+
112
+ console.print(f"\n{address}: [{color}]{level}[/{color}]\n")
113
+
114
+
115
+ def handle_trust_add(address: str, whitelist: bool = False):
116
+ """Add address to contacts or whitelist."""
117
+ if whitelist:
118
+ result = promote_to_whitelist(address)
119
+ console.print(f"\n[green]✓[/green] {result}\n")
120
+ else:
121
+ result = promote_to_contact(address)
122
+ console.print(f"\n[green]✓[/green] {result}\n")
123
+
124
+
125
+ def handle_trust_remove(address: str):
126
+ """Remove address from all lists (demote to stranger)."""
127
+ result = demote_to_stranger(address)
128
+ console.print(f"\n[yellow]✓[/yellow] {result}\n")
129
+
130
+
131
+ def handle_trust_block(address: str, reason: str = ""):
132
+ """Block an address."""
133
+ result = block(address, reason)
134
+ console.print(f"\n[red]✓[/red] {result}\n")
135
+
136
+
137
+ def handle_trust_unblock(address: str):
138
+ """Unblock an address."""
139
+ result = unblock(address)
140
+ console.print(f"\n[green]✓[/green] {result}\n")
141
+
142
+
143
+ def handle_admin_add(address: str):
144
+ """Add an admin."""
145
+ result = add_admin(address)
146
+ console.print(f"\n[green]✓[/green] {result}\n")
147
+
148
+
149
+ def handle_admin_remove(address: str):
150
+ """Remove an admin."""
151
+ result = remove_admin(address)
152
+ console.print(f"\n[yellow]✓[/yellow] {result}\n")
connectonion/cli/main.py CHANGED
@@ -56,6 +56,7 @@ def _show_help():
56
56
  console.print(" [green]init[/green] Initialize in current directory")
57
57
  console.print(" [green]copy[/green] <name> Copy tool/plugin source to project")
58
58
  console.print(" [green]eval[/green] Run evals and show status")
59
+ console.print(" [green]trust[/green] Manage trust lists")
59
60
  console.print(" [green]deploy[/green] Deploy to ConnectOnion Cloud")
60
61
  console.print(" [green]auth[/green] Authenticate for managed keys")
61
62
  console.print(" [green]status[/green] Check account balance")
@@ -174,6 +175,87 @@ def eval(
174
175
  handle_eval(name=name, agent_file=agent)
175
176
 
176
177
 
178
+ # Trust command group
179
+ trust_app = typer.Typer(help="Manage trust lists (contacts, whitelist, blocklist, admins)")
180
+ app.add_typer(trust_app, name="trust")
181
+
182
+
183
+ @trust_app.callback(invoke_without_command=True)
184
+ def trust_callback(ctx: typer.Context):
185
+ """Trust list management."""
186
+ if ctx.invoked_subcommand is None:
187
+ # Default to list
188
+ from .commands.trust_commands import handle_trust_list
189
+ handle_trust_list()
190
+
191
+
192
+ @trust_app.command("list")
193
+ def trust_list():
194
+ """List all trust lists."""
195
+ from .commands.trust_commands import handle_trust_list
196
+ handle_trust_list()
197
+
198
+
199
+ @trust_app.command("level")
200
+ def trust_level(address: str = typer.Argument(..., help="Address to check")):
201
+ """Check trust level of an address."""
202
+ from .commands.trust_commands import handle_trust_level
203
+ handle_trust_level(address)
204
+
205
+
206
+ @trust_app.command("add")
207
+ def trust_add(
208
+ address: str = typer.Argument(..., help="Address to add"),
209
+ whitelist: bool = typer.Option(False, "-w", "--whitelist", help="Add to whitelist instead of contacts"),
210
+ ):
211
+ """Add address to contacts (default) or whitelist."""
212
+ from .commands.trust_commands import handle_trust_add
213
+ handle_trust_add(address, whitelist)
214
+
215
+
216
+ @trust_app.command("remove")
217
+ def trust_remove(address: str = typer.Argument(..., help="Address to remove")):
218
+ """Remove address from all lists (demote to stranger)."""
219
+ from .commands.trust_commands import handle_trust_remove
220
+ handle_trust_remove(address)
221
+
222
+
223
+ @trust_app.command("block")
224
+ def trust_block(
225
+ address: str = typer.Argument(..., help="Address to block"),
226
+ reason: str = typer.Option("", "-r", "--reason", help="Reason for blocking"),
227
+ ):
228
+ """Block an address."""
229
+ from .commands.trust_commands import handle_trust_block
230
+ handle_trust_block(address, reason)
231
+
232
+
233
+ @trust_app.command("unblock")
234
+ def trust_unblock(address: str = typer.Argument(..., help="Address to unblock")):
235
+ """Unblock an address."""
236
+ from .commands.trust_commands import handle_trust_unblock
237
+ handle_trust_unblock(address)
238
+
239
+
240
+ # Admin subcommand group
241
+ admin_app = typer.Typer(help="Manage admins (super admin only)")
242
+ trust_app.add_typer(admin_app, name="admin")
243
+
244
+
245
+ @admin_app.command("add")
246
+ def admin_add(address: str = typer.Argument(..., help="Address to add as admin")):
247
+ """Add an admin."""
248
+ from .commands.trust_commands import handle_admin_add
249
+ handle_admin_add(address)
250
+
251
+
252
+ @admin_app.command("remove")
253
+ def admin_remove(address: str = typer.Argument(..., help="Address to remove from admins")):
254
+ """Remove an admin."""
255
+ from .commands.trust_commands import handle_admin_remove
256
+ handle_admin_remove(address)
257
+
258
+
177
259
  def cli():
178
260
  """Entry point."""
179
261
  app()
connectonion/core/llm.py CHANGED
@@ -3,7 +3,7 @@ Purpose: Unified LLM provider abstraction with factory pattern for OpenAI, Anthr
3
3
  LLM-Note:
4
4
  Dependencies: imports from [abc, typing, dataclasses, json, os, base64, openai, anthropic, requests, pathlib, toml, pydantic, .usage, .exceptions] | imported by [agent.py, llm_do.py, conftest.py] | tested by [tests/test_llm.py, tests/test_llm_do.py, tests/test_real_*.py, tests/test_billing_error_agent.py]
5
5
  Data flow: Agent/llm_do calls create_llm(model, api_key) → factory routes to provider class → Provider.__init__() validates API key → Agent calls complete(messages, tools) OR structured_complete(messages, output_schema) → provider converts to native format → calls API → parses response → returns LLMResponse(content, tool_calls, raw_response) OR Pydantic model instance
6
- State/Effects: reads environment variables (OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENONION_API_KEY) | reads ~/.connectonion/.co/config.toml for OpenOnion auth | makes HTTP requests to LLM APIs | no caching or persistence
6
+ State/Effects: reads environment variables (OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, OPENONION_API_KEY) | reads ~/.co/config.toml for OpenOnion auth | makes HTTP requests to LLM APIs | no caching or persistence
7
7
  Integration: exposes create_llm(model, api_key), LLM abstract base class, OpenAILLM, AnthropicLLM, GeminiLLM, OpenOnionLLM, LLMResponse, ToolCall dataclasses | providers implement complete() and structured_complete() | OpenAI message format is lingua franca | tool calling uses OpenAI schema converted per-provider
8
8
  Performance: stateless (no caching) | synchronous (no streaming) | default max_tokens=8192 for Anthropic (required) | each call hits API
9
9
  Errors: raises ValueError for missing API keys, unknown models, invalid parameters | provider-specific errors bubble up (openai.APIError, anthropic.APIError, etc.) | OpenOnionLLM transforms 402 errors to InsufficientCreditsError with formatted message and typed attributes | Pydantic ValidationError for invalid structured output
@@ -97,7 +97,7 @@ Required (pick one):
97
97
  - OPENAI_API_KEY: For OpenAI models
98
98
  - ANTHROPIC_API_KEY: For Claude models
99
99
  - GEMINI_API_KEY or GOOGLE_API_KEY: For Gemini models
100
- - OPENONION_API_KEY: For co/ managed keys (or from ~/.connectonion/.co/config.toml)
100
+ - OPENONION_API_KEY: For co/ managed keys (or from ~/.co/config.toml)
101
101
 
102
102
  Optional:
103
103
  - OPENONION_DEV: Use localhost:8000 for OpenOnion (development)
@@ -0,0 +1,237 @@
1
+ # Fast Rules
2
+
3
+ Fast rules are code-executed trust checks that run **before** the LLM. They burn zero tokens and are instant.
4
+
5
+ ## Why Fast Rules?
6
+
7
+ Trust verification that uses an LLM costs tokens. Every verification burns money.
8
+
9
+ - 1000 requests/day × $0.001/verification = $365/year just for "should I trust this?"
10
+ - 90% of trust decisions are mechanical (whitelist check, blocklist check)
11
+ - Only 10% need LLM judgment
12
+
13
+ Fast rules handle the 90%. LLM handles the 10%.
14
+
15
+ ## How It Works
16
+
17
+ ```
18
+ Request comes in
19
+
20
+
21
+ ┌─────────────────┐
22
+ │ Fast Rules │ ← No tokens, instant
23
+ │ │
24
+ │ 1. blocked? │──deny──►
25
+ │ 2. whitelisted? │──allow──►
26
+ │ 3. contact? │──allow──►
27
+ │ 4. onboard? │──promote + allow──►
28
+ └─────────────────┘
29
+
30
+ │ No rule matched
31
+
32
+ ┌─────────────────┐
33
+ │ Trust Agent │ ← LLM, burns tokens
34
+ │ (if enabled) │
35
+ └─────────────────┘
36
+ ```
37
+
38
+ ## Policy File Format
39
+
40
+ Trust policies are markdown files with YAML frontmatter:
41
+
42
+ ```yaml
43
+ ---
44
+ # YAML config (fast rules)
45
+ ---
46
+
47
+ # Markdown body (LLM system prompt)
48
+ ```
49
+
50
+ ### Full Example
51
+
52
+ ```yaml
53
+ ---
54
+ # Who has access
55
+ allow:
56
+ - whitelisted
57
+ - contact
58
+
59
+ # Who is blocked
60
+ deny:
61
+ - blocked
62
+
63
+ # How strangers become contacts (onboarding)
64
+ onboard:
65
+ invite_code: [BETA2024, FRIEND123]
66
+ payment: 10
67
+
68
+ # Strangers without credentials
69
+ default: ask # allow | deny | ask
70
+ ---
71
+
72
+ # Careful Trust
73
+
74
+ You evaluate unknown agents...
75
+ ```
76
+
77
+ ## Config Options
78
+
79
+ ### `allow:`
80
+
81
+ Who has access. List of conditions:
82
+
83
+ ```yaml
84
+ ---
85
+ allow:
86
+ - whitelisted # Users in ~/.co/whitelist.txt
87
+ - contact # Users in ~/.co/contacts.txt
88
+ ---
89
+ ```
90
+
91
+ ### `deny:`
92
+
93
+ Who is blocked:
94
+
95
+ ```yaml
96
+ ---
97
+ deny:
98
+ - blocked # Users in ~/.co/blocklist.txt
99
+ ---
100
+ ```
101
+
102
+ ### `onboard:`
103
+
104
+ How strangers become contacts (OR logic):
105
+
106
+ ```yaml
107
+ ---
108
+ onboard:
109
+ invite_code: [CODE1, CODE2] # Valid invite codes
110
+ payment: 10 # Minimum payment amount
111
+ ---
112
+ ```
113
+
114
+ - `invite_code`: Client sends `invite_code` in request → becomes contact
115
+ - `payment`: Client sends `payment` >= amount → becomes contact
116
+
117
+ **OR logic**: Either invite_code OR payment passes → promoted to contact.
118
+
119
+ ### `default:`
120
+
121
+ What to do with strangers who don't onboard:
122
+
123
+ ```yaml
124
+ ---
125
+ default: ask # Use LLM to decide (careful mode)
126
+ # OR
127
+ default: deny # Reject (strict mode)
128
+ # OR
129
+ default: allow # Accept (open mode)
130
+ ---
131
+ ```
132
+
133
+ ## The Three Presets
134
+
135
+ ### open (Development)
136
+
137
+ ```yaml
138
+ ---
139
+ default: allow
140
+ ---
141
+ ```
142
+
143
+ Everyone allowed. No verification.
144
+
145
+ ### careful (Staging)
146
+
147
+ ```yaml
148
+ ---
149
+ allow:
150
+ - whitelisted
151
+ - contact
152
+
153
+ deny:
154
+ - blocked
155
+
156
+ onboard:
157
+ invite_code: [BETA2024]
158
+ payment: 10
159
+
160
+ default: ask
161
+ ---
162
+ ```
163
+
164
+ Whitelisted and contacts allowed. Strangers can onboard or be evaluated by LLM.
165
+
166
+ ### strict (Production)
167
+
168
+ ```yaml
169
+ ---
170
+ allow:
171
+ - whitelisted
172
+
173
+ deny:
174
+ - blocked
175
+
176
+ default: deny
177
+ ---
178
+ ```
179
+
180
+ Whitelist only. Everyone else denied.
181
+
182
+ ## Execution Order
183
+
184
+ Fast rules execute in this order:
185
+
186
+ 1. Check `deny` list → deny if blocked
187
+ 2. Check `allow` list → allow if whitelisted/contact
188
+ 3. Try `onboard` → promote + allow if valid credentials
189
+ 4. `default` → allow / deny / ask
190
+
191
+ ## List Files
192
+
193
+ Fast rules read from `~/.co/`:
194
+
195
+ ```
196
+ ~/.co/
197
+ ├── whitelist.txt # Trusted clients
198
+ ├── contacts.txt # Verified clients
199
+ └── blocklist.txt # Blocked clients
200
+ ```
201
+
202
+ ### Format
203
+
204
+ ```
205
+ # Comments start with #
206
+ client-id-123
207
+ another-client
208
+
209
+ # Wildcards supported
210
+ payment-*
211
+ *.trusted.com
212
+ ```
213
+
214
+ ## Implementation
215
+
216
+ See `connectonion/network/trust/fast_rules.py`:
217
+
218
+ ```python
219
+ from connectonion.network.trust import parse_policy, evaluate_request
220
+
221
+ # Parse policy file
222
+ config, markdown_body = parse_policy(policy_text)
223
+
224
+ # Evaluate request
225
+ result = evaluate_request(config, client_id, request)
226
+ # Returns: 'allow', 'deny', or None (needs LLM)
227
+ ```
228
+
229
+ ## Summary
230
+
231
+ | Feature | Fast Rules | LLM Trust Agent |
232
+ |---------|------------|-----------------|
233
+ | Tokens | Zero | Burns tokens |
234
+ | Speed | Instant | Slow (API call) |
235
+ | Logic | Simple checks | Complex reasoning |
236
+ | Use | 90% of requests | 10% of requests |
237
+ | Config | YAML frontmatter | Markdown body |