open-edison 0.1.42__py3-none-any.whl → 0.1.44__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.
@@ -0,0 +1 @@
1
+ *,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e5e7eb}:before,:after{--tw-content: ""}html,:host{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.\!container{width:100%!important}.container{width:100%}@media (min-width: 640px){.\!container{max-width:640px!important}.container{max-width:640px}}@media (min-width: 768px){.\!container{max-width:768px!important}.container{max-width:768px}}@media (min-width: 1024px){.\!container{max-width:1024px!important}.container{max-width:1024px}}@media (min-width: 1280px){.\!container{max-width:1280px!important}.container{max-width:1280px}}@media (min-width: 1536px){.\!container{max-width:1536px!important}.container{max-width:1536px}}.fixed{position:fixed}.relative{position:relative}.bottom-4{bottom:1rem}.right-4{right:1rem}.z-50{z-index:50}.m-0{margin:0}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-auto{margin-left:auto}.mr-2{margin-right:.5rem}.mt-0\.5{margin-top:.125rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-2{height:.5rem}.h-3{height:.75rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-\[580px\]{height:580px}.w-10{width:2.5rem}.w-2{width:.5rem}.w-3{width:.75rem}.w-4{width:1rem}.w-5{width:1.25rem}.w-\[260px\]{width:260px}.w-full{width:100%}.min-w-\[240px\]{min-width:240px}.max-w-\[1400px\]{max-width:1400px}.max-w-\[260px\]{max-width:260px}.max-w-sm{max-width:24rem}.border-collapse{border-collapse:collapse}.translate-x-1{--tw-translate-x: .25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-5{--tw-translate-x: 1.25rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}@keyframes pulse{50%{opacity:.5}}.animate-pulse{animation:pulse 2s cubic-bezier(.4,0,.6,1) infinite}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-4>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1rem * var(--tw-space-y-reverse))}.overflow-hidden{overflow:hidden}.overflow-y-auto{overflow-y:auto}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-t{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-b{border-bottom-width:1px}.border-r{border-right-width:1px}.border-amber-400\/30{border-color:#fbbf244d}.border-app-accent{border-color:var(--accent)}.border-app-border{border-color:var(--border)}.border-blue-400\/30{border-color:#60a5fa4d}.border-rose-400\/30{border-color:#fb71854d}.bg-amber-400{--tw-bg-opacity: 1;background-color:rgb(251 191 36 / var(--tw-bg-opacity, 1))}.bg-app-accent{background-color:var(--accent)}.bg-app-bg{background-color:var(--bg)}.bg-app-border{background-color:var(--border)}.bg-blue-400{--tw-bg-opacity: 1;background-color:rgb(96 165 250 / var(--tw-bg-opacity, 1))}.bg-blue-500{--tw-bg-opacity: 1;background-color:rgb(59 130 246 / var(--tw-bg-opacity, 1))}.bg-green-500{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity, 1))}.bg-red-500{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity, 1))}.bg-rose-400{--tw-bg-opacity: 1;background-color:rgb(251 113 133 / var(--tw-bg-opacity, 1))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity, 1))}.bg-yellow-500{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity, 1))}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-6{padding:1.5rem}.\!px-2{padding-left:.5rem!important;padding-right:.5rem!important}.\!px-3{padding-left:.75rem!important;padding-right:.75rem!important}.\!py-1\.5{padding-top:.375rem!important;padding-bottom:.375rem!important}.\!py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.pb-2{padding-bottom:.5rem}.text-left{text-align:left}.text-center{text-align:center}.align-bottom{vertical-align:bottom}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-\[10px\]{font-size:10px}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-semibold{font-weight:600}.text-amber-400{--tw-text-opacity: 1;color:rgb(251 191 36 / var(--tw-text-opacity, 1))}.text-app-accent{color:var(--accent)}.text-app-muted{color:var(--muted)}.text-app-text{color:var(--text)}.text-blue-400{--tw-text-opacity: 1;color:rgb(96 165 250 / var(--tw-text-opacity, 1))}.text-blue-500{--tw-text-opacity: 1;color:rgb(59 130 246 / var(--tw-text-opacity, 1))}.text-gray-400{--tw-text-opacity: 1;color:rgb(156 163 175 / var(--tw-text-opacity, 1))}.text-green-400{--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity, 1))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity, 1))}.text-red-400{--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity, 1))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity, 1))}.text-rose-400{--tw-text-opacity: 1;color:rgb(251 113 133 / var(--tw-text-opacity, 1))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity, 1))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity, 1))}.accent-blue-500{accent-color:#3b82f6}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-colors{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-300{transition-duration:.3s}:root{--bg: #0b0c10;--card: #111318;--border: #1f2430;--text: #e6e6e6;--muted: #a0a7b4;--accent: #7c3aed;--success: #10b981;--warning: #f59e0b;--danger: #ef4444}[data-theme=dark]{--bg: #0b0c10;--card: #111318;--border: #1f2430;--text: #e6e6e6;--muted: #a0a7b4}[data-theme=light]{--bg: #f8fafc;--card: #ffffff;--border: #e5e7eb;--text: #0f172a;--muted: #475569}@media (prefers-color-scheme: light){:root{--bg: #f8fafc;--card: #ffffff;--border: #e5e7eb;--text: #0f172a;--muted: #475569}}html,body,#root{height:100%}body{margin:0;background:var(--bg);color:var(--text)}.container{margin:0 auto;padding:24px;max-width:1100px}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}.card{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px;box-shadow:0 1px 2px #0000000a,0 2px 12px #00000014}.stat{display:flex;align-items:center;gap:12px}.badge{display:inline-block;font-size:12px;padding:2px 8px;border-radius:999px;border:1px solid var(--border);background:#7c3aed14;color:var(--text)}.table{width:100%;border-collapse:collapse}.table th,.table td{border-bottom:1px solid var(--border);padding:8px 4px;text-align:left}.muted{color:var(--muted)}.accent{color:var(--accent)}.success{color:var(--success)}.warning{color:var(--warning)}.danger{color:var(--danger)}.toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px}.button{border:1px solid var(--border);background:var(--card);color:var(--text);padding:6px 10px;border-radius:8px;cursor:pointer}.button:hover{filter:brightness(1.05)}.hover\:text-gray-200:hover{--tw-text-opacity: 1;color:rgb(229 231 235 / var(--tw-text-opacity, 1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus-visible\:ring-2:focus-visible{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus-visible\:ring-blue-400:focus-visible{--tw-ring-opacity: 1;--tw-ring-color: rgb(96 165 250 / var(--tw-ring-opacity, 1))}@media (min-width: 640px){.sm\:flex{display:flex}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:flex-row{flex-direction:row}.sm\:items-end{align-items:flex-end}}@media (min-width: 1024px){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-\[220px_1fr\]{grid-template-columns:220px 1fr}}
@@ -1,11 +1,21 @@
1
- <!DOCTYPE html>
2
- <html>
1
+ <!doctype html>
2
+ <html lang="en">
3
+
3
4
  <head>
4
- <title>Open Edison Dashboard</title>
5
- <meta charset="utf-8">
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Open Edison Sessions</title>
8
+ <script>
9
+ // Prevents FOUC by setting theme asap
10
+ const prefersLight = window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches;
11
+ document.documentElement.setAttribute('data-theme', prefersLight ? 'light' : 'dark');
12
+ </script>
13
+ <script type="module" crossorigin src="/assets/index-BUUcUfTt.js"></script>
14
+ <link rel="stylesheet" crossorigin href="/assets/index-o6_8mdM8.css">
6
15
  </head>
16
+
7
17
  <body>
8
- <h1>Open Edison Dashboard</h1>
9
- <p>Frontend assets not available. Run 'make build_package' to build the full dashboard.</p>
18
+ <div id="root"></div>
10
19
  </body>
20
+
11
21
  </html>
@@ -0,0 +1,71 @@
1
+ // Minimal service worker for actionable notifications
2
+
3
+ self.addEventListener('install', (event) => {
4
+ self.skipWaiting();
5
+ });
6
+
7
+ self.addEventListener('activate', (event) => {
8
+ event.waitUntil(self.clients.claim());
9
+ });
10
+
11
+ // Receive messages from pages to show notifications with actions
12
+ self.addEventListener('message', (event) => {
13
+ try {
14
+ const data = event.data || {};
15
+ if (data && data.type === 'SHOW_MCP_BLOCK_NOTIFICATION') {
16
+ const title = data.title || 'Action required';
17
+ const body = data.body || 'Approve or deny the request';
18
+ const payload = data.data || {};
19
+ event.waitUntil(
20
+ self.registration.showNotification(title, {
21
+ body,
22
+ requireInteraction: true,
23
+ data: payload,
24
+ actions: [
25
+ { action: 'approve', title: 'Approve' },
26
+ { action: 'deny', title: 'Deny' }
27
+ ]
28
+ })
29
+ );
30
+ }
31
+ } catch (e) {
32
+ // swallow
33
+ }
34
+ });
35
+
36
+ // Handle action button clicks and generic clicks
37
+ self.addEventListener('notificationclick', (event) => {
38
+ try {
39
+ const payload = (event.notification && event.notification.data) || {};
40
+ const action = event.action;
41
+ event.notification.close();
42
+
43
+ if (action === 'approve') {
44
+ const body = {
45
+ session_id: payload.sessionId,
46
+ kind: payload.kind,
47
+ name: payload.name
48
+ };
49
+ event.waitUntil(
50
+ fetch('/api/approve', {
51
+ method: 'POST',
52
+ headers: { 'Content-Type': 'application/json' },
53
+ body: JSON.stringify(body)
54
+ }).catch(() => { })
55
+ );
56
+ return;
57
+ }
58
+
59
+ if (action === 'deny') {
60
+ // No-op; could notify page if desired
61
+ return;
62
+ }
63
+
64
+ // Generic click: open dashboard as a safe fallback
65
+ event.waitUntil(self.clients.openWindow('/dashboard').catch(() => { }));
66
+ } catch (e) {
67
+ // swallow
68
+ }
69
+ });
70
+
71
+
src/mcp_importer/api.py CHANGED
@@ -1,21 +1,28 @@
1
1
  # pyright: reportMissingImports=false, reportUnknownVariableType=false, reportUnknownMemberType=false, reportUnknownArgumentType=false, reportUnknownParameterType=false
2
+ import asyncio
3
+ from collections.abc import Awaitable
2
4
  from enum import Enum
3
5
  from pathlib import Path
4
6
  from typing import Any
5
7
 
8
+ from fastmcp import FastMCP
9
+ from loguru import logger as log
10
+
6
11
  from src.config import Config, MCPServerConfig, get_config_json_path
7
12
  from src.mcp_importer import paths as _paths
8
- from src.mcp_importer.exporters import export_to_claude_code, export_to_cursor, export_to_vscode
9
- from src.mcp_importer.importers import (
10
- import_from_claude_code as _import_from_claude_code,
11
- )
12
- from src.mcp_importer.importers import (
13
- import_from_cursor as _import_from_cursor,
13
+ from src.mcp_importer.exporters import (
14
+ ExportResult,
15
+ export_to_claude_code,
16
+ export_to_cursor,
17
+ export_to_vscode,
14
18
  )
15
19
  from src.mcp_importer.importers import (
16
- import_from_vscode as _import_from_vscode,
20
+ import_from_claude_code,
21
+ import_from_cursor,
22
+ import_from_vscode,
17
23
  )
18
24
  from src.mcp_importer.merge import MergePolicy, merge_servers
25
+ from src.oauth_manager import OAuthStatus, get_oauth_manager
19
26
 
20
27
 
21
28
  class CLIENT(str, Enum):
@@ -23,6 +30,12 @@ class CLIENT(str, Enum):
23
30
  VSCODE = "vscode"
24
31
  CLAUDE_CODE = "claude-code"
25
32
 
33
+ def __str__(self) -> str:
34
+ return self.value.capitalize()
35
+
36
+ def __repr__(self) -> str:
37
+ return str(self)
38
+
26
39
 
27
40
  def detect_clients() -> set[CLIENT]:
28
41
  detected: set[CLIENT] = set()
@@ -35,30 +48,31 @@ def detect_clients() -> set[CLIENT]:
35
48
  return detected
36
49
 
37
50
 
38
- import_cursor = _import_from_cursor
39
- import_vscode = _import_from_vscode
40
- import_claude_code = _import_from_claude_code
41
-
42
-
43
51
  def import_from(client: CLIENT) -> list[MCPServerConfig]:
44
52
  if client == CLIENT.CURSOR:
45
- return import_cursor()
53
+ return import_from_cursor()
46
54
  if client == CLIENT.VSCODE:
47
- return import_vscode()
55
+ return import_from_vscode()
48
56
  if client == CLIENT.CLAUDE_CODE:
49
- return import_claude_code()
57
+ return import_from_claude_code()
50
58
  raise ValueError(f"Unsupported client: {client}")
51
59
 
52
60
 
53
61
  def save_imported_servers(
54
62
  servers: list[MCPServerConfig],
55
63
  *,
64
+ dry_run: bool = False,
56
65
  merge_policy: str = MergePolicy.SKIP,
57
66
  config_dir: Path | None = None,
58
- ) -> Path:
67
+ ) -> Path | None:
59
68
  target_path: Path = (
60
69
  get_config_json_path() if config_dir is None else (Path(config_dir) / "config.json")
61
70
  )
71
+ if dry_run:
72
+ print(
73
+ f"[dry-run] Would import {len(servers)} server(s) and save to config.json (at {target_path})"
74
+ )
75
+ return None
62
76
  cfg: Config = Config(target_path)
63
77
  merged = merge_servers(existing=cfg.mcp_servers, imported=servers, policy=merge_policy)
64
78
  cfg.mcp_servers = merged
@@ -75,7 +89,17 @@ def export_edison_to(
75
89
  dry_run: bool = False,
76
90
  force: bool = False,
77
91
  create_if_missing: bool = False,
78
- ) -> Any:
92
+ ) -> ExportResult:
93
+ if dry_run:
94
+ print(
95
+ f"[dry-run] Would export Open Edison to '{client}' (backup and replace editor MCP config)"
96
+ )
97
+ return ExportResult(
98
+ target_path=Path(""),
99
+ backup_path=None,
100
+ wrote_changes=False,
101
+ dry_run=True,
102
+ )
79
103
  match client:
80
104
  case CLIENT.CURSOR:
81
105
  return export_to_cursor(
@@ -104,3 +128,77 @@ def export_edison_to(
104
128
  force=force,
105
129
  create_if_missing=create_if_missing,
106
130
  )
131
+
132
+
133
+ def verify_mcp_server(server: MCPServerConfig) -> bool: # noqa
134
+ """Minimal validation: try listing tools/resources/prompts via FastMCP within a timeout."""
135
+
136
+ async def _verify_async() -> bool:
137
+ if not server.command.strip():
138
+ return False
139
+
140
+ # Inline backend config and capability listing (no extra helpers)
141
+ backend_cfg: dict[str, Any] = {
142
+ "mcpServers": {
143
+ server.name: {
144
+ "command": server.command,
145
+ "args": server.args,
146
+ "env": server.env or {},
147
+ **({"roots": server.roots} if server.roots else {}),
148
+ }
149
+ }
150
+ }
151
+
152
+ proxy: FastMCP[Any] | None = None
153
+ host: FastMCP[Any] | None = None
154
+ try:
155
+ proxy = FastMCP.as_proxy(backend_cfg)
156
+ host = FastMCP(name=f"open-edison-verify-host-{server.name}")
157
+ host.mount(proxy, prefix=server.name)
158
+
159
+ async def _call_list(kind: str) -> Any:
160
+ manager_name = {
161
+ "tools": "_tool_manager",
162
+ "resources": "_resource_manager",
163
+ "prompts": "_prompt_manager",
164
+ }[kind]
165
+ manager = getattr(host, manager_name)
166
+ return await getattr(manager, f"list_{kind}")()
167
+
168
+ await asyncio.wait_for(
169
+ asyncio.gather(
170
+ _call_list("tools"),
171
+ _call_list("resources"),
172
+ _call_list("prompts"),
173
+ ),
174
+ timeout=30.0,
175
+ )
176
+ return True
177
+ except Exception as e:
178
+ log.error("MCP verification failed for '{}': {}", server.name, e)
179
+ return False
180
+ finally:
181
+ try:
182
+ for obj in (host, proxy):
183
+ if isinstance(obj, FastMCP):
184
+ result = obj.shutdown() # type: ignore[attr-defined]
185
+ if isinstance(result, Awaitable):
186
+ await result # type: ignore[func-returns-value]
187
+ except Exception:
188
+ pass
189
+
190
+ return asyncio.run(_verify_async())
191
+
192
+
193
+ def server_needs_oauth(server: MCPServerConfig) -> bool: # noqa
194
+ """Return True if the remote server currently needs OAuth; False otherwise."""
195
+
196
+ async def _needs_oauth_async() -> bool:
197
+ if not server.is_remote_server():
198
+ return False
199
+ info = await get_oauth_manager().check_oauth_requirement(
200
+ server.name, server.get_remote_url()
201
+ )
202
+ return info.status == OAuthStatus.NEEDS_AUTH
203
+
204
+ return asyncio.run(_needs_oauth_async())
@@ -26,10 +26,19 @@ def run_cli(argv: list[str] | None = None) -> int:
26
26
  )
27
27
  parser.add_argument("--dry-run", action="store_true", help="Preview actions without writing")
28
28
  parser.add_argument("--yes", action="store_true", help="Skip confirmations (no effect here)")
29
+ parser.add_argument(
30
+ "--source-client", type=CLIENT, help="Client to import from", required=False, default=None
31
+ )
29
32
  args = parser.parse_args(argv)
30
33
 
31
34
  detected = detect_clients()
32
- client = _pick_first(detected)
35
+ print(f"Detected clients: {detected}")
36
+ if args.source_client:
37
+ client = args.source_client
38
+ assert client in detected, f"Client {client} not detected"
39
+ else:
40
+ client = _pick_first(detected)
41
+ print(f"Going to import from client: {client}")
33
42
  if client is None:
34
43
  print("No supported clients detected.")
35
44
  return 2
@@ -39,20 +48,8 @@ def run_cli(argv: list[str] | None = None) -> int:
39
48
  print(f"No servers found to import from '{client.value}'.")
40
49
  return 0
41
50
 
42
- if args.dry_run:
43
- print(
44
- f"[dry-run] Would import {len(servers)} server(s) from '{client.value}' and save to config.json"
45
- )
46
- # Exercise export path safely (no writes)
47
- export_edison_to(client, dry_run=True, force=True, create_if_missing=True)
48
- print(
49
- f"[dry-run] Would export Open Edison to '{client.value}' (backup and replace editor MCP config)"
50
- )
51
- print("Dry-run complete.")
52
- return 0
53
-
54
- save_imported_servers(servers)
55
- export_edison_to(client, dry_run=False, force=True, create_if_missing=True)
51
+ save_imported_servers(servers, dry_run=args.dry_run)
52
+ export_edison_to(client, dry_run=args.dry_run, force=True, create_if_missing=True)
56
53
  print(f"Completed quick import/export for {client.value}.")
57
54
  return 0
58
55
 
src/oauth_manager.py CHANGED
@@ -7,6 +7,7 @@ Provides detection, token management, and authentication flow coordination.
7
7
 
8
8
  import asyncio
9
9
  from dataclasses import dataclass
10
+ from datetime import datetime, timedelta
10
11
  from enum import Enum
11
12
  from pathlib import Path
12
13
 
@@ -137,7 +138,6 @@ class OAuthManager:
137
138
  expires_in = getattr(existing_tokens, "expires_in", None)
138
139
  if expires_in:
139
140
  # If expires_in is available, we can calculate expiration
140
- from datetime import datetime, timedelta
141
141
 
142
142
  expiry = datetime.now() + timedelta(seconds=expires_in)
143
143
  token_expires_at = expiry.isoformat()
@@ -0,0 +1,5 @@
1
+ """Setup TUI package initializer.
2
+
3
+ Intentionally empty to avoid importing submodules at package import time,
4
+ which prevents warnings when executing `python -m src.setup_tui.main`.
5
+ """
src/setup_tui/main.py ADDED
@@ -0,0 +1,157 @@
1
+ import argparse
2
+
3
+ import questionary
4
+
5
+ from src.config import MCPServerConfig
6
+ from src.mcp_importer.api import (
7
+ CLIENT,
8
+ detect_clients,
9
+ export_edison_to,
10
+ import_from,
11
+ verify_mcp_server,
12
+ )
13
+
14
+
15
+ def show_welcome_screen(*, dry_run: bool = False) -> None:
16
+ """Display the welcome screen for open-edison setup."""
17
+ welcome_text = """
18
+ ╔══════════════════════════════════════════════════════════════╗
19
+ ║ ║
20
+ ║ Welcome to open-edison ║
21
+ ║ ║
22
+ ║ This setup wizard will help you configure open-edison ║
23
+ ║ for your development environment. ║
24
+ ║ ║
25
+ ╚══════════════════════════════════════════════════════════════╝
26
+ """
27
+
28
+ print(welcome_text)
29
+
30
+ # Prompt to continue
31
+ questionary.confirm("Ready to begin the setup process?", default=True).ask()
32
+
33
+
34
+ def handle_mcp_source(source: CLIENT, *, dry_run: bool = False) -> list[MCPServerConfig]:
35
+ """Handle the MCP source."""
36
+ if not questionary.confirm(
37
+ f"We have found {source.name} installed. Would you like to import its MCP servers to open-edison?",
38
+ default=True,
39
+ ).ask():
40
+ return []
41
+
42
+ configs = import_from(source)
43
+
44
+ print(f"Loaded {len(configs)} MCP server configuration from {source.name}!")
45
+
46
+ verified_configs: list[MCPServerConfig] = []
47
+
48
+ for config in configs:
49
+ print(f"Verifying the configuration for {config.name}... (TODO)")
50
+ result = verify_mcp_server(config)
51
+ if result:
52
+ verified_configs.append(config)
53
+ else:
54
+ print(
55
+ f"The configuration for {config.name} is not valid. Please check the configuration and try again."
56
+ )
57
+
58
+ return verified_configs
59
+
60
+
61
+ def confirm_configs(configs: list[MCPServerConfig], *, dry_run: bool = False) -> bool:
62
+ """Confirm the MCP configs."""
63
+ print("These are the servers you have selected:")
64
+
65
+ for config in configs:
66
+ print(f"○ {config.name}")
67
+
68
+ return questionary.confirm(
69
+ "Are you sure you want to use these servers with open-edison?", default=True
70
+ ).ask()
71
+
72
+
73
+ def confirm_apply_configs(client: CLIENT, *, dry_run: bool = False) -> None:
74
+ if not questionary.confirm(
75
+ f"We have detected that you have {client.name} installed. Would you like to connect it to open-edison?",
76
+ default=True,
77
+ ).ask():
78
+ return
79
+
80
+ export_edison_to(client, dry_run=dry_run)
81
+ if dry_run:
82
+ print(f"[dry-run] Export prepared for {client.name}; no changes written.")
83
+ else:
84
+ print(f"Successfully set up Open Edison for {client.name}!")
85
+
86
+
87
+ def show_manual_setup_screen() -> None:
88
+ """Display manual setup instructions for open-edison."""
89
+ manual_setup_text = """
90
+ ╔══════════════════════════════════════════════════════════════╗
91
+ ║ ║
92
+ ║ Manual Setup Instructions ║
93
+ ║ ║
94
+ ╚══════════════════════════════════════════════════════════════╝
95
+
96
+ To set up open-edison manually in other clients, find your client's MCP config
97
+ JSON file and add the following configuration:
98
+
99
+ "mcpServers": {
100
+ "open-edison": {
101
+ "command": "npx",
102
+ "args": [
103
+ "-y",
104
+ "mcp-remote",
105
+ "http://localhost:3000/mcp/",
106
+ "--http-only",
107
+ "--header",
108
+ "Authorization: Bearer dev-api-key-change-me"
109
+ ]
110
+ }
111
+ },
112
+
113
+ Make sure to replace 'dev-api-key-change-me' with your actual API key.
114
+ """
115
+
116
+ print(manual_setup_text)
117
+
118
+
119
+ def run(*, dry_run: bool = False) -> None:
120
+ """Run the complete setup process."""
121
+ show_welcome_screen(dry_run=dry_run)
122
+ # Additional setup steps will be added here
123
+
124
+ mcp_sources = detect_clients()
125
+ mcp_clients = detect_clients()
126
+
127
+ configs: list[MCPServerConfig] = []
128
+
129
+ for source in mcp_sources:
130
+ configs.extend(handle_mcp_source(source, dry_run=dry_run))
131
+
132
+ if len(configs) == 0:
133
+ print(
134
+ "No MCP servers found. Please set up an MCP client with some servers and run this setup again."
135
+ )
136
+ return
137
+
138
+ if not confirm_configs(configs, dry_run=dry_run):
139
+ return
140
+
141
+ for client in mcp_clients:
142
+ confirm_apply_configs(client, dry_run=dry_run)
143
+
144
+ show_manual_setup_screen()
145
+
146
+
147
+ def main(argv: list[str] | None = None) -> int:
148
+ parser = argparse.ArgumentParser(description="Open Edison Setup TUI")
149
+ parser.add_argument("--dry-run", action="store_true", help="Preview actions without writing")
150
+ args = parser.parse_args(argv)
151
+
152
+ run(dry_run=args.dry_run)
153
+ return 0
154
+
155
+
156
+ if __name__ == "__main__":
157
+ raise SystemExit(main())
frontend_dist/index.html DELETED
@@ -1,11 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
- <head>
4
- <title>Open Edison Dashboard</title>
5
- <meta charset="utf-8">
6
- </head>
7
- <body>
8
- <h1>Open Edison Dashboard</h1>
9
- <p>Frontend assets not available. Run 'make build_package' to build the full dashboard.</p>
10
- </body>
11
- </html>