@aixyz/cli 0.0.0 → 0.1.2

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 ADDED
@@ -0,0 +1,45 @@
1
+ # aixyz-cli
2
+
3
+ [![npm](https://img.shields.io/npm/v/aixyz-cli)](https://www.npmjs.com/package/aixyz-cli)
4
+
5
+ CLI for building and deploying [aixyz](https://ai-xyz.dev) agents.
6
+
7
+ ## Quick Start
8
+
9
+ Run without installing:
10
+
11
+ ```bash
12
+ bunx aixyz-cli dev
13
+ npx aixyz-cli dev
14
+ ```
15
+
16
+ > Note: Requires [Bun](https://bun.sh) to be installed on your system.
17
+
18
+ ## Installation
19
+
20
+ ```bash
21
+ bun add aixyz-cli
22
+ ```
23
+
24
+ ## Commands
25
+
26
+ ### `aixyz-cli dev`
27
+
28
+ Start a local development server with file watching and auto-restart.
29
+
30
+ ```bash
31
+ aixyz-cli dev
32
+ aixyz-cli dev --port 8080
33
+ ```
34
+
35
+ ### `aixyz-cli build`
36
+
37
+ Build the agent for Vercel deployment using the [Build Output API v3](https://vercel.com/docs/build-output-api/v3).
38
+
39
+ ```bash
40
+ aixyz-cli build
41
+ ```
42
+
43
+ ## License
44
+
45
+ MIT
package/bin.ts ADDED
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bun
2
+ import { program } from "commander";
3
+ import { build } from "./build";
4
+ import { dev } from "./dev";
5
+ import pkg from "./package.json";
6
+
7
+ function handleAction(
8
+ action: (options: Record<string, unknown>) => Promise<void>,
9
+ ): (options: Record<string, unknown>) => Promise<void> {
10
+ return async (options) => {
11
+ try {
12
+ await action(options);
13
+ } catch (error) {
14
+ console.error(`Error: ${error instanceof Error ? error.message : String(error)}`);
15
+ process.exit(1);
16
+ }
17
+ };
18
+ }
19
+
20
+ program.name("aixyz").description("CLI for building and deploying aixyz agents").version(pkg.version);
21
+
22
+ program
23
+ .command("dev")
24
+ .description("Start a local development server")
25
+ .option("-p, --port <port>", "Port to listen on", "3000")
26
+ .action(handleAction(dev));
27
+
28
+ program
29
+ .command("build")
30
+ .description("Build the aixyz agent for Vercel deployment")
31
+ .addHelpText(
32
+ "after",
33
+ `
34
+ Details:
35
+ Bundles your aixyz agent into a Vercel serverless function output.
36
+
37
+ The build process:
38
+ 1. Loads aixyz.config.ts from the current directory
39
+ 2. Detects entrypoint (app/server.ts or auto-generates from app/agent.ts + app/tools/)
40
+ 3. Generates Vercel Build Output API v3 structure
41
+ 4. Copies static assets from public/ (if present)
42
+
43
+ Output is written to .vercel/output/ in the current directory.
44
+
45
+ Prerequisites:
46
+ - An aixyz.config.ts with a default export
47
+ - An entrypoint at app/server.ts, or app/agent.ts + app/tools/ for auto-generation
48
+
49
+ Examples:
50
+ $ aixyz build`,
51
+ )
52
+ .action(handleAction(build));
53
+
54
+ program.parse();
@@ -0,0 +1,24 @@
1
+ import { getAixyzConfig } from "@aixyz/config";
2
+ import type { BunPlugin } from "bun";
3
+
4
+ export function AixyzConfigPlugin(): BunPlugin {
5
+ const materialized = getAixyzConfig();
6
+
7
+ // TODO(@fuxingloh): change how this is formatted
8
+ console.log("AixyzConfig loaded:", materialized);
9
+
10
+ return {
11
+ name: "aixyz-config",
12
+ setup(build) {
13
+ build.onLoad({ filter: /packages\/aixyz\/config\.ts$/ }, () => ({
14
+ contents: `
15
+ const config = ${JSON.stringify(materialized)};
16
+ export function getAixyzConfig() {
17
+ return config;
18
+ }
19
+ `,
20
+ loader: "ts",
21
+ }));
22
+ },
23
+ };
24
+ }
@@ -0,0 +1,96 @@
1
+ import type { BunPlugin } from "bun";
2
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
3
+ import { resolve, relative, basename, join } from "path";
4
+
5
+ export function AixyzServerPlugin(entrypoint: string): BunPlugin {
6
+ return {
7
+ name: "aixyz-entrypoint",
8
+ setup(build) {
9
+ build.onLoad({ filter: /server\.ts$/ }, async (args) => {
10
+ if (args.path !== entrypoint) return;
11
+
12
+ const source = await Bun.file(args.path).text();
13
+ const transformed = source.replace(/export\s+default\s+(\w+)\s*;/, "export default $1.express;");
14
+
15
+ return { contents: transformed, loader: "ts" };
16
+ });
17
+ },
18
+ };
19
+ }
20
+
21
+ export function getEntrypointMayGenerate(cwd: string, mode: "dev" | "build"): string {
22
+ const appDir = resolve(cwd, "app");
23
+
24
+ if (existsSync(resolve(appDir, "server.ts"))) {
25
+ return resolve(appDir, "server.ts");
26
+ }
27
+
28
+ const devDir = resolve(cwd, join(".aixyz", mode));
29
+ mkdirSync(devDir, { recursive: true });
30
+ const entrypoint = resolve(devDir, "server.ts");
31
+ writeFileSync(entrypoint, generateServer(appDir, devDir));
32
+ return entrypoint;
33
+ }
34
+
35
+ /**
36
+ * Generate server.ts content by scanning the app directory for agent.ts and tools/.
37
+ *
38
+ * @param appDir - The app directory containing agent.ts and tools/
39
+ * @param entrypointDir - Directory where the generated file will live (for computing relative imports).
40
+ */
41
+ function generateServer(appDir: string, entrypointDir: string): string {
42
+ const rel = relative(entrypointDir, appDir);
43
+ const importPrefix = rel === "" ? "." : rel.startsWith(".") ? rel : `./${rel}`;
44
+
45
+ const imports: string[] = [];
46
+ const body: string[] = [];
47
+
48
+ imports.push('import { AixyzServer } from "aixyz/server";');
49
+
50
+ const hasAgent = existsSync(resolve(appDir, "agent.ts"));
51
+ if (hasAgent) {
52
+ imports.push('import { useA2A } from "aixyz/server/adapters/a2a";');
53
+ imports.push(`import * as agent from "${importPrefix}/agent";`);
54
+ }
55
+
56
+ const toolsDir = resolve(appDir, "tools");
57
+ const tools: { name: string }[] = [];
58
+ if (existsSync(toolsDir)) {
59
+ for (const file of readdirSync(toolsDir)) {
60
+ if (file.endsWith(".ts")) {
61
+ const name = basename(file, ".ts");
62
+ // Skip tools starting with underscore
63
+ if (!name.startsWith("_")) {
64
+ tools.push({ name });
65
+ }
66
+ }
67
+ }
68
+ }
69
+
70
+ if (tools.length > 0) {
71
+ imports.push('import { AixyzMCP } from "aixyz/server/adapters/mcp";');
72
+ for (const tool of tools) {
73
+ imports.push(`import * as ${tool.name} from "${importPrefix}/tools/${tool.name}";`);
74
+ }
75
+ }
76
+
77
+ body.push("const server = new AixyzServer();");
78
+ body.push("await server.initialize();");
79
+ body.push("server.unstable_withIndexPage();");
80
+
81
+ if (hasAgent) {
82
+ body.push("useA2A(server, agent);");
83
+ }
84
+
85
+ if (tools.length > 0) {
86
+ body.push("const mcp = new AixyzMCP(server);");
87
+ for (const tool of tools) {
88
+ body.push(`await mcp.register("${tool.name}", ${tool.name});`);
89
+ }
90
+ body.push("await mcp.connect();");
91
+ }
92
+
93
+ body.push("export default server;");
94
+
95
+ return [...imports, "", ...body].join("\n");
96
+ }
package/build/index.ts ADDED
@@ -0,0 +1,90 @@
1
+ import { resolve } from "path";
2
+ import { existsSync, mkdirSync, cpSync, rmSync } from "fs";
3
+ import { AixyzConfigPlugin } from "./AixyzConfigPlugin";
4
+ import { AixyzServerPlugin, getEntrypointMayGenerate } from "./AixyzServerPlugin";
5
+
6
+ export async function build(): Promise<void> {
7
+ const cwd = process.cwd();
8
+
9
+ const entrypoint = getEntrypointMayGenerate(cwd, "build");
10
+
11
+ const outputDir = resolve(cwd, ".vercel/output");
12
+ rmSync(outputDir, { recursive: true, force: true });
13
+
14
+ const funcDir = resolve(outputDir, "functions/index.func");
15
+ mkdirSync(funcDir, { recursive: true });
16
+
17
+ // Write functions/index.func
18
+ const result = await Bun.build({
19
+ entrypoints: [entrypoint],
20
+ outdir: funcDir,
21
+ naming: "server.js",
22
+ target: "bun",
23
+ format: "esm",
24
+ sourcemap: "linked",
25
+ plugins: [AixyzConfigPlugin(), AixyzServerPlugin(entrypoint)],
26
+ });
27
+
28
+ if (!result.success) {
29
+ console.error("Build failed:");
30
+ for (const log of result.logs) {
31
+ console.error(log);
32
+ }
33
+ process.exit(1);
34
+ }
35
+
36
+ // Write .vc-config.json
37
+ await Bun.write(
38
+ resolve(funcDir, ".vc-config.json"),
39
+ JSON.stringify(
40
+ {
41
+ handler: "server.js",
42
+ runtime: "bun1.x",
43
+ launcherType: "Bun",
44
+ shouldAddHelpers: true,
45
+ shouldAddSourcemapSupport: true,
46
+ },
47
+ null,
48
+ 2,
49
+ ),
50
+ );
51
+
52
+ // Write package.json for ESM support
53
+ await Bun.write(resolve(funcDir, "package.json"), JSON.stringify({ type: "module" }, null, 2));
54
+
55
+ // Write config.json
56
+ await Bun.write(
57
+ resolve(outputDir, "config.json"),
58
+ JSON.stringify(
59
+ {
60
+ version: 3,
61
+ routes: [{ handle: "filesystem" }, { src: "/(.*)", dest: "/" }],
62
+ },
63
+ null,
64
+ 2,
65
+ ),
66
+ );
67
+
68
+ // Copy static assets (public/ → .vercel/output/static/)
69
+ const staticDir = resolve(outputDir, "static");
70
+
71
+ const publicDir = resolve(cwd, "public");
72
+ if (existsSync(publicDir)) {
73
+ cpSync(publicDir, staticDir, { recursive: true });
74
+ console.log("Copied public/ →", staticDir);
75
+ }
76
+
77
+ const iconFile = resolve(cwd, "app/icon.png");
78
+ if (existsSync(iconFile)) {
79
+ mkdirSync(staticDir, { recursive: true });
80
+ cpSync(iconFile, resolve(staticDir, "icon.png"));
81
+ console.log("Copied app/icon.png →", staticDir);
82
+ }
83
+
84
+ // Log summary
85
+ console.log("");
86
+ console.log("Build complete! Output:");
87
+ console.log(" .vercel/output/config.json");
88
+ console.log(" .vercel/output/functions/index.func/index.js");
89
+ console.log(" .vercel/output/static/");
90
+ }
package/dev/index.ts ADDED
@@ -0,0 +1,96 @@
1
+ import { resolve, relative } from "path";
2
+ import { existsSync, watch } from "fs";
3
+ import { loadEnvConfig } from "@next/env";
4
+ import { getEntrypointMayGenerate } from "../build/AixyzServerPlugin";
5
+ import pkg from "../package.json";
6
+
7
+ export async function dev(options: { port?: string }): Promise<void> {
8
+ const cwd = process.cwd();
9
+
10
+ // Load environment config
11
+ const { loadedEnvFiles } = loadEnvConfig(cwd, true);
12
+ const envFileNames = loadedEnvFiles.map((f) => relative(cwd, f.path));
13
+
14
+ const port = options.port || process.env.PORT || "3000";
15
+ const baseUrl = `http://localhost:${port}`;
16
+
17
+ console.log("");
18
+ console.log(`⟡ ai-xyz.dev v${pkg.version}`);
19
+ console.log("");
20
+ console.log(`- A2A: ${baseUrl}/.well-known/agent-card.json`);
21
+ console.log(`- MCP: ${baseUrl}/mcp`);
22
+ if (envFileNames.length > 0) {
23
+ console.log(`- Environments: ${envFileNames.join(", ")}`);
24
+ }
25
+ console.log("");
26
+
27
+ // Spawn worker process
28
+ const workerPath = resolve(__dirname, "worker.js");
29
+ let child: ReturnType<typeof Bun.spawn> | null = null;
30
+ let restarting = false;
31
+
32
+ function startServer() {
33
+ const endpoint = getEntrypointMayGenerate(cwd, "dev");
34
+ child = Bun.spawn(["bun", workerPath, endpoint, port], {
35
+ cwd,
36
+ stdout: "inherit",
37
+ stderr: "inherit",
38
+ env: process.env,
39
+ });
40
+ child.exited.then((code) => {
41
+ if (!restarting && code !== 0) {
42
+ console.log(`\nServer exited with code ${code}, waiting for changes...`);
43
+ }
44
+ });
45
+ }
46
+
47
+ async function restartServer(reason: string) {
48
+ restarting = true;
49
+ if (child) {
50
+ child.kill();
51
+ await child.exited;
52
+ child = null;
53
+ }
54
+ restarting = false;
55
+ console.log(`Restarting... ${reason}`);
56
+ startServer();
57
+ }
58
+
59
+ startServer();
60
+
61
+ // Watch app/ for changes
62
+ let debounceTimer: ReturnType<typeof setTimeout> | null = null;
63
+
64
+ function scheduleRestart(reason: string) {
65
+ if (debounceTimer) clearTimeout(debounceTimer);
66
+ debounceTimer = setTimeout(() => {
67
+ restartServer(reason);
68
+ }, 100);
69
+ }
70
+
71
+ watch(resolve(cwd, "app"), { recursive: true }, (_event, filename) => {
72
+ scheduleRestart(filename ? `${filename} changed` : "file changed");
73
+ });
74
+
75
+ // Watch config file
76
+ const configFile = resolve(cwd, "aixyz.config.ts");
77
+ if (existsSync(configFile)) {
78
+ watch(configFile, () => {
79
+ scheduleRestart("config changed");
80
+ });
81
+ }
82
+
83
+ // Handle shutdown
84
+ process.on("SIGINT", () => {
85
+ if (child) child.kill();
86
+ process.exit(0);
87
+ });
88
+
89
+ process.on("SIGTERM", () => {
90
+ if (child) child.kill();
91
+ process.exit(0);
92
+ });
93
+
94
+ // Keep the process alive
95
+ await new Promise(() => {});
96
+ }
package/dev/worker.ts ADDED
@@ -0,0 +1,26 @@
1
+ async function main() {
2
+ const entrypoint = process.argv[2];
3
+ const port = parseInt(process.argv[3], 10);
4
+
5
+ if (!entrypoint || isNaN(port)) {
6
+ console.error("Usage: dev-worker <entrypoint> <port>");
7
+ process.exit(1);
8
+ }
9
+
10
+ const startTime = performance.now();
11
+ const mod = await import(entrypoint);
12
+ const app = mod.default;
13
+
14
+ if (!app || typeof app.express?.listen !== "function") {
15
+ console.error("Error: Entrypoint must default-export an AixyzApp");
16
+ process.exit(1);
17
+ }
18
+
19
+ app.express.listen(port, () => {
20
+ const duration = Math.round(performance.now() - startTime);
21
+ console.log(`Ready in ${duration}ms`);
22
+ console.log("");
23
+ });
24
+ }
25
+
26
+ main();
package/package.json CHANGED
@@ -1,6 +1,40 @@
1
1
  {
2
2
  "name": "@aixyz/cli",
3
- "version": "0.0.0",
4
- "private": false,
5
- "files": []
3
+ "version": "0.1.2",
4
+ "description": "CLI for building and deploying aixyz agents.",
5
+ "keywords": [
6
+ "ai",
7
+ "agent",
8
+ "cli",
9
+ "aixyz",
10
+ "erc-8004",
11
+ "deploy"
12
+ ],
13
+ "homepage": "https://ai-xyz.dev",
14
+ "bugs": {
15
+ "url": "https://github.com/AgentlyHQ/aixyz/issues"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/AgentlyHQ/aixyz.git"
20
+ },
21
+ "license": "MIT",
22
+ "author": "AgentlyHQ",
23
+ "type": "module",
24
+ "bin": {
25
+ "aixyz-cli": "bin.ts"
26
+ },
27
+ "files": [
28
+ "build",
29
+ "dev",
30
+ "bin.ts"
31
+ ],
32
+ "dependencies": {
33
+ "@aixyz/config": "workspace:*",
34
+ "@next/env": "^16.1.6",
35
+ "commander": "^13.0.0"
36
+ },
37
+ "engines": {
38
+ "bun": ">=1.3.0"
39
+ }
6
40
  }