@syke1/mcp-server 1.4.5 → 1.4.7
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/dist/ai/provider.d.ts +5 -0
- package/dist/ai/provider.js +8 -0
- package/dist/index.js +35 -4
- package/dist/web/public/app.js +129 -2
- package/dist/web/public/index.html +34 -0
- package/dist/web/public/style.css +148 -0
- package/dist/web/server.d.ts +16 -1
- package/dist/web/server.js +23 -1
- package/package.json +1 -1
package/dist/ai/provider.d.ts
CHANGED
|
@@ -15,6 +15,11 @@ export interface AIProvider {
|
|
|
15
15
|
* 2. Auto-select: GEMINI_KEY > OPENAI_KEY > ANTHROPIC_KEY
|
|
16
16
|
*/
|
|
17
17
|
export declare function getAIProvider(): AIProvider | null;
|
|
18
|
+
/**
|
|
19
|
+
* Reset the cached provider so the next getAIProvider() call re-evaluates config.
|
|
20
|
+
* Call this after changing API keys at runtime.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resetAIProvider(): void;
|
|
18
23
|
/**
|
|
19
24
|
* Human-readable name for the active AI provider (for logs/UI).
|
|
20
25
|
*/
|
package/dist/ai/provider.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.getAIProvider = getAIProvider;
|
|
8
|
+
exports.resetAIProvider = resetAIProvider;
|
|
8
9
|
exports.getProviderName = getProviderName;
|
|
9
10
|
const generative_ai_1 = require("@google/generative-ai");
|
|
10
11
|
const config_1 = require("../config");
|
|
@@ -169,6 +170,13 @@ function getAIProvider() {
|
|
|
169
170
|
}
|
|
170
171
|
return cachedProvider;
|
|
171
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* Reset the cached provider so the next getAIProvider() call re-evaluates config.
|
|
175
|
+
* Call this after changing API keys at runtime.
|
|
176
|
+
*/
|
|
177
|
+
function resetAIProvider() {
|
|
178
|
+
cachedProvider = undefined;
|
|
179
|
+
}
|
|
172
180
|
/**
|
|
173
181
|
* Human-readable name for the active AI provider (for logs/UI).
|
|
174
182
|
*/
|
package/dist/index.js
CHANGED
|
@@ -587,17 +587,48 @@ async function main() {
|
|
|
587
587
|
licenseStatus = { plan: "free", source: "default" };
|
|
588
588
|
return { success: true, plan: "free" };
|
|
589
589
|
}
|
|
590
|
+
},
|
|
591
|
+
// setAIKeyFn
|
|
592
|
+
(provider, key) => {
|
|
593
|
+
const map = { gemini: "geminiKey", openai: "openaiKey", anthropic: "anthropicKey" };
|
|
594
|
+
const configKey = map[provider];
|
|
595
|
+
if (configKey) {
|
|
596
|
+
(0, config_1.setConfig)(configKey, key);
|
|
597
|
+
(0, provider_1.resetAIProvider)();
|
|
598
|
+
}
|
|
599
|
+
const gemini = !!(0, config_1.getConfig)("geminiKey", "GEMINI_KEY");
|
|
600
|
+
const openai = !!(0, config_1.getConfig)("openaiKey", "OPENAI_KEY");
|
|
601
|
+
const anthropic = !!(0, config_1.getConfig)("anthropicKey", "ANTHROPIC_KEY");
|
|
602
|
+
return {
|
|
603
|
+
success: true,
|
|
604
|
+
activeProvider: (0, provider_1.getProviderName)(),
|
|
605
|
+
configured: { gemini, openai, anthropic },
|
|
606
|
+
};
|
|
607
|
+
},
|
|
608
|
+
// getAIInfoFn
|
|
609
|
+
() => {
|
|
610
|
+
return {
|
|
611
|
+
activeProvider: (0, provider_1.getProviderName)(),
|
|
612
|
+
configured: {
|
|
613
|
+
gemini: !!(0, config_1.getConfig)("geminiKey", "GEMINI_KEY"),
|
|
614
|
+
openai: !!(0, config_1.getConfig)("openaiKey", "OPENAI_KEY"),
|
|
615
|
+
anthropic: !!(0, config_1.getConfig)("anthropicKey", "ANTHROPIC_KEY"),
|
|
616
|
+
},
|
|
617
|
+
};
|
|
590
618
|
});
|
|
591
619
|
webServerHandle = { setFileCache: setWebFileCache };
|
|
592
620
|
webApp.listen(WEB_PORT, () => {
|
|
593
621
|
const dashUrl = `http://localhost:${WEB_PORT}`;
|
|
594
622
|
console.error(`[syke] Web dashboard: ${dashUrl}`);
|
|
595
623
|
// Auto-open browser (disable with SYKE_NO_BROWSER=1)
|
|
624
|
+
// Delay 1s to let server fully stabilize before dashboard connects
|
|
596
625
|
if (process.env.SYKE_NO_BROWSER !== "1") {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
: `
|
|
600
|
-
|
|
626
|
+
setTimeout(() => {
|
|
627
|
+
const cmd = process.platform === "win32" ? `start ${dashUrl}`
|
|
628
|
+
: process.platform === "darwin" ? `open ${dashUrl}`
|
|
629
|
+
: `xdg-open ${dashUrl}`;
|
|
630
|
+
(0, child_process_1.exec)(cmd, () => { });
|
|
631
|
+
}, 1000);
|
|
601
632
|
}
|
|
602
633
|
});
|
|
603
634
|
}
|
package/dist/web/public/app.js
CHANGED
|
@@ -464,6 +464,7 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
464
464
|
setupSettings();
|
|
465
465
|
setupProjectModal();
|
|
466
466
|
setupLicenseModal();
|
|
467
|
+
setupAIKeysModal();
|
|
467
468
|
setupFileTree();
|
|
468
469
|
initSSE();
|
|
469
470
|
startHealthCheck();
|
|
@@ -2733,11 +2734,17 @@ window.syke = {
|
|
|
2733
2734
|
let sseSource = null;
|
|
2734
2735
|
let sseReconnectTimer = null;
|
|
2735
2736
|
let sseBlocked = false;
|
|
2737
|
+
let sseEverConnected = false; // track if SSE has ever connected successfully
|
|
2736
2738
|
const realtimeLog = []; // recent events for panel
|
|
2737
2739
|
|
|
2738
2740
|
async function initSSE() {
|
|
2739
2741
|
if (sseSource) { sseSource.close(); sseSource = null; }
|
|
2740
2742
|
|
|
2743
|
+
// Show appropriate status based on whether we've connected before
|
|
2744
|
+
if (!sseEverConnected) {
|
|
2745
|
+
updateSSEStatus("CONNECTING...", "warning");
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2741
2748
|
// Pre-check: if Free tier, SSE will 403 — don't attempt connection
|
|
2742
2749
|
try {
|
|
2743
2750
|
const probe = await fetch("/api/events");
|
|
@@ -2748,7 +2755,14 @@ async function initSSE() {
|
|
|
2748
2755
|
}
|
|
2749
2756
|
// Close the successful probe connection (we'll open EventSource next)
|
|
2750
2757
|
if (probe.body) probe.body.cancel().catch(() => {});
|
|
2751
|
-
} catch(e) {
|
|
2758
|
+
} catch(e) {
|
|
2759
|
+
// Server not ready yet — retry after short delay on first attempt
|
|
2760
|
+
if (!sseEverConnected) {
|
|
2761
|
+
if (sseReconnectTimer) clearTimeout(sseReconnectTimer);
|
|
2762
|
+
sseReconnectTimer = setTimeout(() => { initSSE(); }, 2000);
|
|
2763
|
+
return;
|
|
2764
|
+
}
|
|
2765
|
+
}
|
|
2752
2766
|
|
|
2753
2767
|
sseSource = new EventSource("/api/events");
|
|
2754
2768
|
|
|
@@ -2756,6 +2770,7 @@ async function initSSE() {
|
|
|
2756
2770
|
const data = JSON.parse(e.data);
|
|
2757
2771
|
console.log("[SYKE:SSE] Connected, cache:", data.cacheSize, "files");
|
|
2758
2772
|
healthFailCount = 0; // Reset health failures on SSE connect
|
|
2773
|
+
sseEverConnected = true;
|
|
2759
2774
|
updateSSEStatus("LIVE", "connected");
|
|
2760
2775
|
});
|
|
2761
2776
|
|
|
@@ -2948,7 +2963,7 @@ async function initSSE() {
|
|
|
2948
2963
|
if (sseBlocked) return;
|
|
2949
2964
|
|
|
2950
2965
|
sseRetryCount++;
|
|
2951
|
-
updateSSEStatus("RECONNECTING...", "warning");
|
|
2966
|
+
updateSSEStatus(sseEverConnected ? "RECONNECTING..." : "CONNECTING...", "warning");
|
|
2952
2967
|
|
|
2953
2968
|
// Only show offline after 5 consecutive SSE failures
|
|
2954
2969
|
if (sseRetryCount >= 5) {
|
|
@@ -3974,3 +3989,115 @@ function setupProjectModal() {
|
|
|
3974
3989
|
if (e.target === modal) modal.classList.add("hidden");
|
|
3975
3990
|
});
|
|
3976
3991
|
}
|
|
3992
|
+
|
|
3993
|
+
// ══════════════════════════════════════════════════════════════
|
|
3994
|
+
// AI KEYS MODAL
|
|
3995
|
+
// ══════════════════════════════════════════════════════════════
|
|
3996
|
+
function setupAIKeysModal() {
|
|
3997
|
+
const btn = document.getElementById("btn-ai-keys");
|
|
3998
|
+
const modal = document.getElementById("ai-keys-modal");
|
|
3999
|
+
const closeBtn = document.getElementById("btn-ai-keys-close");
|
|
4000
|
+
const activeEl = document.getElementById("ai-keys-active");
|
|
4001
|
+
if (!btn || !modal) return;
|
|
4002
|
+
|
|
4003
|
+
function updateStatus(row, isConfigured, isActive) {
|
|
4004
|
+
const statusEl = row.querySelector(".ai-key-status");
|
|
4005
|
+
if (isActive) {
|
|
4006
|
+
statusEl.textContent = "ACTIVE";
|
|
4007
|
+
statusEl.className = "ai-key-status active";
|
|
4008
|
+
} else if (isConfigured) {
|
|
4009
|
+
statusEl.textContent = "CONFIGURED";
|
|
4010
|
+
statusEl.className = "ai-key-status configured";
|
|
4011
|
+
} else {
|
|
4012
|
+
statusEl.textContent = "---";
|
|
4013
|
+
statusEl.className = "ai-key-status none";
|
|
4014
|
+
}
|
|
4015
|
+
}
|
|
4016
|
+
|
|
4017
|
+
function updateAll(aiKeys, activeProvider) {
|
|
4018
|
+
const rows = modal.querySelectorAll(".ai-key-row");
|
|
4019
|
+
rows.forEach(function(row) {
|
|
4020
|
+
const provider = row.dataset.provider;
|
|
4021
|
+
const configured = aiKeys[provider] || false;
|
|
4022
|
+
const isActive = configured && activeProvider.toLowerCase().includes(provider);
|
|
4023
|
+
updateStatus(row, configured, isActive);
|
|
4024
|
+
});
|
|
4025
|
+
if (activeProvider && activeProvider !== "disabled") {
|
|
4026
|
+
activeEl.textContent = "Active: " + activeProvider;
|
|
4027
|
+
} else {
|
|
4028
|
+
activeEl.textContent = "No AI provider configured";
|
|
4029
|
+
activeEl.style.color = "var(--text-secondary)";
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
|
|
4033
|
+
async function openModal() {
|
|
4034
|
+
modal.classList.remove("hidden");
|
|
4035
|
+
// Clear inputs
|
|
4036
|
+
modal.querySelectorAll(".ai-key-input").forEach(function(inp) { inp.value = ""; });
|
|
4037
|
+
// Fetch current state
|
|
4038
|
+
try {
|
|
4039
|
+
const res = await fetch("/api/project-info");
|
|
4040
|
+
const info = await res.json();
|
|
4041
|
+
updateAll(info.aiKeys || {}, info.aiProvider || "disabled");
|
|
4042
|
+
} catch {
|
|
4043
|
+
activeEl.textContent = "Failed to fetch status";
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
|
|
4047
|
+
function closeModal() {
|
|
4048
|
+
modal.classList.add("hidden");
|
|
4049
|
+
}
|
|
4050
|
+
|
|
4051
|
+
btn.addEventListener("click", openModal);
|
|
4052
|
+
closeBtn.addEventListener("click", closeModal);
|
|
4053
|
+
modal.addEventListener("click", function(e) { if (e.target === modal) closeModal(); });
|
|
4054
|
+
|
|
4055
|
+
// SET button handlers
|
|
4056
|
+
modal.querySelectorAll(".ai-key-row").forEach(function(row) {
|
|
4057
|
+
const setBtn = row.querySelector(".ai-key-set-btn");
|
|
4058
|
+
const input = row.querySelector(".ai-key-input");
|
|
4059
|
+
const provider = row.dataset.provider;
|
|
4060
|
+
|
|
4061
|
+
setBtn.addEventListener("click", async function() {
|
|
4062
|
+
const key = input.value.trim();
|
|
4063
|
+
setBtn.disabled = true;
|
|
4064
|
+
setBtn.textContent = "...";
|
|
4065
|
+
|
|
4066
|
+
try {
|
|
4067
|
+
const res = await fetch("/api/set-ai-key", {
|
|
4068
|
+
method: "POST",
|
|
4069
|
+
headers: { "Content-Type": "application/json" },
|
|
4070
|
+
body: JSON.stringify({ provider: provider, key: key || null }),
|
|
4071
|
+
});
|
|
4072
|
+
const data = await res.json();
|
|
4073
|
+
if (data.success) {
|
|
4074
|
+
updateAll(data.configured, data.activeProvider);
|
|
4075
|
+
input.value = "";
|
|
4076
|
+
if (key) {
|
|
4077
|
+
input.placeholder = "****" + key.slice(-4);
|
|
4078
|
+
} else {
|
|
4079
|
+
var placeholders = { gemini: "AIzaSy...", openai: "sk-...", anthropic: "sk-ant-..." };
|
|
4080
|
+
input.placeholder = placeholders[provider] || "";
|
|
4081
|
+
}
|
|
4082
|
+
}
|
|
4083
|
+
} catch (err) {
|
|
4084
|
+
var statusEl = row.querySelector(".ai-key-status");
|
|
4085
|
+
statusEl.textContent = "ERROR";
|
|
4086
|
+
statusEl.className = "ai-key-status";
|
|
4087
|
+
statusEl.style.color = "#ff5f57";
|
|
4088
|
+
}
|
|
4089
|
+
setBtn.disabled = false;
|
|
4090
|
+
setBtn.textContent = "SET";
|
|
4091
|
+
});
|
|
4092
|
+
|
|
4093
|
+
input.addEventListener("keydown", function(e) {
|
|
4094
|
+
if (e.key === "Enter") setBtn.click();
|
|
4095
|
+
});
|
|
4096
|
+
});
|
|
4097
|
+
|
|
4098
|
+
document.addEventListener("keydown", function(e) {
|
|
4099
|
+
if (e.key === "Escape" && !modal.classList.contains("hidden")) {
|
|
4100
|
+
closeModal();
|
|
4101
|
+
}
|
|
4102
|
+
});
|
|
4103
|
+
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
<span id="sse-status" class="sse-indicator offline">OFFLINE</span>
|
|
22
22
|
<span id="license-badge" class="license-badge free">FREE</span>
|
|
23
23
|
<button id="btn-license" class="top-btn license-btn">LICENSE</button>
|
|
24
|
+
<button id="btn-ai-keys" class="top-btn ai-keys-btn">AI</button>
|
|
24
25
|
</div>
|
|
25
26
|
<div class="project-selector">
|
|
26
27
|
<span id="current-project" class="project-path">Loading...</span>
|
|
@@ -455,6 +456,39 @@
|
|
|
455
456
|
</div>
|
|
456
457
|
</div>
|
|
457
458
|
|
|
459
|
+
<!-- AI Keys Modal -->
|
|
460
|
+
<div id="ai-keys-modal" class="hidden">
|
|
461
|
+
<div class="ai-keys-modal-panel">
|
|
462
|
+
<h3>AI PROVIDER KEYS</h3>
|
|
463
|
+
<p class="ai-keys-modal-desc">Configure API keys to enable AI analysis.</p>
|
|
464
|
+
<div class="ai-keys-rows">
|
|
465
|
+
<div class="ai-key-row" data-provider="gemini">
|
|
466
|
+
<span class="ai-key-label">GEMINI</span>
|
|
467
|
+
<input type="password" class="ai-key-input" placeholder="AIzaSy..." spellcheck="false" autocomplete="off">
|
|
468
|
+
<button class="ai-key-set-btn">SET</button>
|
|
469
|
+
<span class="ai-key-status"></span>
|
|
470
|
+
</div>
|
|
471
|
+
<div class="ai-key-row" data-provider="openai">
|
|
472
|
+
<span class="ai-key-label">OPENAI</span>
|
|
473
|
+
<input type="password" class="ai-key-input" placeholder="sk-..." spellcheck="false" autocomplete="off">
|
|
474
|
+
<button class="ai-key-set-btn">SET</button>
|
|
475
|
+
<span class="ai-key-status"></span>
|
|
476
|
+
</div>
|
|
477
|
+
<div class="ai-key-row" data-provider="anthropic">
|
|
478
|
+
<span class="ai-key-label">ANTHROPIC</span>
|
|
479
|
+
<input type="password" class="ai-key-input" placeholder="sk-ant-..." spellcheck="false" autocomplete="off">
|
|
480
|
+
<button class="ai-key-set-btn">SET</button>
|
|
481
|
+
<span class="ai-key-status"></span>
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
<div id="ai-keys-active" class="ai-keys-active"></div>
|
|
485
|
+
<div class="ai-keys-modal-actions">
|
|
486
|
+
<button id="btn-ai-keys-close">CLOSE</button>
|
|
487
|
+
</div>
|
|
488
|
+
<p class="ai-keys-priority">Priority: Gemini > OpenAI > Anthropic</p>
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
|
|
458
492
|
<!-- Bottom status bar -->
|
|
459
493
|
<div id="bottom-bar">
|
|
460
494
|
<span id="bottom-info">SYKE v--- · ---</span>
|
|
@@ -2060,6 +2060,154 @@ main {
|
|
|
2060
2060
|
letter-spacing: 1.5px;
|
|
2061
2061
|
}
|
|
2062
2062
|
|
|
2063
|
+
.ai-keys-btn {
|
|
2064
|
+
font-size: 9px !important;
|
|
2065
|
+
padding: 2px 8px !important;
|
|
2066
|
+
letter-spacing: 1.5px;
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
/* ═══════════════════════════════════════════ */
|
|
2070
|
+
/* AI Keys Modal */
|
|
2071
|
+
/* ═══════════════════════════════════════════ */
|
|
2072
|
+
#ai-keys-modal {
|
|
2073
|
+
position: fixed;
|
|
2074
|
+
inset: 0;
|
|
2075
|
+
background: rgba(5,10,24,0.85);
|
|
2076
|
+
z-index: 400;
|
|
2077
|
+
display: flex;
|
|
2078
|
+
align-items: center;
|
|
2079
|
+
justify-content: center;
|
|
2080
|
+
backdrop-filter: blur(4px);
|
|
2081
|
+
}
|
|
2082
|
+
#ai-keys-modal.hidden { display: none; }
|
|
2083
|
+
|
|
2084
|
+
.ai-keys-modal-panel {
|
|
2085
|
+
background: var(--bg-secondary);
|
|
2086
|
+
border: 1px solid var(--accent-dim);
|
|
2087
|
+
border-radius: 6px;
|
|
2088
|
+
padding: 24px 32px;
|
|
2089
|
+
min-width: 460px;
|
|
2090
|
+
max-width: 520px;
|
|
2091
|
+
box-shadow: var(--glow-cyan), 0 16px 64px rgba(0,0,0,0.5);
|
|
2092
|
+
}
|
|
2093
|
+
.ai-keys-modal-panel h3 {
|
|
2094
|
+
font-size: 12px;
|
|
2095
|
+
color: var(--accent);
|
|
2096
|
+
letter-spacing: 3px;
|
|
2097
|
+
margin-bottom: 8px;
|
|
2098
|
+
}
|
|
2099
|
+
.ai-keys-modal-desc {
|
|
2100
|
+
font-size: 11px;
|
|
2101
|
+
color: var(--text-secondary);
|
|
2102
|
+
margin-bottom: 16px;
|
|
2103
|
+
}
|
|
2104
|
+
.ai-keys-rows {
|
|
2105
|
+
display: flex;
|
|
2106
|
+
flex-direction: column;
|
|
2107
|
+
gap: 8px;
|
|
2108
|
+
margin-bottom: 14px;
|
|
2109
|
+
}
|
|
2110
|
+
.ai-key-row {
|
|
2111
|
+
display: flex;
|
|
2112
|
+
align-items: center;
|
|
2113
|
+
gap: 8px;
|
|
2114
|
+
}
|
|
2115
|
+
.ai-key-label {
|
|
2116
|
+
font-size: 10px;
|
|
2117
|
+
letter-spacing: 2px;
|
|
2118
|
+
color: var(--text-secondary);
|
|
2119
|
+
width: 80px;
|
|
2120
|
+
flex-shrink: 0;
|
|
2121
|
+
}
|
|
2122
|
+
.ai-key-input {
|
|
2123
|
+
flex: 1;
|
|
2124
|
+
padding: 7px 10px;
|
|
2125
|
+
background: rgba(0,0,0,0.5);
|
|
2126
|
+
border: 1px solid var(--border);
|
|
2127
|
+
border-radius: 3px;
|
|
2128
|
+
color: var(--text-primary);
|
|
2129
|
+
font-family: inherit;
|
|
2130
|
+
font-size: 12px;
|
|
2131
|
+
letter-spacing: 1px;
|
|
2132
|
+
outline: none;
|
|
2133
|
+
transition: border-color 0.2s;
|
|
2134
|
+
}
|
|
2135
|
+
.ai-key-input:focus {
|
|
2136
|
+
border-color: var(--accent);
|
|
2137
|
+
box-shadow: 0 0 8px rgba(0,212,255,0.15);
|
|
2138
|
+
}
|
|
2139
|
+
.ai-key-input::placeholder {
|
|
2140
|
+
color: var(--text-secondary);
|
|
2141
|
+
font-size: 11px;
|
|
2142
|
+
opacity: 0.5;
|
|
2143
|
+
}
|
|
2144
|
+
.ai-key-set-btn {
|
|
2145
|
+
padding: 6px 12px;
|
|
2146
|
+
border: 1px solid var(--accent-dim);
|
|
2147
|
+
border-radius: 3px;
|
|
2148
|
+
background: transparent;
|
|
2149
|
+
color: var(--accent);
|
|
2150
|
+
font-family: inherit;
|
|
2151
|
+
font-size: 10px;
|
|
2152
|
+
letter-spacing: 2px;
|
|
2153
|
+
cursor: pointer;
|
|
2154
|
+
transition: all 0.2s;
|
|
2155
|
+
flex-shrink: 0;
|
|
2156
|
+
}
|
|
2157
|
+
.ai-key-set-btn:hover {
|
|
2158
|
+
background: rgba(0,212,255,0.08);
|
|
2159
|
+
border-color: var(--accent);
|
|
2160
|
+
}
|
|
2161
|
+
.ai-key-status {
|
|
2162
|
+
font-size: 9px;
|
|
2163
|
+
letter-spacing: 1px;
|
|
2164
|
+
width: 70px;
|
|
2165
|
+
text-align: center;
|
|
2166
|
+
flex-shrink: 0;
|
|
2167
|
+
}
|
|
2168
|
+
.ai-key-status.active {
|
|
2169
|
+
color: var(--risk-low);
|
|
2170
|
+
}
|
|
2171
|
+
.ai-key-status.configured {
|
|
2172
|
+
color: var(--text-secondary);
|
|
2173
|
+
}
|
|
2174
|
+
.ai-key-status.none {
|
|
2175
|
+
color: var(--text-secondary);
|
|
2176
|
+
opacity: 0.4;
|
|
2177
|
+
}
|
|
2178
|
+
.ai-keys-active {
|
|
2179
|
+
font-size: 11px;
|
|
2180
|
+
color: var(--risk-low);
|
|
2181
|
+
margin-bottom: 14px;
|
|
2182
|
+
min-height: 18px;
|
|
2183
|
+
}
|
|
2184
|
+
.ai-keys-modal-actions {
|
|
2185
|
+
display: flex;
|
|
2186
|
+
gap: 8px;
|
|
2187
|
+
margin-bottom: 10px;
|
|
2188
|
+
}
|
|
2189
|
+
.ai-keys-modal-actions button {
|
|
2190
|
+
padding: 8px 18px;
|
|
2191
|
+
border: 1px solid var(--accent-dim);
|
|
2192
|
+
border-radius: 3px;
|
|
2193
|
+
background: transparent;
|
|
2194
|
+
color: var(--accent);
|
|
2195
|
+
font-family: inherit;
|
|
2196
|
+
font-size: 10px;
|
|
2197
|
+
letter-spacing: 2px;
|
|
2198
|
+
cursor: pointer;
|
|
2199
|
+
transition: all 0.2s;
|
|
2200
|
+
}
|
|
2201
|
+
.ai-keys-modal-actions button:hover {
|
|
2202
|
+
background: rgba(0,212,255,0.08);
|
|
2203
|
+
border-color: var(--accent);
|
|
2204
|
+
}
|
|
2205
|
+
.ai-keys-priority {
|
|
2206
|
+
font-size: 9px;
|
|
2207
|
+
color: var(--text-secondary);
|
|
2208
|
+
opacity: 0.6;
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2063
2211
|
/* ═══════════════════════════════════════════ */
|
|
2064
2212
|
/* Project Switch Modal */
|
|
2065
2213
|
/* ═══════════════════════════════════════════ */
|
package/dist/web/server.d.ts
CHANGED
|
@@ -40,4 +40,19 @@ export declare function createWebServer(getGraphFn: () => DependencyGraph, initi
|
|
|
40
40
|
plan?: string;
|
|
41
41
|
expiresAt?: string;
|
|
42
42
|
error?: string;
|
|
43
|
-
}
|
|
43
|
+
}>, setAIKeyFn?: (provider: string, key: string | null) => {
|
|
44
|
+
success: boolean;
|
|
45
|
+
activeProvider: string;
|
|
46
|
+
configured: {
|
|
47
|
+
gemini: boolean;
|
|
48
|
+
openai: boolean;
|
|
49
|
+
anthropic: boolean;
|
|
50
|
+
};
|
|
51
|
+
}, getAIInfoFn?: () => {
|
|
52
|
+
activeProvider: string;
|
|
53
|
+
configured: {
|
|
54
|
+
gemini: boolean;
|
|
55
|
+
openai: boolean;
|
|
56
|
+
anthropic: boolean;
|
|
57
|
+
};
|
|
58
|
+
}): WebServerHandle;
|
package/dist/web/server.js
CHANGED
|
@@ -227,7 +227,7 @@ function acknowledgeWarnings() {
|
|
|
227
227
|
function getAllWarnings() {
|
|
228
228
|
return [...warningStore];
|
|
229
229
|
}
|
|
230
|
-
function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProjectRoot, getPackageName, getLicenseStatus, hasAIKeyFn, setLicenseKeyFn) {
|
|
230
|
+
function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProjectRoot, getPackageName, getLicenseStatus, hasAIKeyFn, setLicenseKeyFn, setAIKeyFn, getAIInfoFn) {
|
|
231
231
|
const app = (0, express_1.default)();
|
|
232
232
|
app.use(express_1.default.json());
|
|
233
233
|
// Serve static files from public/
|
|
@@ -745,6 +745,7 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
|
|
|
745
745
|
const maskedKey = rawKey.length > 10
|
|
746
746
|
? rawKey.substring(0, 9) + "····" + rawKey.substring(rawKey.length - 4)
|
|
747
747
|
: "";
|
|
748
|
+
const aiInfo = getAIInfoFn ? getAIInfoFn() : { activeProvider: "disabled", configured: { gemini: false, openai: false, anthropic: false } };
|
|
748
749
|
res.json({
|
|
749
750
|
projectRoot: getProjectRoot ? getProjectRoot() : graph.projectRoot,
|
|
750
751
|
packageName: getPackageName ? getPackageName() : "",
|
|
@@ -757,6 +758,8 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
|
|
|
757
758
|
licenseKey: maskedKey,
|
|
758
759
|
freeFileLimit: 50,
|
|
759
760
|
sykeVersion,
|
|
761
|
+
aiProvider: aiInfo.activeProvider,
|
|
762
|
+
aiKeys: aiInfo.configured,
|
|
760
763
|
});
|
|
761
764
|
});
|
|
762
765
|
// POST /api/set-license-key — Set or remove license key via dashboard
|
|
@@ -774,6 +777,25 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
|
|
|
774
777
|
res.status(500).json({ success: false, error: err.message || "Unknown error" });
|
|
775
778
|
}
|
|
776
779
|
});
|
|
780
|
+
// POST /api/set-ai-key — Set or remove an AI provider API key
|
|
781
|
+
app.post("/api/set-ai-key", (req, res) => {
|
|
782
|
+
if (!setAIKeyFn) {
|
|
783
|
+
res.status(501).json({ success: false, error: "Not supported" });
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
const { provider, key } = req.body;
|
|
787
|
+
if (!provider || !["gemini", "openai", "anthropic"].includes(provider)) {
|
|
788
|
+
res.status(400).json({ success: false, error: "provider must be gemini, openai, or anthropic" });
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
try {
|
|
792
|
+
const result = setAIKeyFn(provider, key || null);
|
|
793
|
+
res.json(result);
|
|
794
|
+
}
|
|
795
|
+
catch (err) {
|
|
796
|
+
res.status(500).json({ success: false, error: err.message || "Unknown error" });
|
|
797
|
+
}
|
|
798
|
+
});
|
|
777
799
|
// GET /api/browse-dirs — List subdirectories for folder browser
|
|
778
800
|
app.get("/api/browse-dirs", (req, res) => {
|
|
779
801
|
const dirPath = req.query.path || (process.platform === "win32" ? "C:\\" : "/");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syke1/mcp-server",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.7",
|
|
4
4
|
"mcpName": "io.github.khalomsky/syke",
|
|
5
5
|
"description": "AI code impact analysis MCP server — dependency graphs, cascade detection, and a mandatory build gate for AI coding agents",
|
|
6
6
|
"main": "dist/index.js",
|