behaviortrees 0.0.0-stage → 0.1.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.
@@ -0,0 +1,8 @@
1
+ import {
2
+ createMcpServer
3
+ } from "./chunk-7LANS4ZA.js";
4
+ import "./chunk-QW4NPMSB.js";
5
+ import "./chunk-VTXL3OVS.js";
6
+ export {
7
+ createMcpServer
8
+ };
package/dist/cli.js ADDED
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ VERSION
4
+ } from "./chunks/chunk-QW4NPMSB.js";
5
+ import {
6
+ CONVERT_TARGETS,
7
+ InvalidTreeError,
8
+ convertTree,
9
+ createEditorLink,
10
+ getExample,
11
+ listExamples,
12
+ listGuides,
13
+ listNodeTypes,
14
+ readGuide,
15
+ validateTree
16
+ } from "./chunks/chunk-VTXL3OVS.js";
17
+
18
+ // src/cli.ts
19
+ import { spawn } from "node:child_process";
20
+ import { readFile, writeFile } from "node:fs/promises";
21
+ import { parseArgs } from "node:util";
22
+ var HELP = `behaviortrees ${VERSION}: validate, convert and share behavior trees
23
+
24
+ Usage: behaviortrees <command> [file] [options]
25
+
26
+ Commands:
27
+ validate [file] Check a tree for errors (exits 1 if there are any)
28
+ convert [file] --to <f> Convert to behavior3, btcpp-v4 or btcpp-v3
29
+ link [file] Print a link that opens the tree in the editor
30
+ nodes List the built-in behavior3 node types
31
+ examples [id] List example trees, or print one as JSON
32
+ guides [slug] List the behaviortrees.com guides, or print one
33
+ mcp Run the MCP server over stdio
34
+
35
+ Files can be behavior3 JSON (a tree or a project) or BehaviorTree.CPP XML.
36
+ Leave out the file, or pass -, to read from stdin.
37
+
38
+ Options:
39
+ --to <format> convert: behavior3, btcpp-v4 or btcpp-v3
40
+ -o, --out <path> convert: write to a file instead of stdout
41
+ --open link: also open it in your browser
42
+ --json print machine-readable JSON
43
+ -h, --help show this help
44
+ -v, --version show the version
45
+
46
+ Docs: https://www.behaviortrees.com/llms.txt`;
47
+ var UsageError = class extends Error {
48
+ };
49
+ var readInput = async (file) => {
50
+ if (file && file !== "-") return readFile(file, "utf8");
51
+ if (process.stdin.isTTY) throw new UsageError("Give a file, or pipe a tree into stdin");
52
+ const chunks = [];
53
+ for await (const chunk of process.stdin) chunks.push(chunk);
54
+ return Buffer.concat(chunks).toString("utf8");
55
+ };
56
+ var print = (value) => process.stdout.write(`${typeof value === "string" ? value : JSON.stringify(value, null, 2)}
57
+ `);
58
+ var formatFinding = (finding) => `${finding.level.padEnd(7)} ${finding.tree ? `${finding.tree}: ` : ""}${finding.message}`;
59
+ var openInBrowser = (url) => {
60
+ const [command, args] = process.platform === "darwin" ? ["open", [url]] : process.platform === "win32" ? ["cmd", ["/c", "start", '""', url.replace(/&/g, "^&")]] : ["xdg-open", [url]];
61
+ spawn(command, args, { detached: true, stdio: "ignore" }).unref();
62
+ };
63
+ async function main(argv) {
64
+ const { values, positionals } = parseArgs({
65
+ args: argv,
66
+ allowPositionals: true,
67
+ options: {
68
+ to: { type: "string" },
69
+ out: { type: "string", short: "o" },
70
+ open: { type: "boolean" },
71
+ json: { type: "boolean" },
72
+ help: { type: "boolean", short: "h" },
73
+ version: { type: "boolean", short: "v" }
74
+ }
75
+ });
76
+ const [command, arg] = positionals;
77
+ if (values.version) return print(VERSION), 0;
78
+ if (values.help || !command || command === "help") return print(HELP), command || values.help ? 0 : 1;
79
+ switch (command) {
80
+ case "validate": {
81
+ const result = await validateTree(await readInput(arg));
82
+ if (values.json) {
83
+ print(result);
84
+ } else {
85
+ for (const tree of result.trees) {
86
+ print(`${tree.title} (${tree.nodes} node${tree.nodes === 1 ? "" : "s"})`);
87
+ }
88
+ for (const finding of result.findings) print(formatFinding(finding));
89
+ const errors = result.findings.filter((finding) => finding.level === "error").length;
90
+ print(result.valid ? "Valid." : `${errors} error${errors === 1 ? "" : "s"}.`);
91
+ }
92
+ return result.valid ? 0 : 1;
93
+ }
94
+ case "convert": {
95
+ if (!values.to || !CONVERT_TARGETS.includes(values.to)) {
96
+ throw new UsageError(`convert needs --to ${CONVERT_TARGETS.join(" | ")}`);
97
+ }
98
+ const result = await convertTree(await readInput(arg), values.to);
99
+ if (values.out) await writeFile(values.out, result.output);
100
+ else process.stdout.write(result.output);
101
+ for (const issue of result.issues) process.stderr.write(`${formatFinding(issue)}
102
+ `);
103
+ return 0;
104
+ }
105
+ case "link": {
106
+ try {
107
+ const link = await createEditorLink(await readInput(arg));
108
+ if (values.json) print(link);
109
+ else print(link.url);
110
+ for (const finding of link.findings.filter((f) => f.level !== "info")) {
111
+ process.stderr.write(`${formatFinding(finding)}
112
+ `);
113
+ }
114
+ if (values.open) openInBrowser(link.url);
115
+ return 0;
116
+ } catch (error) {
117
+ if (!(error instanceof InvalidTreeError)) throw error;
118
+ process.stderr.write(`${error.message}
119
+ `);
120
+ return 1;
121
+ }
122
+ }
123
+ case "nodes": {
124
+ const nodes = listNodeTypes();
125
+ if (values.json) return print(nodes), 0;
126
+ for (const node of nodes) print(`${node.name.padEnd(20)} ${node.category.padEnd(10)} ${node.description ?? ""}`);
127
+ return 0;
128
+ }
129
+ case "examples": {
130
+ if (arg) {
131
+ const example = getExample(arg);
132
+ if (!example) throw new UsageError(`No example "${arg}". Run "behaviortrees examples" to list them.`);
133
+ return print(example), 0;
134
+ }
135
+ const examples = listExamples();
136
+ if (values.json) return print(examples), 0;
137
+ for (const example of examples) print(`${example.id.padEnd(26)} ${example.title}`);
138
+ return 0;
139
+ }
140
+ case "guides": {
141
+ if (arg) return print(await readGuide(arg)), 0;
142
+ const guides = await listGuides();
143
+ if (values.json) return print(guides), 0;
144
+ for (const guide of guides) print(`${guide.slug.padEnd(36)} ${guide.title}`);
145
+ return 0;
146
+ }
147
+ case "mcp": {
148
+ const [{ StdioServerTransport }, { createMcpServer }] = await Promise.all([
149
+ import("@modelcontextprotocol/sdk/server/stdio.js"),
150
+ import("./chunks/mcp-CHN7W7L5.js")
151
+ ]);
152
+ await createMcpServer().connect(new StdioServerTransport());
153
+ return -1;
154
+ }
155
+ default:
156
+ throw new UsageError(`Unknown command "${command}". Run "behaviortrees --help".`);
157
+ }
158
+ }
159
+ main(process.argv.slice(2)).then(
160
+ (code) => {
161
+ if (code >= 0) process.exitCode = code;
162
+ },
163
+ (error) => {
164
+ process.stderr.write(`behaviortrees: ${error instanceof Error ? error.message : String(error)}
165
+ `);
166
+ if (error instanceof UsageError) process.stderr.write('Run "behaviortrees --help" for usage.\n');
167
+ process.exitCode = error instanceof UsageError ? 2 : 1;
168
+ }
169
+ );
@@ -0,0 +1,38 @@
1
+ export type TreeInput = string | Record<string, unknown> | unknown[];
2
+
3
+ export type Finding = { level: 'error' | 'warning' | 'info'; message: string; tree?: string };
4
+
5
+ export type ValidationResult = {
6
+ valid: boolean;
7
+ format?: 'behavior3' | 'btcpp';
8
+ kind?: 'tree' | 'project';
9
+ trees: { title: string; nodes: number }[];
10
+ findings: Finding[];
11
+ };
12
+ export declare function validateTree(input: TreeInput): Promise<ValidationResult>;
13
+
14
+ export type ConvertTarget = 'behavior3' | 'btcpp-v4' | 'btcpp-v3';
15
+ export declare const CONVERT_TARGETS: ConvertTarget[];
16
+ export type ConvertResult = { output: string; format: ConvertTarget; issues: Finding[] };
17
+ export declare function convertTree(input: TreeInput, to: ConvertTarget): Promise<ConvertResult>;
18
+
19
+ export type EditorLink = { url: string; title: string; findings: Finding[] };
20
+ export declare class InvalidTreeError extends Error {
21
+ readonly findings: Finding[];
22
+ }
23
+ export declare function createEditorLink(input: TreeInput): Promise<EditorLink>;
24
+
25
+ export type NodeType = {
26
+ name: string;
27
+ category: string;
28
+ title?: string;
29
+ description?: string;
30
+ properties: Record<string, unknown>;
31
+ };
32
+ export declare function listNodeTypes(): NodeType[];
33
+ export declare function listExamples(): { id: string; title: string; description: string; editorUrl: string }[];
34
+ export declare function getExample(id: string): Record<string, unknown> | undefined;
35
+
36
+ export type Guide = { slug: string; title: string; description: string; url: string; markdownUrl: string };
37
+ export declare function listGuides(): Promise<Guide[]>;
38
+ export declare function readGuide(slug: string): Promise<string>;
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ import {
2
+ CONVERT_TARGETS,
3
+ InvalidTreeError,
4
+ convertTree,
5
+ createEditorLink,
6
+ getExample,
7
+ listExamples,
8
+ listGuides,
9
+ listNodeTypes,
10
+ readGuide,
11
+ validateTree
12
+ } from "./chunks/chunk-VTXL3OVS.js";
13
+ export {
14
+ CONVERT_TARGETS,
15
+ InvalidTreeError,
16
+ convertTree,
17
+ createEditorLink,
18
+ getExample,
19
+ listExamples,
20
+ listGuides,
21
+ listNodeTypes,
22
+ readGuide,
23
+ validateTree
24
+ };
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+
4
+ export declare function createMcpServer(): McpServer;
5
+ // Stateless Streamable HTTP handler; pass the parsed JSON body if the host
6
+ // already read it (Vercel does)
7
+ export declare function handleMcpRequest(req: IncomingMessage & { body?: unknown }, res: ServerResponse): Promise<void>;
package/dist/mcp.js ADDED
@@ -0,0 +1,43 @@
1
+ import {
2
+ createMcpServer
3
+ } from "./chunks/chunk-7LANS4ZA.js";
4
+ import "./chunks/chunk-QW4NPMSB.js";
5
+ import "./chunks/chunk-VTXL3OVS.js";
6
+
7
+ // src/http.ts
8
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
9
+ var CORS = {
10
+ "Access-Control-Allow-Origin": "*",
11
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
12
+ "Access-Control-Allow-Headers": "Content-Type, Accept, Authorization, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID",
13
+ "Access-Control-Expose-Headers": "Mcp-Session-Id, Mcp-Protocol-Version"
14
+ };
15
+ async function handleMcpRequest(req, res) {
16
+ for (const [key, value] of Object.entries(CORS)) res.setHeader(key, value);
17
+ if (req.method === "OPTIONS") {
18
+ res.writeHead(204).end();
19
+ return;
20
+ }
21
+ if (req.method !== "POST") {
22
+ res.writeHead(405, { "Content-Type": "application/json", Allow: "POST, OPTIONS" }).end(
23
+ JSON.stringify({
24
+ jsonrpc: "2.0",
25
+ error: { code: -32e3, message: "This MCP server is stateless: send JSON-RPC requests with POST." },
26
+ id: null
27
+ })
28
+ );
29
+ return;
30
+ }
31
+ const server = createMcpServer();
32
+ const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0, enableJsonResponse: true });
33
+ res.on("close", () => {
34
+ void transport.close();
35
+ void server.close();
36
+ });
37
+ await server.connect(transport);
38
+ await transport.handleRequest(req, res, req.body);
39
+ }
40
+ export {
41
+ createMcpServer,
42
+ handleMcpRequest
43
+ };
package/package.json CHANGED
@@ -1,6 +1,66 @@
1
1
  {
2
2
  "name": "behaviortrees",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Validate, convert and share behavior trees (behavior3 JSON, BehaviorTree.CPP XML) from the command line or any MCP client.",
5
+ "license": "AGPL-3.0-only",
6
+ "homepage": "https://www.behaviortrees.com",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/behaviortrees/behaviortrees.git",
10
+ "directory": "packages/cli"
11
+ },
12
+ "bugs": "https://github.com/behaviortrees/behaviortrees/issues",
13
+ "keywords": [
14
+ "behavior-tree",
15
+ "behavior3",
16
+ "behaviortree-cpp",
17
+ "groot",
18
+ "ros2",
19
+ "nav2",
20
+ "game-ai",
21
+ "mcp",
22
+ "cli"
23
+ ],
24
+ "mcpName": "io.github.behaviortrees/behaviortrees",
25
+ "type": "module",
26
+ "bin": {
27
+ "behaviortrees": "dist/cli.js"
28
+ },
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "default": "./dist/index.js"
33
+ },
34
+ "./mcp": {
35
+ "types": "./dist/mcp.d.ts",
36
+ "default": "./dist/mcp.js"
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "README.md",
42
+ "LICENSE",
43
+ "LICENSE-MIT"
44
+ ],
45
+ "engines": {
46
+ "node": ">=20"
47
+ },
48
+ "dependencies": {
49
+ "@modelcontextprotocol/sdk": "^1.32.2",
50
+ "@xmldom/xmldom": "^0.9.12",
51
+ "zod": "^3.25.76"
52
+ },
53
+ "devDependencies": {
54
+ "@types/node": "^26.6.5",
55
+ "esbuild": "^0.28.1",
56
+ "typescript": "~5.7.3",
57
+ "uuid": "^11.1.1",
58
+ "vitest": "^4.1.12",
59
+ "@behaviortrees/examples": "0.0.0"
60
+ },
61
+ "scripts": {
62
+ "build": "node build.mjs",
63
+ "test": "vitest run",
64
+ "lint": "tsc --noEmit -p tsconfig.json"
65
+ }
6
66
  }