@syke1/mcp-server 1.4.1 → 1.4.2

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/config.d.ts CHANGED
@@ -14,6 +14,10 @@ export declare function getConfig(key: keyof SykeConfig, envVar?: string): strin
14
14
  * Get all resolved config (for logging/debug)
15
15
  */
16
16
  export declare function getAllConfig(): Record<string, string | undefined>;
17
+ /**
18
+ * Set a config value in ~/.syke/config.json
19
+ */
20
+ export declare function setConfig(key: keyof SykeConfig, value: string | null): void;
17
21
  export declare const CONFIG_DIR_PATH: string;
18
22
  export declare const CONFIG_FILE_PATH: string;
19
23
  export {};
package/dist/config.js CHANGED
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.CONFIG_FILE_PATH = exports.CONFIG_DIR_PATH = void 0;
37
37
  exports.getConfig = getConfig;
38
38
  exports.getAllConfig = getAllConfig;
39
+ exports.setConfig = setConfig;
39
40
  /**
40
41
  * Central config reader for SYKE MCP Server.
41
42
  *
@@ -94,5 +95,27 @@ function getAllConfig() {
94
95
  port: getConfig("port", "SYKE_WEB_PORT"),
95
96
  };
96
97
  }
98
+ /**
99
+ * Set a config value in ~/.syke/config.json
100
+ */
101
+ function setConfig(key, value) {
102
+ const file = readConfigFile();
103
+ if (value === null) {
104
+ delete file[key];
105
+ }
106
+ else {
107
+ file[key] = value;
108
+ }
109
+ try {
110
+ if (!fs.existsSync(CONFIG_DIR)) {
111
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
112
+ }
113
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(file, null, 2));
114
+ cached = file;
115
+ }
116
+ catch {
117
+ // ignore write errors
118
+ }
119
+ }
97
120
  exports.CONFIG_DIR_PATH = CONFIG_DIR;
98
121
  exports.CONFIG_FILE_PATH = CONFIG_FILE;
package/dist/index.js CHANGED
@@ -121,7 +121,7 @@ async function main() {
121
121
  };
122
122
  process.on("SIGINT", shutdown);
123
123
  process.on("SIGTERM", shutdown);
124
- const server = new index_js_1.Server({ name: "syke", version: "1.4.1" }, { capabilities: { tools: {} } });
124
+ const server = new index_js_1.Server({ name: "syke", version: "1.4.2" }, { capabilities: { tools: {} } });
125
125
  // List tools
126
126
  server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
127
127
  tools: [
@@ -558,7 +558,33 @@ async function main() {
558
558
  }
559
559
  // Start Express web server with file cache for SSE (only if project detected)
560
560
  if (currentProjectRoot) {
561
- const { app: webApp, setFileCache: setWebFileCache } = (0, server_1.createWebServer)(() => (0, graph_1.getGraph)(currentProjectRoot, currentPackageName), fileCache, switchProject, () => currentProjectRoot, () => currentPackageName, () => licenseStatus, () => !!(0, provider_1.getAIProvider)());
561
+ const { app: webApp, setFileCache: setWebFileCache } = (0, server_1.createWebServer)(() => (0, graph_1.getGraph)(currentProjectRoot, currentPackageName), fileCache, switchProject, () => currentProjectRoot, () => currentPackageName, () => licenseStatus, () => !!(0, provider_1.getAIProvider)(), async (key) => {
562
+ // Stop existing heartbeat/session
563
+ await (0, validator_1.stopAndDeactivate)();
564
+ if (key && key.startsWith("SYKE-")) {
565
+ (0, config_1.setConfig)("licenseKey", key);
566
+ (0, validator_1.clearLicenseCache)(); // clear stale cache from previous key
567
+ try {
568
+ licenseStatus = await (0, validator_1.checkLicense)();
569
+ }
570
+ catch {
571
+ licenseStatus = { plan: "free", source: "default" };
572
+ }
573
+ if (licenseStatus.plan === "pro") {
574
+ return { success: true, plan: licenseStatus.plan, expiresAt: licenseStatus.expiresAt };
575
+ }
576
+ else {
577
+ return { success: false, error: licenseStatus.error || "Invalid or expired key" };
578
+ }
579
+ }
580
+ else {
581
+ // Remove key
582
+ (0, config_1.setConfig)("licenseKey", null);
583
+ (0, validator_1.clearLicenseCache)();
584
+ licenseStatus = { plan: "free", source: "default" };
585
+ return { success: true, plan: "free" };
586
+ }
587
+ });
562
588
  webServerHandle = { setFileCache: setWebFileCache };
563
589
  webApp.listen(WEB_PORT, () => {
564
590
  const dashUrl = `http://localhost:${WEB_PORT}`;
@@ -587,7 +613,7 @@ main().catch((err) => {
587
613
  * See: https://smithery.ai/docs/deploy#sandbox-server
588
614
  */
589
615
  function createSandboxServer() {
590
- const sandboxServer = new index_js_1.Server({ name: "syke", version: "1.4.1" }, { capabilities: { tools: {} } });
616
+ const sandboxServer = new index_js_1.Server({ name: "syke", version: "1.4.2" }, { capabilities: { tools: {} } });
591
617
  sandboxServer.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => ({
592
618
  tools: [
593
619
  {
@@ -17,6 +17,10 @@ export declare function stopAndDeactivate(): Promise<void>;
17
17
  * Get current device fingerprint (exported for use by index.ts)
18
18
  */
19
19
  export declare function getDeviceId(): string;
20
+ /**
21
+ * Clear license cache file (called when license key changes)
22
+ */
23
+ export declare function clearLicenseCache(): void;
20
24
  /**
21
25
  * Main license validation — called on MCP server startup
22
26
  */
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.startHeartbeat = startHeartbeat;
37
37
  exports.stopAndDeactivate = stopAndDeactivate;
38
38
  exports.getDeviceId = getDeviceId;
39
+ exports.clearLicenseCache = clearLicenseCache;
39
40
  exports.checkLicense = checkLicense;
40
41
  const fs = __importStar(require("fs"));
41
42
  const path = __importStar(require("path"));
@@ -219,6 +220,19 @@ async function stopAndDeactivate() {
219
220
  function getDeviceId() {
220
221
  return getDeviceFingerprint();
221
222
  }
223
+ /**
224
+ * Clear license cache file (called when license key changes)
225
+ */
226
+ function clearLicenseCache() {
227
+ try {
228
+ if (fs.existsSync(CACHE_FILE)) {
229
+ fs.unlinkSync(CACHE_FILE);
230
+ }
231
+ }
232
+ catch {
233
+ // silently fail
234
+ }
235
+ }
222
236
  /**
223
237
  * Main license validation — called on MCP server startup
224
238
  */
@@ -463,6 +463,7 @@ document.addEventListener("DOMContentLoaded", async () => {
463
463
  setupTabs();
464
464
  setupSettings();
465
465
  setupProjectModal();
466
+ setupLicenseModal();
466
467
  setupFileTree();
467
468
  initSSE();
468
469
  startHealthCheck();
@@ -3661,6 +3662,7 @@ async function loadProjectInfo() {
3661
3662
  }
3662
3663
  hideServerOffline();
3663
3664
  updateLicenseBadge(info.plan, info.expiresAt);
3665
+ updateLicenseButton(info.plan);
3664
3666
  // Bottom bar: fetch version from npm registry
3665
3667
  updateBottomBar();
3666
3668
  } catch (e) {
@@ -3802,6 +3804,106 @@ async function switchProject(projectPath) {
3802
3804
  }
3803
3805
  }
3804
3806
 
3807
+ // ══════════════════════════════════════════════════════════════
3808
+ // LICENSE MODAL
3809
+ // ══════════════════════════════════════════════════════════════
3810
+ function setupLicenseModal() {
3811
+ const btn = document.getElementById("btn-license");
3812
+ const modal = document.getElementById("license-modal");
3813
+ const input = document.getElementById("license-key-input");
3814
+ const activateBtn = document.getElementById("btn-license-activate");
3815
+ const deactivateBtn = document.getElementById("btn-license-deactivate");
3816
+ const cancelBtn = document.getElementById("btn-license-cancel");
3817
+ const statusEl = document.getElementById("license-modal-status");
3818
+ if (!btn || !modal) return;
3819
+
3820
+ function openModal() {
3821
+ modal.classList.remove("hidden");
3822
+ input.value = "";
3823
+ statusEl.textContent = "";
3824
+ statusEl.className = "";
3825
+ input.focus();
3826
+ }
3827
+ function closeModal() {
3828
+ modal.classList.add("hidden");
3829
+ }
3830
+
3831
+ btn.addEventListener("click", openModal);
3832
+ cancelBtn.addEventListener("click", closeModal);
3833
+ modal.addEventListener("click", (e) => { if (e.target === modal) closeModal(); });
3834
+
3835
+ activateBtn.addEventListener("click", async () => {
3836
+ const key = input.value.trim();
3837
+ if (!key || !key.startsWith("SYKE-")) {
3838
+ statusEl.className = "error";
3839
+ statusEl.textContent = "Key must start with SYKE-";
3840
+ return;
3841
+ }
3842
+ statusEl.className = "loading";
3843
+ statusEl.textContent = "VALIDATING...";
3844
+ activateBtn.disabled = true;
3845
+
3846
+ try {
3847
+ const res = await fetch("/api/set-license-key", {
3848
+ method: "POST",
3849
+ headers: { "Content-Type": "application/json" },
3850
+ body: JSON.stringify({ key }),
3851
+ });
3852
+ const data = await res.json();
3853
+ if (data.success && data.plan === "pro") {
3854
+ statusEl.className = "success";
3855
+ statusEl.textContent = "PRO ACTIVATED";
3856
+ updateLicenseBadge("pro", data.expiresAt);
3857
+ updateLicenseButton("pro");
3858
+ setTimeout(closeModal, 1200);
3859
+ } else {
3860
+ statusEl.className = "error";
3861
+ statusEl.textContent = data.error || "Activation failed";
3862
+ }
3863
+ } catch (err) {
3864
+ statusEl.className = "error";
3865
+ statusEl.textContent = "Network error";
3866
+ }
3867
+ activateBtn.disabled = false;
3868
+ });
3869
+
3870
+ deactivateBtn.addEventListener("click", async () => {
3871
+ if (!confirm("Remove license key? Dashboard will switch to Free mode.")) return;
3872
+ statusEl.className = "loading";
3873
+ statusEl.textContent = "REMOVING...";
3874
+
3875
+ try {
3876
+ const res = await fetch("/api/set-license-key", {
3877
+ method: "POST",
3878
+ headers: { "Content-Type": "application/json" },
3879
+ body: JSON.stringify({ key: null }),
3880
+ });
3881
+ const data = await res.json();
3882
+ if (data.success) {
3883
+ statusEl.className = "success";
3884
+ statusEl.textContent = "KEY REMOVED";
3885
+ updateLicenseBadge("free", null);
3886
+ updateLicenseButton("free");
3887
+ setTimeout(closeModal, 800);
3888
+ }
3889
+ } catch {
3890
+ statusEl.className = "error";
3891
+ statusEl.textContent = "Failed to remove key";
3892
+ }
3893
+ });
3894
+
3895
+ input.addEventListener("keydown", (e) => {
3896
+ if (e.key === "Enter") activateBtn.click();
3897
+ if (e.key === "Escape") closeModal();
3898
+ });
3899
+ }
3900
+
3901
+ function updateLicenseButton(plan) {
3902
+ const btn = document.getElementById("btn-license");
3903
+ if (!btn) return;
3904
+ btn.textContent = plan === "pro" ? "LICENSED" : "LICENSE";
3905
+ }
3906
+
3805
3907
  function setupProjectModal() {
3806
3908
  const openBtn = document.getElementById("btn-change-project");
3807
3909
  const modal = document.getElementById("project-modal");
@@ -20,6 +20,7 @@
20
20
  <span class="logo-sub">CODE IMPACT RADAR</span>
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
+ <button id="btn-license" class="top-btn license-btn">LICENSE</button>
23
24
  </div>
24
25
  <div class="project-selector">
25
26
  <span id="current-project" class="project-path">Loading...</span>
@@ -436,6 +437,24 @@
436
437
  </div>
437
438
  </div>
438
439
 
440
+ <!-- License Modal -->
441
+ <div id="license-modal" class="hidden">
442
+ <div class="license-modal-panel">
443
+ <h3>ACTIVATE LICENSE</h3>
444
+ <p class="license-modal-desc">Enter your SYKE API key to unlock Pro features.</p>
445
+ <div class="license-input-row">
446
+ <input type="text" id="license-key-input" placeholder="SYKE-XXXX-XXXX-XXXX-XXXX" spellcheck="false" autocomplete="off">
447
+ </div>
448
+ <div id="license-modal-status"></div>
449
+ <div class="license-modal-actions">
450
+ <button id="btn-license-activate">ACTIVATE</button>
451
+ <button id="btn-license-deactivate" class="license-deactivate-btn">REMOVE KEY</button>
452
+ <button id="btn-license-cancel">CANCEL</button>
453
+ </div>
454
+ <p class="license-modal-hint">Get your key at <a href="https://syke.cloud/dashboard" target="_blank">syke.cloud/dashboard</a></p>
455
+ </div>
456
+ </div>
457
+
439
458
  <!-- Bottom status bar -->
440
459
  <div id="bottom-bar">
441
460
  <span id="bottom-info">SYKE v--- · ---</span>
@@ -1941,6 +1941,125 @@ main {
1941
1941
  text-align: left;
1942
1942
  }
1943
1943
 
1944
+ /* ═══════════════════════════════════════════ */
1945
+ /* License Modal */
1946
+ /* ═══════════════════════════════════════════ */
1947
+ #license-modal {
1948
+ position: fixed;
1949
+ inset: 0;
1950
+ background: rgba(5,10,24,0.85);
1951
+ z-index: 400;
1952
+ display: flex;
1953
+ align-items: center;
1954
+ justify-content: center;
1955
+ backdrop-filter: blur(4px);
1956
+ }
1957
+ #license-modal.hidden { display: none; }
1958
+
1959
+ .license-modal-panel {
1960
+ background: var(--bg-secondary);
1961
+ border: 1px solid var(--accent-dim);
1962
+ border-radius: 6px;
1963
+ padding: 24px 32px;
1964
+ min-width: 420px;
1965
+ max-width: 480px;
1966
+ box-shadow: var(--glow-cyan), 0 16px 64px rgba(0,0,0,0.5);
1967
+ }
1968
+ .license-modal-panel h3 {
1969
+ font-size: 12px;
1970
+ color: var(--accent);
1971
+ letter-spacing: 3px;
1972
+ margin-bottom: 8px;
1973
+ }
1974
+ .license-modal-desc {
1975
+ font-size: 11px;
1976
+ color: var(--text-muted);
1977
+ margin-bottom: 16px;
1978
+ }
1979
+ .license-input-row {
1980
+ margin-bottom: 12px;
1981
+ }
1982
+ .license-input-row input {
1983
+ width: 100%;
1984
+ padding: 10px 14px;
1985
+ background: rgba(0,0,0,0.5);
1986
+ border: 1px solid var(--border);
1987
+ border-radius: 3px;
1988
+ color: var(--text-primary);
1989
+ font-family: var(--font-mono);
1990
+ font-size: 13px;
1991
+ letter-spacing: 1.5px;
1992
+ outline: none;
1993
+ transition: border-color 0.2s;
1994
+ box-sizing: border-box;
1995
+ }
1996
+ .license-input-row input:focus {
1997
+ border-color: var(--accent);
1998
+ box-shadow: 0 0 8px rgba(0,212,255,0.15);
1999
+ }
2000
+ .license-input-row input::placeholder {
2001
+ color: var(--text-muted);
2002
+ letter-spacing: 2px;
2003
+ font-size: 11px;
2004
+ }
2005
+ #license-modal-status {
2006
+ min-height: 20px;
2007
+ font-size: 11px;
2008
+ margin-bottom: 12px;
2009
+ }
2010
+ #license-modal-status.success { color: var(--green); }
2011
+ #license-modal-status.error { color: #ff5f57; }
2012
+ #license-modal-status.loading { color: var(--accent); }
2013
+
2014
+ .license-modal-actions {
2015
+ display: flex;
2016
+ gap: 8px;
2017
+ margin-bottom: 12px;
2018
+ }
2019
+ .license-modal-actions button {
2020
+ padding: 8px 18px;
2021
+ border: 1px solid var(--accent-dim);
2022
+ border-radius: 3px;
2023
+ background: transparent;
2024
+ color: var(--accent);
2025
+ font-family: var(--font-mono);
2026
+ font-size: 10px;
2027
+ letter-spacing: 2px;
2028
+ cursor: pointer;
2029
+ transition: all 0.2s;
2030
+ }
2031
+ .license-modal-actions button:hover {
2032
+ background: rgba(0,212,255,0.08);
2033
+ border-color: var(--accent);
2034
+ }
2035
+ #btn-license-activate {
2036
+ background: rgba(0,212,255,0.1) !important;
2037
+ border-color: var(--accent) !important;
2038
+ font-weight: 700;
2039
+ }
2040
+ .license-deactivate-btn {
2041
+ color: #ff5f57 !important;
2042
+ border-color: rgba(255,95,87,0.3) !important;
2043
+ }
2044
+ .license-deactivate-btn:hover {
2045
+ background: rgba(255,95,87,0.08) !important;
2046
+ border-color: #ff5f57 !important;
2047
+ }
2048
+ .license-modal-hint {
2049
+ font-size: 10px;
2050
+ color: var(--text-muted);
2051
+ }
2052
+ .license-modal-hint a {
2053
+ color: var(--accent);
2054
+ text-decoration: none;
2055
+ }
2056
+
2057
+ .license-btn {
2058
+ font-size: 9px !important;
2059
+ padding: 2px 8px !important;
2060
+ letter-spacing: 1.5px;
2061
+ }
2062
+
1944
2063
  /* ═══════════════════════════════════════════ */
1945
2064
  /* Project Switch Modal */
1946
2065
  /* ═══════════════════════════════════════════ */
@@ -35,4 +35,9 @@ export declare function createWebServer(getGraphFn: () => DependencyGraph, initi
35
35
  expiresAt?: string;
36
36
  error?: string;
37
37
  source?: string;
38
- }, hasAIKeyFn?: () => boolean): WebServerHandle;
38
+ }, hasAIKeyFn?: () => boolean, setLicenseKeyFn?: (key: string | null) => Promise<{
39
+ success: boolean;
40
+ plan?: string;
41
+ expiresAt?: string;
42
+ error?: string;
43
+ }>): WebServerHandle;
@@ -226,7 +226,7 @@ function acknowledgeWarnings() {
226
226
  function getAllWarnings() {
227
227
  return [...warningStore];
228
228
  }
229
- function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProjectRoot, getPackageName, getLicenseStatus, hasAIKeyFn) {
229
+ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProjectRoot, getPackageName, getLicenseStatus, hasAIKeyFn, setLicenseKeyFn) {
230
230
  const app = (0, express_1.default)();
231
231
  app.use(express_1.default.json());
232
232
  // Serve static files from public/
@@ -752,6 +752,21 @@ function createWebServer(getGraphFn, initialFileCache, switchProjectFn, getProje
752
752
  sykeVersion,
753
753
  });
754
754
  });
755
+ // POST /api/set-license-key — Set or remove license key via dashboard
756
+ app.post("/api/set-license-key", async (req, res) => {
757
+ if (!setLicenseKeyFn) {
758
+ res.status(501).json({ success: false, error: "Not supported" });
759
+ return;
760
+ }
761
+ const { key } = req.body;
762
+ try {
763
+ const result = await setLicenseKeyFn(key || null);
764
+ res.json(result);
765
+ }
766
+ catch (err) {
767
+ res.status(500).json({ success: false, error: err.message || "Unknown error" });
768
+ }
769
+ });
755
770
  // GET /api/browse-dirs — List subdirectories for folder browser
756
771
  app.get("/api/browse-dirs", (req, res) => {
757
772
  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.1",
3
+ "version": "1.4.2",
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",