hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.1__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.
Potentially problematic release.
This version of hanzo-mcp might be problematic. Click here for more details.
- hanzo_mcp/__init__.py +6 -0
- hanzo_mcp/__main__.py +1 -1
- hanzo_mcp/analytics/__init__.py +2 -2
- hanzo_mcp/analytics/posthog_analytics.py +76 -82
- hanzo_mcp/cli.py +31 -36
- hanzo_mcp/cli_enhanced.py +94 -72
- hanzo_mcp/cli_plugin.py +27 -17
- hanzo_mcp/config/__init__.py +2 -2
- hanzo_mcp/config/settings.py +112 -88
- hanzo_mcp/config/tool_config.py +32 -34
- hanzo_mcp/dev_server.py +66 -67
- hanzo_mcp/prompts/__init__.py +94 -12
- hanzo_mcp/prompts/enhanced_prompts.py +809 -0
- hanzo_mcp/prompts/example_custom_prompt.py +6 -5
- hanzo_mcp/prompts/project_todo_reminder.py +0 -1
- hanzo_mcp/prompts/tool_explorer.py +10 -7
- hanzo_mcp/server.py +17 -21
- hanzo_mcp/server_enhanced.py +15 -22
- hanzo_mcp/tools/__init__.py +56 -28
- hanzo_mcp/tools/agent/__init__.py +16 -19
- hanzo_mcp/tools/agent/agent.py +82 -65
- hanzo_mcp/tools/agent/agent_tool.py +152 -122
- hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
- hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
- hanzo_mcp/tools/agent/clarification_tool.py +11 -10
- hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
- hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
- hanzo_mcp/tools/agent/code_auth.py +102 -107
- hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
- hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
- hanzo_mcp/tools/agent/critic_tool.py +86 -73
- hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
- hanzo_mcp/tools/agent/iching_tool.py +404 -139
- hanzo_mcp/tools/agent/network_tool.py +89 -73
- hanzo_mcp/tools/agent/prompt.py +2 -1
- hanzo_mcp/tools/agent/review_tool.py +101 -98
- hanzo_mcp/tools/agent/swarm_alias.py +87 -0
- hanzo_mcp/tools/agent/swarm_tool.py +246 -161
- hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
- hanzo_mcp/tools/agent/tool_adapter.py +21 -11
- hanzo_mcp/tools/common/__init__.py +1 -1
- hanzo_mcp/tools/common/base.py +3 -5
- hanzo_mcp/tools/common/batch_tool.py +46 -39
- hanzo_mcp/tools/common/config_tool.py +120 -84
- hanzo_mcp/tools/common/context.py +1 -5
- hanzo_mcp/tools/common/context_fix.py +5 -3
- hanzo_mcp/tools/common/critic_tool.py +4 -8
- hanzo_mcp/tools/common/decorators.py +58 -56
- hanzo_mcp/tools/common/enhanced_base.py +29 -32
- hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
- hanzo_mcp/tools/common/forgiving_edit.py +91 -87
- hanzo_mcp/tools/common/mode.py +15 -17
- hanzo_mcp/tools/common/mode_loader.py +27 -24
- hanzo_mcp/tools/common/paginated_base.py +61 -53
- hanzo_mcp/tools/common/paginated_response.py +72 -79
- hanzo_mcp/tools/common/pagination.py +50 -53
- hanzo_mcp/tools/common/permissions.py +4 -4
- hanzo_mcp/tools/common/personality.py +186 -138
- hanzo_mcp/tools/common/plugin_loader.py +54 -54
- hanzo_mcp/tools/common/stats.py +65 -47
- hanzo_mcp/tools/common/test_helpers.py +31 -0
- hanzo_mcp/tools/common/thinking_tool.py +4 -8
- hanzo_mcp/tools/common/tool_disable.py +17 -12
- hanzo_mcp/tools/common/tool_enable.py +13 -14
- hanzo_mcp/tools/common/tool_list.py +36 -28
- hanzo_mcp/tools/common/truncate.py +23 -23
- hanzo_mcp/tools/config/__init__.py +4 -4
- hanzo_mcp/tools/config/config_tool.py +42 -29
- hanzo_mcp/tools/config/index_config.py +37 -34
- hanzo_mcp/tools/config/mode_tool.py +175 -55
- hanzo_mcp/tools/database/__init__.py +15 -12
- hanzo_mcp/tools/database/database_manager.py +77 -75
- hanzo_mcp/tools/database/graph.py +137 -91
- hanzo_mcp/tools/database/graph_add.py +30 -18
- hanzo_mcp/tools/database/graph_query.py +178 -102
- hanzo_mcp/tools/database/graph_remove.py +33 -28
- hanzo_mcp/tools/database/graph_search.py +97 -75
- hanzo_mcp/tools/database/graph_stats.py +91 -59
- hanzo_mcp/tools/database/sql.py +107 -79
- hanzo_mcp/tools/database/sql_query.py +30 -24
- hanzo_mcp/tools/database/sql_search.py +29 -25
- hanzo_mcp/tools/database/sql_stats.py +47 -35
- hanzo_mcp/tools/editor/neovim_command.py +25 -28
- hanzo_mcp/tools/editor/neovim_edit.py +21 -23
- hanzo_mcp/tools/editor/neovim_session.py +60 -54
- hanzo_mcp/tools/filesystem/__init__.py +31 -30
- hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
- hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
- hanzo_mcp/tools/filesystem/base.py +1 -1
- hanzo_mcp/tools/filesystem/batch_search.py +316 -224
- hanzo_mcp/tools/filesystem/content_replace.py +4 -4
- hanzo_mcp/tools/filesystem/diff.py +71 -59
- hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
- hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
- hanzo_mcp/tools/filesystem/edit.py +4 -4
- hanzo_mcp/tools/filesystem/find.py +173 -80
- hanzo_mcp/tools/filesystem/find_files.py +73 -52
- hanzo_mcp/tools/filesystem/git_search.py +157 -104
- hanzo_mcp/tools/filesystem/grep.py +8 -8
- hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
- hanzo_mcp/tools/filesystem/read.py +12 -10
- hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
- hanzo_mcp/tools/filesystem/search_tool.py +263 -207
- hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
- hanzo_mcp/tools/filesystem/tree.py +35 -33
- hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
- hanzo_mcp/tools/filesystem/watch.py +37 -36
- hanzo_mcp/tools/filesystem/write.py +4 -8
- hanzo_mcp/tools/jupyter/__init__.py +4 -4
- hanzo_mcp/tools/jupyter/base.py +4 -5
- hanzo_mcp/tools/jupyter/jupyter.py +67 -47
- hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
- hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
- hanzo_mcp/tools/llm/__init__.py +5 -7
- hanzo_mcp/tools/llm/consensus_tool.py +72 -52
- hanzo_mcp/tools/llm/llm_manage.py +101 -60
- hanzo_mcp/tools/llm/llm_tool.py +226 -166
- hanzo_mcp/tools/llm/provider_tools.py +25 -26
- hanzo_mcp/tools/lsp/__init__.py +1 -1
- hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
- hanzo_mcp/tools/mcp/__init__.py +2 -3
- hanzo_mcp/tools/mcp/mcp_add.py +27 -25
- hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
- hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
- hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
- hanzo_mcp/tools/memory/__init__.py +39 -21
- hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
- hanzo_mcp/tools/memory/memory_tools.py +90 -108
- hanzo_mcp/tools/search/__init__.py +7 -2
- hanzo_mcp/tools/search/find_tool.py +297 -212
- hanzo_mcp/tools/search/unified_search.py +366 -314
- hanzo_mcp/tools/shell/__init__.py +8 -7
- hanzo_mcp/tools/shell/auto_background.py +56 -49
- hanzo_mcp/tools/shell/base.py +1 -1
- hanzo_mcp/tools/shell/base_process.py +75 -75
- hanzo_mcp/tools/shell/bash_session.py +2 -2
- hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
- hanzo_mcp/tools/shell/bash_tool.py +24 -31
- hanzo_mcp/tools/shell/command_executor.py +12 -12
- hanzo_mcp/tools/shell/logs.py +43 -33
- hanzo_mcp/tools/shell/npx.py +13 -13
- hanzo_mcp/tools/shell/npx_background.py +24 -21
- hanzo_mcp/tools/shell/npx_tool.py +18 -22
- hanzo_mcp/tools/shell/open.py +19 -21
- hanzo_mcp/tools/shell/pkill.py +31 -26
- hanzo_mcp/tools/shell/process_tool.py +32 -32
- hanzo_mcp/tools/shell/processes.py +57 -58
- hanzo_mcp/tools/shell/run_background.py +24 -25
- hanzo_mcp/tools/shell/run_command.py +5 -5
- hanzo_mcp/tools/shell/run_command_windows.py +5 -5
- hanzo_mcp/tools/shell/session_storage.py +3 -3
- hanzo_mcp/tools/shell/streaming_command.py +141 -126
- hanzo_mcp/tools/shell/uvx.py +24 -25
- hanzo_mcp/tools/shell/uvx_background.py +35 -33
- hanzo_mcp/tools/shell/uvx_tool.py +18 -22
- hanzo_mcp/tools/todo/__init__.py +6 -2
- hanzo_mcp/tools/todo/todo.py +50 -37
- hanzo_mcp/tools/todo/todo_read.py +5 -8
- hanzo_mcp/tools/todo/todo_write.py +5 -7
- hanzo_mcp/tools/vector/__init__.py +40 -28
- hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
- hanzo_mcp/tools/vector/git_ingester.py +170 -179
- hanzo_mcp/tools/vector/index_tool.py +96 -44
- hanzo_mcp/tools/vector/infinity_store.py +283 -228
- hanzo_mcp/tools/vector/mock_infinity.py +39 -40
- hanzo_mcp/tools/vector/project_manager.py +88 -78
- hanzo_mcp/tools/vector/vector.py +59 -42
- hanzo_mcp/tools/vector/vector_index.py +30 -27
- hanzo_mcp/tools/vector/vector_search.py +64 -45
- hanzo_mcp/types.py +6 -4
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/METADATA +1 -1
- hanzo_mcp-0.8.1.dist-info/RECORD +185 -0
- hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/WHEEL +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/entry_points.txt +0 -0
- {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.1.dist-info}/top_level.txt +0 -0
|
@@ -4,21 +4,21 @@ This module provides tools to manage API keys and authentication for
|
|
|
4
4
|
Claude Code CLI and OpenAI Codex, allowing separate accounts for swarm agents.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import asyncio
|
|
8
7
|
import os
|
|
9
|
-
import subprocess
|
|
10
8
|
import json
|
|
11
|
-
import
|
|
9
|
+
import getpass
|
|
10
|
+
import subprocess
|
|
11
|
+
from typing import Any, Dict, List, Tuple, Optional
|
|
12
12
|
from pathlib import Path
|
|
13
|
-
from typing import Optional, Tuple, Dict, Any, List
|
|
14
13
|
from dataclasses import dataclass
|
|
14
|
+
|
|
15
15
|
import keyring
|
|
16
|
-
import getpass
|
|
17
16
|
|
|
18
17
|
|
|
19
18
|
@dataclass
|
|
20
19
|
class APICredential:
|
|
21
20
|
"""API credential information."""
|
|
21
|
+
|
|
22
22
|
provider: str
|
|
23
23
|
api_key: str
|
|
24
24
|
model: Optional[str] = None
|
|
@@ -29,12 +29,12 @@ class APICredential:
|
|
|
29
29
|
|
|
30
30
|
class CodeAuthManager:
|
|
31
31
|
"""Manages authentication for Claude Code and other AI coding tools."""
|
|
32
|
-
|
|
32
|
+
|
|
33
33
|
# Configuration paths
|
|
34
34
|
CONFIG_DIR = Path.home() / ".hanzo" / "auth"
|
|
35
35
|
ACCOUNTS_FILE = CONFIG_DIR / "accounts.json"
|
|
36
36
|
ACTIVE_ACCOUNT_FILE = CONFIG_DIR / "active_account"
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
# Environment variable mappings
|
|
39
39
|
ENV_VARS = {
|
|
40
40
|
"claude": ["ANTHROPIC_API_KEY", "CLAUDE_API_KEY"],
|
|
@@ -44,7 +44,7 @@ class CodeAuthManager:
|
|
|
44
44
|
"google": ["GOOGLE_API_KEY", "GEMINI_API_KEY"],
|
|
45
45
|
"groq": ["GROQ_API_KEY"],
|
|
46
46
|
}
|
|
47
|
-
|
|
47
|
+
|
|
48
48
|
# Default models
|
|
49
49
|
DEFAULT_MODELS = {
|
|
50
50
|
"claude": "claude-3-5-sonnet-20241022", # Latest Sonnet
|
|
@@ -54,106 +54,106 @@ class CodeAuthManager:
|
|
|
54
54
|
"google": "gemini-1.5-pro",
|
|
55
55
|
"groq": "llama3-70b-8192",
|
|
56
56
|
}
|
|
57
|
-
|
|
57
|
+
|
|
58
58
|
def __init__(self):
|
|
59
59
|
"""Initialize auth manager."""
|
|
60
60
|
self.ensure_config_dir()
|
|
61
61
|
self._env_backup = {}
|
|
62
|
-
|
|
62
|
+
|
|
63
63
|
def ensure_config_dir(self):
|
|
64
64
|
"""Ensure config directory exists."""
|
|
65
65
|
self.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
def get_active_account(self) -> Optional[str]:
|
|
68
68
|
"""Get the currently active account."""
|
|
69
69
|
if self.ACTIVE_ACCOUNT_FILE.exists():
|
|
70
70
|
return self.ACTIVE_ACCOUNT_FILE.read_text().strip()
|
|
71
71
|
return "default"
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
def set_active_account(self, account: str):
|
|
74
74
|
"""Set the active account."""
|
|
75
75
|
self.ACTIVE_ACCOUNT_FILE.write_text(account)
|
|
76
|
-
|
|
76
|
+
|
|
77
77
|
def _load_accounts(self) -> Dict[str, Dict[str, Any]]:
|
|
78
78
|
"""Load all accounts."""
|
|
79
79
|
if not self.ACCOUNTS_FILE.exists():
|
|
80
80
|
return {}
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
try:
|
|
83
|
-
with open(self.ACCOUNTS_FILE,
|
|
83
|
+
with open(self.ACCOUNTS_FILE, "r") as f:
|
|
84
84
|
return json.load(f)
|
|
85
|
-
except:
|
|
85
|
+
except Exception:
|
|
86
86
|
return {}
|
|
87
|
-
|
|
87
|
+
|
|
88
88
|
def _save_accounts(self, accounts: Dict[str, Dict[str, Any]]):
|
|
89
89
|
"""Save accounts."""
|
|
90
|
-
with open(self.ACCOUNTS_FILE,
|
|
90
|
+
with open(self.ACCOUNTS_FILE, "w") as f:
|
|
91
91
|
json.dump(accounts, f, indent=2)
|
|
92
|
-
|
|
92
|
+
|
|
93
93
|
def list_accounts(self) -> List[str]:
|
|
94
94
|
"""List all available accounts."""
|
|
95
95
|
accounts = self._load_accounts()
|
|
96
96
|
return list(accounts.keys())
|
|
97
|
-
|
|
97
|
+
|
|
98
98
|
def get_account_info(self, account: str) -> Optional[Dict[str, Any]]:
|
|
99
99
|
"""Get information about an account."""
|
|
100
100
|
accounts = self._load_accounts()
|
|
101
101
|
return accounts.get(account)
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
def create_account(
|
|
104
|
-
self,
|
|
104
|
+
self,
|
|
105
105
|
account: str,
|
|
106
106
|
provider: str = "claude",
|
|
107
107
|
api_key: Optional[str] = None,
|
|
108
108
|
model: Optional[str] = None,
|
|
109
|
-
description: Optional[str] = None
|
|
109
|
+
description: Optional[str] = None,
|
|
110
110
|
) -> Tuple[bool, str]:
|
|
111
111
|
"""Create a new account.
|
|
112
|
-
|
|
112
|
+
|
|
113
113
|
Args:
|
|
114
114
|
account: Account name
|
|
115
115
|
provider: Provider (claude, openai, etc.)
|
|
116
116
|
api_key: API key (will prompt if not provided)
|
|
117
117
|
model: Model to use (defaults to provider default)
|
|
118
118
|
description: Account description
|
|
119
|
-
|
|
119
|
+
|
|
120
120
|
Returns:
|
|
121
121
|
Tuple of (success, message)
|
|
122
122
|
"""
|
|
123
123
|
accounts = self._load_accounts()
|
|
124
|
-
|
|
124
|
+
|
|
125
125
|
if account in accounts:
|
|
126
126
|
return False, f"Account '{account}' already exists"
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
# Get API key if not provided
|
|
129
129
|
if not api_key:
|
|
130
130
|
api_key = self._prompt_for_api_key(provider)
|
|
131
131
|
if not api_key:
|
|
132
132
|
return False, "No API key provided"
|
|
133
|
-
|
|
133
|
+
|
|
134
134
|
# Use default model if not specified
|
|
135
135
|
if not model:
|
|
136
136
|
model = self.DEFAULT_MODELS.get(provider)
|
|
137
|
-
|
|
137
|
+
|
|
138
138
|
# Store in keyring for security
|
|
139
139
|
try:
|
|
140
140
|
keyring.set_password(f"hanzo-{provider}", account, api_key)
|
|
141
|
-
except:
|
|
141
|
+
except Exception:
|
|
142
142
|
# Fallback to file storage (less secure)
|
|
143
143
|
pass
|
|
144
|
-
|
|
144
|
+
|
|
145
145
|
# Save account info
|
|
146
146
|
accounts[account] = {
|
|
147
147
|
"provider": provider,
|
|
148
148
|
"model": model,
|
|
149
149
|
"description": description or f"{provider} account",
|
|
150
150
|
"created_at": os.path.getmtime(__file__),
|
|
151
|
-
"has_keyring": self._has_keyring_support()
|
|
151
|
+
"has_keyring": self._has_keyring_support(),
|
|
152
152
|
}
|
|
153
|
-
|
|
153
|
+
|
|
154
154
|
self._save_accounts(accounts)
|
|
155
155
|
return True, f"Created account '{account}' for {provider}"
|
|
156
|
-
|
|
156
|
+
|
|
157
157
|
def _prompt_for_api_key(self, provider: str) -> Optional[str]:
|
|
158
158
|
"""Prompt user for API key."""
|
|
159
159
|
prompt = f"Enter {provider.upper()} API key: "
|
|
@@ -161,189 +161,184 @@ class CodeAuthManager:
|
|
|
161
161
|
return getpass.getpass(prompt)
|
|
162
162
|
except KeyboardInterrupt:
|
|
163
163
|
return None
|
|
164
|
-
|
|
164
|
+
|
|
165
165
|
def _has_keyring_support(self) -> bool:
|
|
166
166
|
"""Check if keyring is available."""
|
|
167
167
|
try:
|
|
168
168
|
keyring.get_keyring()
|
|
169
169
|
return True
|
|
170
|
-
except:
|
|
170
|
+
except Exception:
|
|
171
171
|
return False
|
|
172
|
-
|
|
172
|
+
|
|
173
173
|
def login(self, account: str = "default") -> Tuple[bool, str]:
|
|
174
174
|
"""Login to an account by setting environment variables.
|
|
175
|
-
|
|
175
|
+
|
|
176
176
|
Args:
|
|
177
177
|
account: Account name to login to
|
|
178
|
-
|
|
178
|
+
|
|
179
179
|
Returns:
|
|
180
180
|
Tuple of (success, message)
|
|
181
181
|
"""
|
|
182
182
|
accounts = self._load_accounts()
|
|
183
|
-
|
|
183
|
+
|
|
184
184
|
if account not in accounts:
|
|
185
185
|
return False, f"Account '{account}' not found"
|
|
186
|
-
|
|
186
|
+
|
|
187
187
|
account_info = accounts[account]
|
|
188
188
|
provider = account_info["provider"]
|
|
189
|
-
|
|
189
|
+
|
|
190
190
|
# Get API key from keyring or prompt
|
|
191
191
|
api_key = None
|
|
192
192
|
if account_info.get("has_keyring"):
|
|
193
193
|
try:
|
|
194
194
|
api_key = keyring.get_password(f"hanzo-{provider}", account)
|
|
195
|
-
except:
|
|
195
|
+
except Exception:
|
|
196
196
|
pass
|
|
197
|
-
|
|
197
|
+
|
|
198
198
|
if not api_key:
|
|
199
199
|
# Try environment variable
|
|
200
200
|
for env_var in self.ENV_VARS.get(provider, []):
|
|
201
201
|
if env_var in os.environ:
|
|
202
202
|
api_key = os.environ[env_var]
|
|
203
203
|
break
|
|
204
|
-
|
|
204
|
+
|
|
205
205
|
if not api_key:
|
|
206
206
|
api_key = self._prompt_for_api_key(provider)
|
|
207
207
|
if not api_key:
|
|
208
208
|
return False, "No API key available"
|
|
209
|
-
|
|
209
|
+
|
|
210
210
|
# Backup current environment
|
|
211
211
|
self._backup_environment(provider)
|
|
212
|
-
|
|
212
|
+
|
|
213
213
|
# Set environment variables
|
|
214
214
|
for env_var in self.ENV_VARS.get(provider, []):
|
|
215
215
|
os.environ[env_var] = api_key
|
|
216
|
-
|
|
216
|
+
|
|
217
217
|
# Set active account
|
|
218
218
|
self.set_active_account(account)
|
|
219
|
-
|
|
219
|
+
|
|
220
220
|
# Update shell if using claude command
|
|
221
221
|
self._update_claude_command(account_info)
|
|
222
|
-
|
|
222
|
+
|
|
223
223
|
return True, f"Logged in as '{account}' ({provider})"
|
|
224
|
-
|
|
224
|
+
|
|
225
225
|
def logout(self) -> Tuple[bool, str]:
|
|
226
226
|
"""Logout by clearing environment variables."""
|
|
227
227
|
current = self.get_active_account()
|
|
228
|
-
|
|
228
|
+
|
|
229
229
|
if not current or current == "default":
|
|
230
230
|
return False, "No active session"
|
|
231
|
-
|
|
231
|
+
|
|
232
232
|
accounts = self._load_accounts()
|
|
233
233
|
if current not in accounts:
|
|
234
234
|
return False, f"Unknown account: {current}"
|
|
235
|
-
|
|
235
|
+
|
|
236
236
|
provider = accounts[current]["provider"]
|
|
237
|
-
|
|
237
|
+
|
|
238
238
|
# Clear environment variables
|
|
239
239
|
for env_var in self.ENV_VARS.get(provider, []):
|
|
240
240
|
if env_var in os.environ:
|
|
241
241
|
del os.environ[env_var]
|
|
242
|
-
|
|
242
|
+
|
|
243
243
|
# Restore backed up environment if any
|
|
244
244
|
self._restore_environment(provider)
|
|
245
|
-
|
|
245
|
+
|
|
246
246
|
# Clear active account
|
|
247
247
|
if self.ACTIVE_ACCOUNT_FILE.exists():
|
|
248
248
|
self.ACTIVE_ACCOUNT_FILE.unlink()
|
|
249
|
-
|
|
249
|
+
|
|
250
250
|
return True, f"Logged out from '{current}'"
|
|
251
|
-
|
|
251
|
+
|
|
252
252
|
def _backup_environment(self, provider: str):
|
|
253
253
|
"""Backup current environment variables."""
|
|
254
254
|
for env_var in self.ENV_VARS.get(provider, []):
|
|
255
255
|
if env_var in os.environ:
|
|
256
256
|
self._env_backup[env_var] = os.environ[env_var]
|
|
257
|
-
|
|
257
|
+
|
|
258
258
|
def _restore_environment(self, provider: str):
|
|
259
259
|
"""Restore backed up environment variables."""
|
|
260
260
|
for env_var in self.ENV_VARS.get(provider, []):
|
|
261
261
|
if env_var in self._env_backup:
|
|
262
262
|
os.environ[env_var] = self._env_backup[env_var]
|
|
263
263
|
del self._env_backup[env_var]
|
|
264
|
-
|
|
264
|
+
|
|
265
265
|
def _update_claude_command(self, account_info: Dict[str, Any]):
|
|
266
266
|
"""Update claude command configuration if needed."""
|
|
267
267
|
# Check if claude command exists
|
|
268
268
|
try:
|
|
269
|
-
result = subprocess.run(
|
|
270
|
-
["which", "claude"],
|
|
271
|
-
capture_output=True,
|
|
272
|
-
text=True
|
|
273
|
-
)
|
|
269
|
+
result = subprocess.run(["which", "claude"], capture_output=True, text=True)
|
|
274
270
|
if result.returncode == 0:
|
|
275
271
|
# Claude command exists, update its config
|
|
276
272
|
claude_config = Path.home() / ".claude" / "config.json"
|
|
277
273
|
if claude_config.exists():
|
|
278
274
|
try:
|
|
279
|
-
with open(claude_config,
|
|
275
|
+
with open(claude_config, "r") as f:
|
|
280
276
|
config = json.load(f)
|
|
281
|
-
|
|
277
|
+
|
|
282
278
|
# Update model if specified
|
|
283
279
|
if account_info.get("model"):
|
|
284
280
|
config["default_model"] = account_info["model"]
|
|
285
|
-
|
|
286
|
-
with open(claude_config,
|
|
281
|
+
|
|
282
|
+
with open(claude_config, "w") as f:
|
|
287
283
|
json.dump(config, f, indent=2)
|
|
288
|
-
except:
|
|
284
|
+
except Exception:
|
|
289
285
|
pass
|
|
290
|
-
except:
|
|
286
|
+
except Exception:
|
|
291
287
|
pass
|
|
292
|
-
|
|
288
|
+
|
|
293
289
|
def switch_account(self, account: str) -> Tuple[bool, str]:
|
|
294
290
|
"""Switch to a different account."""
|
|
295
291
|
# Logout current
|
|
296
292
|
self.logout()
|
|
297
|
-
|
|
293
|
+
|
|
298
294
|
# Login to new account
|
|
299
295
|
return self.login(account)
|
|
300
|
-
|
|
296
|
+
|
|
301
297
|
def create_agent_account(
|
|
302
|
-
self,
|
|
298
|
+
self,
|
|
303
299
|
agent_id: str,
|
|
304
300
|
provider: str = "claude",
|
|
305
|
-
parent_account: Optional[str] = None
|
|
301
|
+
parent_account: Optional[str] = None,
|
|
306
302
|
) -> Tuple[bool, str]:
|
|
307
303
|
"""Create an account for a swarm agent.
|
|
308
|
-
|
|
304
|
+
|
|
309
305
|
Args:
|
|
310
306
|
agent_id: Unique agent identifier
|
|
311
307
|
provider: AI provider
|
|
312
308
|
parent_account: Parent account to clone from
|
|
313
|
-
|
|
309
|
+
|
|
314
310
|
Returns:
|
|
315
311
|
Tuple of (success, account_name)
|
|
316
312
|
"""
|
|
317
313
|
agent_account = f"agent_{agent_id}"
|
|
318
|
-
|
|
314
|
+
|
|
319
315
|
# If parent account specified, clone its credentials
|
|
320
316
|
if parent_account:
|
|
321
317
|
parent_info = self.get_account_info(parent_account)
|
|
322
318
|
if not parent_info:
|
|
323
319
|
return False, f"Parent account '{parent_account}' not found"
|
|
324
|
-
|
|
320
|
+
|
|
325
321
|
# Get parent API key
|
|
326
322
|
api_key = None
|
|
327
323
|
if parent_info.get("has_keyring"):
|
|
328
324
|
try:
|
|
329
325
|
api_key = keyring.get_password(
|
|
330
|
-
f"hanzo-{parent_info['provider']}",
|
|
331
|
-
parent_account
|
|
326
|
+
f"hanzo-{parent_info['provider']}", parent_account
|
|
332
327
|
)
|
|
333
|
-
except:
|
|
328
|
+
except Exception:
|
|
334
329
|
pass
|
|
335
|
-
|
|
330
|
+
|
|
336
331
|
if api_key:
|
|
337
332
|
success, msg = self.create_account(
|
|
338
333
|
agent_account,
|
|
339
334
|
provider=parent_info["provider"],
|
|
340
335
|
api_key=api_key,
|
|
341
336
|
model=parent_info.get("model"),
|
|
342
|
-
description=f"Agent account (parent: {parent_account})"
|
|
337
|
+
description=f"Agent account (parent: {parent_account})",
|
|
343
338
|
)
|
|
344
339
|
if success:
|
|
345
340
|
return True, agent_account
|
|
346
|
-
|
|
341
|
+
|
|
347
342
|
# Create with current environment
|
|
348
343
|
for env_var in self.ENV_VARS.get(provider, []):
|
|
349
344
|
if env_var in os.environ:
|
|
@@ -352,53 +347,53 @@ class CodeAuthManager:
|
|
|
352
347
|
provider=provider,
|
|
353
348
|
api_key=os.environ[env_var],
|
|
354
349
|
model=self.DEFAULT_MODELS.get(provider),
|
|
355
|
-
description=f"Agent account for {agent_id}"
|
|
350
|
+
description=f"Agent account for {agent_id}",
|
|
356
351
|
)
|
|
357
352
|
if success:
|
|
358
353
|
return True, agent_account
|
|
359
|
-
|
|
354
|
+
|
|
360
355
|
return False, "No credentials available for agent"
|
|
361
|
-
|
|
356
|
+
|
|
362
357
|
def get_agent_credentials(self, agent_id: str) -> Optional[APICredential]:
|
|
363
358
|
"""Get credentials for an agent.
|
|
364
|
-
|
|
359
|
+
|
|
365
360
|
Args:
|
|
366
361
|
agent_id: Agent identifier
|
|
367
|
-
|
|
362
|
+
|
|
368
363
|
Returns:
|
|
369
364
|
APICredential if found
|
|
370
365
|
"""
|
|
371
366
|
agent_account = f"agent_{agent_id}"
|
|
372
367
|
account_info = self.get_account_info(agent_account)
|
|
373
|
-
|
|
368
|
+
|
|
374
369
|
if not account_info:
|
|
375
370
|
return None
|
|
376
|
-
|
|
371
|
+
|
|
377
372
|
# Get API key
|
|
378
373
|
api_key = None
|
|
379
374
|
provider = account_info["provider"]
|
|
380
|
-
|
|
375
|
+
|
|
381
376
|
if account_info.get("has_keyring"):
|
|
382
377
|
try:
|
|
383
378
|
api_key = keyring.get_password(f"hanzo-{provider}", agent_account)
|
|
384
|
-
except:
|
|
379
|
+
except Exception:
|
|
385
380
|
pass
|
|
386
|
-
|
|
381
|
+
|
|
387
382
|
if not api_key:
|
|
388
383
|
# Try current environment
|
|
389
384
|
for env_var in self.ENV_VARS.get(provider, []):
|
|
390
385
|
if env_var in os.environ:
|
|
391
386
|
api_key = os.environ[env_var]
|
|
392
387
|
break
|
|
393
|
-
|
|
388
|
+
|
|
394
389
|
if not api_key:
|
|
395
390
|
return None
|
|
396
|
-
|
|
391
|
+
|
|
397
392
|
return APICredential(
|
|
398
393
|
provider=provider,
|
|
399
394
|
api_key=api_key,
|
|
400
395
|
model=account_info.get("model"),
|
|
401
|
-
description=account_info.get("description")
|
|
396
|
+
description=account_info.get("description"),
|
|
402
397
|
)
|
|
403
398
|
|
|
404
399
|
|
|
@@ -413,24 +408,24 @@ def get_latest_claude_model() -> str:
|
|
|
413
408
|
# Token counting using tiktoken (same as current implementation)
|
|
414
409
|
def count_tokens_streaming(text_stream) -> int:
|
|
415
410
|
"""Count tokens in a streaming fashion.
|
|
416
|
-
|
|
411
|
+
|
|
417
412
|
This uses the same tiktoken approach as the truncate module,
|
|
418
413
|
but processes text as it streams.
|
|
419
414
|
"""
|
|
420
415
|
import tiktoken
|
|
421
|
-
|
|
416
|
+
|
|
422
417
|
try:
|
|
423
418
|
# Use cl100k_base encoding (Claude/GPT-4 compatible)
|
|
424
419
|
encoding = tiktoken.get_encoding("cl100k_base")
|
|
425
|
-
except:
|
|
420
|
+
except Exception:
|
|
426
421
|
# Fallback to simple estimation
|
|
427
422
|
return len(text_stream) // 4
|
|
428
|
-
|
|
423
|
+
|
|
429
424
|
total_tokens = 0
|
|
430
425
|
for chunk in text_stream:
|
|
431
426
|
if isinstance(chunk, str):
|
|
432
427
|
total_tokens += len(encoding.encode(chunk))
|
|
433
428
|
elif isinstance(chunk, bytes):
|
|
434
|
-
total_tokens += len(encoding.encode(chunk.decode(
|
|
435
|
-
|
|
436
|
-
return total_tokens
|
|
429
|
+
total_tokens += len(encoding.encode(chunk.decode("utf-8", errors="ignore")))
|
|
430
|
+
|
|
431
|
+
return total_tokens
|