narada 0.1.27__py3-none-any.whl → 0.1.29__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.
- narada/__init__.py +3 -10
- narada/client.py +160 -5
- narada/config.py +45 -0
- narada/version.py +7 -0
- narada/window.py +40 -3
- {narada-0.1.27.dist-info → narada-0.1.29.dist-info}/METADATA +3 -2
- narada-0.1.29.dist-info/RECORD +10 -0
- {narada-0.1.27.dist-info → narada-0.1.29.dist-info}/WHEEL +1 -1
- narada-0.1.27.dist-info/RECORD +0 -9
narada/__init__.py
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import importlib.metadata
|
|
2
|
-
|
|
3
1
|
from narada_core.errors import (
|
|
4
2
|
NaradaError,
|
|
5
3
|
NaradaExtensionMissingError,
|
|
@@ -11,17 +9,11 @@ from narada_core.errors import (
|
|
|
11
9
|
from narada_core.models import Agent, File, Response, ResponseContent
|
|
12
10
|
|
|
13
11
|
from narada.client import Narada
|
|
14
|
-
from narada.config import BrowserConfig
|
|
12
|
+
from narada.config import BrowserConfig, ProxyConfig
|
|
15
13
|
from narada.utils import download_file, render_html
|
|
14
|
+
from narada.version import __version__
|
|
16
15
|
from narada.window import LocalBrowserWindow, RemoteBrowserWindow
|
|
17
16
|
|
|
18
|
-
# Get version from package metadata
|
|
19
|
-
try:
|
|
20
|
-
__version__ = importlib.metadata.version("narada")
|
|
21
|
-
except Exception:
|
|
22
|
-
# Fallback version if package metadata is not available
|
|
23
|
-
__version__ = "unknown"
|
|
24
|
-
|
|
25
17
|
__all__ = [
|
|
26
18
|
"__version__",
|
|
27
19
|
"Agent",
|
|
@@ -36,6 +28,7 @@ __all__ = [
|
|
|
36
28
|
"NaradaInitializationError",
|
|
37
29
|
"NaradaTimeoutError",
|
|
38
30
|
"NaradaUnsupportedBrowserError",
|
|
31
|
+
"ProxyConfig",
|
|
39
32
|
"RemoteBrowserWindow",
|
|
40
33
|
"render_html",
|
|
41
34
|
"Response",
|
narada/client.py
CHANGED
|
@@ -9,6 +9,8 @@ from dataclasses import dataclass
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
from uuid import uuid4
|
|
11
11
|
|
|
12
|
+
import aiohttp
|
|
13
|
+
import semver
|
|
12
14
|
from narada_core.errors import (
|
|
13
15
|
NaradaExtensionMissingError,
|
|
14
16
|
NaradaExtensionUnauthenticatedError,
|
|
@@ -16,8 +18,11 @@ from narada_core.errors import (
|
|
|
16
18
|
NaradaTimeoutError,
|
|
17
19
|
NaradaUnsupportedBrowserError,
|
|
18
20
|
)
|
|
21
|
+
from narada_core.models import _SdkConfig
|
|
19
22
|
from playwright._impl._errors import Error as PlaywrightError
|
|
20
23
|
from playwright.async_api import (
|
|
24
|
+
Browser,
|
|
25
|
+
CDPSession,
|
|
21
26
|
ElementHandle,
|
|
22
27
|
Page,
|
|
23
28
|
Playwright,
|
|
@@ -29,8 +34,9 @@ from playwright.async_api import (
|
|
|
29
34
|
from playwright.async_api._context_manager import PlaywrightContextManager
|
|
30
35
|
from rich.console import Console
|
|
31
36
|
|
|
32
|
-
from narada.config import BrowserConfig
|
|
37
|
+
from narada.config import BrowserConfig, ProxyConfig
|
|
33
38
|
from narada.utils import assert_never
|
|
39
|
+
from narada.version import __version__
|
|
34
40
|
from narada.window import LocalBrowserWindow, create_side_panel_url
|
|
35
41
|
|
|
36
42
|
|
|
@@ -58,6 +64,8 @@ class Narada:
|
|
|
58
64
|
self._console = Console()
|
|
59
65
|
|
|
60
66
|
async def __aenter__(self) -> Narada:
|
|
67
|
+
await self._validate_sdk_config()
|
|
68
|
+
|
|
61
69
|
self._playwright_context_manager = async_playwright()
|
|
62
70
|
self._playwright = await self._playwright_context_manager.__aenter__()
|
|
63
71
|
return self
|
|
@@ -70,6 +78,40 @@ class Narada:
|
|
|
70
78
|
self._playwright_context_manager = None
|
|
71
79
|
self._playwright = None
|
|
72
80
|
|
|
81
|
+
async def _fetch_sdk_config(self) -> _SdkConfig | None:
|
|
82
|
+
base_url = os.getenv("NARADA_API_BASE_URL", "https://api.narada.ai/fast/v2")
|
|
83
|
+
url = f"{base_url}/sdk/config"
|
|
84
|
+
|
|
85
|
+
try:
|
|
86
|
+
async with aiohttp.ClientSession() as session:
|
|
87
|
+
async with session.get(
|
|
88
|
+
url, headers={"x-api-key": self._api_key}
|
|
89
|
+
) as resp:
|
|
90
|
+
if not resp.ok:
|
|
91
|
+
logging.warning(
|
|
92
|
+
"Failed to fetch SDK config: %s %s",
|
|
93
|
+
resp.status,
|
|
94
|
+
await resp.text(),
|
|
95
|
+
)
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
return _SdkConfig.model_validate(await resp.json())
|
|
99
|
+
except Exception as e:
|
|
100
|
+
logging.warning("Failed to fetch SDK config: %s", e)
|
|
101
|
+
return None
|
|
102
|
+
|
|
103
|
+
async def _validate_sdk_config(self) -> None:
|
|
104
|
+
config = await self._fetch_sdk_config()
|
|
105
|
+
if config is None:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
package_config = config.packages["narada"]
|
|
109
|
+
if semver.compare(__version__, package_config.min_required_version) < 0:
|
|
110
|
+
raise RuntimeError(
|
|
111
|
+
f"narada<={__version__} is not supported. Please upgrade to version "
|
|
112
|
+
f"{package_config.min_required_version} or higher."
|
|
113
|
+
)
|
|
114
|
+
|
|
73
115
|
async def open_and_initialize_browser_window(
|
|
74
116
|
self, config: BrowserConfig | None = None
|
|
75
117
|
) -> LocalBrowserWindow:
|
|
@@ -106,6 +148,13 @@ class Narada:
|
|
|
106
148
|
|
|
107
149
|
config = config or BrowserConfig()
|
|
108
150
|
|
|
151
|
+
if config.proxy is not None:
|
|
152
|
+
raise ValueError(
|
|
153
|
+
"Proxy configuration is not supported for `initialize_in_existing_browser_window`. "
|
|
154
|
+
"Proxy settings must be specified when launching Chrome. "
|
|
155
|
+
"Use `open_and_initialize_browser_window` instead."
|
|
156
|
+
)
|
|
157
|
+
|
|
109
158
|
browser = await playwright.chromium.connect_over_cdp(config.cdp_url)
|
|
110
159
|
|
|
111
160
|
# Generate a unique tag for the initialization URL
|
|
@@ -153,6 +202,13 @@ class Narada:
|
|
|
153
202
|
window_tag = uuid4().hex
|
|
154
203
|
tagged_initialization_url = f"{config.initialization_url}?t={window_tag}"
|
|
155
204
|
|
|
205
|
+
# When proxy auth is needed, launch with about:blank to avoid Chrome's startup auth prompt.
|
|
206
|
+
# We'll set up the CDP auth handler and then navigate to the init URL.
|
|
207
|
+
proxy_requires_auth = (
|
|
208
|
+
config.proxy is not None and config.proxy.requires_authentication
|
|
209
|
+
)
|
|
210
|
+
launch_url = "about:blank" if proxy_requires_auth else tagged_initialization_url
|
|
211
|
+
|
|
156
212
|
browser_args = [
|
|
157
213
|
f"--user-data-dir={config.user_data_dir}",
|
|
158
214
|
f"--profile-directory={config.profile_directory}",
|
|
@@ -160,11 +216,20 @@ class Narada:
|
|
|
160
216
|
"--no-default-browser-check",
|
|
161
217
|
"--no-first-run",
|
|
162
218
|
"--new-window",
|
|
163
|
-
|
|
164
|
-
# TODO: This is needed if we don't use CDP but let Playwright manage the browser.
|
|
165
|
-
# "--disable-blink-features=AutomationControlled",
|
|
219
|
+
launch_url,
|
|
166
220
|
]
|
|
167
221
|
|
|
222
|
+
# Add proxy arguments if configured.
|
|
223
|
+
if config.proxy is not None:
|
|
224
|
+
config.proxy.validate()
|
|
225
|
+
browser_args.append(f"--proxy-server={config.proxy.server}")
|
|
226
|
+
|
|
227
|
+
if config.proxy.bypass:
|
|
228
|
+
browser_args.append(f"--proxy-bypass-list={config.proxy.bypass}")
|
|
229
|
+
|
|
230
|
+
if config.proxy.ignore_cert_errors:
|
|
231
|
+
browser_args.append("--ignore-certificate-errors")
|
|
232
|
+
|
|
168
233
|
# Launch an independent browser process which will not be killed when the current program
|
|
169
234
|
# exits.
|
|
170
235
|
if sys.platform == "win32":
|
|
@@ -195,6 +260,14 @@ class Narada:
|
|
|
195
260
|
browser_window_id = None
|
|
196
261
|
side_panel_page = None
|
|
197
262
|
max_cdp_connect_attempts = 10
|
|
263
|
+
|
|
264
|
+
# Track whether we've already navigated from about:blank to the initialization URL.
|
|
265
|
+
# This is only relevant when proxy auth is enabled, where we launch with about:blank
|
|
266
|
+
# to set up CDP auth handlers before any network traffic. We must only navigate once,
|
|
267
|
+
# because on retry iterations context.pages[0] could be any page (side panel, devtools,
|
|
268
|
+
# etc.) and navigating it would break the initialization flow.
|
|
269
|
+
did_initial_navigation = False
|
|
270
|
+
|
|
198
271
|
for attempt in range(max_cdp_connect_attempts):
|
|
199
272
|
try:
|
|
200
273
|
browser = await playwright.chromium.connect_over_cdp(config.cdp_url)
|
|
@@ -206,8 +279,23 @@ class Narada:
|
|
|
206
279
|
await asyncio.sleep(2)
|
|
207
280
|
continue
|
|
208
281
|
|
|
209
|
-
# Grab the browser window ID from the page we just opened.
|
|
210
282
|
context = browser.contexts[0]
|
|
283
|
+
|
|
284
|
+
# If proxy auth is needed, set up the handler at browser level then navigate to the
|
|
285
|
+
# initialization page. After navigation succeeds, Chrome has cached the proxy
|
|
286
|
+
# credentials, so we can detach the CDP session.
|
|
287
|
+
if proxy_requires_auth and not did_initial_navigation:
|
|
288
|
+
proxy_cdp_session = (
|
|
289
|
+
await self._setup_proxy_authentication_browser_level(
|
|
290
|
+
browser, config.proxy
|
|
291
|
+
)
|
|
292
|
+
)
|
|
293
|
+
blank_page = context.pages[0]
|
|
294
|
+
await blank_page.goto(tagged_initialization_url)
|
|
295
|
+
await proxy_cdp_session.detach()
|
|
296
|
+
did_initial_navigation = True
|
|
297
|
+
|
|
298
|
+
# Grab the browser window ID from the page we just opened.
|
|
211
299
|
initialization_page = next(
|
|
212
300
|
(p for p in context.pages if p.url == tagged_initialization_url), None
|
|
213
301
|
)
|
|
@@ -375,6 +463,73 @@ class Narada:
|
|
|
375
463
|
initialization_page
|
|
376
464
|
)
|
|
377
465
|
|
|
466
|
+
async def _setup_proxy_authentication_browser_level(
|
|
467
|
+
self, browser: Browser, proxy_config: ProxyConfig
|
|
468
|
+
) -> CDPSession:
|
|
469
|
+
"""Sets up proxy authentication handling via CDP at the browser level.
|
|
470
|
+
|
|
471
|
+
This uses a browser-level CDP session which can intercept auth challenges before they reach
|
|
472
|
+
individual pages, preventing Chrome from showing the proxy authentication dialog.
|
|
473
|
+
|
|
474
|
+
Chrome caches proxy credentials for the session after the first successful authentication.
|
|
475
|
+
The caller should detach the returned CDP session after the first navigation succeeds.
|
|
476
|
+
"""
|
|
477
|
+
cdp_session = await browser.new_browser_cdp_session()
|
|
478
|
+
|
|
479
|
+
# Enable Fetch domain with a catch-all pattern to intercept auth challenges.
|
|
480
|
+
await cdp_session.send(
|
|
481
|
+
"Fetch.enable",
|
|
482
|
+
{
|
|
483
|
+
"handleAuthRequests": True,
|
|
484
|
+
"patterns": [{"urlPattern": "*"}],
|
|
485
|
+
},
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
async def handle_auth(params: dict[str, Any]) -> None:
|
|
489
|
+
request_id = params.get("requestId")
|
|
490
|
+
auth_challenge = params.get("authChallenge", {})
|
|
491
|
+
|
|
492
|
+
# Only handle proxy auth challenges
|
|
493
|
+
if auth_challenge.get("source") != "Proxy":
|
|
494
|
+
return
|
|
495
|
+
|
|
496
|
+
try:
|
|
497
|
+
await cdp_session.send(
|
|
498
|
+
"Fetch.continueWithAuth",
|
|
499
|
+
{
|
|
500
|
+
"requestId": request_id,
|
|
501
|
+
"authChallengeResponse": {
|
|
502
|
+
"response": "ProvideCredentials",
|
|
503
|
+
"username": proxy_config.username,
|
|
504
|
+
"password": proxy_config.password,
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
)
|
|
508
|
+
logging.debug("Browser-level proxy authentication credentials provided")
|
|
509
|
+
except Exception as e:
|
|
510
|
+
logging.error("Failed to respond to proxy auth challenge: %s", e)
|
|
511
|
+
|
|
512
|
+
async def handle_request_paused(params: dict[str, Any]) -> None:
|
|
513
|
+
# Continue all paused requests immediately
|
|
514
|
+
request_id = params.get("requestId")
|
|
515
|
+
try:
|
|
516
|
+
await cdp_session.send(
|
|
517
|
+
"Fetch.continueRequest", {"requestId": request_id}
|
|
518
|
+
)
|
|
519
|
+
except Exception:
|
|
520
|
+
pass
|
|
521
|
+
|
|
522
|
+
cdp_session.on(
|
|
523
|
+
"Fetch.authRequired",
|
|
524
|
+
lambda params: asyncio.create_task(handle_auth(params)),
|
|
525
|
+
)
|
|
526
|
+
cdp_session.on(
|
|
527
|
+
"Fetch.requestPaused",
|
|
528
|
+
lambda params: asyncio.create_task(handle_request_paused(params)),
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
return cdp_session
|
|
532
|
+
|
|
378
533
|
async def _fix_download_behavior(self, side_panel_page: Page) -> None:
|
|
379
534
|
"""Reverts the download behavior to the default behavior for the extension, otherwise our
|
|
380
535
|
extension cannot download files.
|
narada/config.py
CHANGED
|
@@ -22,6 +22,50 @@ def _default_user_data_dir() -> str:
|
|
|
22
22
|
return str(Path("~/.config/narada/user-data-dirs/default").expanduser())
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
@dataclass
|
|
26
|
+
class ProxyConfig:
|
|
27
|
+
"""Configuration for HTTP/HTTPS/SOCKS5 proxy.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
server: Proxy server URL. HTTP and SOCKS proxies are supported, for example
|
|
31
|
+
"http://myproxy.com:3128" or "socks5://myproxy.com:3128".
|
|
32
|
+
Short form "myproxy.com:3128" is considered an HTTP proxy.
|
|
33
|
+
username: Optional username for proxy authentication.
|
|
34
|
+
password: Optional password for proxy authentication.
|
|
35
|
+
bypass: Optional comma-separated domains to bypass proxy,
|
|
36
|
+
for example ".com, chromium.org, .domain.com".
|
|
37
|
+
ignore_cert_errors: If True, ignore SSL certificate errors. Required for proxies that
|
|
38
|
+
perform HTTPS inspection (MITM). Use with caution.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
server: str
|
|
42
|
+
username: str | None = None
|
|
43
|
+
password: str | None = None
|
|
44
|
+
bypass: str | None = None
|
|
45
|
+
ignore_cert_errors: bool = False
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def requires_authentication(self) -> bool:
|
|
49
|
+
"""Returns True if proxy requires authentication."""
|
|
50
|
+
return self.username is not None and self.password is not None
|
|
51
|
+
|
|
52
|
+
def validate(self) -> None:
|
|
53
|
+
"""Validates the proxy configuration.
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
ValueError: If configuration is invalid.
|
|
57
|
+
"""
|
|
58
|
+
if not self.server:
|
|
59
|
+
raise ValueError("Proxy server cannot be empty")
|
|
60
|
+
|
|
61
|
+
# Validate that if one credential is provided, both are provided
|
|
62
|
+
if (self.username is None) != (self.password is None):
|
|
63
|
+
raise ValueError(
|
|
64
|
+
"Both username and password must be provided for proxy authentication, "
|
|
65
|
+
"or neither should be provided"
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
25
69
|
@dataclass
|
|
26
70
|
class BrowserConfig:
|
|
27
71
|
executable_path: str = field(default_factory=_default_executable_path)
|
|
@@ -32,6 +76,7 @@ class BrowserConfig:
|
|
|
32
76
|
initialization_url: str = "https://app.narada.ai/initialize"
|
|
33
77
|
extension_id: str = "bhioaidlggjdkheaajakomifblpjmokn"
|
|
34
78
|
interactive: bool = True
|
|
79
|
+
proxy: ProxyConfig | None = None
|
|
35
80
|
|
|
36
81
|
@property
|
|
37
82
|
def cdp_url(self) -> str:
|
narada/version.py
ADDED
narada/window.py
CHANGED
|
@@ -4,12 +4,15 @@ import time
|
|
|
4
4
|
from abc import ABC
|
|
5
5
|
from http import HTTPStatus
|
|
6
6
|
from pathlib import Path
|
|
7
|
-
from typing import IO, Any, TypeVar, overload
|
|
7
|
+
from typing import IO, Any, Optional, TypeVar, overload
|
|
8
8
|
|
|
9
9
|
import aiohttp
|
|
10
10
|
from narada_core.actions.models import (
|
|
11
11
|
AgenticSelectorAction,
|
|
12
12
|
AgenticSelectorRequest,
|
|
13
|
+
AgenticSelectorResponse,
|
|
14
|
+
AgenticMouseActionRequest,
|
|
15
|
+
AgenticMouseAction,
|
|
13
16
|
AgenticSelectors,
|
|
14
17
|
AgentResponse,
|
|
15
18
|
AgentUsage,
|
|
@@ -21,6 +24,7 @@ from narada_core.actions.models import (
|
|
|
21
24
|
ReadGoogleSheetRequest,
|
|
22
25
|
ReadGoogleSheetResponse,
|
|
23
26
|
WriteGoogleSheetRequest,
|
|
27
|
+
RecordedClick,
|
|
24
28
|
)
|
|
25
29
|
from narada_core.errors import (
|
|
26
30
|
NaradaAgentTimeoutError_INTERNAL_DO_NOT_USE,
|
|
@@ -325,14 +329,47 @@ class BaseBrowserWindow(ABC):
|
|
|
325
329
|
fallback_operator_query: str,
|
|
326
330
|
# Larger default timeout because Operator can take a bit to run.
|
|
327
331
|
timeout: int | None = 60,
|
|
328
|
-
) ->
|
|
332
|
+
) -> AgenticSelectorResponse:
|
|
329
333
|
"""Performs an action on an element specified by the given selectors, falling back to using
|
|
330
334
|
the Operator agent if the selectors fail to match a unique element.
|
|
331
335
|
"""
|
|
332
|
-
|
|
336
|
+
response_model = (
|
|
337
|
+
AgenticSelectorResponse
|
|
338
|
+
if action["type"] in {"get_text", "get_property"}
|
|
339
|
+
else None
|
|
340
|
+
)
|
|
341
|
+
result = await self._run_extension_action(
|
|
333
342
|
AgenticSelectorRequest(
|
|
334
343
|
action=action,
|
|
335
344
|
selectors=selectors,
|
|
345
|
+
response_model=response_model,
|
|
346
|
+
fallback_operator_query=fallback_operator_query,
|
|
347
|
+
),
|
|
348
|
+
timeout=timeout,
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
if result is None:
|
|
352
|
+
return {"value": None}
|
|
353
|
+
|
|
354
|
+
return result
|
|
355
|
+
|
|
356
|
+
async def agentic_mouse_action(
|
|
357
|
+
self,
|
|
358
|
+
*,
|
|
359
|
+
action: AgenticMouseAction,
|
|
360
|
+
recorded_click: RecordedClick,
|
|
361
|
+
fallback_operator_query: str,
|
|
362
|
+
resize_window: bool = True,
|
|
363
|
+
timeout: int | None = 60,
|
|
364
|
+
) -> None:
|
|
365
|
+
"""Performs a mouse action at the specified click coordinates, falling back to using
|
|
366
|
+
the Operator agent if the click fails.
|
|
367
|
+
"""
|
|
368
|
+
return await self._run_extension_action(
|
|
369
|
+
AgenticMouseActionRequest(
|
|
370
|
+
action=action,
|
|
371
|
+
recorded_click=recorded_click,
|
|
372
|
+
resize_window=resize_window,
|
|
336
373
|
fallback_operator_query=fallback_operator_query,
|
|
337
374
|
),
|
|
338
375
|
timeout=timeout,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: narada
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.29
|
|
4
4
|
Summary: Python client SDK for Narada
|
|
5
5
|
Project-URL: Homepage, https://github.com/NaradaAI/narada-python-sdk/narada
|
|
6
6
|
Project-URL: Repository, https://github.com/NaradaAI/narada-python-sdk
|
|
@@ -9,9 +9,10 @@ Author-email: Narada <support@narada.ai>
|
|
|
9
9
|
License-Expression: Apache-2.0
|
|
10
10
|
Requires-Python: >=3.12
|
|
11
11
|
Requires-Dist: aiohttp>=3.12.13
|
|
12
|
-
Requires-Dist: narada-core==0.0.
|
|
12
|
+
Requires-Dist: narada-core==0.0.7
|
|
13
13
|
Requires-Dist: playwright>=1.53.0
|
|
14
14
|
Requires-Dist: rich>=14.0.0
|
|
15
|
+
Requires-Dist: semver>=3.0.4
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
|
|
17
18
|
<p align="center">
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
narada/__init__.py,sha256=2AnpZVQpkh4-onnvbgHX1Wlq1Fa8ya4vAzy2fbS0bCY,968
|
|
2
|
+
narada/client.py,sha256=_nTRHkiYt7lmUgO0HibVagrPXf523LCbWx8JmuVGgoo,21622
|
|
3
|
+
narada/config.py,sha256=S0B8GNd-0td_69oKaPN60WAq_ODeYU7avE_KAxN5vCg,3052
|
|
4
|
+
narada/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
narada/utils.py,sha256=gdLwNMXPpRohDcIIe0cB3KhvZ8X1QfAlKVh1sXWeJmk,1284
|
|
6
|
+
narada/version.py,sha256=kwW6yy0_4Pf3kt888eeCG0VwBb2L2rCkrkpdZEC_3rA,193
|
|
7
|
+
narada/window.py,sha256=hWkL637Aj7ZoJNFkvg92lSHRBaK72FY2WcCoyxzXKyk,18928
|
|
8
|
+
narada-0.1.29.dist-info/METADATA,sha256=ZRJ6lY2UYwj-kMsCOL5peS1mjni9Kr0Y5ROOfK4uBPE,5146
|
|
9
|
+
narada-0.1.29.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
10
|
+
narada-0.1.29.dist-info/RECORD,,
|
narada-0.1.27.dist-info/RECORD
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
narada/__init__.py,sha256=TFEfS_6cCaD2mLUDBTUOUar3wJqjz1wyyd7sx43MYUk,1127
|
|
2
|
-
narada/client.py,sha256=jksBBWdSJP88fP1DpLYoyJNNdgkUMrEKlcuYenARzaI,15265
|
|
3
|
-
narada/config.py,sha256=tvj16P_qAHOFB2O_VlrcizA8SrbmS_Nmkm2r7ltG-VM,1345
|
|
4
|
-
narada/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
narada/utils.py,sha256=gdLwNMXPpRohDcIIe0cB3KhvZ8X1QfAlKVh1sXWeJmk,1284
|
|
6
|
-
narada/window.py,sha256=OGuEs778xjxHivzWlJIIHKZPqGd4Sn9_YVArt_8TYoU,17762
|
|
7
|
-
narada-0.1.27.dist-info/METADATA,sha256=_qPEc-3NJIO04Jt9sGUSLeoFTG-ZeoQxJ-5iKhwaPP4,5117
|
|
8
|
-
narada-0.1.27.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
9
|
-
narada-0.1.27.dist-info/RECORD,,
|