@aixyz/cli 0.19.0 → 0.21.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.
@@ -7,9 +7,7 @@ function label(text: string): string {
7
7
  return chalk.dim(text.padEnd(14));
8
8
  }
9
9
 
10
- export function AixyzConfigPlugin(): BunPlugin {
11
- const materialized = getAixyzConfig();
12
-
10
+ function logConfig(materialized: ReturnType<typeof getAixyzConfig>): void {
13
11
  const maxLen = Math.max(materialized.url.length, materialized.x402.payTo.length);
14
12
  const description =
15
13
  materialized.description.length > maxLen
@@ -33,6 +31,11 @@ export function AixyzConfigPlugin(): BunPlugin {
33
31
  titleAlignment: "left",
34
32
  }),
35
33
  );
34
+ }
35
+
36
+ export function AixyzConfigPlugin(): BunPlugin {
37
+ const materialized = getAixyzConfig();
38
+ logConfig(materialized);
36
39
 
37
40
  return {
38
41
  name: "aixyz-config",
@@ -1,9 +1,13 @@
1
1
  import type { BunPlugin } from "bun";
2
- import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
2
+ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "fs";
3
3
  import { resolve, relative, join } from "path";
4
4
  import { getAixyzConfig } from "@aixyz/config";
5
5
 
6
- export function AixyzServerPlugin(entrypoint: string, mode: "vercel" | "standalone" | "executable"): BunPlugin {
6
+ export function AixyzServerPlugin(
7
+ entrypoint: string,
8
+ mode: "vercel" | "standalone" | "executable",
9
+ isCustom = false,
10
+ ): BunPlugin {
7
11
  return {
8
12
  name: "aixyz-entrypoint",
9
13
  setup(build) {
@@ -12,44 +16,58 @@ export function AixyzServerPlugin(entrypoint: string, mode: "vercel" | "standalo
12
16
 
13
17
  const source = await Bun.file(args.path).text();
14
18
 
15
- if (mode === "vercel") {
16
- // For Vercel, export server.express for serverless function
17
- const transformed = source.replace(/export\s+default\s+(\w+)\s*;/, "export default $1.express;");
18
- return { contents: transformed, loader: "ts" };
19
+ // Custom server.ts manages its own lifecycle — pass through as-is
20
+ if (isCustom || mode === "vercel") {
21
+ return { contents: source, loader: "ts" };
22
+ }
23
+
24
+ // For generated entrypoints in standalone/executable, rewrite `export default ...` into Bun.serve().
25
+ // Supports both identifier exports (`export default app;`) and
26
+ // expression exports (`export default new AixyzApp({...});`).
27
+ const identifierRe = /export\s+default\s+(\w+)\s*;/;
28
+ const expressionRe = /export\s+default\s+/;
29
+
30
+ let transformed: string;
31
+ const identifierMatch = source.match(identifierRe);
32
+ if (identifierMatch) {
33
+ transformed = source.replace(
34
+ identifierRe,
35
+ `const __server = Bun.serve({ port: parseInt(process.env.PORT || "3000", 10), fetch: ${identifierMatch[1]}.fetch });\nconsole.log(\`Server listening on port \${__server.port}\`);`,
36
+ );
37
+ } else if (expressionRe.test(source)) {
38
+ transformed = source.replace(expressionRe, `const __app = `);
39
+ transformed += `\nconst __server = Bun.serve({ port: parseInt(process.env.PORT || "3000", 10), fetch: __app.fetch });\nconsole.log(\`Server listening on port \${__server.port}\`);`;
19
40
  } else {
20
- // For standalone and executable, keep the server export but add startup code
21
- // TODO(@fuxingloh): use Bun.serve later.
22
- const transformed = source.replace(
23
- /export\s+default\s+(\w+)\s*;/,
24
- `export default $1;
25
-
26
- // Auto-start server when run directly
27
- if (import.meta.main) {
28
- const port = parseInt(process.env.PORT || "3000", 10);
29
- $1.express.listen(port, () => {
30
- console.log(\`Server listening on port \${port}\`);
31
- });
32
- }`,
41
+ throw new Error(
42
+ `[aixyz] Could not find \`export default\` in entrypoint ${args.path}. ` +
43
+ `Standalone and executable builds require the server entrypoint to use \`export default app;\` ` +
44
+ `or \`export default new AixyzApp({...});\`.`,
33
45
  );
34
- return { contents: transformed, loader: "ts" };
35
46
  }
47
+ return { contents: transformed, loader: "ts" };
36
48
  });
37
49
  },
38
50
  };
39
51
  }
40
52
 
41
- export function getEntrypointMayGenerate(cwd: string, appDirName: string, mode: "dev" | "build"): string {
53
+ export type Entrypoint = { path: string; isCustom: boolean };
54
+
55
+ export function getEntrypointMayGenerate(cwd: string, appDirName: string, mode: "dev" | "build"): Entrypoint {
42
56
  const appDir = resolve(cwd, appDirName);
57
+ const serverFile = resolve(appDir, "server.ts");
43
58
 
44
- if (existsSync(resolve(appDir, "server.ts"))) {
45
- return resolve(appDir, "server.ts");
59
+ if (existsSync(serverFile)) {
60
+ const source = readFileSync(serverFile, "utf-8");
61
+ // assume that export default has `.fetch` typically `app`
62
+ const hasExportDefault = /export\s+default\s+/.test(source);
63
+ return { path: serverFile, isCustom: !hasExportDefault };
46
64
  }
47
65
 
48
66
  const devDir = resolve(cwd, join(".aixyz", mode));
49
67
  mkdirSync(devDir, { recursive: true });
50
68
  const entrypoint = resolve(devDir, "server.ts");
51
69
  writeFileSync(entrypoint, generateServer(appDir, devDir));
52
- return entrypoint;
70
+ return { path: entrypoint, isCustom: false };
53
71
  }
54
72
 
55
73
  class AixyzGlob {
@@ -109,7 +127,8 @@ function generateServer(appDir: string, entrypointDir: string): string {
109
127
  const imports: string[] = [];
110
128
  const body: string[] = [];
111
129
 
112
- imports.push('import { AixyzServer } from "aixyz/server";');
130
+ imports.push('import { AixyzApp } from "aixyz/app";');
131
+ imports.push('import { IndexPagePlugin } from "aixyz/app/plugins/index-page";');
113
132
 
114
133
  const hasAccepts = existsSync(resolve(appDir, "accepts.ts"));
115
134
  if (hasAccepts) {
@@ -119,16 +138,15 @@ function generateServer(appDir: string, entrypointDir: string): string {
119
138
  }
120
139
 
121
140
  const rootAgent = glob.hasRootAgent(appDir);
122
- if (rootAgent) {
123
- imports.push('import { useA2A } from "aixyz/server/adapters/a2a";');
124
- imports.push(`import * as agent from "${importPrefix}/agent";`);
125
- }
126
-
127
141
  const agentsDir = resolve(appDir, "agents");
128
142
  const subAgents = glob.getAgents(agentsDir);
143
+ const needsA2A = rootAgent || subAgents.length > 0;
129
144
 
130
- if (!rootAgent && subAgents.length > 0) {
131
- imports.push('import { useA2A } from "aixyz/server/adapters/a2a";');
145
+ if (needsA2A) {
146
+ imports.push('import { A2APlugin } from "aixyz/app/plugins/a2a";');
147
+ }
148
+ if (rootAgent) {
149
+ imports.push(`import * as agent from "${importPrefix}/agent";`);
132
150
  }
133
151
  for (const subAgent of subAgents) {
134
152
  imports.push(`import * as ${subAgent.identifier} from "${importPrefix}/agents/${subAgent.name}";`);
@@ -138,45 +156,49 @@ function generateServer(appDir: string, entrypointDir: string): string {
138
156
  const tools = glob.getTools(toolsDir);
139
157
 
140
158
  if (tools.length > 0) {
141
- imports.push('import { AixyzMCP } from "aixyz/server/adapters/mcp";');
159
+ imports.push('import { MCPPlugin } from "aixyz/app/plugins/mcp";');
142
160
  for (const tool of tools) {
143
161
  imports.push(`import * as ${tool.identifier} from "${importPrefix}/tools/${tool.name}";`);
144
162
  }
145
163
  }
146
164
 
147
- body.push("const server = new AixyzServer(facilitator);");
148
- body.push("await server.initialize();");
149
- body.push("server.unstable_withIndexPage();");
165
+ // If app/erc-8004.ts exists, auto-register ERC-8004 endpoint
166
+ const hasErc8004 = existsSync(resolve(appDir, "erc-8004.ts"));
167
+ if (hasErc8004) {
168
+ imports.push('import { ERC8004Plugin } from "aixyz/app/plugins/erc-8004";');
169
+ imports.push(`import * as erc8004 from "${importPrefix}/erc-8004";`);
170
+ }
171
+
172
+ body.push("const app = new AixyzApp({ facilitators: facilitator });");
173
+ body.push("await app.withPlugin(new IndexPagePlugin());");
150
174
 
151
175
  if (rootAgent) {
152
- body.push("useA2A(server, agent);");
176
+ body.push("await app.withPlugin(new A2APlugin(agent));");
153
177
  }
154
-
155
178
  for (const subAgent of subAgents) {
156
- body.push(`useA2A(server, ${subAgent.identifier}, "${subAgent.name}");`);
179
+ body.push(`await app.withPlugin(new A2APlugin(${subAgent.identifier}, "${subAgent.name}"));`);
157
180
  }
181
+
158
182
  if (tools.length > 0) {
159
- body.push("const mcp = new AixyzMCP(server);");
160
- for (const tool of tools) {
161
- body.push(`await mcp.register("${tool.name}", ${tool.identifier});`);
183
+ const toolEntries = tools.map((tool) => ` { name: "${tool.name}", exports: ${tool.identifier} },`);
184
+ body.push(`await app.withPlugin(new MCPPlugin([`);
185
+ for (const entry of toolEntries) {
186
+ body.push(entry);
162
187
  }
163
- body.push("await mcp.connect();");
188
+ body.push(`]));`);
164
189
  }
165
190
 
166
- // If app/erc-8004.ts exists, auto-register ERC-8004 endpoint
167
- const hasErc8004 = existsSync(resolve(appDir, "erc-8004.ts"));
168
191
  if (hasErc8004) {
169
- imports.push('import { useERC8004 } from "aixyz/server/adapters/erc-8004";');
170
- imports.push(`import * as erc8004 from "${importPrefix}/erc-8004";`);
171
192
  const a2aPaths: string[] = [];
172
193
  if (rootAgent) a2aPaths.push("/.well-known/agent-card.json");
173
194
  for (const subAgent of subAgents) a2aPaths.push(`/${subAgent.name}/.well-known/agent-card.json`);
174
195
  body.push(
175
- `useERC8004(server, { default: erc8004.default, options: { mcp: ${tools.length > 0}, a2a: ${JSON.stringify(a2aPaths)} } });`,
196
+ `await app.withPlugin(new ERC8004Plugin({ default: erc8004.default, options: { mcp: ${tools.length > 0}, a2a: ${JSON.stringify(a2aPaths)} } }));`,
176
197
  );
177
198
  }
178
199
 
179
- body.push("export default server;");
200
+ body.push("await app.initialize();");
201
+ body.push("export default app;");
180
202
 
181
203
  return [...imports, "", ...body].join("\n");
182
204
  }
package/build/index.ts CHANGED
@@ -63,21 +63,21 @@ export async function action(options: BuildOptions = {}): Promise<void> {
63
63
  const config = getAixyzConfig();
64
64
  const target = options.output ?? config.build?.output ?? (process.env.VERCEL === "1" ? "vercel" : "standalone");
65
65
  const appDir = options.appDir || "app";
66
- const entrypoint = getEntrypointMayGenerate(cwd, appDir, "build");
66
+ const { path: entrypoint, isCustom } = getEntrypointMayGenerate(cwd, appDir, "build");
67
67
 
68
68
  if (target === "vercel") {
69
69
  console.log(chalk.cyan("▶") + " Building for " + chalk.bold("Vercel") + "...");
70
- await buildVercel(entrypoint, config);
70
+ await buildVercel(entrypoint, config, isCustom);
71
71
  } else if (target === "executable") {
72
72
  console.log(chalk.cyan("▶") + " Building " + chalk.bold("Executable") + "...");
73
- await buildExecutable(entrypoint);
73
+ await buildExecutable(entrypoint, isCustom);
74
74
  } else {
75
75
  console.log(chalk.cyan("▶") + " Building for " + chalk.bold("Standalone") + "...");
76
- await buildBun(entrypoint);
76
+ await buildBun(entrypoint, isCustom);
77
77
  }
78
78
  }
79
79
 
80
- async function buildBun(entrypoint: string): Promise<void> {
80
+ async function buildBun(entrypoint: string, isCustom: boolean): Promise<void> {
81
81
  const cwd = process.cwd();
82
82
 
83
83
  const outputDir = resolve(cwd, ".aixyz/output");
@@ -96,7 +96,7 @@ async function buildBun(entrypoint: string): Promise<void> {
96
96
  "process.env.NODE_ENV": JSON.stringify("production"),
97
97
  "process.env.AIXYZ_ENV": JSON.stringify("production"),
98
98
  },
99
- plugins: [AixyzConfigPlugin(), AixyzServerPlugin(entrypoint, "standalone")],
99
+ plugins: [AixyzConfigPlugin(), AixyzServerPlugin(entrypoint, "standalone", isCustom)],
100
100
  });
101
101
 
102
102
  if (!result.success) {
@@ -136,7 +136,7 @@ async function buildBun(entrypoint: string): Promise<void> {
136
136
  console.log("To run: bun .aixyz/output/server.js");
137
137
  }
138
138
 
139
- async function buildExecutable(entrypoint: string): Promise<void> {
139
+ async function buildExecutable(entrypoint: string, isCustom: boolean): Promise<void> {
140
140
  const cwd = process.cwd();
141
141
 
142
142
  const outputDir = resolve(cwd, ".aixyz/output");
@@ -156,7 +156,7 @@ async function buildExecutable(entrypoint: string): Promise<void> {
156
156
  "process.env.NODE_ENV": JSON.stringify("production"),
157
157
  "process.env.AIXYZ_ENV": JSON.stringify("production"),
158
158
  },
159
- plugins: [AixyzConfigPlugin(), AixyzServerPlugin(entrypoint, "executable")],
159
+ plugins: [AixyzConfigPlugin(), AixyzServerPlugin(entrypoint, "executable", isCustom)],
160
160
  });
161
161
 
162
162
  if (!result.success) {
@@ -192,7 +192,11 @@ async function buildExecutable(entrypoint: string): Promise<void> {
192
192
  console.log("To run: ./.aixyz/output/server");
193
193
  }
194
194
 
195
- async function buildVercel(entrypoint: string, config: ReturnType<typeof getAixyzConfig>): Promise<void> {
195
+ async function buildVercel(
196
+ entrypoint: string,
197
+ config: ReturnType<typeof getAixyzConfig>,
198
+ isCustom: boolean,
199
+ ): Promise<void> {
196
200
  const cwd = process.cwd();
197
201
 
198
202
  const outputDir = resolve(cwd, ".vercel/output");
@@ -213,7 +217,7 @@ async function buildVercel(entrypoint: string, config: ReturnType<typeof getAixy
213
217
  "process.env.NODE_ENV": JSON.stringify("production"),
214
218
  "process.env.AIXYZ_ENV": JSON.stringify("production"),
215
219
  },
216
- plugins: [AixyzConfigPlugin(), AixyzServerPlugin(entrypoint, "vercel")],
220
+ plugins: [AixyzConfigPlugin(), AixyzServerPlugin(entrypoint, "vercel", isCustom)],
217
221
  });
218
222
 
219
223
  if (!result.success) {
package/dev/index.ts CHANGED
@@ -8,7 +8,7 @@ import pkg from "../package.json";
8
8
 
9
9
  export const devCommand = new Command("dev")
10
10
  .description("Start a local development server")
11
- .option("-p, --port <port>", "Port to listen on", "3000")
11
+ .option("-p, --port <port>", "Port to listen on")
12
12
  .action(action);
13
13
 
14
14
  type DevOptions = {
@@ -45,8 +45,10 @@ export async function action(options: DevOptions): Promise<void> {
45
45
  let restarting = false;
46
46
 
47
47
  function startServer() {
48
- const endpoint = getEntrypointMayGenerate(cwd, appDir, "dev");
49
- child = Bun.spawn(["bun", workerPath, endpoint, port], {
48
+ const { path: endpoint, isCustom } = getEntrypointMayGenerate(cwd, appDir, "dev");
49
+ const args = ["bun", workerPath, endpoint, port];
50
+ if (isCustom) args.push("custom");
51
+ child = Bun.spawn(args, {
50
52
  cwd,
51
53
  stdout: "inherit",
52
54
  stderr: "inherit",
package/dev/worker.ts CHANGED
@@ -3,26 +3,39 @@ import chalk from "chalk";
3
3
  async function main() {
4
4
  const entrypoint = process.argv[2];
5
5
  const port = parseInt(process.argv[3], 10);
6
+ const isCustom = process.argv[4] === "custom";
6
7
 
7
8
  if (!entrypoint || isNaN(port)) {
8
- console.error("Usage: dev-worker <entrypoint> <port>");
9
+ console.error("Usage: dev-worker <entrypoint> <port> [custom]");
9
10
  process.exit(1);
10
11
  }
11
12
 
13
+ // Expose port so config.url fallback picks it up
14
+ process.env.PORT = String(port);
15
+
12
16
  const startTime = performance.now();
13
17
  const mod = await import(entrypoint);
18
+
19
+ if (isCustom) {
20
+ // Custom server.ts manages its own lifecycle (e.g. Express, Fastify)
21
+ const duration = Math.round(performance.now() - startTime);
22
+ console.log(chalk.blueBright("✓") + ` Ready in ${duration}ms`);
23
+ console.log("");
24
+ return;
25
+ }
26
+
14
27
  const app = mod.default;
15
28
 
16
- if (!app || typeof app.express?.listen !== "function") {
29
+ if (!app || typeof app.fetch !== "function") {
17
30
  console.error("Error: Entrypoint must default-export an AixyzApp");
18
31
  process.exit(1);
19
32
  }
20
33
 
21
- app.express.listen(port, () => {
22
- const duration = Math.round(performance.now() - startTime);
23
- console.log(chalk.blueBright("✓") + ` Ready in ${duration}ms`);
24
- console.log("");
25
- });
34
+ const server = Bun.serve({ port, fetch: app.fetch });
35
+
36
+ const duration = Math.round(performance.now() - startTime);
37
+ console.log(chalk.blueBright("") + ` Ready in ${duration}ms`);
38
+ console.log("");
26
39
  }
27
40
 
28
41
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aixyz/cli",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Payment-native SDK for AI Agent",
5
5
  "keywords": [
6
6
  "ai",
@@ -28,8 +28,8 @@
28
28
  "bin.ts"
29
29
  ],
30
30
  "dependencies": {
31
- "@aixyz/config": "0.19.0",
32
- "@aixyz/erc-8004": "0.19.0",
31
+ "@aixyz/config": "0.21.0",
32
+ "@aixyz/erc-8004": "0.21.0",
33
33
  "@inquirer/prompts": "^8.3.0",
34
34
  "@next/env": "^16.1.6",
35
35
  "boxen": "^8.0.1",