@aixyz/cli 0.0.0 → 0.1.1
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 +45 -0
- package/bin.ts +54 -0
- package/build/AixyzConfigPlugin.ts +24 -0
- package/build/AixyzServerPlugin.ts +96 -0
- package/build/index.ts +90 -0
- package/dev/index.ts +96 -0
- package/dev/worker.ts +26 -0
- package/package.json +37 -3
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# aixyz-cli
|
|
2
|
+
|
|
3
|
+
[](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.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.1",
|
|
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
|
}
|