open-edison 0.1.39__py3-none-any.whl → 0.1.41__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.39.dist-info → open_edison-0.1.41.dist-info}/METADATA +10 -10
- open_edison-0.1.41.dist-info/RECORD +39 -0
- open_edison-0.1.41.dist-info/entry_points.txt +5 -0
- cli.py → src/cli.py +2 -8
- src/frontend_dist/assets/index-BUUcUfTt.js +51 -0
- src/frontend_dist/assets/index-o6_8mdM8.css +1 -0
- src/frontend_dist/index.html +21 -0
- src/frontend_dist/sw.js +71 -0
- src/mcp_importer/__init__.py +15 -0
- src/mcp_importer/__main__.py +19 -0
- src/mcp_importer/api.py +196 -0
- src/mcp_importer/cli.py +113 -0
- src/mcp_importer/export_cli.py +201 -0
- src/mcp_importer/exporters.py +393 -0
- src/mcp_importer/import_api.py +3 -0
- src/mcp_importer/importers.py +63 -0
- src/mcp_importer/merge.py +47 -0
- src/mcp_importer/parsers.py +148 -0
- src/mcp_importer/paths.py +92 -0
- src/mcp_importer/quick_cli.py +62 -0
- src/mcp_importer/types.py +5 -0
- open_edison-0.1.39.dist-info/RECORD +0 -22
- open_edison-0.1.39.dist-info/entry_points.txt +0 -5
- {open_edison-0.1.39.dist-info → open_edison-0.1.41.dist-info}/WHEEL +0 -0
- {open_edison-0.1.39.dist-info → open_edison-0.1.41.dist-info}/licenses/LICENSE +0 -0
- /__init__.py → /src/__init__.py +0 -0
- /__main__.py → /src/__main__.py +0 -0
- /config.py → /src/config.py +0 -0
- /config.pyi → /src/config.pyi +0 -0
- /events.py → /src/events.py +0 -0
- {middleware → src/middleware}/data_access_tracker.py +0 -0
- {middleware → src/middleware}/session_tracking.py +0 -0
- /oauth_manager.py → /src/oauth_manager.py +0 -0
- /permissions.py → /src/permissions.py +0 -0
- /server.py → /src/server.py +0 -0
- /single_user_mcp.py → /src/single_user_mcp.py +0 -0
- /telemetry.py → /src/telemetry.py +0 -0
@@ -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}}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<!doctype html>
|
2
|
+
<html lang="en">
|
3
|
+
|
4
|
+
<head>
|
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">
|
15
|
+
</head>
|
16
|
+
|
17
|
+
<body>
|
18
|
+
<div id="root"></div>
|
19
|
+
</body>
|
20
|
+
|
21
|
+
</html>
|
src/frontend_dist/sw.js
ADDED
@@ -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
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
"""MCP importer package for Open Edison scripts.
|
2
|
+
|
3
|
+
Import submodules explicitly as needed, e.g. `from src.mcp_importer import cli`.
|
4
|
+
"""
|
5
|
+
|
6
|
+
# pyright: reportUnsupportedDunderAll=false
|
7
|
+
|
8
|
+
__all__ = [
|
9
|
+
"paths",
|
10
|
+
"parsers",
|
11
|
+
"importers",
|
12
|
+
"merge",
|
13
|
+
"cli",
|
14
|
+
"api",
|
15
|
+
]
|
@@ -0,0 +1,19 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import sys
|
4
|
+
|
5
|
+
from src.mcp_importer.cli import run_cli as import_run_cli
|
6
|
+
from src.mcp_importer.export_cli import run_cli as export_run_cli
|
7
|
+
|
8
|
+
|
9
|
+
def main() -> int:
|
10
|
+
# Usage:
|
11
|
+
# python -m mcp_importer -> import CLI
|
12
|
+
# python -m mcp_importer export ... -> export CLI
|
13
|
+
if len(sys.argv) > 1 and sys.argv[1] == "export":
|
14
|
+
return export_run_cli(sys.argv[2:])
|
15
|
+
return import_run_cli(sys.argv[1:])
|
16
|
+
|
17
|
+
|
18
|
+
if __name__ == "__main__":
|
19
|
+
raise SystemExit(main())
|
src/mcp_importer/api.py
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
# pyright: reportMissingImports=false, reportUnknownVariableType=false, reportUnknownMemberType=false, reportUnknownArgumentType=false, reportUnknownParameterType=false
|
2
|
+
import asyncio
|
3
|
+
from collections.abc import Awaitable
|
4
|
+
from enum import Enum
|
5
|
+
from pathlib import Path
|
6
|
+
from typing import Any, Protocol, cast, runtime_checkable
|
7
|
+
|
8
|
+
from fastmcp import FastMCP
|
9
|
+
|
10
|
+
from src.config import Config, MCPServerConfig, get_config_json_path
|
11
|
+
from src.mcp_importer import paths as _paths
|
12
|
+
from src.mcp_importer.exporters import (
|
13
|
+
ExportResult,
|
14
|
+
export_to_claude_code,
|
15
|
+
export_to_cursor,
|
16
|
+
export_to_vscode,
|
17
|
+
)
|
18
|
+
from src.mcp_importer.importers import (
|
19
|
+
import_from_claude_code,
|
20
|
+
import_from_cursor,
|
21
|
+
import_from_vscode,
|
22
|
+
)
|
23
|
+
from src.mcp_importer.merge import MergePolicy, merge_servers
|
24
|
+
from src.oauth_manager import OAuthStatus, get_oauth_manager
|
25
|
+
|
26
|
+
|
27
|
+
@runtime_checkable
|
28
|
+
class _MCPClientLike(Protocol):
|
29
|
+
async def list_tools(self) -> Any: ...
|
30
|
+
async def list_resources(self) -> Any: ...
|
31
|
+
async def list_prompts(self) -> Any: ...
|
32
|
+
def shutdown(self) -> Awaitable[Any] | Any: ...
|
33
|
+
|
34
|
+
|
35
|
+
class CLIENT(str, Enum):
|
36
|
+
CURSOR = "cursor"
|
37
|
+
VSCODE = "vscode"
|
38
|
+
CLAUDE_CODE = "claude-code"
|
39
|
+
|
40
|
+
def __str__(self) -> str:
|
41
|
+
return self.value.capitalize()
|
42
|
+
|
43
|
+
def __repr__(self) -> str:
|
44
|
+
return str(self)
|
45
|
+
|
46
|
+
|
47
|
+
def detect_clients() -> set[CLIENT]:
|
48
|
+
detected: set[CLIENT] = set()
|
49
|
+
if _paths.detect_cursor_config_path() is not None:
|
50
|
+
detected.add(CLIENT.CURSOR)
|
51
|
+
if _paths.detect_vscode_config_path() is not None:
|
52
|
+
detected.add(CLIENT.VSCODE)
|
53
|
+
if _paths.detect_claude_code_config_path() is not None:
|
54
|
+
detected.add(CLIENT.CLAUDE_CODE)
|
55
|
+
return detected
|
56
|
+
|
57
|
+
|
58
|
+
def import_from(client: CLIENT) -> list[MCPServerConfig]:
|
59
|
+
if client == CLIENT.CURSOR:
|
60
|
+
return import_from_cursor()
|
61
|
+
if client == CLIENT.VSCODE:
|
62
|
+
return import_from_vscode()
|
63
|
+
if client == CLIENT.CLAUDE_CODE:
|
64
|
+
return import_from_claude_code()
|
65
|
+
raise ValueError(f"Unsupported client: {client}")
|
66
|
+
|
67
|
+
|
68
|
+
def save_imported_servers(
|
69
|
+
servers: list[MCPServerConfig],
|
70
|
+
*,
|
71
|
+
dry_run: bool = False,
|
72
|
+
merge_policy: str = MergePolicy.SKIP,
|
73
|
+
config_dir: Path | None = None,
|
74
|
+
) -> Path | None:
|
75
|
+
target_path: Path = (
|
76
|
+
get_config_json_path() if config_dir is None else (Path(config_dir) / "config.json")
|
77
|
+
)
|
78
|
+
if dry_run:
|
79
|
+
print(
|
80
|
+
f"[dry-run] Would import {len(servers)} server(s) and save to config.json (at {target_path})"
|
81
|
+
)
|
82
|
+
return None
|
83
|
+
cfg: Config = Config(target_path)
|
84
|
+
merged = merge_servers(existing=cfg.mcp_servers, imported=servers, policy=merge_policy)
|
85
|
+
cfg.mcp_servers = merged
|
86
|
+
cfg.save(target_path)
|
87
|
+
return target_path
|
88
|
+
|
89
|
+
|
90
|
+
def export_edison_to(
|
91
|
+
client: CLIENT,
|
92
|
+
*,
|
93
|
+
url: str = "http://localhost:3000/mcp/",
|
94
|
+
api_key: str = "dev-api-key-change-me",
|
95
|
+
server_name: str = "open-edison",
|
96
|
+
dry_run: bool = False,
|
97
|
+
force: bool = False,
|
98
|
+
create_if_missing: bool = False,
|
99
|
+
) -> ExportResult:
|
100
|
+
if dry_run:
|
101
|
+
print(
|
102
|
+
f"[dry-run] Would export Open Edison to '{client}' (backup and replace editor MCP config)"
|
103
|
+
)
|
104
|
+
return ExportResult(
|
105
|
+
target_path=Path(""),
|
106
|
+
backup_path=None,
|
107
|
+
wrote_changes=False,
|
108
|
+
dry_run=True,
|
109
|
+
)
|
110
|
+
match client:
|
111
|
+
case CLIENT.CURSOR:
|
112
|
+
return export_to_cursor(
|
113
|
+
url=url,
|
114
|
+
api_key=api_key,
|
115
|
+
server_name=server_name,
|
116
|
+
dry_run=dry_run,
|
117
|
+
force=force,
|
118
|
+
create_if_missing=create_if_missing,
|
119
|
+
)
|
120
|
+
case CLIENT.VSCODE:
|
121
|
+
return export_to_vscode(
|
122
|
+
url=url,
|
123
|
+
api_key=api_key,
|
124
|
+
server_name=server_name,
|
125
|
+
dry_run=dry_run,
|
126
|
+
force=force,
|
127
|
+
create_if_missing=create_if_missing,
|
128
|
+
)
|
129
|
+
case CLIENT.CLAUDE_CODE:
|
130
|
+
return export_to_claude_code(
|
131
|
+
url=url,
|
132
|
+
api_key=api_key,
|
133
|
+
server_name=server_name,
|
134
|
+
dry_run=dry_run,
|
135
|
+
force=force,
|
136
|
+
create_if_missing=create_if_missing,
|
137
|
+
)
|
138
|
+
|
139
|
+
|
140
|
+
def verify_mcp_server(server: MCPServerConfig) -> bool: # noqa
|
141
|
+
"""Minimal validation: try listing tools/resources/prompts via FastMCP within a timeout."""
|
142
|
+
|
143
|
+
async def _verify_async() -> bool:
|
144
|
+
if not server.command.strip():
|
145
|
+
return False
|
146
|
+
|
147
|
+
backend_cfg: dict[str, Any] = {
|
148
|
+
"mcpServers": {
|
149
|
+
server.name: {
|
150
|
+
"command": server.command,
|
151
|
+
"args": server.args,
|
152
|
+
"env": server.env or {},
|
153
|
+
**({"roots": server.roots} if server.roots else {}),
|
154
|
+
}
|
155
|
+
}
|
156
|
+
}
|
157
|
+
|
158
|
+
proxy: FastMCP[Any] | None = None
|
159
|
+
try:
|
160
|
+
proxy = FastMCP.as_proxy(backend=backend_cfg, name=f"open-edison-verify-{server.name}")
|
161
|
+
s: _MCPClientLike = cast(_MCPClientLike, proxy)
|
162
|
+
await asyncio.wait_for(
|
163
|
+
asyncio.gather(
|
164
|
+
s.list_tools(),
|
165
|
+
s.list_resources(),
|
166
|
+
s.list_prompts(),
|
167
|
+
),
|
168
|
+
timeout=15.0,
|
169
|
+
)
|
170
|
+
return True
|
171
|
+
except Exception:
|
172
|
+
return False
|
173
|
+
finally:
|
174
|
+
try:
|
175
|
+
if isinstance(proxy, FastMCP):
|
176
|
+
result = proxy.shutdown() # type: ignore[attr-defined]
|
177
|
+
if isinstance(result, Awaitable):
|
178
|
+
await result # type: ignore[func-returns-value]
|
179
|
+
except Exception:
|
180
|
+
pass
|
181
|
+
|
182
|
+
return asyncio.run(_verify_async())
|
183
|
+
|
184
|
+
|
185
|
+
def server_needs_oauth(server: MCPServerConfig) -> bool: # noqa
|
186
|
+
"""Return True if the remote server currently needs OAuth; False otherwise."""
|
187
|
+
|
188
|
+
async def _needs_oauth_async() -> bool:
|
189
|
+
if not server.is_remote_server():
|
190
|
+
return False
|
191
|
+
info = await get_oauth_manager().check_oauth_requirement(
|
192
|
+
server.name, server.get_remote_url()
|
193
|
+
)
|
194
|
+
return info.status == OAuthStatus.NEEDS_AUTH
|
195
|
+
|
196
|
+
return asyncio.run(_needs_oauth_async())
|
src/mcp_importer/cli.py
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
# pyright: reportMissingImports=false, reportUnknownVariableType=false, reportUnknownArgumentType=false, reportUnknownMemberType=false, reportUnknownParameterType=false
|
2
|
+
import argparse
|
3
|
+
import sys
|
4
|
+
from pathlib import Path
|
5
|
+
|
6
|
+
from loguru import logger as log
|
7
|
+
|
8
|
+
from src.config import Config, get_config_dir
|
9
|
+
|
10
|
+
from .importers import IMPORTERS
|
11
|
+
from .merge import MergePolicy, merge_servers
|
12
|
+
|
13
|
+
## Ensure import of src config (place src on sys.path before import)
|
14
|
+
# THIS_FILE = Path(__file__).resolve()
|
15
|
+
# REPO_ROOT = THIS_FILE.parents[2]
|
16
|
+
# SRC_DIR = REPO_ROOT / "src"
|
17
|
+
# if str(SRC_DIR) not in sys.path:
|
18
|
+
# sys.path.insert(0, str(SRC_DIR))
|
19
|
+
|
20
|
+
|
21
|
+
def build_arg_parser() -> argparse.ArgumentParser:
|
22
|
+
p = argparse.ArgumentParser(
|
23
|
+
description="Import MCP servers from other tools into Open Edison config.json"
|
24
|
+
)
|
25
|
+
p.add_argument(
|
26
|
+
"--source",
|
27
|
+
choices=["cursor", "vscode", "claude-code"],
|
28
|
+
required=True,
|
29
|
+
)
|
30
|
+
p.add_argument(
|
31
|
+
"--config-dir",
|
32
|
+
type=Path,
|
33
|
+
help="Directory containing target config.json (default: OPEN_EDISON_CONFIG_DIR or repo root)",
|
34
|
+
)
|
35
|
+
p.add_argument(
|
36
|
+
"--merge",
|
37
|
+
choices=[MergePolicy.SKIP, MergePolicy.OVERWRITE, MergePolicy.RENAME],
|
38
|
+
default=MergePolicy.SKIP,
|
39
|
+
)
|
40
|
+
p.add_argument(
|
41
|
+
"--dry-run", action="store_true", help="Show changes without writing to config.json"
|
42
|
+
)
|
43
|
+
return p
|
44
|
+
|
45
|
+
|
46
|
+
def run_cli(argv: list[str] | None = None) -> int: # noqa: C901
|
47
|
+
parser = build_arg_parser()
|
48
|
+
args = parser.parse_args(argv)
|
49
|
+
|
50
|
+
source: str = args.source
|
51
|
+
|
52
|
+
importer = IMPORTERS.get(source)
|
53
|
+
if not importer:
|
54
|
+
print(f"Unsupported source: {source}", file=sys.stderr)
|
55
|
+
return 2
|
56
|
+
|
57
|
+
# Resolve target config path
|
58
|
+
target_dir: Path = args.config_dir or get_config_dir()
|
59
|
+
target_path = target_dir / "config.json"
|
60
|
+
|
61
|
+
# Load existing config (auto-creates default if missing via Config.load)
|
62
|
+
config_obj: Config = Config(target_dir)
|
63
|
+
|
64
|
+
# Import
|
65
|
+
imported_servers = importer()
|
66
|
+
|
67
|
+
if not imported_servers:
|
68
|
+
log.warning("No servers found to import from source '{}'", source)
|
69
|
+
return 0
|
70
|
+
|
71
|
+
# Merge
|
72
|
+
merged = merge_servers(
|
73
|
+
existing=config_obj.mcp_servers,
|
74
|
+
imported=imported_servers,
|
75
|
+
policy=args.merge,
|
76
|
+
)
|
77
|
+
|
78
|
+
existing_names: set[str] = {str(getattr(s, "name", "")) for s in config_obj.mcp_servers}
|
79
|
+
merged_names: set[str] = {str(getattr(s, "name", "")) for s in merged}
|
80
|
+
added = merged_names - existing_names
|
81
|
+
replaced: set[str] = set()
|
82
|
+
if args.merge == MergePolicy.OVERWRITE:
|
83
|
+
replaced = existing_names & {s.name for s in imported_servers}
|
84
|
+
|
85
|
+
log.info("Imported {} server(s) from '{}'", len(imported_servers), source)
|
86
|
+
try:
|
87
|
+
names_preview = ", ".join(sorted(getattr(s, "name", "") for s in imported_servers))
|
88
|
+
if names_preview:
|
89
|
+
log.info("Detected servers: {}", names_preview)
|
90
|
+
except Exception:
|
91
|
+
pass
|
92
|
+
if added:
|
93
|
+
log.info("Added: {}", ", ".join(sorted(added)))
|
94
|
+
if replaced:
|
95
|
+
log.info("Overwrote: {}", ", ".join(sorted(replaced)))
|
96
|
+
|
97
|
+
if args.dry_run:
|
98
|
+
log.info("Dry-run enabled; not writing changes to {}", target_path)
|
99
|
+
log.debug("Merged servers: {}", merged)
|
100
|
+
return 0
|
101
|
+
|
102
|
+
config_obj.mcp_servers = merged
|
103
|
+
config_obj.save(target_path)
|
104
|
+
log.info("Configuration updated: {}", target_path)
|
105
|
+
return 0
|
106
|
+
|
107
|
+
|
108
|
+
def main() -> int:
|
109
|
+
return run_cli()
|
110
|
+
|
111
|
+
|
112
|
+
if __name__ == "__main__":
|
113
|
+
raise SystemExit(main())
|
@@ -0,0 +1,201 @@
|
|
1
|
+
import argparse
|
2
|
+
from pathlib import Path
|
3
|
+
|
4
|
+
from loguru import logger as log
|
5
|
+
|
6
|
+
from .exporters import ExportError, export_to_claude_code, export_to_cursor, export_to_vscode
|
7
|
+
from .paths import (
|
8
|
+
detect_cursor_config_path,
|
9
|
+
detect_vscode_config_path,
|
10
|
+
get_default_cursor_config_path,
|
11
|
+
get_default_vscode_config_path,
|
12
|
+
)
|
13
|
+
|
14
|
+
|
15
|
+
def _prompt_yes_no(message: str, *, default_no: bool = True) -> bool:
|
16
|
+
suffix = "[y/N]" if default_no else "[Y/n]"
|
17
|
+
while True:
|
18
|
+
resp = input(f"{message} {suffix} ").strip().lower()
|
19
|
+
if resp == "y" or resp == "yes":
|
20
|
+
return True
|
21
|
+
if resp == "n" or resp == "no":
|
22
|
+
return False
|
23
|
+
if resp == "" and default_no:
|
24
|
+
return False
|
25
|
+
if resp == "" and not default_no:
|
26
|
+
return True
|
27
|
+
|
28
|
+
|
29
|
+
def build_arg_parser() -> argparse.ArgumentParser:
|
30
|
+
p = argparse.ArgumentParser(
|
31
|
+
description="Export editor MCP config to use Open Edison (Cursor support)",
|
32
|
+
)
|
33
|
+
p.add_argument("--target", choices=["cursor", "vscode", "claude-code"], default="cursor")
|
34
|
+
p.add_argument("--dry-run", action="store_true", help="Show actions without writing")
|
35
|
+
p.add_argument("--force", action="store_true", help="Rewrite even if already configured")
|
36
|
+
p.add_argument(
|
37
|
+
"--yes",
|
38
|
+
action="store_true",
|
39
|
+
help="Automatic yes to prompts (create missing files without confirmation)",
|
40
|
+
)
|
41
|
+
p.add_argument("--url", default="http://localhost:3000/mcp/", help="MCP URL")
|
42
|
+
p.add_argument(
|
43
|
+
"--api-key",
|
44
|
+
default="dev-api-key-change-me",
|
45
|
+
help="API key for Authorization header",
|
46
|
+
)
|
47
|
+
p.add_argument("--name", default="open-edison", help="Name of the server entry")
|
48
|
+
return p
|
49
|
+
|
50
|
+
|
51
|
+
def _handle_cursor(args: argparse.Namespace) -> int:
|
52
|
+
detected = detect_cursor_config_path()
|
53
|
+
target_path: Path = detected if detected else get_default_cursor_config_path()
|
54
|
+
|
55
|
+
create_if_missing = False
|
56
|
+
if not target_path.exists():
|
57
|
+
if args.yes:
|
58
|
+
create_if_missing = True
|
59
|
+
else:
|
60
|
+
confirmed = _prompt_yes_no(
|
61
|
+
f"Cursor config not found at {target_path}. Create it?", default_no=False
|
62
|
+
)
|
63
|
+
if not confirmed:
|
64
|
+
log.info("Aborted: user declined to create missing file")
|
65
|
+
return 0
|
66
|
+
create_if_missing = True
|
67
|
+
|
68
|
+
try:
|
69
|
+
result = export_to_cursor(
|
70
|
+
url=args.url,
|
71
|
+
api_key=args.api_key,
|
72
|
+
server_name=args.name,
|
73
|
+
dry_run=args.dry_run,
|
74
|
+
force=args.force,
|
75
|
+
create_if_missing=create_if_missing,
|
76
|
+
)
|
77
|
+
except ExportError as e:
|
78
|
+
log.error(str(e))
|
79
|
+
return 1
|
80
|
+
|
81
|
+
if result.dry_run:
|
82
|
+
log.info("Dry-run complete. No changes written.")
|
83
|
+
return 0
|
84
|
+
|
85
|
+
if result.wrote_changes:
|
86
|
+
if result.backup_path is not None:
|
87
|
+
log.info("Backup created at {}", result.backup_path)
|
88
|
+
log.info("Updated {}", result.target_path)
|
89
|
+
else:
|
90
|
+
log.info("No changes were necessary.")
|
91
|
+
return 0
|
92
|
+
|
93
|
+
|
94
|
+
def _handle_vscode(args: argparse.Namespace) -> int:
|
95
|
+
detected = detect_vscode_config_path()
|
96
|
+
target_path: Path = detected if detected else get_default_vscode_config_path()
|
97
|
+
|
98
|
+
create_if_missing = False
|
99
|
+
if not target_path.exists():
|
100
|
+
if args.yes:
|
101
|
+
create_if_missing = True
|
102
|
+
else:
|
103
|
+
confirmed = _prompt_yes_no(
|
104
|
+
f"VS Code MCP config not found at {target_path}. Create it?", default_no=False
|
105
|
+
)
|
106
|
+
if not confirmed:
|
107
|
+
log.info("Aborted: user declined to create missing file")
|
108
|
+
return 0
|
109
|
+
create_if_missing = True
|
110
|
+
|
111
|
+
try:
|
112
|
+
result = export_to_vscode(
|
113
|
+
url=args.url,
|
114
|
+
api_key=args.api_key,
|
115
|
+
server_name=args.name,
|
116
|
+
dry_run=args.dry_run,
|
117
|
+
force=args.force,
|
118
|
+
create_if_missing=create_if_missing,
|
119
|
+
)
|
120
|
+
except ExportError as e:
|
121
|
+
log.error(str(e))
|
122
|
+
return 1
|
123
|
+
|
124
|
+
if result.dry_run:
|
125
|
+
log.info("Dry-run complete. No changes written.")
|
126
|
+
return 0
|
127
|
+
|
128
|
+
if result.wrote_changes:
|
129
|
+
if result.backup_path is not None:
|
130
|
+
log.info("Backup created at {}", result.backup_path)
|
131
|
+
log.info("Updated {}", result.target_path)
|
132
|
+
else:
|
133
|
+
log.info("No changes were necessary.")
|
134
|
+
return 0
|
135
|
+
|
136
|
+
|
137
|
+
def _handle_claude_code(args: argparse.Namespace) -> int:
|
138
|
+
from .paths import detect_claude_code_config_path, get_default_claude_code_config_path
|
139
|
+
|
140
|
+
detected = detect_claude_code_config_path()
|
141
|
+
target_path: Path = detected if detected else get_default_claude_code_config_path()
|
142
|
+
|
143
|
+
create_if_missing = False
|
144
|
+
if not target_path.exists():
|
145
|
+
if args.yes:
|
146
|
+
create_if_missing = True
|
147
|
+
else:
|
148
|
+
confirmed = _prompt_yes_no(
|
149
|
+
f"Claude Code config not found at {target_path}. Create it?", default_no=False
|
150
|
+
)
|
151
|
+
if not confirmed:
|
152
|
+
log.info("Aborted: user declined to create missing file")
|
153
|
+
return 0
|
154
|
+
create_if_missing = True
|
155
|
+
|
156
|
+
try:
|
157
|
+
result = export_to_claude_code(
|
158
|
+
url=args.url,
|
159
|
+
api_key=args.api_key,
|
160
|
+
server_name=args.name,
|
161
|
+
dry_run=args.dry_run,
|
162
|
+
force=args.force,
|
163
|
+
create_if_missing=create_if_missing,
|
164
|
+
)
|
165
|
+
except ExportError as e:
|
166
|
+
log.error(str(e))
|
167
|
+
return 1
|
168
|
+
|
169
|
+
if result.dry_run:
|
170
|
+
log.info("Dry-run complete. No changes written.")
|
171
|
+
return 0
|
172
|
+
|
173
|
+
if result.wrote_changes:
|
174
|
+
if result.backup_path is not None:
|
175
|
+
log.info("Backup created at {}", result.backup_path)
|
176
|
+
log.info("Updated {}", result.target_path)
|
177
|
+
else:
|
178
|
+
log.info("No changes were necessary.")
|
179
|
+
return 0
|
180
|
+
|
181
|
+
|
182
|
+
def run_cli(argv: list[str] | None = None) -> int:
|
183
|
+
parser = build_arg_parser()
|
184
|
+
args = parser.parse_args(argv)
|
185
|
+
|
186
|
+
if args.target == "cursor":
|
187
|
+
return _handle_cursor(args)
|
188
|
+
if args.target == "vscode":
|
189
|
+
return _handle_vscode(args)
|
190
|
+
if args.target == "claude-code":
|
191
|
+
return _handle_claude_code(args)
|
192
|
+
log.error("Unsupported target: {}", args.target)
|
193
|
+
return 2
|
194
|
+
|
195
|
+
|
196
|
+
def main() -> int:
|
197
|
+
return run_cli()
|
198
|
+
|
199
|
+
|
200
|
+
if __name__ == "__main__":
|
201
|
+
raise SystemExit(main())
|