@curenorway/kode-mcp 1.8.0 → 2.0.0
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/index.js +782 -81
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -10,6 +10,387 @@ import {
|
|
|
10
10
|
ReadResourceRequestSchema
|
|
11
11
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
12
12
|
|
|
13
|
+
// src/file-utils.ts
|
|
14
|
+
import * as fs from "fs";
|
|
15
|
+
import * as path from "path";
|
|
16
|
+
var SUPPORTED_EXTENSIONS = [".js", ".css", ".ts", ".tsx", ".jsx"];
|
|
17
|
+
var BUNDLEABLE_EXTENSIONS = [".ts", ".tsx", ".jsx"];
|
|
18
|
+
var SKIP_DIRS = ["packages", "node_modules", ".git", "dist", "build"];
|
|
19
|
+
function isSupportedFile(fileName) {
|
|
20
|
+
return SUPPORTED_EXTENSIONS.some((ext) => fileName.endsWith(ext));
|
|
21
|
+
}
|
|
22
|
+
function needsBundling(fileName) {
|
|
23
|
+
return BUNDLEABLE_EXTENSIONS.some((ext) => fileName.endsWith(ext));
|
|
24
|
+
}
|
|
25
|
+
function slugFromFileName(fileName) {
|
|
26
|
+
return fileName.replace(/\.(js|ts|tsx|jsx|css)$/, "");
|
|
27
|
+
}
|
|
28
|
+
function extFromFileName(fileName) {
|
|
29
|
+
const ext = path.extname(fileName);
|
|
30
|
+
return ext.startsWith(".") ? ext.slice(1) : ext;
|
|
31
|
+
}
|
|
32
|
+
function mimeTypeFromFileName(fileName) {
|
|
33
|
+
if (fileName.endsWith(".css")) return "text/css";
|
|
34
|
+
if (fileName.endsWith(".ts")) return "application/typescript";
|
|
35
|
+
if (fileName.endsWith(".tsx")) return "text/tsx";
|
|
36
|
+
if (fileName.endsWith(".jsx")) return "text/jsx";
|
|
37
|
+
return "application/javascript";
|
|
38
|
+
}
|
|
39
|
+
function buildFileInfo(filePath, scriptsDir, isModule) {
|
|
40
|
+
const fileName = path.basename(filePath);
|
|
41
|
+
return {
|
|
42
|
+
fileName,
|
|
43
|
+
slug: slugFromFileName(fileName),
|
|
44
|
+
ext: extFromFileName(fileName),
|
|
45
|
+
filePath,
|
|
46
|
+
needsBundling: needsBundling(fileName),
|
|
47
|
+
isModule,
|
|
48
|
+
relativePath: path.relative(scriptsDir, filePath)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function getEntryFiles(scriptsDir) {
|
|
52
|
+
if (!fs.existsSync(scriptsDir)) return [];
|
|
53
|
+
return fs.readdirSync(scriptsDir).filter((f) => {
|
|
54
|
+
const fullPath = path.join(scriptsDir, f);
|
|
55
|
+
return fs.statSync(fullPath).isFile() && isSupportedFile(f);
|
|
56
|
+
}).map((f) => buildFileInfo(path.join(scriptsDir, f), scriptsDir, false));
|
|
57
|
+
}
|
|
58
|
+
function getModuleFiles(scriptsDir) {
|
|
59
|
+
if (!fs.existsSync(scriptsDir)) return [];
|
|
60
|
+
const modules = [];
|
|
61
|
+
function scanDir(dir) {
|
|
62
|
+
const entries2 = fs.readdirSync(dir, { withFileTypes: true });
|
|
63
|
+
for (const entry of entries2) {
|
|
64
|
+
const fullPath = path.join(dir, entry.name);
|
|
65
|
+
if (entry.isDirectory()) {
|
|
66
|
+
if (!SKIP_DIRS.includes(entry.name)) {
|
|
67
|
+
scanDir(fullPath);
|
|
68
|
+
}
|
|
69
|
+
} else if (entry.isFile() && isSupportedFile(entry.name)) {
|
|
70
|
+
modules.push(buildFileInfo(fullPath, scriptsDir, true));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const entries = fs.readdirSync(scriptsDir, { withFileTypes: true });
|
|
75
|
+
for (const entry of entries) {
|
|
76
|
+
if (entry.isDirectory() && !SKIP_DIRS.includes(entry.name)) {
|
|
77
|
+
scanDir(path.join(scriptsDir, entry.name));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return modules;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/bundler.ts
|
|
84
|
+
import * as esbuild from "esbuild";
|
|
85
|
+
import * as fs2 from "fs";
|
|
86
|
+
import * as path2 from "path";
|
|
87
|
+
async function bundleEntryPoint(options) {
|
|
88
|
+
const {
|
|
89
|
+
entryPoint,
|
|
90
|
+
scriptsDir,
|
|
91
|
+
minify = true,
|
|
92
|
+
packageAliases = {}
|
|
93
|
+
} = options;
|
|
94
|
+
try {
|
|
95
|
+
const result = await esbuild.build({
|
|
96
|
+
entryPoints: [entryPoint],
|
|
97
|
+
bundle: true,
|
|
98
|
+
format: "iife",
|
|
99
|
+
platform: "browser",
|
|
100
|
+
target: ["es2020"],
|
|
101
|
+
minify,
|
|
102
|
+
write: false,
|
|
103
|
+
// React is loaded from CDN at runtime, not bundled
|
|
104
|
+
external: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
|
|
105
|
+
// Modern JSX transform — no need for React import in every file
|
|
106
|
+
jsx: "automatic",
|
|
107
|
+
jsxImportSource: "react",
|
|
108
|
+
// Resolve imports relative to scripts directory
|
|
109
|
+
absWorkingDir: scriptsDir,
|
|
110
|
+
// File type loaders
|
|
111
|
+
loader: {
|
|
112
|
+
".ts": "ts",
|
|
113
|
+
".tsx": "tsx",
|
|
114
|
+
".jsx": "jsx",
|
|
115
|
+
".js": "js",
|
|
116
|
+
".css": "css"
|
|
117
|
+
},
|
|
118
|
+
// Try these extensions when resolving imports without extension
|
|
119
|
+
resolveExtensions: [".tsx", ".ts", ".jsx", ".js", ".css"],
|
|
120
|
+
// Package aliases for @kode/* imports (populated in Step 5)
|
|
121
|
+
alias: packageAliases,
|
|
122
|
+
// Shim require() for external React in browser IIFE context
|
|
123
|
+
// Maps require("react") → window.React, require("react-dom") → window.ReactDOM, etc.
|
|
124
|
+
banner: {
|
|
125
|
+
js: `var require=function(m){var g={"react":window.React,"react-dom":window.ReactDOM,"react-dom/client":window.ReactDOM,"react/jsx-runtime":window.React,"react/jsx-dev-runtime":window.React};if(g[m])return g[m];throw new Error("Cannot find module '"+m+"'")};`
|
|
126
|
+
},
|
|
127
|
+
// Readable output names for debugging
|
|
128
|
+
logLevel: "silent"
|
|
129
|
+
});
|
|
130
|
+
const code = result.outputFiles?.[0]?.text || "";
|
|
131
|
+
const warnings = result.warnings.map((w) => formatMessage(w));
|
|
132
|
+
const sourceCode = fs2.readFileSync(entryPoint, "utf-8");
|
|
133
|
+
return {
|
|
134
|
+
code,
|
|
135
|
+
warnings,
|
|
136
|
+
errors: [],
|
|
137
|
+
usesReact: detectReactUsage(sourceCode),
|
|
138
|
+
size: code.length
|
|
139
|
+
};
|
|
140
|
+
} catch (err) {
|
|
141
|
+
if (err && typeof err === "object" && "errors" in err) {
|
|
142
|
+
const buildErr = err;
|
|
143
|
+
return {
|
|
144
|
+
code: "",
|
|
145
|
+
warnings: buildErr.warnings.map((w) => formatMessage(w)),
|
|
146
|
+
errors: buildErr.errors.map((e) => formatMessage(e)),
|
|
147
|
+
usesReact: false,
|
|
148
|
+
size: 0
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
code: "",
|
|
153
|
+
warnings: [],
|
|
154
|
+
errors: [err instanceof Error ? err.message : String(err)],
|
|
155
|
+
usesReact: false,
|
|
156
|
+
size: 0
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function detectReactUsage(sourceCode) {
|
|
161
|
+
return /from\s+['"]react['"]/.test(sourceCode) || /from\s+['"]react-dom/.test(sourceCode) || /import\s+React/.test(sourceCode) || /require\s*\(\s*['"]react/.test(sourceCode) || /React\.createElement/.test(sourceCode);
|
|
162
|
+
}
|
|
163
|
+
function formatMessage(msg) {
|
|
164
|
+
let text = msg.text;
|
|
165
|
+
if (msg.location) {
|
|
166
|
+
const loc = msg.location;
|
|
167
|
+
text = `${loc.file}:${loc.line}:${loc.column}: ${text}`;
|
|
168
|
+
}
|
|
169
|
+
return text;
|
|
170
|
+
}
|
|
171
|
+
function getPackageAliases(scriptsDir) {
|
|
172
|
+
const pkgDir = path2.join(scriptsDir, "packages", "@kode");
|
|
173
|
+
if (!fs2.existsSync(pkgDir)) return {};
|
|
174
|
+
const aliases = {};
|
|
175
|
+
const ENTRY_FILES = ["index.ts", "index.tsx", "index.jsx", "index.js"];
|
|
176
|
+
for (const name of fs2.readdirSync(pkgDir)) {
|
|
177
|
+
const pkgPath = path2.join(pkgDir, name);
|
|
178
|
+
if (!fs2.statSync(pkgPath).isDirectory()) continue;
|
|
179
|
+
for (const entry of ENTRY_FILES) {
|
|
180
|
+
const entryPath = path2.join(pkgPath, entry);
|
|
181
|
+
if (fs2.existsSync(entryPath)) {
|
|
182
|
+
aliases[`@kode/${name}`] = entryPath;
|
|
183
|
+
break;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return aliases;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// src/pkg.ts
|
|
191
|
+
import * as fs3 from "fs";
|
|
192
|
+
import * as path3 from "path";
|
|
193
|
+
var PACKAGES_DIR = "packages/@kode";
|
|
194
|
+
function getPackagesDir(scriptsDir) {
|
|
195
|
+
return path3.join(scriptsDir, PACKAGES_DIR);
|
|
196
|
+
}
|
|
197
|
+
function listInstalledPackages(scriptsDir) {
|
|
198
|
+
const pkgDir = getPackagesDir(scriptsDir);
|
|
199
|
+
if (!fs3.existsSync(pkgDir)) return [];
|
|
200
|
+
return fs3.readdirSync(pkgDir).filter((name) => {
|
|
201
|
+
return fs3.statSync(path3.join(pkgDir, name)).isDirectory();
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function isPackageInstalled(scriptsDir, slug) {
|
|
205
|
+
return fs3.existsSync(path3.join(getPackagesDir(scriptsDir), slug));
|
|
206
|
+
}
|
|
207
|
+
async function pkgAdd(client, scriptsDir, packageSlug) {
|
|
208
|
+
let pkg;
|
|
209
|
+
try {
|
|
210
|
+
pkg = await client.getPackage(packageSlug);
|
|
211
|
+
} catch {
|
|
212
|
+
return { success: false, message: `Package "${packageSlug}" not found in registry.` };
|
|
213
|
+
}
|
|
214
|
+
if (!pkg.files || pkg.files.length === 0) {
|
|
215
|
+
return { success: false, message: `Package "${packageSlug}" has no files.` };
|
|
216
|
+
}
|
|
217
|
+
const pkgDir = path3.join(getPackagesDir(scriptsDir), pkg.slug);
|
|
218
|
+
fs3.mkdirSync(pkgDir, { recursive: true });
|
|
219
|
+
for (const file of pkg.files) {
|
|
220
|
+
const filePath = path3.join(pkgDir, file.path);
|
|
221
|
+
fs3.mkdirSync(path3.dirname(filePath), { recursive: true });
|
|
222
|
+
fs3.writeFileSync(filePath, file.content, "utf-8");
|
|
223
|
+
}
|
|
224
|
+
const manifest = {
|
|
225
|
+
name: pkg.name,
|
|
226
|
+
slug: pkg.slug,
|
|
227
|
+
version: pkg.version,
|
|
228
|
+
description: pkg.description,
|
|
229
|
+
entryPoint: pkg.entry_point,
|
|
230
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
231
|
+
};
|
|
232
|
+
fs3.writeFileSync(path3.join(pkgDir, "kode.json"), JSON.stringify(manifest, null, 2) + "\n");
|
|
233
|
+
const already = isPackageInstalled(scriptsDir, pkg.slug) ? " (updated)" : "";
|
|
234
|
+
return {
|
|
235
|
+
success: true,
|
|
236
|
+
message: `\u2705 Installed @kode/${pkg.slug} v${pkg.version}${already}
|
|
237
|
+
${pkg.files.length} file(s) \u2192 .cure-kode-scripts/packages/@kode/${pkg.slug}/
|
|
238
|
+
Import with: import { ... } from '@kode/${pkg.slug}'`,
|
|
239
|
+
data: manifest
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
async function pkgRemove(scriptsDir, packageSlug) {
|
|
243
|
+
const pkgDir = path3.join(getPackagesDir(scriptsDir), packageSlug);
|
|
244
|
+
if (!fs3.existsSync(pkgDir)) {
|
|
245
|
+
return { success: false, message: `Package "@kode/${packageSlug}" is not installed.` };
|
|
246
|
+
}
|
|
247
|
+
fs3.rmSync(pkgDir, { recursive: true, force: true });
|
|
248
|
+
return {
|
|
249
|
+
success: true,
|
|
250
|
+
message: `\u2705 Removed @kode/${packageSlug}`
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
async function pkgPublish(client, scriptsDir, options) {
|
|
254
|
+
const sourcePath = path3.join(scriptsDir, options.path);
|
|
255
|
+
if (!fs3.existsSync(sourcePath)) {
|
|
256
|
+
return { success: false, message: `Path not found: ${options.path}` };
|
|
257
|
+
}
|
|
258
|
+
const files = [];
|
|
259
|
+
const stat = fs3.statSync(sourcePath);
|
|
260
|
+
if (stat.isFile()) {
|
|
261
|
+
const ext = path3.extname(sourcePath);
|
|
262
|
+
const content = fs3.readFileSync(sourcePath, "utf-8");
|
|
263
|
+
files.push({ path: `index${ext}`, content });
|
|
264
|
+
} else if (stat.isDirectory()) {
|
|
265
|
+
collectFiles(sourcePath, sourcePath, files);
|
|
266
|
+
}
|
|
267
|
+
if (files.length === 0) {
|
|
268
|
+
return { success: false, message: `No files found at "${options.path}".` };
|
|
269
|
+
}
|
|
270
|
+
let entryPoint = options.entryPoint;
|
|
271
|
+
if (!entryPoint) {
|
|
272
|
+
const entryNames = ["index.ts", "index.tsx", "index.jsx", "index.js"];
|
|
273
|
+
const found = files.find((f) => entryNames.includes(f.path));
|
|
274
|
+
if (found) {
|
|
275
|
+
entryPoint = found.path;
|
|
276
|
+
} else {
|
|
277
|
+
entryPoint = files[0].path;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const displayName = options.name || options.slug.split("-").map(
|
|
281
|
+
(w) => w.charAt(0).toUpperCase() + w.slice(1)
|
|
282
|
+
).join(" ");
|
|
283
|
+
try {
|
|
284
|
+
const result = await client.publishPackage({
|
|
285
|
+
name: displayName,
|
|
286
|
+
slug: options.slug,
|
|
287
|
+
description: options.description,
|
|
288
|
+
entryPoint,
|
|
289
|
+
files,
|
|
290
|
+
tags: options.tags
|
|
291
|
+
});
|
|
292
|
+
const verb = result.created ? "Published" : "Updated";
|
|
293
|
+
return {
|
|
294
|
+
success: true,
|
|
295
|
+
message: `\u2705 ${verb} @kode/${result.slug} v${result.version}
|
|
296
|
+
${files.length} file(s), entry: ${entryPoint}
|
|
297
|
+
Install with: kode_pkg add ${result.slug}`,
|
|
298
|
+
data: { slug: result.slug, version: result.version, files: files.length }
|
|
299
|
+
};
|
|
300
|
+
} catch (err) {
|
|
301
|
+
return {
|
|
302
|
+
success: false,
|
|
303
|
+
message: `Failed to publish: ${err instanceof Error ? err.message : String(err)}`
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
async function pkgList(client, scriptsDir, options) {
|
|
308
|
+
const queryParts = [];
|
|
309
|
+
if (options?.search) queryParts.push(`search=${encodeURIComponent(options.search)}`);
|
|
310
|
+
if (options?.category) queryParts.push(`category=${encodeURIComponent(options.category)}`);
|
|
311
|
+
const qs = queryParts.length > 0 ? queryParts.join("&") : void 0;
|
|
312
|
+
const packages = await client.listPackages(qs);
|
|
313
|
+
const installed = scriptsDir ? listInstalledPackages(scriptsDir) : [];
|
|
314
|
+
if (packages.length === 0) {
|
|
315
|
+
return { success: true, message: "No packages found in the registry." };
|
|
316
|
+
}
|
|
317
|
+
const lines = packages.map((p) => {
|
|
318
|
+
const isInstalled = installed.includes(p.slug);
|
|
319
|
+
const status = isInstalled ? " \u2705" : "";
|
|
320
|
+
const tags = p.tags?.length ? ` [${p.tags.join(", ")}]` : "";
|
|
321
|
+
return ` ${p.slug} v${p.version}${status} \u2014 ${p.description || "No description"}${tags}`;
|
|
322
|
+
});
|
|
323
|
+
return {
|
|
324
|
+
success: true,
|
|
325
|
+
message: `\u{1F4E6} Packages in registry (${packages.length}):
|
|
326
|
+
${lines.join("\n")}`,
|
|
327
|
+
data: packages
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
async function pkgInfo(client, scriptsDir, packageSlug) {
|
|
331
|
+
let pkg;
|
|
332
|
+
try {
|
|
333
|
+
pkg = await client.getPackage(packageSlug);
|
|
334
|
+
} catch {
|
|
335
|
+
return { success: false, message: `Package "${packageSlug}" not found.` };
|
|
336
|
+
}
|
|
337
|
+
const installed = scriptsDir ? isPackageInstalled(scriptsDir, pkg.slug) : false;
|
|
338
|
+
const fileList = pkg.files?.map((f) => ` ${f.path}`).join("\n") || " (no files)";
|
|
339
|
+
const info = [
|
|
340
|
+
`\u{1F4E6} @kode/${pkg.slug} v${pkg.version}`,
|
|
341
|
+
` Name: ${pkg.name}`,
|
|
342
|
+
pkg.description ? ` Description: ${pkg.description}` : null,
|
|
343
|
+
` Entry: ${pkg.entry_point}`,
|
|
344
|
+
pkg.tags?.length ? ` Tags: ${pkg.tags.join(", ")}` : null,
|
|
345
|
+
` Installs: ${pkg.install_count}`,
|
|
346
|
+
` Status: ${installed ? "\u2705 Installed locally" : "Not installed"}`,
|
|
347
|
+
` Files:
|
|
348
|
+
${fileList}`,
|
|
349
|
+
pkg.example_usage ? `
|
|
350
|
+
Example:
|
|
351
|
+
${pkg.example_usage}` : null
|
|
352
|
+
].filter(Boolean).join("\n");
|
|
353
|
+
return { success: true, message: info, data: pkg };
|
|
354
|
+
}
|
|
355
|
+
async function pkgUpdate(client, scriptsDir, packageSlug) {
|
|
356
|
+
if (!isPackageInstalled(scriptsDir, packageSlug)) {
|
|
357
|
+
return { success: false, message: `Package "@kode/${packageSlug}" is not installed.` };
|
|
358
|
+
}
|
|
359
|
+
const manifestPath = path3.join(getPackagesDir(scriptsDir), packageSlug, "kode.json");
|
|
360
|
+
let currentVersion = 0;
|
|
361
|
+
if (fs3.existsSync(manifestPath)) {
|
|
362
|
+
try {
|
|
363
|
+
const manifest = JSON.parse(fs3.readFileSync(manifestPath, "utf-8"));
|
|
364
|
+
currentVersion = manifest.version || 0;
|
|
365
|
+
} catch {
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
let pkg;
|
|
369
|
+
try {
|
|
370
|
+
pkg = await client.getPackage(packageSlug);
|
|
371
|
+
} catch {
|
|
372
|
+
return { success: false, message: `Package "${packageSlug}" not found in registry.` };
|
|
373
|
+
}
|
|
374
|
+
if (pkg.version <= currentVersion) {
|
|
375
|
+
return { success: true, message: `@kode/${packageSlug} is already up to date (v${currentVersion}).` };
|
|
376
|
+
}
|
|
377
|
+
return pkgAdd(client, scriptsDir, packageSlug);
|
|
378
|
+
}
|
|
379
|
+
function collectFiles(baseDir, currentDir, files) {
|
|
380
|
+
for (const entry of fs3.readdirSync(currentDir, { withFileTypes: true })) {
|
|
381
|
+
const fullPath = path3.join(currentDir, entry.name);
|
|
382
|
+
if (entry.isDirectory()) {
|
|
383
|
+
if (!["node_modules", ".git", "dist"].includes(entry.name)) {
|
|
384
|
+
collectFiles(baseDir, fullPath, files);
|
|
385
|
+
}
|
|
386
|
+
} else if (entry.isFile()) {
|
|
387
|
+
const relativePath = path3.relative(baseDir, fullPath);
|
|
388
|
+
const content = fs3.readFileSync(fullPath, "utf-8");
|
|
389
|
+
files.push({ path: relativePath, content });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
13
394
|
// src/api.ts
|
|
14
395
|
var KodeApiClient = class {
|
|
15
396
|
apiUrl;
|
|
@@ -18,8 +399,8 @@ var KodeApiClient = class {
|
|
|
18
399
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
19
400
|
this.apiKey = apiKey;
|
|
20
401
|
}
|
|
21
|
-
async request(
|
|
22
|
-
const url = `${this.apiUrl}${
|
|
402
|
+
async request(path6, options = {}) {
|
|
403
|
+
const url = `${this.apiUrl}${path6}`;
|
|
23
404
|
const headers = {
|
|
24
405
|
"Content-Type": "application/json",
|
|
25
406
|
"X-API-Key": this.apiKey,
|
|
@@ -161,6 +542,26 @@ var KodeApiClient = class {
|
|
|
161
542
|
async getLibrarySnippet(slugOrId) {
|
|
162
543
|
return this.request(`/api/cdn/library/${encodeURIComponent(slugOrId)}`);
|
|
163
544
|
}
|
|
545
|
+
async createLibrarySnippet(data) {
|
|
546
|
+
return this.request("/api/cdn/library", {
|
|
547
|
+
method: "POST",
|
|
548
|
+
body: JSON.stringify(data)
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
async updateLibrarySnippet(slugOrId, data) {
|
|
552
|
+
return this.request(`/api/cdn/library/${encodeURIComponent(slugOrId)}`, {
|
|
553
|
+
method: "PATCH",
|
|
554
|
+
body: JSON.stringify(data)
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
async deleteLibrarySnippet(slugOrId) {
|
|
558
|
+
await this.request(`/api/cdn/library/${encodeURIComponent(slugOrId)}`, {
|
|
559
|
+
method: "DELETE"
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
async listLibraryFolders() {
|
|
563
|
+
return this.request("/api/cdn/library/folders");
|
|
564
|
+
}
|
|
164
565
|
// Validate API key
|
|
165
566
|
async validateKey() {
|
|
166
567
|
try {
|
|
@@ -180,32 +581,60 @@ var KodeApiClient = class {
|
|
|
180
581
|
async getScriptMetadata(scriptId) {
|
|
181
582
|
return this.request(`/api/cdn/scripts/${scriptId}/metadata`);
|
|
182
583
|
}
|
|
584
|
+
// ==================== Packages ====================
|
|
585
|
+
async listPackages(queryString) {
|
|
586
|
+
const qs = queryString ? `?${queryString}` : "";
|
|
587
|
+
return this.request(`/api/cdn/packages${qs}`);
|
|
588
|
+
}
|
|
589
|
+
async getPackage(slugOrId) {
|
|
590
|
+
return this.request(`/api/cdn/packages/${slugOrId}`);
|
|
591
|
+
}
|
|
592
|
+
async publishPackage(data) {
|
|
593
|
+
return this.request("/api/cdn/packages", {
|
|
594
|
+
method: "POST",
|
|
595
|
+
body: JSON.stringify(data)
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
async updatePackage(slugOrId, data) {
|
|
599
|
+
return this.request(`/api/cdn/packages/${slugOrId}`, {
|
|
600
|
+
method: "PATCH",
|
|
601
|
+
body: JSON.stringify(data)
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
async deletePackage(slugOrId) {
|
|
605
|
+
return this.request(`/api/cdn/packages/${slugOrId}`, {
|
|
606
|
+
method: "DELETE"
|
|
607
|
+
});
|
|
608
|
+
}
|
|
609
|
+
async getCmsTypes(siteId) {
|
|
610
|
+
return this.request(`/api/cdn/sites/${siteId}/cms-types`);
|
|
611
|
+
}
|
|
183
612
|
};
|
|
184
613
|
|
|
185
614
|
// src/config.ts
|
|
186
|
-
import * as
|
|
187
|
-
import * as
|
|
615
|
+
import * as fs4 from "fs";
|
|
616
|
+
import * as path4 from "path";
|
|
188
617
|
var DEFAULT_API_URL = "https://app.cure.no";
|
|
189
618
|
var CONFIG_DIR = ".cure-kode";
|
|
190
619
|
var CONFIG_FILE = "config.json";
|
|
191
620
|
function findProjectRoot(startDir = process.cwd()) {
|
|
192
621
|
let currentDir = startDir;
|
|
193
|
-
while (currentDir !==
|
|
194
|
-
const configPath =
|
|
195
|
-
if (
|
|
622
|
+
while (currentDir !== path4.dirname(currentDir)) {
|
|
623
|
+
const configPath = path4.join(currentDir, CONFIG_DIR, CONFIG_FILE);
|
|
624
|
+
if (fs4.existsSync(configPath)) {
|
|
196
625
|
return currentDir;
|
|
197
626
|
}
|
|
198
|
-
currentDir =
|
|
627
|
+
currentDir = path4.dirname(currentDir);
|
|
199
628
|
}
|
|
200
629
|
return void 0;
|
|
201
630
|
}
|
|
202
631
|
function loadProjectConfig() {
|
|
203
632
|
const projectRoot = findProjectRoot();
|
|
204
633
|
if (!projectRoot) return void 0;
|
|
205
|
-
const configPath =
|
|
206
|
-
if (!
|
|
634
|
+
const configPath = path4.join(projectRoot, CONFIG_DIR, CONFIG_FILE);
|
|
635
|
+
if (!fs4.existsSync(configPath)) return void 0;
|
|
207
636
|
try {
|
|
208
|
-
const content =
|
|
637
|
+
const content = fs4.readFileSync(configPath, "utf-8");
|
|
209
638
|
return JSON.parse(content);
|
|
210
639
|
} catch {
|
|
211
640
|
return void 0;
|
|
@@ -242,22 +671,22 @@ function getScriptsDir() {
|
|
|
242
671
|
const projectRoot = findProjectRoot();
|
|
243
672
|
const projectConfig = loadProjectConfig();
|
|
244
673
|
if (!projectRoot || !projectConfig) return void 0;
|
|
245
|
-
return
|
|
674
|
+
return path4.join(projectRoot, projectConfig.scriptsDir);
|
|
246
675
|
}
|
|
247
676
|
function getContextPath() {
|
|
248
677
|
const projectRoot = findProjectRoot();
|
|
249
678
|
if (!projectRoot) return void 0;
|
|
250
|
-
return
|
|
679
|
+
return path4.join(projectRoot, CONFIG_DIR, "context.md");
|
|
251
680
|
}
|
|
252
681
|
function getPagesDir() {
|
|
253
682
|
const projectRoot = findProjectRoot();
|
|
254
683
|
if (!projectRoot) return void 0;
|
|
255
|
-
return
|
|
684
|
+
return path4.join(projectRoot, CONFIG_DIR, "pages");
|
|
256
685
|
}
|
|
257
686
|
|
|
258
687
|
// src/index.ts
|
|
259
|
-
import * as
|
|
260
|
-
import * as
|
|
688
|
+
import * as fs5 from "fs";
|
|
689
|
+
import * as path5 from "path";
|
|
261
690
|
function urlToSlug(url) {
|
|
262
691
|
try {
|
|
263
692
|
const parsed = new URL(url);
|
|
@@ -336,7 +765,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
336
765
|
},
|
|
337
766
|
{
|
|
338
767
|
name: "kode_create_script",
|
|
339
|
-
description: "Create a new script entry (metadata only). After creating, write the script file locally to .cure-kode-scripts/{name}.
|
|
768
|
+
description: "Create a new script entry (metadata only). After creating, write the script file locally to .cure-kode-scripts/{name}.{ext} and use kode_push to upload content. Supports JS, TS, TSX, JSX, and CSS files.",
|
|
340
769
|
inputSchema: {
|
|
341
770
|
type: "object",
|
|
342
771
|
properties: {
|
|
@@ -347,7 +776,12 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
347
776
|
type: {
|
|
348
777
|
type: "string",
|
|
349
778
|
enum: ["javascript", "css"],
|
|
350
|
-
description:
|
|
779
|
+
description: 'Script type (CDN type). Use "javascript" for JS/TS/TSX/JSX files.'
|
|
780
|
+
},
|
|
781
|
+
sourceType: {
|
|
782
|
+
type: "string",
|
|
783
|
+
enum: ["js", "ts", "tsx", "jsx", "css"],
|
|
784
|
+
description: 'Source file extension. Determines the local file type. Default: "js" for javascript, "css" for css.'
|
|
351
785
|
},
|
|
352
786
|
scope: {
|
|
353
787
|
type: "string",
|
|
@@ -853,6 +1287,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
853
1287
|
search: {
|
|
854
1288
|
type: "string",
|
|
855
1289
|
description: "Search by name or tags"
|
|
1290
|
+
},
|
|
1291
|
+
folder: {
|
|
1292
|
+
type: "string",
|
|
1293
|
+
description: "Filter by folder ID"
|
|
856
1294
|
}
|
|
857
1295
|
},
|
|
858
1296
|
required: []
|
|
@@ -889,6 +1327,115 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
889
1327
|
},
|
|
890
1328
|
required: ["snippetSlug"]
|
|
891
1329
|
}
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
name: "kode_library_create",
|
|
1333
|
+
description: "Create a new snippet in the global Kode library. Requires a unique slug.",
|
|
1334
|
+
inputSchema: {
|
|
1335
|
+
type: "object",
|
|
1336
|
+
properties: {
|
|
1337
|
+
name: { type: "string", description: "Display name" },
|
|
1338
|
+
slug: { type: "string", description: "URL-safe slug (must be unique)" },
|
|
1339
|
+
type: { type: "string", enum: ["javascript", "css"], description: "Script type" },
|
|
1340
|
+
code: { type: "string", description: "Script code" },
|
|
1341
|
+
description: { type: "string", description: "Short description" },
|
|
1342
|
+
category: { type: "string", description: "Category: animations, forms, utilities, tracking, analytics, consent, integrations, other" },
|
|
1343
|
+
tags: { type: "array", items: { type: "string" }, description: "Tags for discovery" },
|
|
1344
|
+
folderId: { type: "string", description: "Target folder ID" }
|
|
1345
|
+
},
|
|
1346
|
+
required: ["name", "slug", "type", "code"]
|
|
1347
|
+
}
|
|
1348
|
+
},
|
|
1349
|
+
{
|
|
1350
|
+
name: "kode_library_update",
|
|
1351
|
+
description: "Update an existing library snippet (code, metadata, or both). Version auto-increments when code changes.",
|
|
1352
|
+
inputSchema: {
|
|
1353
|
+
type: "object",
|
|
1354
|
+
properties: {
|
|
1355
|
+
slug: { type: "string", description: "Snippet slug or ID" },
|
|
1356
|
+
code: { type: "string", description: "New code (triggers version bump)" },
|
|
1357
|
+
name: { type: "string", description: "New display name" },
|
|
1358
|
+
description: { type: "string", description: "New description" },
|
|
1359
|
+
category: { type: "string", description: "New category" },
|
|
1360
|
+
tags: { type: "array", items: { type: "string" }, description: "New tags" },
|
|
1361
|
+
folderId: { type: ["string", "null"], description: "Move to folder (null = root)" }
|
|
1362
|
+
},
|
|
1363
|
+
required: ["slug"]
|
|
1364
|
+
}
|
|
1365
|
+
},
|
|
1366
|
+
{
|
|
1367
|
+
name: "kode_library_delete",
|
|
1368
|
+
description: "Soft-delete a library snippet (moves to trash, recoverable for 30 days).",
|
|
1369
|
+
inputSchema: {
|
|
1370
|
+
type: "object",
|
|
1371
|
+
properties: {
|
|
1372
|
+
slug: { type: "string", description: "Snippet slug or ID to delete" }
|
|
1373
|
+
},
|
|
1374
|
+
required: ["slug"]
|
|
1375
|
+
}
|
|
1376
|
+
},
|
|
1377
|
+
{
|
|
1378
|
+
name: "kode_library_folders",
|
|
1379
|
+
description: "List all folders in the Kode library. Returns folder hierarchy with snippet counts.",
|
|
1380
|
+
inputSchema: {
|
|
1381
|
+
type: "object",
|
|
1382
|
+
properties: {},
|
|
1383
|
+
required: []
|
|
1384
|
+
}
|
|
1385
|
+
},
|
|
1386
|
+
{
|
|
1387
|
+
name: "kode_pkg",
|
|
1388
|
+
description: "Package manager for Cure Kode. Like npm but for @kode/* packages. Actions: add (install a package), remove (uninstall), publish (upload a component to registry), list (browse registry), info (package details), update (update installed package).",
|
|
1389
|
+
inputSchema: {
|
|
1390
|
+
type: "object",
|
|
1391
|
+
properties: {
|
|
1392
|
+
action: {
|
|
1393
|
+
type: "string",
|
|
1394
|
+
enum: ["add", "remove", "publish", "list", "info", "update"],
|
|
1395
|
+
description: "The action to perform."
|
|
1396
|
+
},
|
|
1397
|
+
name: {
|
|
1398
|
+
type: "string",
|
|
1399
|
+
description: "Package slug. Required for add, remove, info, update."
|
|
1400
|
+
},
|
|
1401
|
+
// publish-specific options
|
|
1402
|
+
path: {
|
|
1403
|
+
type: "string",
|
|
1404
|
+
description: 'For publish: path relative to .cure-kode-scripts/ (e.g., "components/Button" or "lib/utils.ts").'
|
|
1405
|
+
},
|
|
1406
|
+
slug: {
|
|
1407
|
+
type: "string",
|
|
1408
|
+
description: "For publish: package slug to publish as."
|
|
1409
|
+
},
|
|
1410
|
+
displayName: {
|
|
1411
|
+
type: "string",
|
|
1412
|
+
description: "For publish: human-readable name."
|
|
1413
|
+
},
|
|
1414
|
+
description: {
|
|
1415
|
+
type: "string",
|
|
1416
|
+
description: "For publish: package description."
|
|
1417
|
+
},
|
|
1418
|
+
entryPoint: {
|
|
1419
|
+
type: "string",
|
|
1420
|
+
description: "For publish: entry point file (auto-detected if not set)."
|
|
1421
|
+
},
|
|
1422
|
+
tags: {
|
|
1423
|
+
type: "array",
|
|
1424
|
+
items: { type: "string" },
|
|
1425
|
+
description: "For publish: tags for discoverability."
|
|
1426
|
+
},
|
|
1427
|
+
// list-specific options
|
|
1428
|
+
search: {
|
|
1429
|
+
type: "string",
|
|
1430
|
+
description: "For list: search query."
|
|
1431
|
+
},
|
|
1432
|
+
category: {
|
|
1433
|
+
type: "string",
|
|
1434
|
+
description: "For list: filter by category."
|
|
1435
|
+
}
|
|
1436
|
+
},
|
|
1437
|
+
required: ["action"]
|
|
1438
|
+
}
|
|
892
1439
|
}
|
|
893
1440
|
]
|
|
894
1441
|
};
|
|
@@ -992,7 +1539,7 @@ ${script.content}`
|
|
|
992
1539
|
};
|
|
993
1540
|
}
|
|
994
1541
|
case "kode_create_script": {
|
|
995
|
-
const { name: scriptName, type, scope, autoLoad, purpose } = args;
|
|
1542
|
+
const { name: scriptName, type, sourceType, scope, autoLoad, purpose } = args;
|
|
996
1543
|
const scriptScope = scope || "global";
|
|
997
1544
|
const script = await client.createScript(siteId, {
|
|
998
1545
|
name: scriptName,
|
|
@@ -1005,7 +1552,7 @@ ${script.content}`
|
|
|
1005
1552
|
metadata: purpose ? { purpose } : void 0
|
|
1006
1553
|
});
|
|
1007
1554
|
const scriptsDir = getScriptsDir();
|
|
1008
|
-
const ext = type === "javascript" ? "js" : "css";
|
|
1555
|
+
const ext = sourceType || (type === "javascript" ? "js" : "css");
|
|
1009
1556
|
const localPath = scriptsDir ? `${scriptsDir}/${scriptName}.${ext}` : `.cure-kode-scripts/${scriptName}.${ext}`;
|
|
1010
1557
|
let responseText = `Created script "${script.name}" (${script.type})`;
|
|
1011
1558
|
responseText += `
|
|
@@ -1014,6 +1561,10 @@ Slug: ${script.slug}`;
|
|
|
1014
1561
|
Scope: ${script.scope}`;
|
|
1015
1562
|
responseText += `
|
|
1016
1563
|
Auto-load: ${script.auto_load ? "yes" : "no"}`;
|
|
1564
|
+
if (sourceType && sourceType !== "js" && sourceType !== "css") {
|
|
1565
|
+
responseText += `
|
|
1566
|
+
Source type: ${sourceType.toUpperCase()}`;
|
|
1567
|
+
}
|
|
1017
1568
|
if (purpose) responseText += `
|
|
1018
1569
|
Purpose: ${purpose}`;
|
|
1019
1570
|
responseText += `
|
|
@@ -1022,7 +1573,7 @@ Next steps:`;
|
|
|
1022
1573
|
responseText += `
|
|
1023
1574
|
1. Create file: ${localPath}`;
|
|
1024
1575
|
responseText += `
|
|
1025
|
-
2. Write your ${
|
|
1576
|
+
2. Write your ${ext.toUpperCase()} code`;
|
|
1026
1577
|
responseText += `
|
|
1027
1578
|
3. Run kode_push to upload content`;
|
|
1028
1579
|
responseText += `
|
|
@@ -1039,7 +1590,7 @@ Next steps:`;
|
|
|
1039
1590
|
case "kode_push": {
|
|
1040
1591
|
const { scriptSlug, force } = args;
|
|
1041
1592
|
const scriptsDir = getScriptsDir();
|
|
1042
|
-
if (!scriptsDir || !
|
|
1593
|
+
if (!scriptsDir || !fs5.existsSync(scriptsDir)) {
|
|
1043
1594
|
return {
|
|
1044
1595
|
content: [{
|
|
1045
1596
|
type: "text",
|
|
@@ -1049,23 +1600,21 @@ Next steps:`;
|
|
|
1049
1600
|
};
|
|
1050
1601
|
}
|
|
1051
1602
|
const remoteScripts = await client.listScripts(siteId);
|
|
1052
|
-
const
|
|
1603
|
+
const entryFiles = getEntryFiles(scriptsDir);
|
|
1053
1604
|
if (scriptSlug) {
|
|
1054
|
-
const
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
if (!fs2.existsSync(filePath)) {
|
|
1605
|
+
const entry = entryFiles.find((f) => f.slug === scriptSlug);
|
|
1606
|
+
if (!entry) {
|
|
1607
|
+
const available = entryFiles.map((f) => f.fileName).join(", ") || "(none)";
|
|
1058
1608
|
return {
|
|
1059
1609
|
content: [{
|
|
1060
1610
|
type: "text",
|
|
1061
|
-
text: `
|
|
1611
|
+
text: `No entry file found for slug "${scriptSlug}"
|
|
1062
1612
|
|
|
1063
|
-
Available files: ${
|
|
1613
|
+
Available entry files: ${available}`
|
|
1064
1614
|
}],
|
|
1065
1615
|
isError: true
|
|
1066
1616
|
};
|
|
1067
1617
|
}
|
|
1068
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1069
1618
|
const remote = remoteScripts.find((s) => s.slug === scriptSlug);
|
|
1070
1619
|
if (!remote) {
|
|
1071
1620
|
return {
|
|
@@ -1076,6 +1625,36 @@ Available files: ${localFiles.join(", ") || "(none)"}`
|
|
|
1076
1625
|
isError: true
|
|
1077
1626
|
};
|
|
1078
1627
|
}
|
|
1628
|
+
const sourceContent = fs5.readFileSync(entry.filePath, "utf-8");
|
|
1629
|
+
let content = sourceContent;
|
|
1630
|
+
let bundleNote = "";
|
|
1631
|
+
let usesReact = false;
|
|
1632
|
+
if (entry.needsBundling) {
|
|
1633
|
+
const aliases2 = getPackageAliases(scriptsDir);
|
|
1634
|
+
const result = await bundleEntryPoint({
|
|
1635
|
+
entryPoint: entry.filePath,
|
|
1636
|
+
scriptsDir,
|
|
1637
|
+
packageAliases: aliases2
|
|
1638
|
+
});
|
|
1639
|
+
if (result.errors.length > 0) {
|
|
1640
|
+
return {
|
|
1641
|
+
content: [{
|
|
1642
|
+
type: "text",
|
|
1643
|
+
text: `Build failed for "${scriptSlug}":
|
|
1644
|
+
|
|
1645
|
+
${result.errors.join("\n")}`
|
|
1646
|
+
}],
|
|
1647
|
+
isError: true
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
content = result.code;
|
|
1651
|
+
usesReact = result.usesReact;
|
|
1652
|
+
bundleNote = `
|
|
1653
|
+
\u{1F4E6} Bundled from ${entry.ext.toUpperCase()}: ${result.size} bytes`;
|
|
1654
|
+
if (usesReact) bundleNote += " (uses React)";
|
|
1655
|
+
if (result.warnings.length > 0) bundleNote += `
|
|
1656
|
+
\u26A0\uFE0F ${result.warnings.join(", ")}`;
|
|
1657
|
+
}
|
|
1079
1658
|
if (!force && remote.content === content) {
|
|
1080
1659
|
return {
|
|
1081
1660
|
content: [{
|
|
@@ -1084,14 +1663,20 @@ Available files: ${localFiles.join(", ") || "(none)"}`
|
|
|
1084
1663
|
}]
|
|
1085
1664
|
};
|
|
1086
1665
|
}
|
|
1666
|
+
const existingMetadata = remote.metadata || {};
|
|
1087
1667
|
const updated = await client.updateScript(remote.id, {
|
|
1088
1668
|
content,
|
|
1089
|
-
changeSummary:
|
|
1669
|
+
changeSummary: `Pushed via MCP (${entry.ext})`,
|
|
1670
|
+
...entry.needsBundling ? {
|
|
1671
|
+
metadata: { ...existingMetadata, usesReact },
|
|
1672
|
+
sourceContent,
|
|
1673
|
+
sourceType: entry.ext
|
|
1674
|
+
} : {}
|
|
1090
1675
|
});
|
|
1091
1676
|
return {
|
|
1092
1677
|
content: [{
|
|
1093
1678
|
type: "text",
|
|
1094
|
-
text: `Pushed "${scriptSlug}": ${content.length} chars \u2192 v${updated.current_version}
|
|
1679
|
+
text: `Pushed "${scriptSlug}": ${content.length} chars \u2192 v${updated.current_version}${bundleNote}
|
|
1095
1680
|
|
|
1096
1681
|
Run kode_deploy to make changes live.`
|
|
1097
1682
|
}]
|
|
@@ -1100,28 +1685,48 @@ Run kode_deploy to make changes live.`
|
|
|
1100
1685
|
const results = [];
|
|
1101
1686
|
let pushedCount = 0;
|
|
1102
1687
|
let skippedCount = 0;
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
const
|
|
1106
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1107
|
-
const remote = remoteScripts.find((s) => s.slug === slug);
|
|
1688
|
+
const aliases = getPackageAliases(scriptsDir);
|
|
1689
|
+
for (const entry of entryFiles) {
|
|
1690
|
+
const remote = remoteScripts.find((s) => s.slug === entry.slug);
|
|
1108
1691
|
if (!remote) {
|
|
1109
|
-
results.push(`\u26A0\uFE0F ${slug}: not on server (create with kode_create_script first)`);
|
|
1692
|
+
results.push(`\u26A0\uFE0F ${entry.slug}: not on server (create with kode_create_script first)`);
|
|
1110
1693
|
continue;
|
|
1111
1694
|
}
|
|
1695
|
+
const sourceContent = fs5.readFileSync(entry.filePath, "utf-8");
|
|
1696
|
+
let content = sourceContent;
|
|
1697
|
+
let usesReact = false;
|
|
1698
|
+
if (entry.needsBundling) {
|
|
1699
|
+
const bundleResult = await bundleEntryPoint({
|
|
1700
|
+
entryPoint: entry.filePath,
|
|
1701
|
+
scriptsDir,
|
|
1702
|
+
packageAliases: aliases
|
|
1703
|
+
});
|
|
1704
|
+
if (bundleResult.errors.length > 0) {
|
|
1705
|
+
results.push(`\u2717 ${entry.slug}: build failed: ${bundleResult.errors[0]}`);
|
|
1706
|
+
continue;
|
|
1707
|
+
}
|
|
1708
|
+
content = bundleResult.code;
|
|
1709
|
+
usesReact = bundleResult.usesReact;
|
|
1710
|
+
}
|
|
1112
1711
|
if (!force && remote.content === content) {
|
|
1113
1712
|
skippedCount++;
|
|
1114
1713
|
continue;
|
|
1115
1714
|
}
|
|
1116
1715
|
try {
|
|
1716
|
+
const existingMeta = remote.metadata || {};
|
|
1117
1717
|
const updated = await client.updateScript(remote.id, {
|
|
1118
1718
|
content,
|
|
1119
|
-
changeSummary:
|
|
1719
|
+
changeSummary: `Pushed via MCP (${entry.ext})`,
|
|
1720
|
+
...entry.needsBundling ? {
|
|
1721
|
+
metadata: { ...existingMeta, usesReact },
|
|
1722
|
+
sourceContent,
|
|
1723
|
+
sourceType: entry.ext
|
|
1724
|
+
} : {}
|
|
1120
1725
|
});
|
|
1121
|
-
results.push(`\u2713 ${slug}: ${content.length} chars \u2192 v${updated.current_version}`);
|
|
1726
|
+
results.push(`\u2713 ${entry.slug}: ${content.length} chars \u2192 v${updated.current_version}`);
|
|
1122
1727
|
pushedCount++;
|
|
1123
1728
|
} catch (err) {
|
|
1124
|
-
results.push(`\u2717 ${slug}: ${err instanceof Error ? err.message : "failed"}`);
|
|
1729
|
+
results.push(`\u2717 ${entry.slug}: ${err instanceof Error ? err.message : "failed"}`);
|
|
1125
1730
|
}
|
|
1126
1731
|
}
|
|
1127
1732
|
let responseText = `Push complete: ${pushedCount} updated, ${skippedCount} unchanged`;
|
|
@@ -1962,13 +2567,13 @@ Last analyzed: ${result.lastAnalyzed}`;
|
|
|
1962
2567
|
isError: true
|
|
1963
2568
|
};
|
|
1964
2569
|
}
|
|
1965
|
-
if (!
|
|
2570
|
+
if (!fs5.existsSync(contextPath)) {
|
|
1966
2571
|
return {
|
|
1967
2572
|
content: [{ type: "text", text: 'No context file found. Run "kode context --refresh" to create one.' }],
|
|
1968
2573
|
isError: true
|
|
1969
2574
|
};
|
|
1970
2575
|
}
|
|
1971
|
-
const content =
|
|
2576
|
+
const content = fs5.readFileSync(contextPath, "utf-8");
|
|
1972
2577
|
return {
|
|
1973
2578
|
content: [{ type: "text", text: content }]
|
|
1974
2579
|
};
|
|
@@ -1981,14 +2586,14 @@ Last analyzed: ${result.lastAnalyzed}`;
|
|
|
1981
2586
|
isError: true
|
|
1982
2587
|
};
|
|
1983
2588
|
}
|
|
1984
|
-
if (!
|
|
2589
|
+
if (!fs5.existsSync(contextPath)) {
|
|
1985
2590
|
return {
|
|
1986
2591
|
content: [{ type: "text", text: 'No context file found. Run "kode context --refresh" to create one.' }],
|
|
1987
2592
|
isError: true
|
|
1988
2593
|
};
|
|
1989
2594
|
}
|
|
1990
2595
|
const { addNote, addSession, updateScriptPurpose } = args;
|
|
1991
|
-
let content =
|
|
2596
|
+
let content = fs5.readFileSync(contextPath, "utf-8");
|
|
1992
2597
|
const updates = [];
|
|
1993
2598
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1994
2599
|
content = content.replace(
|
|
@@ -2049,7 +2654,7 @@ ${sessionMd}`
|
|
|
2049
2654
|
updates.push(`Updated purpose for "${slug}": "${purpose}"`);
|
|
2050
2655
|
}
|
|
2051
2656
|
}
|
|
2052
|
-
|
|
2657
|
+
fs5.writeFileSync(contextPath, content, "utf-8");
|
|
2053
2658
|
return {
|
|
2054
2659
|
content: [
|
|
2055
2660
|
{
|
|
@@ -2157,10 +2762,10 @@ ${result.htmlPreview.slice(0, 5e3)}`;
|
|
|
2157
2762
|
};
|
|
2158
2763
|
}
|
|
2159
2764
|
const slug = urlToSlug(url);
|
|
2160
|
-
const cachePath =
|
|
2161
|
-
if (!force &&
|
|
2765
|
+
const cachePath = path5.join(pagesDir, `${slug}.json`);
|
|
2766
|
+
if (!force && fs5.existsSync(cachePath)) {
|
|
2162
2767
|
try {
|
|
2163
|
-
const cached = JSON.parse(
|
|
2768
|
+
const cached = JSON.parse(fs5.readFileSync(cachePath, "utf-8"));
|
|
2164
2769
|
return {
|
|
2165
2770
|
content: [{
|
|
2166
2771
|
type: "text",
|
|
@@ -2200,12 +2805,12 @@ CMS Collections: ${cached.cmsPatterns.length}`
|
|
|
2200
2805
|
};
|
|
2201
2806
|
}
|
|
2202
2807
|
const structure = await response.json();
|
|
2203
|
-
if (!
|
|
2204
|
-
|
|
2808
|
+
if (!fs5.existsSync(pagesDir)) {
|
|
2809
|
+
fs5.mkdirSync(pagesDir, { recursive: true });
|
|
2205
2810
|
}
|
|
2206
|
-
|
|
2811
|
+
fs5.writeFileSync(cachePath, JSON.stringify(structure, null, 2), "utf-8");
|
|
2207
2812
|
const contextPath = getContextPath();
|
|
2208
|
-
if (contextPath &&
|
|
2813
|
+
if (contextPath && fs5.existsSync(contextPath)) {
|
|
2209
2814
|
}
|
|
2210
2815
|
let text = `Page cached: ${slug}
|
|
2211
2816
|
Path: ${cachePath}
|
|
@@ -2273,8 +2878,8 @@ CMS Collections (${structure.cmsPatterns.length}):
|
|
|
2273
2878
|
};
|
|
2274
2879
|
}
|
|
2275
2880
|
const slug = urlOrSlug.startsWith("http") ? urlToSlug(urlOrSlug) : urlOrSlug;
|
|
2276
|
-
const cachePath =
|
|
2277
|
-
if (!
|
|
2881
|
+
const cachePath = path5.join(pagesDir, `${slug}.json`);
|
|
2882
|
+
if (!fs5.existsSync(cachePath)) {
|
|
2278
2883
|
return {
|
|
2279
2884
|
content: [{
|
|
2280
2885
|
type: "text",
|
|
@@ -2285,7 +2890,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
|
|
|
2285
2890
|
isError: true
|
|
2286
2891
|
};
|
|
2287
2892
|
}
|
|
2288
|
-
const context = JSON.parse(
|
|
2893
|
+
const context = JSON.parse(fs5.readFileSync(cachePath, "utf-8"));
|
|
2289
2894
|
const cacheAge = Date.now() - new Date(context.extractedAt).getTime();
|
|
2290
2895
|
const cacheAgeDays = Math.floor(cacheAge / (1e3 * 60 * 60 * 24));
|
|
2291
2896
|
const isStale = cacheAgeDays > 7;
|
|
@@ -2529,7 +3134,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
|
|
|
2529
3134
|
isError: true
|
|
2530
3135
|
};
|
|
2531
3136
|
}
|
|
2532
|
-
if (!
|
|
3137
|
+
if (!fs5.existsSync(pagesDir)) {
|
|
2533
3138
|
return {
|
|
2534
3139
|
content: [{
|
|
2535
3140
|
type: "text",
|
|
@@ -2537,7 +3142,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
|
|
|
2537
3142
|
}]
|
|
2538
3143
|
};
|
|
2539
3144
|
}
|
|
2540
|
-
const files =
|
|
3145
|
+
const files = fs5.readdirSync(pagesDir).filter((f) => f.endsWith(".json"));
|
|
2541
3146
|
if (files.length === 0) {
|
|
2542
3147
|
return {
|
|
2543
3148
|
content: [{
|
|
@@ -2552,7 +3157,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
|
|
|
2552
3157
|
for (const file of files) {
|
|
2553
3158
|
const slug = file.replace(".json", "");
|
|
2554
3159
|
try {
|
|
2555
|
-
const context = JSON.parse(
|
|
3160
|
+
const context = JSON.parse(fs5.readFileSync(path5.join(pagesDir, file), "utf-8"));
|
|
2556
3161
|
const urlPath = new URL(context.url).pathname;
|
|
2557
3162
|
text += `${urlPath} [${slug}]
|
|
2558
3163
|
`;
|
|
@@ -2633,8 +3238,8 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
|
|
|
2633
3238
|
text += "\n";
|
|
2634
3239
|
}
|
|
2635
3240
|
if (updateClaudeMd) {
|
|
2636
|
-
const kodeMdPath =
|
|
2637
|
-
const claudeMdPath =
|
|
3241
|
+
const kodeMdPath = path5.join(projectRoot, ".cure-kode", "KODE.md");
|
|
3242
|
+
const claudeMdPath = path5.join(projectRoot, "CLAUDE.md");
|
|
2638
3243
|
const siteName = config.siteName || "Site";
|
|
2639
3244
|
const siteSlug = config.siteSlug || "site";
|
|
2640
3245
|
let kodeMd = `# Cure Kode: ${siteName}
|
|
@@ -2709,7 +3314,7 @@ Use kode_list_pages_context to see cached pages, or kode_refresh_page to cache a
|
|
|
2709
3314
|
`;
|
|
2710
3315
|
kodeMd += `**Check for updates:** \`kode doctor\`
|
|
2711
3316
|
`;
|
|
2712
|
-
|
|
3317
|
+
fs5.writeFileSync(kodeMdPath, kodeMd);
|
|
2713
3318
|
text += `Updated .cure-kode/KODE.md with current scripts and pages.
|
|
2714
3319
|
`;
|
|
2715
3320
|
const kodeReference = `## Cure Kode
|
|
@@ -2725,16 +3330,16 @@ Full documentation: [.cure-kode/KODE.md](.cure-kode/KODE.md)
|
|
|
2725
3330
|
---
|
|
2726
3331
|
|
|
2727
3332
|
`;
|
|
2728
|
-
if (
|
|
2729
|
-
const claudeContent =
|
|
3333
|
+
if (fs5.existsSync(claudeMdPath)) {
|
|
3334
|
+
const claudeContent = fs5.readFileSync(claudeMdPath, "utf-8");
|
|
2730
3335
|
const hasReference = claudeContent.includes("KODE.md") || claudeContent.includes(".cure-kode/KODE.md");
|
|
2731
3336
|
if (!hasReference) {
|
|
2732
|
-
|
|
3337
|
+
fs5.writeFileSync(claudeMdPath, kodeReference + claudeContent);
|
|
2733
3338
|
text += `Added Kode reference to CLAUDE.md.
|
|
2734
3339
|
`;
|
|
2735
3340
|
}
|
|
2736
3341
|
} else {
|
|
2737
|
-
|
|
3342
|
+
fs5.writeFileSync(claudeMdPath, kodeReference);
|
|
2738
3343
|
text += `Created CLAUDE.md with Kode reference.
|
|
2739
3344
|
`;
|
|
2740
3345
|
}
|
|
@@ -2751,10 +3356,11 @@ Full documentation: [.cure-kode/KODE.md](.cure-kode/KODE.md)
|
|
|
2751
3356
|
}
|
|
2752
3357
|
}
|
|
2753
3358
|
case "kode_library_list": {
|
|
2754
|
-
const { category, search } = args;
|
|
3359
|
+
const { category, search, folder } = args;
|
|
2755
3360
|
const params = new URLSearchParams();
|
|
2756
3361
|
if (category) params.set("category", category);
|
|
2757
3362
|
if (search) params.set("search", search);
|
|
3363
|
+
if (folder) params.set("folder", folder);
|
|
2758
3364
|
const snippets = await client.listLibrary(params.toString() ? `?${params.toString()}` : "");
|
|
2759
3365
|
return {
|
|
2760
3366
|
content: [{ type: "text", text: JSON.stringify(snippets, null, 2) }]
|
|
@@ -2779,23 +3385,117 @@ Full documentation: [.cure-kode/KODE.md](.cure-kode/KODE.md)
|
|
|
2779
3385
|
isError: true
|
|
2780
3386
|
};
|
|
2781
3387
|
}
|
|
2782
|
-
const filePath =
|
|
2783
|
-
if (
|
|
3388
|
+
const filePath = path5.join(scriptsDir, `${fileName}.${ext}`);
|
|
3389
|
+
if (fs5.existsSync(filePath)) {
|
|
2784
3390
|
return {
|
|
2785
3391
|
content: [{ type: "text", text: `File already exists: ${fileName}.${ext}. Use a different newName or delete the existing file.` }],
|
|
2786
3392
|
isError: true
|
|
2787
3393
|
};
|
|
2788
3394
|
}
|
|
2789
|
-
if (!
|
|
2790
|
-
|
|
3395
|
+
if (!fs5.existsSync(scriptsDir)) {
|
|
3396
|
+
fs5.mkdirSync(scriptsDir, { recursive: true });
|
|
2791
3397
|
}
|
|
2792
|
-
|
|
3398
|
+
fs5.writeFileSync(filePath, snippet.code);
|
|
2793
3399
|
return {
|
|
2794
3400
|
content: [{ type: "text", text: `Copied "${snippet.name}" to ${fileName}.${ext}
|
|
2795
3401
|
|
|
2796
3402
|
Run \`kode push\` to upload to CDN.` }]
|
|
2797
3403
|
};
|
|
2798
3404
|
}
|
|
3405
|
+
case "kode_library_create": {
|
|
3406
|
+
const { name: name2, slug, type, code, description, category, tags, folderId } = args;
|
|
3407
|
+
const result = await client.createLibrarySnippet({
|
|
3408
|
+
name: name2,
|
|
3409
|
+
slug,
|
|
3410
|
+
type,
|
|
3411
|
+
code,
|
|
3412
|
+
description,
|
|
3413
|
+
category,
|
|
3414
|
+
tags,
|
|
3415
|
+
folderId
|
|
3416
|
+
});
|
|
3417
|
+
return {
|
|
3418
|
+
content: [{ type: "text", text: `Created library snippet "${name2}" (${result.slug})` }]
|
|
3419
|
+
};
|
|
3420
|
+
}
|
|
3421
|
+
case "kode_library_update": {
|
|
3422
|
+
const { slug, ...updates } = args;
|
|
3423
|
+
const result = await client.updateLibrarySnippet(slug, updates);
|
|
3424
|
+
return {
|
|
3425
|
+
content: [{ type: "text", text: `Updated library snippet "${result.slug}" (now v${result.version})` }]
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
case "kode_library_delete": {
|
|
3429
|
+
const { slug } = args;
|
|
3430
|
+
await client.deleteLibrarySnippet(slug);
|
|
3431
|
+
return {
|
|
3432
|
+
content: [{ type: "text", text: `Soft-deleted library snippet "${slug}". It can be restored from trash within 30 days.` }]
|
|
3433
|
+
};
|
|
3434
|
+
}
|
|
3435
|
+
case "kode_library_folders": {
|
|
3436
|
+
const folders = await client.listLibraryFolders();
|
|
3437
|
+
return {
|
|
3438
|
+
content: [{ type: "text", text: JSON.stringify(folders, null, 2) }]
|
|
3439
|
+
};
|
|
3440
|
+
}
|
|
3441
|
+
case "kode_pkg": {
|
|
3442
|
+
const action = args?.action;
|
|
3443
|
+
const scriptsDir = getScriptsDir();
|
|
3444
|
+
switch (action) {
|
|
3445
|
+
case "add": {
|
|
3446
|
+
const pkgName = args?.name;
|
|
3447
|
+
if (!pkgName) return { content: [{ type: "text", text: "Error: name is required for add." }], isError: true };
|
|
3448
|
+
if (!scriptsDir) return { content: [{ type: "text", text: "Error: Not in a Kode project. Run kode init first." }], isError: true };
|
|
3449
|
+
const result = await pkgAdd(client, scriptsDir, pkgName);
|
|
3450
|
+
return { content: [{ type: "text", text: result.message }], isError: !result.success };
|
|
3451
|
+
}
|
|
3452
|
+
case "remove": {
|
|
3453
|
+
const pkgName = args?.name;
|
|
3454
|
+
if (!pkgName) return { content: [{ type: "text", text: "Error: name is required for remove." }], isError: true };
|
|
3455
|
+
if (!scriptsDir) return { content: [{ type: "text", text: "Error: Not in a Kode project." }], isError: true };
|
|
3456
|
+
const result = await pkgRemove(scriptsDir, pkgName);
|
|
3457
|
+
return { content: [{ type: "text", text: result.message }], isError: !result.success };
|
|
3458
|
+
}
|
|
3459
|
+
case "publish": {
|
|
3460
|
+
const pubPath = args?.path;
|
|
3461
|
+
const pubSlug = args?.slug || args?.name;
|
|
3462
|
+
if (!pubPath) return { content: [{ type: "text", text: "Error: path is required for publish." }], isError: true };
|
|
3463
|
+
if (!pubSlug) return { content: [{ type: "text", text: "Error: slug (or name) is required for publish." }], isError: true };
|
|
3464
|
+
if (!scriptsDir) return { content: [{ type: "text", text: "Error: Not in a Kode project." }], isError: true };
|
|
3465
|
+
const result = await pkgPublish(client, scriptsDir, {
|
|
3466
|
+
path: pubPath,
|
|
3467
|
+
slug: pubSlug,
|
|
3468
|
+
name: args?.displayName,
|
|
3469
|
+
description: args?.description,
|
|
3470
|
+
entryPoint: args?.entryPoint,
|
|
3471
|
+
tags: args?.tags
|
|
3472
|
+
});
|
|
3473
|
+
return { content: [{ type: "text", text: result.message }], isError: !result.success };
|
|
3474
|
+
}
|
|
3475
|
+
case "list": {
|
|
3476
|
+
const result = await pkgList(client, scriptsDir, {
|
|
3477
|
+
search: args?.search,
|
|
3478
|
+
category: args?.category
|
|
3479
|
+
});
|
|
3480
|
+
return { content: [{ type: "text", text: result.message }] };
|
|
3481
|
+
}
|
|
3482
|
+
case "info": {
|
|
3483
|
+
const pkgName = args?.name;
|
|
3484
|
+
if (!pkgName) return { content: [{ type: "text", text: "Error: name is required for info." }], isError: true };
|
|
3485
|
+
const result = await pkgInfo(client, scriptsDir, pkgName);
|
|
3486
|
+
return { content: [{ type: "text", text: result.message }], isError: !result.success };
|
|
3487
|
+
}
|
|
3488
|
+
case "update": {
|
|
3489
|
+
const pkgName = args?.name;
|
|
3490
|
+
if (!pkgName) return { content: [{ type: "text", text: "Error: name is required for update." }], isError: true };
|
|
3491
|
+
if (!scriptsDir) return { content: [{ type: "text", text: "Error: Not in a Kode project." }], isError: true };
|
|
3492
|
+
const result = await pkgUpdate(client, scriptsDir, pkgName);
|
|
3493
|
+
return { content: [{ type: "text", text: result.message }], isError: !result.success };
|
|
3494
|
+
}
|
|
3495
|
+
default:
|
|
3496
|
+
return { content: [{ type: "text", text: `Unknown kode_pkg action: "${action}". Use: add, remove, publish, list, info, update.` }], isError: true };
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
2799
3499
|
default:
|
|
2800
3500
|
return {
|
|
2801
3501
|
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
@@ -2816,14 +3516,15 @@ Run \`kode push\` to upload to CDN.` }]
|
|
|
2816
3516
|
});
|
|
2817
3517
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
2818
3518
|
const scriptsDir = getScriptsDir();
|
|
2819
|
-
if (!scriptsDir || !
|
|
3519
|
+
if (!scriptsDir || !fs5.existsSync(scriptsDir)) {
|
|
2820
3520
|
return { resources: [] };
|
|
2821
3521
|
}
|
|
2822
|
-
const
|
|
2823
|
-
const
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
3522
|
+
const entries = getEntryFiles(scriptsDir);
|
|
3523
|
+
const modules = getModuleFiles(scriptsDir);
|
|
3524
|
+
const resources = [...entries, ...modules].map((f) => ({
|
|
3525
|
+
uri: `file://${f.filePath}`,
|
|
3526
|
+
name: f.isModule ? f.relativePath : f.fileName,
|
|
3527
|
+
mimeType: mimeTypeFromFileName(f.fileName)
|
|
2827
3528
|
}));
|
|
2828
3529
|
return { resources };
|
|
2829
3530
|
});
|
|
@@ -2833,11 +3534,11 @@ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
|
2833
3534
|
throw new Error("Invalid resource URI");
|
|
2834
3535
|
}
|
|
2835
3536
|
const filePath = uri.replace("file://", "");
|
|
2836
|
-
if (!
|
|
3537
|
+
if (!fs5.existsSync(filePath)) {
|
|
2837
3538
|
throw new Error("File not found");
|
|
2838
3539
|
}
|
|
2839
|
-
const content =
|
|
2840
|
-
const mimeType = filePath
|
|
3540
|
+
const content = fs5.readFileSync(filePath, "utf-8");
|
|
3541
|
+
const mimeType = mimeTypeFromFileName(filePath);
|
|
2841
3542
|
return {
|
|
2842
3543
|
contents: [
|
|
2843
3544
|
{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@curenorway/kode-mcp",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "MCP server for Cure Kode CDN - enables AI agents to manage, deploy, and analyze Webflow scripts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
"prepublishOnly": "pnpm build"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
22
|
+
"esbuild": "^0.24.2"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
25
|
"@types/node": "^20.10.0",
|