supervaizer 0.10.5__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.
Files changed (76) hide show
  1. supervaizer/__init__.py +97 -0
  2. supervaizer/__version__.py +10 -0
  3. supervaizer/account.py +308 -0
  4. supervaizer/account_service.py +93 -0
  5. supervaizer/admin/routes.py +1293 -0
  6. supervaizer/admin/static/js/job-start-form.js +373 -0
  7. supervaizer/admin/templates/agent_detail.html +145 -0
  8. supervaizer/admin/templates/agents.html +249 -0
  9. supervaizer/admin/templates/agents_grid.html +82 -0
  10. supervaizer/admin/templates/base.html +233 -0
  11. supervaizer/admin/templates/case_detail.html +230 -0
  12. supervaizer/admin/templates/cases_list.html +182 -0
  13. supervaizer/admin/templates/cases_table.html +134 -0
  14. supervaizer/admin/templates/console.html +389 -0
  15. supervaizer/admin/templates/dashboard.html +153 -0
  16. supervaizer/admin/templates/job_detail.html +192 -0
  17. supervaizer/admin/templates/job_start_test.html +109 -0
  18. supervaizer/admin/templates/jobs_list.html +180 -0
  19. supervaizer/admin/templates/jobs_table.html +122 -0
  20. supervaizer/admin/templates/navigation.html +163 -0
  21. supervaizer/admin/templates/recent_activity.html +81 -0
  22. supervaizer/admin/templates/server.html +105 -0
  23. supervaizer/admin/templates/server_status_cards.html +121 -0
  24. supervaizer/admin/templates/supervaize_instructions.html +212 -0
  25. supervaizer/agent.py +956 -0
  26. supervaizer/case.py +432 -0
  27. supervaizer/cli.py +395 -0
  28. supervaizer/common.py +324 -0
  29. supervaizer/deploy/__init__.py +16 -0
  30. supervaizer/deploy/cli.py +305 -0
  31. supervaizer/deploy/commands/__init__.py +9 -0
  32. supervaizer/deploy/commands/clean.py +294 -0
  33. supervaizer/deploy/commands/down.py +119 -0
  34. supervaizer/deploy/commands/local.py +460 -0
  35. supervaizer/deploy/commands/plan.py +167 -0
  36. supervaizer/deploy/commands/status.py +169 -0
  37. supervaizer/deploy/commands/up.py +281 -0
  38. supervaizer/deploy/docker.py +377 -0
  39. supervaizer/deploy/driver_factory.py +42 -0
  40. supervaizer/deploy/drivers/__init__.py +39 -0
  41. supervaizer/deploy/drivers/aws_app_runner.py +607 -0
  42. supervaizer/deploy/drivers/base.py +196 -0
  43. supervaizer/deploy/drivers/cloud_run.py +570 -0
  44. supervaizer/deploy/drivers/do_app_platform.py +504 -0
  45. supervaizer/deploy/health.py +404 -0
  46. supervaizer/deploy/state.py +210 -0
  47. supervaizer/deploy/templates/Dockerfile.template +44 -0
  48. supervaizer/deploy/templates/debug_env.py +69 -0
  49. supervaizer/deploy/templates/docker-compose.yml.template +37 -0
  50. supervaizer/deploy/templates/dockerignore.template +66 -0
  51. supervaizer/deploy/templates/entrypoint.sh +20 -0
  52. supervaizer/deploy/utils.py +52 -0
  53. supervaizer/event.py +181 -0
  54. supervaizer/examples/controller_template.py +196 -0
  55. supervaizer/instructions.py +145 -0
  56. supervaizer/job.py +392 -0
  57. supervaizer/job_service.py +156 -0
  58. supervaizer/lifecycle.py +417 -0
  59. supervaizer/parameter.py +233 -0
  60. supervaizer/protocol/__init__.py +11 -0
  61. supervaizer/protocol/a2a/__init__.py +21 -0
  62. supervaizer/protocol/a2a/model.py +227 -0
  63. supervaizer/protocol/a2a/routes.py +99 -0
  64. supervaizer/py.typed +1 -0
  65. supervaizer/routes.py +917 -0
  66. supervaizer/server.py +553 -0
  67. supervaizer/server_utils.py +54 -0
  68. supervaizer/storage.py +462 -0
  69. supervaizer/telemetry.py +81 -0
  70. supervaizer/utils/__init__.py +16 -0
  71. supervaizer/utils/version_check.py +56 -0
  72. supervaizer-0.10.5.dist-info/METADATA +317 -0
  73. supervaizer-0.10.5.dist-info/RECORD +76 -0
  74. supervaizer-0.10.5.dist-info/WHEEL +4 -0
  75. supervaizer-0.10.5.dist-info/entry_points.txt +2 -0
  76. supervaizer-0.10.5.dist-info/licenses/LICENSE.md +346 -0
supervaizer/cli.py ADDED
@@ -0,0 +1,395 @@
1
+ # Copyright (c) 2024-2025 Alain Prasquier - Supervaize.com. All rights reserved.
2
+ #
3
+ # This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
4
+ # If a copy of the MPL was not distributed with this file, you can obtain one at
5
+ # https://mozilla.org/MPL/2.0/.
6
+
7
+ import asyncio
8
+ import os
9
+ import shutil
10
+ import signal
11
+ import subprocess
12
+ import sys
13
+ from typing import Any
14
+ from pathlib import Path
15
+ from typing import Optional
16
+ from supervaizer.deploy.cli import deploy_app
17
+
18
+ import typer
19
+ from rich.console import Console
20
+ from rich.prompt import Confirm
21
+
22
+ from supervaizer.__version__ import VERSION
23
+ from supervaizer.utils.version_check import check_is_latest_version
24
+
25
+ console = Console()
26
+
27
+ # Cache version status to avoid multiple checks
28
+ _version_status: tuple[bool, str | None] | None = None
29
+
30
+
31
+ def _check_version() -> tuple[bool, str | None]:
32
+ """Check version status, caching the result."""
33
+ global _version_status
34
+ if _version_status is None:
35
+ try:
36
+ _version_status = asyncio.run(check_is_latest_version())
37
+ except Exception:
38
+ # On error, assume we're on latest to avoid false positives
39
+ _version_status = (True, None)
40
+ return _version_status
41
+
42
+
43
+ def _display_version_info() -> None:
44
+ """Display version information."""
45
+ is_latest, latest_version = _check_version()
46
+ console.print(f"Supervaizer v{VERSION}")
47
+ if latest_version:
48
+ if is_latest:
49
+ console.print(f"[green]✓[/] Up to date (latest: v{latest_version})")
50
+ else:
51
+ console.print(f"[yellow]⚠[/] Latest available: v{latest_version}")
52
+ console.print("Update with: [bold]pip install --upgrade supervaizer[/]")
53
+ else:
54
+ console.print("(Unable to check for latest version)")
55
+
56
+
57
+ def _display_update_warning() -> None:
58
+ """Display update warning for commands."""
59
+ is_latest, latest_version = _check_version()
60
+ if latest_version and not is_latest:
61
+ console.print(
62
+ f"\n[bold yellow]⚠ Warning:[/] You are running Supervaizer v{VERSION}, "
63
+ f"but v{latest_version} is available.\n"
64
+ f"Update with: [bold]pip install --upgrade supervaizer[/]\n",
65
+ style="yellow",
66
+ )
67
+
68
+
69
+ app = typer.Typer(
70
+ help=f"Supervaizer Controller CLI v{VERSION} - Documentation @ https://doc.supervaize.com/docs/category/supervaizer-controller"
71
+ )
72
+
73
+
74
+ @app.callback(invoke_without_command=True)
75
+ def main_callback(
76
+ ctx: typer.Context,
77
+ version: bool = typer.Option(
78
+ False, "--version", "-v", help="Show version information and exit"
79
+ ),
80
+ ) -> None:
81
+ """CLI callback that runs before any command."""
82
+ # Handle --version option
83
+ if version:
84
+ _display_version_info()
85
+ raise typer.Exit()
86
+
87
+ # Show version status in help or as warning for commands
88
+ if ctx.invoked_subcommand is None:
89
+ # For help display, show version status
90
+ _display_version_info()
91
+ else:
92
+ # For actual commands, show warning if not latest
93
+ _display_update_warning()
94
+
95
+
96
+ # Add deploy subcommand
97
+ app.add_typer(
98
+ deploy_app,
99
+ name="deploy",
100
+ help="Deploy Supervaizer agents to cloud platforms (requires deploy extras: pip install supervaizer[deploy])",
101
+ )
102
+
103
+ # Create scaffold subcommand group
104
+ scaffold_app = typer.Typer(help="Scaffold commands for creating project files")
105
+ app.add_typer(scaffold_app, name="scaffold", invoke_without_command=True)
106
+
107
+
108
+ @app.command()
109
+ def start(
110
+ public_url: Optional[str] = typer.Option(
111
+ os.environ.get("SUPERVAIZER_PUBLIC_URL") or None,
112
+ help="Public URL to use for inbound connections",
113
+ ),
114
+ host: str = typer.Option(
115
+ os.environ.get("SUPERVAIZER_HOST", "0.0.0.0"), help="Host to bind the server to"
116
+ ),
117
+ port: int = typer.Option(
118
+ int(os.environ.get("SUPERVAIZER_PORT") or "8000"),
119
+ help="Port to bind the server to",
120
+ ),
121
+ log_level: str = typer.Option(
122
+ os.environ.get("SUPERVAIZER_LOG_LEVEL", "INFO"),
123
+ help="Log level (DEBUG, INFO, WARNING, ERROR)",
124
+ ),
125
+ debug: bool = typer.Option(
126
+ (os.environ.get("SUPERVAIZER_DEBUG") or "False").lower() == "true",
127
+ help="Enable debug mode",
128
+ ),
129
+ reload: bool = typer.Option(
130
+ (os.environ.get("SUPERVAIZER_RELOAD") or "False").lower() == "true",
131
+ help="Enable auto-reload",
132
+ ),
133
+ environment: str = typer.Option(
134
+ os.environ.get("SUPERVAIZER_ENVIRONMENT", "dev"), help="Environment name"
135
+ ),
136
+ persist: bool = typer.Option(
137
+ (os.environ.get("SUPERVAIZER_PERSISTENCE") or "false").lower()
138
+ in ("true", "1", "yes"),
139
+ "--persist/--no-persist",
140
+ help="Persist data to file (default: off; set for self-hosted, off for Vercel/serverless)",
141
+ ),
142
+ script_path: Optional[str] = typer.Argument(
143
+ None,
144
+ help="Path to the supervaizer_control.py script",
145
+ ),
146
+ ) -> None:
147
+ """Start the Supervaizer Controller server."""
148
+ if script_path is None:
149
+ # Try to get from environment variable first, then default
150
+ script_path = (
151
+ os.environ.get("SUPERVAIZER_SCRIPT_PATH") or "supervaizer_control.py"
152
+ )
153
+
154
+ if not os.path.exists(script_path):
155
+ console.print(f"[bold red]Error:[/] {script_path} not found")
156
+ console.print("Run [bold]supervaizer scaffold[/] to create a default script")
157
+ sys.exit(1)
158
+
159
+ # Set environment variables for the server configuration
160
+ os.environ["SUPERVAIZER_HOST"] = host
161
+ os.environ["SUPERVAIZER_PORT"] = str(port)
162
+ os.environ["SUPERVAIZER_ENVIRONMENT"] = environment
163
+ os.environ["SUPERVAIZER_PERSISTENCE"] = str(persist).lower()
164
+ os.environ["SUPERVAIZER_LOG_LEVEL"] = log_level
165
+ os.environ["SUPERVAIZER_DEBUG"] = str(debug)
166
+ os.environ["SUPERVAIZER_RELOAD"] = str(reload)
167
+ if public_url is not None:
168
+ os.environ["SUPERVAIZER_PUBLIC_URL"] = public_url
169
+
170
+ console.print(f"[bold green]Starting Supervaizer Controller v{VERSION}[/]")
171
+ console.print(f"Loading configuration from [bold]{script_path}[/]")
172
+
173
+ # Execute the script in a new Python process with proper signal handling
174
+
175
+ def signal_handler(signum: int, frame: Any) -> None:
176
+ # Send the signal to the subprocess
177
+ if "process" in globals():
178
+ globals()["process"].terminate()
179
+ sys.exit(0)
180
+
181
+ signal.signal(signal.SIGINT, signal_handler)
182
+ signal.signal(signal.SIGTERM, signal_handler)
183
+
184
+ process = subprocess.Popen([sys.executable, script_path])
185
+ globals()["process"] = process
186
+ process.wait()
187
+
188
+
189
+ def _create_instructions_file(
190
+ output_dir: Path, force: bool = False, silent: bool = False
191
+ ) -> Path:
192
+ """Create supervaize_instructions.html file in the given directory.
193
+
194
+ Args:
195
+ output_dir: Directory where to create the instructions file
196
+ force: If True, overwrite existing file
197
+ silent: If True, don't show warnings if file already exists (just skip)
198
+ """
199
+ instructions_path = output_dir / "supervaize_instructions.html"
200
+
201
+ # Check if file already exists
202
+ if instructions_path.exists() and not force:
203
+ if not silent:
204
+ console.print(
205
+ f"[bold yellow]Warning:[/] {instructions_path} already exists"
206
+ )
207
+ console.print(
208
+ "Use [bold]--force[/] to overwrite it, or run [bold]supervaizer refresh-instructions[/]"
209
+ )
210
+ return instructions_path
211
+
212
+ # Get the path to the admin templates directory
213
+ admin_templates_dir = Path(__file__).parent / "admin" / "templates"
214
+ template_file = admin_templates_dir / "supervaize_instructions.html"
215
+
216
+ if not template_file.exists():
217
+ console.print("[bold red]Error:[/] Template file not found")
218
+ sys.exit(1)
219
+
220
+ # Copy the template file
221
+ shutil.copy(template_file, instructions_path)
222
+ return instructions_path
223
+
224
+
225
+ @scaffold_app.callback(invoke_without_command=True)
226
+ def scaffold(
227
+ ctx: typer.Context,
228
+ output_path: str = typer.Option(
229
+ os.environ.get("SUPERVAIZER_OUTPUT_PATH", "supervaizer_control.py"),
230
+ help="Path to save the script",
231
+ ),
232
+ force: bool = typer.Option(
233
+ (os.environ.get("SUPERVAIZER_FORCE_INSTALL") or "False").lower() == "true",
234
+ help="Overwrite existing file",
235
+ ),
236
+ ) -> None:
237
+ """Create a draft supervaizer_control.py script and supervaize_instructions.html."""
238
+ # Only run if no subcommand was invoked
239
+ if ctx.invoked_subcommand is not None:
240
+ return
241
+ # Check if file already exists
242
+ if os.path.exists(output_path) and not force:
243
+ console.print(f"[bold red]Error:[/] {output_path} already exists")
244
+ console.print("Use [bold]--force[/] to overwrite it")
245
+ sys.exit(1)
246
+
247
+ # Get the path to the examples directory
248
+ examples_dir = Path(__file__).parent / "examples"
249
+ example_file = examples_dir / "controller_template.py"
250
+
251
+ if not example_file.exists():
252
+ console.print("[bold red]Error:[/] Example file not found")
253
+ sys.exit(1)
254
+
255
+ # Copy the example file to the output path
256
+ shutil.copy(example_file, output_path)
257
+ console.print(
258
+ f"[bold green]Success:[/] Created an example file at [bold blue]{output_path}[/]"
259
+ )
260
+
261
+ # Create instructions file in the same directory (silently if it already exists)
262
+ output_dir = Path(output_path).parent
263
+ instructions_path = output_dir / "supervaize_instructions.html"
264
+ instructions_existed = instructions_path.exists()
265
+ _create_instructions_file(output_dir, force=force, silent=True)
266
+ # Only show success message if we actually created the file (didn't exist before or force was used)
267
+ if not instructions_existed or force:
268
+ console.print(
269
+ f"[bold green]Success:[/] Created instructions template at [bold blue]{instructions_path}[/]"
270
+ )
271
+
272
+ console.print(
273
+ "1. Copy this file to [bold]supervaizer_control.py[/] and edit it to configure your agent(s)"
274
+ )
275
+ console.print(
276
+ "2. Customize [bold]supervaize_instructions.html[/] to match your agent's documentation"
277
+ )
278
+ console.print(
279
+ "3. (Optional) Get your API from [bold]supervaizer.com and setup your environment variables"
280
+ )
281
+ console.print(
282
+ "4. Run [bold]supervaizer start[/] to start the supervaizer controller"
283
+ )
284
+ console.print("5. Open [bold]http://localhost:8000/docs[/] to explore the API")
285
+ sys.exit(0)
286
+
287
+
288
+ @scaffold_app.command(name="instructions")
289
+ def scaffold_instructions(
290
+ control_file: Optional[str] = typer.Option(
291
+ None,
292
+ help="Path to supervaizer_control.py (default: auto-detect)",
293
+ ),
294
+ output_path: Optional[str] = typer.Option(
295
+ None,
296
+ help="Path to save supervaize_instructions.html (default: same directory as control file)",
297
+ ),
298
+ force: bool = typer.Option(
299
+ False,
300
+ help="Overwrite existing file",
301
+ ),
302
+ ) -> None:
303
+ """Create supervaize_instructions.html file."""
304
+ # Determine control file path
305
+ if control_file is None:
306
+ control_file = (
307
+ os.environ.get("SUPERVAIZER_SCRIPT_PATH") or "supervaizer_control.py"
308
+ )
309
+
310
+ control_path = Path(control_file)
311
+
312
+ # Determine output directory
313
+ if output_path is None:
314
+ output_dir = control_path.parent
315
+ instructions_path = output_dir / "supervaize_instructions.html"
316
+ else:
317
+ instructions_path = Path(output_path)
318
+ output_dir = instructions_path.parent
319
+
320
+ # Check if control file exists (informational)
321
+ if not control_path.exists():
322
+ console.print(f"[bold yellow]Warning:[/] Control file {control_file} not found")
323
+ console.print("Creating instructions file anyway...")
324
+
325
+ # Create instructions file
326
+ _create_instructions_file(output_dir, force=force)
327
+ console.print(
328
+ f"[bold green]Success:[/] Created instructions template at [bold blue]{instructions_path}[/]"
329
+ )
330
+ console.print(
331
+ "Customize this file to match your agent's documentation and instructions."
332
+ )
333
+
334
+
335
+ @scaffold_app.command(name="refresh-instructions")
336
+ def refresh_instructions(
337
+ control_file: Optional[str] = typer.Option(
338
+ None,
339
+ help="Path to supervaizer_control.py (default: auto-detect)",
340
+ ),
341
+ output_path: Optional[str] = typer.Option(
342
+ None,
343
+ help="Path to supervaize_instructions.html (default: same directory as control file)",
344
+ ),
345
+ force: bool = typer.Option(
346
+ False,
347
+ help="Skip confirmation prompt",
348
+ ),
349
+ ) -> None:
350
+ """Refresh/update supervaize_instructions.html file."""
351
+ # Determine control file path
352
+ if control_file is None:
353
+ control_file = (
354
+ os.environ.get("SUPERVAIZER_SCRIPT_PATH") or "supervaizer_control.py"
355
+ )
356
+
357
+ control_path = Path(control_file)
358
+
359
+ # Determine output path
360
+ if output_path is None:
361
+ output_dir = control_path.parent
362
+ instructions_path = output_dir / "supervaize_instructions.html"
363
+ else:
364
+ instructions_path = Path(output_path)
365
+
366
+ # Check if instructions file exists
367
+ if instructions_path.exists():
368
+ if not force:
369
+ console.print(
370
+ f"[bold yellow]Warning:[/] {instructions_path} already exists"
371
+ )
372
+ if not Confirm.ask(
373
+ "Delete existing file and create a fresh template?",
374
+ default=False,
375
+ ):
376
+ console.print("[bold]Cancelled.[/]")
377
+ sys.exit(0)
378
+
379
+ # Delete existing file
380
+ instructions_path.unlink()
381
+ console.print(f"[bold]Deleted[/] existing {instructions_path}")
382
+
383
+ # Create new instructions file
384
+ output_dir = instructions_path.parent
385
+ _create_instructions_file(output_dir, force=True)
386
+ console.print(
387
+ f"[bold green]Success:[/] Created fresh instructions template at [bold blue]{instructions_path}[/]"
388
+ )
389
+ console.print(
390
+ "Customize this file to match your agent's documentation and instructions."
391
+ )
392
+
393
+
394
+ if __name__ == "__main__":
395
+ app()