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.
- deepline-1.0.0/PKG-INFO +12 -0
- deepline-1.0.0/README.md +5 -0
- deepline-1.0.0/deepline.egg-info/PKG-INFO +12 -0
- deepline-1.0.0/deepline.egg-info/SOURCES.txt +39 -0
- deepline-1.0.0/deepline.egg-info/dependency_links.txt +1 -0
- deepline-1.0.0/deepline.egg-info/entry_points.txt +2 -0
- deepline-1.0.0/deepline.egg-info/top_level.txt +1 -0
- deepline-1.0.0/deepline_core/__init__.py +0 -0
- deepline-1.0.0/deepline_core/browser.py +428 -0
- deepline-1.0.0/deepline_core/cli_events.py +132 -0
- deepline-1.0.0/deepline_core/command_common.py +288 -0
- deepline-1.0.0/deepline_core/command_decorators.py +254 -0
- deepline-1.0.0/deepline_core/commands/__init__.py +27 -0
- deepline-1.0.0/deepline_core/commands/auth.py +652 -0
- deepline-1.0.0/deepline_core/commands/backend.py +1278 -0
- deepline-1.0.0/deepline_core/commands/billing.py +528 -0
- deepline-1.0.0/deepline_core/commands/csv.py +1451 -0
- deepline-1.0.0/deepline_core/commands/db.py +148 -0
- deepline-1.0.0/deepline_core/commands/enrich.py +5323 -0
- deepline-1.0.0/deepline_core/commands/events.py +67 -0
- deepline-1.0.0/deepline_core/commands/feedback.py +140 -0
- deepline-1.0.0/deepline_core/commands/onboard.py +194 -0
- deepline-1.0.0/deepline_core/commands/org.py +45 -0
- deepline-1.0.0/deepline_core/commands/retry_policy.py +2 -0
- deepline-1.0.0/deepline_core/commands/session.py +1269 -0
- deepline-1.0.0/deepline_core/commands/tools.py +3143 -0
- deepline-1.0.0/deepline_core/commands/workflows.py +1399 -0
- deepline-1.0.0/deepline_core/dataset.py +170 -0
- deepline-1.0.0/deepline_core/enrich/__init__.py +18 -0
- deepline-1.0.0/deepline_core/enrich/core.py +19 -0
- deepline-1.0.0/deepline_core/enrich/run_block.py +137 -0
- deepline-1.0.0/deepline_core/http.py +266 -0
- deepline-1.0.0/deepline_core/local_tools.py +1498 -0
- deepline-1.0.0/deepline_core/main.py +790 -0
- deepline-1.0.0/deepline_core/playground_helpers.py +673 -0
- deepline-1.0.0/deepline_core/recipes.json +143 -0
- deepline-1.0.0/deepline_core/recipes_data.py +55 -0
- deepline-1.0.0/deepline_core/self_update.py +955 -0
- deepline-1.0.0/deepline_core/tool_helpers.py +27 -0
- deepline-1.0.0/pyproject.toml +31 -0
- deepline-1.0.0/setup.cfg +4 -0
- deepline-1.0.0/setup.py +52 -0
deepline-1.0.0/PKG-INFO
ADDED
|
@@ -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.
|
deepline-1.0.0/README.md
ADDED
|
@@ -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 @@
|
|
|
1
|
+
|
|
@@ -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)
|