supyagent 0.1.0__py3-none-any.whl → 0.2.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 supyagent might be problematic. Click here for more details.

supyagent/cli/main.py CHANGED
@@ -17,6 +17,7 @@ from rich.table import Table
17
17
  from typing import Any
18
18
 
19
19
  from supyagent.core.agent import Agent
20
+ from supyagent.core.config import ConfigManager, load_config
20
21
  from supyagent.core.executor import ExecutionRunner
21
22
  from supyagent.core.registry import AgentRegistry
22
23
  from supyagent.core.session_manager import SessionManager
@@ -26,7 +27,7 @@ console = Console()
26
27
 
27
28
 
28
29
  @click.group()
29
- @click.version_option(version="0.1.0", prog_name="supyagent")
30
+ @click.version_option(version="0.2.0", prog_name="supyagent")
30
31
  def cli():
31
32
  """
32
33
  Supyagent - LLM agents powered by supypowers.
@@ -175,6 +176,9 @@ def chat(agent_name: str, new_session: bool, session_id: str | None):
175
176
  By default, resumes the most recent session. Use --new to start fresh,
176
177
  or --session <id> to resume a specific session.
177
178
  """
179
+ # Load global config (API keys) into environment
180
+ load_config()
181
+
178
182
  # Load agent config
179
183
  try:
180
184
  config = load_agent_config(agent_name)
@@ -625,6 +629,9 @@ def run(
625
629
  echo "text" | supyagent run summarizer
626
630
  supyagent run api-caller '{"endpoint": "/users"}' --secrets API_KEY=xxx
627
631
  """
632
+ # Load global config (API keys) into environment
633
+ load_config()
634
+
628
635
  # Load agent config
629
636
  try:
630
637
  config = load_agent_config(agent_name)
@@ -742,6 +749,9 @@ def batch(
742
749
  supyagent batch summarizer inputs.jsonl --output results.jsonl
743
750
  supyagent batch summarizer data.csv --format csv
744
751
  """
752
+ # Load global config (API keys) into environment
753
+ load_config()
754
+
745
755
  # Load agent config
746
756
  try:
747
757
  config = load_agent_config(agent_name)
@@ -879,6 +889,9 @@ def plan(task: str, planner: str, new_session: bool):
879
889
  supyagent plan "Create a Python library for data validation"
880
890
  supyagent plan "Write a blog post about AI" --planner my-planner
881
891
  """
892
+ # Load global config (API keys) into environment
893
+ load_config()
894
+
882
895
  # Load planner config
883
896
  try:
884
897
  config = load_agent_config(planner)
@@ -942,5 +955,171 @@ def cleanup():
942
955
  console.print(f"[green]✓[/green] Cleaned up {count} instance(s)")
943
956
 
944
957
 
958
+ # =============================================================================
959
+ # Config Commands
960
+ # =============================================================================
961
+
962
+
963
+ @cli.group()
964
+ def config():
965
+ """Manage API keys and global configuration."""
966
+ pass
967
+
968
+
969
+ @config.command("set")
970
+ @click.argument("key_name", required=False)
971
+ @click.option(
972
+ "--value",
973
+ "-v",
974
+ help="Set value directly (use with caution - visible in shell history)",
975
+ )
976
+ def config_set(key_name: str | None, value: str | None):
977
+ """
978
+ Set an API key.
979
+
980
+ If KEY_NAME is not provided, shows an interactive menu of common keys.
981
+
982
+ \b
983
+ Examples:
984
+ supyagent config set # Interactive menu
985
+ supyagent config set OPENAI_API_KEY # Set specific key
986
+ supyagent config set MY_KEY -v "value" # Set with value (not recommended)
987
+ """
988
+ config_mgr = ConfigManager()
989
+
990
+ if value:
991
+ if not key_name:
992
+ console.print("[red]Error:[/red] KEY_NAME required when using --value")
993
+ sys.exit(1)
994
+ config_mgr.set(key_name, value)
995
+ console.print(f"[green]✓[/green] Saved {key_name}")
996
+ else:
997
+ config_mgr.set_interactive(key_name)
998
+
999
+
1000
+ @config.command("list")
1001
+ def config_list():
1002
+ """List all configured API keys."""
1003
+ config_mgr = ConfigManager()
1004
+ config_mgr.show_status()
1005
+
1006
+
1007
+ @config.command("delete")
1008
+ @click.argument("key_name")
1009
+ def config_delete(key_name: str):
1010
+ """Delete a stored API key."""
1011
+ config_mgr = ConfigManager()
1012
+
1013
+ if config_mgr.delete(key_name):
1014
+ console.print(f"[green]✓[/green] Deleted {key_name}")
1015
+ else:
1016
+ console.print(f"[yellow]Key not found:[/yellow] {key_name}")
1017
+
1018
+
1019
+ @config.command("import")
1020
+ @click.argument("file_path", type=click.Path(exists=True))
1021
+ @click.option(
1022
+ "--filter",
1023
+ "-f",
1024
+ "key_filter",
1025
+ help="Only import keys matching this prefix (e.g., 'OPENAI')",
1026
+ )
1027
+ def config_import(file_path: str, key_filter: str | None):
1028
+ """
1029
+ Import API keys from a .env file.
1030
+
1031
+ The file should contain KEY=VALUE pairs, one per line.
1032
+ Lines starting with # are ignored.
1033
+
1034
+ \b
1035
+ Examples:
1036
+ supyagent config import .env
1037
+ supyagent config import secrets.env --filter OPENAI
1038
+ """
1039
+ config_mgr = ConfigManager()
1040
+
1041
+ try:
1042
+ # If filter is specified, we need custom handling
1043
+ if key_filter:
1044
+ from pathlib import Path
1045
+ import re
1046
+
1047
+ path = Path(file_path)
1048
+ pattern = re.compile(r"^(?:export\s+)?([A-Z_][A-Z0-9_]*)=(.+)$")
1049
+ imported = 0
1050
+
1051
+ with open(path) as f:
1052
+ for line in f:
1053
+ line = line.strip()
1054
+ if not line or line.startswith("#"):
1055
+ continue
1056
+
1057
+ match = pattern.match(line)
1058
+ if match:
1059
+ name, value = match.groups()
1060
+ if name.startswith(key_filter.upper()):
1061
+ if (value.startswith('"') and value.endswith('"')) or (
1062
+ value.startswith("'") and value.endswith("'")
1063
+ ):
1064
+ value = value[1:-1]
1065
+ config_mgr.set(name, value)
1066
+ console.print(f" [green]✓[/green] {name}")
1067
+ imported += 1
1068
+ else:
1069
+ imported = config_mgr.set_from_file(file_path)
1070
+
1071
+ if imported == 0:
1072
+ console.print("[yellow]No keys found in file[/yellow]")
1073
+ else:
1074
+ console.print(f"\n[green]✓[/green] Imported {imported} key(s)")
1075
+
1076
+ except FileNotFoundError:
1077
+ console.print(f"[red]Error:[/red] File not found: {file_path}")
1078
+ sys.exit(1)
1079
+ except Exception as e:
1080
+ console.print(f"[red]Error:[/red] {e}")
1081
+ sys.exit(1)
1082
+
1083
+
1084
+ @config.command("export")
1085
+ @click.argument("file_path", type=click.Path())
1086
+ @click.option("--force", "-f", is_flag=True, help="Overwrite existing file")
1087
+ def config_export(file_path: str, force: bool):
1088
+ """
1089
+ Export stored API keys to a .env file.
1090
+
1091
+ \b
1092
+ Example:
1093
+ supyagent config export backup.env
1094
+ """
1095
+ config_mgr = ConfigManager()
1096
+ path = Path(file_path)
1097
+
1098
+ if path.exists() and not force:
1099
+ console.print(f"[red]Error:[/red] File exists: {file_path}")
1100
+ console.print("Use --force to overwrite")
1101
+ sys.exit(1)
1102
+
1103
+ keys = config_mgr._load_keys()
1104
+
1105
+ if not keys:
1106
+ console.print("[yellow]No keys to export[/yellow]")
1107
+ return
1108
+
1109
+ with open(path, "w") as f:
1110
+ f.write("# Supyagent API Keys\n")
1111
+ f.write("# Generated export - keep this file secure!\n\n")
1112
+ for name, value in sorted(keys.items()):
1113
+ f.write(f"{name}={value}\n")
1114
+
1115
+ # Set restrictive permissions
1116
+ try:
1117
+ path.chmod(0o600)
1118
+ except OSError:
1119
+ pass
1120
+
1121
+ console.print(f"[green]✓[/green] Exported {len(keys)} key(s) to {file_path}")
1122
+
1123
+
945
1124
  if __name__ == "__main__":
946
1125
  cli()
@@ -1,6 +1,7 @@
1
1
  """Core module for supyagent."""
2
2
 
3
3
  from supyagent.core.agent import Agent
4
+ from supyagent.core.config import ConfigManager, load_config
4
5
  from supyagent.core.context import DelegationContext
5
6
  from supyagent.core.credentials import CredentialManager
6
7
  from supyagent.core.delegation import DelegationManager
@@ -12,10 +13,12 @@ from supyagent.core.session_manager import SessionManager
12
13
  __all__ = [
13
14
  "Agent",
14
15
  "AgentRegistry",
16
+ "ConfigManager",
15
17
  "CredentialManager",
16
18
  "DelegationContext",
17
19
  "DelegationManager",
18
20
  "ExecutionRunner",
19
21
  "LLMClient",
20
22
  "SessionManager",
23
+ "load_config",
21
24
  ]
@@ -0,0 +1,352 @@
1
+ """
2
+ Configuration manager for global settings like LLM API keys.
3
+
4
+ Stores encrypted configuration in ~/.supyagent/config/ that is shared
5
+ across all agents and projects.
6
+ """
7
+
8
+ import getpass
9
+ import json
10
+ import os
11
+ import re
12
+ from pathlib import Path
13
+
14
+ from cryptography.fernet import Fernet, InvalidToken
15
+ from rich.console import Console
16
+ from rich.panel import Panel
17
+ from rich.prompt import Confirm
18
+ from rich.table import Table
19
+
20
+ console = Console()
21
+
22
+ # Common LLM provider API key names
23
+ KNOWN_LLM_KEYS = {
24
+ "OPENAI_API_KEY": "OpenAI (GPT-4, GPT-3.5)",
25
+ "ANTHROPIC_API_KEY": "Anthropic (Claude)",
26
+ "GOOGLE_API_KEY": "Google (Gemini)",
27
+ "AZURE_API_KEY": "Azure OpenAI",
28
+ "AZURE_API_BASE": "Azure OpenAI endpoint",
29
+ "COHERE_API_KEY": "Cohere",
30
+ "HUGGINGFACE_API_KEY": "Hugging Face",
31
+ "REPLICATE_API_KEY": "Replicate",
32
+ "TOGETHER_API_KEY": "Together AI",
33
+ "GROQ_API_KEY": "Groq",
34
+ "MISTRAL_API_KEY": "Mistral AI",
35
+ "PERPLEXITY_API_KEY": "Perplexity AI",
36
+ "DEEPSEEK_API_KEY": "DeepSeek",
37
+ "FIREWORKS_API_KEY": "Fireworks AI",
38
+ "OLLAMA_API_BASE": "Ollama (local) base URL",
39
+ }
40
+
41
+
42
+ class ConfigManager:
43
+ """
44
+ Manages global configuration including LLM API keys.
45
+
46
+ Configuration is stored encrypted in ~/.supyagent/config/
47
+ and automatically loaded into environment variables when
48
+ agents are run.
49
+
50
+ Directory structure:
51
+ ~/.supyagent/config/.key # Encryption key
52
+ ~/.supyagent/config/keys.enc # Encrypted API keys
53
+ """
54
+
55
+ def __init__(self, base_dir: Path | None = None):
56
+ """
57
+ Initialize the config manager.
58
+
59
+ Args:
60
+ base_dir: Base directory for config storage.
61
+ Defaults to ~/.supyagent/config/
62
+ """
63
+ if base_dir is None:
64
+ base_dir = Path.home() / ".supyagent" / "config"
65
+
66
+ self.base_dir = base_dir
67
+ self.base_dir.mkdir(parents=True, exist_ok=True)
68
+ self._fernet = self._get_fernet()
69
+ self._cache: dict[str, str] | None = None
70
+
71
+ def _get_fernet(self) -> Fernet:
72
+ """Get or create the encryption key."""
73
+ key_file = self.base_dir / ".key"
74
+
75
+ if key_file.exists():
76
+ key = key_file.read_bytes()
77
+ else:
78
+ key = Fernet.generate_key()
79
+ key_file.write_bytes(key)
80
+ try:
81
+ key_file.chmod(0o600)
82
+ except OSError:
83
+ pass
84
+
85
+ return Fernet(key)
86
+
87
+ def _keys_path(self) -> Path:
88
+ """Get path to the encrypted keys file."""
89
+ return self.base_dir / "keys.enc"
90
+
91
+ def _load_keys(self) -> dict[str, str]:
92
+ """Load and decrypt stored keys."""
93
+ if self._cache is not None:
94
+ return self._cache
95
+
96
+ path = self._keys_path()
97
+ if not path.exists():
98
+ self._cache = {}
99
+ return {}
100
+
101
+ try:
102
+ encrypted = path.read_bytes()
103
+ decrypted = self._fernet.decrypt(encrypted)
104
+ keys = json.loads(decrypted)
105
+ self._cache = keys
106
+ return keys
107
+ except (InvalidToken, json.JSONDecodeError):
108
+ self._cache = {}
109
+ return {}
110
+
111
+ def _save_keys(self, keys: dict[str, str]) -> None:
112
+ """Encrypt and save keys."""
113
+ encrypted = self._fernet.encrypt(json.dumps(keys).encode())
114
+ path = self._keys_path()
115
+ path.write_bytes(encrypted)
116
+ try:
117
+ path.chmod(0o600)
118
+ except OSError:
119
+ pass
120
+ self._cache = keys
121
+
122
+ def get(self, name: str) -> str | None:
123
+ """
124
+ Get a config value.
125
+
126
+ Checks environment first, then stored config.
127
+
128
+ Args:
129
+ name: Key name
130
+
131
+ Returns:
132
+ Value or None
133
+ """
134
+ # Environment takes precedence
135
+ if name in os.environ:
136
+ return os.environ[name]
137
+
138
+ keys = self._load_keys()
139
+ return keys.get(name)
140
+
141
+ def set(self, name: str, value: str) -> None:
142
+ """
143
+ Set a config value.
144
+
145
+ Args:
146
+ name: Key name
147
+ value: Key value
148
+ """
149
+ keys = self._load_keys()
150
+ keys[name] = value
151
+ self._save_keys(keys)
152
+
153
+ def delete(self, name: str) -> bool:
154
+ """
155
+ Delete a stored key.
156
+
157
+ Args:
158
+ name: Key name
159
+
160
+ Returns:
161
+ True if deleted, False if not found
162
+ """
163
+ keys = self._load_keys()
164
+ if name in keys:
165
+ del keys[name]
166
+ self._save_keys(keys)
167
+ return True
168
+ return False
169
+
170
+ def list_keys(self) -> list[str]:
171
+ """List all stored key names."""
172
+ return list(self._load_keys().keys())
173
+
174
+ def load_into_environment(self) -> int:
175
+ """
176
+ Load all stored keys into environment variables.
177
+
178
+ Only sets variables that aren't already in the environment.
179
+
180
+ Returns:
181
+ Number of keys loaded
182
+ """
183
+ keys = self._load_keys()
184
+ loaded = 0
185
+
186
+ for name, value in keys.items():
187
+ if name not in os.environ:
188
+ os.environ[name] = value
189
+ loaded += 1
190
+
191
+ return loaded
192
+
193
+ def set_interactive(self, name: str | None = None) -> bool:
194
+ """
195
+ Interactively prompt user to set a key.
196
+
197
+ Args:
198
+ name: Key name, or None to show a menu of common keys
199
+
200
+ Returns:
201
+ True if key was set
202
+ """
203
+ if name is None:
204
+ # Show menu of common keys
205
+ console.print()
206
+ console.print("[bold]Common LLM API Keys:[/bold]")
207
+ console.print()
208
+
209
+ items = list(KNOWN_LLM_KEYS.items())
210
+ for i, (key, desc) in enumerate(items, 1):
211
+ status = "[green]✓[/green]" if self.get(key) else "[dim]○[/dim]"
212
+ console.print(f" {status} [{i}] {key}")
213
+ console.print(f" [dim]{desc}[/dim]")
214
+
215
+ console.print()
216
+ console.print(f" [0] Enter custom key name")
217
+ console.print()
218
+
219
+ choice = input("Select key to set (number or name): ").strip()
220
+
221
+ if choice == "0":
222
+ name = input("Enter key name: ").strip()
223
+ if not name:
224
+ return False
225
+ elif choice.isdigit():
226
+ idx = int(choice) - 1
227
+ if 0 <= idx < len(items):
228
+ name = items[idx][0]
229
+ else:
230
+ console.print("[red]Invalid selection[/red]")
231
+ return False
232
+ else:
233
+ # Treat as key name
234
+ name = choice.upper()
235
+
236
+ # Get the value
237
+ description = KNOWN_LLM_KEYS.get(name, "API key")
238
+ console.print()
239
+ console.print(
240
+ Panel(
241
+ f"[bold]{name}[/bold]\n{description}",
242
+ title="🔑 Set API Key",
243
+ border_style="blue",
244
+ )
245
+ )
246
+
247
+ value = getpass.getpass("Enter value (or press Enter to cancel): ")
248
+
249
+ if not value:
250
+ console.print("[dim]Cancelled[/dim]")
251
+ return False
252
+
253
+ self.set(name, value)
254
+ console.print(f"[green]✓[/green] Saved {name}")
255
+ return True
256
+
257
+ def set_from_file(self, file_path: str | Path) -> int:
258
+ """
259
+ Load keys from a .env file.
260
+
261
+ File format (one per line):
262
+ KEY_NAME=value
263
+ # comments are ignored
264
+ export KEY_NAME=value # export prefix is stripped
265
+
266
+ Args:
267
+ file_path: Path to .env file
268
+
269
+ Returns:
270
+ Number of keys imported
271
+ """
272
+ path = Path(file_path)
273
+ if not path.exists():
274
+ raise FileNotFoundError(f"File not found: {file_path}")
275
+
276
+ imported = 0
277
+ pattern = re.compile(r"^(?:export\s+)?([A-Z_][A-Z0-9_]*)=(.+)$")
278
+
279
+ with open(path) as f:
280
+ for line in f:
281
+ line = line.strip()
282
+
283
+ # Skip empty lines and comments
284
+ if not line or line.startswith("#"):
285
+ continue
286
+
287
+ match = pattern.match(line)
288
+ if match:
289
+ name, value = match.groups()
290
+
291
+ # Strip quotes if present
292
+ if (value.startswith('"') and value.endswith('"')) or \
293
+ (value.startswith("'") and value.endswith("'")):
294
+ value = value[1:-1]
295
+
296
+ self.set(name, value)
297
+ imported += 1
298
+
299
+ return imported
300
+
301
+ def show_status(self) -> None:
302
+ """Display current configuration status."""
303
+ keys = self._load_keys()
304
+
305
+ if not keys:
306
+ console.print("[dim]No API keys configured[/dim]")
307
+ console.print()
308
+ console.print("Run [cyan]supyagent config set[/cyan] to add keys")
309
+ return
310
+
311
+ table = Table(title="Configured API Keys")
312
+ table.add_column("Key", style="cyan")
313
+ table.add_column("Provider")
314
+ table.add_column("Status")
315
+
316
+ for name in sorted(keys.keys()):
317
+ provider = KNOWN_LLM_KEYS.get(name, "Custom")
318
+ # Show if it's overridden by environment
319
+ if name in os.environ and os.environ[name] != keys[name]:
320
+ status = "[yellow]env override[/yellow]"
321
+ else:
322
+ status = "[green]stored[/green]"
323
+
324
+ table.add_row(name, provider, status)
325
+
326
+ console.print(table)
327
+ console.print()
328
+ console.print(f"[dim]Config location: {self.base_dir}[/dim]")
329
+
330
+
331
+ # Global config manager instance
332
+ _config_manager: ConfigManager | None = None
333
+
334
+
335
+ def get_config_manager() -> ConfigManager:
336
+ """Get the global config manager instance."""
337
+ global _config_manager
338
+ if _config_manager is None:
339
+ _config_manager = ConfigManager()
340
+ return _config_manager
341
+
342
+
343
+ def load_config() -> int:
344
+ """
345
+ Load global config into environment.
346
+
347
+ Call this at the start of any agent execution.
348
+
349
+ Returns:
350
+ Number of keys loaded
351
+ """
352
+ return get_config_manager().load_into_environment()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: supyagent
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: LLM agents powered by supypowers - build AI agents with tool use, multi-agent orchestration, and secure credential management
5
5
  Project-URL: Homepage, https://github.com/ergodic-ai/supyagent
6
6
  Project-URL: Documentation, https://github.com/ergodic-ai/supyagent#readme
@@ -68,6 +68,9 @@ uv pip install supyagent
68
68
  ## Quick Start
69
69
 
70
70
  ```bash
71
+ # Set up your API key (stored securely)
72
+ supyagent config set ANTHROPIC_API_KEY
73
+
71
74
  # Create your first agent
72
75
  supyagent new myagent
73
76
 
@@ -133,39 +136,40 @@ supyagent plan "Build a Python library for data validation"
133
136
  # The planner will delegate to specialist agents (coder, writer, researcher)
134
137
  ```
135
138
 
136
- ## Agent Configuration
139
+ ## Configuration
137
140
 
138
- Agents are defined in YAML files in the `agents/` directory:
141
+ ### Setting Up API Keys
139
142
 
140
- ```yaml
141
- name: researcher
142
- description: An AI research assistant
143
- type: interactive # or "execution"
143
+ Supyagent securely stores your LLM API keys so you don't need to export them every time:
144
144
 
145
- model:
146
- provider: anthropic/claude-3-5-sonnet-20241022
147
- temperature: 0.7
148
- max_tokens: 4096
145
+ ```bash
146
+ # Interactive setup (recommended)
147
+ supyagent config set
148
+ # Shows a menu of common providers to choose from
149
149
 
150
- system_prompt: |
151
- You are a helpful research assistant...
150
+ # Set a specific key
151
+ supyagent config set ANTHROPIC_API_KEY
152
+ supyagent config set OPENAI_API_KEY
152
153
 
153
- tools:
154
- allow:
155
- - "*" # Allow all tools
156
- # or be specific:
157
- # - "web:*" # All functions in web.py
158
- # - "math:calc" # Specific function
159
- deny:
160
- - "dangerous:*" # Block specific tools
154
+ # Import from a .env file
155
+ supyagent config import .env
161
156
 
162
- # For multi-agent support
163
- delegates:
164
- - coder
165
- - writer
157
+ # Import only specific keys
158
+ supyagent config import .env --filter OPENAI
159
+
160
+ # List configured keys
161
+ supyagent config list
162
+
163
+ # Export keys to backup
164
+ supyagent config export backup.env
165
+
166
+ # Delete a key
167
+ supyagent config delete OPENAI_API_KEY
166
168
  ```
167
169
 
168
- ### LLM Providers
170
+ Keys are encrypted and stored in `~/.supyagent/config/`. They're automatically loaded when running any agent command.
171
+
172
+ ### Supported Providers
169
173
 
170
174
  Supyagent uses LiteLLM, supporting 100+ providers:
171
175
 
@@ -187,11 +191,36 @@ model:
187
191
  provider: gemini/gemini-pro
188
192
  ```
189
193
 
190
- Set API keys as environment variables:
194
+ ## Agent Configuration
191
195
 
192
- ```bash
193
- export OPENAI_API_KEY=sk-...
194
- export ANTHROPIC_API_KEY=sk-ant-...
196
+ Agents are defined in YAML files in the `agents/` directory:
197
+
198
+ ```yaml
199
+ name: researcher
200
+ description: An AI research assistant
201
+ type: interactive # or "execution"
202
+
203
+ model:
204
+ provider: anthropic/claude-3-5-sonnet-20241022
205
+ temperature: 0.7
206
+ max_tokens: 4096
207
+
208
+ system_prompt: |
209
+ You are a helpful research assistant...
210
+
211
+ tools:
212
+ allow:
213
+ - "*" # Allow all tools
214
+ # or be specific:
215
+ # - "web:*" # All functions in web.py
216
+ # - "math:calc" # Specific function
217
+ deny:
218
+ - "dangerous:*" # Block specific tools
219
+
220
+ # For multi-agent support
221
+ delegates:
222
+ - coder
223
+ - writer
195
224
  ```
196
225
 
197
226
  ## CLI Reference
@@ -223,6 +252,16 @@ export ANTHROPIC_API_KEY=sk-ant-...
223
252
  | `supyagent agents` | List active agent instances |
224
253
  | `supyagent cleanup` | Remove completed instances |
225
254
 
255
+ ### Config Commands
256
+
257
+ | Command | Description |
258
+ |---------|-------------|
259
+ | `supyagent config set [KEY]` | Set an API key (interactive menu if no key specified) |
260
+ | `supyagent config list` | List all configured keys |
261
+ | `supyagent config import <file>` | Import keys from .env file |
262
+ | `supyagent config export <file>` | Export keys to .env file |
263
+ | `supyagent config delete <key>` | Delete a stored key |
264
+
226
265
  ### In-Chat Commands
227
266
 
228
267
  While chatting, use these commands:
@@ -1,9 +1,10 @@
1
1
  supyagent/__init__.py,sha256=JrrQbk1bmDhUb2qOqxqDGBq1V-oArwVS4f5f9Qu-VcM,77
2
2
  supyagent/__main__.py,sha256=szUvsccEIA428c2_5J_X1qzq4L44qLvDvHfuRPYoy7c,121
3
3
  supyagent/cli/__init__.py,sha256=7jb5kxHtzce4rNKd9zrAiATp3Zxj7UQvtpQsyA97BTc,32
4
- supyagent/cli/main.py,sha256=5oM_IZ85C3gyW_ypPmFi3x-khkgd7tZrbowW_kJ_JhY,31028
5
- supyagent/core/__init__.py,sha256=5GOA6Ca5UBWG_d6lhfZxYdKdDjnKE4UdVooe0lUDpe0,626
4
+ supyagent/cli/main.py,sha256=xV34tTDORoFnp-MU_l0e_RP675bWeUdh3ohVB9uVsBg,36328
5
+ supyagent/core/__init__.py,sha256=7MI6K2-OR_Fg0wcMziC4u3gNa0YxWmzJXO0RtOCevUg,727
6
6
  supyagent/core/agent.py,sha256=mkiV6CnK6Vk0Wg-DiAMm29wAViMa52-1Yb33Y5-FvQY,12655
7
+ supyagent/core/config.py,sha256=kuTpuxy-qr-fhou7rJ-JLF82cdzdHQTu46kCZccSw1Y,10041
7
8
  supyagent/core/context.py,sha256=ywen6IjWxcudxtipeqA_PHpZKdjK52eqOb9xy7QKnTM,4565
8
9
  supyagent/core/credentials.py,sha256=35JBrzMOEDzHnn3XuyGmRjXVdSICL3tups3BLO_NCXQ,7600
9
10
  supyagent/core/delegation.py,sha256=B6OYlLjkZaRTPMQsK9nIGeXVO9JeC7RDpxXsOk652xs,9391
@@ -17,8 +18,8 @@ supyagent/models/agent_config.py,sha256=sQ7S7zExoFs0g-IMgACsGH2SMiDRjJTnCVZwhN6D
17
18
  supyagent/models/session.py,sha256=_Dwuija_Uvt_KzcNp82ja8qP5ltNHHz43Llug1vq1ic,1136
18
19
  supyagent/utils/__init__.py,sha256=l4HdE3n8axphMInBex1J1dB_O1MjzbZz6ot1l8Syf0g,39
19
20
  supyagent/utils/paths.py,sha256=Xum9kX_UjRvmshifSq423QsAKynpnyAVfpWuFjlsdDQ,692
20
- supyagent-0.1.0.dist-info/METADATA,sha256=ooG14oWAy-YelwL-QC3DDZL8aFhDzM6Gs9mdjFW3xw4,8430
21
- supyagent-0.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
22
- supyagent-0.1.0.dist-info/entry_points.txt,sha256=7dFXWtRewhLfA5XUckV7Yu1E5_c6c9lq8Gz-7BFs6TM,53
23
- supyagent-0.1.0.dist-info/licenses/LICENSE,sha256=35fw1cvTM-IhiR3xaUohbiFBc2OBNRjI5z1b1cF7vZI,1067
24
- supyagent-0.1.0.dist-info/RECORD,,
21
+ supyagent-0.2.0.dist-info/METADATA,sha256=lnle2YIGJXssPNm8jllCvhA_202_j3DL9CUNFiRGHO0,9562
22
+ supyagent-0.2.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
23
+ supyagent-0.2.0.dist-info/entry_points.txt,sha256=7dFXWtRewhLfA5XUckV7Yu1E5_c6c9lq8Gz-7BFs6TM,53
24
+ supyagent-0.2.0.dist-info/licenses/LICENSE,sha256=35fw1cvTM-IhiR3xaUohbiFBc2OBNRjI5z1b1cF7vZI,1067
25
+ supyagent-0.2.0.dist-info/RECORD,,