opc-agent 1.4.0 → 2.0.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/CHANGELOG.md +25 -0
- package/README.md +91 -32
- package/dist/channels/telegram.d.ts +30 -9
- package/dist/channels/telegram.js +125 -33
- package/dist/cli.js +415 -8
- package/dist/core/agent.d.ts +23 -0
- package/dist/core/agent.js +120 -3
- package/dist/core/runtime.d.ts +1 -0
- package/dist/core/runtime.js +44 -0
- package/dist/core/scheduler.d.ts +52 -0
- package/dist/core/scheduler.js +168 -0
- package/dist/core/subagent.d.ts +28 -0
- package/dist/core/subagent.js +65 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.js +134 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +17 -1
- package/dist/providers/index.d.ts +5 -1
- package/dist/providers/index.js +16 -9
- package/dist/schema/oad.d.ts +179 -4
- package/dist/schema/oad.js +12 -1
- package/dist/skills/auto-learn.d.ts +28 -0
- package/dist/skills/auto-learn.js +257 -0
- package/dist/tools/builtin/datetime.d.ts +3 -0
- package/dist/tools/builtin/datetime.js +44 -0
- package/dist/tools/builtin/file.d.ts +3 -0
- package/dist/tools/builtin/file.js +151 -0
- package/dist/tools/builtin/index.d.ts +15 -0
- package/dist/tools/builtin/index.js +30 -0
- package/dist/tools/builtin/shell.d.ts +3 -0
- package/dist/tools/builtin/shell.js +43 -0
- package/dist/tools/builtin/web.d.ts +3 -0
- package/dist/tools/builtin/web.js +37 -0
- package/dist/tools/mcp-client.d.ts +24 -0
- package/dist/tools/mcp-client.js +119 -0
- package/package.json +1 -1
- package/src/channels/telegram.ts +212 -90
- package/src/cli.ts +418 -8
- package/src/core/agent.ts +295 -152
- package/src/core/runtime.ts +47 -0
- package/src/core/scheduler.ts +187 -0
- package/src/core/subagent.ts +98 -0
- package/src/daemon.ts +96 -0
- package/src/index.ts +11 -0
- package/src/providers/index.ts +354 -339
- package/src/schema/oad.ts +167 -154
- package/src/skills/auto-learn.ts +262 -0
- package/src/tools/builtin/datetime.ts +41 -0
- package/src/tools/builtin/file.ts +107 -0
- package/src/tools/builtin/index.ts +28 -0
- package/src/tools/builtin/shell.ts +43 -0
- package/src/tools/builtin/web.ts +35 -0
- package/src/tools/mcp-client.ts +131 -0
- package/tests/auto-learn.test.ts +105 -0
- package/tests/builtin-tools.test.ts +83 -0
- package/tests/cli.test.ts +46 -0
- package/tests/subagent.test.ts +130 -0
- package/tests/telegram-discord.test.ts +60 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SkillLearner = void 0;
|
|
37
|
+
exports.skillToMarkdown = skillToMarkdown;
|
|
38
|
+
exports.parseSkillMarkdown = parseSkillMarkdown;
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const SKILL_EXTRACTION_PROMPT = `Analyze this conversation and determine if it represents a repeatable task that should be saved as a reusable skill.
|
|
42
|
+
|
|
43
|
+
Criteria for creating a skill:
|
|
44
|
+
1. The task is specific and well-defined
|
|
45
|
+
2. It could reasonably happen again
|
|
46
|
+
3. The solution has clear steps
|
|
47
|
+
|
|
48
|
+
If yes, extract:
|
|
49
|
+
- name: short kebab-case name
|
|
50
|
+
- description: one-line description
|
|
51
|
+
- trigger: regex pattern or keywords that would identify this task
|
|
52
|
+
- instructions: step-by-step instructions to complete the task
|
|
53
|
+
- examples: 2-3 example user inputs that would trigger this skill
|
|
54
|
+
|
|
55
|
+
If this is just casual chat or a one-off question, return null.
|
|
56
|
+
|
|
57
|
+
Respond in JSON format only: { "shouldCreate": boolean, "skill": { "name": string, "description": string, "trigger": string, "instructions": string, "examples": string[] } | null }
|
|
58
|
+
|
|
59
|
+
Conversation:
|
|
60
|
+
`;
|
|
61
|
+
const SKILL_IMPROVEMENT_PROMPT = `This skill was just used. Based on the outcome, suggest improvements:
|
|
62
|
+
|
|
63
|
+
Current skill:
|
|
64
|
+
`;
|
|
65
|
+
const SKILL_IMPROVEMENT_SUFFIX = `
|
|
66
|
+
|
|
67
|
+
Respond in JSON only: { "shouldImprove": boolean, "improvements": { "instructions"?: string, "trigger"?: string, "examples"?: string[] } | null }`;
|
|
68
|
+
class SkillLearner {
|
|
69
|
+
skillsDir;
|
|
70
|
+
skills = [];
|
|
71
|
+
loaded = false;
|
|
72
|
+
constructor(skillsDir) {
|
|
73
|
+
this.skillsDir = skillsDir;
|
|
74
|
+
}
|
|
75
|
+
async analyzeForSkillCreation(conversation, provider) {
|
|
76
|
+
const conversationText = conversation
|
|
77
|
+
.map((m) => `${m.role}: ${m.content}`)
|
|
78
|
+
.join('\n');
|
|
79
|
+
const prompt = SKILL_EXTRACTION_PROMPT + conversationText;
|
|
80
|
+
try {
|
|
81
|
+
const response = await provider.chat([{ id: 'analysis', role: 'user', content: prompt, timestamp: Date.now() }], 'You are a skill extraction assistant. Respond only with valid JSON.');
|
|
82
|
+
const json = extractJson(response);
|
|
83
|
+
if (!json || !json.shouldCreate || !json.skill)
|
|
84
|
+
return null;
|
|
85
|
+
const skill = {
|
|
86
|
+
name: json.skill.name,
|
|
87
|
+
description: json.skill.description,
|
|
88
|
+
trigger: json.skill.trigger,
|
|
89
|
+
instructions: json.skill.instructions,
|
|
90
|
+
examples: json.skill.examples || [],
|
|
91
|
+
createdAt: new Date(),
|
|
92
|
+
usageCount: 0,
|
|
93
|
+
version: 1,
|
|
94
|
+
};
|
|
95
|
+
return skill;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async saveSkill(skill) {
|
|
102
|
+
fs.mkdirSync(this.skillsDir, { recursive: true });
|
|
103
|
+
const filePath = path.join(this.skillsDir, `${skill.name}.md`);
|
|
104
|
+
fs.writeFileSync(filePath, skillToMarkdown(skill), 'utf-8');
|
|
105
|
+
// Update cache
|
|
106
|
+
const idx = this.skills.findIndex((s) => s.name === skill.name);
|
|
107
|
+
if (idx >= 0) {
|
|
108
|
+
this.skills[idx] = skill;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.skills.push(skill);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async loadLearnedSkills() {
|
|
115
|
+
if (!fs.existsSync(this.skillsDir))
|
|
116
|
+
return [];
|
|
117
|
+
const files = fs.readdirSync(this.skillsDir).filter((f) => f.endsWith('.md'));
|
|
118
|
+
this.skills = files
|
|
119
|
+
.map((f) => {
|
|
120
|
+
try {
|
|
121
|
+
const content = fs.readFileSync(path.join(this.skillsDir, f), 'utf-8');
|
|
122
|
+
return parseSkillMarkdown(content);
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
.filter((s) => s !== null);
|
|
129
|
+
this.loaded = true;
|
|
130
|
+
return this.skills;
|
|
131
|
+
}
|
|
132
|
+
matchSkill(message) {
|
|
133
|
+
if (!this.loaded)
|
|
134
|
+
return null;
|
|
135
|
+
for (const skill of this.skills) {
|
|
136
|
+
try {
|
|
137
|
+
const regex = new RegExp(skill.trigger, 'i');
|
|
138
|
+
if (regex.test(message))
|
|
139
|
+
return skill;
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Fallback: keyword matching — split on common separators, strip non-word chars
|
|
143
|
+
const keywords = skill.trigger.split(/[\s,;|]+/).map(k => k.replace(/[^\w-]/g, '').toLowerCase()).filter(k => k.length > 2);
|
|
144
|
+
const lower = message.toLowerCase();
|
|
145
|
+
if (keywords.some((kw) => lower.includes(kw)))
|
|
146
|
+
return skill;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
async improveSkill(skill, conversation, provider) {
|
|
152
|
+
const conversationText = conversation
|
|
153
|
+
.map((m) => `${m.role}: ${m.content}`)
|
|
154
|
+
.join('\n');
|
|
155
|
+
const prompt = SKILL_IMPROVEMENT_PROMPT +
|
|
156
|
+
skillToMarkdown(skill) +
|
|
157
|
+
'\n\nConversation where it was used:\n' +
|
|
158
|
+
conversationText +
|
|
159
|
+
SKILL_IMPROVEMENT_SUFFIX;
|
|
160
|
+
try {
|
|
161
|
+
const response = await provider.chat([{ id: 'improve', role: 'user', content: prompt, timestamp: Date.now() }], 'You are a skill improvement assistant. Respond only with valid JSON.');
|
|
162
|
+
const json = extractJson(response);
|
|
163
|
+
if (!json || !json.shouldImprove || !json.improvements)
|
|
164
|
+
return;
|
|
165
|
+
const { improvements } = json;
|
|
166
|
+
if (improvements.instructions)
|
|
167
|
+
skill.instructions = improvements.instructions;
|
|
168
|
+
if (improvements.trigger)
|
|
169
|
+
skill.trigger = improvements.trigger;
|
|
170
|
+
if (improvements.examples)
|
|
171
|
+
skill.examples = [...skill.examples, ...improvements.examples];
|
|
172
|
+
skill.version++;
|
|
173
|
+
await this.saveSkill(skill);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
// Silently fail — improvement is best-effort
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
getSkills() {
|
|
180
|
+
return [...this.skills];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
exports.SkillLearner = SkillLearner;
|
|
184
|
+
// ─── Helpers ────────────────────────────────────────────────
|
|
185
|
+
function extractJson(text) {
|
|
186
|
+
// Try to extract JSON from response (may be wrapped in markdown code block)
|
|
187
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
188
|
+
if (!match)
|
|
189
|
+
return null;
|
|
190
|
+
try {
|
|
191
|
+
return JSON.parse(match[0]);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
function skillToMarkdown(skill) {
|
|
198
|
+
const lines = [
|
|
199
|
+
`# Skill: ${skill.name}`,
|
|
200
|
+
'',
|
|
201
|
+
'## Description',
|
|
202
|
+
skill.description,
|
|
203
|
+
'',
|
|
204
|
+
'## Trigger',
|
|
205
|
+
`Pattern: ${skill.trigger}`,
|
|
206
|
+
'',
|
|
207
|
+
'## Instructions',
|
|
208
|
+
skill.instructions,
|
|
209
|
+
'',
|
|
210
|
+
'## Examples',
|
|
211
|
+
...skill.examples.map((e) => `- "${e}"`),
|
|
212
|
+
'',
|
|
213
|
+
'## Metadata',
|
|
214
|
+
`- Created: ${skill.createdAt.toISOString()}`,
|
|
215
|
+
`- Version: ${skill.version}`,
|
|
216
|
+
`- Usage Count: ${skill.usageCount}`,
|
|
217
|
+
`- Last Used: ${skill.lastUsed?.toISOString() ?? 'never'}`,
|
|
218
|
+
'',
|
|
219
|
+
];
|
|
220
|
+
return lines.join('\n');
|
|
221
|
+
}
|
|
222
|
+
function parseSkillMarkdown(content) {
|
|
223
|
+
const nameMatch = content.match(/^# Skill:\s*(.+)$/m);
|
|
224
|
+
if (!nameMatch)
|
|
225
|
+
return null;
|
|
226
|
+
const section = (heading) => {
|
|
227
|
+
const re = new RegExp(`## ${heading}\\s*\\n([\\s\\S]*?)(?=\\n## |$)`);
|
|
228
|
+
const m = content.match(re);
|
|
229
|
+
return m ? m[1].trim() : '';
|
|
230
|
+
};
|
|
231
|
+
const description = section('Description');
|
|
232
|
+
const triggerLine = section('Trigger');
|
|
233
|
+
const trigger = triggerLine.replace(/^Pattern:\s*/i, '').trim();
|
|
234
|
+
const instructions = section('Instructions');
|
|
235
|
+
const examplesRaw = section('Examples');
|
|
236
|
+
const examples = examplesRaw
|
|
237
|
+
.split('\n')
|
|
238
|
+
.map((l) => l.replace(/^-\s*"?|"?\s*$/g, '').trim())
|
|
239
|
+
.filter(Boolean);
|
|
240
|
+
const metadata = section('Metadata');
|
|
241
|
+
const getMeta = (key) => {
|
|
242
|
+
const m = metadata.match(new RegExp(`- ${key}:\\s*(.+)`, 'i'));
|
|
243
|
+
return m ? m[1].trim() : '';
|
|
244
|
+
};
|
|
245
|
+
return {
|
|
246
|
+
name: nameMatch[1].trim(),
|
|
247
|
+
description,
|
|
248
|
+
trigger,
|
|
249
|
+
instructions,
|
|
250
|
+
examples,
|
|
251
|
+
createdAt: new Date(getMeta('Created') || Date.now()),
|
|
252
|
+
version: parseInt(getMeta('Version') || '1', 10),
|
|
253
|
+
usageCount: parseInt(getMeta('Usage Count') || '0', 10),
|
|
254
|
+
lastUsed: getMeta('Last Used') !== 'never' ? new Date(getMeta('Last Used')) : undefined,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=auto-learn.js.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.datetimeTool = void 0;
|
|
4
|
+
exports.datetimeTool = {
|
|
5
|
+
name: 'datetime',
|
|
6
|
+
description: 'Get current date, time, timezone info',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
format: { type: 'string', default: 'iso' },
|
|
11
|
+
timezone: { type: 'string' },
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
async execute(input) {
|
|
15
|
+
const now = new Date();
|
|
16
|
+
const timezone = input.timezone;
|
|
17
|
+
const format = input.format || 'iso';
|
|
18
|
+
let content;
|
|
19
|
+
if (format === 'iso') {
|
|
20
|
+
content = now.toISOString();
|
|
21
|
+
}
|
|
22
|
+
else if (format === 'locale') {
|
|
23
|
+
content = timezone
|
|
24
|
+
? now.toLocaleString('en-US', { timeZone: timezone })
|
|
25
|
+
: now.toLocaleString();
|
|
26
|
+
}
|
|
27
|
+
else if (format === 'unix') {
|
|
28
|
+
content = String(Math.floor(now.getTime() / 1000));
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
content = now.toISOString();
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
content: JSON.stringify({
|
|
35
|
+
iso: now.toISOString(),
|
|
36
|
+
unix: Math.floor(now.getTime() / 1000),
|
|
37
|
+
formatted: content,
|
|
38
|
+
timezone: timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
39
|
+
}),
|
|
40
|
+
isError: false,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
//# sourceMappingURL=datetime.js.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.fileTool = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
function resolveSafe(basePath, targetPath) {
|
|
40
|
+
const resolved = path.resolve(basePath, targetPath);
|
|
41
|
+
if (!resolved.startsWith(path.resolve(basePath)))
|
|
42
|
+
return null;
|
|
43
|
+
return resolved;
|
|
44
|
+
}
|
|
45
|
+
function searchFiles(dir, query, results = [], maxResults = 20) {
|
|
46
|
+
if (results.length >= maxResults)
|
|
47
|
+
return results;
|
|
48
|
+
try {
|
|
49
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
50
|
+
for (const entry of entries) {
|
|
51
|
+
if (results.length >= maxResults)
|
|
52
|
+
break;
|
|
53
|
+
const full = path.join(dir, entry.name);
|
|
54
|
+
if (entry.isDirectory()) {
|
|
55
|
+
if (entry.name === 'node_modules' || entry.name === '.git')
|
|
56
|
+
continue;
|
|
57
|
+
searchFiles(full, query, results, maxResults);
|
|
58
|
+
}
|
|
59
|
+
else if (entry.isFile()) {
|
|
60
|
+
try {
|
|
61
|
+
const content = fs.readFileSync(full, 'utf-8');
|
|
62
|
+
const lines = content.split('\n');
|
|
63
|
+
for (let i = 0; i < lines.length; i++) {
|
|
64
|
+
if (lines[i].includes(query)) {
|
|
65
|
+
results.push(`${full}:${i + 1}: ${lines[i].trim()}`);
|
|
66
|
+
if (results.length >= maxResults)
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch { /* skip binary/unreadable */ }
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
catch { /* skip inaccessible dirs */ }
|
|
76
|
+
return results;
|
|
77
|
+
}
|
|
78
|
+
exports.fileTool = {
|
|
79
|
+
name: 'file_operations',
|
|
80
|
+
description: 'Read, write, list, and search files in the workspace',
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
action: { type: 'string', enum: ['read', 'write', 'list', 'search', 'exists'] },
|
|
85
|
+
path: { type: 'string' },
|
|
86
|
+
content: { type: 'string' },
|
|
87
|
+
query: { type: 'string' },
|
|
88
|
+
},
|
|
89
|
+
required: ['action'],
|
|
90
|
+
},
|
|
91
|
+
async execute(input, context) {
|
|
92
|
+
const action = input.action;
|
|
93
|
+
const workspace = process.cwd();
|
|
94
|
+
const targetPath = input.path;
|
|
95
|
+
if (action === 'search') {
|
|
96
|
+
const query = input.query;
|
|
97
|
+
if (!query)
|
|
98
|
+
return { content: 'query is required for search', isError: true };
|
|
99
|
+
const results = searchFiles(workspace, query);
|
|
100
|
+
return { content: results.length ? results.join('\n') : 'No matches found', isError: false };
|
|
101
|
+
}
|
|
102
|
+
if (action === 'list') {
|
|
103
|
+
const dir = targetPath ? resolveSafe(workspace, targetPath) : workspace;
|
|
104
|
+
if (!dir)
|
|
105
|
+
return { content: 'Path outside workspace', isError: true };
|
|
106
|
+
try {
|
|
107
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
108
|
+
const listing = entries.map(e => `${e.isDirectory() ? '[DIR] ' : ''}${e.name}`).join('\n');
|
|
109
|
+
return { content: listing || '(empty directory)', isError: false };
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
return { content: `Error listing directory: ${err instanceof Error ? err.message : String(err)}`, isError: true };
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (!targetPath)
|
|
116
|
+
return { content: 'path is required', isError: true };
|
|
117
|
+
const resolved = resolveSafe(workspace, targetPath);
|
|
118
|
+
if (!resolved)
|
|
119
|
+
return { content: 'Path outside workspace', isError: true };
|
|
120
|
+
switch (action) {
|
|
121
|
+
case 'read': {
|
|
122
|
+
try {
|
|
123
|
+
const content = fs.readFileSync(resolved, 'utf-8');
|
|
124
|
+
return { content: content.slice(0, 50000), isError: false };
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
return { content: `Error reading file: ${err instanceof Error ? err.message : String(err)}`, isError: true };
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
case 'write': {
|
|
131
|
+
const content = input.content;
|
|
132
|
+
if (content === undefined)
|
|
133
|
+
return { content: 'content is required for write', isError: true };
|
|
134
|
+
try {
|
|
135
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
136
|
+
fs.writeFileSync(resolved, content, 'utf-8');
|
|
137
|
+
return { content: `Written ${content.length} bytes to ${targetPath}`, isError: false };
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
return { content: `Error writing file: ${err instanceof Error ? err.message : String(err)}`, isError: true };
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
case 'exists': {
|
|
144
|
+
return { content: String(fs.existsSync(resolved)), isError: false };
|
|
145
|
+
}
|
|
146
|
+
default:
|
|
147
|
+
return { content: `Unknown action: ${action}`, isError: true };
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MCPTool } from '../mcp';
|
|
2
|
+
import { fileTool } from './file';
|
|
3
|
+
import { webTool } from './web';
|
|
4
|
+
import { shellTool } from './shell';
|
|
5
|
+
import { datetimeTool } from './datetime';
|
|
6
|
+
export { fileTool, webTool, shellTool, datetimeTool };
|
|
7
|
+
/**
|
|
8
|
+
* Get all built-in tools.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getBuiltinTools(): MCPTool[];
|
|
11
|
+
/**
|
|
12
|
+
* Get specific built-in tools by name. If no names given, returns all.
|
|
13
|
+
*/
|
|
14
|
+
export declare function getBuiltinToolsByName(names?: string[]): MCPTool[];
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.datetimeTool = exports.shellTool = exports.webTool = exports.fileTool = void 0;
|
|
4
|
+
exports.getBuiltinTools = getBuiltinTools;
|
|
5
|
+
exports.getBuiltinToolsByName = getBuiltinToolsByName;
|
|
6
|
+
const file_1 = require("./file");
|
|
7
|
+
Object.defineProperty(exports, "fileTool", { enumerable: true, get: function () { return file_1.fileTool; } });
|
|
8
|
+
const web_1 = require("./web");
|
|
9
|
+
Object.defineProperty(exports, "webTool", { enumerable: true, get: function () { return web_1.webTool; } });
|
|
10
|
+
const shell_1 = require("./shell");
|
|
11
|
+
Object.defineProperty(exports, "shellTool", { enumerable: true, get: function () { return shell_1.shellTool; } });
|
|
12
|
+
const datetime_1 = require("./datetime");
|
|
13
|
+
Object.defineProperty(exports, "datetimeTool", { enumerable: true, get: function () { return datetime_1.datetimeTool; } });
|
|
14
|
+
const ALL_BUILTIN_TOOLS = [file_1.fileTool, web_1.webTool, shell_1.shellTool, datetime_1.datetimeTool];
|
|
15
|
+
const BUILTIN_MAP = new Map(ALL_BUILTIN_TOOLS.map(t => [t.name, t]));
|
|
16
|
+
/**
|
|
17
|
+
* Get all built-in tools.
|
|
18
|
+
*/
|
|
19
|
+
function getBuiltinTools() {
|
|
20
|
+
return [...ALL_BUILTIN_TOOLS];
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get specific built-in tools by name. If no names given, returns all.
|
|
24
|
+
*/
|
|
25
|
+
function getBuiltinToolsByName(names) {
|
|
26
|
+
if (!names || names.length === 0)
|
|
27
|
+
return getBuiltinTools();
|
|
28
|
+
return names.map(n => BUILTIN_MAP.get(n)).filter((t) => !!t);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.shellTool = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
exports.shellTool = {
|
|
6
|
+
name: 'shell_exec',
|
|
7
|
+
description: 'Execute a shell command (sandboxed to workspace)',
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
command: { type: 'string' },
|
|
12
|
+
timeout: { type: 'number', default: 30000 },
|
|
13
|
+
},
|
|
14
|
+
required: ['command'],
|
|
15
|
+
},
|
|
16
|
+
async execute(input) {
|
|
17
|
+
const command = input.command;
|
|
18
|
+
const timeout = input.timeout || 30000;
|
|
19
|
+
const workspace = process.cwd();
|
|
20
|
+
// Block path traversal attempts
|
|
21
|
+
if (command.includes('..')) {
|
|
22
|
+
return { content: 'Commands with ".." are not allowed for security', isError: true };
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const output = (0, child_process_1.execSync)(command, {
|
|
26
|
+
cwd: workspace,
|
|
27
|
+
timeout,
|
|
28
|
+
encoding: 'utf-8',
|
|
29
|
+
maxBuffer: 1024 * 1024,
|
|
30
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
31
|
+
});
|
|
32
|
+
const result = (output || '').slice(0, 5000);
|
|
33
|
+
return { content: result || '(no output)', isError: false };
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const stderr = err.stderr ? String(err.stderr).slice(0, 2500) : '';
|
|
37
|
+
const stdout = err.stdout ? String(err.stdout).slice(0, 2500) : '';
|
|
38
|
+
const output = [stdout, stderr].filter(Boolean).join('\n') || err.message;
|
|
39
|
+
return { content: `Command failed: ${output.slice(0, 5000)}`, isError: true };
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.webTool = void 0;
|
|
4
|
+
exports.webTool = {
|
|
5
|
+
name: 'web_fetch',
|
|
6
|
+
description: 'Fetch content from a URL',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
url: { type: 'string' },
|
|
11
|
+
method: { type: 'string', enum: ['GET', 'POST'], default: 'GET' },
|
|
12
|
+
maxLength: { type: 'number', default: 5000 },
|
|
13
|
+
},
|
|
14
|
+
required: ['url'],
|
|
15
|
+
},
|
|
16
|
+
async execute(input) {
|
|
17
|
+
const url = input.url;
|
|
18
|
+
const method = input.method || 'GET';
|
|
19
|
+
const maxLength = input.maxLength || 5000;
|
|
20
|
+
try {
|
|
21
|
+
const response = await fetch(url, { method, signal: AbortSignal.timeout(15000) });
|
|
22
|
+
const text = await response.text();
|
|
23
|
+
const truncated = text.length > maxLength ? text.slice(0, maxLength) + '\n...[truncated]' : text;
|
|
24
|
+
return {
|
|
25
|
+
content: `Status: ${response.status}\n\n${truncated}`,
|
|
26
|
+
isError: false,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
catch (err) {
|
|
30
|
+
return {
|
|
31
|
+
content: `Fetch error: ${err instanceof Error ? err.message : String(err)}`,
|
|
32
|
+
isError: true,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
//# sourceMappingURL=web.js.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { MCPToolDefinition, MCPToolResult } from './mcp';
|
|
2
|
+
export interface MCPServerConfig {
|
|
3
|
+
name: string;
|
|
4
|
+
command: string;
|
|
5
|
+
args?: string[];
|
|
6
|
+
env?: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
export declare class MCPClient {
|
|
9
|
+
private process;
|
|
10
|
+
private config;
|
|
11
|
+
private nextId;
|
|
12
|
+
private pending;
|
|
13
|
+
private buffer;
|
|
14
|
+
private connected;
|
|
15
|
+
connect(config: MCPServerConfig): Promise<void>;
|
|
16
|
+
private processBuffer;
|
|
17
|
+
private sendRequest;
|
|
18
|
+
private sendNotification;
|
|
19
|
+
listTools(): Promise<MCPToolDefinition[]>;
|
|
20
|
+
callTool(name: string, input: Record<string, unknown>): Promise<MCPToolResult>;
|
|
21
|
+
disconnect(): Promise<void>;
|
|
22
|
+
isConnected(): boolean;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=mcp-client.d.ts.map
|