deepline 1.0.0__tar.gz

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 (42) hide show
  1. deepline-1.0.0/PKG-INFO +12 -0
  2. deepline-1.0.0/README.md +5 -0
  3. deepline-1.0.0/deepline.egg-info/PKG-INFO +12 -0
  4. deepline-1.0.0/deepline.egg-info/SOURCES.txt +39 -0
  5. deepline-1.0.0/deepline.egg-info/dependency_links.txt +1 -0
  6. deepline-1.0.0/deepline.egg-info/entry_points.txt +2 -0
  7. deepline-1.0.0/deepline.egg-info/top_level.txt +1 -0
  8. deepline-1.0.0/deepline_core/__init__.py +0 -0
  9. deepline-1.0.0/deepline_core/browser.py +428 -0
  10. deepline-1.0.0/deepline_core/cli_events.py +132 -0
  11. deepline-1.0.0/deepline_core/command_common.py +288 -0
  12. deepline-1.0.0/deepline_core/command_decorators.py +254 -0
  13. deepline-1.0.0/deepline_core/commands/__init__.py +27 -0
  14. deepline-1.0.0/deepline_core/commands/auth.py +652 -0
  15. deepline-1.0.0/deepline_core/commands/backend.py +1278 -0
  16. deepline-1.0.0/deepline_core/commands/billing.py +528 -0
  17. deepline-1.0.0/deepline_core/commands/csv.py +1451 -0
  18. deepline-1.0.0/deepline_core/commands/db.py +148 -0
  19. deepline-1.0.0/deepline_core/commands/enrich.py +5323 -0
  20. deepline-1.0.0/deepline_core/commands/events.py +67 -0
  21. deepline-1.0.0/deepline_core/commands/feedback.py +140 -0
  22. deepline-1.0.0/deepline_core/commands/onboard.py +194 -0
  23. deepline-1.0.0/deepline_core/commands/org.py +45 -0
  24. deepline-1.0.0/deepline_core/commands/retry_policy.py +2 -0
  25. deepline-1.0.0/deepline_core/commands/session.py +1269 -0
  26. deepline-1.0.0/deepline_core/commands/tools.py +3143 -0
  27. deepline-1.0.0/deepline_core/commands/workflows.py +1399 -0
  28. deepline-1.0.0/deepline_core/dataset.py +170 -0
  29. deepline-1.0.0/deepline_core/enrich/__init__.py +18 -0
  30. deepline-1.0.0/deepline_core/enrich/core.py +19 -0
  31. deepline-1.0.0/deepline_core/enrich/run_block.py +137 -0
  32. deepline-1.0.0/deepline_core/http.py +266 -0
  33. deepline-1.0.0/deepline_core/local_tools.py +1498 -0
  34. deepline-1.0.0/deepline_core/main.py +790 -0
  35. deepline-1.0.0/deepline_core/playground_helpers.py +673 -0
  36. deepline-1.0.0/deepline_core/recipes.json +143 -0
  37. deepline-1.0.0/deepline_core/recipes_data.py +55 -0
  38. deepline-1.0.0/deepline_core/self_update.py +955 -0
  39. deepline-1.0.0/deepline_core/tool_helpers.py +27 -0
  40. deepline-1.0.0/pyproject.toml +31 -0
  41. deepline-1.0.0/setup.cfg +4 -0
  42. deepline-1.0.0/setup.py +52 -0
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: deepline
3
+ Version: 1.0.0
4
+ Summary: Deepline CLI
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+
8
+ # Deepline CLI
9
+
10
+ The Deepline command line interface for running enrichments, workflows, and local runtime setup.
11
+
12
+ After installation, run `deepline --help` to get started.
@@ -0,0 +1,5 @@
1
+ # Deepline CLI
2
+
3
+ The Deepline command line interface for running enrichments, workflows, and local runtime setup.
4
+
5
+ After installation, run `deepline --help` to get started.
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: deepline
3
+ Version: 1.0.0
4
+ Summary: Deepline CLI
5
+ Requires-Python: >=3.11
6
+ Description-Content-Type: text/markdown
7
+
8
+ # Deepline CLI
9
+
10
+ The Deepline command line interface for running enrichments, workflows, and local runtime setup.
11
+
12
+ After installation, run `deepline --help` to get started.
@@ -0,0 +1,39 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.py
4
+ deepline.egg-info/PKG-INFO
5
+ deepline.egg-info/SOURCES.txt
6
+ deepline.egg-info/dependency_links.txt
7
+ deepline.egg-info/entry_points.txt
8
+ deepline.egg-info/top_level.txt
9
+ deepline_core/__init__.py
10
+ deepline_core/browser.py
11
+ deepline_core/cli_events.py
12
+ deepline_core/command_common.py
13
+ deepline_core/command_decorators.py
14
+ deepline_core/dataset.py
15
+ deepline_core/http.py
16
+ deepline_core/local_tools.py
17
+ deepline_core/main.py
18
+ deepline_core/playground_helpers.py
19
+ deepline_core/recipes_data.py
20
+ deepline_core/self_update.py
21
+ deepline_core/tool_helpers.py
22
+ deepline_core/commands/__init__.py
23
+ deepline_core/commands/auth.py
24
+ deepline_core/commands/backend.py
25
+ deepline_core/commands/billing.py
26
+ deepline_core/commands/csv.py
27
+ deepline_core/commands/db.py
28
+ deepline_core/commands/enrich.py
29
+ deepline_core/commands/events.py
30
+ deepline_core/commands/feedback.py
31
+ deepline_core/commands/onboard.py
32
+ deepline_core/commands/org.py
33
+ deepline_core/commands/retry_policy.py
34
+ deepline_core/commands/session.py
35
+ deepline_core/commands/tools.py
36
+ deepline_core/commands/workflows.py
37
+ deepline_core/enrich/__init__.py
38
+ deepline_core/enrich/core.py
39
+ deepline_core/enrich/run_block.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ deepline = deepline_core.main:run
@@ -0,0 +1 @@
1
+ deepline_core
File without changes
@@ -0,0 +1,428 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import math
5
+ import os
6
+ import plistlib
7
+ import subprocess
8
+ import time
9
+ from pathlib import Path
10
+ from typing import Protocol
11
+ from urllib.parse import parse_qs, quote, urlparse
12
+
13
+ MACOS_BROWSER_FOCUS_COOLDOWN_SECONDS = 30.0
14
+
15
+
16
+ class _SupportsFileno(Protocol):
17
+ def fileno(self) -> int: ...
18
+
19
+
20
+ def _extract_url_host(raw: str) -> str:
21
+ """Return host:port so tab retarget only matches the exact origin.
22
+
23
+ Using hostname alone (no port) would match ANY localhost tab regardless of
24
+ port, hijacking e.g. a session-UI tab at :4173 when targeting :4174.
25
+ """
26
+ parsed = urlparse(raw if "://" in raw else f"https://{raw}")
27
+ hostname = (parsed.hostname or "").strip().lower()
28
+ if not hostname:
29
+ return ""
30
+ port = parsed.port
31
+ if port:
32
+ return f"{hostname}:{port}"
33
+ return hostname
34
+
35
+
36
+ def _is_localhost_host(host: str) -> bool:
37
+ normalized = str(host or "").strip().lower()
38
+ return normalized == "localhost" or normalized.startswith("127.") or normalized == "0.0.0.0" or normalized == "::1"
39
+
40
+
41
+ def _is_local_playground_render_url(raw: str) -> bool:
42
+ try:
43
+ parsed = urlparse(str(raw or "").strip())
44
+ except Exception:
45
+ return False
46
+ path = (parsed.path or "").strip()
47
+ return _is_localhost_host(parsed.hostname or "") and path in {"", "/"}
48
+
49
+
50
+ def _extract_session_id(raw: str) -> str:
51
+ try:
52
+ parsed = urlparse(str(raw or "").strip())
53
+ except Exception:
54
+ return ""
55
+ if not parsed.query:
56
+ return ""
57
+ query = parse_qs(parsed.query)
58
+ return str((query.get("session_id") or [""])[0]).strip()
59
+
60
+
61
+ def _default_bundle_macos() -> str:
62
+ plist_path = os.path.expanduser(
63
+ "~/Library/Preferences/com.apple.LaunchServices/com.apple.launchservices.secure.plist"
64
+ )
65
+ if not os.path.isfile(plist_path):
66
+ return ""
67
+ try:
68
+ with open(plist_path, "rb") as handle:
69
+ payload = plistlib.load(handle) or {}
70
+ except Exception:
71
+ return ""
72
+ handlers = payload.get("LSHandlers") or []
73
+ if not isinstance(handlers, list):
74
+ return ""
75
+
76
+ def pick_bundle(scheme: str) -> str:
77
+ for item in handlers:
78
+ if not isinstance(item, dict):
79
+ continue
80
+ if str(item.get("LSHandlerURLScheme") or "").lower() != scheme:
81
+ continue
82
+ bundle = (
83
+ item.get("LSHandlerRoleAll")
84
+ or item.get("LSHandlerRoleViewer")
85
+ or item.get("LSHandlerRoleEditor")
86
+ or ""
87
+ )
88
+ bundle = str(bundle).strip()
89
+ if bundle:
90
+ return bundle
91
+ return ""
92
+
93
+ return pick_bundle("https") or pick_bundle("http")
94
+
95
+
96
+ def _browser_strategy_for_bundle(bundle_id: str) -> str:
97
+ bundle = bundle_id.lower()
98
+ if bundle in {
99
+ "com.google.chrome",
100
+ "com.google.chrome.canary",
101
+ "com.microsoft.edgemac",
102
+ "com.brave.browser",
103
+ "com.operasoftware.opera",
104
+ "com.operasoftware.operagx",
105
+ "com.vivaldi.vivaldi",
106
+ "company.thebrowser.browser",
107
+ }:
108
+ return "chromium"
109
+ if bundle == "com.apple.safari":
110
+ return "safari"
111
+ return "fallback"
112
+
113
+
114
+ def _browser_app_name_for_bundle(bundle_id: str) -> str:
115
+ names = {
116
+ "com.google.chrome": "Google Chrome",
117
+ "com.google.chrome.canary": "Google Chrome Canary",
118
+ "com.microsoft.edgemac": "Microsoft Edge",
119
+ "com.brave.browser": "Brave Browser",
120
+ "com.operasoftware.opera": "Opera",
121
+ "com.operasoftware.operagx": "Opera GX",
122
+ "com.vivaldi.vivaldi": "Vivaldi",
123
+ "company.thebrowser.browser": "Arc",
124
+ "com.apple.safari": "Safari",
125
+ }
126
+ return names.get(bundle_id.lower(), "")
127
+
128
+
129
+ def _browser_focus_state_file() -> Path:
130
+ home_dir = os.environ.get("HOME", str(Path.home()))
131
+ return Path(home_dir) / ".local" / "deepline" / "runtime" / "state" / "browser-focus.json"
132
+
133
+
134
+ def _lock_focus_state(handle: _SupportsFileno) -> None:
135
+ try:
136
+ import fcntl
137
+ except Exception:
138
+ return
139
+ fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
140
+
141
+
142
+ def _unlock_focus_state(handle: _SupportsFileno) -> None:
143
+ try:
144
+ import fcntl
145
+ except Exception:
146
+ return
147
+ fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
148
+
149
+
150
+ def _claim_macos_browser_focus(now: float | None = None) -> bool:
151
+ current_time = time.time() if now is None else float(now)
152
+ state_path = _browser_focus_state_file()
153
+ try:
154
+ state_path.parent.mkdir(parents=True, exist_ok=True)
155
+ state_path.touch(exist_ok=True)
156
+ with state_path.open("r+", encoding="utf-8") as handle:
157
+ _lock_focus_state(handle)
158
+ try:
159
+ handle.seek(0)
160
+ payload_raw = handle.read().strip()
161
+ last_focused_at = 0.0
162
+ if payload_raw:
163
+ payload = json.loads(payload_raw)
164
+ if isinstance(payload, dict):
165
+ last_value = payload.get("last_focused_at")
166
+ if isinstance(last_value, (int, float)) and math.isfinite(last_value):
167
+ last_focused_at = float(last_value)
168
+ if last_focused_at > current_time:
169
+ last_focused_at = 0.0
170
+ if current_time - last_focused_at < MACOS_BROWSER_FOCUS_COOLDOWN_SECONDS:
171
+ return False
172
+ handle.seek(0)
173
+ handle.truncate()
174
+ json.dump({"last_focused_at": current_time}, handle)
175
+ handle.flush()
176
+ os.fsync(handle.fileno())
177
+ return True
178
+ finally:
179
+ _unlock_focus_state(handle)
180
+ except Exception:
181
+ return True
182
+
183
+
184
+ def _open_url_macos(
185
+ target_url: str,
186
+ *,
187
+ allow_focus: bool,
188
+ app_name: str = "",
189
+ bundle_id: str = "",
190
+ ) -> bool:
191
+ args = ["open"]
192
+ if not allow_focus:
193
+ args.append("-g")
194
+ if app_name:
195
+ args.extend(["-a", app_name])
196
+ elif bundle_id:
197
+ args.extend(["-b", bundle_id])
198
+ args.append(target_url)
199
+ try:
200
+ # Run synchronously so we can detect launch failures and fall back to
201
+ # a generic `open <url>` invocation.
202
+ result = subprocess.run(
203
+ args,
204
+ stdout=subprocess.DEVNULL,
205
+ stderr=subprocess.DEVNULL,
206
+ check=False,
207
+ )
208
+ return result.returncode == 0
209
+ except Exception:
210
+ return False
211
+
212
+
213
+ def _retarget_chromium_macos(
214
+ app_name: str,
215
+ target_url: str,
216
+ *,
217
+ allow_focus: bool,
218
+ required_session_token: str = "",
219
+ ) -> bool:
220
+ host = _extract_url_host(target_url)
221
+ if not host:
222
+ return False
223
+ escaped_app_name = app_name.replace('"', '\\"')
224
+ activate_block = " activate\n" if allow_focus else ""
225
+ found_tab_block = (
226
+ " set active tab index of w to i\n"
227
+ " set index of w to 1\n"
228
+ " set URL of t to targetUrl\n"
229
+ if allow_focus
230
+ else " set URL of t to targetUrl\n"
231
+ )
232
+ new_tab_block = (
233
+ " tell window 1\n"
234
+ " make new tab with properties {{URL:targetUrl}}\n"
235
+ " set active tab index to (count of tabs)\n"
236
+ " set index to 1\n"
237
+ " end tell\n"
238
+ if allow_focus
239
+ else " tell window 1\n"
240
+ " make new tab with properties {{URL:targetUrl}}\n"
241
+ " end tell\n"
242
+ )
243
+ script = f"""
244
+ on run argv
245
+ set targetUrl to item 1 of argv
246
+ set targetHost to item 2 of argv
247
+ set requiredSessionToken to item 3 of argv
248
+ tell application "{escaped_app_name}"
249
+ {activate_block} if (count of windows) is 0 then
250
+ make new window
251
+ end if
252
+ set foundTab to false
253
+ repeat with w in windows
254
+ set tabCount to count of tabs of w
255
+ repeat with i from 1 to tabCount
256
+ set t to tab i of w
257
+ set tabUrl to URL of t
258
+ set tabMatchesSession to true
259
+ if requiredSessionToken is not "" then
260
+ if tabUrl does not contain requiredSessionToken then
261
+ set tabMatchesSession to false
262
+ end if
263
+ end if
264
+ if tabUrl contains targetHost and tabMatchesSession then
265
+ {found_tab_block} set foundTab to true
266
+ exit repeat
267
+ end if
268
+ end repeat
269
+ if foundTab then exit repeat
270
+ end repeat
271
+ if not foundTab then
272
+ {new_tab_block} end if
273
+ end tell
274
+ end run
275
+ """
276
+ try:
277
+ result = subprocess.run(
278
+ ["osascript", "-", target_url, host, required_session_token],
279
+ input=script,
280
+ text=True,
281
+ stdout=subprocess.DEVNULL,
282
+ stderr=subprocess.DEVNULL,
283
+ check=False,
284
+ timeout=5,
285
+ )
286
+ return result.returncode == 0
287
+ except subprocess.TimeoutExpired:
288
+ return False
289
+
290
+
291
+ def _retarget_safari_macos(
292
+ app_name: str,
293
+ target_url: str,
294
+ *,
295
+ allow_focus: bool,
296
+ required_session_token: str = "",
297
+ ) -> bool:
298
+ host = _extract_url_host(target_url)
299
+ if not host:
300
+ return False
301
+ escaped_app_name = app_name.replace('"', '\\"')
302
+ activate_block = " activate\n" if allow_focus else ""
303
+ found_tab_block = (
304
+ " set current tab of w to t\n"
305
+ " set index of w to 1\n"
306
+ " set URL of t to targetUrl\n"
307
+ if allow_focus
308
+ else " set URL of t to targetUrl\n"
309
+ )
310
+ new_tab_block = (
311
+ " tell window 1\n"
312
+ " set current tab to (make new tab with properties {{URL:targetUrl}})\n"
313
+ " set index to 1\n"
314
+ " end tell\n"
315
+ if allow_focus
316
+ else " tell window 1\n"
317
+ " make new tab with properties {{URL:targetUrl}}\n"
318
+ " end tell\n"
319
+ )
320
+ script = f"""
321
+ on run argv
322
+ set targetUrl to item 1 of argv
323
+ set targetHost to item 2 of argv
324
+ set requiredSessionToken to item 3 of argv
325
+ tell application "{escaped_app_name}"
326
+ {activate_block} if (count of windows) is 0 then
327
+ make new document
328
+ end if
329
+ set foundTab to false
330
+ repeat with w in windows
331
+ set tabCount to count of tabs of w
332
+ repeat with i from 1 to tabCount
333
+ set t to tab i of w
334
+ set tabUrl to URL of t
335
+ set tabMatchesSession to true
336
+ if requiredSessionToken is not "" then
337
+ if tabUrl does not contain requiredSessionToken then
338
+ set tabMatchesSession to false
339
+ end if
340
+ end if
341
+ if tabUrl contains targetHost and tabMatchesSession then
342
+ {found_tab_block} set foundTab to true
343
+ exit repeat
344
+ end if
345
+ end repeat
346
+ if foundTab then exit repeat
347
+ end repeat
348
+ if not foundTab then
349
+ {new_tab_block} end if
350
+ end tell
351
+ end run
352
+ """
353
+ try:
354
+ result = subprocess.run(
355
+ ["osascript", "-", target_url, host, required_session_token],
356
+ input=script,
357
+ text=True,
358
+ stdout=subprocess.DEVNULL,
359
+ stderr=subprocess.DEVNULL,
360
+ check=False,
361
+ timeout=5,
362
+ )
363
+ return result.returncode == 0
364
+ except subprocess.TimeoutExpired:
365
+ return False
366
+
367
+
368
+ def open_url_in_browser(target_url: str, *, force_focus: bool = False) -> None:
369
+ no_browser = str(os.environ.get("DEEPLINE_NO_BROWSER") or os.environ.get("PLAYGROUND_HEADLESS") or "").strip().lower()
370
+ if no_browser in {"1", "true", "yes", "on"}:
371
+ return
372
+
373
+ target_url = str(target_url or "").strip()
374
+ if not target_url:
375
+ return
376
+
377
+ uname = os.uname().sysname if hasattr(os, "uname") else ""
378
+ allow_focus = True
379
+ if uname == "Darwin":
380
+ allow_focus = force_focus or _claim_macos_browser_focus()
381
+ if not allow_focus and _is_local_playground_render_url(target_url):
382
+ return
383
+ required_session_token = ""
384
+ if _is_local_playground_render_url(target_url):
385
+ target_session_id = _extract_session_id(target_url)
386
+ if target_session_id:
387
+ required_session_token = f"session_id={quote(target_session_id, safe='')}"
388
+ default_bundle = _default_bundle_macos()
389
+ if default_bundle:
390
+ app_name = _browser_app_name_for_bundle(default_bundle)
391
+ strategy = _browser_strategy_for_bundle(default_bundle)
392
+ if strategy == "chromium" and app_name and _retarget_chromium_macos(
393
+ app_name,
394
+ target_url,
395
+ allow_focus=allow_focus,
396
+ required_session_token=required_session_token,
397
+ ):
398
+ return
399
+ if strategy == "safari" and app_name and _retarget_safari_macos(
400
+ app_name,
401
+ target_url,
402
+ allow_focus=allow_focus,
403
+ required_session_token=required_session_token,
404
+ ):
405
+ return
406
+
407
+ if app_name and _open_url_macos(target_url, allow_focus=allow_focus, app_name=app_name):
408
+ return
409
+ if _open_url_macos(target_url, allow_focus=allow_focus, bundle_id=default_bundle):
410
+ return
411
+
412
+ if shutil_which("open"):
413
+ if uname == "Darwin":
414
+ _open_url_macos(target_url, allow_focus=allow_focus)
415
+ return
416
+ subprocess.Popen(["open", target_url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
417
+ return
418
+ if shutil_which("xdg-open"):
419
+ subprocess.Popen(["xdg-open", target_url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
420
+ return
421
+ if shutil_which("cmd.exe"):
422
+ subprocess.Popen(["cmd.exe", "/c", "start", "", target_url], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
423
+
424
+
425
+ def shutil_which(command: str) -> str | None:
426
+ from shutil import which
427
+
428
+ return which(command)
@@ -0,0 +1,132 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from deepline_core.http import http_request
6
+ from deepline_core.playground_helpers import (
7
+ playground_default_api,
8
+ playground_read_state,
9
+ )
10
+
11
+
12
+ def _cli_event_endpoint() -> str:
13
+ playground_api = str(
14
+ playground_read_state().get("backend_api_url") or playground_default_api()
15
+ ).strip()
16
+ return f"{playground_api.rstrip('/')}/api/cli-event"
17
+
18
+
19
+ def _inject_session_id(payload: dict[str, Any]) -> None:
20
+ """Auto-inject session_id from the PPID-keyed cache if not already present."""
21
+ if "session_id" in payload:
22
+ return
23
+ try:
24
+ import os
25
+ state = playground_read_state()
26
+ ppid_key = str(os.getppid())
27
+
28
+ # Primary cache shape used by the CLI.
29
+ session_ids = state.get("session_ids")
30
+ if isinstance(session_ids, dict):
31
+ cached = str(session_ids.get(ppid_key, "")).strip()
32
+ if cached:
33
+ payload["session_id"] = cached
34
+ return
35
+
36
+ # Compatibility fallback: newer runtime state can store PPID-scoped
37
+ # bindings under session_context_bindings (e.g. {"ppid:123": {...}}).
38
+ bindings = state.get("session_context_bindings")
39
+ if isinstance(bindings, dict):
40
+ for binding_key in (f"ppid:{ppid_key}", ppid_key):
41
+ entry = bindings.get(binding_key)
42
+ if isinstance(entry, dict):
43
+ cached = str(
44
+ entry.get("session_id")
45
+ or entry.get("sessionId")
46
+ or ""
47
+ ).strip()
48
+ else:
49
+ cached = str(entry or "").strip()
50
+ if cached:
51
+ payload["session_id"] = cached
52
+ return
53
+ except Exception:
54
+ pass
55
+
56
+
57
+ def post_cli_event(payload: dict[str, Any]) -> None:
58
+ try:
59
+ _inject_session_id(payload)
60
+ http_request("POST", _cli_event_endpoint(), None, payload, timeout=3)
61
+ except Exception:
62
+ # Best effort only; runtime feedback must never break enrich execution.
63
+ pass
64
+
65
+
66
+ def update_cli_event(
67
+ *,
68
+ source: str,
69
+ status: str,
70
+ title: str,
71
+ detail: str = "",
72
+ command: str | None = None,
73
+ csv_path: str | None = None,
74
+ run_id: str | None = None,
75
+ response: str | None = None,
76
+ meta: dict[str, Any] | None = None,
77
+ session_id: str | None = None,
78
+ ) -> None:
79
+ payload: dict[str, Any] = {
80
+ "action": "update",
81
+ "source": source,
82
+ "status": status,
83
+ "title": title,
84
+ "detail": detail,
85
+ }
86
+ if command:
87
+ payload["command"] = command
88
+ if csv_path:
89
+ payload["csv_path"] = csv_path
90
+ if run_id:
91
+ payload["run_id"] = run_id
92
+ if response is not None:
93
+ payload["response"] = response
94
+ if meta:
95
+ payload["meta"] = meta
96
+ if session_id:
97
+ payload["session_id"] = session_id
98
+ post_cli_event(payload)
99
+
100
+
101
+ def append_cli_event(
102
+ *,
103
+ source: str,
104
+ status: str,
105
+ message: str,
106
+ level: str = "info",
107
+ kind: str = "event",
108
+ command: str | None = None,
109
+ csv_path: str | None = None,
110
+ run_id: str | None = None,
111
+ meta: dict[str, Any] | None = None,
112
+ session_id: str | None = None,
113
+ ) -> None:
114
+ payload: dict[str, Any] = {
115
+ "action": "append_event",
116
+ "source": source,
117
+ "status": status,
118
+ "level": level,
119
+ "kind": kind,
120
+ "message": message,
121
+ }
122
+ if command:
123
+ payload["command"] = command
124
+ if csv_path:
125
+ payload["csv_path"] = csv_path
126
+ if run_id:
127
+ payload["run_id"] = run_id
128
+ if meta:
129
+ payload["meta"] = meta
130
+ if session_id:
131
+ payload["session_id"] = session_id
132
+ post_cli_event(payload)