@surph_ai/cli 0.0.7 → 0.0.9
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 +16 -0
- package/scaffold/tool/examples/README.md +1 -0
- package/scaffold/tool/examples/dealfinder/index.ts +45 -0
- package/scaffold/tool/examples/dealfinder/manifest.json +21 -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/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,22 @@ 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
|
+
shell.mkdir('-p', 'examples/dealfinder')
|
|
83
|
+
shell.cp('-R', path.join(__dirname, `tool/examples/dealfinder/index.ts`), 'examples/dealfinder/index.ts')
|
|
84
|
+
shell.cp('-R', path.join(__dirname, `tool/examples/dealfinder/manifest.json`), 'examples/dealfinder/manifest.json')
|
|
85
|
+
|
|
86
|
+
|
|
71
87
|
utils.write('manifest-example.json', JSON.stringify({
|
|
72
88
|
name: 'youtube-transcript',
|
|
73
89
|
slug: 'y2script',
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
README
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ToolHandler } from '@surph_ai/sdk';
|
|
2
|
+
import { surph } from '@surph_ai/sdk';
|
|
3
|
+
|
|
4
|
+
// your custom tool args goes here
|
|
5
|
+
type ToolArgs = {
|
|
6
|
+
preferences: string[]
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const handler: ToolHandler<ToolArgs> = async ({ args, user, context }: Record<string, any>) => {
|
|
10
|
+
try {
|
|
11
|
+
if (!args) {
|
|
12
|
+
throw new Error('Args required.')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (!context) {
|
|
16
|
+
throw new Error('Context required.')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const { preferences } = args; // string[]
|
|
20
|
+
const searchQuery = preferences.join(', ')
|
|
21
|
+
|
|
22
|
+
const searchResults = await surph.search(searchQuery, context)
|
|
23
|
+
|
|
24
|
+
const promptResponse = await surph.prompt({
|
|
25
|
+
instructions: `searchResults is a JSON array data feed of events and announcements. Your job is to sort each item into one of three categories: offering free food, free event, or free giveaway (such as raffles, prizes, etc.).`,
|
|
26
|
+
prompt: `Given this list of items, separate them into free food, free event, or free giveaway:\n\n${JSON.stringify(searchResults)}`,
|
|
27
|
+
output: {
|
|
28
|
+
name: 'deal_search_results',
|
|
29
|
+
description: 'The deal search results',
|
|
30
|
+
fields: [
|
|
31
|
+
{ name: 'food', type: 'array', description: 'a list of events and announcements with free food.' },
|
|
32
|
+
{ name: 'event', type: 'array', description: 'a list of events that are free to attend.' },
|
|
33
|
+
{ name: 'items', type: 'array', description: 'a list of events and announcements with free items.' }
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return promptResponse
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw error
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default handler;
|
|
45
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"deal-finder-q9je7jou": {
|
|
3
|
+
"name": "deal-finder",
|
|
4
|
+
"slug": "deal-finder-q9je7jou",
|
|
5
|
+
"type": "custom",
|
|
6
|
+
"description": "Search the user's active Surph context for local deals, discounts, promotions, specials, sales, and related opportunities across food, drinks, restaurants, shops, events, entertainment, and other businesses or venues covered by the context. Use whenever the user asks to find, compare, filter, or recommend current or upcoming local deals—including requests for happy hours, meal specials, event discounts, retail sales, coupons, or promotions. Prefer this instead of webSearch when the requested local businesses, venues, events, or listings are represented in the active Surph context.",
|
|
7
|
+
"parameters": {
|
|
8
|
+
"type": "object",
|
|
9
|
+
"properties": {
|
|
10
|
+
"preferences": {
|
|
11
|
+
"type": "array",
|
|
12
|
+
"description": "Optional free-form preferences, categories, constraints, or deal criteria to prioritize, such as food, drinks, events, retail sales, happy hour, nearby neighborhoods, budget, dates, or specific businesses.",
|
|
13
|
+
"items": {
|
|
14
|
+
"type": "string"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"required": []
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -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)
|