promethios-bridge 2.0.0 → 2.0.1
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.
- package/package.json +1 -1
- package/src/overlay/main.js +81 -64
- package/src/overlay/renderer/index.html +62 -28
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "promethios-bridge",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"description": "Run Promethios agent frameworks locally on your computer with full file, terminal, browser access, ambient context capture, and the always-on-top floating chat overlay. Native Framework Mode supports OpenClaw and other frameworks via the bridge.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/overlay/main.js
CHANGED
|
@@ -573,84 +573,110 @@ function openManusBrowser({ mcpPrompt } = {}) {
|
|
|
573
573
|
if (DEV) manusBrowserWin.webContents.openDevTools({ mode: 'detach' });
|
|
574
574
|
}
|
|
575
575
|
|
|
576
|
-
// ── OAuth
|
|
576
|
+
// ── OAuth via real system browser + localhost callback server ─────────────────
|
|
577
577
|
/**
|
|
578
|
-
* Open
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
*
|
|
578
|
+
* Open OAuth in the user's REAL system browser (Chrome/Edge/Safari/Firefox).
|
|
579
|
+
*
|
|
580
|
+
* Why: Electron's Chromium webview is blocked by Google, Meta, and other OAuth
|
|
581
|
+
* providers with "This browser or app may not be secure". The fix:
|
|
582
|
+
* 1. Start a temporary localhost HTTP server on port 7826
|
|
583
|
+
* 2. Build the OAuth URL with redirect_uri=http://localhost:7826/callback
|
|
584
|
+
* 3. Open that URL in the real system browser via shell.openExternal()
|
|
585
|
+
* 4. The relay redirects back to localhost:7826/callback?token=xxx
|
|
586
|
+
* 5. We intercept it, close the server, and deliver the token to the renderer
|
|
587
|
+
* 6. The localhost page shows "You can close this tab" to the user
|
|
583
588
|
*/
|
|
589
|
+
let oauthCallbackServer = null;
|
|
590
|
+
let oauthCallbackPort = 7826;
|
|
591
|
+
|
|
584
592
|
function openOAuthPopup(provider) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
593
|
+
// Close any existing callback server
|
|
594
|
+
if (oauthCallbackServer) {
|
|
595
|
+
try { oauthCallbackServer.close(); } catch {}
|
|
596
|
+
oauthCallbackServer = null;
|
|
588
597
|
}
|
|
589
598
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
const tokenParam
|
|
599
|
+
const callbackPort = oauthCallbackPort;
|
|
600
|
+
const redirectUri = `http://localhost:${callbackPort}/callback`;
|
|
601
|
+
const tokenParam = AUTH_TOKEN ? `&bridge_token=${encodeURIComponent(AUTH_TOKEN)}` : '';
|
|
593
602
|
const authUrl = `${RELAY_URL}/api/mcp/oauth/authorize?` +
|
|
594
603
|
`client_id=${encodeURIComponent(provider)}&` +
|
|
595
604
|
`response_type=code&` +
|
|
596
|
-
`redirect_uri=${encodeURIComponent(
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
605
|
+
`redirect_uri=${encodeURIComponent(redirectUri)}${tokenParam}`;
|
|
606
|
+
|
|
607
|
+
const successHtml = `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Promethios Connected</title>
|
|
608
|
+
<style>*{box-sizing:border-box}body{font-family:system-ui,-apple-system,sans-serif;background:#0f0f14;color:#e2e8f0;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;flex-direction:column;gap:16px;}
|
|
609
|
+
.check{width:56px;height:56px;background:rgba(139,92,246,0.15);border:2px solid rgba(139,92,246,0.4);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:24px;}
|
|
610
|
+
h1{font-size:20px;font-weight:700;margin:0;}p{font-size:13px;color:rgba(255,255,255,0.45);margin:0;text-align:center;}</style></head>
|
|
611
|
+
<body><div class="check">✓</div><h1>Connected to Promethios!</h1><p>Authorization complete.<br>You can close this tab and return to the overlay.</p></body></html>`;
|
|
612
|
+
|
|
613
|
+
const errorHtml = (msg) => `<!DOCTYPE html><html><head><meta charset="utf-8"><title>Connection Failed</title>
|
|
614
|
+
<style>*{box-sizing:border-box}body{font-family:system-ui,-apple-system,sans-serif;background:#0f0f14;color:#e2e8f0;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;flex-direction:column;gap:16px;}
|
|
615
|
+
.x{width:56px;height:56px;background:rgba(239,68,68,0.1);border:2px solid rgba(239,68,68,0.3);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:24px;color:#ef4444;}
|
|
616
|
+
h1{font-size:20px;font-weight:700;margin:0;}p{font-size:13px;color:rgba(255,255,255,0.45);margin:0;text-align:center;}</style></head>
|
|
617
|
+
<body><div class="x">✗</div><h1>Connection Failed</h1><p>${msg}<br>Please close this tab and try again.</p></body></html>`;
|
|
618
|
+
|
|
619
|
+
// Start the localhost callback server
|
|
620
|
+
oauthCallbackServer = http.createServer((req, res) => {
|
|
621
|
+
const reqUrl = new URL(req.url, `http://localhost:${callbackPort}`);
|
|
622
|
+
if (reqUrl.pathname !== '/callback') { res.writeHead(404); res.end('Not found'); return; }
|
|
623
|
+
|
|
624
|
+
const token = reqUrl.searchParams.get('token') || reqUrl.searchParams.get('access_token');
|
|
625
|
+
const code = reqUrl.searchParams.get('code');
|
|
626
|
+
const clientId = reqUrl.searchParams.get('client_id') || provider;
|
|
627
|
+
const error = reqUrl.searchParams.get('error');
|
|
613
628
|
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
629
|
+
if (error) {
|
|
630
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
631
|
+
res.end(errorHtml(error));
|
|
632
|
+
if (DEV) console.error(`[OAuth] Error for ${provider}: ${error}`);
|
|
633
|
+
try { oauthCallbackServer.close(); } catch {}
|
|
634
|
+
oauthCallbackServer = null;
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
618
637
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
638
|
+
if (token) {
|
|
639
|
+
res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(successHtml);
|
|
640
|
+
deliverOAuthResult({ provider, token });
|
|
641
|
+
try { oauthCallbackServer.close(); } catch {}
|
|
642
|
+
oauthCallbackServer = null;
|
|
643
|
+
} else if (code) {
|
|
644
|
+
res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(successHtml);
|
|
645
|
+
exchangeCodeForToken(code, clientId, provider, redirectUri);
|
|
646
|
+
try { oauthCallbackServer.close(); } catch {}
|
|
647
|
+
oauthCallbackServer = null;
|
|
648
|
+
} else {
|
|
649
|
+
res.writeHead(400, { 'Content-Type': 'text/html' }); res.end(errorHtml('No token received'));
|
|
624
650
|
}
|
|
625
651
|
});
|
|
626
652
|
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const clientId = clientMatch ? clientMatch[1] : provider;
|
|
636
|
-
exchangeCodeForToken(code, clientId, provider);
|
|
637
|
-
}
|
|
653
|
+
oauthCallbackServer.on('error', (err) => {
|
|
654
|
+
if (err.code === 'EADDRINUSE') {
|
|
655
|
+
// Port in use — try next port
|
|
656
|
+
oauthCallbackPort++;
|
|
657
|
+
oauthCallbackServer = null;
|
|
658
|
+
openOAuthPopup(provider);
|
|
659
|
+
} else {
|
|
660
|
+
console.error('[OAuth] Callback server error:', err.message);
|
|
638
661
|
}
|
|
639
662
|
});
|
|
640
663
|
|
|
641
|
-
|
|
642
|
-
|
|
664
|
+
oauthCallbackServer.listen(callbackPort, '127.0.0.1', () => {
|
|
665
|
+
shell.openExternal(authUrl);
|
|
666
|
+
if (DEV) console.log(`[OAuth] Opened system browser for ${provider}: ${authUrl}`);
|
|
667
|
+
// Auto-close after 5 minutes if no callback received
|
|
668
|
+
setTimeout(() => {
|
|
669
|
+
if (oauthCallbackServer) { try { oauthCallbackServer.close(); } catch {} oauthCallbackServer = null; }
|
|
670
|
+
}, 5 * 60 * 1000);
|
|
643
671
|
});
|
|
644
|
-
|
|
645
|
-
if (DEV) console.log(`[OAuth] Opened popup for ${provider}: ${authUrl}`);
|
|
646
672
|
}
|
|
647
673
|
|
|
648
674
|
/**
|
|
649
675
|
* Exchange an OAuth auth code for a bridge session token.
|
|
650
676
|
* Called after the consent page sets its title to promethios_oauth_success|code=...
|
|
651
677
|
*/
|
|
652
|
-
async function exchangeCodeForToken(code, clientId, provider) {
|
|
653
|
-
|
|
678
|
+
async function exchangeCodeForToken(code, clientId, provider, redirectUri) {
|
|
679
|
+
if (!redirectUri) redirectUri = `http://localhost:${oauthCallbackPort}/callback`;
|
|
654
680
|
const tokenUrl = `${RELAY_URL}/api/mcp/oauth/token`;
|
|
655
681
|
|
|
656
682
|
try {
|
|
@@ -695,12 +721,9 @@ async function exchangeCodeForToken(code, clientId, provider) {
|
|
|
695
721
|
deliverOAuthResult({ provider, token: resp.body.access_token });
|
|
696
722
|
} else {
|
|
697
723
|
console.error('[OAuth] Token exchange failed:', resp.body);
|
|
698
|
-
// Still close the popup — user can try again
|
|
699
|
-
if (oauthPopup && !oauthPopup.isDestroyed()) oauthPopup.close();
|
|
700
724
|
}
|
|
701
725
|
} catch (e) {
|
|
702
726
|
console.error('[OAuth] Token exchange error:', e.message);
|
|
703
|
-
if (oauthPopup && !oauthPopup.isDestroyed()) oauthPopup.close();
|
|
704
727
|
}
|
|
705
728
|
}
|
|
706
729
|
|
|
@@ -730,12 +753,6 @@ function deliverOAuthResult({ provider, token }) {
|
|
|
730
753
|
connectedProviders[provider] = { token, clientId: provider, ts: Date.now() };
|
|
731
754
|
saveState();
|
|
732
755
|
|
|
733
|
-
// Close the popup
|
|
734
|
-
if (oauthPopup && !oauthPopup.isDestroyed()) {
|
|
735
|
-
oauthPopup.close();
|
|
736
|
-
oauthPopup = null;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
756
|
// Send result to renderer so it can show the copy-prompt modal
|
|
740
757
|
if (overlayWindow && !overlayWindow.isDestroyed()) {
|
|
741
758
|
overlayWindow.webContents.send('oauth-complete', { provider, token, clientId: provider });
|
|
@@ -824,9 +824,13 @@
|
|
|
824
824
|
|
|
825
825
|
<!-- ══ Providers tab ═══════════════════════════════════════════════════ -->
|
|
826
826
|
<div class="tab-content" id="tab-providers">
|
|
827
|
-
|
|
828
827
|
<div id="providers-list" style="display:flex;flex-direction:column;gap:10px;padding:14px;overflow-y:auto;flex:1;">
|
|
829
|
-
|
|
828
|
+
<!-- Section header -->
|
|
829
|
+
<div style="font-size:10px;font-weight:700;letter-spacing:0.08em;color:rgba(139,92,246,0.8);text-transform:uppercase;margin-bottom:2px;">AI Providers</div>
|
|
830
|
+
<div style="font-size:10px;color:rgba(255,255,255,0.35);line-height:1.5;margin-bottom:6px;">
|
|
831
|
+
<strong style="color:rgba(255,255,255,0.55)">Open</strong> launches the provider in your real browser.
|
|
832
|
+
<strong style="color:rgba(255,255,255,0.55)">Connect</strong> authorizes MCP — sign in first, then connect.
|
|
833
|
+
</div>
|
|
830
834
|
<!-- Manus -->
|
|
831
835
|
<div class="provider-card" data-provider="manus">
|
|
832
836
|
<div class="provider-icon manus">M</div>
|
|
@@ -835,11 +839,10 @@
|
|
|
835
839
|
<div class="provider-desc">manus.im — autonomous AI agent</div>
|
|
836
840
|
</div>
|
|
837
841
|
<div class="provider-actions">
|
|
838
|
-
<button class="btn-open" data-url="https://manus.im">Open ↗</button>
|
|
842
|
+
<button class="btn-open" data-url="https://manus.im" data-provider="manus">Open ↗</button>
|
|
839
843
|
<button class="btn-connect" data-provider="manus" id="connect-manus">Connect</button>
|
|
840
844
|
</div>
|
|
841
845
|
</div>
|
|
842
|
-
|
|
843
846
|
<!-- Claude -->
|
|
844
847
|
<div class="provider-card" data-provider="claude">
|
|
845
848
|
<div class="provider-icon claude">C</div>
|
|
@@ -848,11 +851,10 @@
|
|
|
848
851
|
<div class="provider-desc">claude.ai — Anthropic's AI assistant</div>
|
|
849
852
|
</div>
|
|
850
853
|
<div class="provider-actions">
|
|
851
|
-
<button class="btn-open" data-url="https://claude.ai">Open ↗</button>
|
|
854
|
+
<button class="btn-open" data-url="https://claude.ai" data-provider="claude">Open ↗</button>
|
|
852
855
|
<button class="btn-connect" data-provider="claude" id="connect-claude">Connect</button>
|
|
853
856
|
</div>
|
|
854
857
|
</div>
|
|
855
|
-
|
|
856
858
|
<!-- ChatGPT -->
|
|
857
859
|
<div class="provider-card" data-provider="chatgpt">
|
|
858
860
|
<div class="provider-icon chatgpt">G</div>
|
|
@@ -861,11 +863,10 @@
|
|
|
861
863
|
<div class="provider-desc">chatgpt.com — OpenAI's assistant</div>
|
|
862
864
|
</div>
|
|
863
865
|
<div class="provider-actions">
|
|
864
|
-
<button class="btn-open" data-url="https://chatgpt.com">Open ↗</button>
|
|
866
|
+
<button class="btn-open" data-url="https://chatgpt.com" data-provider="chatgpt">Open ↗</button>
|
|
865
867
|
<button class="btn-connect" data-provider="chatgpt" id="connect-chatgpt">Connect</button>
|
|
866
868
|
</div>
|
|
867
869
|
</div>
|
|
868
|
-
|
|
869
870
|
<!-- Perplexity -->
|
|
870
871
|
<div class="provider-card" data-provider="perplexity">
|
|
871
872
|
<div class="provider-icon perplexity">P</div>
|
|
@@ -874,24 +875,24 @@
|
|
|
874
875
|
<div class="provider-desc">perplexity.ai — search-powered AI</div>
|
|
875
876
|
</div>
|
|
876
877
|
<div class="provider-actions">
|
|
877
|
-
<button class="btn-open" data-url="https://perplexity.ai">Open ↗</button>
|
|
878
|
+
<button class="btn-open" data-url="https://perplexity.ai" data-provider="perplexity">Open ↗</button>
|
|
878
879
|
<button class="btn-connect" data-provider="perplexity" id="connect-perplexity">Connect</button>
|
|
879
880
|
</div>
|
|
880
881
|
</div>
|
|
881
|
-
|
|
882
882
|
<!-- Gemini -->
|
|
883
883
|
<div class="provider-card" data-provider="gemini">
|
|
884
|
-
<div class="provider-icon gemini"
|
|
884
|
+
<div class="provider-icon gemini">✦</div>
|
|
885
885
|
<div class="provider-info">
|
|
886
886
|
<div class="provider-name">Gemini</div>
|
|
887
887
|
<div class="provider-desc">gemini.google.com — Google's AI</div>
|
|
888
888
|
</div>
|
|
889
889
|
<div class="provider-actions">
|
|
890
|
-
<button class="btn-open" data-url="https://gemini.google.com">Open ↗</button>
|
|
890
|
+
<button class="btn-open" data-url="https://gemini.google.com" data-provider="gemini">Open ↗</button>
|
|
891
891
|
<button class="btn-connect" data-provider="gemini" id="connect-gemini">Connect</button>
|
|
892
892
|
</div>
|
|
893
893
|
</div>
|
|
894
|
-
|
|
894
|
+
<!-- Inline error/hint row (shown when login check fails) -->
|
|
895
|
+
<div id="provider-login-hint" style="display:none;margin-top:4px;padding:10px 12px;background:rgba(239,68,68,0.08);border:1px solid rgba(239,68,68,0.2);border-radius:8px;font-size:10px;color:rgba(239,68,68,0.9);line-height:1.5;"></div>
|
|
895
896
|
</div>
|
|
896
897
|
</div><!-- /tab-providers -->
|
|
897
898
|
|
|
@@ -1207,14 +1208,12 @@
|
|
|
1207
1208
|
document.querySelectorAll('.btn-open').forEach(btn => {
|
|
1208
1209
|
btn.addEventListener('click', () => {
|
|
1209
1210
|
const url = btn.dataset.url;
|
|
1210
|
-
const provider = btn.closest('[data-provider]')?.dataset.provider;
|
|
1211
|
+
const provider = btn.dataset.provider || btn.closest('[data-provider]')?.dataset.provider;
|
|
1211
1212
|
if (!url) return;
|
|
1212
|
-
//
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
window.promethios.openExternal(url);
|
|
1217
|
-
}
|
|
1213
|
+
// Always open in the real system browser — OAuth requires it (Google/Meta block Electron webviews)
|
|
1214
|
+
// Manus also gets the system browser so Google login works
|
|
1215
|
+
window.promethios.openExternal(url);
|
|
1216
|
+
hideProviderLoginHint();
|
|
1218
1217
|
});
|
|
1219
1218
|
});
|
|
1220
1219
|
|
|
@@ -1229,10 +1228,33 @@
|
|
|
1229
1228
|
});
|
|
1230
1229
|
});
|
|
1231
1230
|
|
|
1231
|
+
// Provider login URLs — used to open the sign-in page if not logged in
|
|
1232
|
+
const PROVIDER_LOGIN_URLS = {
|
|
1233
|
+
manus: 'https://manus.im',
|
|
1234
|
+
claude: 'https://claude.ai/login',
|
|
1235
|
+
chatgpt: 'https://chatgpt.com/auth/login',
|
|
1236
|
+
perplexity: 'https://www.perplexity.ai/',
|
|
1237
|
+
gemini: 'https://gemini.google.com/',
|
|
1238
|
+
};
|
|
1239
|
+
|
|
1240
|
+
function showProviderLoginHint(provider, message) {
|
|
1241
|
+
const hint = document.getElementById('provider-login-hint');
|
|
1242
|
+
if (!hint) return;
|
|
1243
|
+
hint.innerHTML = message;
|
|
1244
|
+
hint.style.display = 'block';
|
|
1245
|
+
setTimeout(() => { hint.style.display = 'none'; }, 12000);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
function hideProviderLoginHint() {
|
|
1249
|
+
const hint = document.getElementById('provider-login-hint');
|
|
1250
|
+
if (hint) hint.style.display = 'none';
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1232
1253
|
async function startOAuthFlow(provider) {
|
|
1254
|
+
hideProviderLoginHint();
|
|
1255
|
+
|
|
1233
1256
|
if (!authToken) {
|
|
1234
|
-
showToast('
|
|
1235
|
-
// Switch to Bridge tab
|
|
1257
|
+
showToast('Bridge not connected — start the bridge first.');
|
|
1236
1258
|
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
1237
1259
|
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
|
1238
1260
|
document.querySelector('.tab[data-tab="bridge"]').classList.add('active');
|
|
@@ -1240,7 +1262,7 @@
|
|
|
1240
1262
|
return;
|
|
1241
1263
|
}
|
|
1242
1264
|
|
|
1243
|
-
|
|
1265
|
+
const info = PROVIDER_LABELS[provider] || { name: provider, color: 'rgba(255,255,255,0.1)', text: '#e2e8f0' };
|
|
1244
1266
|
const btn = document.querySelector(`.btn-connect[data-provider="${provider}"]`);
|
|
1245
1267
|
if (btn) { btn.textContent = 'Connecting…'; btn.disabled = true; }
|
|
1246
1268
|
|
|
@@ -1253,17 +1275,29 @@
|
|
|
1253
1275
|
const data = await res.json();
|
|
1254
1276
|
|
|
1255
1277
|
if (!data.success) {
|
|
1256
|
-
|
|
1278
|
+
const errMsg = (data.error || '').toLowerCase();
|
|
1279
|
+
const isLoginRequired = errMsg.includes('login') || errMsg.includes('not logged') ||
|
|
1280
|
+
errMsg.includes('auth') || errMsg.includes('session') ||
|
|
1281
|
+
errMsg.includes('sign in') || res.status === 401;
|
|
1282
|
+
|
|
1283
|
+
if (isLoginRequired) {
|
|
1284
|
+
const loginUrl = PROVIDER_LOGIN_URLS[provider] || `https://${provider}.com`;
|
|
1285
|
+
showProviderLoginHint(provider,
|
|
1286
|
+
`<strong>Sign in to ${info.name} first.</strong><br>` +
|
|
1287
|
+
`Click <strong>Open ↗</strong> next to ${info.name} to sign in, then click <strong>Connect</strong> again.`
|
|
1288
|
+
);
|
|
1289
|
+
window.promethios.openExternal(loginUrl);
|
|
1290
|
+
} else {
|
|
1291
|
+
showToast(`Connect failed: ${data.error || 'Unknown error'}`);
|
|
1292
|
+
}
|
|
1257
1293
|
if (btn) { btn.textContent = 'Connect'; btn.disabled = false; }
|
|
1258
1294
|
return;
|
|
1259
1295
|
}
|
|
1260
1296
|
|
|
1261
|
-
// Store the connection
|
|
1262
1297
|
connectedProviders[provider] = { token: data.token, clientId: provider, ts: Date.now() };
|
|
1263
1298
|
updateProviderButtons();
|
|
1299
|
+
hideProviderLoginHint();
|
|
1264
1300
|
|
|
1265
|
-
// Show copy-prompt modal with the relay-generated prompt
|
|
1266
|
-
const info = PROVIDER_LABELS[provider] || { name: provider, color: 'rgba(255,255,255,0.1)', text: '#e2e8f0' };
|
|
1267
1301
|
modalProviderName.textContent = info.name;
|
|
1268
1302
|
modalProviderBadge.textContent = info.name;
|
|
1269
1303
|
modalProviderBadge.style.background = info.color;
|
|
@@ -1272,8 +1306,8 @@
|
|
|
1272
1306
|
modalCopyBtn.textContent = '⌘ Copy Prompt';
|
|
1273
1307
|
modalCopyBtn.classList.remove('copied');
|
|
1274
1308
|
promptModal.classList.add('open');
|
|
1275
|
-
|
|
1276
1309
|
showToast(`✓ ${info.name} connected!`);
|
|
1310
|
+
|
|
1277
1311
|
} catch (err) {
|
|
1278
1312
|
showToast('Network error — check your connection and try again.');
|
|
1279
1313
|
if (btn) { btn.textContent = 'Connect'; btn.disabled = false; }
|