erdo 0.1.31__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.
- erdo/__init__.py +35 -0
- erdo/_generated/__init__.py +18 -0
- erdo/_generated/actions/__init__.py +34 -0
- erdo/_generated/actions/analysis.py +179 -0
- erdo/_generated/actions/bot.py +186 -0
- erdo/_generated/actions/codeexec.py +199 -0
- erdo/_generated/actions/llm.py +148 -0
- erdo/_generated/actions/memory.py +463 -0
- erdo/_generated/actions/pdfextractor.py +97 -0
- erdo/_generated/actions/resource_definitions.py +296 -0
- erdo/_generated/actions/sqlexec.py +90 -0
- erdo/_generated/actions/utils.py +475 -0
- erdo/_generated/actions/webparser.py +119 -0
- erdo/_generated/actions/websearch.py +85 -0
- erdo/_generated/condition/__init__.py +556 -0
- erdo/_generated/internal.py +51 -0
- erdo/_generated/internal_actions.py +91 -0
- erdo/_generated/parameters.py +17 -0
- erdo/_generated/secrets.py +17 -0
- erdo/_generated/template_functions.py +55 -0
- erdo/_generated/types.py +3907 -0
- erdo/actions/__init__.py +40 -0
- erdo/bot_permissions.py +266 -0
- erdo/cli_entry.py +73 -0
- erdo/conditions/__init__.py +11 -0
- erdo/config/__init__.py +5 -0
- erdo/config/config.py +140 -0
- erdo/formatting.py +279 -0
- erdo/install_cli.py +140 -0
- erdo/integrations.py +131 -0
- erdo/invoke/__init__.py +11 -0
- erdo/invoke/client.py +234 -0
- erdo/invoke/invoke.py +555 -0
- erdo/state.py +376 -0
- erdo/sync/__init__.py +17 -0
- erdo/sync/client.py +95 -0
- erdo/sync/extractor.py +492 -0
- erdo/sync/sync.py +327 -0
- erdo/template.py +136 -0
- erdo/test/__init__.py +41 -0
- erdo/test/evaluate.py +272 -0
- erdo/test/runner.py +263 -0
- erdo/types.py +1431 -0
- erdo-0.1.31.dist-info/METADATA +471 -0
- erdo-0.1.31.dist-info/RECORD +48 -0
- erdo-0.1.31.dist-info/WHEEL +4 -0
- erdo-0.1.31.dist-info/entry_points.txt +2 -0
- erdo-0.1.31.dist-info/licenses/LICENSE +22 -0
erdo/actions/__init__.py
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# DO NOT EDIT THIS FILE MANUALLY - it will be overwritten.
|
|
2
|
+
# Generated by: erdo gen-client
|
|
3
|
+
"""
|
|
4
|
+
Actions module re-exports.
|
|
5
|
+
|
|
6
|
+
This module provides clean access to all action services from the top level.
|
|
7
|
+
Users can import like: from erdo.actions import llm, bot, memory
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Import all services as modules
|
|
11
|
+
from .._generated.actions import analysis # noqa: F401
|
|
12
|
+
from .._generated.actions import bot # noqa: F401
|
|
13
|
+
from .._generated.actions import codeexec # noqa: F401
|
|
14
|
+
from .._generated.actions import llm # noqa: F401
|
|
15
|
+
from .._generated.actions import memory # noqa: F401
|
|
16
|
+
from .._generated.actions import resource_definitions # noqa: F401
|
|
17
|
+
from .._generated.actions import sqlexec # noqa: F401
|
|
18
|
+
from .._generated.actions import utils # noqa: F401
|
|
19
|
+
from .._generated.actions import webparser # noqa: F401
|
|
20
|
+
from .._generated.actions import websearch # noqa: F401
|
|
21
|
+
|
|
22
|
+
# Import internal actions explicitly
|
|
23
|
+
from .._generated.internal_actions import checkpoint_attempt # noqa: F401
|
|
24
|
+
from .._generated.internal_actions import refresh_resource # noqa: F401
|
|
25
|
+
|
|
26
|
+
# Make all services available for import
|
|
27
|
+
__all__ = [
|
|
28
|
+
"analysis",
|
|
29
|
+
"bot",
|
|
30
|
+
"checkpoint_attempt",
|
|
31
|
+
"codeexec",
|
|
32
|
+
"llm",
|
|
33
|
+
"memory",
|
|
34
|
+
"refresh_resource",
|
|
35
|
+
"resource_definitions",
|
|
36
|
+
"sqlexec",
|
|
37
|
+
"utils",
|
|
38
|
+
"webparser",
|
|
39
|
+
"websearch",
|
|
40
|
+
]
|
erdo/bot_permissions.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
"""Bot permission management for the Erdo SDK."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BotPermissions:
|
|
10
|
+
"""Manage RBAC permissions for bots."""
|
|
11
|
+
|
|
12
|
+
def __init__(self, base_url: Optional[str] = None):
|
|
13
|
+
"""Initialize bot permissions manager.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
base_url: Erdo server base URL. Defaults to ERDO_SERVER_URL env var or localhost:4000
|
|
17
|
+
"""
|
|
18
|
+
self.base_url = base_url or os.getenv(
|
|
19
|
+
"ERDO_SERVER_URL", "http://localhost:4000"
|
|
20
|
+
)
|
|
21
|
+
self.session = requests.Session()
|
|
22
|
+
|
|
23
|
+
# Set auth token if available
|
|
24
|
+
token = os.getenv("ERDO_AUTH_TOKEN")
|
|
25
|
+
if token:
|
|
26
|
+
self.session.headers.update({"Authorization": f"Bearer {token}"})
|
|
27
|
+
|
|
28
|
+
def set_public_access(
|
|
29
|
+
self, bot_id: str, is_public: bool = True, permission_level: str = "view"
|
|
30
|
+
) -> bool:
|
|
31
|
+
"""Set public access for a bot.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
bot_id: Bot ID
|
|
35
|
+
is_public: Whether to make the bot public
|
|
36
|
+
permission_level: Permission level for public access (view, comment, edit, admin)
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
True if successful, False otherwise
|
|
40
|
+
"""
|
|
41
|
+
try:
|
|
42
|
+
url = f"{self.base_url}/rbac/bot/{bot_id}/public"
|
|
43
|
+
data = {
|
|
44
|
+
"isPublic": is_public,
|
|
45
|
+
"level": permission_level if is_public else None,
|
|
46
|
+
}
|
|
47
|
+
response = self.session.put(url, json=data)
|
|
48
|
+
return response.status_code == 200
|
|
49
|
+
except Exception as e:
|
|
50
|
+
print(f"Error setting public access: {e}")
|
|
51
|
+
return False
|
|
52
|
+
|
|
53
|
+
def set_user_permission(
|
|
54
|
+
self, bot_id: str, user_id: str, permission_level: str
|
|
55
|
+
) -> bool:
|
|
56
|
+
"""Set permissions for a specific user.
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
bot_id: Bot ID
|
|
60
|
+
user_id: User ID
|
|
61
|
+
permission_level: Permission level (view, comment, edit, admin, owner)
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
True if successful, False otherwise
|
|
65
|
+
"""
|
|
66
|
+
try:
|
|
67
|
+
url = f"{self.base_url}/rbac/bot/{bot_id}/user/{user_id}"
|
|
68
|
+
data = {"level": permission_level}
|
|
69
|
+
response = self.session.put(url, json=data)
|
|
70
|
+
return response.status_code == 200
|
|
71
|
+
except Exception as e:
|
|
72
|
+
print(f"Error setting user permission: {e}")
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
def set_org_permission(
|
|
76
|
+
self, bot_id: str, org_id: str, permission_level: str
|
|
77
|
+
) -> bool:
|
|
78
|
+
"""Set permissions for an organization.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
bot_id: Bot ID
|
|
82
|
+
org_id: Organization ID
|
|
83
|
+
permission_level: Permission level (view, comment, edit, admin, owner)
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
True if successful, False otherwise
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
url = f"{self.base_url}/rbac/bot/{bot_id}/org/{org_id}"
|
|
90
|
+
data = {"level": permission_level}
|
|
91
|
+
response = self.session.put(url, json=data)
|
|
92
|
+
return response.status_code == 200
|
|
93
|
+
except Exception as e:
|
|
94
|
+
print(f"Error setting org permission: {e}")
|
|
95
|
+
return False
|
|
96
|
+
|
|
97
|
+
def remove_user_permission(self, bot_id: str, user_id: str) -> bool:
|
|
98
|
+
"""Remove permissions for a specific user.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
bot_id: Bot ID
|
|
102
|
+
user_id: User ID
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
True if successful, False otherwise
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
url = f"{self.base_url}/rbac/bot/{bot_id}/user/{user_id}"
|
|
109
|
+
response = self.session.delete(url)
|
|
110
|
+
return response.status_code == 200
|
|
111
|
+
except Exception as e:
|
|
112
|
+
print(f"Error removing user permission: {e}")
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
def remove_org_permission(self, bot_id: str, org_id: str) -> bool:
|
|
116
|
+
"""Remove permissions for an organization.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
bot_id: Bot ID
|
|
120
|
+
org_id: Organization ID
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
True if successful, False otherwise
|
|
124
|
+
"""
|
|
125
|
+
try:
|
|
126
|
+
url = f"{self.base_url}/rbac/bot/{bot_id}/org/{org_id}"
|
|
127
|
+
response = self.session.delete(url)
|
|
128
|
+
return response.status_code == 200
|
|
129
|
+
except Exception as e:
|
|
130
|
+
print(f"Error removing org permission: {e}")
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
def get_permissions(self, bot_id: str) -> Optional[Dict[str, Any]]:
|
|
134
|
+
"""Get all permissions for a bot.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
bot_id: Bot ID
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
Dictionary with permission details or None if error
|
|
141
|
+
"""
|
|
142
|
+
try:
|
|
143
|
+
url = f"{self.base_url}/rbac/permissions/bot/{bot_id}"
|
|
144
|
+
response = self.session.get(url)
|
|
145
|
+
if response.status_code == 200:
|
|
146
|
+
return response.json()
|
|
147
|
+
return None
|
|
148
|
+
except Exception as e:
|
|
149
|
+
print(f"Error getting permissions: {e}")
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
def check_access(self, bot_id: str, permission_level: str = "view") -> bool:
|
|
153
|
+
"""Check if current user has access to a bot.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
bot_id: Bot ID
|
|
157
|
+
permission_level: Required permission level
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
True if user has access, False otherwise
|
|
161
|
+
"""
|
|
162
|
+
try:
|
|
163
|
+
url = f"{self.base_url}/rbac/access/bot/{bot_id}/{permission_level}"
|
|
164
|
+
response = self.session.get(url)
|
|
165
|
+
if response.status_code == 200:
|
|
166
|
+
data = response.json()
|
|
167
|
+
return data.get("hasAccess", False)
|
|
168
|
+
return False
|
|
169
|
+
except Exception as e:
|
|
170
|
+
print(f"Error checking access: {e}")
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
def invite_user(self, bot_id: str, email: str, permission_level: str) -> bool:
|
|
174
|
+
"""Invite a user to access a bot.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
bot_id: Bot ID
|
|
178
|
+
email: User's email address
|
|
179
|
+
permission_level: Permission level to grant
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
True if successful, False otherwise
|
|
183
|
+
"""
|
|
184
|
+
try:
|
|
185
|
+
url = f"{self.base_url}/rbac/resource/bot/{bot_id}/invite"
|
|
186
|
+
data = {"email": email, "permissionLevel": permission_level}
|
|
187
|
+
response = self.session.post(url, json=data)
|
|
188
|
+
return response.status_code == 200
|
|
189
|
+
except Exception as e:
|
|
190
|
+
print(f"Error inviting user: {e}")
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# Convenience functions for direct use
|
|
195
|
+
def set_bot_public(
|
|
196
|
+
bot_id: str, is_public: bool = True, permission_level: str = "view"
|
|
197
|
+
) -> bool:
|
|
198
|
+
"""Set public access for a bot.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
bot_id: Bot ID
|
|
202
|
+
is_public: Whether to make the bot public
|
|
203
|
+
permission_level: Permission level for public access
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
True if successful, False otherwise
|
|
207
|
+
"""
|
|
208
|
+
permissions = BotPermissions()
|
|
209
|
+
return permissions.set_public_access(bot_id, is_public, permission_level)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def set_bot_user_permission(bot_id: str, user_id: str, permission_level: str) -> bool:
|
|
213
|
+
"""Set permissions for a user on a bot.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
bot_id: Bot ID
|
|
217
|
+
user_id: User ID
|
|
218
|
+
permission_level: Permission level
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
True if successful, False otherwise
|
|
222
|
+
"""
|
|
223
|
+
permissions = BotPermissions()
|
|
224
|
+
return permissions.set_user_permission(bot_id, user_id, permission_level)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def set_bot_org_permission(bot_id: str, org_id: str, permission_level: str) -> bool:
|
|
228
|
+
"""Set permissions for an organization on a bot.
|
|
229
|
+
|
|
230
|
+
Args:
|
|
231
|
+
bot_id: Bot ID
|
|
232
|
+
org_id: Organization ID
|
|
233
|
+
permission_level: Permission level
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
True if successful, False otherwise
|
|
237
|
+
"""
|
|
238
|
+
permissions = BotPermissions()
|
|
239
|
+
return permissions.set_org_permission(bot_id, org_id, permission_level)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def get_bot_permissions(bot_id: str) -> Optional[Dict[str, Any]]:
|
|
243
|
+
"""Get all permissions for a bot.
|
|
244
|
+
|
|
245
|
+
Args:
|
|
246
|
+
bot_id: Bot ID
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
Dictionary with permission details or None if error
|
|
250
|
+
"""
|
|
251
|
+
permissions = BotPermissions()
|
|
252
|
+
return permissions.get_permissions(bot_id)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def check_bot_access(bot_id: str, permission_level: str = "view") -> bool:
|
|
256
|
+
"""Check if current user has access to a bot.
|
|
257
|
+
|
|
258
|
+
Args:
|
|
259
|
+
bot_id: Bot ID
|
|
260
|
+
permission_level: Required permission level
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
True if user has access, False otherwise
|
|
264
|
+
"""
|
|
265
|
+
permissions = BotPermissions()
|
|
266
|
+
return permissions.check_access(bot_id, permission_level)
|
erdo/cli_entry.py
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CLI entry point that executes the downloaded Erdo CLI binary."""
|
|
3
|
+
|
|
4
|
+
import platform
|
|
5
|
+
import shutil
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_binary_path() -> Path:
|
|
13
|
+
"""Get the path to the CLI binary."""
|
|
14
|
+
package_dir = Path(__file__).parent
|
|
15
|
+
bin_dir = package_dir / "bin"
|
|
16
|
+
|
|
17
|
+
# Determine binary name based on platform
|
|
18
|
+
if platform.system().lower() == "windows":
|
|
19
|
+
binary_name = "erdo.exe"
|
|
20
|
+
else:
|
|
21
|
+
binary_name = "erdo"
|
|
22
|
+
|
|
23
|
+
return bin_dir / binary_name
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def find_system_cli() -> Optional[str]:
|
|
27
|
+
"""Find erdo CLI in system PATH, excluding pyenv shims."""
|
|
28
|
+
erdo_path = shutil.which("erdo")
|
|
29
|
+
if erdo_path and "pyenv/shims" not in erdo_path:
|
|
30
|
+
return erdo_path
|
|
31
|
+
return None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def main() -> None:
|
|
35
|
+
"""Main CLI entry point."""
|
|
36
|
+
# First, check if erdo is available in system PATH
|
|
37
|
+
system_cli = find_system_cli()
|
|
38
|
+
if system_cli:
|
|
39
|
+
try:
|
|
40
|
+
result = subprocess.run([system_cli] + sys.argv[1:])
|
|
41
|
+
sys.exit(result.returncode)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f"Error executing system CLI: {e}")
|
|
44
|
+
# Fall through to try local binary
|
|
45
|
+
|
|
46
|
+
# Check for local binary
|
|
47
|
+
binary_path = get_binary_path()
|
|
48
|
+
if not binary_path.exists():
|
|
49
|
+
try:
|
|
50
|
+
from .install_cli import download_and_install_cli
|
|
51
|
+
|
|
52
|
+
download_and_install_cli()
|
|
53
|
+
except Exception as e:
|
|
54
|
+
print(f"Failed to download CLI: {e}")
|
|
55
|
+
print(
|
|
56
|
+
"Please manually download from: https://github.com/erdoai/homebrew-tap/releases"
|
|
57
|
+
)
|
|
58
|
+
sys.exit(1)
|
|
59
|
+
|
|
60
|
+
# Execute the local binary with all arguments
|
|
61
|
+
try:
|
|
62
|
+
result = subprocess.run([str(binary_path)] + sys.argv[1:])
|
|
63
|
+
sys.exit(result.returncode)
|
|
64
|
+
except FileNotFoundError:
|
|
65
|
+
print(f"Error: CLI binary not found at {binary_path}")
|
|
66
|
+
sys.exit(1)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
print(f"Error executing CLI: {e}")
|
|
69
|
+
sys.exit(1)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
main()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# DO NOT EDIT THIS FILE MANUALLY - it will be overwritten.
|
|
2
|
+
# Generated by: erdo gen-client
|
|
3
|
+
"""
|
|
4
|
+
Conditions module for clean imports.
|
|
5
|
+
|
|
6
|
+
This module re-exports all condition classes from the generated condition module
|
|
7
|
+
so users can import like: from erdo.conditions import IsSuccess, And
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Re-export all condition classes from _generated
|
|
11
|
+
from .._generated.condition import * # noqa: F403,F401
|
erdo/config/__init__.py
ADDED
erdo/config/config.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
"""Configuration management for Erdo SDK."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Config:
|
|
10
|
+
"""Configuration manager for Erdo SDK."""
|
|
11
|
+
|
|
12
|
+
def __init__(
|
|
13
|
+
self, endpoint: Optional[str] = None, auth_token: Optional[str] = None
|
|
14
|
+
):
|
|
15
|
+
"""Initialize configuration.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
endpoint: API endpoint URL. If not provided, will try environment variable or config file.
|
|
19
|
+
auth_token: Authentication token. If not provided, will try environment variable or config file.
|
|
20
|
+
"""
|
|
21
|
+
self._endpoint = endpoint
|
|
22
|
+
self._auth_token = auth_token
|
|
23
|
+
self._config_yaml = Path.home() / ".erdo" / "config.yaml"
|
|
24
|
+
self._config_json = Path.home() / ".erdo" / "config.json"
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def endpoint(self) -> str:
|
|
28
|
+
"""Get the API endpoint."""
|
|
29
|
+
if self._endpoint:
|
|
30
|
+
return self._endpoint
|
|
31
|
+
|
|
32
|
+
# Try environment variable
|
|
33
|
+
env_endpoint = os.environ.get("ERDO_ENDPOINT")
|
|
34
|
+
if env_endpoint:
|
|
35
|
+
return env_endpoint
|
|
36
|
+
|
|
37
|
+
# Try config file
|
|
38
|
+
config = self._load_config_file()
|
|
39
|
+
if config and "endpoint" in config:
|
|
40
|
+
return config["endpoint"]
|
|
41
|
+
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"No endpoint configured. Set ERDO_ENDPOINT environment variable or run 'erdo configure'"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
@property
|
|
47
|
+
def auth_token(self) -> str:
|
|
48
|
+
"""Get the authentication token."""
|
|
49
|
+
if self._auth_token:
|
|
50
|
+
return self._auth_token
|
|
51
|
+
|
|
52
|
+
# Try environment variable
|
|
53
|
+
env_token = os.environ.get("ERDO_AUTH_TOKEN")
|
|
54
|
+
if env_token:
|
|
55
|
+
return env_token
|
|
56
|
+
|
|
57
|
+
# Try config file
|
|
58
|
+
config = self._load_config_file()
|
|
59
|
+
if config and "auth_token" in config:
|
|
60
|
+
return config["auth_token"]
|
|
61
|
+
|
|
62
|
+
raise ValueError(
|
|
63
|
+
"No auth token configured. Set ERDO_AUTH_TOKEN environment variable or run 'erdo login'"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def _load_config_file(self) -> Optional[dict]:
|
|
67
|
+
"""Load configuration from file (supports both YAML and JSON)."""
|
|
68
|
+
# Try YAML first (CLI default format)
|
|
69
|
+
if self._config_yaml.exists():
|
|
70
|
+
try:
|
|
71
|
+
import yaml
|
|
72
|
+
|
|
73
|
+
with open(self._config_yaml, "r") as f:
|
|
74
|
+
return yaml.safe_load(f)
|
|
75
|
+
except Exception:
|
|
76
|
+
try:
|
|
77
|
+
# Fallback to simple parsing if yaml not available
|
|
78
|
+
with open(self._config_yaml, "r") as f:
|
|
79
|
+
config = {}
|
|
80
|
+
for line in f:
|
|
81
|
+
if ": " in line:
|
|
82
|
+
key, value = line.strip().split(": ", 1)
|
|
83
|
+
config[key] = value
|
|
84
|
+
return config
|
|
85
|
+
except Exception:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
# Try JSON as fallback
|
|
89
|
+
if self._config_json.exists():
|
|
90
|
+
try:
|
|
91
|
+
with open(self._config_json, "r") as f:
|
|
92
|
+
return json.load(f)
|
|
93
|
+
except (json.JSONDecodeError, IOError):
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
def save(self):
|
|
99
|
+
"""Save configuration to file (as JSON)."""
|
|
100
|
+
self._config_json.parent.mkdir(parents=True, exist_ok=True)
|
|
101
|
+
|
|
102
|
+
config = {}
|
|
103
|
+
if self._endpoint:
|
|
104
|
+
config["endpoint"] = self._endpoint
|
|
105
|
+
if self._auth_token:
|
|
106
|
+
config["auth_token"] = self._auth_token
|
|
107
|
+
|
|
108
|
+
with open(self._config_json, "w") as f:
|
|
109
|
+
json.dump(config, f, indent=2)
|
|
110
|
+
|
|
111
|
+
def set_endpoint(self, endpoint: str):
|
|
112
|
+
"""Set the API endpoint."""
|
|
113
|
+
self._endpoint = endpoint
|
|
114
|
+
|
|
115
|
+
def set_auth_token(self, auth_token: str):
|
|
116
|
+
"""Set the authentication token."""
|
|
117
|
+
self._auth_token = auth_token
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# Global config instance
|
|
121
|
+
_config: Optional[Config] = None
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_config() -> Config:
|
|
125
|
+
"""Get the global configuration instance."""
|
|
126
|
+
global _config
|
|
127
|
+
if _config is None:
|
|
128
|
+
_config = Config()
|
|
129
|
+
return _config
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def set_config(endpoint: Optional[str] = None, auth_token: Optional[str] = None):
|
|
133
|
+
"""Set the global configuration.
|
|
134
|
+
|
|
135
|
+
Args:
|
|
136
|
+
endpoint: API endpoint URL
|
|
137
|
+
auth_token: Authentication token
|
|
138
|
+
"""
|
|
139
|
+
global _config
|
|
140
|
+
_config = Config(endpoint=endpoint, auth_token=auth_token)
|