open-edison 0.1.43__py3-none-any.whl → 0.1.64__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.
- {open_edison-0.1.43.dist-info → open_edison-0.1.64.dist-info}/METADATA +3 -46
- open_edison-0.1.64.dist-info/RECORD +40 -0
- src/cli.py +25 -109
- src/config.py +26 -9
- src/mcp_importer/__main__.py +0 -2
- src/mcp_importer/api.py +254 -44
- src/mcp_importer/export_cli.py +2 -2
- src/mcp_importer/import_api.py +0 -2
- src/mcp_importer/parsers.py +47 -9
- src/mcp_importer/quick_cli.py +0 -2
- src/mcp_importer/types.py +0 -2
- src/oauth_manager.py +3 -1
- src/oauth_override.py +10 -0
- src/permissions.py +24 -1
- src/server.py +30 -10
- src/setup_tui/__init__.py +5 -0
- src/setup_tui/main.py +294 -0
- src/single_user_mcp.py +7 -3
- src/tools/io.py +35 -0
- src/vulture_whitelist.py +3 -0
- open_edison-0.1.43.dist-info/RECORD +0 -35
- {open_edison-0.1.43.dist-info → open_edison-0.1.64.dist-info}/WHEEL +0 -0
- {open_edison-0.1.43.dist-info → open_edison-0.1.64.dist-info}/entry_points.txt +0 -0
- {open_edison-0.1.43.dist-info → open_edison-0.1.64.dist-info}/licenses/LICENSE +0 -0
src/setup_tui/main.py
ADDED
@@ -0,0 +1,294 @@
|
|
1
|
+
import argparse
|
2
|
+
import asyncio
|
3
|
+
import contextlib
|
4
|
+
import sys
|
5
|
+
from collections.abc import Generator
|
6
|
+
|
7
|
+
import questionary
|
8
|
+
from loguru import logger as log
|
9
|
+
|
10
|
+
import src.oauth_manager as oauth_mod
|
11
|
+
from src.config import MCPServerConfig, get_config_dir
|
12
|
+
from src.mcp_importer.api import (
|
13
|
+
CLIENT,
|
14
|
+
authorize_server_oauth,
|
15
|
+
detect_clients,
|
16
|
+
export_edison_to,
|
17
|
+
has_oauth_tokens,
|
18
|
+
import_from,
|
19
|
+
save_imported_servers,
|
20
|
+
verify_mcp_server,
|
21
|
+
)
|
22
|
+
from src.mcp_importer.parsers import deduplicate_by_name
|
23
|
+
from src.oauth_manager import OAuthStatus, get_oauth_manager
|
24
|
+
|
25
|
+
|
26
|
+
def show_welcome_screen(*, dry_run: bool = False) -> None:
|
27
|
+
"""Display the welcome screen for open-edison setup."""
|
28
|
+
welcome_text = """
|
29
|
+
╔══════════════════════════════════════════════════════════════╗
|
30
|
+
║ ║
|
31
|
+
║ Welcome to open-edison ║
|
32
|
+
║ ║
|
33
|
+
║ This setup wizard will help you configure open-edison ║
|
34
|
+
║ for your development environment. ║
|
35
|
+
║ ║
|
36
|
+
╚══════════════════════════════════════════════════════════════╝
|
37
|
+
"""
|
38
|
+
|
39
|
+
print(welcome_text)
|
40
|
+
|
41
|
+
# Prompt to continue
|
42
|
+
questionary.confirm("Ready to begin the setup process?", default=True).ask()
|
43
|
+
|
44
|
+
|
45
|
+
def handle_mcp_source( # noqa: C901
|
46
|
+
source: CLIENT, *, dry_run: bool = False, skip_oauth: bool = False
|
47
|
+
) -> list[MCPServerConfig]:
|
48
|
+
"""Handle the MCP source."""
|
49
|
+
if not questionary.confirm(
|
50
|
+
f"We have found {source.name} installed. Would you like to import its MCP servers to open-edison?",
|
51
|
+
default=True,
|
52
|
+
).ask():
|
53
|
+
return []
|
54
|
+
|
55
|
+
configs = import_from(source)
|
56
|
+
|
57
|
+
# Filter out any "open-edison" configs
|
58
|
+
if "open-edison" in [config.name for config in configs]:
|
59
|
+
print(
|
60
|
+
"Found an 'open-edison' config. This is not allowed, so it will be excluded from the import."
|
61
|
+
)
|
62
|
+
|
63
|
+
configs = [config for config in configs if config.name != "open-edison"]
|
64
|
+
|
65
|
+
print(f"Loaded {len(configs)} MCP server configuration from {source.name}!")
|
66
|
+
|
67
|
+
verified_configs: list[MCPServerConfig] = []
|
68
|
+
|
69
|
+
for config in configs:
|
70
|
+
print(f"Verifying the configuration for {config.name}... ")
|
71
|
+
result = verify_mcp_server(config)
|
72
|
+
if result:
|
73
|
+
# For remote servers, only prompt if OAuth is actually required
|
74
|
+
if config.is_remote_server():
|
75
|
+
# Heuristic: if inline headers are present (e.g., API key), treat as not requiring OAuth
|
76
|
+
has_inline_headers: bool = any(
|
77
|
+
(a == "--header" or a.startswith("--header")) for a in config.args
|
78
|
+
)
|
79
|
+
if not has_inline_headers:
|
80
|
+
# Prefer cached result from verification; only check if missing
|
81
|
+
oauth_mgr = get_oauth_manager()
|
82
|
+
info = oauth_mgr.get_server_info(config.name)
|
83
|
+
if info is None:
|
84
|
+
info = asyncio.run(
|
85
|
+
oauth_mgr.check_oauth_requirement(config.name, config.get_remote_url())
|
86
|
+
)
|
87
|
+
|
88
|
+
if info.status == OAuthStatus.NEEDS_AUTH:
|
89
|
+
tokens_present: bool = has_oauth_tokens(config)
|
90
|
+
if not tokens_present:
|
91
|
+
if skip_oauth:
|
92
|
+
print(
|
93
|
+
f"Skipping OAuth for {config.name} due to --skip-oauth (OAuth required, no tokens). This server will not be imported."
|
94
|
+
)
|
95
|
+
continue
|
96
|
+
|
97
|
+
if questionary.confirm(
|
98
|
+
f"{config.name} requires OAuth and no credentials were found. Obtain credentials now?",
|
99
|
+
default=True,
|
100
|
+
).ask():
|
101
|
+
success = authorize_server_oauth(config)
|
102
|
+
if not success:
|
103
|
+
print(
|
104
|
+
f"Failed to obtain OAuth credentials for {config.name}. Skipping this server."
|
105
|
+
)
|
106
|
+
continue
|
107
|
+
else:
|
108
|
+
print(f"Skipping {config.name} per user choice.")
|
109
|
+
continue
|
110
|
+
|
111
|
+
print(f"Verification successful for {config.name}.")
|
112
|
+
verified_configs.append(config)
|
113
|
+
else:
|
114
|
+
print(
|
115
|
+
f"Verification failed for the configuration of {config.name}. Please check the configuration and try again."
|
116
|
+
)
|
117
|
+
|
118
|
+
return verified_configs
|
119
|
+
|
120
|
+
|
121
|
+
def confirm_configs(configs: list[MCPServerConfig], *, dry_run: bool = False) -> bool:
|
122
|
+
"""Confirm the MCP configs."""
|
123
|
+
print("These are the servers you have selected:")
|
124
|
+
|
125
|
+
for config in configs:
|
126
|
+
print(f"○ {config.name}")
|
127
|
+
|
128
|
+
return questionary.confirm(
|
129
|
+
"Are you sure you want to use these servers with open-edison?", default=True
|
130
|
+
).ask()
|
131
|
+
|
132
|
+
|
133
|
+
def confirm_apply_configs(client: CLIENT, *, dry_run: bool = False) -> None:
|
134
|
+
if not questionary.confirm(
|
135
|
+
f"We have detected that you have {client.name} installed. Would you like to connect it to open-edison?",
|
136
|
+
default=True,
|
137
|
+
).ask():
|
138
|
+
return
|
139
|
+
|
140
|
+
export_edison_to(client, dry_run=dry_run)
|
141
|
+
if dry_run:
|
142
|
+
print(f"[dry-run] Export prepared for {client.name}; no changes written.")
|
143
|
+
else:
|
144
|
+
print(f"Successfully set up Open Edison for {client.name}!")
|
145
|
+
|
146
|
+
|
147
|
+
def show_manual_setup_screen() -> None:
|
148
|
+
"""Display manual setup instructions for open-edison."""
|
149
|
+
manual_setup_text = """
|
150
|
+
╔══════════════════════════════════════════════════════════════╗
|
151
|
+
║ ║
|
152
|
+
║ Manual Setup Instructions ║
|
153
|
+
║ ║
|
154
|
+
╚══════════════════════════════════════════════════════════════╝
|
155
|
+
|
156
|
+
To set up open-edison manually in other clients, find your client's MCP config
|
157
|
+
JSON file and add the following configuration:
|
158
|
+
"""
|
159
|
+
|
160
|
+
json_snippet = """\t{
|
161
|
+
"mcpServers": {
|
162
|
+
"open-edison": {
|
163
|
+
"command": "npx",
|
164
|
+
"args": [
|
165
|
+
"-y",
|
166
|
+
"mcp-remote",
|
167
|
+
"http://localhost:3000/mcp/",
|
168
|
+
"--http-only",
|
169
|
+
"--header",
|
170
|
+
"Authorization: Bearer dev-api-key-change-me"
|
171
|
+
]
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}"""
|
175
|
+
|
176
|
+
after_text = """
|
177
|
+
Make sure to replace 'dev-api-key-change-me' with your actual API key.
|
178
|
+
"""
|
179
|
+
|
180
|
+
print(manual_setup_text)
|
181
|
+
# Use questionary's print with style for color
|
182
|
+
questionary.print(json_snippet, style="bold fg:ansigreen")
|
183
|
+
print(after_text)
|
184
|
+
|
185
|
+
|
186
|
+
class _TuiLogger:
|
187
|
+
def _fmt(self, msg: object, *args: object) -> str:
|
188
|
+
try:
|
189
|
+
if isinstance(msg, str) and args:
|
190
|
+
return msg.format(*args)
|
191
|
+
except Exception:
|
192
|
+
pass
|
193
|
+
return str(msg)
|
194
|
+
|
195
|
+
def info(self, msg: object, *args: object, **kwargs: object) -> None:
|
196
|
+
questionary.print(self._fmt(msg, *args), style="fg:ansiblue")
|
197
|
+
|
198
|
+
def debug(self, msg: object, *args: object, **kwargs: object) -> None:
|
199
|
+
questionary.print(self._fmt(msg, *args), style="fg:ansiblack")
|
200
|
+
|
201
|
+
def warning(self, msg: object, *args: object, **kwargs: object) -> None:
|
202
|
+
questionary.print(self._fmt(msg, *args), style="bold fg:ansiyellow")
|
203
|
+
|
204
|
+
def error(self, msg: object, *args: object, **kwargs: object) -> None:
|
205
|
+
questionary.print(self._fmt(msg, *args), style="bold fg:ansired")
|
206
|
+
|
207
|
+
|
208
|
+
@contextlib.contextmanager
|
209
|
+
def suppress_loguru_output() -> Generator[None, None, None]:
|
210
|
+
"""Suppress loguru output."""
|
211
|
+
with contextlib.suppress(Exception):
|
212
|
+
log.remove()
|
213
|
+
|
214
|
+
old_logger = oauth_mod.log
|
215
|
+
# Route oauth_manager's log calls to questionary for TUI output
|
216
|
+
oauth_mod.log = _TuiLogger() # type: ignore[attr-defined]
|
217
|
+
yield
|
218
|
+
oauth_mod.log = old_logger
|
219
|
+
log.add(sys.stdout, level="INFO")
|
220
|
+
|
221
|
+
|
222
|
+
@suppress_loguru_output()
|
223
|
+
def run(*, dry_run: bool = False, skip_oauth: bool = False) -> bool: # noqa: C901
|
224
|
+
"""Run the complete setup process.
|
225
|
+
Returns whether the setup was successful."""
|
226
|
+
show_welcome_screen(dry_run=dry_run)
|
227
|
+
# Additional setup steps will be added here
|
228
|
+
|
229
|
+
mcp_clients = sorted(detect_clients(), key=lambda x: x.value)
|
230
|
+
|
231
|
+
configs: list[MCPServerConfig] = []
|
232
|
+
|
233
|
+
for client in mcp_clients:
|
234
|
+
configs.extend(handle_mcp_source(client, dry_run=dry_run, skip_oauth=skip_oauth))
|
235
|
+
|
236
|
+
if len(configs) == 0:
|
237
|
+
if not questionary.confirm(
|
238
|
+
"No MCP servers found. Would you like to continue without them?", default=False
|
239
|
+
).ask():
|
240
|
+
print("Setup aborted. Please configure an MCP client and try again.")
|
241
|
+
return False
|
242
|
+
return True
|
243
|
+
|
244
|
+
# Deduplicate configs
|
245
|
+
configs = deduplicate_by_name(configs)
|
246
|
+
|
247
|
+
if not confirm_configs(configs, dry_run=dry_run):
|
248
|
+
return False
|
249
|
+
|
250
|
+
for client in mcp_clients:
|
251
|
+
confirm_apply_configs(client, dry_run=dry_run)
|
252
|
+
|
253
|
+
# Persist imported servers into config.json
|
254
|
+
if len(configs) > 0:
|
255
|
+
save_imported_servers(configs, dry_run=dry_run)
|
256
|
+
|
257
|
+
show_manual_setup_screen()
|
258
|
+
|
259
|
+
return True
|
260
|
+
|
261
|
+
|
262
|
+
# Triggered from cli.py
|
263
|
+
def run_import_tui(args: argparse.Namespace, force: bool = False) -> bool:
|
264
|
+
"""Run the import TUI, if necessary."""
|
265
|
+
# Find config dir, check if ".setup_tui_ran" exists
|
266
|
+
config_dir = get_config_dir()
|
267
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
268
|
+
|
269
|
+
setup_tui_ran_file = config_dir / ".setup_tui_ran"
|
270
|
+
success = True
|
271
|
+
if not setup_tui_ran_file.exists() or force:
|
272
|
+
success = run(dry_run=args.wizard_dry_run, skip_oauth=args.wizard_skip_oauth)
|
273
|
+
|
274
|
+
setup_tui_ran_file.touch()
|
275
|
+
|
276
|
+
return success
|
277
|
+
|
278
|
+
|
279
|
+
def main(argv: list[str] | None = None) -> int:
|
280
|
+
parser = argparse.ArgumentParser(description="Open Edison Setup TUI")
|
281
|
+
parser.add_argument("--dry-run", action="store_true", help="Preview actions without writing")
|
282
|
+
parser.add_argument(
|
283
|
+
"--skip-oauth",
|
284
|
+
action="store_true",
|
285
|
+
help="Skip OAuth for remote servers (they will be omitted from import)",
|
286
|
+
)
|
287
|
+
args = parser.parse_args(argv)
|
288
|
+
|
289
|
+
run(dry_run=args.dry_run, skip_oauth=args.skip_oauth)
|
290
|
+
return 0
|
291
|
+
|
292
|
+
|
293
|
+
if __name__ == "__main__":
|
294
|
+
raise SystemExit(main())
|
src/single_user_mcp.py
CHANGED
@@ -9,6 +9,7 @@ from typing import Any, TypedDict
|
|
9
9
|
|
10
10
|
from fastmcp import Client as FastMCPClient
|
11
11
|
from fastmcp import Context, FastMCP
|
12
|
+
from fastmcp.server.dependencies import get_context
|
12
13
|
from loguru import logger as log
|
13
14
|
|
14
15
|
from src.config import Config, MCPServerConfig
|
@@ -281,9 +282,6 @@ class SingleUserMCP(FastMCP[Any]):
|
|
281
282
|
async def _send_list_changed_notifications(self) -> None:
|
282
283
|
"""Send notifications to clients about changed component lists."""
|
283
284
|
try:
|
284
|
-
# Import here to avoid circular imports
|
285
|
-
from fastmcp.server.dependencies import get_context
|
286
|
-
|
287
285
|
try:
|
288
286
|
context = get_context()
|
289
287
|
# Queue notifications for all component types since we don't know
|
@@ -323,12 +321,18 @@ class SingleUserMCP(FastMCP[Any]):
|
|
323
321
|
log.info("✅ Single User MCP server initialized with composite proxy")
|
324
322
|
|
325
323
|
# Invalidate and warm lists to ensure reload
|
324
|
+
log.debug("Reloading tool list...")
|
326
325
|
_ = await self._tool_manager.list_tools()
|
326
|
+
log.debug("Reloading resource list...")
|
327
327
|
_ = await self._resource_manager.list_resources()
|
328
|
+
log.debug("Reloading prompt list...")
|
328
329
|
_ = await self._prompt_manager.list_prompts()
|
330
|
+
log.debug("Reloading complete")
|
329
331
|
|
330
332
|
# Send notifications to clients about changed component lists
|
333
|
+
log.debug("Sending list changed notifications...")
|
331
334
|
await self._send_list_changed_notifications()
|
335
|
+
log.debug("List changed notifications sent")
|
332
336
|
|
333
337
|
def _calculate_risk_level(self, trifecta: dict[str, bool]) -> str:
|
334
338
|
"""
|
src/tools/io.py
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
import os
|
2
|
+
from collections.abc import Iterator
|
3
|
+
from contextlib import contextmanager
|
4
|
+
|
5
|
+
|
6
|
+
@contextmanager
|
7
|
+
def suppress_fds(*, suppress_stdout: bool = False, suppress_stderr: bool = True) -> Iterator[None]:
|
8
|
+
"""Temporarily redirect process-level stdout/stderr to os.devnull.
|
9
|
+
|
10
|
+
Args:
|
11
|
+
suppress_stdout: If True, redirect fd 1 to devnull
|
12
|
+
suppress_stderr: If True, redirect fd 2 to devnull
|
13
|
+
|
14
|
+
Yields:
|
15
|
+
None
|
16
|
+
"""
|
17
|
+
saved: list[tuple[int, int]] = []
|
18
|
+
try:
|
19
|
+
if suppress_stdout:
|
20
|
+
saved.append((1, os.dup(1)))
|
21
|
+
devnull_out = os.open(os.devnull, os.O_WRONLY)
|
22
|
+
os.dup2(devnull_out, 1)
|
23
|
+
os.close(devnull_out)
|
24
|
+
if suppress_stderr:
|
25
|
+
saved.append((2, os.dup(2)))
|
26
|
+
devnull_err = os.open(os.devnull, os.O_WRONLY)
|
27
|
+
os.dup2(devnull_err, 2)
|
28
|
+
os.close(devnull_err)
|
29
|
+
yield
|
30
|
+
finally:
|
31
|
+
for fd, backup in saved:
|
32
|
+
try:
|
33
|
+
os.dup2(backup, fd)
|
34
|
+
finally:
|
35
|
+
os.close(backup)
|
src/vulture_whitelist.py
ADDED
@@ -1,35 +0,0 @@
|
|
1
|
-
src/__init__.py,sha256=bEYMwBiuW9jzF07iWhas4Vb30EcpnqfpNfz_Q6yO1jU,209
|
2
|
-
src/__main__.py,sha256=kQsaVyzRa_ESC57JpKDSQJAHExuXme0rM5beJsYxFeA,161
|
3
|
-
src/cli.py,sha256=fqX-HuRDePRasexpnURQ_pVYeycJuWxllMcwfqDxMQw,8490
|
4
|
-
src/config.py,sha256=RSsAYzl8cj6eaDN1RORMcfKKWBcp4bKTQp2BdhAL9mg,10258
|
5
|
-
src/config.pyi,sha256=FgehEGli8ZXSjGlANBgMGv5497q4XskQciOc1fUcxqM,2033
|
6
|
-
src/events.py,sha256=aFQrVXDIZwt55Dz6OtyoXu2yi9evqo-8jZzo3CR2Tto,4965
|
7
|
-
src/oauth_manager.py,sha256=W9QSo0vfGDQ_i-QWCngkv7YLSL3Rk5jfPmqjU1J2rnU,9911
|
8
|
-
src/permissions.py,sha256=NGAnlG_z59HEiVA-k3cYvwmmiuHzxuNb5Tbd5umbL00,10483
|
9
|
-
src/server.py,sha256=cnO5bgxT-lrfuwk9AIvB_HBV8SWOtFClfGUn5_zFWyo,45652
|
10
|
-
src/single_user_mcp.py,sha256=rJrlqHcIubGkos_24ux5rb3OoKYDzvagCHghhfDeXTI,18535
|
11
|
-
src/telemetry.py,sha256=-RZPIjpI53zbsKmp-63REeZ1JirWHV5WvpSRa2nqZEk,11321
|
12
|
-
src/frontend_dist/index.html,sha256=s95FMkH8VLisvawLH7bZxbLzRUFvMhHkH6ZMzpVBngs,673
|
13
|
-
src/frontend_dist/sw.js,sha256=rihX1es-vWwjmtnXyaksJjs2dio6MVAOTAWwQPeJUYw,2164
|
14
|
-
src/frontend_dist/assets/index-BUUcUfTt.js,sha256=awoyPI6u0v6ao2iarZdSkrSDUvyU8aNkMLqHMvgVgyY,257666
|
15
|
-
src/frontend_dist/assets/index-o6_8mdM8.css,sha256=nwmX_6q55mB9463XN2JM8BdeihjkALpQK83Fc3_iGvE,15936
|
16
|
-
src/mcp_importer/__init__.py,sha256=Mk59pVr7OMGfYGWeSYk8-URfhIcrs3SPLYS7fmJbMII,275
|
17
|
-
src/mcp_importer/__main__.py,sha256=0jVfxKzyr6koVu1ghhWseah5ilKIoGovE6zkEZ-u-Og,515
|
18
|
-
src/mcp_importer/api.py,sha256=47tur0xgl1NBI1Vnh3cpScEmDS64bKMYcWjZDuqx7HQ,6644
|
19
|
-
src/mcp_importer/cli.py,sha256=Pe0GLWm1nMd1VuNXOSkxIrFZuGNFc9dNvfBsvf-bdBI,3487
|
20
|
-
src/mcp_importer/export_cli.py,sha256=daEadB6nL8P4OpEGFx0GshuN1a091L7BhiitpV1bPqA,6294
|
21
|
-
src/mcp_importer/exporters.py,sha256=fSgl6seduoXFp7YnKH26UEaC1sFBnd4whSut7CJLBQs,11348
|
22
|
-
src/mcp_importer/import_api.py,sha256=xWaKoE3vibSWpA5roVL7qEMS73vcmAC0tcHP6CsZw6E,95
|
23
|
-
src/mcp_importer/importers.py,sha256=zGN8lT7qQJ95jDTd-ck09j_w5PSvH-uj33TILoHfHbs,2191
|
24
|
-
src/mcp_importer/merge.py,sha256=KIGT7UgbAm07-LdyoUXEJ7ABSIiPTFlj_qjz669yFxg,1569
|
25
|
-
src/mcp_importer/parsers.py,sha256=JRE7y_Gg-QmlAARvZdrI9CmUyy-ODvDPbS695pb3Aw8,4856
|
26
|
-
src/mcp_importer/paths.py,sha256=4L-cPr7KCM9X9gAUP7Da6ictLNrPWuQ_IM419zqY-2I,2700
|
27
|
-
src/mcp_importer/quick_cli.py,sha256=4mJe10q_lZCYLm75QBt1rYy2j8mGEsRZoAqA0agjfSM,1834
|
28
|
-
src/mcp_importer/types.py,sha256=h03TbAnJbap6OWWd0dT0QcFWNvSaiVFWH9V9PD6x4s0,138
|
29
|
-
src/middleware/data_access_tracker.py,sha256=bArBffWgYmvxOx9z_pgXQhogvnWQcc1m6WvEblDD4gw,15039
|
30
|
-
src/middleware/session_tracking.py,sha256=5W1VH9HNqIZeX0HNxDEm41U4GY6SqKSXtApDEeZK2qo,23084
|
31
|
-
open_edison-0.1.43.dist-info/METADATA,sha256=OO5PDNk7pByRgIShryHaCtp2_7ua9XOwC8eBVdU-b5o,13188
|
32
|
-
open_edison-0.1.43.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
33
|
-
open_edison-0.1.43.dist-info/entry_points.txt,sha256=YiGNm9x2I00hgT10HDyB4gxC1LcaV_mu8bXFjolu0Yw,171
|
34
|
-
open_edison-0.1.43.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
35
|
-
open_edison-0.1.43.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|