@surph_ai/cli 0.0.6 → 0.0.8
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/cli.js +15 -0
- package/package.json +1 -1
- package/scaffold/index.js +13 -0
- package/scaffold/tool/examples/README.md +1 -0
- package/scaffold/tool/examples/population/index.ts +31 -0
- package/scaffold/tool/examples/population/manifest.json +20 -0
- package/scaffold/tool/examples/weather/index.ts +127 -0
- package/scaffold/tool/examples/weather/manifest.json +20 -0
- package/toolserver/ensure-chat.js +12 -7
- package/toolserver/routes/tool.js +7 -6
package/cli.js
CHANGED
|
@@ -183,6 +183,8 @@ program.command('manifest')
|
|
|
183
183
|
try {
|
|
184
184
|
const manifestJson = require('../../../manifest.json')
|
|
185
185
|
const slug = Object.keys(manifestJson)[0]
|
|
186
|
+
// console.log('TEST: ' + JSON.stringify(manifestJson))
|
|
187
|
+
// console.log('FIRST KEY: ' + slug)
|
|
186
188
|
const manifest = manifestJson[slug]
|
|
187
189
|
if (!manifest) {
|
|
188
190
|
throw new Error('manifest.json not found')
|
|
@@ -218,6 +220,8 @@ program.command('manifest')
|
|
|
218
220
|
})
|
|
219
221
|
|
|
220
222
|
const { response } = await resp.json()
|
|
223
|
+
console.log('SLUG: ' + slug)
|
|
224
|
+
console.log('MANIFEST: ' + JSON.stringify(response))
|
|
221
225
|
utils.write('manifest.json', JSON.stringify({[slug]: response}, null, 2) + '\n')
|
|
222
226
|
console.log(colors.white('\n\nManifest updated. Check manifest.json for changes.\n'))
|
|
223
227
|
} catch (error) {
|
|
@@ -437,6 +441,7 @@ program.command('deploy')
|
|
|
437
441
|
}
|
|
438
442
|
*/
|
|
439
443
|
|
|
444
|
+
/*
|
|
440
445
|
const { SURPH_TOOL = null } = dotenv.parse(utils.readFile('.env') ?? '')
|
|
441
446
|
|
|
442
447
|
if (SURPH_TOOL === null) {
|
|
@@ -444,6 +449,16 @@ program.command('deploy')
|
|
|
444
449
|
}
|
|
445
450
|
|
|
446
451
|
await deploy.tool({ SURPH_TOOL_SLUG: SURPH_TOOL })
|
|
452
|
+
*/
|
|
453
|
+
|
|
454
|
+
const manifest = require('../../../manifest.json')
|
|
455
|
+
if (!manifest) {
|
|
456
|
+
throw new Error('manifest.json not found')
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
const keys = Object.keys(manifest);
|
|
460
|
+
const SURPH_TOOL = keys[0]
|
|
461
|
+
await deploy.tool({ SURPH_TOOL_SLUG: SURPH_TOOL })
|
|
447
462
|
} catch (error) {
|
|
448
463
|
utils.printError(error)
|
|
449
464
|
program.error(`Unexpected error: ${error}`, ERRORS.UNSPECIFIED)
|
package/package.json
CHANGED
package/scaffold/index.js
CHANGED
|
@@ -68,6 +68,19 @@ module.exports = (name, type = 'app') => {
|
|
|
68
68
|
shell.mkdir('-p', 'tools')
|
|
69
69
|
shell.cp('-R', path.join(__dirname, `tool/tools/index.ts`), 'tools/index.ts')
|
|
70
70
|
|
|
71
|
+
shell.mkdir('-p', 'examples')
|
|
72
|
+
shell.cp('-R', path.join(__dirname, `tool/examples/README.md`), 'examples/README.md')
|
|
73
|
+
|
|
74
|
+
shell.mkdir('-p', 'examples/population')
|
|
75
|
+
shell.cp('-R', path.join(__dirname, `tool/examples/population/index.ts`), 'examples/population/index.ts')
|
|
76
|
+
shell.cp('-R', path.join(__dirname, `tool/examples/population/manifest.json`), 'examples/population/manifest.json')
|
|
77
|
+
|
|
78
|
+
shell.mkdir('-p', 'examples/weather')
|
|
79
|
+
shell.cp('-R', path.join(__dirname, `tool/examples/weather/index.ts`), 'examples/weather/index.ts')
|
|
80
|
+
shell.cp('-R', path.join(__dirname, `tool/examples/weather/manifest.json`), 'examples/weather/manifest.json')
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
|
|
71
84
|
utils.write('manifest-example.json', JSON.stringify({
|
|
72
85
|
name: 'youtube-transcript',
|
|
73
86
|
slug: 'y2script',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
README
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ToolHandler } from '@surph_ai/sdk';
|
|
2
|
+
|
|
3
|
+
const cities: Record<string, number> = {
|
|
4
|
+
'chicago': 3000000,
|
|
5
|
+
'new york': 8500000,
|
|
6
|
+
'los angeles': 4000000
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// your custom tool args goes here
|
|
10
|
+
type ToolArgs = {
|
|
11
|
+
city: string
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const handler: ToolHandler<ToolArgs> = async ({ args, user, context }: Record<string, any>) => {
|
|
15
|
+
try {
|
|
16
|
+
// your custom tool code goes here
|
|
17
|
+
const { city } = args
|
|
18
|
+
|
|
19
|
+
const population: number = cities[city.toLowerCase()]
|
|
20
|
+
if (!population) {
|
|
21
|
+
throw new Error(`${city} population not found.`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return population
|
|
25
|
+
} catch (error) {
|
|
26
|
+
throw error
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default handler;
|
|
31
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"city-population-0d6v8oad": {
|
|
3
|
+
"name": "city-population",
|
|
4
|
+
"slug": "city-population-0d6v8oad",
|
|
5
|
+
"type": "custom",
|
|
6
|
+
"description": "Return the population of a specified city. Use this whenever the user asks for a city's population, including questions such as \"What is the population of Tokyo?\" or requests that provide a city name directly. The city input may include a country, state, or other region to disambiguate cities with the same name.",
|
|
7
|
+
"parameters": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"city": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "The city whose population is requested, provided as a city name; it may optionally include a state, province, country, or other geographic qualifier, such as \"Paris, France\" or \"Springfield, Illinois\"."
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"required": [
|
|
16
|
+
"city"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import type { ToolHandler } from '@surph_ai/sdk';
|
|
2
|
+
|
|
3
|
+
// Define interfaces for API responses
|
|
4
|
+
interface GeocodingResult {
|
|
5
|
+
id: number;
|
|
6
|
+
name: string;
|
|
7
|
+
latitude: number;
|
|
8
|
+
longitude: number;
|
|
9
|
+
country?: string;
|
|
10
|
+
country_code?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface GeocodingResponse {
|
|
14
|
+
results?: GeocodingResult[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface CurrentWeather {
|
|
18
|
+
time: string;
|
|
19
|
+
interval: number;
|
|
20
|
+
temperature_2m: number;
|
|
21
|
+
relative_humidity_2m: number;
|
|
22
|
+
apparent_temperature: number;
|
|
23
|
+
precipitation: number;
|
|
24
|
+
weather_code: number;
|
|
25
|
+
wind_speed_10m: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface CurrentUnits {
|
|
29
|
+
temperature_2m: string;
|
|
30
|
+
relative_humidity_2m: string;
|
|
31
|
+
apparent_temperature: string;
|
|
32
|
+
precipitation: string;
|
|
33
|
+
wind_speed_10m: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface WeatherResponse {
|
|
37
|
+
latitude: number;
|
|
38
|
+
longitude: number;
|
|
39
|
+
current?: CurrentWeather;
|
|
40
|
+
current_units?: CurrentUnits;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Fetches current weather conditions for a specified city name.
|
|
45
|
+
*/
|
|
46
|
+
async function getCityWeather(cityName: string): Promise<any> {
|
|
47
|
+
try {
|
|
48
|
+
// Step 1: Get coordinates for the city using the Geocoding API
|
|
49
|
+
const geoUrl = `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(cityName)}&count=1`;
|
|
50
|
+
const geoRes = await fetch(geoUrl);
|
|
51
|
+
|
|
52
|
+
if (!geoRes.ok) {
|
|
53
|
+
throw new Error(`Geocoding HTTP error! Status: ${geoRes.status}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const geoData = (await geoRes.json()) as GeocodingResponse;
|
|
57
|
+
|
|
58
|
+
if (!geoData.results || geoData.results.length === 0) {
|
|
59
|
+
console.log(`City "${cityName}" not found.`);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const location = geoData.results[0];
|
|
64
|
+
const { latitude, longitude, name, country = '' } = location;
|
|
65
|
+
|
|
66
|
+
console.log(`Found: ${name}, ${country} (Lat: ${latitude}, Lon: ${longitude})`);
|
|
67
|
+
|
|
68
|
+
// Step 2: Get weather data using the retrieved coordinates
|
|
69
|
+
const weatherParams = new URLSearchParams({
|
|
70
|
+
latitude: latitude.toString(),
|
|
71
|
+
longitude: longitude.toString(),
|
|
72
|
+
current: 'temperature_2m,relative_humidity_2m,apparent_temperature,precipitation,weather_code,wind_speed_10m'
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const weatherUrl = `https://api.open-meteo.com/v1/forecast?${weatherParams.toString()}`;
|
|
76
|
+
const weatherRes = await fetch(weatherUrl);
|
|
77
|
+
|
|
78
|
+
if (!weatherRes.ok) {
|
|
79
|
+
throw new Error(`Weather HTTP error! Status: ${weatherRes.status}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const weatherData = (await weatherRes.json()) as WeatherResponse;
|
|
83
|
+
const { current, current_units: units } = weatherData;
|
|
84
|
+
|
|
85
|
+
if (!current || !units) {
|
|
86
|
+
console.log('No current weather data available.');
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Display the formatted results
|
|
91
|
+
console.log(`Current Temperature: ${current.temperature_2m} ${units.temperature_2m}`);
|
|
92
|
+
console.log(`Feels Like: ${current.apparent_temperature} ${units.apparent_temperature}`);
|
|
93
|
+
console.log(`Humidity: ${current.relative_humidity_2m} ${units.relative_humidity_2m}`);
|
|
94
|
+
console.log(`Wind Speed: ${current.wind_speed_10m} ${units.wind_speed_10m}`);
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
temperature: `${current.temperature_2m} ${units.temperature_2m}`,
|
|
98
|
+
humidity: `${current.relative_humidity_2m} ${units.relative_humidity_2m}`,
|
|
99
|
+
wind: `${current.wind_speed_10m} ${units.wind_speed_10m}`,
|
|
100
|
+
}
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('Failed to fetch weather data:', error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
// your custom tool args goes here
|
|
108
|
+
type ToolArgs = {
|
|
109
|
+
location: string
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const handler: ToolHandler<ToolArgs> = async ({ args, user, context }: Record<string, any>) => {
|
|
113
|
+
try {
|
|
114
|
+
const { location } = args;
|
|
115
|
+
if (!location) {
|
|
116
|
+
throw new Error(`location argument required.`)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const weahter = await getCityWeather(location)
|
|
120
|
+
return weahter
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw error
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
export default handler;
|
|
127
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"weather-forecast-q9je7jou": {
|
|
3
|
+
"name": "weather-forecast",
|
|
4
|
+
"slug": "weather-forecast-q9je7jou",
|
|
5
|
+
"type": "custom",
|
|
6
|
+
"description": "Return current weather conditions and the forecast for a specified location. Use this whenever the user asks about the weather, temperature, conditions, rain, snow, wind, or upcoming forecast for a place, including when the location is provided as a city, region, country, postal code, address, or coordinates.",
|
|
7
|
+
"parameters": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"location": {
|
|
11
|
+
"type": "string",
|
|
12
|
+
"description": "The location to retrieve weather for. Accept a city or town, region/state and country, postal or ZIP code, street address, landmark, or latitude/longitude coordinates."
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"required": [
|
|
16
|
+
"location"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -8,11 +8,11 @@ const CLI_NODE_MODULES = path.join(__dirname, '..', 'node_modules')
|
|
|
8
8
|
const BUNDLED_FALLBACK = path.join(__dirname, 'chat.js')
|
|
9
9
|
|
|
10
10
|
const cachePaths = () => {
|
|
11
|
-
const cacheDir = path.join(process.cwd(), '
|
|
11
|
+
const cacheDir = path.join(process.cwd(), 'node_modules', '@surph_ai', 'cache')
|
|
12
12
|
return {
|
|
13
13
|
cacheDir,
|
|
14
|
-
cacheFile: path.join(cacheDir, 'chat.
|
|
15
|
-
cacheMeta: path.join(cacheDir, '
|
|
14
|
+
cacheFile: path.join(cacheDir, 'chat.js'),
|
|
15
|
+
cacheMeta: path.join(cacheDir, 'manifest.json')
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -96,11 +96,16 @@ const downloadChat = async (manifest) => {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
const { cacheDir, cacheFile, cacheMeta } = cachePaths()
|
|
99
|
-
fs.mkdirSync(cacheDir, { recursive: true
|
|
99
|
+
fs.mkdirSync(cacheDir, { recursive: true })
|
|
100
|
+
fs.writeFileSync(path.join(cacheDir, 'package.json'), JSON.stringify({
|
|
101
|
+
name: '@surph_ai/cache',
|
|
102
|
+
private: true,
|
|
103
|
+
type: 'commonjs'
|
|
104
|
+
}, null, 2) + '\n')
|
|
100
105
|
const tmp = `${cacheFile}.tmp`
|
|
101
|
-
fs.writeFileSync(tmp, buf
|
|
106
|
+
fs.writeFileSync(tmp, buf)
|
|
102
107
|
fs.renameSync(tmp, cacheFile)
|
|
103
|
-
fs.writeFileSync(cacheMeta, JSON.stringify(manifest, null, 2) + '\n'
|
|
108
|
+
fs.writeFileSync(cacheMeta, JSON.stringify(manifest, null, 2) + '\n')
|
|
104
109
|
}
|
|
105
110
|
|
|
106
111
|
const fallbackPath = () => {
|
|
@@ -134,7 +139,7 @@ const ensureChat = async ({ cliVersion, log } = {}) => {
|
|
|
134
139
|
return cacheFile
|
|
135
140
|
}
|
|
136
141
|
|
|
137
|
-
if (log) log('Downloading latest chat server...')
|
|
142
|
+
// if (log) log('Downloading latest chat server...')
|
|
138
143
|
try {
|
|
139
144
|
await downloadChat(manifest)
|
|
140
145
|
return cacheFile
|
|
@@ -70,9 +70,10 @@ router.get('/', async (req, res, next) => {
|
|
|
70
70
|
|
|
71
71
|
router.get('/test', async (req, res, next) => {
|
|
72
72
|
try {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
73
|
+
// this should be enforced at the tool level, not here:
|
|
74
|
+
// if (Object.keys(req.query).length === 0) {
|
|
75
|
+
// throw new Error('No arguments provided.')
|
|
76
|
+
// }
|
|
76
77
|
|
|
77
78
|
const context = req.query._context || null
|
|
78
79
|
const user = req.query._user || null
|
|
@@ -94,9 +95,9 @@ router.get('/test', async (req, res, next) => {
|
|
|
94
95
|
router.post('/custom', async (req, res, next) => {
|
|
95
96
|
try {
|
|
96
97
|
const { name, args, tool, user = null, context = null } = req.body;
|
|
97
|
-
if (process.env.SURPH_TOOL !== tool.slug) {
|
|
98
|
-
|
|
99
|
-
}
|
|
98
|
+
// if (process.env.SURPH_TOOL !== tool.slug) {
|
|
99
|
+
// throw new Error(`Invalid tool ${tool.slug}. Check .env file.`)
|
|
100
|
+
// }
|
|
100
101
|
|
|
101
102
|
const toolRunner = await loadTool()
|
|
102
103
|
const response = await handler({ args, context, user }, toolRunner)
|