@agentoctopus/cli 0.4.2 → 0.4.4
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/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +35 -0
- package/dist/config.js.map +1 -0
- package/dist/index.js +87 -3
- package/dist/index.js.map +1 -1
- package/dist/onboard.d.ts.map +1 -1
- package/dist/onboard.js +122 -15
- package/dist/onboard.js.map +1 -1
- package/dist/skill-create.d.ts +3 -0
- package/dist/skill-create.d.ts.map +1 -0
- package/dist/skill-create.js +220 -0
- package/dist/skill-create.js.map +1 -0
- package/package.json +5 -4
- package/skills/ip-lookup/SKILL.md +25 -0
- package/skills/ip-lookup/scripts/invoke.js +56 -0
- package/skills/translation/SKILL.md +23 -0
- package/skills/translation/scripts/invoke.js +63 -0
- package/skills/weather/SKILL.md +23 -0
- package/skills/weather/scripts/invoke.js +58 -0
- package/skills/x-search/SKILL.md +26 -0
- package/skills/x-search/scripts/invoke.js +57 -0
- package/skills/x-search/scripts/search.py +282 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { input, select, confirm } from '@inquirer/prompts';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { loadOctopusConfig } from './config.js';
|
|
6
|
+
// ── Template scaffold ─────────────────────────────────────────────────────────
|
|
7
|
+
const SKILL_MD_TEMPLATE = `---
|
|
8
|
+
name: my-skill
|
|
9
|
+
description: Describe what this skill does.
|
|
10
|
+
tags: [example]
|
|
11
|
+
version: 1.0.0
|
|
12
|
+
adapter: subprocess
|
|
13
|
+
hosting: local
|
|
14
|
+
auth: none
|
|
15
|
+
input_schema:
|
|
16
|
+
query: string
|
|
17
|
+
output_schema:
|
|
18
|
+
result: string
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Instructions
|
|
22
|
+
|
|
23
|
+
Describe how the skill should behave. The router uses this text to decide
|
|
24
|
+
when to invoke the skill.
|
|
25
|
+
`;
|
|
26
|
+
const INVOKE_JS_TEMPLATE = `#!/usr/bin/env node
|
|
27
|
+
// TODO: implement your skill logic here
|
|
28
|
+
const input = JSON.parse(process.env.OCTOPUS_INPUT || '{}');
|
|
29
|
+
const { query } = input;
|
|
30
|
+
|
|
31
|
+
// Example: fetch from an external API
|
|
32
|
+
// const res = await fetch(\`https://api.example.com?q=\${query}\`);
|
|
33
|
+
// const data = await res.json();
|
|
34
|
+
|
|
35
|
+
console.log(JSON.stringify({ result: 'TODO' }));
|
|
36
|
+
`;
|
|
37
|
+
// ── LLM prompt ────────────────────────────────────────────────────────────────
|
|
38
|
+
function buildLLMSystemPrompt() {
|
|
39
|
+
return `You are generating a SKILL.md file for AgentOctopus.
|
|
40
|
+
|
|
41
|
+
A SKILL.md file has YAML frontmatter followed by a markdown instructions block.
|
|
42
|
+
Return ONLY the SKILL.md content, no explanation, no markdown code fences.`;
|
|
43
|
+
}
|
|
44
|
+
function buildLLMUserPrompt(answers) {
|
|
45
|
+
const apiSection = answers.type === 'api'
|
|
46
|
+
? `The skill calls an external API.
|
|
47
|
+
Endpoint: ${answers.endpoint || 'not specified'}
|
|
48
|
+
Auth: ${answers.authType || 'none'}
|
|
49
|
+
Sample input/output: ${answers.sampleIO || 'not provided'}`
|
|
50
|
+
: `The skill is LLM-only (no external API calls).
|
|
51
|
+
Constraints/tone: ${answers.constraints || 'none specified'}`;
|
|
52
|
+
return `User description: ${answers.description}
|
|
53
|
+
${apiSection}
|
|
54
|
+
|
|
55
|
+
Generate a valid SKILL.md with this exact structure:
|
|
56
|
+
---
|
|
57
|
+
name: <slug, lowercase, hyphens>
|
|
58
|
+
description: <one sentence, what it does and when to use it>
|
|
59
|
+
tags: [<3-5 relevant tags>]
|
|
60
|
+
version: 1.0.0
|
|
61
|
+
adapter: ${answers.type === 'api' ? 'subprocess' : 'http'}
|
|
62
|
+
hosting: local
|
|
63
|
+
auth: ${answers.authType === 'none' || answers.type === 'llm' ? 'none' : answers.authType || 'none'}
|
|
64
|
+
llm_powered: ${answers.type === 'llm' ? 'true' : 'false'}
|
|
65
|
+
input_schema:
|
|
66
|
+
query: string
|
|
67
|
+
output_schema:
|
|
68
|
+
result: string
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Instructions
|
|
72
|
+
|
|
73
|
+
<2-4 sentences describing the behavior, how to extract input, and what to return>`;
|
|
74
|
+
}
|
|
75
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
76
|
+
function resolveSkillsDir() {
|
|
77
|
+
if (process.env.REGISTRY_PATH)
|
|
78
|
+
return process.env.REGISTRY_PATH;
|
|
79
|
+
const config = loadOctopusConfig();
|
|
80
|
+
if (config?.skillsDir)
|
|
81
|
+
return config.skillsDir;
|
|
82
|
+
return path.join(process.cwd(), 'registry', 'skills');
|
|
83
|
+
}
|
|
84
|
+
function writeSkillFiles(skillDir, skillMd, writeScript) {
|
|
85
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
86
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), skillMd, 'utf8');
|
|
87
|
+
if (writeScript) {
|
|
88
|
+
const scriptsDir = path.join(skillDir, 'scripts');
|
|
89
|
+
fs.mkdirSync(scriptsDir, { recursive: true });
|
|
90
|
+
const invokeJs = path.join(scriptsDir, 'invoke.js');
|
|
91
|
+
if (!fs.existsSync(invokeJs)) {
|
|
92
|
+
fs.writeFileSync(invokeJs, INVOKE_JS_TEMPLATE, 'utf8');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function extractSkillName(skillMd) {
|
|
97
|
+
const match = skillMd.match(/^---[\s\S]*?^name:\s*(.+)$/m);
|
|
98
|
+
return match ? match[1].trim() : 'my-skill';
|
|
99
|
+
}
|
|
100
|
+
// ── Template mode ─────────────────────────────────────────────────────────────
|
|
101
|
+
export async function runSkillTemplate(skillsDir) {
|
|
102
|
+
const targetDir = skillsDir ?? resolveSkillsDir();
|
|
103
|
+
const skillName = 'my-skill';
|
|
104
|
+
const skillDir = path.join(targetDir, skillName);
|
|
105
|
+
if (fs.existsSync(skillDir)) {
|
|
106
|
+
console.log(chalk.red(`\n Directory already exists: ${skillDir}`));
|
|
107
|
+
console.log(chalk.gray(' Choose a different name or remove the existing skill.\n'));
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
writeSkillFiles(skillDir, SKILL_MD_TEMPLATE, true);
|
|
111
|
+
console.log(chalk.green(`\n Scaffolded skill at ${skillDir}`));
|
|
112
|
+
console.log(chalk.gray(' Edit SKILL.md to define your skill, then restart the server.\n'));
|
|
113
|
+
}
|
|
114
|
+
// ── AI wizard mode ────────────────────────────────────────────────────────────
|
|
115
|
+
export async function runSkillCreateWizard(skillsDir) {
|
|
116
|
+
const targetDir = skillsDir ?? resolveSkillsDir();
|
|
117
|
+
console.log(chalk.bold('\n Skill Create Wizard\n'));
|
|
118
|
+
const description = await input({
|
|
119
|
+
message: 'What does your skill do?',
|
|
120
|
+
validate: (v) => (v.trim().length > 0 ? true : 'Description is required'),
|
|
121
|
+
});
|
|
122
|
+
const skillType = await select({
|
|
123
|
+
message: 'How does it work?',
|
|
124
|
+
choices: [
|
|
125
|
+
{ value: 'api', name: 'Calls an external API' },
|
|
126
|
+
{ value: 'llm', name: 'LLM-only (no external calls)' },
|
|
127
|
+
],
|
|
128
|
+
});
|
|
129
|
+
const answers = { description, type: skillType };
|
|
130
|
+
if (skillType === 'api') {
|
|
131
|
+
answers.endpoint = await input({ message: 'API endpoint URL (optional, press Enter to skip):' });
|
|
132
|
+
answers.authType = await select({
|
|
133
|
+
message: 'Authentication type:',
|
|
134
|
+
choices: [
|
|
135
|
+
{ value: 'none', name: 'None' },
|
|
136
|
+
{ value: 'api_key', name: 'API key' },
|
|
137
|
+
{ value: 'bearer', name: 'Bearer token' },
|
|
138
|
+
{ value: 'oauth', name: 'OAuth' },
|
|
139
|
+
],
|
|
140
|
+
});
|
|
141
|
+
answers.sampleIO = await input({ message: 'Describe a sample input and expected output (optional):' });
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
answers.constraints = await input({ message: 'Any constraints, tone, or output format? (optional):' });
|
|
145
|
+
}
|
|
146
|
+
// AI generation — use createChatClient from @agentoctopus/core
|
|
147
|
+
// ChatClient has a .chat(systemPrompt, userMessage) method
|
|
148
|
+
let llmChat;
|
|
149
|
+
try {
|
|
150
|
+
const { createChatClient } = await import('@agentoctopus/core');
|
|
151
|
+
const llmProvider = process.env.LLM_PROVIDER || 'openai';
|
|
152
|
+
const llmConfig = {
|
|
153
|
+
provider: llmProvider,
|
|
154
|
+
model: process.env.LLM_MODEL || 'gpt-4o-mini',
|
|
155
|
+
apiKey: process.env.OPENAI_API_KEY || process.env.GEMINI_API_KEY,
|
|
156
|
+
baseUrl: llmProvider === 'openai' ? process.env.OPENAI_BASE_URL : process.env.OLLAMA_BASE_URL,
|
|
157
|
+
};
|
|
158
|
+
const chatClient = createChatClient(llmConfig);
|
|
159
|
+
llmChat = (systemPrompt, userMessage) => chatClient.chat(systemPrompt, userMessage);
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
console.error(chalk.red(' Could not initialize LLM client. Falling back to template.'));
|
|
163
|
+
await runSkillTemplate(targetDir);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
console.log(chalk.gray('\n Generating SKILL.md with AI...\n'));
|
|
167
|
+
let skillMd = '';
|
|
168
|
+
let additionalNotes = '';
|
|
169
|
+
while (true) {
|
|
170
|
+
const systemPrompt = buildLLMSystemPrompt();
|
|
171
|
+
const userPrompt = buildLLMUserPrompt(answers) +
|
|
172
|
+
(additionalNotes ? `\n\nAdditional notes: ${additionalNotes}` : '');
|
|
173
|
+
try {
|
|
174
|
+
skillMd = await llmChat(systemPrompt, userPrompt);
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
console.error(chalk.red(` AI generation failed: ${err.message}`));
|
|
178
|
+
console.log(chalk.gray(' Falling back to template scaffold.\n'));
|
|
179
|
+
await runSkillTemplate(targetDir);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
console.log(chalk.cyan('─── Generated SKILL.md ───────────────────────────────────────'));
|
|
183
|
+
console.log(skillMd);
|
|
184
|
+
console.log(chalk.cyan('──────────────────────────────────────────────────────────────\n'));
|
|
185
|
+
const action = await select({
|
|
186
|
+
message: 'Does this look right?',
|
|
187
|
+
choices: [
|
|
188
|
+
{ value: 'yes', name: 'Yes — write the files' },
|
|
189
|
+
{ value: 'regenerate', name: 'Regenerate — add notes for the AI' },
|
|
190
|
+
{ value: 'template', name: 'Use template instead (skip AI)' },
|
|
191
|
+
],
|
|
192
|
+
});
|
|
193
|
+
if (action === 'yes')
|
|
194
|
+
break;
|
|
195
|
+
if (action === 'template') {
|
|
196
|
+
await runSkillTemplate(targetDir);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
additionalNotes = await input({ message: 'Notes for the AI (what to change):' });
|
|
200
|
+
}
|
|
201
|
+
const skillName = extractSkillName(skillMd);
|
|
202
|
+
const skillDir = path.join(targetDir, skillName);
|
|
203
|
+
if (fs.existsSync(skillDir)) {
|
|
204
|
+
const overwrite = await confirm({
|
|
205
|
+
message: `Skill directory "${skillDir}" already exists. Overwrite?`,
|
|
206
|
+
default: false,
|
|
207
|
+
});
|
|
208
|
+
if (!overwrite) {
|
|
209
|
+
console.log(chalk.gray('\n Cancelled. No files written.\n'));
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
writeSkillFiles(skillDir, skillMd, skillType === 'api');
|
|
214
|
+
console.log(chalk.green(`\n Skill written to ${skillDir}`));
|
|
215
|
+
if (skillType === 'api') {
|
|
216
|
+
console.log(chalk.gray(' Edit scripts/invoke.js to implement the API call.'));
|
|
217
|
+
}
|
|
218
|
+
console.log(chalk.yellow(' Restart the server to pick up the new skill.\n'));
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=skill-create.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-create.js","sourceRoot":"","sources":["../src/skill-create.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentoctopus/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "AgentOctopus CLI — route natural language queries to skills via `octopus ask`",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"README.md",
|
|
11
|
-
"dist"
|
|
11
|
+
"dist",
|
|
12
|
+
"skills"
|
|
12
13
|
],
|
|
13
14
|
"dependencies": {
|
|
14
15
|
"@inquirer/prompts": "^8.3.2",
|
|
@@ -19,8 +20,8 @@
|
|
|
19
20
|
"ora": "^8.0.1",
|
|
20
21
|
"readline": "^1.3.0",
|
|
21
22
|
"@agentoctopus/core": "0.4.0",
|
|
22
|
-
"@agentoctopus/
|
|
23
|
-
"@agentoctopus/
|
|
23
|
+
"@agentoctopus/registry": "0.4.1",
|
|
24
|
+
"@agentoctopus/gateway": "0.4.4"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"@types/node": "^20.12.0",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ip-lookup
|
|
3
|
+
description: >
|
|
4
|
+
Look up geolocation and network details for a specific IP address or domain name.
|
|
5
|
+
ONLY use this when the user provides an actual IP address (e.g. 8.8.8.8, 1.1.1.1)
|
|
6
|
+
or a domain name (e.g. github.com) to look up. Do NOT use for general questions
|
|
7
|
+
about what ISP, AS, or networking terms mean.
|
|
8
|
+
tags: [ip, geolocation, network, lookup, dns]
|
|
9
|
+
version: 1.0.0
|
|
10
|
+
adapter: subprocess
|
|
11
|
+
hosting: local
|
|
12
|
+
input_schema:
|
|
13
|
+
query: string
|
|
14
|
+
output_schema:
|
|
15
|
+
report: string
|
|
16
|
+
auth: none
|
|
17
|
+
rating: 4.6
|
|
18
|
+
invocations: 0
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Instructions
|
|
22
|
+
|
|
23
|
+
Extract the IP address or domain from the user's query and call ip-api.com
|
|
24
|
+
to retrieve geolocation and ISP information. Return a formatted report.
|
|
25
|
+
If no IP address or domain is present in the query, say so clearly.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// IP Lookup skill — calls ip-api.com (free, no key, 45 req/min)
|
|
3
|
+
const input = JSON.parse(process.env.OCTOPUS_INPUT || '{}');
|
|
4
|
+
const query = input.query || '';
|
|
5
|
+
|
|
6
|
+
function extractTarget(q) {
|
|
7
|
+
// Match an IPv4, IPv6, or domain
|
|
8
|
+
const ipv4 = q.match(/\b(\d{1,3}(?:\.\d{1,3}){3})\b/);
|
|
9
|
+
if (ipv4) return ipv4[1];
|
|
10
|
+
const domain = q.match(/\b([a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z]{2,})+)\b/);
|
|
11
|
+
if (domain) return domain[1];
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
const target = extractTarget(query);
|
|
17
|
+
|
|
18
|
+
if (!target) {
|
|
19
|
+
console.log(JSON.stringify({
|
|
20
|
+
result: 'Please provide an IP address (e.g. 8.8.8.8) or domain name (e.g. github.com) to look up.',
|
|
21
|
+
}));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const url = `http://ip-api.com/json/${encodeURIComponent(target)}?fields=status,message,country,regionName,city,zip,lat,lon,timezone,isp,org,as,query`;
|
|
25
|
+
|
|
26
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'AgentOctopus/0.1' } });
|
|
27
|
+
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
console.error(`ip-api error: ${res.status}`);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const d = await res.json();
|
|
34
|
+
|
|
35
|
+
if (d.status === 'fail') {
|
|
36
|
+
console.log(JSON.stringify({ result: `Lookup failed: ${d.message}` }));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const report = [
|
|
41
|
+
`IP / Host : ${d.query}`,
|
|
42
|
+
`Location : ${d.city}, ${d.regionName}, ${d.country}`,
|
|
43
|
+
`Coordinates: ${d.lat}, ${d.lon}`,
|
|
44
|
+
`Timezone : ${d.timezone}`,
|
|
45
|
+
`ISP : ${d.isp}`,
|
|
46
|
+
`Org : ${d.org}`,
|
|
47
|
+
`AS : ${d.as}`,
|
|
48
|
+
].join('\n');
|
|
49
|
+
|
|
50
|
+
console.log(JSON.stringify({ result: report }));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
main().catch((err) => {
|
|
54
|
+
console.error(err.message);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: translation
|
|
3
|
+
description: >
|
|
4
|
+
Translates text between languages using MyMemory. Use when the user asks
|
|
5
|
+
to translate text, convert language, or says things like "in French",
|
|
6
|
+
"en Español", "translate to Japanese", etc.
|
|
7
|
+
tags: [translation, language, text, convert]
|
|
8
|
+
version: 2.0.0
|
|
9
|
+
adapter: subprocess
|
|
10
|
+
hosting: local
|
|
11
|
+
input_schema:
|
|
12
|
+
query: string
|
|
13
|
+
output_schema:
|
|
14
|
+
translated_text: string
|
|
15
|
+
auth: none
|
|
16
|
+
rating: 4.5
|
|
17
|
+
invocations: 0
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Instructions
|
|
21
|
+
|
|
22
|
+
Parse the user's query to extract the text to translate and the target language.
|
|
23
|
+
Call the MyMemory free translation API and return the translated text.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Translation skill — calls MyMemory free API (no key required, 5000 chars/day)
|
|
3
|
+
const input = JSON.parse(process.env.OCTOPUS_INPUT || '{}');
|
|
4
|
+
const query = input.query || '';
|
|
5
|
+
|
|
6
|
+
// Language code map (common names → ISO 639-1)
|
|
7
|
+
const LANG_CODES = {
|
|
8
|
+
french: 'fr', spanish: 'es', german: 'de', italian: 'it', portuguese: 'pt',
|
|
9
|
+
japanese: 'ja', chinese: 'zh', korean: 'ko', arabic: 'ar', russian: 'ru',
|
|
10
|
+
dutch: 'nl', polish: 'pl', turkish: 'tr', hindi: 'hi', thai: 'th',
|
|
11
|
+
vietnamese: 'vi', greek: 'el', czech: 'cs', swedish: 'sv', danish: 'da',
|
|
12
|
+
finnish: 'fi', norwegian: 'no', hebrew: 'he', indonesian: 'id', malay: 'ms',
|
|
13
|
+
romanian: 'ro', hungarian: 'hu', ukrainian: 'uk',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function detectTargetLang(q) {
|
|
17
|
+
const lower = q.toLowerCase();
|
|
18
|
+
for (const [name, code] of Object.entries(LANG_CODES)) {
|
|
19
|
+
if (lower.includes(name)) return { code, name };
|
|
20
|
+
}
|
|
21
|
+
const m = lower.match(/\bto\s+([a-z]{2})\b/);
|
|
22
|
+
if (m) return { code: m[1], name: m[1] };
|
|
23
|
+
return { code: 'fr', name: 'French' };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function extractText(q) {
|
|
27
|
+
return q
|
|
28
|
+
.replace(/translate\s+(to\s+\w+\s+)?["']?/gi, '')
|
|
29
|
+
.replace(/\b(to|in|into)\s+(french|spanish|german|italian|portuguese|japanese|chinese|korean|arabic|russian|dutch|polish|turkish|hindi|thai|vietnamese|greek|czech|swedish|danish|finnish|norwegian|hebrew|indonesian|malay|romanian|hungarian|ukrainian|[a-z]{2})\b/gi, '')
|
|
30
|
+
.replace(/["']/g, '')
|
|
31
|
+
.trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function main() {
|
|
35
|
+
const { code: langCode, name: langName } = detectTargetLang(query);
|
|
36
|
+
const text = extractText(query) || query;
|
|
37
|
+
|
|
38
|
+
const url = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(text)}&langpair=en|${langCode}`;
|
|
39
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'AgentOctopus/0.1' } });
|
|
40
|
+
|
|
41
|
+
if (!res.ok) {
|
|
42
|
+
console.error(`MyMemory error: ${res.status}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
const translated = data.responseData?.translatedText;
|
|
48
|
+
|
|
49
|
+
if (!translated) {
|
|
50
|
+
console.log(JSON.stringify({ result: 'Translation failed — no result returned.' }));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const langLabel = langName.charAt(0).toUpperCase() + langName.slice(1);
|
|
55
|
+
console.log(JSON.stringify({
|
|
56
|
+
result: `"${text}" in ${langLabel}: ${translated}`,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
main().catch((err) => {
|
|
61
|
+
console.error(err.message);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: weather
|
|
3
|
+
description: >
|
|
4
|
+
Get current weather conditions and forecast for any city or location.
|
|
5
|
+
Use when the user asks about weather, temperature, rain, forecast,
|
|
6
|
+
or conditions in a place — e.g. "What's the weather in Tokyo?".
|
|
7
|
+
tags: [weather, forecast, temperature, climate]
|
|
8
|
+
version: 1.0.0
|
|
9
|
+
adapter: subprocess
|
|
10
|
+
hosting: local
|
|
11
|
+
input_schema:
|
|
12
|
+
query: string
|
|
13
|
+
output_schema:
|
|
14
|
+
report: string
|
|
15
|
+
auth: none
|
|
16
|
+
rating: 4.8
|
|
17
|
+
invocations: 0
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Instructions
|
|
21
|
+
|
|
22
|
+
Parse the location from the user's query and call wttr.in to get the current weather report.
|
|
23
|
+
Return a concise plain-text summary including temperature, conditions, humidity, and wind.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Weather skill — calls wttr.in (free, no API key)
|
|
3
|
+
const input = JSON.parse(process.env.OCTOPUS_INPUT || '{}');
|
|
4
|
+
const query = input.query || '';
|
|
5
|
+
|
|
6
|
+
// Extract location: strip common phrases like "weather in", "forecast for"
|
|
7
|
+
const location = query
|
|
8
|
+
.replace(/\b(what('s| is) the |get |show |weather|forecast|temperature|conditions?|climate)\b/gi, '')
|
|
9
|
+
.replace(/\b(in|for|at|of)\b/gi, '')
|
|
10
|
+
.trim()
|
|
11
|
+
.split(/\s+/)
|
|
12
|
+
.join('+') || 'London';
|
|
13
|
+
|
|
14
|
+
async function main() {
|
|
15
|
+
const url = `https://wttr.in/${encodeURIComponent(location)}?format=j1`;
|
|
16
|
+
const res = await fetch(url, { headers: { 'User-Agent': 'AgentOctopus/0.1' } });
|
|
17
|
+
|
|
18
|
+
if (!res.ok) {
|
|
19
|
+
console.error(`wttr.in error: ${res.status}`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const data = await res.json();
|
|
24
|
+
const current = data.current_condition?.[0];
|
|
25
|
+
const area = data.nearest_area?.[0];
|
|
26
|
+
|
|
27
|
+
if (!current) {
|
|
28
|
+
console.log(JSON.stringify({ result: 'No weather data found for that location.' }));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const place = [
|
|
33
|
+
area?.areaName?.[0]?.value,
|
|
34
|
+
area?.country?.[0]?.value,
|
|
35
|
+
].filter(Boolean).join(', ') || location;
|
|
36
|
+
|
|
37
|
+
const tempC = current.temp_C;
|
|
38
|
+
const tempF = current.temp_F;
|
|
39
|
+
const desc = current.weatherDesc?.[0]?.value || '';
|
|
40
|
+
const humidity = current.humidity;
|
|
41
|
+
const windKmph = current.windspeedKmph;
|
|
42
|
+
const feelsC = current.FeelsLikeC;
|
|
43
|
+
|
|
44
|
+
const report = [
|
|
45
|
+
`Weather in ${place}:`,
|
|
46
|
+
` Conditions : ${desc}`,
|
|
47
|
+
` Temperature: ${tempC}°C / ${tempF}°F (feels like ${feelsC}°C)`,
|
|
48
|
+
` Humidity : ${humidity}%`,
|
|
49
|
+
` Wind : ${windKmph} km/h`,
|
|
50
|
+
].join('\n');
|
|
51
|
+
|
|
52
|
+
console.log(JSON.stringify({ result: report }));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
main().catch((err) => {
|
|
56
|
+
console.error(err.message);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: x-search
|
|
3
|
+
description: >
|
|
4
|
+
Search X (Twitter) posts using the xAI Grok API with real-time access to X content.
|
|
5
|
+
tags: [x, twitter, social, search]
|
|
6
|
+
version: 1.0.0
|
|
7
|
+
adapter: subprocess
|
|
8
|
+
hosting: local
|
|
9
|
+
input_schema:
|
|
10
|
+
query: string
|
|
11
|
+
output_schema:
|
|
12
|
+
report: string
|
|
13
|
+
auth: api_key
|
|
14
|
+
rating: 3.0
|
|
15
|
+
invocations: 0
|
|
16
|
+
credentials:
|
|
17
|
+
- key: XAI_API_KEY
|
|
18
|
+
label: "xAI API Key (get one at console.x.ai)"
|
|
19
|
+
required: true
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Instructions
|
|
23
|
+
|
|
24
|
+
Search X (Twitter) for posts matching the user's query using the xAI Grok API.
|
|
25
|
+
Parse the query, call the Grok live-search endpoint, and return a concise
|
|
26
|
+
plain-text summary of the top results including author handles and timestamps.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// x-search skill — search X/Twitter via xAI Grok API
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const input = JSON.parse(process.env.OCTOPUS_INPUT || '{}');
|
|
7
|
+
const query = input.query;
|
|
8
|
+
if (!query || typeof query !== 'string') {
|
|
9
|
+
console.log(JSON.stringify({ report: 'Missing or invalid query', status: 'error' }));
|
|
10
|
+
process.exit(0);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const args = [query];
|
|
14
|
+
|
|
15
|
+
if (input.handles) {
|
|
16
|
+
const handles = Array.isArray(input.handles) ? input.handles.join(',') : input.handles;
|
|
17
|
+
args.push('--handles', handles);
|
|
18
|
+
}
|
|
19
|
+
if (input.exclude) {
|
|
20
|
+
const exclude = Array.isArray(input.exclude) ? input.exclude.join(',') : input.exclude;
|
|
21
|
+
args.push('--exclude', exclude);
|
|
22
|
+
}
|
|
23
|
+
if (input.from) args.push('--from', input.from);
|
|
24
|
+
if (input.to) args.push('--to', input.to);
|
|
25
|
+
if (input.images) args.push('--images');
|
|
26
|
+
if (input.video) args.push('--video');
|
|
27
|
+
|
|
28
|
+
// Path to search.py relative to this script's location
|
|
29
|
+
const scriptPath = path.resolve(__dirname, 'search.py');
|
|
30
|
+
|
|
31
|
+
const proc = spawn('python3', [scriptPath, ...args], {
|
|
32
|
+
env: { ...process.env },
|
|
33
|
+
timeout: 120000,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
let stdout = '';
|
|
37
|
+
let stderr = '';
|
|
38
|
+
|
|
39
|
+
proc.stdout.on('data', (data) => { stdout += data; });
|
|
40
|
+
proc.stderr.on('data', (data) => { stderr += data; });
|
|
41
|
+
|
|
42
|
+
proc.on('close', (code) => {
|
|
43
|
+
if (code !== 0) {
|
|
44
|
+
console.log(JSON.stringify({ report: `Search failed: ${stderr || 'unknown error'}`, status: 'error' }));
|
|
45
|
+
process.exit(0);
|
|
46
|
+
}
|
|
47
|
+
try {
|
|
48
|
+
const result = JSON.parse(stdout);
|
|
49
|
+
console.log(JSON.stringify({ report: result.text || '', status: result.status || 'completed' }));
|
|
50
|
+
} catch (e) {
|
|
51
|
+
console.log(JSON.stringify({ report: `Failed to parse result: ${e.message}`, status: 'error' }));
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
proc.on('error', (err) => {
|
|
56
|
+
console.log(JSON.stringify({ report: `Spawn error: ${err.message}`, status: 'error' }));
|
|
57
|
+
});
|