opc-agent 4.1.0 → 4.1.2
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/.github/ISSUE_TEMPLATE/bug_report.md +20 -20
- package/.github/ISSUE_TEMPLATE/feature_request.md +14 -14
- package/.github/PULL_REQUEST_TEMPLATE.md +13 -13
- package/CHANGELOG.md +48 -48
- package/CONTRIBUTING.md +36 -36
- package/README.zh-CN.md +497 -497
- package/USABILITY-ISSUES.md +73 -0
- package/dist/channels/web.js +8 -2
- package/dist/channels/wechat.js +6 -6
- package/dist/cli.js +200 -85
- package/dist/core/runtime.js +37 -15
- package/dist/deploy/index.js +56 -56
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +105 -10
- package/dist/memory/deepbrain.d.ts +1 -1
- package/dist/memory/deepbrain.js +95 -4
- package/dist/scheduler/cron-engine.js +3 -36
- package/dist/studio/server.js +30 -1
- package/dist/studio-ui/index.html +230 -10
- package/dist/ui/components.js +105 -105
- package/examples/README.md +22 -22
- package/examples/basic-agent.ts +90 -90
- package/examples/brain-integration.ts +71 -71
- package/examples/multi-channel.ts +74 -74
- package/fix-sidebar.mjs +188 -188
- package/install.ps1 +154 -154
- package/install.sh +164 -164
- package/package.json +1 -1
- package/scripts/install.ps1 +31 -31
- package/scripts/install.sh +40 -40
- package/serve-studio.js +13 -13
- package/serve-test.js +25 -25
- package/src/channels/dingtalk.ts +46 -46
- package/src/channels/email.ts +351 -351
- package/src/channels/feishu.ts +349 -349
- package/src/channels/googlechat.ts +42 -42
- package/src/channels/imessage.ts +31 -31
- package/src/channels/irc.ts +82 -82
- package/src/channels/line.ts +32 -32
- package/src/channels/matrix.ts +33 -33
- package/src/channels/mattermost.ts +57 -57
- package/src/channels/msteams.ts +32 -32
- package/src/channels/nostr.ts +32 -32
- package/src/channels/qq.ts +33 -33
- package/src/channels/signal.ts +32 -32
- package/src/channels/sms.ts +33 -33
- package/src/channels/telegram.ts +616 -616
- package/src/channels/twitch.ts +65 -65
- package/src/channels/voice-call.ts +100 -100
- package/src/channels/web.ts +8 -2
- package/src/channels/websocket.ts +399 -399
- package/src/channels/wechat.ts +329 -329
- package/src/channels/whatsapp.ts +32 -32
- package/src/cli/chat.ts +99 -99
- package/src/cli/setup.ts +314 -314
- package/src/cli.ts +195 -92
- package/src/core/agent.ts +476 -476
- package/src/core/api-server.ts +277 -277
- package/src/core/audio.ts +98 -98
- package/src/core/collaboration.ts +275 -275
- package/src/core/context-discovery.ts +85 -85
- package/src/core/context-refs.ts +140 -140
- package/src/core/gateway.ts +106 -106
- package/src/core/heartbeat.ts +51 -51
- package/src/core/hooks.ts +105 -105
- package/src/core/ide-bridge.ts +133 -133
- package/src/core/node-network.ts +86 -86
- package/src/core/profiles.ts +122 -122
- package/src/core/runtime.ts +25 -0
- package/src/core/scheduler.ts +187 -187
- package/src/core/session-manager.ts +137 -137
- package/src/core/subagent.ts +98 -98
- package/src/core/vision.ts +180 -180
- package/src/core/workflow-graph.ts +365 -365
- package/src/daemon.ts +96 -96
- package/src/deploy/index.ts +255 -255
- package/src/doctor.ts +98 -11
- package/src/eval/index.ts +211 -211
- package/src/eval/suites/basic.json +16 -16
- package/src/eval/suites/memory.json +12 -12
- package/src/eval/suites/safety.json +14 -14
- package/src/hub/brain-seed.ts +54 -54
- package/src/hub/client.ts +60 -60
- package/src/mcp/servers/calculator-mcp.ts +65 -65
- package/src/mcp/servers/crypto-mcp.ts +73 -73
- package/src/mcp/servers/database-mcp.ts +72 -72
- package/src/mcp/servers/datetime-mcp.ts +69 -69
- package/src/mcp/servers/filesystem.ts +66 -66
- package/src/mcp/servers/github-mcp.ts +58 -58
- package/src/mcp/servers/index.ts +63 -63
- package/src/mcp/servers/json-mcp.ts +102 -102
- package/src/mcp/servers/memory-mcp.ts +56 -56
- package/src/mcp/servers/regex-mcp.ts +53 -53
- package/src/mcp/servers/web-mcp.ts +49 -49
- package/src/memory/context-compressor.ts +189 -189
- package/src/memory/deepbrain.ts +99 -5
- package/src/memory/seed-loader.ts +212 -212
- package/src/memory/user-profiler.ts +215 -215
- package/src/plugins/content-filter.ts +23 -23
- package/src/plugins/logger.ts +18 -18
- package/src/plugins/rate-limiter.ts +38 -38
- package/src/protocols/a2a/client.ts +132 -132
- package/src/protocols/a2a/index.ts +8 -8
- package/src/protocols/a2a/server.ts +333 -333
- package/src/protocols/a2a/types.ts +88 -88
- package/src/protocols/a2a/utils.ts +50 -50
- package/src/protocols/agui/client.ts +83 -83
- package/src/protocols/agui/index.ts +4 -4
- package/src/protocols/agui/server.ts +218 -218
- package/src/protocols/agui/types.ts +153 -153
- package/src/protocols/index.ts +2 -2
- package/src/protocols/mcp/agent-tools.ts +134 -134
- package/src/protocols/mcp/index.ts +8 -8
- package/src/protocols/mcp/server.ts +262 -262
- package/src/protocols/mcp/types.ts +69 -69
- package/src/providers/index.ts +632 -632
- package/src/publish/index.ts +376 -376
- package/src/scheduler/cron-engine.ts +191 -191
- package/src/scheduler/index.ts +2 -2
- package/src/schema/oad.ts +217 -217
- package/src/security/approval.ts +131 -131
- package/src/security/approvals.ts +143 -143
- package/src/security/elevated.ts +105 -105
- package/src/security/guardrails.ts +248 -248
- package/src/security/index.ts +9 -9
- package/src/security/keys.ts +87 -87
- package/src/security/secrets.ts +129 -129
- package/src/skills/builtin/index.ts +408 -408
- package/src/skills/marketplace.ts +113 -113
- package/src/skills/types.ts +42 -42
- package/src/studio/server.ts +31 -1
- package/src/studio/templates-data.ts +178 -178
- package/src/studio-ui/index.html +230 -10
- package/src/telemetry/index.ts +324 -324
- package/src/tools/builtin/browser.ts +299 -299
- package/src/tools/builtin/datetime.ts +41 -41
- package/src/tools/builtin/file.ts +107 -107
- package/src/tools/builtin/home-assistant.ts +116 -116
- package/src/tools/builtin/rl-tools.ts +243 -243
- package/src/tools/builtin/shell.ts +43 -43
- package/src/tools/builtin/vision.ts +64 -64
- package/src/tools/builtin/web-search.ts +126 -126
- package/src/tools/builtin/web.ts +35 -35
- package/src/tools/document-processor.ts +213 -213
- package/src/tools/image-generator.ts +150 -150
- package/src/tools/integrations/calendar.ts +73 -73
- package/src/tools/integrations/code-exec.ts +39 -39
- package/src/tools/integrations/csv-analyzer.ts +92 -92
- package/src/tools/integrations/database.ts +44 -44
- package/src/tools/integrations/email-send.ts +76 -76
- package/src/tools/integrations/git-tool.ts +42 -42
- package/src/tools/integrations/github-tool.ts +76 -76
- package/src/tools/integrations/image-gen.ts +56 -56
- package/src/tools/integrations/index.ts +92 -92
- package/src/tools/integrations/jira.ts +83 -83
- package/src/tools/integrations/notion.ts +71 -71
- package/src/tools/integrations/npm-tool.ts +48 -48
- package/src/tools/integrations/pdf-reader.ts +58 -58
- package/src/tools/integrations/slack.ts +65 -65
- package/src/tools/integrations/summarizer.ts +49 -49
- package/src/tools/integrations/translator.ts +48 -48
- package/src/tools/integrations/trello.ts +60 -60
- package/src/tools/integrations/vector-search.ts +42 -42
- package/src/tools/integrations/web-scraper.ts +47 -47
- package/src/tools/integrations/web-search.ts +58 -58
- package/src/tools/integrations/webhook.ts +38 -38
- package/src/tools/mcp-client.ts +131 -131
- package/src/tools/web-scraper.ts +179 -179
- package/src/tools/web-search.ts +180 -180
- package/src/ui/components.ts +127 -127
- package/srv-out.txt +1 -1
- package/templates/ecommerce-assistant/README.md +45 -45
- package/templates/ecommerce-assistant/oad.yaml +47 -47
- package/templates/tech-support/README.md +43 -43
- package/templates/tech-support/oad.yaml +45 -45
- package/test-agent/Dockerfile +9 -9
- package/test-agent/README.md +50 -50
- package/test-agent/agent.yaml +23 -23
- package/test-agent/docker-compose.yml +11 -11
- package/test-agent/oad.yaml +31 -31
- package/test-agent/package-lock.json +1492 -1492
- package/test-agent/package.json +17 -17
- package/test-agent/src/index.ts +24 -24
- package/test-agent/src/skills/echo.ts +15 -15
- package/test-agent/tsconfig.json +24 -24
- package/test-full.js +43 -43
- package/test-sidebar.js +22 -22
- package/test-studio3.js +75 -75
- package/test-studio4.js +41 -41
- package/tests/a2a-protocol.test.ts +285 -285
- package/tests/agui-protocol.test.ts +246 -246
- package/tests/api-server.test.ts +148 -148
- package/tests/approvals.test.ts +89 -89
- package/tests/audio.test.ts +40 -40
- package/tests/brain-seed-extended.test.ts +490 -490
- package/tests/brain-seed.test.ts +239 -239
- package/tests/browser.test.ts +179 -179
- package/tests/channels/discord.test.ts +79 -79
- package/tests/channels/email.test.ts +148 -148
- package/tests/channels/feishu.test.ts +123 -123
- package/tests/channels/telegram.test.ts +129 -129
- package/tests/channels/websocket.test.ts +53 -53
- package/tests/channels/wechat.test.ts +170 -170
- package/tests/channels-extra.test.ts +45 -45
- package/tests/chat-cli.test.ts +160 -160
- package/tests/cli.test.ts +46 -46
- package/tests/context-compressor.test.ts +172 -172
- package/tests/context-refs.test.ts +121 -121
- package/tests/cron-engine.test.ts +101 -101
- package/tests/daemon.test.ts +135 -135
- package/tests/deepbrain-wire.test.ts +234 -234
- package/tests/deploy-and-dag.test.ts +196 -196
- package/tests/doctor.test.ts +38 -38
- package/tests/document-processor.test.ts +69 -69
- package/tests/e2e-nocode.test.ts +442 -442
- package/tests/elevated.test.ts +69 -69
- package/tests/eval.test.ts +173 -173
- package/tests/gateway.test.ts +63 -63
- package/tests/guardrails.test.ts +177 -177
- package/tests/home-assistant.test.ts +40 -40
- package/tests/hooks.test.ts +79 -79
- package/tests/ide-bridge.test.ts +38 -38
- package/tests/image-generator.test.ts +84 -84
- package/tests/init-role.test.ts +124 -124
- package/tests/integrations.test.ts +249 -249
- package/tests/mcp-client.test.ts +92 -92
- package/tests/mcp-server.test.ts +178 -178
- package/tests/mcp-servers.test.ts +260 -260
- package/tests/node-network.test.ts +74 -74
- package/tests/plugin-a2a-enhanced.test.ts +230 -230
- package/tests/profiles.test.ts +61 -61
- package/tests/publish.test.ts +231 -231
- package/tests/rl-tools.test.ts +93 -93
- package/tests/sandbox-manager.test.ts +46 -46
- package/tests/scheduler.test.ts +200 -200
- package/tests/secrets.test.ts +107 -107
- package/tests/security-enhanced.test.ts +233 -233
- package/tests/settings-api.test.ts +148 -148
- package/tests/setup.test.ts +73 -73
- package/tests/subagent.test.ts +193 -193
- package/tests/telegram-discord.test.ts +60 -60
- package/tests/telemetry.test.ts +186 -186
- package/tests/user-profiler.test.ts +169 -169
- package/tests/v090-features.test.ts +254 -254
- package/tests/vision.test.ts +61 -61
- package/tests/voice-call.test.ts +47 -47
- package/tests/voice-enhanced.test.ts +169 -169
- package/tests/voice-interaction.test.ts +38 -38
- package/tests/web-search.test.ts +155 -155
- package/tests/workflow-graph.test.ts +279 -279
- package/tutorial/customer-service-agent/README.md +612 -612
- package/tutorial/customer-service-agent/SOUL.md +26 -26
- package/tutorial/customer-service-agent/agent.yaml +63 -63
- package/tutorial/customer-service-agent/package.json +19 -19
- package/tutorial/customer-service-agent/src/index.ts +69 -69
- package/tutorial/customer-service-agent/src/skills/faq.ts +27 -27
- package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -22
- package/tutorial/customer-service-agent/tsconfig.json +14 -14
|
@@ -1,41 +1,41 @@
|
|
|
1
|
-
import type { MCPTool, MCPToolResult } from '../mcp';
|
|
2
|
-
|
|
3
|
-
export const datetimeTool: MCPTool = {
|
|
4
|
-
name: 'datetime',
|
|
5
|
-
description: 'Get current date, time, timezone info',
|
|
6
|
-
inputSchema: {
|
|
7
|
-
type: 'object',
|
|
8
|
-
properties: {
|
|
9
|
-
format: { type: 'string', default: 'iso' },
|
|
10
|
-
timezone: { type: 'string' },
|
|
11
|
-
},
|
|
12
|
-
},
|
|
13
|
-
async execute(input: Record<string, unknown>): Promise<MCPToolResult> {
|
|
14
|
-
const now = new Date();
|
|
15
|
-
const timezone = input.timezone as string | undefined;
|
|
16
|
-
const format = (input.format as string) || 'iso';
|
|
17
|
-
|
|
18
|
-
let content: string;
|
|
19
|
-
if (format === 'iso') {
|
|
20
|
-
content = now.toISOString();
|
|
21
|
-
} else if (format === 'locale') {
|
|
22
|
-
content = timezone
|
|
23
|
-
? now.toLocaleString('en-US', { timeZone: timezone })
|
|
24
|
-
: now.toLocaleString();
|
|
25
|
-
} else if (format === 'unix') {
|
|
26
|
-
content = String(Math.floor(now.getTime() / 1000));
|
|
27
|
-
} else {
|
|
28
|
-
content = now.toISOString();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return {
|
|
32
|
-
content: JSON.stringify({
|
|
33
|
-
iso: now.toISOString(),
|
|
34
|
-
unix: Math.floor(now.getTime() / 1000),
|
|
35
|
-
formatted: content,
|
|
36
|
-
timezone: timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
37
|
-
}),
|
|
38
|
-
isError: false,
|
|
39
|
-
};
|
|
40
|
-
},
|
|
41
|
-
};
|
|
1
|
+
import type { MCPTool, MCPToolResult } from '../mcp';
|
|
2
|
+
|
|
3
|
+
export const datetimeTool: MCPTool = {
|
|
4
|
+
name: 'datetime',
|
|
5
|
+
description: 'Get current date, time, timezone info',
|
|
6
|
+
inputSchema: {
|
|
7
|
+
type: 'object',
|
|
8
|
+
properties: {
|
|
9
|
+
format: { type: 'string', default: 'iso' },
|
|
10
|
+
timezone: { type: 'string' },
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
async execute(input: Record<string, unknown>): Promise<MCPToolResult> {
|
|
14
|
+
const now = new Date();
|
|
15
|
+
const timezone = input.timezone as string | undefined;
|
|
16
|
+
const format = (input.format as string) || 'iso';
|
|
17
|
+
|
|
18
|
+
let content: string;
|
|
19
|
+
if (format === 'iso') {
|
|
20
|
+
content = now.toISOString();
|
|
21
|
+
} else if (format === 'locale') {
|
|
22
|
+
content = timezone
|
|
23
|
+
? now.toLocaleString('en-US', { timeZone: timezone })
|
|
24
|
+
: now.toLocaleString();
|
|
25
|
+
} else if (format === 'unix') {
|
|
26
|
+
content = String(Math.floor(now.getTime() / 1000));
|
|
27
|
+
} else {
|
|
28
|
+
content = now.toISOString();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return {
|
|
32
|
+
content: JSON.stringify({
|
|
33
|
+
iso: now.toISOString(),
|
|
34
|
+
unix: Math.floor(now.getTime() / 1000),
|
|
35
|
+
formatted: content,
|
|
36
|
+
timezone: timezone || Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
37
|
+
}),
|
|
38
|
+
isError: false,
|
|
39
|
+
};
|
|
40
|
+
},
|
|
41
|
+
};
|
|
@@ -1,107 +1,107 @@
|
|
|
1
|
-
import * as fs from 'fs';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import type { MCPTool, MCPToolResult } from '../mcp';
|
|
4
|
-
import type { AgentContext } from '../../core/types';
|
|
5
|
-
|
|
6
|
-
function resolveSafe(basePath: string, targetPath: string): string | null {
|
|
7
|
-
const resolved = path.resolve(basePath, targetPath);
|
|
8
|
-
if (!resolved.startsWith(path.resolve(basePath))) return null;
|
|
9
|
-
return resolved;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function searchFiles(dir: string, query: string, results: string[] = [], maxResults = 20): string[] {
|
|
13
|
-
if (results.length >= maxResults) return results;
|
|
14
|
-
try {
|
|
15
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
16
|
-
for (const entry of entries) {
|
|
17
|
-
if (results.length >= maxResults) break;
|
|
18
|
-
const full = path.join(dir, entry.name);
|
|
19
|
-
if (entry.isDirectory()) {
|
|
20
|
-
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
21
|
-
searchFiles(full, query, results, maxResults);
|
|
22
|
-
} else if (entry.isFile()) {
|
|
23
|
-
try {
|
|
24
|
-
const content = fs.readFileSync(full, 'utf-8');
|
|
25
|
-
const lines = content.split('\n');
|
|
26
|
-
for (let i = 0; i < lines.length; i++) {
|
|
27
|
-
if (lines[i].includes(query)) {
|
|
28
|
-
results.push(`${full}:${i + 1}: ${lines[i].trim()}`);
|
|
29
|
-
if (results.length >= maxResults) break;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
} catch { /* skip binary/unreadable */ }
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
} catch { /* skip inaccessible dirs */ }
|
|
36
|
-
return results;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const fileTool: MCPTool = {
|
|
40
|
-
name: 'file_operations',
|
|
41
|
-
description: 'Read, write, list, and search files in the workspace',
|
|
42
|
-
inputSchema: {
|
|
43
|
-
type: 'object',
|
|
44
|
-
properties: {
|
|
45
|
-
action: { type: 'string', enum: ['read', 'write', 'list', 'search', 'exists'] },
|
|
46
|
-
path: { type: 'string' },
|
|
47
|
-
content: { type: 'string' },
|
|
48
|
-
query: { type: 'string' },
|
|
49
|
-
},
|
|
50
|
-
required: ['action'],
|
|
51
|
-
},
|
|
52
|
-
async execute(input: Record<string, unknown>, context?: AgentContext): Promise<MCPToolResult> {
|
|
53
|
-
const action = input.action as string;
|
|
54
|
-
const workspace = process.cwd();
|
|
55
|
-
const targetPath = input.path as string | undefined;
|
|
56
|
-
|
|
57
|
-
if (action === 'search') {
|
|
58
|
-
const query = input.query as string;
|
|
59
|
-
if (!query) return { content: 'query is required for search', isError: true };
|
|
60
|
-
const results = searchFiles(workspace, query);
|
|
61
|
-
return { content: results.length ? results.join('\n') : 'No matches found', isError: false };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
if (action === 'list') {
|
|
65
|
-
const dir = targetPath ? resolveSafe(workspace, targetPath) : workspace;
|
|
66
|
-
if (!dir) return { content: 'Path outside workspace', isError: true };
|
|
67
|
-
try {
|
|
68
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
69
|
-
const listing = entries.map(e => `${e.isDirectory() ? '[DIR] ' : ''}${e.name}`).join('\n');
|
|
70
|
-
return { content: listing || '(empty directory)', isError: false };
|
|
71
|
-
} catch (err) {
|
|
72
|
-
return { content: `Error listing directory: ${err instanceof Error ? err.message : String(err)}`, isError: true };
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (!targetPath) return { content: 'path is required', isError: true };
|
|
77
|
-
const resolved = resolveSafe(workspace, targetPath);
|
|
78
|
-
if (!resolved) return { content: 'Path outside workspace', isError: true };
|
|
79
|
-
|
|
80
|
-
switch (action) {
|
|
81
|
-
case 'read': {
|
|
82
|
-
try {
|
|
83
|
-
const content = fs.readFileSync(resolved, 'utf-8');
|
|
84
|
-
return { content: content.slice(0, 50000), isError: false };
|
|
85
|
-
} catch (err) {
|
|
86
|
-
return { content: `Error reading file: ${err instanceof Error ? err.message : String(err)}`, isError: true };
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
case 'write': {
|
|
90
|
-
const content = input.content as string;
|
|
91
|
-
if (content === undefined) return { content: 'content is required for write', isError: true };
|
|
92
|
-
try {
|
|
93
|
-
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
94
|
-
fs.writeFileSync(resolved, content, 'utf-8');
|
|
95
|
-
return { content: `Written ${content.length} bytes to ${targetPath}`, isError: false };
|
|
96
|
-
} catch (err) {
|
|
97
|
-
return { content: `Error writing file: ${err instanceof Error ? err.message : String(err)}`, isError: true };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
case 'exists': {
|
|
101
|
-
return { content: String(fs.existsSync(resolved)), isError: false };
|
|
102
|
-
}
|
|
103
|
-
default:
|
|
104
|
-
return { content: `Unknown action: ${action}`, isError: true };
|
|
105
|
-
}
|
|
106
|
-
},
|
|
107
|
-
};
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import type { MCPTool, MCPToolResult } from '../mcp';
|
|
4
|
+
import type { AgentContext } from '../../core/types';
|
|
5
|
+
|
|
6
|
+
function resolveSafe(basePath: string, targetPath: string): string | null {
|
|
7
|
+
const resolved = path.resolve(basePath, targetPath);
|
|
8
|
+
if (!resolved.startsWith(path.resolve(basePath))) return null;
|
|
9
|
+
return resolved;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function searchFiles(dir: string, query: string, results: string[] = [], maxResults = 20): string[] {
|
|
13
|
+
if (results.length >= maxResults) return results;
|
|
14
|
+
try {
|
|
15
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
16
|
+
for (const entry of entries) {
|
|
17
|
+
if (results.length >= maxResults) break;
|
|
18
|
+
const full = path.join(dir, entry.name);
|
|
19
|
+
if (entry.isDirectory()) {
|
|
20
|
+
if (entry.name === 'node_modules' || entry.name === '.git') continue;
|
|
21
|
+
searchFiles(full, query, results, maxResults);
|
|
22
|
+
} else if (entry.isFile()) {
|
|
23
|
+
try {
|
|
24
|
+
const content = fs.readFileSync(full, 'utf-8');
|
|
25
|
+
const lines = content.split('\n');
|
|
26
|
+
for (let i = 0; i < lines.length; i++) {
|
|
27
|
+
if (lines[i].includes(query)) {
|
|
28
|
+
results.push(`${full}:${i + 1}: ${lines[i].trim()}`);
|
|
29
|
+
if (results.length >= maxResults) break;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
} catch { /* skip binary/unreadable */ }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
} catch { /* skip inaccessible dirs */ }
|
|
36
|
+
return results;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const fileTool: MCPTool = {
|
|
40
|
+
name: 'file_operations',
|
|
41
|
+
description: 'Read, write, list, and search files in the workspace',
|
|
42
|
+
inputSchema: {
|
|
43
|
+
type: 'object',
|
|
44
|
+
properties: {
|
|
45
|
+
action: { type: 'string', enum: ['read', 'write', 'list', 'search', 'exists'] },
|
|
46
|
+
path: { type: 'string' },
|
|
47
|
+
content: { type: 'string' },
|
|
48
|
+
query: { type: 'string' },
|
|
49
|
+
},
|
|
50
|
+
required: ['action'],
|
|
51
|
+
},
|
|
52
|
+
async execute(input: Record<string, unknown>, context?: AgentContext): Promise<MCPToolResult> {
|
|
53
|
+
const action = input.action as string;
|
|
54
|
+
const workspace = process.cwd();
|
|
55
|
+
const targetPath = input.path as string | undefined;
|
|
56
|
+
|
|
57
|
+
if (action === 'search') {
|
|
58
|
+
const query = input.query as string;
|
|
59
|
+
if (!query) return { content: 'query is required for search', isError: true };
|
|
60
|
+
const results = searchFiles(workspace, query);
|
|
61
|
+
return { content: results.length ? results.join('\n') : 'No matches found', isError: false };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (action === 'list') {
|
|
65
|
+
const dir = targetPath ? resolveSafe(workspace, targetPath) : workspace;
|
|
66
|
+
if (!dir) return { content: 'Path outside workspace', isError: true };
|
|
67
|
+
try {
|
|
68
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
69
|
+
const listing = entries.map(e => `${e.isDirectory() ? '[DIR] ' : ''}${e.name}`).join('\n');
|
|
70
|
+
return { content: listing || '(empty directory)', isError: false };
|
|
71
|
+
} catch (err) {
|
|
72
|
+
return { content: `Error listing directory: ${err instanceof Error ? err.message : String(err)}`, isError: true };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (!targetPath) return { content: 'path is required', isError: true };
|
|
77
|
+
const resolved = resolveSafe(workspace, targetPath);
|
|
78
|
+
if (!resolved) return { content: 'Path outside workspace', isError: true };
|
|
79
|
+
|
|
80
|
+
switch (action) {
|
|
81
|
+
case 'read': {
|
|
82
|
+
try {
|
|
83
|
+
const content = fs.readFileSync(resolved, 'utf-8');
|
|
84
|
+
return { content: content.slice(0, 50000), isError: false };
|
|
85
|
+
} catch (err) {
|
|
86
|
+
return { content: `Error reading file: ${err instanceof Error ? err.message : String(err)}`, isError: true };
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
case 'write': {
|
|
90
|
+
const content = input.content as string;
|
|
91
|
+
if (content === undefined) return { content: 'content is required for write', isError: true };
|
|
92
|
+
try {
|
|
93
|
+
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
94
|
+
fs.writeFileSync(resolved, content, 'utf-8');
|
|
95
|
+
return { content: `Written ${content.length} bytes to ${targetPath}`, isError: false };
|
|
96
|
+
} catch (err) {
|
|
97
|
+
return { content: `Error writing file: ${err instanceof Error ? err.message : String(err)}`, isError: true };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
case 'exists': {
|
|
101
|
+
return { content: String(fs.existsSync(resolved)), isError: false };
|
|
102
|
+
}
|
|
103
|
+
default:
|
|
104
|
+
return { content: `Unknown action: ${action}`, isError: true };
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
};
|
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
import type { MCPTool, MCPToolResult } from '../mcp';
|
|
2
|
-
|
|
3
|
-
export interface HomeAssistantConfig {
|
|
4
|
-
url: string;
|
|
5
|
-
token: string;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
let haConfig: HomeAssistantConfig | null = null;
|
|
9
|
-
|
|
10
|
-
export function configureHomeAssistant(config: HomeAssistantConfig): void {
|
|
11
|
-
haConfig = config;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async function haFetch(path: string, options?: RequestInit): Promise<any> {
|
|
15
|
-
if (!haConfig) throw new Error('Home Assistant not configured. Call configureHomeAssistant({ url, token }) first.');
|
|
16
|
-
const resp = await fetch(`${haConfig.url}/api${path}`, {
|
|
17
|
-
...options,
|
|
18
|
-
headers: { Authorization: `Bearer ${haConfig.token}`, 'Content-Type': 'application/json', ...options?.headers as Record<string, string> },
|
|
19
|
-
});
|
|
20
|
-
if (!resp.ok) throw new Error(`HA API error: ${resp.status} ${resp.statusText}`);
|
|
21
|
-
return resp.json();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const haGetStates: MCPTool = {
|
|
25
|
-
name: 'ha_get_states',
|
|
26
|
-
description: 'Get all Home Assistant entity states or filter by domain',
|
|
27
|
-
inputSchema: {
|
|
28
|
-
type: 'object',
|
|
29
|
-
properties: { domain: { type: 'string', description: 'Filter by domain (e.g. light, switch)' } },
|
|
30
|
-
},
|
|
31
|
-
async execute(input): Promise<MCPToolResult> {
|
|
32
|
-
try {
|
|
33
|
-
let states = await haFetch('/states');
|
|
34
|
-
if (input.domain) states = states.filter((s: any) => s.entity_id.startsWith(`${input.domain}.`));
|
|
35
|
-
return { content: JSON.stringify(states.map((s: any) => ({ entity_id: s.entity_id, state: s.state, attributes: s.attributes }))) };
|
|
36
|
-
} catch (e: any) { return { content: e.message, isError: true }; }
|
|
37
|
-
},
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export const haCallService: MCPTool = {
|
|
41
|
-
name: 'ha_call_service',
|
|
42
|
-
description: 'Call a Home Assistant service (turn_on, turn_off, toggle, etc.)',
|
|
43
|
-
inputSchema: {
|
|
44
|
-
type: 'object',
|
|
45
|
-
properties: {
|
|
46
|
-
domain: { type: 'string' },
|
|
47
|
-
service: { type: 'string' },
|
|
48
|
-
entity_id: { type: 'string' },
|
|
49
|
-
data: { type: 'object' },
|
|
50
|
-
},
|
|
51
|
-
required: ['domain', 'service'],
|
|
52
|
-
},
|
|
53
|
-
async execute(input): Promise<MCPToolResult> {
|
|
54
|
-
try {
|
|
55
|
-
const body: any = {};
|
|
56
|
-
if (input.entity_id) body.entity_id = input.entity_id;
|
|
57
|
-
if (input.data) Object.assign(body, input.data);
|
|
58
|
-
const result = await haFetch(`/services/${input.domain}/${input.service}`, {
|
|
59
|
-
method: 'POST', body: JSON.stringify(body),
|
|
60
|
-
});
|
|
61
|
-
return { content: JSON.stringify({ success: true, result }) };
|
|
62
|
-
} catch (e: any) { return { content: e.message, isError: true }; }
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
export const haGetHistory: MCPTool = {
|
|
67
|
-
name: 'ha_get_history',
|
|
68
|
-
description: 'Get entity history for a time period',
|
|
69
|
-
inputSchema: {
|
|
70
|
-
type: 'object',
|
|
71
|
-
properties: {
|
|
72
|
-
entity_id: { type: 'string' },
|
|
73
|
-
start: { type: 'string', description: 'ISO datetime' },
|
|
74
|
-
end: { type: 'string', description: 'ISO datetime' },
|
|
75
|
-
},
|
|
76
|
-
required: ['entity_id'],
|
|
77
|
-
},
|
|
78
|
-
async execute(input): Promise<MCPToolResult> {
|
|
79
|
-
try {
|
|
80
|
-
const start = input.start || new Date(Date.now() - 86400000).toISOString();
|
|
81
|
-
let path = `/history/period/${start}?filter_entity_id=${input.entity_id}`;
|
|
82
|
-
if (input.end) path += `&end_time=${input.end}`;
|
|
83
|
-
const result = await haFetch(path);
|
|
84
|
-
return { content: JSON.stringify(result) };
|
|
85
|
-
} catch (e: any) { return { content: e.message, isError: true }; }
|
|
86
|
-
},
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export const haAutomation: MCPTool = {
|
|
90
|
-
name: 'ha_automation',
|
|
91
|
-
description: 'Trigger or manage Home Assistant automations',
|
|
92
|
-
inputSchema: {
|
|
93
|
-
type: 'object',
|
|
94
|
-
properties: {
|
|
95
|
-
action: { type: 'string', enum: ['trigger', 'turn_on', 'turn_off', 'list'] },
|
|
96
|
-
automation_id: { type: 'string' },
|
|
97
|
-
},
|
|
98
|
-
required: ['action'],
|
|
99
|
-
},
|
|
100
|
-
async execute(input): Promise<MCPToolResult> {
|
|
101
|
-
try {
|
|
102
|
-
if (input.action === 'list') {
|
|
103
|
-
const states = await haFetch('/states');
|
|
104
|
-
const automations = states.filter((s: any) => s.entity_id.startsWith('automation.'));
|
|
105
|
-
return { content: JSON.stringify(automations.map((a: any) => ({ entity_id: a.entity_id, state: a.state }))) };
|
|
106
|
-
}
|
|
107
|
-
if (!input.automation_id) return { content: 'automation_id required for this action', isError: true };
|
|
108
|
-
const result = await haFetch(`/services/automation/${input.action}`, {
|
|
109
|
-
method: 'POST', body: JSON.stringify({ entity_id: input.automation_id }),
|
|
110
|
-
});
|
|
111
|
-
return { content: JSON.stringify({ success: true, result }) };
|
|
112
|
-
} catch (e: any) { return { content: e.message, isError: true }; }
|
|
113
|
-
},
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
export const homeAssistantTools: MCPTool[] = [haGetStates, haCallService, haGetHistory, haAutomation];
|
|
1
|
+
import type { MCPTool, MCPToolResult } from '../mcp';
|
|
2
|
+
|
|
3
|
+
export interface HomeAssistantConfig {
|
|
4
|
+
url: string;
|
|
5
|
+
token: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let haConfig: HomeAssistantConfig | null = null;
|
|
9
|
+
|
|
10
|
+
export function configureHomeAssistant(config: HomeAssistantConfig): void {
|
|
11
|
+
haConfig = config;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function haFetch(path: string, options?: RequestInit): Promise<any> {
|
|
15
|
+
if (!haConfig) throw new Error('Home Assistant not configured. Call configureHomeAssistant({ url, token }) first.');
|
|
16
|
+
const resp = await fetch(`${haConfig.url}/api${path}`, {
|
|
17
|
+
...options,
|
|
18
|
+
headers: { Authorization: `Bearer ${haConfig.token}`, 'Content-Type': 'application/json', ...options?.headers as Record<string, string> },
|
|
19
|
+
});
|
|
20
|
+
if (!resp.ok) throw new Error(`HA API error: ${resp.status} ${resp.statusText}`);
|
|
21
|
+
return resp.json();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const haGetStates: MCPTool = {
|
|
25
|
+
name: 'ha_get_states',
|
|
26
|
+
description: 'Get all Home Assistant entity states or filter by domain',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: { domain: { type: 'string', description: 'Filter by domain (e.g. light, switch)' } },
|
|
30
|
+
},
|
|
31
|
+
async execute(input): Promise<MCPToolResult> {
|
|
32
|
+
try {
|
|
33
|
+
let states = await haFetch('/states');
|
|
34
|
+
if (input.domain) states = states.filter((s: any) => s.entity_id.startsWith(`${input.domain}.`));
|
|
35
|
+
return { content: JSON.stringify(states.map((s: any) => ({ entity_id: s.entity_id, state: s.state, attributes: s.attributes }))) };
|
|
36
|
+
} catch (e: any) { return { content: e.message, isError: true }; }
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export const haCallService: MCPTool = {
|
|
41
|
+
name: 'ha_call_service',
|
|
42
|
+
description: 'Call a Home Assistant service (turn_on, turn_off, toggle, etc.)',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
domain: { type: 'string' },
|
|
47
|
+
service: { type: 'string' },
|
|
48
|
+
entity_id: { type: 'string' },
|
|
49
|
+
data: { type: 'object' },
|
|
50
|
+
},
|
|
51
|
+
required: ['domain', 'service'],
|
|
52
|
+
},
|
|
53
|
+
async execute(input): Promise<MCPToolResult> {
|
|
54
|
+
try {
|
|
55
|
+
const body: any = {};
|
|
56
|
+
if (input.entity_id) body.entity_id = input.entity_id;
|
|
57
|
+
if (input.data) Object.assign(body, input.data);
|
|
58
|
+
const result = await haFetch(`/services/${input.domain}/${input.service}`, {
|
|
59
|
+
method: 'POST', body: JSON.stringify(body),
|
|
60
|
+
});
|
|
61
|
+
return { content: JSON.stringify({ success: true, result }) };
|
|
62
|
+
} catch (e: any) { return { content: e.message, isError: true }; }
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const haGetHistory: MCPTool = {
|
|
67
|
+
name: 'ha_get_history',
|
|
68
|
+
description: 'Get entity history for a time period',
|
|
69
|
+
inputSchema: {
|
|
70
|
+
type: 'object',
|
|
71
|
+
properties: {
|
|
72
|
+
entity_id: { type: 'string' },
|
|
73
|
+
start: { type: 'string', description: 'ISO datetime' },
|
|
74
|
+
end: { type: 'string', description: 'ISO datetime' },
|
|
75
|
+
},
|
|
76
|
+
required: ['entity_id'],
|
|
77
|
+
},
|
|
78
|
+
async execute(input): Promise<MCPToolResult> {
|
|
79
|
+
try {
|
|
80
|
+
const start = input.start || new Date(Date.now() - 86400000).toISOString();
|
|
81
|
+
let path = `/history/period/${start}?filter_entity_id=${input.entity_id}`;
|
|
82
|
+
if (input.end) path += `&end_time=${input.end}`;
|
|
83
|
+
const result = await haFetch(path);
|
|
84
|
+
return { content: JSON.stringify(result) };
|
|
85
|
+
} catch (e: any) { return { content: e.message, isError: true }; }
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const haAutomation: MCPTool = {
|
|
90
|
+
name: 'ha_automation',
|
|
91
|
+
description: 'Trigger or manage Home Assistant automations',
|
|
92
|
+
inputSchema: {
|
|
93
|
+
type: 'object',
|
|
94
|
+
properties: {
|
|
95
|
+
action: { type: 'string', enum: ['trigger', 'turn_on', 'turn_off', 'list'] },
|
|
96
|
+
automation_id: { type: 'string' },
|
|
97
|
+
},
|
|
98
|
+
required: ['action'],
|
|
99
|
+
},
|
|
100
|
+
async execute(input): Promise<MCPToolResult> {
|
|
101
|
+
try {
|
|
102
|
+
if (input.action === 'list') {
|
|
103
|
+
const states = await haFetch('/states');
|
|
104
|
+
const automations = states.filter((s: any) => s.entity_id.startsWith('automation.'));
|
|
105
|
+
return { content: JSON.stringify(automations.map((a: any) => ({ entity_id: a.entity_id, state: a.state }))) };
|
|
106
|
+
}
|
|
107
|
+
if (!input.automation_id) return { content: 'automation_id required for this action', isError: true };
|
|
108
|
+
const result = await haFetch(`/services/automation/${input.action}`, {
|
|
109
|
+
method: 'POST', body: JSON.stringify({ entity_id: input.automation_id }),
|
|
110
|
+
});
|
|
111
|
+
return { content: JSON.stringify({ success: true, result }) };
|
|
112
|
+
} catch (e: any) { return { content: e.message, isError: true }; }
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export const homeAssistantTools: MCPTool[] = [haGetStates, haCallService, haGetHistory, haAutomation];
|