create-codemodekit 0.3.0 → 0.5.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/README.md +45 -5
- package/dist/agent-plugin.d.ts +6 -7
- package/dist/agent-plugin.d.ts.map +1 -1
- package/dist/agent-plugin.js +34 -27
- package/dist/agent-plugin.js.map +1 -1
- package/dist/authoring-skill.d.ts +8 -1
- package/dist/authoring-skill.d.ts.map +1 -1
- package/dist/authoring-skill.js +17 -38
- package/dist/authoring-skill.js.map +1 -1
- package/dist/catalog-files.d.ts +28 -0
- package/dist/catalog-files.d.ts.map +1 -0
- package/dist/catalog-files.js +193 -0
- package/dist/catalog-files.js.map +1 -0
- package/dist/cli.d.ts +14 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +345 -39
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/scaffold.d.ts +14 -6
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +177 -70
- package/dist/scaffold.js.map +1 -1
- package/dist/source-template.d.ts +31 -0
- package/dist/source-template.d.ts.map +1 -0
- package/dist/source-template.js +356 -0
- package/dist/source-template.js.map +1 -0
- package/dist/verify-template.d.ts +2 -0
- package/dist/verify-template.d.ts.map +1 -0
- package/dist/verify-template.js +113 -0
- package/dist/verify-template.js.map +1 -0
- package/package.json +5 -5
- package/skills/build-codemodekit-plugin/SKILL.md +0 -44
- package/skills/build-codemodekit-plugin/agents/openai.yaml +0 -4
- package/skills/build-codemodekit-plugin/references/generator.md +0 -42
- package/skills/build-codemodekit-plugin/references/plugin-layout.md +0 -38
- package/skills/build-codemodekit-plugin/references/programmatic-api.md +0 -48
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
export function renderSources(sources) {
|
|
2
|
+
const rendered = sources.map(renderSource);
|
|
3
|
+
const needsEnvironment = sources.some((source) => source.type === "mcp-http" &&
|
|
4
|
+
(source.bearerTokenEnv !== undefined ||
|
|
5
|
+
Object.keys(source.headerEnv ?? {}).length > 0));
|
|
6
|
+
return {
|
|
7
|
+
imports: [...new Set(rendered.flatMap((source) => source.imports))],
|
|
8
|
+
prelude: (needsEnvironment ? renderEnvironmentHelper() : "") +
|
|
9
|
+
rendered.map((source) => source.prelude).join(""),
|
|
10
|
+
expressions: rendered.map((source) => source.expression),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function renderSource(source) {
|
|
14
|
+
switch (source.type) {
|
|
15
|
+
case "mcp-stdio":
|
|
16
|
+
return {
|
|
17
|
+
imports: ["mcp"],
|
|
18
|
+
prelude: "",
|
|
19
|
+
expression: `
|
|
20
|
+
mcp.stdio({
|
|
21
|
+
name: ${JSON.stringify(source.name)},
|
|
22
|
+
command: ${JSON.stringify(source.command.command)},
|
|
23
|
+
args: ${JSON.stringify(source.command.args)},
|
|
24
|
+
}),
|
|
25
|
+
`,
|
|
26
|
+
};
|
|
27
|
+
case "mcp-http": {
|
|
28
|
+
const headers = [
|
|
29
|
+
...(source.bearerTokenEnv === undefined
|
|
30
|
+
? []
|
|
31
|
+
: [
|
|
32
|
+
` authorization: "Bearer " + requiredEnvironment(${JSON.stringify(source.bearerTokenEnv)}),`,
|
|
33
|
+
]),
|
|
34
|
+
...Object.entries(source.headerEnv ?? {})
|
|
35
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
36
|
+
.map(([header, environment]) => ` ${JSON.stringify(header)}: requiredEnvironment(${JSON.stringify(environment)}),`),
|
|
37
|
+
];
|
|
38
|
+
return {
|
|
39
|
+
imports: ["mcp"],
|
|
40
|
+
prelude: "",
|
|
41
|
+
expression: `
|
|
42
|
+
mcp.http({
|
|
43
|
+
name: ${JSON.stringify(source.name)},
|
|
44
|
+
url: ${JSON.stringify(source.url)},
|
|
45
|
+
${headers.length === 0 ? "" : ` headers: {\n${headers.join("\n")}\n },\n`}
|
|
46
|
+
}),
|
|
47
|
+
`,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
case "weather":
|
|
51
|
+
return {
|
|
52
|
+
imports: ["defineTool", "local", "ToolError"],
|
|
53
|
+
prelude: renderWeatherPrelude(),
|
|
54
|
+
expression: `
|
|
55
|
+
local({
|
|
56
|
+
name: "weather",
|
|
57
|
+
tools: { findLocation, getCurrentWeather },
|
|
58
|
+
}),
|
|
59
|
+
`,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
export function renderSourcesReadme(sources) {
|
|
64
|
+
if (sources.length === 1)
|
|
65
|
+
return renderSourceReadme(sources[0]);
|
|
66
|
+
return `It combines ${String(sources.length)} sources in one catalog:\n\n${sources
|
|
67
|
+
.map((source) => `### \`${sourceName(source)}\`\n\n${renderSourceReadme(source)}`)
|
|
68
|
+
.join("\n\n")}`;
|
|
69
|
+
}
|
|
70
|
+
function renderSourceReadme(source) {
|
|
71
|
+
switch (source.type) {
|
|
72
|
+
case "mcp-stdio":
|
|
73
|
+
return `It wraps the \`${source.name}\` MCP source using this shell-free process configuration:
|
|
74
|
+
|
|
75
|
+
\`\`\`json
|
|
76
|
+
${JSON.stringify(source.command, null, 2)}
|
|
77
|
+
\`\`\``;
|
|
78
|
+
case "mcp-http": {
|
|
79
|
+
const environment = sourceEnvironmentVariables(source);
|
|
80
|
+
const authentication = environment.length === 0
|
|
81
|
+
? ""
|
|
82
|
+
: ` Authentication headers are assembled at runtime from ${environment
|
|
83
|
+
.map((name) => `\`${name}\``)
|
|
84
|
+
.join(", ")}; their values are not written to source or plugin artifacts.`;
|
|
85
|
+
return `It wraps the \`${source.name}\` Streamable HTTP MCP source at \`${source.url}\`.${authentication}`;
|
|
86
|
+
}
|
|
87
|
+
case "weather":
|
|
88
|
+
return `This is the editable, keyless weather starter. It exposes two local tools—\`weather.findLocation\` and \`weather.getCurrentWeather\`—so an agent can compose geocoding and current-weather lookup inside one \`run_typescript\` call.
|
|
89
|
+
|
|
90
|
+
It uses Open-Meteo's public endpoints by default. The free endpoint is intended for non-commercial use within Open-Meteo's published limits; review their terms before production use. Set \`OPEN_METEO_GEOCODING_URL\` and \`OPEN_METEO_FORECAST_URL\` to use customer or self-hosted endpoints.`;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
export function renderEnvExample(sources) {
|
|
94
|
+
const sections = sources.map((source) => {
|
|
95
|
+
switch (source.type) {
|
|
96
|
+
case "weather":
|
|
97
|
+
return `# Optional Open-Meteo customer or self-hosted endpoints.
|
|
98
|
+
# OPEN_METEO_GEOCODING_URL=https://geocoding-api.open-meteo.com/v1/search
|
|
99
|
+
# OPEN_METEO_FORECAST_URL=https://api.open-meteo.com/v1/forecast
|
|
100
|
+
`;
|
|
101
|
+
case "mcp-http": {
|
|
102
|
+
const variables = sourceEnvironmentVariables(source);
|
|
103
|
+
return variables.length === 0
|
|
104
|
+
? `# ${source.name}: add any environment required by the remote MCP source.\n`
|
|
105
|
+
: `# ${source.name}: required HTTP authentication environment.\n${variables
|
|
106
|
+
.map((name) => `${name}=`)
|
|
107
|
+
.join("\n")}\n`;
|
|
108
|
+
}
|
|
109
|
+
case "mcp-stdio":
|
|
110
|
+
return `# ${source.name}: add any environment required by the upstream MCP process.\n`;
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
return sections.join("\n");
|
|
114
|
+
}
|
|
115
|
+
function sourceEnvironmentVariables(source) {
|
|
116
|
+
return [
|
|
117
|
+
...(source.bearerTokenEnv === undefined ? [] : [source.bearerTokenEnv]),
|
|
118
|
+
...Object.values(source.headerEnv ?? {}),
|
|
119
|
+
].filter((value, index, values) => values.indexOf(value) === index);
|
|
120
|
+
}
|
|
121
|
+
function sourceName(source) {
|
|
122
|
+
return source.type === "weather" ? "weather" : source.name;
|
|
123
|
+
}
|
|
124
|
+
function renderEnvironmentHelper() {
|
|
125
|
+
return `function requiredEnvironment(name) {
|
|
126
|
+
const value = process.env[name];
|
|
127
|
+
if (typeof value !== "string" || value.trim() === "") {
|
|
128
|
+
throw new Error("Missing required environment variable: " + name);
|
|
129
|
+
}
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
`;
|
|
134
|
+
}
|
|
135
|
+
function renderWeatherPrelude() {
|
|
136
|
+
return `const geocodingEndpoint =
|
|
137
|
+
process.env.OPEN_METEO_GEOCODING_URL ??
|
|
138
|
+
"https://geocoding-api.open-meteo.com/v1/search";
|
|
139
|
+
const forecastEndpoint =
|
|
140
|
+
process.env.OPEN_METEO_FORECAST_URL ??
|
|
141
|
+
"https://api.open-meteo.com/v1/forecast";
|
|
142
|
+
|
|
143
|
+
const findLocation = defineTool({
|
|
144
|
+
description: "Find the best matching city and its coordinates",
|
|
145
|
+
inputSchema: {
|
|
146
|
+
type: "object",
|
|
147
|
+
properties: {
|
|
148
|
+
query: {
|
|
149
|
+
type: "string",
|
|
150
|
+
minLength: 2,
|
|
151
|
+
maxLength: 200,
|
|
152
|
+
description: "City, postal code, or city and administrative area",
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
required: ["query"],
|
|
156
|
+
additionalProperties: false,
|
|
157
|
+
},
|
|
158
|
+
outputSchema: {
|
|
159
|
+
type: "object",
|
|
160
|
+
properties: {
|
|
161
|
+
name: { type: "string" },
|
|
162
|
+
country: { type: "string" },
|
|
163
|
+
latitude: { type: "number" },
|
|
164
|
+
longitude: { type: "number" },
|
|
165
|
+
timezone: { type: "string" },
|
|
166
|
+
},
|
|
167
|
+
required: ["name", "country", "latitude", "longitude", "timezone"],
|
|
168
|
+
additionalProperties: false,
|
|
169
|
+
},
|
|
170
|
+
annotations: {
|
|
171
|
+
title: "Find Location",
|
|
172
|
+
readOnlyHint: true,
|
|
173
|
+
destructiveHint: false,
|
|
174
|
+
idempotentHint: true,
|
|
175
|
+
openWorldHint: true,
|
|
176
|
+
},
|
|
177
|
+
execute: async ({ query }, { signal }) => {
|
|
178
|
+
const url = new URL(geocodingEndpoint);
|
|
179
|
+
url.searchParams.set("name", query);
|
|
180
|
+
url.searchParams.set("count", "1");
|
|
181
|
+
url.searchParams.set("language", "en");
|
|
182
|
+
url.searchParams.set("format", "json");
|
|
183
|
+
const payload = await fetchJson(url, signal, "Location lookup");
|
|
184
|
+
const results = Array.isArray(payload.results) ? payload.results : [];
|
|
185
|
+
const match = asRecord(results[0]);
|
|
186
|
+
if (match === undefined) {
|
|
187
|
+
throw new ToolError("No matching location was found");
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
name: stringField(match, "name", "Location lookup"),
|
|
191
|
+
country: optionalString(match.country),
|
|
192
|
+
latitude: numberField(match, "latitude", "Location lookup"),
|
|
193
|
+
longitude: numberField(match, "longitude", "Location lookup"),
|
|
194
|
+
timezone: optionalString(match.timezone),
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const getCurrentWeather = defineTool({
|
|
200
|
+
description: "Get current weather for latitude and longitude",
|
|
201
|
+
inputSchema: {
|
|
202
|
+
type: "object",
|
|
203
|
+
properties: {
|
|
204
|
+
latitude: { type: "number", minimum: -90, maximum: 90 },
|
|
205
|
+
longitude: { type: "number", minimum: -180, maximum: 180 },
|
|
206
|
+
},
|
|
207
|
+
required: ["latitude", "longitude"],
|
|
208
|
+
additionalProperties: false,
|
|
209
|
+
},
|
|
210
|
+
outputSchema: {
|
|
211
|
+
type: "object",
|
|
212
|
+
properties: {
|
|
213
|
+
time: { type: "string" },
|
|
214
|
+
temperature: { type: "number" },
|
|
215
|
+
apparentTemperature: { type: "number" },
|
|
216
|
+
relativeHumidity: { type: "number" },
|
|
217
|
+
precipitation: { type: "number" },
|
|
218
|
+
weatherCode: { type: "number" },
|
|
219
|
+
condition: { type: "string" },
|
|
220
|
+
windSpeed: { type: "number" },
|
|
221
|
+
units: {
|
|
222
|
+
type: "object",
|
|
223
|
+
properties: {
|
|
224
|
+
temperature: { type: "string" },
|
|
225
|
+
precipitation: { type: "string" },
|
|
226
|
+
windSpeed: { type: "string" },
|
|
227
|
+
},
|
|
228
|
+
required: ["temperature", "precipitation", "windSpeed"],
|
|
229
|
+
additionalProperties: false,
|
|
230
|
+
},
|
|
231
|
+
},
|
|
232
|
+
required: [
|
|
233
|
+
"time",
|
|
234
|
+
"temperature",
|
|
235
|
+
"apparentTemperature",
|
|
236
|
+
"relativeHumidity",
|
|
237
|
+
"precipitation",
|
|
238
|
+
"weatherCode",
|
|
239
|
+
"condition",
|
|
240
|
+
"windSpeed",
|
|
241
|
+
"units",
|
|
242
|
+
],
|
|
243
|
+
additionalProperties: false,
|
|
244
|
+
},
|
|
245
|
+
annotations: {
|
|
246
|
+
title: "Get Current Weather",
|
|
247
|
+
readOnlyHint: true,
|
|
248
|
+
destructiveHint: false,
|
|
249
|
+
idempotentHint: true,
|
|
250
|
+
openWorldHint: true,
|
|
251
|
+
},
|
|
252
|
+
execute: async ({ latitude, longitude }, { signal }) => {
|
|
253
|
+
const url = new URL(forecastEndpoint);
|
|
254
|
+
url.searchParams.set("latitude", String(latitude));
|
|
255
|
+
url.searchParams.set("longitude", String(longitude));
|
|
256
|
+
url.searchParams.set(
|
|
257
|
+
"current",
|
|
258
|
+
"temperature_2m,apparent_temperature,relative_humidity_2m,precipitation,weather_code,wind_speed_10m",
|
|
259
|
+
);
|
|
260
|
+
url.searchParams.set("timezone", "auto");
|
|
261
|
+
const payload = await fetchJson(url, signal, "Weather lookup");
|
|
262
|
+
const current = requiredRecord(payload.current, "Weather lookup");
|
|
263
|
+
const units = requiredRecord(payload.current_units, "Weather lookup");
|
|
264
|
+
const weatherCode = numberField(current, "weather_code", "Weather lookup");
|
|
265
|
+
return {
|
|
266
|
+
time: stringField(current, "time", "Weather lookup"),
|
|
267
|
+
temperature: numberField(current, "temperature_2m", "Weather lookup"),
|
|
268
|
+
apparentTemperature: numberField(
|
|
269
|
+
current,
|
|
270
|
+
"apparent_temperature",
|
|
271
|
+
"Weather lookup",
|
|
272
|
+
),
|
|
273
|
+
relativeHumidity: numberField(
|
|
274
|
+
current,
|
|
275
|
+
"relative_humidity_2m",
|
|
276
|
+
"Weather lookup",
|
|
277
|
+
),
|
|
278
|
+
precipitation: numberField(current, "precipitation", "Weather lookup"),
|
|
279
|
+
weatherCode,
|
|
280
|
+
condition: weatherCondition(weatherCode),
|
|
281
|
+
windSpeed: numberField(current, "wind_speed_10m", "Weather lookup"),
|
|
282
|
+
units: {
|
|
283
|
+
temperature: stringField(units, "temperature_2m", "Weather lookup"),
|
|
284
|
+
precipitation: stringField(units, "precipitation", "Weather lookup"),
|
|
285
|
+
windSpeed: stringField(units, "wind_speed_10m", "Weather lookup"),
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
},
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
async function fetchJson(url, signal, label) {
|
|
292
|
+
try {
|
|
293
|
+
const response = await fetch(url, {
|
|
294
|
+
signal,
|
|
295
|
+
headers: { accept: "application/json" },
|
|
296
|
+
});
|
|
297
|
+
if (!response.ok) {
|
|
298
|
+
throw new ToolError(label + " returned HTTP " + response.status);
|
|
299
|
+
}
|
|
300
|
+
return requiredRecord(await response.json(), label);
|
|
301
|
+
} catch (error) {
|
|
302
|
+
if (signal.aborted || error instanceof ToolError) throw error;
|
|
303
|
+
throw new ToolError(label + " failed", { cause: error });
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function requiredRecord(value, label) {
|
|
308
|
+
const record = asRecord(value);
|
|
309
|
+
if (record === undefined) {
|
|
310
|
+
throw new ToolError(label + " returned an unexpected response");
|
|
311
|
+
}
|
|
312
|
+
return record;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function asRecord(value) {
|
|
316
|
+
return typeof value === "object" && value !== null && !Array.isArray(value)
|
|
317
|
+
? value
|
|
318
|
+
: undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function stringField(record, key, label) {
|
|
322
|
+
const value = record[key];
|
|
323
|
+
if (typeof value !== "string") {
|
|
324
|
+
throw new ToolError(label + " returned an unexpected response");
|
|
325
|
+
}
|
|
326
|
+
return value;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function numberField(record, key, label) {
|
|
330
|
+
const value = record[key];
|
|
331
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
332
|
+
throw new ToolError(label + " returned an unexpected response");
|
|
333
|
+
}
|
|
334
|
+
return value;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function optionalString(value) {
|
|
338
|
+
return typeof value === "string" ? value : "";
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function weatherCondition(code) {
|
|
342
|
+
if (code === 0) return "clear sky";
|
|
343
|
+
if (code <= 3) return "partly cloudy";
|
|
344
|
+
if (code === 45 || code === 48) return "fog";
|
|
345
|
+
if (code <= 57) return "drizzle";
|
|
346
|
+
if (code <= 67) return "rain";
|
|
347
|
+
if (code <= 77) return "snow";
|
|
348
|
+
if (code <= 82) return "rain showers";
|
|
349
|
+
if (code <= 86) return "snow showers";
|
|
350
|
+
if (code >= 95) return "thunderstorm";
|
|
351
|
+
return "unknown";
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
`;
|
|
355
|
+
}
|
|
356
|
+
//# sourceMappingURL=source-template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"source-template.js","sourceRoot":"","sources":["../src/source-template.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-template.d.ts","sourceRoot":"","sources":["../src/verify-template.ts"],"names":[],"mappings":"AAAA,wBAAgB,kBAAkB,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CA+GzE"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
export function renderVerifyScript(sourceNames) {
|
|
2
|
+
return `import assert from "node:assert/strict";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { Client } from "@modelcontextprotocol/client";
|
|
7
|
+
import { StdioClientTransport } from "@modelcontextprotocol/client/stdio";
|
|
8
|
+
|
|
9
|
+
const root = fileURLToPath(new URL("../", import.meta.url));
|
|
10
|
+
const client = new Client(
|
|
11
|
+
{ name: "codemodekit-generated-verifier", version: "1.0.0" },
|
|
12
|
+
{
|
|
13
|
+
versionNegotiation: {
|
|
14
|
+
mode: "auto",
|
|
15
|
+
probe: { timeoutMs: 5_000, maxRetries: 0 },
|
|
16
|
+
},
|
|
17
|
+
inputRequired: { autoFulfill: false },
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
await client.connect(
|
|
23
|
+
new StdioClientTransport({
|
|
24
|
+
command: process.execPath,
|
|
25
|
+
args: [fileURLToPath(new URL("../src/server.mjs", import.meta.url))],
|
|
26
|
+
cwd: root,
|
|
27
|
+
stderr: "pipe",
|
|
28
|
+
}),
|
|
29
|
+
{ timeout: 15_000 },
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const listed = await client.listTools();
|
|
33
|
+
assert.deepEqual(
|
|
34
|
+
listed.tools.map((tool) => tool.name).sort(),
|
|
35
|
+
["run_typescript", "search_tools"],
|
|
36
|
+
"The server must expose exactly the two Code Mode tools",
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
for (const source of ${JSON.stringify(sourceNames)}) {
|
|
40
|
+
const search = await client.callTool({
|
|
41
|
+
name: "search_tools",
|
|
42
|
+
arguments: { query: source, source, detail: "summary", limit: 1 },
|
|
43
|
+
});
|
|
44
|
+
assert.notEqual(search.isError, true, "search_tools failed for " + source);
|
|
45
|
+
assert.equal(typeof search.structuredContent?.catalogRevision, "string");
|
|
46
|
+
assert.equal(typeof search.structuredContent?.returned, "number");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const isolation = await client.callTool({
|
|
50
|
+
name: "run_typescript",
|
|
51
|
+
arguments: {
|
|
52
|
+
code: \`return {
|
|
53
|
+
sum: [1, 2, 3].reduce((total, value) => total + value, 0),
|
|
54
|
+
process: typeof process,
|
|
55
|
+
require: typeof require,
|
|
56
|
+
fetch: typeof fetch,
|
|
57
|
+
};\`,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
assert.equal(isolation.structuredContent?.ok, true);
|
|
61
|
+
assert.deepEqual(isolation.structuredContent?.value, {
|
|
62
|
+
sum: 6,
|
|
63
|
+
process: "undefined",
|
|
64
|
+
require: "undefined",
|
|
65
|
+
fetch: "undefined",
|
|
66
|
+
});
|
|
67
|
+
assert.deepEqual(isolation.structuredContent?.logs, []);
|
|
68
|
+
|
|
69
|
+
await expectToolError(
|
|
70
|
+
() => client.callTool({ name: "run_typescript", arguments: {} }),
|
|
71
|
+
/code|required/i,
|
|
72
|
+
);
|
|
73
|
+
await expectToolError(
|
|
74
|
+
() => client.callTool({ name: "not_a_code_mode_tool", arguments: {} }),
|
|
75
|
+
/tool|not found|unknown/i,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const liveFile = process.env.CODEMODEKIT_VERIFY_CODE_FILE;
|
|
79
|
+
if (liveFile !== undefined && liveFile.trim() !== "") {
|
|
80
|
+
const code = await readFile(liveFile, "utf8");
|
|
81
|
+
const live = await client.callTool({
|
|
82
|
+
name: "run_typescript",
|
|
83
|
+
arguments: { code },
|
|
84
|
+
});
|
|
85
|
+
assert.equal(live.structuredContent?.ok, true, "Live provider composition failed");
|
|
86
|
+
assert.equal(
|
|
87
|
+
live.structuredContent?.value?.verified,
|
|
88
|
+
true,
|
|
89
|
+
"Live verifier code must return an object containing verified: true",
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
process.stdout.write(
|
|
94
|
+
\`Verified Code Mode surface, ${String(sourceNames.length)} source(s), sandbox isolation\${liveFile ? ", and live composition" : ""}.\\n\`,
|
|
95
|
+
);
|
|
96
|
+
} finally {
|
|
97
|
+
await client.close();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function expectToolError(call, pattern) {
|
|
101
|
+
let result;
|
|
102
|
+
try {
|
|
103
|
+
result = await call();
|
|
104
|
+
} catch (error) {
|
|
105
|
+
assert.match(error instanceof Error ? error.message : String(error), pattern);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
assert.equal(result.isError, true, "Expected an MCP tool error result");
|
|
109
|
+
assert.match(JSON.stringify(result), pattern);
|
|
110
|
+
}
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=verify-template.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify-template.js","sourceRoot":"","sources":["../src/verify-template.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,kBAAkB,CAAC,WAA8B;IAC/D,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;yBAqCgB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;oCAuDhB,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;;;;;;;;;;;;;;;;;CAiB7D,CAAC;AACF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-codemodekit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Scaffold runnable CodeModeKit servers and portable Agent Plugins
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Scaffold runnable CodeModeKit servers, Local Tool starters, and portable Agent Plugins.",
|
|
5
5
|
"author": "Stephen Brown",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"repository": {
|
|
@@ -33,8 +33,7 @@
|
|
|
33
33
|
"files": [
|
|
34
34
|
"dist/*.js",
|
|
35
35
|
"dist/*.d.ts",
|
|
36
|
-
"dist/*.map"
|
|
37
|
-
"skills/**/*"
|
|
36
|
+
"dist/*.map"
|
|
38
37
|
],
|
|
39
38
|
"keywords": [
|
|
40
39
|
"code-mode",
|
|
@@ -48,6 +47,7 @@
|
|
|
48
47
|
"access": "public"
|
|
49
48
|
},
|
|
50
49
|
"dependencies": {
|
|
51
|
-
"esbuild": "0.28.1"
|
|
50
|
+
"esbuild": "0.28.1",
|
|
51
|
+
"@codemodekit/skills": "0.2.0"
|
|
52
52
|
}
|
|
53
53
|
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: build-codemodekit-plugin
|
|
3
|
-
description: Scaffold, retrofit, or maintain CodeModeKit servers and portable Agent Plugins with companion runtime skills. Use when creating a Code Mode MCP wrapper from an MCP command, adding Agent Plugins 1.0 packaging, refreshing generated tool TypeScript, configuring tool policy, installing a plugin into Cursor, or diagnosing a generated CodeModeKit project.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Build a CodeModeKit Plugin
|
|
7
|
-
|
|
8
|
-
Prefer CodeModeKit's generator and lifecycle commands over hand-writing manifests, bundles, or generated tool declarations.
|
|
9
|
-
|
|
10
|
-
## Create a new project
|
|
11
|
-
|
|
12
|
-
1. Identify a short source name and a shell-free MCP executable plus arguments.
|
|
13
|
-
2. Run:
|
|
14
|
-
|
|
15
|
-
```sh
|
|
16
|
-
npm create codemodekit@latest <directory> -- \
|
|
17
|
-
--mcp-name <source-name> \
|
|
18
|
-
--mcp-command '<executable> [args...]' \
|
|
19
|
-
--agent-plugin
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
3. Inspect `src/server.mjs`, `plugin.json`, `mcp.json`, and the generated runtime skill before changing defaults.
|
|
23
|
-
4. Keep credentials outside committed files. Pass an env-file argument to the upstream executable when it supports one; do not embed secrets in `mcp.json`.
|
|
24
|
-
5. Run `npm run plugin:sync` after credentials and the upstream MCP are available.
|
|
25
|
-
6. Run `npm run plugin:build` to recreate the self-contained `dist/plugin` package.
|
|
26
|
-
7. Test `npm start` through an MCP client and confirm both `run_typescript` and `search_tools` are advertised.
|
|
27
|
-
|
|
28
|
-
Read [references/generator.md](references/generator.md) for flags and lifecycle commands.
|
|
29
|
-
|
|
30
|
-
## Update an existing project
|
|
31
|
-
|
|
32
|
-
Preserve the one-file server unless the integration genuinely needs more structure. Use `scaffoldAgentPlugin` for portable manifests and the companion skill, and use `syncAgentPluginSkill` with the project's `CodeMode` instance to refresh catalog-derived references. Never hand-edit `tools.d.ts`; it is generated output.
|
|
33
|
-
|
|
34
|
-
Read [references/programmatic-api.md](references/programmatic-api.md) for the builder contracts and [references/plugin-layout.md](references/plugin-layout.md) for generated-file ownership.
|
|
35
|
-
|
|
36
|
-
## Validate
|
|
37
|
-
|
|
38
|
-
- Treat a failed dependency install or plugin build as a generator failure; do not report the project as ready.
|
|
39
|
-
- Treat a failed catalog sync as recoverable when the upstream MCP merely needs credentials or connectivity. Keep the pending references and report the exact `npm run plugin:sync` follow-up.
|
|
40
|
-
- Reject partial snapshots when any source is unavailable.
|
|
41
|
-
- Keep `SKILL.md` procedural and compact. Put generated schemas, TypeScript declarations, result semantics, and examples in `references/`.
|
|
42
|
-
- Tell runtime agents to search large declaration references for focused matches instead of loading an entire catalog into context.
|
|
43
|
-
- Preserve `search_tools` as a live fallback for pending, stale, or dynamic catalogs.
|
|
44
|
-
- Reinstall the Cursor copy after changing source, policy, metadata, or generated references.
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
# Generator reference
|
|
2
|
-
|
|
3
|
-
## Minimal server
|
|
4
|
-
|
|
5
|
-
```sh
|
|
6
|
-
npm create codemodekit@latest my-code-mode -- \
|
|
7
|
-
--mcp-name upstream \
|
|
8
|
-
--mcp-command 'uvx upstream-mcp'
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Dependency installation is automatic. Use `--no-install` only when installation must happen later. The generator also installs this development-time skill at `.agents/skills/build-codemodekit-plugin`; use `--no-authoring-skill` to omit it.
|
|
12
|
-
|
|
13
|
-
## Agent Plugin
|
|
14
|
-
|
|
15
|
-
Add `--agent-plugin` to generate the portable manifests, companion Agent Skill, catalog references, and self-contained `dist/plugin` artifact. After installation, the generator attempts a live catalog sync. Use `--no-sync` to leave the references pending intentionally.
|
|
16
|
-
|
|
17
|
-
The generated package includes:
|
|
18
|
-
|
|
19
|
-
```json
|
|
20
|
-
{
|
|
21
|
-
"scripts": {
|
|
22
|
-
"start": "node src/server.mjs",
|
|
23
|
-
"plugin:sync": "node src/server.mjs --sync-plugin",
|
|
24
|
-
"plugin:build": "codemodekit-plugin build",
|
|
25
|
-
"plugin:install:cursor": "codemodekit-plugin install cursor",
|
|
26
|
-
"plugin:status:cursor": "codemodekit-plugin status cursor",
|
|
27
|
-
"plugin:uninstall:cursor": "codemodekit-plugin uninstall cursor"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
`plugin:install:cursor` rebuilds the artifact, copies it into Cursor's local plugin directory, and resolves concrete Node and server paths for Cursor. Reload Cursor after install. Re-run the command after source or catalog changes.
|
|
33
|
-
|
|
34
|
-
## Tool policy
|
|
35
|
-
|
|
36
|
-
`--policy allow-all` is the runnable default. It allows every tool advertised by configured sources, subject to restrictions enforced by the upstream server itself.
|
|
37
|
-
|
|
38
|
-
Use `--policy deny-all` when the generated server must begin closed. Replace the policy in `src/server.mjs` with an explicit application policy before expecting tool calls to succeed.
|
|
39
|
-
|
|
40
|
-
## Command parsing
|
|
41
|
-
|
|
42
|
-
`--mcp-command` is parsed into one executable and an argument array without a shell. Quotes and backslash escaping are supported. Pipes, redirects, command substitution, and leading environment assignments are rejected.
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
# Generated plugin layout
|
|
2
|
-
|
|
3
|
-
```text
|
|
4
|
-
my-code-mode/
|
|
5
|
-
├── .agents/skills/build-codemodekit-plugin/
|
|
6
|
-
├── dist/plugin/
|
|
7
|
-
│ ├── emscripten-module.wasm
|
|
8
|
-
│ ├── mcp.json
|
|
9
|
-
│ ├── plugin.json
|
|
10
|
-
│ ├── server.mjs
|
|
11
|
-
│ └── skills/
|
|
12
|
-
├── package.json
|
|
13
|
-
├── plugin.json
|
|
14
|
-
├── mcp.json
|
|
15
|
-
├── src/
|
|
16
|
-
│ └── server.mjs
|
|
17
|
-
└── skills/
|
|
18
|
-
└── use-upstream-codemode/
|
|
19
|
-
├── SKILL.md
|
|
20
|
-
└── references/
|
|
21
|
-
├── catalog-metadata.json
|
|
22
|
-
├── examples.md
|
|
23
|
-
├── result-contract.md
|
|
24
|
-
├── runtime.md
|
|
25
|
-
└── tools.d.ts
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Ownership
|
|
29
|
-
|
|
30
|
-
- The developer owns `src/server.mjs`, tool policy, provider configuration, and plugin metadata.
|
|
31
|
-
- CodeModeKit owns generated `tools.d.ts`, `catalog-metadata.json`, and `dist/plugin`.
|
|
32
|
-
- The generated runtime `SKILL.md` contains stable procedure and should stay small.
|
|
33
|
-
- `runtime.md`, `result-contract.md`, and `examples.md` are scaffolded reference templates and may be tailored when an integration needs additional guidance.
|
|
34
|
-
- `.agents/skills/build-codemodekit-plugin` is development-time authoring guidance and is intentionally excluded from the portable plugin artifact.
|
|
35
|
-
|
|
36
|
-
`mcp.json` exposes the bundled Code Mode server to an Agent Plugins client. The upstream MCP remains configured inside `src/server.mjs`; it is not exposed as a second direct server that would bypass Code Mode policy and sandboxing.
|
|
37
|
-
|
|
38
|
-
The portable `dist/plugin/mcp.json` uses `${PLUGIN_ROOT}` as required by Agent Plugins. Cursor installation produces a separate concrete copy because Cursor currently needs absolute executable and server paths for local plugins.
|