Skip to content

Commit 4a74af8

Browse files
committed
add client CLI example
1 parent d066f58 commit 4a74af8

File tree

10 files changed

+566
-125
lines changed

10 files changed

+566
-125
lines changed

typescript-sdk/apps/cli-client/src/index.ts

Lines changed: 0 additions & 5 deletions
This file was deleted.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
dist/
2+
mastra.db*
File renamed without changes.

typescript-sdk/apps/cli-client/package.json renamed to typescript-sdk/apps/client-cli/package.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"name": "cli-client",
2+
"name": "client-cli",
33
"version": "0.1.0",
44
"private": true,
55
"scripts": {
@@ -11,7 +11,15 @@
1111
"dependencies": {
1212
"@ag-ui/client": "workspace:*",
1313
"@ag-ui/core": "workspace:*",
14-
"@ag-ui/mastra": "workspace:*"
14+
"@ag-ui/mastra": "workspace:*",
15+
"@ai-sdk/openai": "^1.3.22",
16+
"@mastra/client-js": "^0.10.9",
17+
"@mastra/core": "^0.10.10",
18+
"@mastra/libsql": "^0.11.0",
19+
"@mastra/loggers": "^0.10.3",
20+
"@mastra/memory": "^0.11.1",
21+
"open": "^10.1.2",
22+
"zod": "^3.22.4"
1523
},
1624
"devDependencies": {
1725
"@types/node": "^20",
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { openai } from "@ai-sdk/openai";
2+
import { Agent } from "@mastra/core/agent";
3+
import { MastraAgent } from "@ag-ui/mastra";
4+
import { Memory } from "@mastra/memory";
5+
import { LibSQLStore } from "@mastra/libsql";
6+
import { weatherTool } from "./tools/weather.tool";
7+
import { browserTool } from "./tools/browser.tool";
8+
import { z } from "zod";
9+
10+
export const agent = new MastraAgent({
11+
// @ts-ignore
12+
agent: new Agent({
13+
name: "AG-UI Agent",
14+
instructions: `
15+
You are a helpful assistant that runs a CLI application.
16+
17+
When helping users get weather details for specific locations, respond:
18+
- Always ask for a location if none is provided.
19+
- If the location name isn’t in English, please translate it
20+
- If giving a location with multiple parts (e.g. "New York, NY"), use the most relevant part (e.g. "New York")
21+
- Include relevant details like humidity, wind conditions, and precipitation
22+
- Keep responses concise but informative
23+
24+
Use the weatherTool to fetch current weather data.
25+
26+
When helping users browse the web, always use a full URL, for example: "https://www.google.com"
27+
Use the browserTool to browse the web.
28+
29+
`,
30+
model: openai("gpt-4o-mini"),
31+
tools: { weatherTool, browserTool },
32+
memory: new Memory({
33+
storage: new LibSQLStore({
34+
url: "file:./mastra.db",
35+
}),
36+
}),
37+
}),
38+
threadId: "1",
39+
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import * as readline from "readline";
2+
import { agent } from "./agent";
3+
import { randomUUID } from "node:crypto";
4+
5+
const rl = readline.createInterface({
6+
input: process.stdin,
7+
output: process.stdout,
8+
});
9+
10+
async function chatLoop() {
11+
console.log("🤖 AG-UI chat started! Type your messages and press Enter. Press Ctrl+D to quit.\n");
12+
13+
return new Promise<void>((resolve) => {
14+
const promptUser = () => {
15+
rl.question("> ", async (input) => {
16+
if (input.trim() === "") {
17+
promptUser();
18+
return;
19+
}
20+
console.log("");
21+
22+
rl.pause();
23+
24+
agent.messages.push({
25+
id: randomUUID(),
26+
role: "user",
27+
content: input.trim(),
28+
});
29+
30+
try {
31+
await agent.runAgent(
32+
{},
33+
{
34+
onTextMessageStartEvent() {
35+
process.stdout.write("🤖 AG-UI assistant: ");
36+
},
37+
onTextMessageContentEvent({ event }) {
38+
process.stdout.write(event.delta);
39+
},
40+
onTextMessageEndEvent() {
41+
console.log("\n");
42+
},
43+
onToolCallStartEvent({ event }) {
44+
console.log("🔧 Tool call:", event.toolCallName);
45+
},
46+
onToolCallArgsEvent({ event }) {
47+
process.stdout.write(event.delta);
48+
},
49+
onToolCallEndEvent() {
50+
console.log("\n");
51+
},
52+
onToolCallResultEvent({ event }) {
53+
if (event.content) {
54+
console.log("🔍 Tool call result:", event.content);
55+
}
56+
},
57+
},
58+
);
59+
} catch (error) {
60+
console.error("❌ Error running agent:", error);
61+
}
62+
63+
rl.resume();
64+
promptUser();
65+
});
66+
};
67+
68+
rl.on("close", () => {
69+
console.log("\n👋 Goodbye!");
70+
resolve();
71+
});
72+
73+
promptUser();
74+
});
75+
}
76+
77+
async function main() {
78+
await chatLoop();
79+
}
80+
81+
main().catch(console.error);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { createTool } from "@mastra/core/tools";
2+
import { z } from "zod";
3+
import open from "open";
4+
5+
export const browserTool = createTool({
6+
id: "browser",
7+
description: "Browse the web",
8+
inputSchema: z.object({
9+
url: z.string().describe("URL to browse"),
10+
}),
11+
outputSchema: z.string(),
12+
execute: async ({ context }) => {
13+
open(context.url);
14+
return `Browsed ${context.url}`;
15+
},
16+
});
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import { createTool } from "@mastra/core/tools";
2+
import { z } from "zod";
3+
4+
interface GeocodingResponse {
5+
results: {
6+
latitude: number;
7+
longitude: number;
8+
name: string;
9+
}[];
10+
}
11+
interface WeatherResponse {
12+
current: {
13+
time: string;
14+
temperature_2m: number;
15+
apparent_temperature: number;
16+
relative_humidity_2m: number;
17+
wind_speed_10m: number;
18+
wind_gusts_10m: number;
19+
weather_code: number;
20+
};
21+
}
22+
23+
export const weatherTool = createTool({
24+
id: "get-weather",
25+
description: "Get current weather for a location",
26+
inputSchema: z.object({
27+
location: z.string().describe("City name"),
28+
}),
29+
outputSchema: z.object({
30+
temperature: z.number(),
31+
feelsLike: z.number(),
32+
humidity: z.number(),
33+
windSpeed: z.number(),
34+
windGust: z.number(),
35+
conditions: z.string(),
36+
location: z.string(),
37+
}),
38+
execute: async ({ context }) => {
39+
return await getWeather(context.location);
40+
},
41+
});
42+
43+
const getWeather = async (location: string) => {
44+
const geocodingUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(location)}&count=1`;
45+
const geocodingResponse = await fetch(geocodingUrl);
46+
const geocodingData = (await geocodingResponse.json()) as GeocodingResponse;
47+
48+
if (!geocodingData.results?.[0]) {
49+
throw new Error(`Location '${location}' not found`);
50+
}
51+
52+
const { latitude, longitude, name } = geocodingData.results[0];
53+
54+
const weatherUrl = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,wind_gusts_10m,weather_code`;
55+
56+
const response = await fetch(weatherUrl);
57+
const data = (await response.json()) as WeatherResponse;
58+
59+
return {
60+
temperature: data.current.temperature_2m,
61+
feelsLike: data.current.apparent_temperature,
62+
humidity: data.current.relative_humidity_2m,
63+
windSpeed: data.current.wind_speed_10m,
64+
windGust: data.current.wind_gusts_10m,
65+
conditions: getWeatherCondition(data.current.weather_code),
66+
location: name,
67+
};
68+
};
69+
70+
function getWeatherCondition(code: number): string {
71+
const conditions: Record<number, string> = {
72+
0: "Clear sky",
73+
1: "Mainly clear",
74+
2: "Partly cloudy",
75+
3: "Overcast",
76+
45: "Foggy",
77+
48: "Depositing rime fog",
78+
51: "Light drizzle",
79+
53: "Moderate drizzle",
80+
55: "Dense drizzle",
81+
56: "Light freezing drizzle",
82+
57: "Dense freezing drizzle",
83+
61: "Slight rain",
84+
63: "Moderate rain",
85+
65: "Heavy rain",
86+
66: "Light freezing rain",
87+
67: "Heavy freezing rain",
88+
71: "Slight snow fall",
89+
73: "Moderate snow fall",
90+
75: "Heavy snow fall",
91+
77: "Snow grains",
92+
80: "Slight rain showers",
93+
81: "Moderate rain showers",
94+
82: "Violent rain showers",
95+
85: "Slight snow showers",
96+
86: "Heavy snow showers",
97+
95: "Thunderstorm",
98+
96: "Thunderstorm with slight hail",
99+
99: "Thunderstorm with heavy hail",
100+
};
101+
return conditions[code] || "Unknown";
102+
}
File renamed without changes.

0 commit comments

Comments
 (0)