cade-cli 0.3.3__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.
- cade_cli-0.3.3.dist-info/METADATA +151 -0
- cade_cli-0.3.3.dist-info/RECORD +44 -0
- cade_cli-0.3.3.dist-info/WHEEL +4 -0
- cade_cli-0.3.3.dist-info/entry_points.txt +2 -0
- cadecoder/__init__.py +1 -0
- cadecoder/ai/__init__.py +6 -0
- cadecoder/ai/prompts.py +572 -0
- cadecoder/cli/__init__.py +0 -0
- cadecoder/cli/app.py +147 -0
- cadecoder/cli/auth.py +483 -0
- cadecoder/cli/commands/__init__.py +5 -0
- cadecoder/cli/commands/auth.py +143 -0
- cadecoder/cli/commands/chat.py +264 -0
- cadecoder/cli/commands/mcp.py +477 -0
- cadecoder/cli/commands/tools.py +226 -0
- cadecoder/core/__init__.py +12 -0
- cadecoder/core/config.py +380 -0
- cadecoder/core/constants.py +281 -0
- cadecoder/core/errors.py +145 -0
- cadecoder/core/logging.py +148 -0
- cadecoder/core/types.py +235 -0
- cadecoder/core/utils.py +279 -0
- cadecoder/execution/__init__.py +46 -0
- cadecoder/execution/context_window.py +521 -0
- cadecoder/execution/orchestrator.py +562 -0
- cadecoder/execution/parallel.py +287 -0
- cadecoder/providers/__init__.py +60 -0
- cadecoder/providers/base.py +294 -0
- cadecoder/providers/openai.py +251 -0
- cadecoder/storage/__init__.py +0 -0
- cadecoder/storage/threads.py +489 -0
- cadecoder/templates/login_failed.html +21 -0
- cadecoder/templates/login_success.html +21 -0
- cadecoder/templates/styles.css +87 -0
- cadecoder/tools/__init__.py +19 -0
- cadecoder/tools/builtin.py +644 -0
- cadecoder/tools/filesystem.py +315 -0
- cadecoder/tools/git.py +221 -0
- cadecoder/tools/manager.py +1635 -0
- cadecoder/ui/__init__.py +7 -0
- cadecoder/ui/display.py +338 -0
- cadecoder/ui/input.py +145 -0
- cadecoder/ui/session.py +455 -0
- cadecoder/ui/state.py +20 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Core modules for CadeCoder.
|
|
2
|
+
|
|
3
|
+
Note: config is intentionally NOT imported at module level to avoid
|
|
4
|
+
triggering auth validation on simple imports (e.g., importing constants).
|
|
5
|
+
Use `from cadecoder.core.config import get_config` when config is needed.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from cadecoder.core.errors import CadeCoderError
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"CadeCoderError",
|
|
12
|
+
]
|
cadecoder/core/config.py
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"""Configuration management for CadeCoder CLI.
|
|
2
|
+
|
|
3
|
+
Handles loading Arcade credentials (via arcade-core) and CadeCoder specific settings.
|
|
4
|
+
Credentials are shared with arcade-cli at ~/.arcade/credentials.yaml.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
import threading
|
|
9
|
+
import tomllib
|
|
10
|
+
from functools import lru_cache
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
import toml
|
|
14
|
+
import yaml
|
|
15
|
+
from arcade_core.auth_tokens import get_valid_access_token
|
|
16
|
+
from arcade_core.config_model import Config as ArcadeConfig
|
|
17
|
+
from arcade_core.constants import PROD_COORDINATOR_HOST
|
|
18
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
19
|
+
|
|
20
|
+
from cadecoder.core import constants
|
|
21
|
+
from cadecoder.core.errors import AuthError, ConfigError
|
|
22
|
+
|
|
23
|
+
# --- Constants ---
|
|
24
|
+
|
|
25
|
+
CADECODER_CONFIG_DIR_NAME = "cadecoder"
|
|
26
|
+
CADECODER_CONFIG_FILE_NAME = "cadecoder.toml"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# --- Pydantic Models for CadeCoder Settings ---
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ResponsesAPIConfig(BaseModel):
|
|
33
|
+
"""Configuration for Responses API."""
|
|
34
|
+
|
|
35
|
+
model_config = ConfigDict(extra="ignore")
|
|
36
|
+
enabled: bool = Field(default=True, description="Enable Responses API when available")
|
|
37
|
+
streaming_enabled: bool = Field(
|
|
38
|
+
default=True, description="Enable streaming responses by default"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ModelConfig(BaseModel):
|
|
43
|
+
"""Model configuration settings."""
|
|
44
|
+
|
|
45
|
+
model_config = ConfigDict(extra="ignore")
|
|
46
|
+
provider: str = Field(
|
|
47
|
+
default="openai", description="AI provider name (openai, anthropic, etc.)"
|
|
48
|
+
)
|
|
49
|
+
model: str = Field(default=constants.DEFAULT_AI_MODEL, description="Model name")
|
|
50
|
+
host: str | None = Field(
|
|
51
|
+
default=None,
|
|
52
|
+
description="Custom API host (for OpenAI-compatible APIs)",
|
|
53
|
+
)
|
|
54
|
+
api_key: str | None = Field(default=None, description="API key (overrides environment)")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ToolConfig(BaseModel):
|
|
58
|
+
"""Tool configuration settings."""
|
|
59
|
+
|
|
60
|
+
model_config = ConfigDict(extra="ignore")
|
|
61
|
+
included_toolkits: list[str] = Field(
|
|
62
|
+
default_factory=lambda: list(constants.DEFAULT_INCLUDED_TOOLKITS),
|
|
63
|
+
description="List of toolkit names to include",
|
|
64
|
+
)
|
|
65
|
+
included_tools: list[str] = Field(
|
|
66
|
+
default_factory=lambda: list(constants.DEFAULT_INCLUDED_TOOLS),
|
|
67
|
+
description="List of specific tool names to include",
|
|
68
|
+
)
|
|
69
|
+
enabled_tools: list[str] = Field(
|
|
70
|
+
default_factory=list,
|
|
71
|
+
description="List of enabled tool names (local and remote)",
|
|
72
|
+
)
|
|
73
|
+
disabled_tools: list[str] = Field(
|
|
74
|
+
default_factory=list,
|
|
75
|
+
description="List of disabled tool names (local and remote)",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class CadeCoderSettings(BaseModel):
|
|
80
|
+
"""Model for settings specific to cadecoder.toml."""
|
|
81
|
+
|
|
82
|
+
model_config = ConfigDict(extra="ignore")
|
|
83
|
+
default_model: str = constants.DEFAULT_AI_MODEL
|
|
84
|
+
debug_mode: bool = Field(
|
|
85
|
+
default=True,
|
|
86
|
+
description="Enable debug mode for verbose logging and error details.",
|
|
87
|
+
)
|
|
88
|
+
use_responses_api: bool = Field(
|
|
89
|
+
default=True, description="Use OpenAI Responses API when available"
|
|
90
|
+
)
|
|
91
|
+
responses_config: ResponsesAPIConfig = Field(
|
|
92
|
+
default_factory=ResponsesAPIConfig,
|
|
93
|
+
description="Responses API specific configuration",
|
|
94
|
+
)
|
|
95
|
+
model_settings: ModelConfig = Field(
|
|
96
|
+
default_factory=ModelConfig, description="Model configuration"
|
|
97
|
+
)
|
|
98
|
+
tool_settings: ToolConfig = Field(default_factory=ToolConfig, description="Tool configuration")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# --- Helper Functions ---
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _get_cadecoder_config_path() -> Path:
|
|
105
|
+
"""Gets the path to the cadecoder specific config file."""
|
|
106
|
+
home = Path.home()
|
|
107
|
+
cadecoder_dir = home / ".cadecoder"
|
|
108
|
+
return cadecoder_dir / CADECODER_CONFIG_FILE_NAME
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@lru_cache(maxsize=1)
|
|
112
|
+
def _load_cadecoder_settings() -> CadeCoderSettings:
|
|
113
|
+
"""Loads the CadeCoder specific settings from cadecoder.toml.
|
|
114
|
+
|
|
115
|
+
Creates a default file if it doesn't exist.
|
|
116
|
+
Returns default settings if file is invalid or missing.
|
|
117
|
+
"""
|
|
118
|
+
config_path = _get_cadecoder_config_path()
|
|
119
|
+
|
|
120
|
+
if config_path.exists():
|
|
121
|
+
try:
|
|
122
|
+
with config_path.open("rb") as f:
|
|
123
|
+
config_data = tomllib.load(f)
|
|
124
|
+
return CadeCoderSettings.model_validate(config_data)
|
|
125
|
+
except tomllib.TOMLDecodeError as e:
|
|
126
|
+
raise ConfigError(f"Error decoding TOML file '{config_path}': {e}. Using defaults.")
|
|
127
|
+
except OSError as e:
|
|
128
|
+
raise ConfigError(f"Error reading settings file '{config_path}': {e}. Using defaults.")
|
|
129
|
+
else:
|
|
130
|
+
default_settings = CadeCoderSettings()
|
|
131
|
+
_save_cadecoder_settings(default_settings)
|
|
132
|
+
return default_settings
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def _save_cadecoder_settings(settings: CadeCoderSettings) -> None:
|
|
136
|
+
"""Save the CadeCoder specific settings to cadecoder.toml."""
|
|
137
|
+
config_path = _get_cadecoder_config_path()
|
|
138
|
+
try:
|
|
139
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
config_data = settings.model_dump(mode="python")
|
|
141
|
+
with config_path.open("w", encoding="utf-8") as f:
|
|
142
|
+
toml.dump(config_data, f)
|
|
143
|
+
except (OSError, TypeError) as e:
|
|
144
|
+
raise ConfigError(f"Error writing CadeCoder settings file '{config_path}': {e}") from e
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _load_arcade_config() -> ArcadeConfig | None:
|
|
148
|
+
"""Load arcade-core config if available."""
|
|
149
|
+
try:
|
|
150
|
+
return ArcadeConfig.load_from_file()
|
|
151
|
+
except (FileNotFoundError, ValueError):
|
|
152
|
+
return None
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _check_legacy_api_key() -> tuple[str, str] | None:
|
|
156
|
+
"""Check for legacy API key credentials (backward compatibility).
|
|
157
|
+
|
|
158
|
+
Returns:
|
|
159
|
+
Tuple of (api_key, email) if found, None otherwise.
|
|
160
|
+
"""
|
|
161
|
+
creds_path = Path.home() / ".arcade" / "credentials.yaml"
|
|
162
|
+
if not creds_path.exists():
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
with creds_path.open() as f:
|
|
167
|
+
data = yaml.safe_load(f) or {}
|
|
168
|
+
|
|
169
|
+
cloud = data.get("cloud", {})
|
|
170
|
+
if isinstance(cloud, dict) and "api" in cloud:
|
|
171
|
+
api_key = cloud.get("api", {}).get("key")
|
|
172
|
+
email = cloud.get("user", {}).get("email")
|
|
173
|
+
if api_key and email:
|
|
174
|
+
return api_key, email
|
|
175
|
+
except Exception:
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# --- Singleton Config Class ---
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class AppConfig:
|
|
185
|
+
"""Singleton configuration class for CadeCoder.
|
|
186
|
+
|
|
187
|
+
Loads credentials from arcade-core (OAuth) or falls back to
|
|
188
|
+
legacy API key format. CadeCoder-specific settings are loaded
|
|
189
|
+
from ~/.cadecoder/cadecoder.toml.
|
|
190
|
+
"""
|
|
191
|
+
|
|
192
|
+
_instance: "AppConfig | None" = None
|
|
193
|
+
_lock = threading.Lock()
|
|
194
|
+
|
|
195
|
+
def __new__(cls) -> "AppConfig":
|
|
196
|
+
"""Create or return the singleton instance."""
|
|
197
|
+
if cls._instance is None:
|
|
198
|
+
with cls._lock:
|
|
199
|
+
if cls._instance is None:
|
|
200
|
+
cls._instance = super().__new__(cls)
|
|
201
|
+
cls._instance._load_configuration()
|
|
202
|
+
return cls._instance
|
|
203
|
+
|
|
204
|
+
def _load_configuration(self) -> None:
|
|
205
|
+
"""Load configurations from arcade-core and cadecoder.toml."""
|
|
206
|
+
self._arcade_config: ArcadeConfig | None = None
|
|
207
|
+
self._legacy_api_key: str | None = None
|
|
208
|
+
self._legacy_email: str | None = None
|
|
209
|
+
|
|
210
|
+
# Try to load arcade-core config (new OAuth format)
|
|
211
|
+
self._arcade_config = _load_arcade_config()
|
|
212
|
+
|
|
213
|
+
# Check for environment variable override
|
|
214
|
+
env_api_key = os.environ.get("ARCADE_API_KEY")
|
|
215
|
+
env_email = os.environ.get("ARCADE_USER_EMAIL")
|
|
216
|
+
|
|
217
|
+
if env_api_key:
|
|
218
|
+
# Environment variable takes precedence
|
|
219
|
+
self._legacy_api_key = env_api_key
|
|
220
|
+
self._legacy_email = env_email
|
|
221
|
+
elif self._arcade_config and self._arcade_config.is_authenticated():
|
|
222
|
+
# New OAuth format is available
|
|
223
|
+
pass
|
|
224
|
+
else:
|
|
225
|
+
# Fall back to legacy API key format
|
|
226
|
+
legacy = _check_legacy_api_key()
|
|
227
|
+
if legacy:
|
|
228
|
+
self._legacy_api_key, self._legacy_email = legacy
|
|
229
|
+
|
|
230
|
+
# Load CadeCoder-specific settings
|
|
231
|
+
try:
|
|
232
|
+
self._settings = _load_cadecoder_settings()
|
|
233
|
+
except ConfigError:
|
|
234
|
+
self._settings = CadeCoderSettings()
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def is_authenticated(self) -> bool:
|
|
238
|
+
"""Check if user is authenticated (OAuth or API key)."""
|
|
239
|
+
if self._legacy_api_key:
|
|
240
|
+
return True
|
|
241
|
+
if self._arcade_config and self._arcade_config.is_authenticated():
|
|
242
|
+
return True
|
|
243
|
+
return False
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def api_key(self) -> str:
|
|
247
|
+
"""Get the API key or access token for Arcade API calls.
|
|
248
|
+
|
|
249
|
+
For OAuth auth, this returns a valid access token (auto-refreshed).
|
|
250
|
+
For legacy auth, this returns the API key.
|
|
251
|
+
|
|
252
|
+
Raises:
|
|
253
|
+
AuthError: If not authenticated.
|
|
254
|
+
"""
|
|
255
|
+
# Environment variable override
|
|
256
|
+
if self._legacy_api_key:
|
|
257
|
+
return self._legacy_api_key
|
|
258
|
+
|
|
259
|
+
# Try OAuth token
|
|
260
|
+
if self._arcade_config and self._arcade_config.is_authenticated():
|
|
261
|
+
try:
|
|
262
|
+
coordinator_url = (
|
|
263
|
+
self._arcade_config.coordinator_url or f"https://{PROD_COORDINATOR_HOST}"
|
|
264
|
+
)
|
|
265
|
+
return get_valid_access_token(coordinator_url)
|
|
266
|
+
except ValueError as e:
|
|
267
|
+
raise AuthError(f"Failed to get access token: {e}") from e
|
|
268
|
+
|
|
269
|
+
raise AuthError("Not authenticated. Run 'cade login' to authenticate with Arcade Cloud.")
|
|
270
|
+
|
|
271
|
+
@property
|
|
272
|
+
def base_url(self) -> str | None:
|
|
273
|
+
"""Returns the Arcade base URL from environment."""
|
|
274
|
+
return os.environ.get("ARCADE_BASE_URL")
|
|
275
|
+
|
|
276
|
+
@property
|
|
277
|
+
def model_api_key(self) -> str:
|
|
278
|
+
"""Returns the API key for the model service (OpenAI, Anthropic, etc.)."""
|
|
279
|
+
model_service = constants.MODEL_SERVICE
|
|
280
|
+
try:
|
|
281
|
+
if model_service == "openai":
|
|
282
|
+
return os.environ["OPENAI_API_KEY"]
|
|
283
|
+
elif model_service == "anthropic":
|
|
284
|
+
return os.environ["ANTHROPIC_API_KEY"]
|
|
285
|
+
else:
|
|
286
|
+
raise ValueError(f"Invalid model service: {model_service}")
|
|
287
|
+
except KeyError:
|
|
288
|
+
raise AuthError(f"API key is not available for {model_service}.")
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def user_email(self) -> str:
|
|
292
|
+
"""Returns the user's email address."""
|
|
293
|
+
# Legacy format or env override
|
|
294
|
+
if self._legacy_email:
|
|
295
|
+
return self._legacy_email
|
|
296
|
+
|
|
297
|
+
# OAuth format
|
|
298
|
+
if self._arcade_config and self._arcade_config.user:
|
|
299
|
+
return self._arcade_config.user.email or ""
|
|
300
|
+
|
|
301
|
+
return ""
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def org_id(self) -> str | None:
|
|
305
|
+
"""Returns the active organization ID (OAuth only)."""
|
|
306
|
+
if self._arcade_config and self._arcade_config.context:
|
|
307
|
+
return self._arcade_config.context.org_id
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def project_id(self) -> str | None:
|
|
312
|
+
"""Returns the active project ID (OAuth only)."""
|
|
313
|
+
if self._arcade_config and self._arcade_config.context:
|
|
314
|
+
return self._arcade_config.context.project_id
|
|
315
|
+
return None
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def settings(self) -> CadeCoderSettings:
|
|
319
|
+
"""Returns the CadeCoder specific settings."""
|
|
320
|
+
return self._settings
|
|
321
|
+
|
|
322
|
+
@property
|
|
323
|
+
def model_settings(self) -> ModelConfig:
|
|
324
|
+
"""Returns the model configuration."""
|
|
325
|
+
return self._settings.model_settings
|
|
326
|
+
|
|
327
|
+
@property
|
|
328
|
+
def tool_settings(self) -> ToolConfig:
|
|
329
|
+
"""Returns the tool configuration."""
|
|
330
|
+
return self._settings.tool_settings
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def app_dir(self) -> str:
|
|
334
|
+
"""Returns the CadeCoder application directory path."""
|
|
335
|
+
base = os.environ.get("CADECODER_HOME", Path.home())
|
|
336
|
+
return str(Path(base) / ".cadecoder")
|
|
337
|
+
|
|
338
|
+
def ensure_app_dir(self) -> str:
|
|
339
|
+
"""Ensures the application directory exists and returns its path."""
|
|
340
|
+
path = Path(self.app_dir)
|
|
341
|
+
try:
|
|
342
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
343
|
+
return str(path)
|
|
344
|
+
except OSError as e:
|
|
345
|
+
raise ConfigError(f"Could not create application directory {path}: {e}") from e
|
|
346
|
+
|
|
347
|
+
def reload(self) -> None:
|
|
348
|
+
"""Clear caches and reload configuration."""
|
|
349
|
+
_load_cadecoder_settings.cache_clear()
|
|
350
|
+
self._load_configuration()
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
# --- Public Access ---
|
|
354
|
+
|
|
355
|
+
# Lazy initialization - don't create instance on import
|
|
356
|
+
_config: AppConfig | None = None
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def get_config() -> AppConfig:
|
|
360
|
+
"""Get the singleton AppConfig instance.
|
|
361
|
+
|
|
362
|
+
Use this function instead of directly accessing `config` to ensure
|
|
363
|
+
lazy initialization (no AuthError on import).
|
|
364
|
+
"""
|
|
365
|
+
global _config
|
|
366
|
+
if _config is None:
|
|
367
|
+
_config = AppConfig()
|
|
368
|
+
return _config
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
# For backward compatibility with code that imports `config` directly
|
|
372
|
+
# Note: prefer using get_config() for lazy initialization
|
|
373
|
+
class _ConfigProxy:
|
|
374
|
+
"""Proxy that provides lazy access to AppConfig singleton."""
|
|
375
|
+
|
|
376
|
+
def __getattr__(self, name: str):
|
|
377
|
+
return getattr(get_config(), name)
|
|
378
|
+
|
|
379
|
+
|
|
380
|
+
config = _ConfigProxy()
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"""Constants used across the cade application."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import pathlib # Added for PROJECT_ROOT
|
|
7
|
+
from typing import Final
|
|
8
|
+
|
|
9
|
+
# ------------------------------------------------------------------------------
|
|
10
|
+
# General Application & AI Configuration
|
|
11
|
+
# ------------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
DEFAULT_AI_MODEL: Final[str] = "gpt-4.1"
|
|
14
|
+
DEFAULT_MAX_TOKENS: Final[int] = 1_000_000
|
|
15
|
+
MODEL_SERVICE: Final[str] = os.environ.get("CADE_MODEL_SERVICE", "openai").lower()
|
|
16
|
+
DEFAULT_TEMPERATURE: Final[float] = 0.7
|
|
17
|
+
|
|
18
|
+
# Backwards‑compatibility aliases for AI model (scheduled for deprecation)
|
|
19
|
+
DEFAULT_MODEL: Final[str] = DEFAULT_AI_MODEL
|
|
20
|
+
MAX_TOKENS: Final[int] = DEFAULT_MAX_TOKENS
|
|
21
|
+
|
|
22
|
+
# ------------------------------------------------------------------------------
|
|
23
|
+
# Project & File Operations
|
|
24
|
+
# ------------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
PROJECT_ROOT: Final[pathlib.Path] = pathlib.Path(os.getenv("PROJECT_ROOT", ".")).resolve()
|
|
27
|
+
MAX_PREVIEW_BYTES: Final[int] = 65_536 # For truncating file previews by tools
|
|
28
|
+
MAX_LIST_DEPTH: Final[int] = 40 # Max depth for listing files by tools
|
|
29
|
+
MAX_LIST_RESULTS: Final[int] = 10_000 # Max results for listing files by tools
|
|
30
|
+
MODE_OVERWRITE: Final[str] = "overwrite" # File write mode
|
|
31
|
+
MODE_APPEND: Final[str] = "append" # File write mode
|
|
32
|
+
|
|
33
|
+
DEFAULT_INCLUDED_TOOLKITS: Final[list[str]] = ["github", "googlesearch"]
|
|
34
|
+
DEFAULT_INCLUDED_TOOLS: Final[list[str]] = []
|
|
35
|
+
MAX_AGENT_TOOLS: Final[int] = 120 # Max tools to provide to agent (OpenAI limit is 128)
|
|
36
|
+
MAX_CONCURRENT_STEPS: Final[int] = (
|
|
37
|
+
5 # Max steps to execute in parallel (prevents resource exhaustion)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
# ------------------------------------------------------------------------------
|
|
41
|
+
# Orchestrator & Execution Configuration
|
|
42
|
+
# ------------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
# Execution limits
|
|
45
|
+
MAX_EXECUTION_ITERATIONS: Final[int] = 100 # Max iterations for streaming execution
|
|
46
|
+
|
|
47
|
+
# Planning defaults
|
|
48
|
+
DEFAULT_COMPLEXITY_HINT: Final[str] = "moderate" # Default complexity hint for planning
|
|
49
|
+
|
|
50
|
+
# ------------------------------------------------------------------------------
|
|
51
|
+
# Hosts & Paths
|
|
52
|
+
# ------------------------------------------------------------------------------
|
|
53
|
+
|
|
54
|
+
PROD_CLOUD_HOST: Final[str] = "cloud.arcade.dev"
|
|
55
|
+
PROD_ENGINE_HOST: Final[str] = "api.arcade.dev"
|
|
56
|
+
LOCALHOST: Final[str] = "localhost"
|
|
57
|
+
|
|
58
|
+
# Port constants
|
|
59
|
+
DEFAULT_DEV_PORT: Final[int] = 9099 # Default port for localhost development
|
|
60
|
+
DEFAULT_CALLBACK_PORT: Final[int] = 9905 # Port for OAuth callback server
|
|
61
|
+
DEFAULT_LOGIN_PORT: Final[int] = 8000 # Default port for login endpoint
|
|
62
|
+
|
|
63
|
+
# Output truncation limits (line length, not total characters)
|
|
64
|
+
MAX_DEBUG_OUTPUT_LENGTH: Final[int] = 500 # Max chars per line for debug/log output
|
|
65
|
+
MAX_DISPLAY_LINE_LENGTH: Final[int] = 100 # Content width (10-char margins on 120-char terminal)
|
|
66
|
+
MAX_ERROR_LINE_LENGTH: Final[int] = 100 # Max chars per line for error messages
|
|
67
|
+
|
|
68
|
+
# Tool display configuration
|
|
69
|
+
DEFAULT_PANEL_WIDTH: Final[int] = 100 # Width for tool result panels (matches content width)
|
|
70
|
+
DEFAULT_MAX_LINES: Final[int] = 10 # Default max lines for tool result display
|
|
71
|
+
|
|
72
|
+
# Output formatting
|
|
73
|
+
OUTPUT_MAX_WIDTH: Final[int] = 100 # Max content width (120 terminal - 10 margin each side)
|
|
74
|
+
OUTPUT_MARGIN: Final[int] = 10 # Margin on each side of terminal
|
|
75
|
+
|
|
76
|
+
# UI Style configuration
|
|
77
|
+
UI_STYLE: Final[str] = os.environ.get("CADE_UI_STYLE", "minimal") # Options: "minimal", "panels"
|
|
78
|
+
|
|
79
|
+
ARCADE_CONFIG_PATH: Final[str] = os.path.join(
|
|
80
|
+
os.path.expanduser(os.getenv("ARCADE_WORK_DIR", "~")), ".arcade"
|
|
81
|
+
)
|
|
82
|
+
CREDENTIALS_FILE_PATH: Final[str] = os.path.join(ARCADE_CONFIG_PATH, "credentials.yaml")
|
|
83
|
+
|
|
84
|
+
# ------------------------------------------------------------------------------
|
|
85
|
+
# Template Paths
|
|
86
|
+
# ------------------------------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
TEMPLATES_DIR: Final[pathlib.Path] = pathlib.Path(__file__).parent.parent / "templates"
|
|
89
|
+
LOGIN_SUCCESS_TEMPLATE: Final[str] = "login_success.html"
|
|
90
|
+
LOGIN_FAILED_TEMPLATE: Final[str] = "login_failed.html"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def get_template_path(template_name: str) -> pathlib.Path:
|
|
94
|
+
"""Get the full path to a template file."""
|
|
95
|
+
return TEMPLATES_DIR / template_name
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def load_template(template_name: str) -> bytes:
|
|
99
|
+
"""Load a template file and return its contents as bytes."""
|
|
100
|
+
template_path = get_template_path(template_name)
|
|
101
|
+
return template_path.read_bytes()
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ------------------------------------------------------------------------------
|
|
105
|
+
# Code Analysis & File Patterns
|
|
106
|
+
# ------------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
DEFAULT_IGNORE_PATTERNS: Final[list[str]] = [
|
|
109
|
+
# Build & distribution
|
|
110
|
+
"node_modules",
|
|
111
|
+
"dist",
|
|
112
|
+
"build",
|
|
113
|
+
"*.egg-info",
|
|
114
|
+
# Version control
|
|
115
|
+
".git",
|
|
116
|
+
# Editors & IDEs
|
|
117
|
+
".vscode",
|
|
118
|
+
".idea",
|
|
119
|
+
".cursor",
|
|
120
|
+
".claude",
|
|
121
|
+
# OS files
|
|
122
|
+
".DS_Store",
|
|
123
|
+
"Thumbs.db",
|
|
124
|
+
# Testing & coverage
|
|
125
|
+
"coverage",
|
|
126
|
+
".pytest_cache",
|
|
127
|
+
".ruff_cache",
|
|
128
|
+
".mypy_cache",
|
|
129
|
+
"htmlcov",
|
|
130
|
+
".coverage",
|
|
131
|
+
# Virtual environments
|
|
132
|
+
".venv",
|
|
133
|
+
"venv",
|
|
134
|
+
"env",
|
|
135
|
+
# Environment & secrets
|
|
136
|
+
".env",
|
|
137
|
+
".env.local",
|
|
138
|
+
".env.*.local",
|
|
139
|
+
# Compiled & bundled files
|
|
140
|
+
"*.min.js",
|
|
141
|
+
"*.bundle.js",
|
|
142
|
+
"*.map",
|
|
143
|
+
"*.pyc",
|
|
144
|
+
"*.pyo",
|
|
145
|
+
"*.pyd",
|
|
146
|
+
"*.so",
|
|
147
|
+
"*.dylib",
|
|
148
|
+
"__pycache__",
|
|
149
|
+
]
|
|
150
|
+
|
|
151
|
+
EXTENSION_TO_LANGUAGE: Final[dict[str, str]] = {
|
|
152
|
+
"ts": "TypeScript",
|
|
153
|
+
"tsx": "TypeScript (React)",
|
|
154
|
+
"js": "JavaScript",
|
|
155
|
+
"jsx": "JavaScript (React)",
|
|
156
|
+
"py": "Python",
|
|
157
|
+
"java": "Java",
|
|
158
|
+
"c": "C",
|
|
159
|
+
"cpp": "C++",
|
|
160
|
+
"cs": "C#",
|
|
161
|
+
"go": "Go",
|
|
162
|
+
"rs": "Rust",
|
|
163
|
+
"php": "PHP",
|
|
164
|
+
"rb": "Ruby",
|
|
165
|
+
"swift": "Swift",
|
|
166
|
+
"kt": "Kotlin",
|
|
167
|
+
"scala": "Scala",
|
|
168
|
+
"html": "HTML",
|
|
169
|
+
"css": "CSS",
|
|
170
|
+
"scss": "SCSS",
|
|
171
|
+
"less": "Less",
|
|
172
|
+
"json": "JSON",
|
|
173
|
+
"md": "Markdown",
|
|
174
|
+
"yml": "YAML",
|
|
175
|
+
"yaml": "YAML",
|
|
176
|
+
"xml": "XML",
|
|
177
|
+
"sql": "SQL",
|
|
178
|
+
"sh": "Shell",
|
|
179
|
+
"bat": "Batch",
|
|
180
|
+
"ps1": "PowerShell",
|
|
181
|
+
"dockerfile": "Dockerfile",
|
|
182
|
+
"tf": "Terraform",
|
|
183
|
+
"hcl": "HCL",
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
CODE_EXTENSIONS: Final[set[str]] = {
|
|
187
|
+
"js",
|
|
188
|
+
"jsx",
|
|
189
|
+
"ts",
|
|
190
|
+
"tsx",
|
|
191
|
+
"py",
|
|
192
|
+
"java",
|
|
193
|
+
"c",
|
|
194
|
+
"cpp",
|
|
195
|
+
"cs",
|
|
196
|
+
"go",
|
|
197
|
+
"rs",
|
|
198
|
+
"php",
|
|
199
|
+
"rb",
|
|
200
|
+
"swift",
|
|
201
|
+
"kt",
|
|
202
|
+
"scala",
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
PYTHON_STDLIB_MODULES: Final[set[str]] = {
|
|
206
|
+
"os",
|
|
207
|
+
"sys",
|
|
208
|
+
"re",
|
|
209
|
+
"math",
|
|
210
|
+
"datetime",
|
|
211
|
+
"time",
|
|
212
|
+
"random",
|
|
213
|
+
"json",
|
|
214
|
+
"csv",
|
|
215
|
+
"collections",
|
|
216
|
+
"itertools",
|
|
217
|
+
"functools",
|
|
218
|
+
"pathlib",
|
|
219
|
+
"shutil",
|
|
220
|
+
"glob",
|
|
221
|
+
"pickle",
|
|
222
|
+
"urllib",
|
|
223
|
+
"http",
|
|
224
|
+
"logging",
|
|
225
|
+
"argparse",
|
|
226
|
+
"unittest",
|
|
227
|
+
"subprocess",
|
|
228
|
+
"threading",
|
|
229
|
+
"multiprocessing",
|
|
230
|
+
"typing",
|
|
231
|
+
"enum",
|
|
232
|
+
"io",
|
|
233
|
+
"tempfile",
|
|
234
|
+
"contextlib",
|
|
235
|
+
"decimal",
|
|
236
|
+
"fractions",
|
|
237
|
+
"statistics",
|
|
238
|
+
"asyncio",
|
|
239
|
+
"concurrent",
|
|
240
|
+
"socket",
|
|
241
|
+
"ssl",
|
|
242
|
+
"select",
|
|
243
|
+
"signal",
|
|
244
|
+
"struct",
|
|
245
|
+
"zlib",
|
|
246
|
+
"gzip",
|
|
247
|
+
"bz2",
|
|
248
|
+
"lzma",
|
|
249
|
+
"tarfile",
|
|
250
|
+
"zipfile",
|
|
251
|
+
"calendar",
|
|
252
|
+
"copy",
|
|
253
|
+
"pprint",
|
|
254
|
+
"base64",
|
|
255
|
+
"hashlib",
|
|
256
|
+
"hmac",
|
|
257
|
+
"secrets",
|
|
258
|
+
"uuid",
|
|
259
|
+
"xml",
|
|
260
|
+
"gettext",
|
|
261
|
+
"locale",
|
|
262
|
+
"platform",
|
|
263
|
+
"importlib",
|
|
264
|
+
"inspect",
|
|
265
|
+
"pdb",
|
|
266
|
+
"profile",
|
|
267
|
+
"traceback",
|
|
268
|
+
"warnings",
|
|
269
|
+
"weakref",
|
|
270
|
+
"abc",
|
|
271
|
+
"numbers",
|
|
272
|
+
"operator",
|
|
273
|
+
"heapq",
|
|
274
|
+
"bisect",
|
|
275
|
+
"array",
|
|
276
|
+
"queue",
|
|
277
|
+
"dataclasses",
|
|
278
|
+
"graphlib",
|
|
279
|
+
"sched",
|
|
280
|
+
"zoneinfo",
|
|
281
|
+
}
|