@syke1/mcp-server 1.3.5 → 1.3.6
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.js +17 -13
- package/dist/config.d.ts +19 -0
- package/dist/config.js +98 -0
- package/dist/graph.js +2 -0
- package/dist/index.js +2 -1
- package/dist/languages/plugin.js +5 -2
- package/dist/license/validator.js +5 -17
- package/dist/web/public/app.js +150 -2
- package/dist/web/public/index.html +15 -0
- package/dist/web/public/style.css +76 -3
- package/dist/web/server.js +1 -0
- package/package.json +1 -1
package/dist/ai/provider.js
CHANGED
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
7
7
|
exports.getAIProvider = getAIProvider;
|
|
8
8
|
exports.getProviderName = getProviderName;
|
|
9
9
|
const generative_ai_1 = require("@google/generative-ai");
|
|
10
|
+
const config_1 = require("../config");
|
|
10
11
|
// ── Gemini ──────────────────────────────────────────────────────────
|
|
11
12
|
class GeminiProvider {
|
|
12
13
|
constructor(apiKey) {
|
|
@@ -133,16 +134,19 @@ let cachedProvider = undefined;
|
|
|
133
134
|
function getAIProvider() {
|
|
134
135
|
if (cachedProvider !== undefined)
|
|
135
136
|
return cachedProvider;
|
|
136
|
-
const forced =
|
|
137
|
+
const forced = (0, config_1.getConfig)("aiProvider", "SYKE_AI_PROVIDER")?.toLowerCase();
|
|
138
|
+
const geminiKey = (0, config_1.getConfig)("geminiKey", "GEMINI_KEY");
|
|
139
|
+
const openaiKey = (0, config_1.getConfig)("openaiKey", "OPENAI_KEY");
|
|
140
|
+
const anthropicKey = (0, config_1.getConfig)("anthropicKey", "ANTHROPIC_KEY");
|
|
137
141
|
if (forced) {
|
|
138
|
-
if (forced === "gemini" &&
|
|
139
|
-
cachedProvider = new GeminiProvider(
|
|
142
|
+
if (forced === "gemini" && geminiKey) {
|
|
143
|
+
cachedProvider = new GeminiProvider(geminiKey);
|
|
140
144
|
}
|
|
141
|
-
else if (forced === "openai" &&
|
|
142
|
-
cachedProvider = new OpenAIProvider(
|
|
145
|
+
else if (forced === "openai" && openaiKey) {
|
|
146
|
+
cachedProvider = new OpenAIProvider(openaiKey);
|
|
143
147
|
}
|
|
144
|
-
else if (forced === "anthropic" &&
|
|
145
|
-
cachedProvider = new AnthropicProvider(
|
|
148
|
+
else if (forced === "anthropic" && anthropicKey) {
|
|
149
|
+
cachedProvider = new AnthropicProvider(anthropicKey);
|
|
146
150
|
}
|
|
147
151
|
else {
|
|
148
152
|
console.error(`[syke] SYKE_AI_PROVIDER=${forced} but no matching API key found`);
|
|
@@ -151,14 +155,14 @@ function getAIProvider() {
|
|
|
151
155
|
return cachedProvider;
|
|
152
156
|
}
|
|
153
157
|
// Auto-select
|
|
154
|
-
if (
|
|
155
|
-
cachedProvider = new GeminiProvider(
|
|
158
|
+
if (geminiKey) {
|
|
159
|
+
cachedProvider = new GeminiProvider(geminiKey);
|
|
156
160
|
}
|
|
157
|
-
else if (
|
|
158
|
-
cachedProvider = new OpenAIProvider(
|
|
161
|
+
else if (openaiKey) {
|
|
162
|
+
cachedProvider = new OpenAIProvider(openaiKey);
|
|
159
163
|
}
|
|
160
|
-
else if (
|
|
161
|
-
cachedProvider = new AnthropicProvider(
|
|
164
|
+
else if (anthropicKey) {
|
|
165
|
+
cachedProvider = new AnthropicProvider(anthropicKey);
|
|
162
166
|
}
|
|
163
167
|
else {
|
|
164
168
|
cachedProvider = null;
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface SykeConfig {
|
|
2
|
+
licenseKey?: string;
|
|
3
|
+
geminiKey?: string;
|
|
4
|
+
openaiKey?: string;
|
|
5
|
+
anthropicKey?: string;
|
|
6
|
+
aiProvider?: string;
|
|
7
|
+
port?: number;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Get a config value. Env var takes priority over config file.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getConfig(key: keyof SykeConfig, envVar?: string): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Get all resolved config (for logging/debug)
|
|
15
|
+
*/
|
|
16
|
+
export declare function getAllConfig(): Record<string, string | undefined>;
|
|
17
|
+
export declare const CONFIG_DIR_PATH: string;
|
|
18
|
+
export declare const CONFIG_FILE_PATH: string;
|
|
19
|
+
export {};
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.CONFIG_FILE_PATH = exports.CONFIG_DIR_PATH = void 0;
|
|
37
|
+
exports.getConfig = getConfig;
|
|
38
|
+
exports.getAllConfig = getAllConfig;
|
|
39
|
+
/**
|
|
40
|
+
* Central config reader for SYKE MCP Server.
|
|
41
|
+
*
|
|
42
|
+
* Priority: environment variables > ~/.syke/config.json
|
|
43
|
+
*
|
|
44
|
+
* IDE users (Cursor, Windsurf, etc.) set env vars in their MCP config.
|
|
45
|
+
* Terminal users (Claude Code CLI) edit ~/.syke/config.json directly.
|
|
46
|
+
*/
|
|
47
|
+
const fs = __importStar(require("fs"));
|
|
48
|
+
const path = __importStar(require("path"));
|
|
49
|
+
const os = __importStar(require("os"));
|
|
50
|
+
const CONFIG_DIR = path.join(os.homedir(), ".syke");
|
|
51
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, "config.json");
|
|
52
|
+
let cached = null;
|
|
53
|
+
/**
|
|
54
|
+
* Read ~/.syke/config.json (cached after first read)
|
|
55
|
+
*/
|
|
56
|
+
function readConfigFile() {
|
|
57
|
+
if (cached)
|
|
58
|
+
return cached;
|
|
59
|
+
try {
|
|
60
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
61
|
+
cached = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
62
|
+
return cached;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// ignore parse errors
|
|
67
|
+
}
|
|
68
|
+
cached = {};
|
|
69
|
+
return cached;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Get a config value. Env var takes priority over config file.
|
|
73
|
+
*/
|
|
74
|
+
function getConfig(key, envVar) {
|
|
75
|
+
// 1. Environment variable
|
|
76
|
+
if (envVar && process.env[envVar]) {
|
|
77
|
+
return process.env[envVar];
|
|
78
|
+
}
|
|
79
|
+
// 2. Config file
|
|
80
|
+
const file = readConfigFile();
|
|
81
|
+
const val = file[key];
|
|
82
|
+
return val !== undefined && val !== null ? String(val) : undefined;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Get all resolved config (for logging/debug)
|
|
86
|
+
*/
|
|
87
|
+
function getAllConfig() {
|
|
88
|
+
return {
|
|
89
|
+
licenseKey: getConfig("licenseKey", "SYKE_LICENSE_KEY"),
|
|
90
|
+
geminiKey: getConfig("geminiKey", "GEMINI_KEY"),
|
|
91
|
+
openaiKey: getConfig("openaiKey", "OPENAI_KEY"),
|
|
92
|
+
anthropicKey: getConfig("anthropicKey", "ANTHROPIC_KEY"),
|
|
93
|
+
aiProvider: getConfig("aiProvider", "SYKE_AI_PROVIDER"),
|
|
94
|
+
port: getConfig("port", "SYKE_WEB_PORT"),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
exports.CONFIG_DIR_PATH = CONFIG_DIR;
|
|
98
|
+
exports.CONFIG_FILE_PATH = CONFIG_FILE;
|
package/dist/graph.js
CHANGED
|
@@ -48,10 +48,12 @@ function buildGraph(projectRoot, packageName) {
|
|
|
48
48
|
const allSourceDirs = [];
|
|
49
49
|
for (const plugin of detectedPlugins) {
|
|
50
50
|
const dirs = plugin.getSourceDirs(projectRoot);
|
|
51
|
+
console.error(`[syke:debug] ${plugin.id} getSourceDirs(${projectRoot}) => ${dirs.length} dirs: ${dirs.join(", ")}`);
|
|
51
52
|
for (const dir of dirs) {
|
|
52
53
|
if (!allSourceDirs.includes(dir))
|
|
53
54
|
allSourceDirs.push(dir);
|
|
54
55
|
const sourceFiles = plugin.discoverFiles(dir);
|
|
56
|
+
console.error(`[syke:debug] ${plugin.id} discoverFiles(${dir}) => ${sourceFiles.length} files`);
|
|
55
57
|
for (const f of sourceFiles) {
|
|
56
58
|
files.add(f);
|
|
57
59
|
if (!forward.has(f))
|
package/dist/index.js
CHANGED
|
@@ -55,10 +55,11 @@ const provider_1 = require("./ai/provider");
|
|
|
55
55
|
const server_1 = require("./web/server");
|
|
56
56
|
const file_cache_1 = require("./watcher/file-cache");
|
|
57
57
|
const validator_1 = require("./license/validator");
|
|
58
|
+
const config_1 = require("./config");
|
|
58
59
|
// Configuration — auto-detect if env vars not set
|
|
59
60
|
let currentProjectRoot = process.env.SYKE_currentProjectRoot || (0, plugin_1.detectProjectRoot)();
|
|
60
61
|
let currentPackageName = process.env.SYKE_currentPackageName || (0, plugin_1.detectPackageName)(currentProjectRoot, (0, plugin_1.detectLanguages)(currentProjectRoot));
|
|
61
|
-
const WEB_PORT = parseInt(
|
|
62
|
+
const WEB_PORT = parseInt((0, config_1.getConfig)("port", "SYKE_WEB_PORT") || "3333", 10);
|
|
62
63
|
function resolveFilePath(fileArg, projectRoot, sourceDir) {
|
|
63
64
|
const srcDir = sourceDir || path.join(projectRoot, "src");
|
|
64
65
|
const srcDirName = path.basename(srcDir); // "lib" or "src"
|
package/dist/languages/plugin.js
CHANGED
|
@@ -114,7 +114,8 @@ function discoverAllFiles(rootDir, extensions, extraSkipDirs) {
|
|
|
114
114
|
try {
|
|
115
115
|
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
116
116
|
}
|
|
117
|
-
catch {
|
|
117
|
+
catch (err) {
|
|
118
|
+
console.error(`[syke] discoverAllFiles walk error for ${dir}: ${err.message}`);
|
|
118
119
|
return;
|
|
119
120
|
}
|
|
120
121
|
for (const entry of entries) {
|
|
@@ -178,7 +179,9 @@ function findSourceDirsWithFiles(root, extensions) {
|
|
|
178
179
|
}
|
|
179
180
|
}
|
|
180
181
|
}
|
|
181
|
-
catch {
|
|
182
|
+
catch (err) {
|
|
183
|
+
console.error(`[syke] findSourceDirsWithFiles error for ${root}: ${err.message}`);
|
|
184
|
+
}
|
|
182
185
|
return dirs;
|
|
183
186
|
}
|
|
184
187
|
// ── Register All Plugins ──
|
|
@@ -42,9 +42,9 @@ const path = __importStar(require("path"));
|
|
|
42
42
|
const os = __importStar(require("os"));
|
|
43
43
|
const https = __importStar(require("https"));
|
|
44
44
|
const crypto = __importStar(require("crypto"));
|
|
45
|
-
const
|
|
45
|
+
const config_1 = require("../config");
|
|
46
|
+
const CACHE_DIR = config_1.CONFIG_DIR_PATH;
|
|
46
47
|
const CACHE_FILE = path.join(CACHE_DIR, ".license-cache.json");
|
|
47
|
-
const CONFIG_FILE = path.join(CACHE_DIR, "config.json");
|
|
48
48
|
// Cache durations
|
|
49
49
|
const CACHE_MAX_AGE = 24 * 60 * 60 * 1000; // 24 hours
|
|
50
50
|
const CACHE_GRACE_PERIOD = 7 * 24 * 60 * 60 * 1000; // 7 days offline grace
|
|
@@ -82,21 +82,9 @@ function getDeviceName() {
|
|
|
82
82
|
* Read license key from env var or config file
|
|
83
83
|
*/
|
|
84
84
|
function getLicenseKey() {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return envKey;
|
|
89
|
-
// 2. Config file
|
|
90
|
-
try {
|
|
91
|
-
if (fs.existsSync(CONFIG_FILE)) {
|
|
92
|
-
const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf-8"));
|
|
93
|
-
if (config.licenseKey && config.licenseKey.startsWith("SYKE-"))
|
|
94
|
-
return config.licenseKey;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
catch {
|
|
98
|
-
// ignore
|
|
99
|
-
}
|
|
85
|
+
const key = (0, config_1.getConfig)("licenseKey", "SYKE_LICENSE_KEY");
|
|
86
|
+
if (key && key.startsWith("SYKE-"))
|
|
87
|
+
return key;
|
|
100
88
|
return null;
|
|
101
89
|
}
|
|
102
90
|
/**
|
package/dist/web/public/app.js
CHANGED
|
@@ -49,8 +49,25 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|
|
49
49
|
setupTabs();
|
|
50
50
|
setupProjectModal();
|
|
51
51
|
initSSE();
|
|
52
|
+
startHealthCheck();
|
|
52
53
|
});
|
|
53
54
|
|
|
55
|
+
// Periodic server health check (catches server down even without SSE)
|
|
56
|
+
let healthCheckTimer = null;
|
|
57
|
+
function startHealthCheck() {
|
|
58
|
+
if (healthCheckTimer) return;
|
|
59
|
+
healthCheckTimer = setInterval(async () => {
|
|
60
|
+
try {
|
|
61
|
+
const res = await fetch("/api/project-info", { signal: AbortSignal.timeout(3000) });
|
|
62
|
+
if (res.ok) {
|
|
63
|
+
// Server is alive — if overlay was showing, hideServerOffline handles reconnection
|
|
64
|
+
}
|
|
65
|
+
} catch (e) {
|
|
66
|
+
showServerOffline();
|
|
67
|
+
}
|
|
68
|
+
}, 10000); // Check every 10 seconds
|
|
69
|
+
}
|
|
70
|
+
|
|
54
71
|
// ═══════════════════════════════════════════
|
|
55
72
|
// WELCOME OVERLAY
|
|
56
73
|
// ═══════════════════════════════════════════
|
|
@@ -2116,12 +2133,22 @@ async function initSSE() {
|
|
|
2116
2133
|
updateSSEStatus("PROJECT LOADED", "connected");
|
|
2117
2134
|
});
|
|
2118
2135
|
|
|
2119
|
-
sseSource.onerror = () => {
|
|
2136
|
+
sseSource.onerror = async () => {
|
|
2120
2137
|
console.warn("[SYKE:SSE] Connection error");
|
|
2121
2138
|
updateSSEStatus("OFFLINE", "offline");
|
|
2122
2139
|
sseSource.close();
|
|
2123
2140
|
sseSource = null;
|
|
2124
2141
|
if (sseBlocked) return; // Don't reconnect if Pro-only block
|
|
2142
|
+
|
|
2143
|
+
// Check if server is actually down (not just SSE hiccup)
|
|
2144
|
+
try {
|
|
2145
|
+
const probe = await fetch("/api/project-info");
|
|
2146
|
+
if (!probe.ok) throw new Error("not ok");
|
|
2147
|
+
} catch (e) {
|
|
2148
|
+
// Server is truly down — show offline overlay
|
|
2149
|
+
showServerOffline();
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2125
2152
|
if (sseReconnectTimer) clearTimeout(sseReconnectTimer);
|
|
2126
2153
|
sseReconnectTimer = setTimeout(initSSE, 3000);
|
|
2127
2154
|
};
|
|
@@ -2262,6 +2289,124 @@ function setupResizeHandle() {
|
|
|
2262
2289
|
// ═══════════════════════════════════════════
|
|
2263
2290
|
// PROJECT SELECTOR
|
|
2264
2291
|
// ═══════════════════════════════════════════
|
|
2292
|
+
let licenseTimerInterval = null;
|
|
2293
|
+
|
|
2294
|
+
function updateLicenseBadge(plan, expiresAt) {
|
|
2295
|
+
const badge = document.getElementById("license-badge");
|
|
2296
|
+
if (!badge) return;
|
|
2297
|
+
|
|
2298
|
+
const planEl = badge.querySelector(".license-plan");
|
|
2299
|
+
const timerEl = badge.querySelector(".license-timer");
|
|
2300
|
+
|
|
2301
|
+
// Clear previous timer
|
|
2302
|
+
if (licenseTimerInterval) {
|
|
2303
|
+
clearInterval(licenseTimerInterval);
|
|
2304
|
+
licenseTimerInterval = null;
|
|
2305
|
+
}
|
|
2306
|
+
|
|
2307
|
+
badge.className = "license-badge";
|
|
2308
|
+
|
|
2309
|
+
if (plan === "pro" && expiresAt) {
|
|
2310
|
+
const expiry = new Date(expiresAt);
|
|
2311
|
+
const now = new Date();
|
|
2312
|
+
const diffMs = expiry - now;
|
|
2313
|
+
|
|
2314
|
+
if (diffMs <= 0) {
|
|
2315
|
+
// Expired
|
|
2316
|
+
badge.classList.add("expired");
|
|
2317
|
+
planEl.textContent = "EXPIRED";
|
|
2318
|
+
timerEl.textContent = "";
|
|
2319
|
+
} else {
|
|
2320
|
+
const totalDays = Math.ceil(diffMs / (1000 * 60 * 60 * 24));
|
|
2321
|
+
|
|
2322
|
+
if (totalDays <= 3) {
|
|
2323
|
+
// Trial or near-expiry
|
|
2324
|
+
badge.classList.add("trial-urgent");
|
|
2325
|
+
planEl.textContent = "TRIAL";
|
|
2326
|
+
} else if (totalDays <= 14) {
|
|
2327
|
+
badge.classList.add("trial");
|
|
2328
|
+
planEl.textContent = "TRIAL";
|
|
2329
|
+
} else {
|
|
2330
|
+
badge.classList.add("pro");
|
|
2331
|
+
planEl.textContent = "PRO";
|
|
2332
|
+
}
|
|
2333
|
+
|
|
2334
|
+
// Live countdown
|
|
2335
|
+
function tick() {
|
|
2336
|
+
const remaining = new Date(expiresAt) - new Date();
|
|
2337
|
+
if (remaining <= 0) {
|
|
2338
|
+
timerEl.textContent = "EXPIRED";
|
|
2339
|
+
badge.className = "license-badge expired";
|
|
2340
|
+
planEl.textContent = "EXPIRED";
|
|
2341
|
+
clearInterval(licenseTimerInterval);
|
|
2342
|
+
return;
|
|
2343
|
+
}
|
|
2344
|
+
const d = Math.floor(remaining / (1000 * 60 * 60 * 24));
|
|
2345
|
+
const h = Math.floor((remaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
|
|
2346
|
+
const m = Math.floor((remaining % (1000 * 60 * 60)) / (1000 * 60));
|
|
2347
|
+
const s = Math.floor((remaining % (1000 * 60)) / 1000);
|
|
2348
|
+
|
|
2349
|
+
if (d > 0) {
|
|
2350
|
+
timerEl.textContent = `D-${d} ${String(h).padStart(2,"0")}:${String(m).padStart(2,"0")}:${String(s).padStart(2,"0")}`;
|
|
2351
|
+
} else {
|
|
2352
|
+
timerEl.textContent = `${String(h).padStart(2,"0")}:${String(m).padStart(2,"0")}:${String(s).padStart(2,"0")}`;
|
|
2353
|
+
}
|
|
2354
|
+
}
|
|
2355
|
+
tick();
|
|
2356
|
+
licenseTimerInterval = setInterval(tick, 1000);
|
|
2357
|
+
}
|
|
2358
|
+
} else if (plan === "pro") {
|
|
2359
|
+
// Pro without expiry (permanent or subscription)
|
|
2360
|
+
badge.classList.add("pro");
|
|
2361
|
+
planEl.textContent = "PRO";
|
|
2362
|
+
timerEl.textContent = "";
|
|
2363
|
+
} else {
|
|
2364
|
+
// Free
|
|
2365
|
+
badge.classList.add("free");
|
|
2366
|
+
planEl.textContent = "FREE";
|
|
2367
|
+
timerEl.textContent = "";
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
let offlineRetryTimer = null;
|
|
2372
|
+
|
|
2373
|
+
function showServerOffline() {
|
|
2374
|
+
const overlay = document.getElementById("server-offline-overlay");
|
|
2375
|
+
if (overlay) overlay.classList.remove("hidden");
|
|
2376
|
+
// Hide other content
|
|
2377
|
+
const topbar = document.getElementById("topbar");
|
|
2378
|
+
if (topbar) topbar.style.opacity = "0.2";
|
|
2379
|
+
|
|
2380
|
+
// Auto-retry every 3 seconds
|
|
2381
|
+
if (!offlineRetryTimer) {
|
|
2382
|
+
offlineRetryTimer = setInterval(async () => {
|
|
2383
|
+
const retryEl = document.getElementById("offline-retry-status");
|
|
2384
|
+
try {
|
|
2385
|
+
const res = await fetch("/api/project-info");
|
|
2386
|
+
if (res.ok) {
|
|
2387
|
+
// Server is back!
|
|
2388
|
+
clearInterval(offlineRetryTimer);
|
|
2389
|
+
offlineRetryTimer = null;
|
|
2390
|
+
hideServerOffline();
|
|
2391
|
+
await loadProjectInfo();
|
|
2392
|
+
await loadGraph();
|
|
2393
|
+
await loadHubFiles();
|
|
2394
|
+
initSSE();
|
|
2395
|
+
}
|
|
2396
|
+
} catch (e) {
|
|
2397
|
+
if (retryEl) retryEl.textContent = "Retrying connection... (" + new Date().toLocaleTimeString() + ")";
|
|
2398
|
+
}
|
|
2399
|
+
}, 3000);
|
|
2400
|
+
}
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
function hideServerOffline() {
|
|
2404
|
+
const overlay = document.getElementById("server-offline-overlay");
|
|
2405
|
+
if (overlay) overlay.classList.add("hidden");
|
|
2406
|
+
const topbar = document.getElementById("topbar");
|
|
2407
|
+
if (topbar) topbar.style.opacity = "1";
|
|
2408
|
+
}
|
|
2409
|
+
|
|
2265
2410
|
async function loadProjectInfo() {
|
|
2266
2411
|
try {
|
|
2267
2412
|
const res = await fetch("/api/project-info");
|
|
@@ -2274,8 +2419,11 @@ async function loadProjectInfo() {
|
|
|
2274
2419
|
el.textContent = short;
|
|
2275
2420
|
el.title = info.projectRoot + " | " + info.languages.join(", ") + " | " + info.fileCount + " files";
|
|
2276
2421
|
}
|
|
2422
|
+
hideServerOffline();
|
|
2423
|
+
updateLicenseBadge(info.plan, info.expiresAt);
|
|
2277
2424
|
} catch (e) {
|
|
2278
2425
|
console.warn("[SYKE] Failed to load project info:", e);
|
|
2426
|
+
showServerOffline();
|
|
2279
2427
|
}
|
|
2280
2428
|
}
|
|
2281
2429
|
|
|
@@ -2343,7 +2491,7 @@ async function browseDir(dirPath) {
|
|
|
2343
2491
|
});
|
|
2344
2492
|
}
|
|
2345
2493
|
} catch (e) {
|
|
2346
|
-
if (listEl) listEl.innerHTML = '<div class="browse-empty">
|
|
2494
|
+
if (listEl) listEl.innerHTML = '<div class="browse-empty">CONNECTION LOST — server may have restarted</div>';
|
|
2347
2495
|
}
|
|
2348
2496
|
}
|
|
2349
2497
|
|
|
@@ -24,6 +24,10 @@
|
|
|
24
24
|
<span id="current-project" class="project-path">Loading...</span>
|
|
25
25
|
<button id="btn-change-project" class="top-btn" title="Switch project">OPEN</button>
|
|
26
26
|
</div>
|
|
27
|
+
<div id="license-badge" class="license-badge free">
|
|
28
|
+
<span class="license-plan">FREE</span>
|
|
29
|
+
<span class="license-timer"></span>
|
|
30
|
+
</div>
|
|
27
31
|
<div class="top-controls">
|
|
28
32
|
<button id="btn-cycles" class="top-btn" title="Detect circular dependencies">CYCLES</button>
|
|
29
33
|
<button id="btn-stats" class="top-btn" title="Toggle statistics panel">STATS</button>
|
|
@@ -57,6 +61,17 @@
|
|
|
57
61
|
</div>
|
|
58
62
|
<div id="3d-graph"></div>
|
|
59
63
|
<!-- Welcome Overlay (shown when no project loaded) -->
|
|
64
|
+
<div id="server-offline-overlay" class="welcome-overlay hidden">
|
|
65
|
+
<div class="welcome-content">
|
|
66
|
+
<div class="welcome-logo">
|
|
67
|
+
<div class="welcome-pulse-ring offline-pulse"><span class="welcome-pulse-dot offline-dot"></span></div>
|
|
68
|
+
<span class="welcome-title">SYKE</span>
|
|
69
|
+
</div>
|
|
70
|
+
<p class="welcome-subtitle" style="color:#ff5f57">SERVER OFFLINE</p>
|
|
71
|
+
<p class="welcome-msg">MCP server is not running.<br>Start Claude Code to launch the server.</p>
|
|
72
|
+
<div id="offline-retry-status" style="font-family:var(--font-mono,'JetBrains Mono',monospace);font-size:11px;color:rgba(255,255,255,0.3);margin-top:12px">Retrying connection...</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
60
75
|
<div id="welcome-overlay" class="welcome-overlay hidden">
|
|
61
76
|
<div class="welcome-content">
|
|
62
77
|
<div class="welcome-logo">
|
|
@@ -123,6 +123,70 @@ body {
|
|
|
123
123
|
text-transform: uppercase;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/* ── License Badge ── */
|
|
127
|
+
.license-badge {
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: 8px;
|
|
131
|
+
padding: 5px 16px;
|
|
132
|
+
border-radius: 20px;
|
|
133
|
+
font-size: 11px;
|
|
134
|
+
font-weight: 700;
|
|
135
|
+
letter-spacing: 2px;
|
|
136
|
+
border: 1px solid;
|
|
137
|
+
transition: all 0.3s;
|
|
138
|
+
cursor: default;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.license-badge.free {
|
|
142
|
+
color: #8899aa;
|
|
143
|
+
border-color: rgba(136,153,170,0.4);
|
|
144
|
+
background: rgba(136,153,170,0.12);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.license-badge.pro {
|
|
148
|
+
color: #ffd700;
|
|
149
|
+
border-color: rgba(255,215,0,0.4);
|
|
150
|
+
background: rgba(255,215,0,0.08);
|
|
151
|
+
text-shadow: 0 0 8px rgba(255,215,0,0.3);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.license-badge.trial {
|
|
155
|
+
color: var(--risk-medium);
|
|
156
|
+
border-color: rgba(255,159,10,0.4);
|
|
157
|
+
background: rgba(255,159,10,0.08);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.license-badge.trial-urgent {
|
|
161
|
+
color: var(--risk-high);
|
|
162
|
+
border-color: rgba(255,45,85,0.4);
|
|
163
|
+
background: rgba(255,45,85,0.08);
|
|
164
|
+
animation: license-urgent 1.5s ease-in-out infinite;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.license-badge.expired {
|
|
168
|
+
color: var(--risk-high);
|
|
169
|
+
border-color: rgba(255,45,85,0.4);
|
|
170
|
+
background: rgba(255,45,85,0.1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
@keyframes license-urgent {
|
|
174
|
+
0%, 100% { opacity: 1; }
|
|
175
|
+
50% { opacity: 0.6; }
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.license-plan {
|
|
179
|
+
font-weight: 700;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.license-timer {
|
|
183
|
+
font-size: 9px;
|
|
184
|
+
font-weight: 400;
|
|
185
|
+
letter-spacing: 1px;
|
|
186
|
+
opacity: 0.85;
|
|
187
|
+
font-variant-numeric: tabular-nums;
|
|
188
|
+
}
|
|
189
|
+
|
|
126
190
|
.top-controls {
|
|
127
191
|
display: flex;
|
|
128
192
|
gap: 8px;
|
|
@@ -1349,8 +1413,14 @@ main {
|
|
|
1349
1413
|
text-shadow: 0 0 8px rgba(48,209,88,0.5);
|
|
1350
1414
|
}
|
|
1351
1415
|
.sse-indicator.offline {
|
|
1352
|
-
color:
|
|
1353
|
-
border-color:
|
|
1416
|
+
color: #ff5f57;
|
|
1417
|
+
border-color: #ff5f57;
|
|
1418
|
+
text-shadow: 0 0 8px rgba(255,95,87,0.5);
|
|
1419
|
+
animation: offline-blink 1.5s ease-in-out infinite;
|
|
1420
|
+
}
|
|
1421
|
+
@keyframes offline-blink {
|
|
1422
|
+
0%, 100% { opacity: 1; }
|
|
1423
|
+
50% { opacity: 0.4; }
|
|
1354
1424
|
}
|
|
1355
1425
|
.sse-indicator.warning {
|
|
1356
1426
|
color: var(--risk-medium);
|
|
@@ -1391,7 +1461,7 @@ main {
|
|
|
1391
1461
|
.pulse-dot.analyzing { background: var(--accent); box-shadow: 0 0 12px var(--accent); }
|
|
1392
1462
|
.pulse-dot.danger { background: var(--risk-high); box-shadow: 0 0 12px var(--risk-high); }
|
|
1393
1463
|
.pulse-dot.critical { background: #ff0040; box-shadow: 0 0 16px #ff0040; }
|
|
1394
|
-
.pulse-dot.offline { background:
|
|
1464
|
+
.pulse-dot.offline { background: #ff5f57; box-shadow: 0 0 8px rgba(255,95,87,0.5); }
|
|
1395
1465
|
|
|
1396
1466
|
/* ═══════════════════════════════════════════ */
|
|
1397
1467
|
/* Realtime Monitor Panel */
|
|
@@ -1888,6 +1958,9 @@ main {
|
|
|
1888
1958
|
animation: pulse-glow 2s ease-in-out infinite;
|
|
1889
1959
|
}
|
|
1890
1960
|
|
|
1961
|
+
.offline-pulse { border-color: #ff5f57; }
|
|
1962
|
+
.offline-dot { background: #ff5f57; box-shadow: 0 0 12px #ff5f57; }
|
|
1963
|
+
|
|
1891
1964
|
.welcome-title {
|
|
1892
1965
|
font-size: 42px;
|
|
1893
1966
|
font-weight: 700;
|
package/dist/web/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syke1/mcp-server",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
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",
|