@plasm_lang/vercel-agent 0.3.78 → 0.3.80
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/agent/instructions.md +1 -2
- package/bin/plasm-agent.mjs +5 -7
- package/package.json +26 -19
- package/scripts/plasm-cli.ts +4 -3
- package/scripts/run-plasm-cli.mjs +41 -0
- package/src/cli/init-scaffold.ts +198 -0
- package/src/cli/init.ts +16 -278
- package/src/cli/nitro-dev.ts +2 -6
- package/src/cli/node-dev-imports.ts +14 -0
- package/src/package-version.ts +14 -0
- package/src/project-info.ts +2 -1
- package/templates/mcp-radar/.vercelignore +3 -0
- package/templates/mcp-radar/README.md +139 -0
- package/templates/mcp-radar/agent/agent.ts +38 -0
- package/templates/mcp-radar/agent/catalogs/hackernews/README.md +23 -0
- package/templates/mcp-radar/agent/catalogs/hackernews/domain.yaml +329 -0
- package/templates/mcp-radar/agent/catalogs/hackernews/eval/cases.yaml +112 -0
- package/templates/mcp-radar/agent/catalogs/hackernews/mappings.yaml +129 -0
- package/templates/mcp-radar/agent/catalogs/tavily/README.md +218 -0
- package/templates/mcp-radar/agent/catalogs/tavily/domain.yaml +373 -0
- package/templates/mcp-radar/agent/catalogs/tavily/eval/cases.yaml +56 -0
- package/templates/mcp-radar/agent/catalogs/tavily/eval/google-gemma-3-4b-it.latest.human.txt +67 -0
- package/templates/mcp-radar/agent/catalogs/tavily/eval/google-gemma-3-4b-it.latest.json +363 -0
- package/templates/mcp-radar/agent/catalogs/tavily/mappings.yaml +171 -0
- package/templates/mcp-radar/agent/channels/mcp-radar.ts +85 -0
- package/templates/mcp-radar/agent/hooks/proof-audit.ts +10 -0
- package/templates/mcp-radar/agent/instructions.md +24 -0
- package/templates/mcp-radar/agent/research/mcp-innovations-proof.md +4 -0
- package/templates/mcp-radar/agent/schedules/mcp-radar-scan.ts +14 -0
- package/templates/mcp-radar/agent/skills/mcp-proof-format.md +19 -0
- package/templates/mcp-radar/api/[[...path]].ts +23 -0
- package/templates/mcp-radar/evals/mcp-radar-discover.eval.ts +14 -0
- package/templates/mcp-radar/lib/proof-store.ts +265 -0
- package/templates/mcp-radar/lib/run-radar.ts +214 -0
- package/templates/mcp-radar/nitro.config.ts +17 -0
- package/templates/mcp-radar/package.json +14 -0
- package/templates/mcp-radar/public/index.html +10 -0
- package/templates/mcp-radar/routes/[...path].ts +29 -0
- package/templates/mcp-radar/scripts/run-evals.ts +46 -0
- package/templates/mcp-radar/scripts/smoke-channel.ts +66 -0
- package/templates/mcp-radar/vercel.json +15 -0
- package/templates/scaffold/.vercelignore +3 -0
- package/templates/scaffold/api/[[...path]].ts +23 -0
- package/templates/scaffold/nitro.config.ts +17 -0
- package/templates/scaffold/public/index.html +10 -0
- package/templates/scaffold/public/index.mcp-radar.html +10 -0
- package/templates/scaffold/routes/[...path].ts +29 -0
- package/templates/scaffold/vercel.default.json +4 -0
- package/templates/scaffold/vercel.mcp-radar.json +15 -0
package/src/cli/init.ts
CHANGED
|
@@ -1,112 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { fileURLToPath } from "node:url";
|
|
4
3
|
|
|
5
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
blankPackageJsonScaffold,
|
|
6
|
+
exists,
|
|
7
|
+
patchProjectPackageJson,
|
|
8
|
+
plasmAgentPackageRoot,
|
|
9
|
+
runTemplateInit,
|
|
10
|
+
writeDeployScaffold,
|
|
11
|
+
} from "./init-scaffold.js";
|
|
6
12
|
import type { ResolvedAgentProject } from "./project-root.js";
|
|
7
13
|
|
|
8
|
-
export
|
|
9
|
-
template?: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
const SKIP_TEMPLATE_DIRS = new Set(["node_modules", ".plasm", ".nitro", ".output", "server"]);
|
|
13
|
-
const SKIP_TEMPLATE_FILES = new Set(["package-lock.json"]);
|
|
14
|
-
|
|
15
|
-
function plasmAgentPackageRoot(): string {
|
|
16
|
-
return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function resolveTemplateDir(template: string): string {
|
|
20
|
-
const templates: Record<string, string> = {
|
|
21
|
-
"mcp-radar": path.join(plasmAgentPackageRoot(), "../../../examples/mcp-radar-agent"),
|
|
22
|
-
};
|
|
23
|
-
const dir = templates[template];
|
|
24
|
-
if (!dir) {
|
|
25
|
-
throw new Error(
|
|
26
|
-
`Unknown template "${template}". Available: ${Object.keys(templates).join(", ")}`,
|
|
27
|
-
);
|
|
28
|
-
}
|
|
29
|
-
return dir;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function shouldCopyTemplateEntry(src: string, templateRoot: string): boolean {
|
|
33
|
-
const rel = path.relative(templateRoot, src);
|
|
34
|
-
if (!rel || rel === "") return true;
|
|
35
|
-
const parts = rel.split(path.sep);
|
|
36
|
-
if (parts.some((part) => SKIP_TEMPLATE_DIRS.has(part))) return false;
|
|
37
|
-
if (SKIP_TEMPLATE_FILES.has(path.basename(src))) return false;
|
|
38
|
-
return true;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async function copyTemplate(templateRoot: string, projectRoot: string): Promise<void> {
|
|
42
|
-
await cp(templateRoot, projectRoot, {
|
|
43
|
-
recursive: true,
|
|
44
|
-
filter: (src) => src === templateRoot || shouldCopyTemplateEntry(src, templateRoot),
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
async function patchBootstrapPackageJson(
|
|
49
|
-
projectRoot: string,
|
|
50
|
-
packageRoot: string,
|
|
51
|
-
): Promise<void> {
|
|
52
|
-
const pkgPath = path.join(projectRoot, "package.json");
|
|
53
|
-
const raw = await readFile(pkgPath, "utf8");
|
|
54
|
-
const pkg = JSON.parse(raw) as {
|
|
55
|
-
name?: string;
|
|
56
|
-
scripts?: Record<string, string>;
|
|
57
|
-
dependencies?: Record<string, string>;
|
|
58
|
-
devDependencies?: Record<string, string>;
|
|
59
|
-
};
|
|
60
|
-
pkg.name = path.basename(projectRoot);
|
|
61
|
-
const engineRoot = path.resolve(packageRoot, "../plasm-engine");
|
|
62
|
-
pkg.dependencies = {
|
|
63
|
-
...pkg.dependencies,
|
|
64
|
-
"@plasm_lang/vercel-agent": `file:${path.resolve(packageRoot)}`,
|
|
65
|
-
"@plasm_lang/engine": `file:${engineRoot}`,
|
|
66
|
-
"@vercel/blob": "^0.27.3",
|
|
67
|
-
"@vercel/functions": "^3.4.3",
|
|
68
|
-
"@vercel/kv": "^3.0.0",
|
|
69
|
-
};
|
|
70
|
-
const nodeRunner =
|
|
71
|
-
"node --experimental-strip-types --experimental-transform-types ./node_modules/@plasm_lang/vercel-agent/scripts/plasm-node.mjs";
|
|
72
|
-
pkg.scripts = {
|
|
73
|
-
build: "plasm-agent build",
|
|
74
|
-
"vercel-build": "plasm-agent build",
|
|
75
|
-
dev: "plasm-agent dev",
|
|
76
|
-
"dev:interactive": "plasm-agent dev --interactive",
|
|
77
|
-
info: "plasm-agent info",
|
|
78
|
-
deploy: "vercel deploy",
|
|
79
|
-
...pkg.scripts,
|
|
80
|
-
eval: `${nodeRunner} scripts/run-evals.ts`,
|
|
81
|
-
"smoke:channel": `${nodeRunner} scripts/smoke-channel.ts`,
|
|
82
|
-
};
|
|
83
|
-
pkg.devDependencies = {
|
|
84
|
-
...pkg.devDependencies,
|
|
85
|
-
nitropack: "^2.13.4",
|
|
86
|
-
};
|
|
87
|
-
await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async function runTemplateInit(
|
|
91
|
-
targetDir: string,
|
|
92
|
-
template: string,
|
|
93
|
-
): Promise<ResolvedAgentProject> {
|
|
94
|
-
const projectRoot = path.resolve(targetDir);
|
|
95
|
-
const agentRoot = path.join(projectRoot, "agent");
|
|
96
|
-
if (await exists(path.join(agentRoot, "agent.ts"))) {
|
|
97
|
-
throw new Error(`agent/agent.ts already exists in ${projectRoot}`);
|
|
98
|
-
}
|
|
99
|
-
const templateRoot = resolveTemplateDir(template);
|
|
100
|
-
if (!(await exists(templateRoot))) {
|
|
101
|
-
throw new Error(`Template source missing: ${templateRoot}`);
|
|
102
|
-
}
|
|
103
|
-
await mkdir(projectRoot, { recursive: true });
|
|
104
|
-
await copyTemplate(templateRoot, projectRoot);
|
|
105
|
-
await patchBootstrapPackageJson(projectRoot, plasmAgentPackageRoot());
|
|
106
|
-
await writeVercelScaffold(projectRoot, template);
|
|
107
|
-
await writeNitroScaffold(projectRoot);
|
|
108
|
-
return { projectRoot, agentRoot };
|
|
109
|
-
}
|
|
14
|
+
export type { InitOptions } from "./init-scaffold.js";
|
|
110
15
|
|
|
111
16
|
const AGENT_TS = `import path from "node:path";
|
|
112
17
|
import { fileURLToPath } from "node:url";
|
|
@@ -203,177 +108,12 @@ PLASM_TENANT_SCOPE=local
|
|
|
203
108
|
PORT=3000
|
|
204
109
|
`;
|
|
205
110
|
|
|
206
|
-
const VERCEL_JSON_DEFAULT = `{
|
|
207
|
-
"buildCommand": "plasm-agent build",
|
|
208
|
-
"rewrites": [{ "source": "/(.*)", "destination": "/api/$1" }]
|
|
209
|
-
}
|
|
210
|
-
`;
|
|
211
|
-
|
|
212
|
-
const VERCEL_JSON_MCP_RADAR = `{
|
|
213
|
-
"buildCommand": "plasm-agent build",
|
|
214
|
-
"rewrites": [{ "source": "/(.*)", "destination": "/api/$1" }],
|
|
215
|
-
"crons": [
|
|
216
|
-
{
|
|
217
|
-
"path": "/internal/cron/mcp-radar-scan",
|
|
218
|
-
"schedule": "0 */6 * * *"
|
|
219
|
-
}
|
|
220
|
-
],
|
|
221
|
-
"functions": {
|
|
222
|
-
"api/**": {
|
|
223
|
-
"maxDuration": 300
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
`;
|
|
228
|
-
|
|
229
|
-
const VERCELIGNORE = `node_modules
|
|
230
|
-
.env*
|
|
231
|
-
agent/.plasm/research
|
|
232
|
-
`;
|
|
233
|
-
|
|
234
|
-
const API_HANDLER_TS = `import path from "node:path";
|
|
235
|
-
import { fileURLToPath } from "node:url";
|
|
236
|
-
|
|
237
|
-
import agentDefinition from "../agent/agent.js";
|
|
238
|
-
import { createPlasmApp, vercelPlasmHandler } from "@plasm_lang/vercel-agent";
|
|
239
|
-
|
|
240
|
-
const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
241
|
-
const agentRoot = path.join(packageRoot, "agent");
|
|
242
|
-
|
|
243
|
-
let app: Awaited<ReturnType<typeof createPlasmApp>> | undefined;
|
|
244
|
-
|
|
245
|
-
export default async function handler(
|
|
246
|
-
req: import("node:http").IncomingMessage,
|
|
247
|
-
res: import("node:http").ServerResponse,
|
|
248
|
-
): Promise<void> {
|
|
249
|
-
app ??= await createPlasmApp({
|
|
250
|
-
agentRoot,
|
|
251
|
-
definition: agentDefinition,
|
|
252
|
-
mode: "prod",
|
|
253
|
-
sessions: false,
|
|
254
|
-
});
|
|
255
|
-
await vercelPlasmHandler(app)(req, res);
|
|
256
|
-
}
|
|
257
|
-
`;
|
|
258
|
-
|
|
259
|
-
const NITRO_CONFIG_TS = `import { defineNitroConfig } from "nitropack/config";
|
|
260
|
-
|
|
261
|
-
export default defineNitroConfig({
|
|
262
|
-
compatibilityDate: "2026-06-26",
|
|
263
|
-
srcDir: ".",
|
|
264
|
-
ignore: ["api/**"],
|
|
265
|
-
devServer: {
|
|
266
|
-
port: Number(process.env.PORT ?? 3000),
|
|
267
|
-
host: process.env.HOST ?? "127.0.0.1",
|
|
268
|
-
},
|
|
269
|
-
typescript: {
|
|
270
|
-
strict: false,
|
|
271
|
-
},
|
|
272
|
-
externals: {
|
|
273
|
-
inline: ["@plasm_lang/engine"],
|
|
274
|
-
},
|
|
275
|
-
});
|
|
276
|
-
`;
|
|
277
|
-
|
|
278
|
-
const NITRO_CATCHALL_ROUTE_TS = `import path from "node:path";
|
|
279
|
-
|
|
280
|
-
import { fromNodeMiddleware } from "h3";
|
|
281
|
-
|
|
282
|
-
import agentDefinition from "../agent/agent.js";
|
|
283
|
-
import { createPlasmApp, vercelPlasmHandler } from "@plasm_lang/vercel-agent";
|
|
284
|
-
|
|
285
|
-
const agentRoot = path.join(process.cwd(), "agent");
|
|
286
|
-
|
|
287
|
-
let appPromise: ReturnType<typeof createPlasmApp> | undefined;
|
|
288
|
-
|
|
289
|
-
async function plasmApp() {
|
|
290
|
-
appPromise ??= createPlasmApp({
|
|
291
|
-
agentRoot,
|
|
292
|
-
definition: agentDefinition,
|
|
293
|
-
mode: "prod",
|
|
294
|
-
sessions: false,
|
|
295
|
-
});
|
|
296
|
-
return appPromise;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
export default fromNodeMiddleware(async (req, res) => {
|
|
300
|
-
const app = await plasmApp();
|
|
301
|
-
await new Promise<void>((resolve, reject) => {
|
|
302
|
-
res.once("finish", () => resolve());
|
|
303
|
-
res.once("error", reject);
|
|
304
|
-
void vercelPlasmHandler(app)(req, res).catch(reject);
|
|
305
|
-
});
|
|
306
|
-
});
|
|
307
|
-
`;
|
|
308
|
-
|
|
309
|
-
const PACKAGE_JSON_TEMPLATE = {
|
|
310
|
-
name: "my-plasm-agent",
|
|
311
|
-
private: true,
|
|
312
|
-
type: "module",
|
|
313
|
-
scripts: {
|
|
314
|
-
dev: "plasm-agent dev",
|
|
315
|
-
"dev:interactive": "plasm-agent dev --interactive",
|
|
316
|
-
build: "plasm-agent build",
|
|
317
|
-
"vercel-build": "plasm-agent build",
|
|
318
|
-
info: "plasm-agent info",
|
|
319
|
-
deploy: "vercel deploy",
|
|
320
|
-
},
|
|
321
|
-
dependencies: {} as Record<string, string>,
|
|
322
|
-
devDependencies: {
|
|
323
|
-
nitropack: "^2.13.4",
|
|
324
|
-
},
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
async function writeVercelScaffold(
|
|
328
|
-
projectRoot: string,
|
|
329
|
-
template?: string,
|
|
330
|
-
): Promise<void> {
|
|
331
|
-
const vercelJson =
|
|
332
|
-
template === "mcp-radar" ? VERCEL_JSON_MCP_RADAR : VERCEL_JSON_DEFAULT;
|
|
333
|
-
await mkdir(path.join(projectRoot, "api"), { recursive: true });
|
|
334
|
-
await writeFile(path.join(projectRoot, "vercel.json"), vercelJson, "utf8");
|
|
335
|
-
await writeFile(path.join(projectRoot, ".vercelignore"), VERCELIGNORE, "utf8");
|
|
336
|
-
await writeFile(
|
|
337
|
-
path.join(projectRoot, "api", "[[...path]].ts"),
|
|
338
|
-
API_HANDLER_TS,
|
|
339
|
-
"utf8",
|
|
340
|
-
);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
async function writeNitroScaffold(projectRoot: string): Promise<void> {
|
|
344
|
-
await mkdir(path.join(projectRoot, "routes"), { recursive: true });
|
|
345
|
-
await writeFile(path.join(projectRoot, "nitro.config.ts"), NITRO_CONFIG_TS, "utf8");
|
|
346
|
-
await writeFile(
|
|
347
|
-
path.join(projectRoot, "routes", "[...path].ts"),
|
|
348
|
-
NITRO_CATCHALL_ROUTE_TS,
|
|
349
|
-
"utf8",
|
|
350
|
-
);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
function packageJsonScaffold(): Record<string, unknown> {
|
|
354
|
-
return {
|
|
355
|
-
...PACKAGE_JSON_TEMPLATE,
|
|
356
|
-
dependencies: {
|
|
357
|
-
"@plasm_lang/vercel-agent": `^${FRAMEWORK_VERSION}`,
|
|
358
|
-
},
|
|
359
|
-
};
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
async function exists(p: string): Promise<boolean> {
|
|
363
|
-
try {
|
|
364
|
-
await access(p);
|
|
365
|
-
return true;
|
|
366
|
-
} catch {
|
|
367
|
-
return false;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
111
|
export async function runPlasmInit(
|
|
372
112
|
targetDir: string,
|
|
373
|
-
options?: InitOptions,
|
|
113
|
+
options?: import("./init-scaffold.js").InitOptions,
|
|
374
114
|
): Promise<ResolvedAgentProject> {
|
|
375
115
|
if (options?.template) {
|
|
376
|
-
return runTemplateInit(targetDir, options.template);
|
|
116
|
+
return runTemplateInit(targetDir, options.template, options);
|
|
377
117
|
}
|
|
378
118
|
|
|
379
119
|
const projectRoot = path.resolve(targetDir);
|
|
@@ -399,21 +139,19 @@ export async function runPlasmInit(
|
|
|
399
139
|
if (!(await exists(path.join(projectRoot, "package.json")))) {
|
|
400
140
|
await writeFile(
|
|
401
141
|
path.join(projectRoot, "package.json"),
|
|
402
|
-
`${JSON.stringify(
|
|
142
|
+
`${JSON.stringify(blankPackageJsonScaffold(), null, 2)}\n`,
|
|
403
143
|
"utf8",
|
|
404
144
|
);
|
|
405
|
-
await
|
|
145
|
+
await patchProjectPackageJson(projectRoot, plasmAgentPackageRoot(), { npm: options?.npm });
|
|
406
146
|
}
|
|
407
147
|
|
|
408
|
-
await
|
|
409
|
-
await writeNitroScaffold(projectRoot);
|
|
410
|
-
|
|
148
|
+
await writeDeployScaffold(projectRoot);
|
|
411
149
|
return { projectRoot, agentRoot };
|
|
412
150
|
}
|
|
413
151
|
|
|
414
152
|
export function formatInitSuccess(
|
|
415
153
|
project: ResolvedAgentProject,
|
|
416
|
-
options?: InitOptions,
|
|
154
|
+
options?: import("./init-scaffold.js").InitOptions,
|
|
417
155
|
): string {
|
|
418
156
|
if (options?.template === "mcp-radar") {
|
|
419
157
|
return [
|
package/src/cli/nitro-dev.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { access } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
|
|
6
|
+
import { nitroDevNodeOptions } from "./node-dev-imports.js";
|
|
6
7
|
import type { ResolvedAgentProject } from "./project-root.js";
|
|
7
8
|
|
|
8
9
|
function plasmAgentPackageRoot(): string {
|
|
@@ -32,12 +33,7 @@ function nitroBin(projectRoot: string): string {
|
|
|
32
33
|
export async function startNitroDevForProject(project: ResolvedAgentProject): Promise<void> {
|
|
33
34
|
await assertNitroScaffold(project.projectRoot);
|
|
34
35
|
|
|
35
|
-
const
|
|
36
|
-
const nodeOptions = [
|
|
37
|
-
"--experimental-strip-types",
|
|
38
|
-
"--experimental-transform-types",
|
|
39
|
-
`--import=${loader}`,
|
|
40
|
-
].join(" ");
|
|
36
|
+
const nodeOptions = nitroDevNodeOptions(plasmAgentPackageRoot());
|
|
41
37
|
|
|
42
38
|
const bin = nitroBin(project.projectRoot);
|
|
43
39
|
try {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
|
|
7
|
+
/** NODE_OPTIONS imports for Nitro dev (tsx + Plasm .ts resolution). */
|
|
8
|
+
export function nitroDevNodeOptions(packageRoot: string): string {
|
|
9
|
+
const tsxImport = require.resolve("tsx/esm");
|
|
10
|
+
const plasmLoader = pathToFileURL(
|
|
11
|
+
path.join(packageRoot, "scripts", "register-plasm-loader.mjs"),
|
|
12
|
+
).href;
|
|
13
|
+
return [`--import=${tsxImport}`, `--import=${plasmLoader}`].join(" ");
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
5
|
+
let cachedVersion: string | undefined;
|
|
6
|
+
|
|
7
|
+
/** Published semver from @plasm_lang/vercel-agent package.json. */
|
|
8
|
+
export function frameworkPackageVersion(): string {
|
|
9
|
+
if (cachedVersion) return cachedVersion;
|
|
10
|
+
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
|
+
const raw = readFileSync(path.join(packageRoot, "package.json"), "utf8");
|
|
12
|
+
cachedVersion = (JSON.parse(raw) as { version: string }).version;
|
|
13
|
+
return cachedVersion;
|
|
14
|
+
}
|
package/src/project-info.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { isNativeEngineAvailable } from "./engine/napi-binding.js";
|
|
|
10
10
|
import { exportScheduleCronManifest } from "./authoring/schedule-manager.js";
|
|
11
11
|
import { isGatewayConfigured } from "./gateway-model.js";
|
|
12
12
|
import type { LoadedProjectSlots } from "./authoring/slot-loader.js";
|
|
13
|
+
import { frameworkPackageVersion } from "./package-version.js";
|
|
13
14
|
import { resolveCatalogLiveHash } from "./stubs/catalog-hash.js";
|
|
14
15
|
import { stubFreshness } from "./stubs/generator.js";
|
|
15
16
|
|
|
@@ -21,7 +22,7 @@ export const PLASM_LANGUAGE_TOOLS = [
|
|
|
21
22
|
] as const;
|
|
22
23
|
|
|
23
24
|
export const FRAMEWORK_NAME = "@plasm_lang/vercel-agent";
|
|
24
|
-
export const FRAMEWORK_VERSION =
|
|
25
|
+
export const FRAMEWORK_VERSION = frameworkPackageVersion();
|
|
25
26
|
|
|
26
27
|
export interface CatalogInfoEntry {
|
|
27
28
|
name: string;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# MCP Radar Agent
|
|
2
|
+
|
|
3
|
+
Event-driven research example for [`@plasm_lang/vercel-agent`](../../plasm-oss/packages/plasm-agent/): watch Hacker News for **MCP** innovation signals, corroborate with **Tavily**, and append structured entries to a maintained proof log.
|
|
4
|
+
|
|
5
|
+
## Purpose
|
|
6
|
+
|
|
7
|
+
Demonstrates Plasm’s **dual surface**:
|
|
8
|
+
|
|
9
|
+
- **Code surface** — channel/schedule handlers preflight HN via typed stubs and own proof persistence
|
|
10
|
+
- **Model surface** — federated `plasm_context` (hackernews + tavily) → `plasm` → `plasm_run` for synthesis
|
|
11
|
+
|
|
12
|
+
## Bootstrap from CLI
|
|
13
|
+
|
|
14
|
+
Scaffold a fresh copy (verified by `npm run smoke:bootstrap` in the framework package):
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
mkdir /tmp/my-radar && cd /tmp/my-radar
|
|
18
|
+
plasm-agent init --template mcp-radar .
|
|
19
|
+
npm install
|
|
20
|
+
plasm-agent build
|
|
21
|
+
npm run smoke:channel
|
|
22
|
+
plasm-agent dev
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The canonical template source is this directory (`examples/mcp-radar-agent/`).
|
|
26
|
+
|
|
27
|
+
## Deploy to Vercel
|
|
28
|
+
|
|
29
|
+
Low-friction production path (eve-aligned): `vercel.json` + catch-all `api/[[...path]].ts` mounts the same routes as local dev (channels, cron, `/plasm/v1/info`).
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
cd examples/mcp-radar-agent # or your bootstrapped project
|
|
33
|
+
plasm-agent link
|
|
34
|
+
plasm-agent build
|
|
35
|
+
vercel env pull .env.local # AI_GATEWAY_API_KEY, CRON_SECRET, KV, Blob
|
|
36
|
+
vercel deploy
|
|
37
|
+
curl -s "$DEPLOY_URL/channel/mcp-radar/status" | jq .
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
| Variable | Deploy |
|
|
41
|
+
|----------|--------|
|
|
42
|
+
| `AI_GATEWAY_API_KEY` | Required for agent synthesis |
|
|
43
|
+
| `CRON_SECRET` | Required — Vercel Cron hits `/internal/cron/mcp-radar-scan` |
|
|
44
|
+
| `KV_REST_API_URL` + `KV_REST_API_TOKEN` | Durable seen-items + last-run state |
|
|
45
|
+
| `BLOB_READ_WRITE_TOKEN` | Durable proof markdown log |
|
|
46
|
+
| `TAVILY_API_TOKEN` | Optional corroboration |
|
|
47
|
+
|
|
48
|
+
On Vercel, `POST /channel/mcp-radar/run` returns `202` and continues in the background via `waitUntil`. Locally it runs synchronously for smoke tests.
|
|
49
|
+
|
|
50
|
+
Monorepo deploy: set Vercel **Root Directory** to `examples/mcp-radar-agent`.
|
|
51
|
+
|
|
52
|
+
## Setup (monorepo checkout)
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cd examples/mcp-radar-agent
|
|
56
|
+
npm install
|
|
57
|
+
cp .env.example .env.local
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
| Variable | Required | Role |
|
|
61
|
+
|----------|----------|------|
|
|
62
|
+
| `AI_GATEWAY_API_KEY` | Yes (live runs) | Agent model turns |
|
|
63
|
+
| `TAVILY_API_TOKEN` | Optional | Tavily corroboration; HN-only when unset |
|
|
64
|
+
|
|
65
|
+
Build CGS stubs (symlinked catalogs):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npm run build
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Dev
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npm run build
|
|
75
|
+
npm run dev # Nitro dev server — Vercel routing parity (channels, cron, /plasm/v1/*)
|
|
76
|
+
npm run dev:interactive # optional: in-process server + TUI + sessions + hot reload
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`plasm-agent dev` starts Nitro by default so channel routes like `/channel/mcp-radar/status` match production.
|
|
80
|
+
|
|
81
|
+
Slash commands in the interactive TUI: `/info`, `/catalogs`, `/new`, `/quit`.
|
|
82
|
+
|
|
83
|
+
## Channel API
|
|
84
|
+
|
|
85
|
+
Start headless dev server (`npm run dev:headless`), then:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
BASE=http://127.0.0.1:3000
|
|
89
|
+
|
|
90
|
+
# Status
|
|
91
|
+
curl -s "$BASE/channel/mcp-radar/status" | jq .
|
|
92
|
+
|
|
93
|
+
# Read proof log (markdown)
|
|
94
|
+
curl -s "$BASE/channel/mcp-radar/proof"
|
|
95
|
+
|
|
96
|
+
# Trigger one scan (dedupes seen HN ids)
|
|
97
|
+
curl -s -X POST "$BASE/channel/mcp-radar/run" -H 'content-type: application/json' -d '{}'
|
|
98
|
+
|
|
99
|
+
# Force scan even when no new ids
|
|
100
|
+
curl -s -X POST "$BASE/channel/mcp-radar/run" -H 'content-type: application/json' -d '{"force":true}'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Proof artifact (local fs): [`agent/research/mcp-innovations-proof.md`](./agent/research/mcp-innovations-proof.md)
|
|
104
|
+
|
|
105
|
+
Dedupe state (local fs): `agent/.plasm/research/seen-hn-items.json` (gitignored). On Vercel deploy, proof + dedupe use Blob + KV when env vars are set.
|
|
106
|
+
|
|
107
|
+
## Schedule
|
|
108
|
+
|
|
109
|
+
`agent/schedules/mcp-radar-scan.ts` runs every **6 hours** (`0 */6 * * *`). Vercel Cron calls `/internal/cron/mcp-radar-scan` with `Authorization: Bearer $CRON_SECRET`.
|
|
110
|
+
|
|
111
|
+
## Evals
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
npm run eval # requires AI_GATEWAY_API_KEY
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Live eval: `evals/mcp-radar-discover.eval.ts`
|
|
118
|
+
|
|
119
|
+
## Smoke
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm run build
|
|
123
|
+
npm run smoke:channel
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Catalogs
|
|
127
|
+
|
|
128
|
+
Symlinked from monorepo `apis/`:
|
|
129
|
+
|
|
130
|
+
- `agent/catalogs/hackernews` → `plasm-oss/apis/hackernews`
|
|
131
|
+
- `agent/catalogs/tavily` → `plasm-oss/apis/tavily`
|
|
132
|
+
|
|
133
|
+
## Stable session intent
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
track MCP innovations from Hacker News and corroborate with Tavily web search
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Federated seeds: `hackernews:Item`, `tavily:SearchResult`
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createAgentFromProject,
|
|
6
|
+
createProductionHostTransport,
|
|
7
|
+
defineAgent,
|
|
8
|
+
loadAgentEnv,
|
|
9
|
+
} from "@plasm_lang/vercel-agent";
|
|
10
|
+
|
|
11
|
+
loadAgentEnv();
|
|
12
|
+
|
|
13
|
+
const agentRoot = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
|
|
15
|
+
const agentDefinition = defineAgent({
|
|
16
|
+
model: process.env.PLASM_AGENT_MODEL ?? "anthropic/claude-sonnet-4.6",
|
|
17
|
+
compaction: { thresholdPercent: 0.75 },
|
|
18
|
+
modelOptions: { temperature: 0.2 },
|
|
19
|
+
experimental: {
|
|
20
|
+
workflow: { world: { type: "vercel" } },
|
|
21
|
+
skills: true,
|
|
22
|
+
},
|
|
23
|
+
build: {
|
|
24
|
+
externalDependencies: ["@plasm_lang/engine"],
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
export default agentDefinition;
|
|
29
|
+
|
|
30
|
+
export async function createPlasmAgent() {
|
|
31
|
+
return createAgentFromProject(agentDefinition, {
|
|
32
|
+
agentRoot,
|
|
33
|
+
tenantScope: process.env.PLASM_TENANT_SCOPE ?? "mcp-radar",
|
|
34
|
+
maxSteps: 24,
|
|
35
|
+
telemetry: process.env.PLASM_AGENT_TELEMETRY !== "0",
|
|
36
|
+
hostTransport: createProductionHostTransport(),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Hacker News (Firebase API)
|
|
2
|
+
|
|
3
|
+
Read-only mirror of the public [Hacker News API](https://github.com/HackerNews/API) plus the [Algolia HN search API](https://hn.algolia.com/api) (full-URL CML; same `http_backend` is Firebase for `item_get`). No authentication.
|
|
4
|
+
|
|
5
|
+
## Scope
|
|
6
|
+
|
|
7
|
+
- **item_search** / **item_search_by_date** — Algolia `search` and `search_by_date` (default `tags=story`). Rows use `objectID` as `id`; use **item_get** to load the Firebase item.
|
|
8
|
+
- **item_feed_query** — ordered item ids for `top`, `new`, `best`, `ask`, `show`, or `job` (ids only; use **item_get** to hydrate).
|
|
9
|
+
- **max_item_id_query** — current largest item id (`maxitem.json` returns a bare integer; mapped via `wrap_root_scalar`).
|
|
10
|
+
- **recent_updated_item_query** / **recent_updated_user_query** — live slices from `updates.json` (`items` and `profiles` arrays; profiles are usernames only).
|
|
11
|
+
- **item_get** / **user_get** — full JSON for an item or user. Poll stories expose **`parts`** (option ids) and the **`poll_options`** relation for chaining.
|
|
12
|
+
|
|
13
|
+
## Try it
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cargo run --bin plasm-repl -- --schema apis/hackernews
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Eval coverage (no LLM):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
cargo run -p plasm-eval -- coverage --schema apis/hackernews --cases apis/hackernews/eval/cases.yaml
|
|
23
|
+
```
|