hanzo-mcp 0.7.7__py3-none-any.whl → 0.8.0__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.

Files changed (178) hide show
  1. hanzo_mcp/__init__.py +6 -0
  2. hanzo_mcp/__main__.py +1 -1
  3. hanzo_mcp/analytics/__init__.py +2 -2
  4. hanzo_mcp/analytics/posthog_analytics.py +76 -82
  5. hanzo_mcp/cli.py +31 -36
  6. hanzo_mcp/cli_enhanced.py +94 -72
  7. hanzo_mcp/cli_plugin.py +27 -17
  8. hanzo_mcp/config/__init__.py +2 -2
  9. hanzo_mcp/config/settings.py +112 -88
  10. hanzo_mcp/config/tool_config.py +32 -34
  11. hanzo_mcp/dev_server.py +66 -67
  12. hanzo_mcp/prompts/__init__.py +94 -12
  13. hanzo_mcp/prompts/enhanced_prompts.py +809 -0
  14. hanzo_mcp/prompts/example_custom_prompt.py +6 -5
  15. hanzo_mcp/prompts/project_todo_reminder.py +0 -1
  16. hanzo_mcp/prompts/tool_explorer.py +10 -7
  17. hanzo_mcp/server.py +17 -21
  18. hanzo_mcp/server_enhanced.py +15 -22
  19. hanzo_mcp/tools/__init__.py +56 -28
  20. hanzo_mcp/tools/agent/__init__.py +16 -19
  21. hanzo_mcp/tools/agent/agent.py +82 -65
  22. hanzo_mcp/tools/agent/agent_tool.py +152 -122
  23. hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py +66 -62
  24. hanzo_mcp/tools/agent/clarification_protocol.py +55 -50
  25. hanzo_mcp/tools/agent/clarification_tool.py +11 -10
  26. hanzo_mcp/tools/agent/claude_cli_tool.py +21 -20
  27. hanzo_mcp/tools/agent/claude_desktop_auth.py +130 -144
  28. hanzo_mcp/tools/agent/cli_agent_base.py +59 -53
  29. hanzo_mcp/tools/agent/code_auth.py +102 -107
  30. hanzo_mcp/tools/agent/code_auth_tool.py +28 -27
  31. hanzo_mcp/tools/agent/codex_cli_tool.py +20 -19
  32. hanzo_mcp/tools/agent/critic_tool.py +86 -73
  33. hanzo_mcp/tools/agent/gemini_cli_tool.py +21 -20
  34. hanzo_mcp/tools/agent/grok_cli_tool.py +21 -20
  35. hanzo_mcp/tools/agent/iching_tool.py +404 -139
  36. hanzo_mcp/tools/agent/network_tool.py +89 -73
  37. hanzo_mcp/tools/agent/prompt.py +2 -1
  38. hanzo_mcp/tools/agent/review_tool.py +101 -98
  39. hanzo_mcp/tools/agent/swarm_alias.py +87 -0
  40. hanzo_mcp/tools/agent/swarm_tool.py +246 -161
  41. hanzo_mcp/tools/agent/swarm_tool_v1_deprecated.py +134 -92
  42. hanzo_mcp/tools/agent/tool_adapter.py +21 -11
  43. hanzo_mcp/tools/common/__init__.py +1 -1
  44. hanzo_mcp/tools/common/base.py +3 -5
  45. hanzo_mcp/tools/common/batch_tool.py +46 -39
  46. hanzo_mcp/tools/common/config_tool.py +120 -84
  47. hanzo_mcp/tools/common/context.py +1 -5
  48. hanzo_mcp/tools/common/context_fix.py +5 -3
  49. hanzo_mcp/tools/common/critic_tool.py +4 -8
  50. hanzo_mcp/tools/common/decorators.py +58 -56
  51. hanzo_mcp/tools/common/enhanced_base.py +29 -32
  52. hanzo_mcp/tools/common/fastmcp_pagination.py +91 -94
  53. hanzo_mcp/tools/common/forgiving_edit.py +91 -87
  54. hanzo_mcp/tools/common/mode.py +15 -17
  55. hanzo_mcp/tools/common/mode_loader.py +27 -24
  56. hanzo_mcp/tools/common/paginated_base.py +61 -53
  57. hanzo_mcp/tools/common/paginated_response.py +72 -79
  58. hanzo_mcp/tools/common/pagination.py +50 -53
  59. hanzo_mcp/tools/common/permissions.py +4 -4
  60. hanzo_mcp/tools/common/personality.py +186 -138
  61. hanzo_mcp/tools/common/plugin_loader.py +54 -54
  62. hanzo_mcp/tools/common/stats.py +65 -47
  63. hanzo_mcp/tools/common/test_helpers.py +31 -0
  64. hanzo_mcp/tools/common/thinking_tool.py +4 -8
  65. hanzo_mcp/tools/common/tool_disable.py +17 -12
  66. hanzo_mcp/tools/common/tool_enable.py +13 -14
  67. hanzo_mcp/tools/common/tool_list.py +36 -28
  68. hanzo_mcp/tools/common/truncate.py +23 -23
  69. hanzo_mcp/tools/config/__init__.py +4 -4
  70. hanzo_mcp/tools/config/config_tool.py +42 -29
  71. hanzo_mcp/tools/config/index_config.py +37 -34
  72. hanzo_mcp/tools/config/mode_tool.py +175 -55
  73. hanzo_mcp/tools/database/__init__.py +15 -12
  74. hanzo_mcp/tools/database/database_manager.py +77 -75
  75. hanzo_mcp/tools/database/graph.py +137 -91
  76. hanzo_mcp/tools/database/graph_add.py +30 -18
  77. hanzo_mcp/tools/database/graph_query.py +178 -102
  78. hanzo_mcp/tools/database/graph_remove.py +33 -28
  79. hanzo_mcp/tools/database/graph_search.py +97 -75
  80. hanzo_mcp/tools/database/graph_stats.py +91 -59
  81. hanzo_mcp/tools/database/sql.py +107 -79
  82. hanzo_mcp/tools/database/sql_query.py +30 -24
  83. hanzo_mcp/tools/database/sql_search.py +29 -25
  84. hanzo_mcp/tools/database/sql_stats.py +47 -35
  85. hanzo_mcp/tools/editor/neovim_command.py +25 -28
  86. hanzo_mcp/tools/editor/neovim_edit.py +21 -23
  87. hanzo_mcp/tools/editor/neovim_session.py +60 -54
  88. hanzo_mcp/tools/filesystem/__init__.py +31 -30
  89. hanzo_mcp/tools/filesystem/ast_multi_edit.py +329 -249
  90. hanzo_mcp/tools/filesystem/ast_tool.py +4 -4
  91. hanzo_mcp/tools/filesystem/base.py +1 -1
  92. hanzo_mcp/tools/filesystem/batch_search.py +316 -224
  93. hanzo_mcp/tools/filesystem/content_replace.py +4 -4
  94. hanzo_mcp/tools/filesystem/diff.py +71 -59
  95. hanzo_mcp/tools/filesystem/directory_tree.py +7 -7
  96. hanzo_mcp/tools/filesystem/directory_tree_paginated.py +49 -37
  97. hanzo_mcp/tools/filesystem/edit.py +4 -4
  98. hanzo_mcp/tools/filesystem/find.py +173 -80
  99. hanzo_mcp/tools/filesystem/find_files.py +73 -52
  100. hanzo_mcp/tools/filesystem/git_search.py +157 -104
  101. hanzo_mcp/tools/filesystem/grep.py +8 -8
  102. hanzo_mcp/tools/filesystem/multi_edit.py +4 -8
  103. hanzo_mcp/tools/filesystem/read.py +12 -10
  104. hanzo_mcp/tools/filesystem/rules_tool.py +59 -43
  105. hanzo_mcp/tools/filesystem/search_tool.py +263 -207
  106. hanzo_mcp/tools/filesystem/symbols_tool.py +94 -54
  107. hanzo_mcp/tools/filesystem/tree.py +35 -33
  108. hanzo_mcp/tools/filesystem/unix_aliases.py +13 -18
  109. hanzo_mcp/tools/filesystem/watch.py +37 -36
  110. hanzo_mcp/tools/filesystem/write.py +4 -8
  111. hanzo_mcp/tools/jupyter/__init__.py +4 -4
  112. hanzo_mcp/tools/jupyter/base.py +4 -5
  113. hanzo_mcp/tools/jupyter/jupyter.py +67 -47
  114. hanzo_mcp/tools/jupyter/notebook_edit.py +4 -4
  115. hanzo_mcp/tools/jupyter/notebook_read.py +4 -7
  116. hanzo_mcp/tools/llm/__init__.py +5 -7
  117. hanzo_mcp/tools/llm/consensus_tool.py +72 -52
  118. hanzo_mcp/tools/llm/llm_manage.py +101 -60
  119. hanzo_mcp/tools/llm/llm_tool.py +226 -166
  120. hanzo_mcp/tools/llm/provider_tools.py +25 -26
  121. hanzo_mcp/tools/lsp/__init__.py +1 -1
  122. hanzo_mcp/tools/lsp/lsp_tool.py +228 -143
  123. hanzo_mcp/tools/mcp/__init__.py +2 -3
  124. hanzo_mcp/tools/mcp/mcp_add.py +27 -25
  125. hanzo_mcp/tools/mcp/mcp_remove.py +7 -8
  126. hanzo_mcp/tools/mcp/mcp_stats.py +23 -22
  127. hanzo_mcp/tools/mcp/mcp_tool.py +129 -98
  128. hanzo_mcp/tools/memory/__init__.py +39 -21
  129. hanzo_mcp/tools/memory/knowledge_tools.py +124 -99
  130. hanzo_mcp/tools/memory/memory_tools.py +90 -108
  131. hanzo_mcp/tools/search/__init__.py +7 -2
  132. hanzo_mcp/tools/search/find_tool.py +297 -212
  133. hanzo_mcp/tools/search/unified_search.py +366 -314
  134. hanzo_mcp/tools/shell/__init__.py +8 -7
  135. hanzo_mcp/tools/shell/auto_background.py +56 -49
  136. hanzo_mcp/tools/shell/base.py +1 -1
  137. hanzo_mcp/tools/shell/base_process.py +75 -75
  138. hanzo_mcp/tools/shell/bash_session.py +2 -2
  139. hanzo_mcp/tools/shell/bash_session_executor.py +4 -4
  140. hanzo_mcp/tools/shell/bash_tool.py +24 -31
  141. hanzo_mcp/tools/shell/command_executor.py +12 -12
  142. hanzo_mcp/tools/shell/logs.py +43 -33
  143. hanzo_mcp/tools/shell/npx.py +13 -13
  144. hanzo_mcp/tools/shell/npx_background.py +24 -21
  145. hanzo_mcp/tools/shell/npx_tool.py +18 -22
  146. hanzo_mcp/tools/shell/open.py +19 -21
  147. hanzo_mcp/tools/shell/pkill.py +31 -26
  148. hanzo_mcp/tools/shell/process_tool.py +32 -32
  149. hanzo_mcp/tools/shell/processes.py +57 -58
  150. hanzo_mcp/tools/shell/run_background.py +24 -25
  151. hanzo_mcp/tools/shell/run_command.py +5 -5
  152. hanzo_mcp/tools/shell/run_command_windows.py +5 -5
  153. hanzo_mcp/tools/shell/session_storage.py +3 -3
  154. hanzo_mcp/tools/shell/streaming_command.py +141 -126
  155. hanzo_mcp/tools/shell/uvx.py +24 -25
  156. hanzo_mcp/tools/shell/uvx_background.py +35 -33
  157. hanzo_mcp/tools/shell/uvx_tool.py +18 -22
  158. hanzo_mcp/tools/todo/__init__.py +6 -2
  159. hanzo_mcp/tools/todo/todo.py +50 -37
  160. hanzo_mcp/tools/todo/todo_read.py +5 -8
  161. hanzo_mcp/tools/todo/todo_write.py +5 -7
  162. hanzo_mcp/tools/vector/__init__.py +40 -28
  163. hanzo_mcp/tools/vector/ast_analyzer.py +176 -143
  164. hanzo_mcp/tools/vector/git_ingester.py +170 -179
  165. hanzo_mcp/tools/vector/index_tool.py +96 -44
  166. hanzo_mcp/tools/vector/infinity_store.py +283 -228
  167. hanzo_mcp/tools/vector/mock_infinity.py +39 -40
  168. hanzo_mcp/tools/vector/project_manager.py +88 -78
  169. hanzo_mcp/tools/vector/vector.py +59 -42
  170. hanzo_mcp/tools/vector/vector_index.py +30 -27
  171. hanzo_mcp/tools/vector/vector_search.py +64 -45
  172. hanzo_mcp/types.py +6 -4
  173. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/METADATA +1 -1
  174. hanzo_mcp-0.8.0.dist-info/RECORD +185 -0
  175. hanzo_mcp-0.7.7.dist-info/RECORD +0 -182
  176. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/WHEEL +0 -0
  177. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.dist-info}/entry_points.txt +0 -0
  178. {hanzo_mcp-0.7.7.dist-info → hanzo_mcp-0.8.0.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 tempfile
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, 'r') as f:
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, 'w') as f:
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, 'r') as f:
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, 'w') as f:
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('utf-8', errors='ignore')))
435
-
436
- return total_tokens
429
+ total_tokens += len(encoding.encode(chunk.decode("utf-8", errors="ignore")))
430
+
431
+ return total_tokens