@aixyz/cli 0.12.0 → 0.14.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.
- package/bin.ts +2 -0
- package/build/AixyzServerPlugin.ts +42 -28
- package/package.json +3 -3
- package/register/wallet/index.ts +13 -6
- package/register/wallet/local.test.ts +102 -0
- package/register/wallet/local.ts +62 -0
package/bin.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { Command } from "commander";
|
|
|
3
3
|
import { devCommand } from "./dev";
|
|
4
4
|
import { buildCommand } from "./build";
|
|
5
5
|
import { erc8004Command } from "./register";
|
|
6
|
+
import { walletCommand } from "./wallet";
|
|
6
7
|
import pkg from "./package.json";
|
|
7
8
|
|
|
8
9
|
const cli = new Command();
|
|
@@ -11,6 +12,7 @@ cli.name("aixyz").description("CLI for building and deploying aixyz agents").ver
|
|
|
11
12
|
cli.addCommand(devCommand);
|
|
12
13
|
cli.addCommand(buildCommand);
|
|
13
14
|
cli.addCommand(erc8004Command);
|
|
15
|
+
cli.addCommand(walletCommand);
|
|
14
16
|
|
|
15
17
|
try {
|
|
16
18
|
await cli.parseAsync();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { BunPlugin } from "bun";
|
|
2
2
|
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs";
|
|
3
|
-
import { resolve, relative,
|
|
3
|
+
import { resolve, relative, join } from "path";
|
|
4
4
|
import { getAixyzConfig } from "@aixyz/config";
|
|
5
5
|
|
|
6
6
|
export function AixyzServerPlugin(entrypoint: string, mode: "vercel" | "standalone" | "executable"): BunPlugin {
|
|
@@ -55,8 +55,40 @@ export function getEntrypointMayGenerate(cwd: string, mode: "dev" | "build"): st
|
|
|
55
55
|
class AixyzGlob {
|
|
56
56
|
constructor(readonly config = getAixyzConfig()) {}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
const
|
|
58
|
+
hasRootAgent(appDir: string): { file: string } | undefined {
|
|
59
|
+
const file = readdirSync(appDir).find((f) => /^agent\.(js|ts)$/.test(f) && this.includesAgent(f));
|
|
60
|
+
return file ? { file } : undefined;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
getAgents(agentsDir: string): { name: string; identifier: string }[] {
|
|
64
|
+
if (!existsSync(agentsDir)) return [];
|
|
65
|
+
return readdirSync(agentsDir)
|
|
66
|
+
.filter((file) => this.includesAgent(`agents/${file}`))
|
|
67
|
+
.map((file) => {
|
|
68
|
+
const name = file.replace(/\.(js|ts)$/, "");
|
|
69
|
+
return { name, identifier: toIdentifier(name) };
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getTools(toolsDir: string): { name: string; identifier: string }[] {
|
|
74
|
+
if (!existsSync(toolsDir)) return [];
|
|
75
|
+
return readdirSync(toolsDir)
|
|
76
|
+
.filter((file) => this.includesTool(`tools/${file}`))
|
|
77
|
+
.map((file) => {
|
|
78
|
+
const name = file.replace(/\.(js|ts)$/, "");
|
|
79
|
+
return { name, identifier: toIdentifier(name) };
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private includesAgent(file: string): boolean {
|
|
84
|
+
const included = this.config.build.agents.some((pattern) => new Bun.Glob(pattern).match(file));
|
|
85
|
+
if (!included) return false;
|
|
86
|
+
const excluded = this.config.build.excludes.some((pattern) => new Bun.Glob(pattern).match(file));
|
|
87
|
+
return !excluded;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private includesTool(file: string): boolean {
|
|
91
|
+
const included = this.config.build.tools.some((pattern) => new Bun.Glob(pattern).match(file));
|
|
60
92
|
if (!included) return false;
|
|
61
93
|
const excluded = this.config.build.excludes.some((pattern) => new Bun.Glob(pattern).match(file));
|
|
62
94
|
return !excluded;
|
|
@@ -86,25 +118,16 @@ function generateServer(appDir: string, entrypointDir: string): string {
|
|
|
86
118
|
imports.push('import { facilitator } from "aixyz/accepts";');
|
|
87
119
|
}
|
|
88
120
|
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
121
|
+
const rootAgent = glob.hasRootAgent(appDir);
|
|
122
|
+
if (rootAgent) {
|
|
91
123
|
imports.push('import { useA2A } from "aixyz/server/adapters/a2a";');
|
|
92
124
|
imports.push(`import * as agent from "${importPrefix}/agent";`);
|
|
93
125
|
}
|
|
94
126
|
|
|
95
127
|
const agentsDir = resolve(appDir, "agents");
|
|
96
|
-
const subAgents
|
|
97
|
-
if (existsSync(agentsDir)) {
|
|
98
|
-
for (const file of readdirSync(agentsDir)) {
|
|
99
|
-
if (glob.includes(`agents/${file}`)) {
|
|
100
|
-
const name = basename(file, ".ts");
|
|
101
|
-
const identifier = toIdentifier(name);
|
|
102
|
-
subAgents.push({ name, identifier });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
128
|
+
const subAgents = glob.getAgents(agentsDir);
|
|
106
129
|
|
|
107
|
-
if (!
|
|
130
|
+
if (!rootAgent && subAgents.length > 0) {
|
|
108
131
|
imports.push('import { useA2A } from "aixyz/server/adapters/a2a";');
|
|
109
132
|
}
|
|
110
133
|
for (const subAgent of subAgents) {
|
|
@@ -112,16 +135,7 @@ function generateServer(appDir: string, entrypointDir: string): string {
|
|
|
112
135
|
}
|
|
113
136
|
|
|
114
137
|
const toolsDir = resolve(appDir, "tools");
|
|
115
|
-
const tools
|
|
116
|
-
if (existsSync(toolsDir)) {
|
|
117
|
-
for (const file of readdirSync(toolsDir)) {
|
|
118
|
-
if (glob.includes(`tools/${file}`)) {
|
|
119
|
-
const name = basename(file, ".ts");
|
|
120
|
-
const identifier = toIdentifier(name);
|
|
121
|
-
tools.push({ name, identifier });
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
138
|
+
const tools = glob.getTools(toolsDir);
|
|
125
139
|
|
|
126
140
|
if (tools.length > 0) {
|
|
127
141
|
imports.push('import { AixyzMCP } from "aixyz/server/adapters/mcp";');
|
|
@@ -134,7 +148,7 @@ function generateServer(appDir: string, entrypointDir: string): string {
|
|
|
134
148
|
body.push("await server.initialize();");
|
|
135
149
|
body.push("server.unstable_withIndexPage();");
|
|
136
150
|
|
|
137
|
-
if (
|
|
151
|
+
if (rootAgent) {
|
|
138
152
|
body.push("useA2A(server, agent);");
|
|
139
153
|
}
|
|
140
154
|
|
|
@@ -155,7 +169,7 @@ function generateServer(appDir: string, entrypointDir: string): string {
|
|
|
155
169
|
imports.push('import { useERC8004 } from "aixyz/server/adapters/erc-8004";');
|
|
156
170
|
imports.push(`import * as erc8004 from "${importPrefix}/erc-8004";`);
|
|
157
171
|
const a2aPaths: string[] = [];
|
|
158
|
-
if (
|
|
172
|
+
if (rootAgent) a2aPaths.push("/.well-known/agent-card.json");
|
|
159
173
|
for (const subAgent of subAgents) a2aPaths.push(`/${subAgent.name}/.well-known/agent-card.json`);
|
|
160
174
|
body.push(
|
|
161
175
|
`useERC8004(server, { default: erc8004.default, options: { mcp: ${tools.length > 0}, a2a: ${JSON.stringify(a2aPaths)} } });`,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aixyz/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Payment-native SDK for AI Agent",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"bin.ts"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@aixyz/config": "0.
|
|
31
|
-
"@aixyz/erc-8004": "0.
|
|
30
|
+
"@aixyz/config": "0.14.0",
|
|
31
|
+
"@aixyz/erc-8004": "0.14.0",
|
|
32
32
|
"@inquirer/prompts": "^8.3.0",
|
|
33
33
|
"@next/env": "^16.1.6",
|
|
34
34
|
"boxen": "^8.0.1",
|
package/register/wallet/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { select, input, password } from "@inquirer/prompts";
|
|
|
4
4
|
import { withTTY } from "../utils/prompt";
|
|
5
5
|
import { createPrivateKeyWallet } from "./privatekey";
|
|
6
6
|
import { createKeystoreWallet } from "./keystore";
|
|
7
|
+
import { hasLocalWallet, getLocalWalletPrivateKey } from "./local";
|
|
7
8
|
|
|
8
9
|
export interface WalletOptions {
|
|
9
10
|
keystore?: string;
|
|
@@ -35,13 +36,17 @@ export async function selectWalletMethod(options: WalletOptions): Promise<Wallet
|
|
|
35
36
|
|
|
36
37
|
// Interactive: prompt user to choose
|
|
37
38
|
return withTTY(async () => {
|
|
39
|
+
const localWalletExists = hasLocalWallet();
|
|
40
|
+
const choices = [
|
|
41
|
+
{ name: "Keystore file", value: "keystore" },
|
|
42
|
+
{ name: "Browser wallet (any EIP-6963 compatible wallets)", value: "browser" },
|
|
43
|
+
{ name: "Private key (not recommended)", value: "privatekey" },
|
|
44
|
+
...(localWalletExists ? [{ name: "Local wallet (.aixyz/wallet.json)", value: "local" }] : []),
|
|
45
|
+
];
|
|
46
|
+
|
|
38
47
|
const method = await select({
|
|
39
48
|
message: "Select signing method:",
|
|
40
|
-
choices
|
|
41
|
-
{ name: "Keystore file", value: "keystore" },
|
|
42
|
-
{ name: "Browser wallet (any EIP-6963 compatible wallets)", value: "browser" },
|
|
43
|
-
{ name: "Private key (not recommended)", value: "privatekey" },
|
|
44
|
-
],
|
|
49
|
+
choices,
|
|
45
50
|
});
|
|
46
51
|
|
|
47
52
|
switch (method) {
|
|
@@ -54,6 +59,8 @@ export async function selectWalletMethod(options: WalletOptions): Promise<Wallet
|
|
|
54
59
|
}
|
|
55
60
|
case "browser":
|
|
56
61
|
return { type: "browser" };
|
|
62
|
+
case "local":
|
|
63
|
+
return { type: "privatekey", resolveKey: () => Promise.resolve(getLocalWalletPrivateKey()) };
|
|
57
64
|
case "privatekey": {
|
|
58
65
|
const key = await password({
|
|
59
66
|
message: "Enter private key:",
|
|
@@ -65,7 +72,7 @@ export async function selectWalletMethod(options: WalletOptions): Promise<Wallet
|
|
|
65
72
|
default:
|
|
66
73
|
throw new Error("No wallet method selected");
|
|
67
74
|
}
|
|
68
|
-
}, "No TTY detected. Provide --keystore, --browser,
|
|
75
|
+
}, "No TTY detected. Provide --keystore, --browser, PRIVATE_KEY environment variable, or run `aixyz wallet generate` to create a local wallet.");
|
|
69
76
|
}
|
|
70
77
|
|
|
71
78
|
export async function createWalletFromMethod(
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { describe, expect, test, beforeAll, afterAll } from "bun:test";
|
|
2
|
+
import { mkdirSync, rmSync, existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import {
|
|
6
|
+
generateLocalWallet,
|
|
7
|
+
hasLocalWallet,
|
|
8
|
+
readLocalWallet,
|
|
9
|
+
getLocalWalletPrivateKey,
|
|
10
|
+
getLocalWalletPath,
|
|
11
|
+
} from "./local";
|
|
12
|
+
|
|
13
|
+
const testDir = join(tmpdir(), "aixyz-cli-local-wallet-test");
|
|
14
|
+
|
|
15
|
+
beforeAll(() => {
|
|
16
|
+
mkdirSync(testDir, { recursive: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterAll(() => {
|
|
20
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("generateLocalWallet", () => {
|
|
24
|
+
test("creates .aixyz/wallet.json with mnemonic and address", () => {
|
|
25
|
+
const wallet = generateLocalWallet(testDir);
|
|
26
|
+
expect(wallet.mnemonic).toBeTruthy();
|
|
27
|
+
expect(wallet.address).toMatch(/^0x[0-9a-fA-F]{40}$/);
|
|
28
|
+
const walletPath = getLocalWalletPath(testDir);
|
|
29
|
+
expect(existsSync(walletPath)).toBe(true);
|
|
30
|
+
const content = JSON.parse(readFileSync(walletPath, "utf-8"));
|
|
31
|
+
expect(content.mnemonic).toBe(wallet.mnemonic);
|
|
32
|
+
expect(content.address).toBe(wallet.address);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("creates .aixyz/.gitignore ignoring everything", () => {
|
|
36
|
+
const gitignorePath = join(testDir, ".aixyz", ".gitignore");
|
|
37
|
+
expect(existsSync(gitignorePath)).toBe(true);
|
|
38
|
+
expect(readFileSync(gitignorePath, "utf-8").trim()).toBe("*");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("creates .aixyz/.aiignore ignoring everything", () => {
|
|
42
|
+
const aiignorePath = join(testDir, ".aixyz", ".aiignore");
|
|
43
|
+
expect(existsSync(aiignorePath)).toBe(true);
|
|
44
|
+
expect(readFileSync(aiignorePath, "utf-8").trim()).toBe("*");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("mnemonic is 12 words", () => {
|
|
48
|
+
const wallet = generateLocalWallet(testDir);
|
|
49
|
+
expect(wallet.mnemonic.split(" ")).toHaveLength(12);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("hasLocalWallet", () => {
|
|
54
|
+
test("returns true when wallet exists", () => {
|
|
55
|
+
generateLocalWallet(testDir);
|
|
56
|
+
expect(hasLocalWallet(testDir)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("returns false when wallet does not exist", () => {
|
|
60
|
+
const emptyDir = join(tmpdir(), "aixyz-cli-no-wallet-test");
|
|
61
|
+
mkdirSync(emptyDir, { recursive: true });
|
|
62
|
+
try {
|
|
63
|
+
expect(hasLocalWallet(emptyDir)).toBe(false);
|
|
64
|
+
} finally {
|
|
65
|
+
rmSync(emptyDir, { recursive: true, force: true });
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("readLocalWallet", () => {
|
|
71
|
+
test("reads wallet from .aixyz/wallet.json", () => {
|
|
72
|
+
const wallet = generateLocalWallet(testDir);
|
|
73
|
+
const read = readLocalWallet(testDir);
|
|
74
|
+
expect(read.mnemonic).toBe(wallet.mnemonic);
|
|
75
|
+
expect(read.address).toBe(wallet.address);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test("throws when wallet does not exist", () => {
|
|
79
|
+
const emptyDir = join(tmpdir(), "aixyz-cli-no-wallet-read-test");
|
|
80
|
+
mkdirSync(emptyDir, { recursive: true });
|
|
81
|
+
try {
|
|
82
|
+
expect(() => readLocalWallet(emptyDir)).toThrow("aixyz wallet generate");
|
|
83
|
+
} finally {
|
|
84
|
+
rmSync(emptyDir, { recursive: true, force: true });
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("getLocalWalletPrivateKey", () => {
|
|
90
|
+
test("returns a valid private key hex string", () => {
|
|
91
|
+
generateLocalWallet(testDir);
|
|
92
|
+
const key = getLocalWalletPrivateKey(testDir);
|
|
93
|
+
expect(key).toMatch(/^0x[0-9a-fA-F]{64}$/);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("returns deterministic private key for same mnemonic", () => {
|
|
97
|
+
generateLocalWallet(testDir);
|
|
98
|
+
const key1 = getLocalWalletPrivateKey(testDir);
|
|
99
|
+
const key2 = getLocalWalletPrivateKey(testDir);
|
|
100
|
+
expect(key1).toBe(key2);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { resolve } from "node:path";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { generateMnemonic, mnemonicToAccount, english } from "viem/accounts";
|
|
4
|
+
import { bytesToHex } from "viem";
|
|
5
|
+
|
|
6
|
+
const WALLET_DIR = ".aixyz";
|
|
7
|
+
const WALLET_FILE = "wallet.json";
|
|
8
|
+
const GITIGNORE_CONTENT = "*\n";
|
|
9
|
+
const AIIGNORE_CONTENT = "*\n";
|
|
10
|
+
|
|
11
|
+
export interface LocalWallet {
|
|
12
|
+
mnemonic: string;
|
|
13
|
+
address: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getLocalWalletPath(cwd: string = process.cwd()): string {
|
|
17
|
+
return resolve(cwd, WALLET_DIR, WALLET_FILE);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function hasLocalWallet(cwd?: string): boolean {
|
|
21
|
+
return existsSync(getLocalWalletPath(cwd));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function readLocalWallet(cwd?: string): LocalWallet {
|
|
25
|
+
const path = getLocalWalletPath(cwd);
|
|
26
|
+
if (!existsSync(path)) {
|
|
27
|
+
throw new Error(`No local wallet found at ${path}. Run \`aixyz wallet generate\` to create one.`);
|
|
28
|
+
}
|
|
29
|
+
const content = readFileSync(path, "utf-8");
|
|
30
|
+
return JSON.parse(content) as LocalWallet;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function generateLocalWallet(cwd: string = process.cwd()): LocalWallet {
|
|
34
|
+
const dir = resolve(cwd, WALLET_DIR);
|
|
35
|
+
mkdirSync(dir, { recursive: true });
|
|
36
|
+
|
|
37
|
+
// Write .gitignore and .aiignore to protect wallet.json
|
|
38
|
+
writeFileSync(resolve(dir, ".gitignore"), GITIGNORE_CONTENT, "utf-8");
|
|
39
|
+
writeFileSync(resolve(dir, ".aiignore"), AIIGNORE_CONTENT, "utf-8");
|
|
40
|
+
|
|
41
|
+
const mnemonic = generateMnemonic(english);
|
|
42
|
+
const account = mnemonicToAccount(mnemonic);
|
|
43
|
+
|
|
44
|
+
const wallet: LocalWallet = {
|
|
45
|
+
mnemonic,
|
|
46
|
+
address: account.address,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
writeFileSync(resolve(dir, WALLET_FILE), JSON.stringify(wallet, null, 2) + "\n", "utf-8");
|
|
50
|
+
|
|
51
|
+
return wallet;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function getLocalWalletPrivateKey(cwd?: string): `0x${string}` {
|
|
55
|
+
const { mnemonic } = readLocalWallet(cwd);
|
|
56
|
+
const account = mnemonicToAccount(mnemonic);
|
|
57
|
+
const hdKey = account.getHdKey();
|
|
58
|
+
if (!hdKey.privKeyBytes) {
|
|
59
|
+
throw new Error("Failed to derive private key from mnemonic.");
|
|
60
|
+
}
|
|
61
|
+
return bytesToHex(hdKey.privKeyBytes);
|
|
62
|
+
}
|