@plasm_lang/vercel-agent 0.3.77 → 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.
Files changed (58) hide show
  1. package/README.md +9 -2
  2. package/agent/instructions.md +1 -2
  3. package/bin/plasm-agent.mjs +5 -7
  4. package/package.json +29 -19
  5. package/scripts/build-stubs.ts +27 -0
  6. package/scripts/plasm-cli.ts +162 -0
  7. package/scripts/register-plasm-loader.mjs +6 -0
  8. package/scripts/run-plasm-cli.mjs +41 -0
  9. package/src/cli/init-scaffold.ts +198 -0
  10. package/src/cli/init.ts +20 -208
  11. package/src/cli/nitro-dev.ts +69 -0
  12. package/src/cli/node-dev-imports.ts +14 -0
  13. package/src/engine/create-host-transport.ts +7 -0
  14. package/src/index.ts +1 -1
  15. package/src/package-version.ts +14 -0
  16. package/src/project-info.ts +2 -1
  17. package/src/stubs/catalog-client.ts +2 -2
  18. package/src/tools/harness-tools.ts +42 -41
  19. package/src/tools/plasm-tools.ts +64 -55
  20. package/src/tools/tool-input.ts +9 -0
  21. package/templates/mcp-radar/.vercelignore +3 -0
  22. package/templates/mcp-radar/README.md +139 -0
  23. package/templates/mcp-radar/agent/agent.ts +38 -0
  24. package/templates/mcp-radar/agent/catalogs/hackernews/README.md +23 -0
  25. package/templates/mcp-radar/agent/catalogs/hackernews/domain.yaml +329 -0
  26. package/templates/mcp-radar/agent/catalogs/hackernews/eval/cases.yaml +112 -0
  27. package/templates/mcp-radar/agent/catalogs/hackernews/mappings.yaml +129 -0
  28. package/templates/mcp-radar/agent/catalogs/tavily/README.md +218 -0
  29. package/templates/mcp-radar/agent/catalogs/tavily/domain.yaml +373 -0
  30. package/templates/mcp-radar/agent/catalogs/tavily/eval/cases.yaml +56 -0
  31. package/templates/mcp-radar/agent/catalogs/tavily/eval/google-gemma-3-4b-it.latest.human.txt +67 -0
  32. package/templates/mcp-radar/agent/catalogs/tavily/eval/google-gemma-3-4b-it.latest.json +363 -0
  33. package/templates/mcp-radar/agent/catalogs/tavily/mappings.yaml +171 -0
  34. package/templates/mcp-radar/agent/channels/mcp-radar.ts +85 -0
  35. package/templates/mcp-radar/agent/hooks/proof-audit.ts +10 -0
  36. package/templates/mcp-radar/agent/instructions.md +24 -0
  37. package/templates/mcp-radar/agent/research/mcp-innovations-proof.md +4 -0
  38. package/templates/mcp-radar/agent/schedules/mcp-radar-scan.ts +14 -0
  39. package/templates/mcp-radar/agent/skills/mcp-proof-format.md +19 -0
  40. package/templates/mcp-radar/api/[[...path]].ts +23 -0
  41. package/templates/mcp-radar/evals/mcp-radar-discover.eval.ts +14 -0
  42. package/templates/mcp-radar/lib/proof-store.ts +265 -0
  43. package/templates/mcp-radar/lib/run-radar.ts +214 -0
  44. package/templates/mcp-radar/nitro.config.ts +17 -0
  45. package/templates/mcp-radar/package.json +14 -0
  46. package/templates/mcp-radar/public/index.html +10 -0
  47. package/templates/mcp-radar/routes/[...path].ts +29 -0
  48. package/templates/mcp-radar/scripts/run-evals.ts +46 -0
  49. package/templates/mcp-radar/scripts/smoke-channel.ts +66 -0
  50. package/templates/mcp-radar/vercel.json +15 -0
  51. package/templates/scaffold/.vercelignore +3 -0
  52. package/templates/scaffold/api/[[...path]].ts +23 -0
  53. package/templates/scaffold/nitro.config.ts +17 -0
  54. package/templates/scaffold/public/index.html +10 -0
  55. package/templates/scaffold/public/index.mcp-radar.html +10 -0
  56. package/templates/scaffold/routes/[...path].ts +29 -0
  57. package/templates/scaffold/vercel.default.json +4 -0
  58. package/templates/scaffold/vercel.mcp-radar.json +15 -0
package/README.md CHANGED
@@ -152,9 +152,16 @@ npm run smoke:stubs:matrix
152
152
  npm run agent -- "your prompt" # requires AI_GATEWAY_API_KEY (Vercel AI Gateway)
153
153
  ```
154
154
 
155
- ### Dev server (`npm run dev` / `plasm-agent dev`)
155
+ ### Dev server (`plasm-agent dev`)
156
156
 
157
- When stdin is a TTY, `plasm-agent dev` starts the server **and** opens an interactive terminal client (eve-style). Use `--no-tui` or `PLASM_DEV_NO_TUI=1` for headless mode. Attach later with `plasm-agent chat --url http://127.0.0.1:3000`.
157
+ **Default:** Nitro dev server with **Vercel routing parity** (channels, cron, `/plasm/v1/info`) same `vercelPlasmHandler` as deploy. Init scaffolds `nitro.config.ts` + `routes/[...path].ts` and adds `nitropack` as a devDependency.
158
+
159
+ ```bash
160
+ plasm-agent dev # Nitro (default)
161
+ plasm-agent dev --interactive # in-process server + TUI when TTY
162
+ plasm-agent dev --interactive --no-tui
163
+ plasm-agent chat --url http://127.0.0.1:3000
164
+ ```
158
165
 
159
166
  Slash commands in the TUI: `/help`, `/info`, `/model`, `/channels`, `/catalogs`, `/new`, `/quit`.
160
167
 
@@ -13,8 +13,7 @@ You operate external APIs through **Plasm catalogs**, not ad-hoc REST tools.
13
13
 
14
14
  - **One user goal → one stable `intent` string → one `logical_session_ref`.**
15
15
  - Do not rotate `intent` per message (e.g. `"msg 3: sort moves"` breaks symbol reuse).
16
- - Copy symbols from the **left column** of teaching TSV into programs — never invent `e#` / `p#` values.
17
- - Federated catalogs: use opaque symbols from the table; never bare homonym wire names.
16
+ - Copy symbols from the **left column** of teaching TSV into programs — never invent `e#` / `p#` values or catalog English names (`Issue`, `create`).
18
17
 
19
18
  ## Programs
20
19
 
@@ -4,14 +4,12 @@ import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
 
6
6
  const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
7
- const runner = path.join(packageRoot, "scripts", "plasm-node.mjs");
8
- const cli = path.join(packageRoot, "scripts", "plasm-cli.ts");
7
+ const runner = path.join(packageRoot, "scripts", "run-plasm-cli.mjs");
9
8
 
10
- const child = spawn(
11
- process.execPath,
12
- ["--experimental-strip-types", "--experimental-transform-types", runner, cli, ...process.argv.slice(2)],
13
- { stdio: "inherit", env: process.env },
14
- );
9
+ const child = spawn(process.execPath, [runner, ...process.argv.slice(2)], {
10
+ stdio: "inherit",
11
+ env: process.env,
12
+ });
15
13
 
16
14
  child.on("exit", (code) => {
17
15
  process.exit(code ?? 0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasm_lang/vercel-agent",
3
- "version": "0.3.77",
3
+ "version": "0.3.80",
4
4
  "description": "Catalog-native TypeScript agent framework (Plasm CGS/CML, Vercel AI SDK, Nitro-oriented)",
5
5
  "license": "GPL-3.0-or-later",
6
6
  "repository": {
@@ -15,6 +15,8 @@
15
15
  "type": "module",
16
16
  "exports": {
17
17
  ".": "./src/index.ts",
18
+ "./build": "./src/cli/build.ts",
19
+ "./server": "./src/server/plasm-handler.ts",
18
20
  "./instrumentation": "./src/instrumentation.ts",
19
21
  "./operator": "./src/operator/routes.ts",
20
22
  "./agent": "./agent/agent.ts"
@@ -25,36 +27,43 @@
25
27
  "files": [
26
28
  "src",
27
29
  "bin",
30
+ "templates",
28
31
  "scripts/plasm-node.mjs",
32
+ "scripts/plasm-cli.ts",
33
+ "scripts/run-plasm-cli.mjs",
34
+ "scripts/build-stubs.ts",
35
+ "scripts/register-plasm-loader.mjs",
29
36
  "scripts/resolve-ts-extension.mjs",
30
37
  "agent",
31
38
  "README.md"
32
39
  ],
33
40
  "scripts": {
34
41
  "typecheck": "tsc --noEmit",
35
- "plasm-agent": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/plasm-cli.ts",
42
+ "plasm-agent": "node scripts/run-plasm-cli.mjs",
36
43
  "build:engine": "npm run build --prefix ../plasm-engine",
37
- "build:stubs": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/build-stubs.ts",
38
- "smoke:tools": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/smoke-agent-tools.ts",
39
- "smoke:async-transport": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/smoke-async-transport.ts",
40
- "smoke:operator": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/smoke-operator.ts",
41
- "smoke:stubs": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/smoke-stubs.ts",
42
- "smoke:stubs:matrix": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/smoke-stubs-matrix.ts",
43
- "eval": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/run-evals.ts",
44
- "eval:catalog": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/run-catalog-eval.ts",
45
- "smoke:hot-reload": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/smoke-hot-reload.ts",
46
- "smoke:sessions": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/smoke-sessions.ts",
47
- "smoke:tui-async": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/smoke-tui-async.ts",
48
- "smoke:bootstrap": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/smoke-bootstrap-devx.ts",
49
- "smoke:vercel-handler": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/smoke-vercel-handler.ts",
50
- "agent": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/run-agent.ts",
51
- "dev": "node --experimental-strip-types --experimental-transform-types scripts/plasm-node.mjs scripts/dev-server.ts",
44
+ "build:stubs": "node scripts/run-plasm-cli.mjs scripts/build-stubs.ts",
45
+ "smoke:tools": "node scripts/run-plasm-cli.mjs scripts/smoke-agent-tools.ts",
46
+ "smoke:async-transport": "node scripts/run-plasm-cli.mjs scripts/smoke-async-transport.ts",
47
+ "smoke:operator": "node scripts/run-plasm-cli.mjs scripts/smoke-operator.ts",
48
+ "smoke:stubs": "node scripts/run-plasm-cli.mjs scripts/smoke-stubs.ts",
49
+ "smoke:stubs:matrix": "node scripts/run-plasm-cli.mjs scripts/smoke-stubs-matrix.ts",
50
+ "eval": "node scripts/run-plasm-cli.mjs scripts/run-evals.ts",
51
+ "eval:catalog": "node scripts/run-plasm-cli.mjs scripts/run-catalog-eval.ts",
52
+ "smoke:hot-reload": "node scripts/run-plasm-cli.mjs scripts/smoke-hot-reload.ts",
53
+ "smoke:sessions": "node scripts/run-plasm-cli.mjs scripts/smoke-sessions.ts",
54
+ "smoke:tui-async": "node scripts/run-plasm-cli.mjs scripts/smoke-tui-async.ts",
55
+ "smoke:bootstrap": "node scripts/run-plasm-cli.mjs scripts/smoke-bootstrap-devx.ts",
56
+ "smoke:bootstrap:npm": "node scripts/run-plasm-cli.mjs scripts/smoke-bootstrap-npm.ts",
57
+ "smoke:vercel-build": "node scripts/run-plasm-cli.mjs scripts/smoke-vercel-build.ts",
58
+ "smoke:vercel-handler": "node scripts/run-plasm-cli.mjs scripts/smoke-vercel-handler.ts",
59
+ "agent": "node scripts/run-plasm-cli.mjs scripts/run-agent.ts",
60
+ "dev": "node scripts/run-plasm-cli.mjs scripts/dev-server.ts",
52
61
  "plasm:dev": "npm run dev"
53
62
  },
54
63
  "dependencies": {
55
64
  "@ai-sdk/otel": "^1.0.0-beta.6",
56
65
  "@opentelemetry/api": "^1.9.0",
57
- "@plasm_lang/engine": "^0.3.77",
66
+ "@plasm_lang/engine": "^0.3.80",
58
67
  "@vercel/blob": "^0.27.3",
59
68
  "@vercel/connect": "^0.2.6",
60
69
  "@vercel/kv": "^3.0.0",
@@ -63,7 +72,8 @@
63
72
  "js-yaml": "^4.1.0",
64
73
  "pg": "^8.22.0",
65
74
  "workflow": "^4.5.0",
66
- "zod": "^3.24.0"
75
+ "zod": "^3.25.76",
76
+ "tsx": "^4.19.4"
67
77
  },
68
78
  "devDependencies": {
69
79
  "@types/js-yaml": "^4.0.9",
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Scan agent/catalogs/ and emit TypeScript stubs to agent/.plasm/stubs/<entry_id>.ts
4
+ */
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ import { generateAllStubs } from "../src/stubs/generator.js";
9
+
10
+ const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
11
+ const agentRoot = path.join(packageRoot, "agent");
12
+
13
+ async function main() {
14
+ const results = await generateAllStubs(agentRoot);
15
+ if (!results.length) {
16
+ console.error("no catalogs found under agent/catalogs/");
17
+ process.exit(1);
18
+ }
19
+ for (const result of results) {
20
+ console.log(`${result.entryId}\t${result.catalogCgsHash}\t${result.outPath}`);
21
+ }
22
+ }
23
+
24
+ main().catch((err) => {
25
+ console.error(err);
26
+ process.exit(1);
27
+ });
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * TypeScript CLI for @plasm_lang/vercel-agent (not the Rust plasm-server TUI).
4
+ */
5
+ import path from "node:path";
6
+
7
+ import { runPlasmBuild } from "../src/cli/build.js";
8
+ import {
9
+ installDevServerShutdown,
10
+ startDevServerForProject,
11
+ } from "../src/cli/dev.js";
12
+ import { startNitroDevForProject } from "../src/cli/nitro-dev.js";
13
+ import { runDevTui } from "../src/dev/client/repl.js";
14
+ import { collectProjectInfo, formatPlasmInfoHuman } from "../src/cli/info.js";
15
+ import { runPlasmInit, formatInitSuccess } from "../src/cli/init.js";
16
+ import { runPlasmLink } from "../src/cli/link.js";
17
+ import {
18
+ requireAgentProject,
19
+ readPackageName,
20
+ resolveAgentProject,
21
+ } from "../src/cli/project-root.js";
22
+ import { loadAgentEnv } from "../src/load-env.js";
23
+
24
+ loadAgentEnv();
25
+
26
+ const args = process.argv.slice(2);
27
+ const command = args[0];
28
+
29
+ function flag(name: string): boolean {
30
+ return args.includes(name);
31
+ }
32
+
33
+ function optionValue(flagName: string): string | undefined {
34
+ const index = args.indexOf(flagName);
35
+ if (index < 0) return undefined;
36
+ const value = args[index + 1];
37
+ if (!value || value.startsWith("-")) return undefined;
38
+ return value;
39
+ }
40
+
41
+ function initTargetDir(): string {
42
+ for (let i = 1; i < args.length; i++) {
43
+ const arg = args[i];
44
+ if (arg.startsWith("-")) {
45
+ if (arg === "--template") i++;
46
+ continue;
47
+ }
48
+ return arg;
49
+ }
50
+ return process.cwd();
51
+ }
52
+
53
+ async function cmdInit(): Promise<void> {
54
+ const template = optionValue("--template");
55
+ const npm = flag("--npm");
56
+ const target = initTargetDir();
57
+ const project = await runPlasmInit(target, { template, npm: npm || undefined });
58
+ console.log(formatInitSuccess(project, { template, npm: npm || undefined }));
59
+ }
60
+
61
+ async function cmdInfo(): Promise<void> {
62
+ const project = await requireAgentProject();
63
+ const packageName = await readPackageName(project.projectRoot);
64
+ const info = await collectProjectInfo({
65
+ projectRoot: project.projectRoot,
66
+ agentRoot: project.agentRoot,
67
+ packageName,
68
+ });
69
+ if (flag("--json")) {
70
+ console.log(JSON.stringify(info, null, 2));
71
+ return;
72
+ }
73
+ console.log(formatPlasmInfoHuman(info));
74
+ }
75
+
76
+ async function cmdLink(): Promise<void> {
77
+ const project = await requireAgentProject();
78
+ const result = await runPlasmLink(project.projectRoot);
79
+ for (const line of result.messages) console.log(line);
80
+ if (!result.linked && !result.envPulled) process.exitCode = 1;
81
+ }
82
+
83
+ async function cmdBuild(): Promise<void> {
84
+ const project = await requireAgentProject();
85
+ const result = await runPlasmBuild(project);
86
+ console.log(`Built ${result.stubs.length} stub(s)`);
87
+ console.log(`manifest: ${result.manifestPath}`);
88
+ for (const stub of result.stubs) {
89
+ console.log(` - ${stub.entryId} → ${path.relative(project.projectRoot, stub.outPath)}`);
90
+ }
91
+ }
92
+
93
+ async function cmdDev(): Promise<void> {
94
+ const project = await requireAgentProject();
95
+ if (flag("--interactive")) {
96
+ const handle = await startDevServerForProject({
97
+ project,
98
+ tui: flag("--no-tui") ? false : "auto",
99
+ });
100
+ installDevServerShutdown(handle);
101
+ return;
102
+ }
103
+ await startNitroDevForProject(project);
104
+ }
105
+
106
+ async function cmdChat(): Promise<void> {
107
+ const urlFlag = args.find((a, i) => args[i - 1] === "--url");
108
+ const baseUrl = urlFlag ?? process.env.PLASM_DEV_URL ?? "http://127.0.0.1:3000";
109
+ await runDevTui({ baseUrl });
110
+ }
111
+
112
+ async function main(): Promise<void> {
113
+ if (!command || command === "--help" || command === "-h") {
114
+ console.log(`plasm-agent — @plasm_lang/vercel-agent CLI
115
+
116
+ Commands:
117
+ init [dir] [--template NAME] [--npm] Scaffold agent project (templates: mcp-radar)
118
+ info [--json] Project + catalog diagnostics
119
+ link vercel link + env pull (AI Gateway key)
120
+ build CGS stubs + .plasm/discovery/manifest.json
121
+ dev [--interactive] [--no-tui] Nitro dev (Vercel routing parity; default). --interactive for TUI/sessions.
122
+ chat [--url URL] Terminal client for a running dev server
123
+ `);
124
+ return;
125
+ }
126
+
127
+ switch (command) {
128
+ case "init":
129
+ await cmdInit();
130
+ break;
131
+ case "info": {
132
+ const resolved = await resolveAgentProject();
133
+ if (!resolved) {
134
+ console.error("No agent project found. Run `plasm-agent init` first.");
135
+ process.exitCode = 1;
136
+ return;
137
+ }
138
+ await cmdInfo();
139
+ break;
140
+ }
141
+ case "link":
142
+ await cmdLink();
143
+ break;
144
+ case "build":
145
+ await cmdBuild();
146
+ break;
147
+ case "dev":
148
+ await cmdDev();
149
+ break;
150
+ case "chat":
151
+ await cmdChat();
152
+ break;
153
+ default:
154
+ console.error(`Unknown command: ${command}`);
155
+ process.exitCode = 1;
156
+ }
157
+ }
158
+
159
+ await main().catch((err) => {
160
+ console.error(err);
161
+ process.exit(1);
162
+ });
@@ -0,0 +1,6 @@
1
+ import { register } from "node:module";
2
+ import path from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+
5
+ const root = path.dirname(fileURLToPath(import.meta.url));
6
+ register(pathToFileURL(path.join(root, "resolve-ts-extension.mjs")));
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Run TypeScript via tsx (works on Node 20–26 and Vercel build).
4
+ * With no .ts first arg, runs plasm-cli.ts.
5
+ */
6
+ import { spawnSync } from "node:child_process";
7
+ import { createRequire } from "node:module";
8
+ import path from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const require = createRequire(import.meta.url);
12
+ const packageRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
13
+ const args = process.argv.slice(2);
14
+
15
+ const scriptArg = args[0];
16
+ const isScriptEntry = scriptArg?.endsWith(".ts");
17
+ const entry = isScriptEntry
18
+ ? path.resolve(packageRoot, scriptArg)
19
+ : path.join(packageRoot, "scripts", "plasm-cli.ts");
20
+ const entryArgs = isScriptEntry ? args.slice(1) : args;
21
+
22
+ function runNode(nodeArgs) {
23
+ const result = spawnSync(process.execPath, [...nodeArgs, entry, ...entryArgs], {
24
+ stdio: "inherit",
25
+ env: process.env,
26
+ });
27
+ process.exit(result.status ?? 1);
28
+ }
29
+
30
+ try {
31
+ const tsxCli = require.resolve("tsx/cli");
32
+ runNode([tsxCli]);
33
+ } catch {
34
+ const plasmNode = path.join(packageRoot, "scripts", "plasm-node.mjs");
35
+ const result = spawnSync(
36
+ process.execPath,
37
+ ["--experimental-strip-types", "--experimental-transform-types", plasmNode, entry, ...entryArgs],
38
+ { stdio: "inherit", env: process.env },
39
+ );
40
+ process.exit(result.status ?? 1);
41
+ }
@@ -0,0 +1,198 @@
1
+ import { access, cp, mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ import { frameworkPackageVersion } from "../package-version.js";
6
+ import type { ResolvedAgentProject } from "./project-root.js";
7
+
8
+ export interface InitOptions {
9
+ template?: string;
10
+ /** Use npm semver deps instead of monorepo file: links (default: auto-detect). */
11
+ npm?: boolean;
12
+ }
13
+
14
+ const SKIP_TEMPLATE_DIRS = new Set(["node_modules", ".plasm", ".nitro", ".output", "server"]);
15
+ const SKIP_TEMPLATE_FILES = new Set(["package-lock.json", "vercel-build.ts"]);
16
+
17
+ function plasmAgentPackageRoot(): string {
18
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
19
+ }
20
+
21
+ function scaffoldRoot(): string {
22
+ return path.join(plasmAgentPackageRoot(), "templates", "scaffold");
23
+ }
24
+
25
+ function resolveTemplateDir(template: string): string {
26
+ const dir = path.join(plasmAgentPackageRoot(), "templates", template);
27
+ return dir;
28
+ }
29
+
30
+ function shouldCopyTemplateEntry(src: string, templateRoot: string): boolean {
31
+ const rel = path.relative(templateRoot, src);
32
+ if (!rel || rel === "") return true;
33
+ const parts = rel.split(path.sep);
34
+ if (parts.some((part) => SKIP_TEMPLATE_DIRS.has(part))) return false;
35
+ if (SKIP_TEMPLATE_FILES.has(path.basename(src))) return false;
36
+ return true;
37
+ }
38
+
39
+ async function exists(p: string): Promise<boolean> {
40
+ try {
41
+ await access(p);
42
+ return true;
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ async function copyTemplate(templateRoot: string, projectRoot: string): Promise<void> {
49
+ await cp(templateRoot, projectRoot, {
50
+ recursive: true,
51
+ filter: (src) => src === templateRoot || shouldCopyTemplateEntry(src, templateRoot),
52
+ });
53
+ }
54
+
55
+ async function copyScaffoldFile(relativePath: string, projectRoot: string): Promise<void> {
56
+ const src = path.join(scaffoldRoot(), relativePath);
57
+ const dest = path.join(projectRoot, relativePath);
58
+ await mkdir(path.dirname(dest), { recursive: true });
59
+ await cp(src, dest);
60
+ }
61
+
62
+ export async function writeDeployScaffold(
63
+ projectRoot: string,
64
+ template?: string,
65
+ ): Promise<void> {
66
+ const vercelJsonName =
67
+ template === "mcp-radar" ? "vercel.mcp-radar.json" : "vercel.default.json";
68
+ await copyScaffoldFile("api/[[...path]].ts", projectRoot);
69
+ await copyScaffoldFile("routes/[...path].ts", projectRoot);
70
+ await copyScaffoldFile("nitro.config.ts", projectRoot);
71
+ await copyScaffoldFile(".vercelignore", projectRoot);
72
+ const publicIndex =
73
+ template === "mcp-radar" ? "public/index.mcp-radar.html" : "public/index.html";
74
+ const publicDest = path.join(projectRoot, "public", "index.html");
75
+ await mkdir(path.dirname(publicDest), { recursive: true });
76
+ await cp(path.join(scaffoldRoot(), publicIndex), publicDest);
77
+ await cp(
78
+ path.join(scaffoldRoot(), vercelJsonName),
79
+ path.join(projectRoot, "vercel.json"),
80
+ );
81
+ }
82
+
83
+ function monorepoEngineRoot(packageRoot: string): string {
84
+ return path.resolve(packageRoot, "../plasm-engine");
85
+ }
86
+
87
+ export async function isMonorepoDevPackage(packageRoot: string): Promise<boolean> {
88
+ return exists(path.join(monorepoEngineRoot(packageRoot), "package.json"));
89
+ }
90
+
91
+ export async function patchProjectPackageJson(
92
+ projectRoot: string,
93
+ packageRoot: string,
94
+ options?: { npm?: boolean },
95
+ ): Promise<void> {
96
+ const pkgPath = path.join(projectRoot, "package.json");
97
+ const raw = await readFile(pkgPath, "utf8");
98
+ const pkg = JSON.parse(raw) as {
99
+ name?: string;
100
+ scripts?: Record<string, string>;
101
+ dependencies?: Record<string, string>;
102
+ devDependencies?: Record<string, string>;
103
+ };
104
+
105
+ const version = frameworkPackageVersion();
106
+ const useNpm =
107
+ options?.npm === true
108
+ ? true
109
+ : options?.npm === false
110
+ ? false
111
+ : !(await isMonorepoDevPackage(packageRoot));
112
+
113
+ pkg.name = path.basename(projectRoot);
114
+ if (useNpm) {
115
+ pkg.dependencies = {
116
+ ...pkg.dependencies,
117
+ "@plasm_lang/vercel-agent": `^${version}`,
118
+ "@plasm_lang/engine": `^${version}`,
119
+ "@vercel/blob": "^0.27.3",
120
+ "@vercel/functions": "^3.4.3",
121
+ "@vercel/kv": "^3.0.0",
122
+ };
123
+ } else {
124
+ pkg.dependencies = {
125
+ ...pkg.dependencies,
126
+ "@plasm_lang/vercel-agent": `file:${path.resolve(packageRoot)}`,
127
+ "@plasm_lang/engine": `file:${monorepoEngineRoot(packageRoot)}`,
128
+ "@vercel/blob": "^0.27.3",
129
+ "@vercel/functions": "^3.4.3",
130
+ "@vercel/kv": "^3.0.0",
131
+ };
132
+ }
133
+
134
+ pkg.scripts = {
135
+ build: "plasm-agent build",
136
+ dev: "plasm-agent dev",
137
+ "dev:interactive": "plasm-agent dev --interactive",
138
+ info: "plasm-agent info",
139
+ deploy: "vercel deploy",
140
+ ...pkg.scripts,
141
+ };
142
+ delete pkg.scripts["vercel-build"];
143
+
144
+ pkg.devDependencies = {
145
+ ...pkg.devDependencies,
146
+ nitropack: "^2.13.4",
147
+ tsx: "^4.19.4",
148
+ };
149
+
150
+ await writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf8");
151
+ }
152
+
153
+ export function blankPackageJsonScaffold(): Record<string, unknown> {
154
+ const version = frameworkPackageVersion();
155
+ return {
156
+ name: "my-plasm-agent",
157
+ private: true,
158
+ type: "module",
159
+ scripts: {
160
+ dev: "plasm-agent dev",
161
+ "dev:interactive": "plasm-agent dev --interactive",
162
+ build: "plasm-agent build",
163
+ info: "plasm-agent info",
164
+ deploy: "vercel deploy",
165
+ },
166
+ dependencies: {
167
+ "@plasm_lang/vercel-agent": `^${version}`,
168
+ "@plasm_lang/engine": `^${version}`,
169
+ },
170
+ devDependencies: {
171
+ nitropack: "^2.13.4",
172
+ tsx: "^4.19.4",
173
+ },
174
+ };
175
+ }
176
+
177
+ export async function runTemplateInit(
178
+ targetDir: string,
179
+ template: string,
180
+ options?: InitOptions,
181
+ ): Promise<ResolvedAgentProject> {
182
+ const projectRoot = path.resolve(targetDir);
183
+ const agentRoot = path.join(projectRoot, "agent");
184
+ if (await exists(path.join(agentRoot, "agent.ts"))) {
185
+ throw new Error(`agent/agent.ts already exists in ${projectRoot}`);
186
+ }
187
+ const templateRoot = resolveTemplateDir(template);
188
+ if (!(await exists(templateRoot))) {
189
+ throw new Error(`Unknown template "${template}" or missing source: ${templateRoot}`);
190
+ }
191
+ await mkdir(projectRoot, { recursive: true });
192
+ await copyTemplate(templateRoot, projectRoot);
193
+ await patchProjectPackageJson(projectRoot, plasmAgentPackageRoot(), { npm: options?.npm });
194
+ await writeDeployScaffold(projectRoot, template);
195
+ return { projectRoot, agentRoot };
196
+ }
197
+
198
+ export { exists, plasmAgentPackageRoot, resolveTemplateDir };