@utopia-ai/cli 0.1.0

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 (49) hide show
  1. package/.claude/settings.json +1 -0
  2. package/.claude/settings.local.json +38 -0
  3. package/bin/utopia.js +20 -0
  4. package/package.json +46 -0
  5. package/python/README.md +34 -0
  6. package/python/instrumenter/instrument.py +1148 -0
  7. package/python/pyproject.toml +32 -0
  8. package/python/setup.py +27 -0
  9. package/python/utopia_runtime/__init__.py +30 -0
  10. package/python/utopia_runtime/__pycache__/__init__.cpython-313.pyc +0 -0
  11. package/python/utopia_runtime/__pycache__/client.cpython-313.pyc +0 -0
  12. package/python/utopia_runtime/__pycache__/probe.cpython-313.pyc +0 -0
  13. package/python/utopia_runtime/client.py +31 -0
  14. package/python/utopia_runtime/probe.py +446 -0
  15. package/python/utopia_runtime.egg-info/PKG-INFO +59 -0
  16. package/python/utopia_runtime.egg-info/SOURCES.txt +10 -0
  17. package/python/utopia_runtime.egg-info/dependency_links.txt +1 -0
  18. package/python/utopia_runtime.egg-info/top_level.txt +1 -0
  19. package/scripts/publish-npm.sh +14 -0
  20. package/scripts/publish-pypi.sh +17 -0
  21. package/src/cli/commands/codex.ts +193 -0
  22. package/src/cli/commands/context.ts +188 -0
  23. package/src/cli/commands/destruct.ts +237 -0
  24. package/src/cli/commands/easter-eggs.ts +203 -0
  25. package/src/cli/commands/init.ts +505 -0
  26. package/src/cli/commands/instrument.ts +962 -0
  27. package/src/cli/commands/mcp.ts +16 -0
  28. package/src/cli/commands/serve.ts +194 -0
  29. package/src/cli/commands/status.ts +304 -0
  30. package/src/cli/commands/validate.ts +328 -0
  31. package/src/cli/index.ts +37 -0
  32. package/src/cli/utils/config.ts +54 -0
  33. package/src/graph/index.ts +687 -0
  34. package/src/instrumenter/javascript.ts +1798 -0
  35. package/src/mcp/index.ts +886 -0
  36. package/src/runtime/js/index.ts +518 -0
  37. package/src/runtime/js/package-lock.json +30 -0
  38. package/src/runtime/js/package.json +30 -0
  39. package/src/runtime/js/tsconfig.json +16 -0
  40. package/src/server/db/index.ts +26 -0
  41. package/src/server/db/schema.ts +45 -0
  42. package/src/server/index.ts +79 -0
  43. package/src/server/middleware/auth.ts +74 -0
  44. package/src/server/routes/admin.ts +36 -0
  45. package/src/server/routes/graph.ts +358 -0
  46. package/src/server/routes/probes.ts +286 -0
  47. package/src/types.ts +147 -0
  48. package/src/utopia-mode/index.ts +206 -0
  49. package/tsconfig.json +19 -0
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "utopia-runtime"
7
+ version = "0.1.0"
8
+ description = "Zero-impact production probe runtime for Utopia — gives AI coding agents real-time visibility into how code runs"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.9"
12
+ keywords = ["utopia", "probes", "observability", "production-context", "ai-agents"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Topic :: Software Development :: Libraries",
24
+ "Topic :: System :: Monitoring",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://github.com/paulvann/utopia"
29
+ Repository = "https://github.com/paulvann/utopia"
30
+
31
+ [tool.setuptools.packages.find]
32
+ include = ["utopia_runtime*"]
@@ -0,0 +1,27 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name='utopia-runtime',
5
+ version='0.1.0',
6
+ description='Lightweight probe runtime for Utopia - zero-impact production observability',
7
+ long_description='Utopia Runtime provides non-blocking, zero-dependency probe reporting '
8
+ 'for Python applications. It collects errors, database calls, API calls, '
9
+ 'infrastructure info, and function traces, shipping them asynchronously '
10
+ 'to the Utopia data service via a background daemon thread.',
11
+ author='Utopia',
12
+ license='MIT',
13
+ packages=find_packages(),
14
+ python_requires='>=3.9',
15
+ classifiers=[
16
+ 'Development Status :: 3 - Alpha',
17
+ 'Intended Audience :: Developers',
18
+ 'License :: OSI Approved :: MIT License',
19
+ 'Programming Language :: Python :: 3',
20
+ 'Programming Language :: Python :: 3.9',
21
+ 'Programming Language :: Python :: 3.10',
22
+ 'Programming Language :: Python :: 3.11',
23
+ 'Programming Language :: Python :: 3.12',
24
+ 'Topic :: Software Development :: Libraries',
25
+ 'Topic :: System :: Monitoring',
26
+ ],
27
+ )
@@ -0,0 +1,30 @@
1
+ """
2
+ Utopia Runtime - Lightweight probe runtime for production observability.
3
+
4
+ This package provides zero-impact probe reporting functions that collect
5
+ observability data (errors, database calls, API calls, infrastructure info,
6
+ function traces) and send them asynchronously to the Utopia data service.
7
+
8
+ Only Python stdlib is used -- no external dependencies required.
9
+ """
10
+
11
+ from .probe import (
12
+ init,
13
+ report_error,
14
+ report_db,
15
+ report_api,
16
+ report_infra,
17
+ report_function,
18
+ report_llm_context,
19
+ )
20
+
21
+ __version__ = '0.1.0'
22
+ __all__ = [
23
+ 'init',
24
+ 'report_error',
25
+ 'report_db',
26
+ 'report_api',
27
+ 'report_infra',
28
+ 'report_function',
29
+ 'report_llm_context',
30
+ ]
@@ -0,0 +1,31 @@
1
+ """HTTP client for sending probe data to the Utopia data service.
2
+
3
+ Uses only ``urllib`` from the standard library -- no external dependencies.
4
+ All public functions are designed to never raise; failures are silently
5
+ swallowed so that instrumented applications are never impacted by probe
6
+ reporting issues.
7
+ """
8
+
9
+ import json
10
+ import urllib.request
11
+ import urllib.error
12
+ from typing import Any
13
+
14
+
15
+ def send_probes(endpoint: str, probes: list[dict[str, Any]]) -> bool:
16
+ """Send a batch of probes to the data service. Never raises."""
17
+ try:
18
+ url = f"{endpoint.rstrip('/')}/api/v1/probes"
19
+ data = json.dumps(probes).encode("utf-8")
20
+ req = urllib.request.Request(
21
+ url,
22
+ data=data,
23
+ headers={
24
+ "Content-Type": "application/json",
25
+ },
26
+ method="POST",
27
+ )
28
+ with urllib.request.urlopen(req, timeout=2) as resp:
29
+ return 200 <= resp.status < 300
30
+ except Exception:
31
+ return False
@@ -0,0 +1,446 @@
1
+ """
2
+ Utopia probe engine -- the core reporting module.
3
+
4
+ All ``report_*`` functions are designed to be completely non-blocking and
5
+ safe: they never raise exceptions, never slow down the caller, and silently
6
+ discard data when the circuit breaker is open or the queue is full.
7
+
8
+ Architecture:
9
+ * A module-level ``queue.Queue`` buffers probe dicts.
10
+ * A single daemon thread drains the queue every 5 s (or sooner when the
11
+ batch reaches 50 items) and ships them via ``client.send_probes()``.
12
+ * A simple circuit breaker (3 consecutive failures -> open for 60 s)
13
+ avoids hammering a dead endpoint.
14
+ """
15
+
16
+ import atexit
17
+ import os
18
+ import queue
19
+ import socket
20
+ import threading
21
+ import time
22
+ import uuid
23
+ from datetime import datetime, timezone
24
+ from typing import Any, Optional
25
+
26
+ from . import client as _client
27
+
28
+ # ---------------------------------------------------------------------------
29
+ # Module-level state
30
+ # ---------------------------------------------------------------------------
31
+
32
+ _config: dict[str, str] = {
33
+ "endpoint": "",
34
+ "project_id": "",
35
+ }
36
+
37
+ _queue: queue.Queue[dict[str, Any]] = queue.Queue(maxsize=10_000)
38
+
39
+ _worker_thread: Optional[threading.Thread] = None
40
+ _worker_lock = threading.Lock()
41
+ _started = False
42
+
43
+ # Circuit breaker
44
+ _consecutive_failures = 0
45
+ _circuit_open = False
46
+ _circuit_open_time: float = 0.0
47
+ _FAILURE_THRESHOLD = 3
48
+ _CIRCUIT_OPEN_DURATION = 60.0 # seconds
49
+
50
+ # Flush settings
51
+ _FLUSH_INTERVAL = 5.0 # seconds
52
+ _BATCH_SIZE = 50
53
+
54
+ # Shutdown flag
55
+ _shutdown_event = threading.Event()
56
+
57
+
58
+ # ---------------------------------------------------------------------------
59
+ # Initialisation
60
+ # ---------------------------------------------------------------------------
61
+
62
+ def _load_from_config_file() -> None:
63
+ """Try to load config from .utopia/config.json (walks up from cwd)."""
64
+ try:
65
+ import json
66
+ import pathlib
67
+ d = pathlib.Path.cwd()
68
+ for _ in range(10):
69
+ cfg_path = d / ".utopia" / "config.json"
70
+ if cfg_path.exists():
71
+ cfg = json.loads(cfg_path.read_text())
72
+ if not _config["endpoint"] and cfg.get("dataEndpoint"):
73
+ _config["endpoint"] = cfg["dataEndpoint"]
74
+ if not _config["project_id"] and cfg.get("projectId"):
75
+ _config["project_id"] = cfg["projectId"]
76
+ return
77
+ parent = d.parent
78
+ if parent == d:
79
+ break
80
+ d = parent
81
+ except Exception:
82
+ pass
83
+
84
+
85
+ def init(
86
+ endpoint: str = "",
87
+ project_id: str = "",
88
+ ) -> None:
89
+ """Explicitly initialise the Utopia runtime."""
90
+ _config["endpoint"] = endpoint or os.environ.get("UTOPIA_ENDPOINT", "")
91
+ _config["project_id"] = project_id or os.environ.get("UTOPIA_PROJECT_ID", "")
92
+ if not _config["endpoint"] or not _config["project_id"]:
93
+ _load_from_config_file()
94
+ _start_worker()
95
+
96
+
97
+ def _ensure_initialized() -> None:
98
+ """Auto-initialise from env vars or .utopia/config.json."""
99
+ global _started
100
+ if _started:
101
+ return
102
+ with _worker_lock:
103
+ if _started:
104
+ return
105
+ if not _config["endpoint"]:
106
+ _config["endpoint"] = os.environ.get("UTOPIA_ENDPOINT", "")
107
+ if not _config["project_id"]:
108
+ _config["project_id"] = os.environ.get("UTOPIA_PROJECT_ID", "")
109
+ if not _config["endpoint"] or not _config["project_id"]:
110
+ _load_from_config_file()
111
+ _start_worker()
112
+
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # Background worker
116
+ # ---------------------------------------------------------------------------
117
+
118
+ def _start_worker() -> None:
119
+ """Start the daemon flush thread (idempotent)."""
120
+ global _worker_thread, _started
121
+ if _started:
122
+ return
123
+ _started = True
124
+ _worker_thread = threading.Thread(target=_flush_loop, name="utopia-probe-worker", daemon=True)
125
+ _worker_thread.start()
126
+ atexit.register(_shutdown)
127
+
128
+
129
+ def _shutdown() -> None:
130
+ """Drain remaining probes on interpreter shutdown."""
131
+ _shutdown_event.set()
132
+ # Give the worker a moment to flush
133
+ if _worker_thread is not None and _worker_thread.is_alive():
134
+ _worker_thread.join(timeout=3.0)
135
+ # Final emergency flush
136
+ _flush_batch()
137
+
138
+
139
+ def _flush_loop() -> None:
140
+ """Main loop for the background worker thread."""
141
+ while not _shutdown_event.is_set():
142
+ try:
143
+ # Wait up to FLUSH_INTERVAL, but wake early if batch is large
144
+ deadline = time.monotonic() + _FLUSH_INTERVAL
145
+ while time.monotonic() < deadline and not _shutdown_event.is_set():
146
+ if _queue.qsize() >= _BATCH_SIZE:
147
+ break
148
+ _shutdown_event.wait(timeout=0.25)
149
+ _flush_batch()
150
+ except Exception:
151
+ # Never let the worker die
152
+ pass
153
+
154
+
155
+ def _flush_batch() -> None:
156
+ """Drain the queue and send a batch to the data service."""
157
+ global _consecutive_failures, _circuit_open, _circuit_open_time
158
+
159
+ # Circuit breaker check
160
+ if _circuit_open:
161
+ if time.monotonic() - _circuit_open_time < _CIRCUIT_OPEN_DURATION:
162
+ # Discard everything while circuit is open to avoid memory growth
163
+ while not _queue.empty():
164
+ try:
165
+ _queue.get_nowait()
166
+ except queue.Empty:
167
+ break
168
+ return
169
+ else:
170
+ # Half-open: allow one attempt
171
+ _circuit_open = False
172
+ _consecutive_failures = 0
173
+
174
+ # Drain queue
175
+ batch: list[dict[str, Any]] = []
176
+ while len(batch) < 500: # cap per-flush to avoid huge payloads
177
+ try:
178
+ item = _queue.get_nowait()
179
+ batch.append(item)
180
+ except queue.Empty:
181
+ break
182
+
183
+ if not batch:
184
+ return
185
+
186
+ endpoint = _config.get("endpoint", "")
187
+
188
+ if not endpoint:
189
+ # Nowhere to send -- silently discard
190
+ return
191
+
192
+ success = _client.send_probes(endpoint, batch)
193
+
194
+ if success:
195
+ _consecutive_failures = 0
196
+ else:
197
+ _consecutive_failures += 1
198
+ if _consecutive_failures >= _FAILURE_THRESHOLD:
199
+ _circuit_open = True
200
+ _circuit_open_time = time.monotonic()
201
+
202
+
203
+ # ---------------------------------------------------------------------------
204
+ # Helpers
205
+ # ---------------------------------------------------------------------------
206
+
207
+ def _generate_id() -> str:
208
+ """Generate a unique probe identifier."""
209
+ return str(uuid.uuid4())
210
+
211
+
212
+ def _timestamp() -> str:
213
+ """ISO-8601 UTC timestamp."""
214
+ return datetime.now(timezone.utc).isoformat()
215
+
216
+
217
+ def _hostname() -> str:
218
+ try:
219
+ return socket.gethostname()
220
+ except Exception:
221
+ return "<unknown>"
222
+
223
+
224
+ def _base_probe(probe_type: str, file: str, line: int, function_name: str = "") -> dict[str, Any]:
225
+ """Build the common probe envelope."""
226
+ return {
227
+ "id": _generate_id(),
228
+ "probeType": probe_type,
229
+ "timestamp": _timestamp(),
230
+ "projectId": _config.get("project_id", ""),
231
+ "file": file,
232
+ "line": line,
233
+ "functionName": function_name,
234
+ "metadata": {
235
+ "runtime": "python",
236
+ "hostname": _hostname(),
237
+ "pid": os.getpid(),
238
+ "env": os.environ.get("UTOPIA_ENV", os.environ.get("NODE_ENV", "production")),
239
+ },
240
+ }
241
+
242
+
243
+ def _enqueue(probe: dict[str, Any]) -> None:
244
+ """Push a probe dict onto the queue; drop silently if full."""
245
+ try:
246
+ _queue.put_nowait(probe)
247
+ except queue.Full:
248
+ pass # back-pressure: silently drop
249
+
250
+
251
+ # ---------------------------------------------------------------------------
252
+ # Public report_* functions
253
+ # ---------------------------------------------------------------------------
254
+
255
+ def report_error(
256
+ file: str,
257
+ line: int,
258
+ function_name: str,
259
+ error_type: str,
260
+ message: str,
261
+ stack: str,
262
+ input_data: Optional[dict[str, str]] = None,
263
+ ) -> None:
264
+ """Report a caught exception.
265
+
266
+ Called by the injected error-probe try/except wrappers.
267
+ """
268
+ try:
269
+ _ensure_initialized()
270
+ probe = _base_probe("error", file, line, function_name)
271
+ probe["data"] = {
272
+ "error_type": error_type,
273
+ "message": message,
274
+ "stack": stack,
275
+ "input_data": input_data or {},
276
+ }
277
+ _enqueue(probe)
278
+ except Exception:
279
+ pass
280
+
281
+
282
+ def report_db(
283
+ file: str,
284
+ line: int,
285
+ function_name: str = "",
286
+ operation: str = "",
287
+ query: Optional[str] = None,
288
+ table: Optional[str] = None,
289
+ duration: float = 0.0,
290
+ row_count: Optional[int] = None,
291
+ connection_info: Optional[dict[str, Any]] = None,
292
+ params: Optional[Any] = None,
293
+ ) -> None:
294
+ """Report a database operation.
295
+
296
+ Called by the injected database-probe wrappers.
297
+ """
298
+ try:
299
+ _ensure_initialized()
300
+ probe = _base_probe("database", file, line, function_name)
301
+ probe["data"] = {
302
+ "operation": operation,
303
+ "query": query,
304
+ "table": table,
305
+ "duration": duration,
306
+ "row_count": row_count,
307
+ "connection_info": connection_info or {},
308
+ "params": repr(params) if params is not None else None,
309
+ }
310
+ _enqueue(probe)
311
+ except Exception:
312
+ pass
313
+
314
+
315
+ def report_api(
316
+ file: str,
317
+ line: int,
318
+ function_name: str = "",
319
+ method: str = "",
320
+ url: str = "",
321
+ status_code: Optional[int] = None,
322
+ duration: float = 0.0,
323
+ request_headers: Optional[dict[str, str]] = None,
324
+ response_headers: Optional[dict[str, str]] = None,
325
+ request_body: Optional[str] = None,
326
+ response_body: Optional[str] = None,
327
+ error: Optional[str] = None,
328
+ ) -> None:
329
+ """Report an outbound HTTP API call.
330
+
331
+ Called by the injected API-probe wrappers.
332
+ """
333
+ try:
334
+ _ensure_initialized()
335
+ probe = _base_probe("api", file, line, function_name)
336
+
337
+ # Sanitize headers: strip sensitive values
338
+ _sensitive_keys = {"authorization", "x-api-key", "cookie", "set-cookie"}
339
+
340
+ def _sanitize_headers(headers: Optional[dict[str, str]]) -> Optional[dict[str, str]]:
341
+ if headers is None:
342
+ return None
343
+ return {
344
+ k: ("***" if k.lower() in _sensitive_keys else v)
345
+ for k, v in headers.items()
346
+ }
347
+
348
+ probe["data"] = {
349
+ "method": method,
350
+ "url": url,
351
+ "status_code": status_code,
352
+ "duration": duration,
353
+ "request_headers": _sanitize_headers(request_headers),
354
+ "response_headers": _sanitize_headers(response_headers),
355
+ "request_body": request_body,
356
+ "response_body": response_body,
357
+ "error": error,
358
+ }
359
+ _enqueue(probe)
360
+ except Exception:
361
+ pass
362
+
363
+
364
+ def report_infra(
365
+ file: str,
366
+ line: int = 0,
367
+ provider: str = "other",
368
+ region: Optional[str] = None,
369
+ service_type: Optional[str] = None,
370
+ instance_id: Optional[str] = None,
371
+ container_info: Optional[dict[str, Any]] = None,
372
+ env_vars: Optional[dict[str, str]] = None,
373
+ ) -> None:
374
+ """Report infrastructure / deployment environment information.
375
+
376
+ Typically called once at application startup from the injected infra probe.
377
+ """
378
+ try:
379
+ _ensure_initialized()
380
+ probe = _base_probe("infra", file, line)
381
+ probe["data"] = {
382
+ "provider": provider,
383
+ "region": region,
384
+ "service_type": service_type,
385
+ "instance_id": instance_id,
386
+ "container_info": container_info or {},
387
+ "env_vars": env_vars or {},
388
+ }
389
+ _enqueue(probe)
390
+ except Exception:
391
+ pass
392
+
393
+
394
+ def report_function(
395
+ file: str,
396
+ line: int,
397
+ function_name: str,
398
+ args: Optional[dict[str, str]] = None,
399
+ return_value: Optional[str] = None,
400
+ duration: float = 0.0,
401
+ call_stack: Optional[list[str]] = None,
402
+ ) -> None:
403
+ """Report a function invocation (utopia mode).
404
+
405
+ Captures entry arguments and execution duration for every instrumented
406
+ function call.
407
+ """
408
+ try:
409
+ _ensure_initialized()
410
+ probe = _base_probe("function", file, line, function_name)
411
+ probe["data"] = {
412
+ "args": args or {},
413
+ "return_value": return_value,
414
+ "duration": duration,
415
+ "call_stack": call_stack or [],
416
+ }
417
+ _enqueue(probe)
418
+ except Exception:
419
+ pass
420
+
421
+
422
+ def report_llm_context(
423
+ file: str,
424
+ line: int,
425
+ function_name: str,
426
+ context: Any,
427
+ ) -> None:
428
+ """Report LLM context data (utopia mode).
429
+
430
+ Used to capture context windows, prompt chains, and other LLM-specific
431
+ observability data that helps AI coding agents understand the production
432
+ environment.
433
+ """
434
+ try:
435
+ _ensure_initialized()
436
+ probe = _base_probe("llm_context", file, line, function_name)
437
+ # Serialize context safely
438
+ if isinstance(context, dict):
439
+ probe["data"] = context
440
+ elif isinstance(context, str):
441
+ probe["data"] = {"context": context}
442
+ else:
443
+ probe["data"] = {"context": repr(context)}
444
+ _enqueue(probe)
445
+ except Exception:
446
+ pass
@@ -0,0 +1,59 @@
1
+ Metadata-Version: 2.4
2
+ Name: utopia-runtime
3
+ Version: 0.1.0
4
+ Summary: Zero-impact production probe runtime for Utopia — gives AI coding agents real-time visibility into how code runs
5
+ Author: Utopia
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/paulvann/utopia
8
+ Project-URL: Repository, https://github.com/paulvann/utopia
9
+ Keywords: utopia,probes,observability,production-context,ai-agents
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Libraries
20
+ Classifier: Topic :: System :: Monitoring
21
+ Requires-Python: >=3.9
22
+ Description-Content-Type: text/markdown
23
+ Dynamic: author
24
+ Dynamic: requires-python
25
+
26
+ # utopia-runtime
27
+
28
+ Zero-impact production probe runtime for [Utopia](https://github.com/paulvann/utopia).
29
+
30
+ Captures errors, API calls, database queries, function behavior, and infrastructure context — sending it to the Utopia data service so AI coding agents can understand how your code runs in production.
31
+
32
+ ## Installation
33
+
34
+ ```bash
35
+ pip install utopia-runtime
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ Probes are added by `utopia instrument` — you don't typically import this directly. The runtime auto-initializes from `.utopia/config.json` in your project root.
41
+
42
+ ```python
43
+ import utopia_runtime
44
+
45
+ # Reports are non-blocking and never raise
46
+ utopia_runtime.report_function(
47
+ file="app/routes.py",
48
+ line=25,
49
+ function_name="get_user",
50
+ args=[{"user_id": 123}],
51
+ return_value={"found": True},
52
+ duration=15,
53
+ call_stack=[],
54
+ )
55
+ ```
56
+
57
+ ## Zero dependencies
58
+
59
+ Uses only the Python standard library. No external packages required.
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ utopia_runtime/__init__.py
5
+ utopia_runtime/client.py
6
+ utopia_runtime/probe.py
7
+ utopia_runtime.egg-info/PKG-INFO
8
+ utopia_runtime.egg-info/SOURCES.txt
9
+ utopia_runtime.egg-info/dependency_links.txt
10
+ utopia_runtime.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ utopia_runtime
@@ -0,0 +1,14 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo "Publishing utopia-runtime to npm..."
5
+ cd "$(dirname "$0")/../src/runtime/js"
6
+
7
+ # Build
8
+ npm install
9
+ npm run build
10
+
11
+ # Publish (use --access public for first publish of scoped packages)
12
+ npm publish --access public
13
+
14
+ echo "Done! Published utopia-runtime@$(node -e "console.log(require('./package.json').version)")"
@@ -0,0 +1,17 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ echo "Publishing utopia-runtime to PyPI..."
5
+ cd "$(dirname "$0")/../python"
6
+
7
+ # Clean old builds
8
+ rm -rf dist/ build/ *.egg-info
9
+
10
+ # Build
11
+ python3 -m pip install --upgrade build twine 2>/dev/null
12
+ python3 -m build
13
+
14
+ # Upload
15
+ python3 -m twine upload dist/*
16
+
17
+ echo "Done! Published utopia-runtime@$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")"