fotric-claw 0.1.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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +276 -0
  3. package/backend/.env.example +26 -0
  4. package/backend/nest-cli.json +8 -0
  5. package/backend/package-lock.json +13239 -0
  6. package/backend/package.json +82 -0
  7. package/backend/src/agent/agent.module.ts +10 -0
  8. package/backend/src/agent/agent.service.ts +210 -0
  9. package/backend/src/agent/index.ts +4 -0
  10. package/backend/src/agent/llm.factory.ts +20 -0
  11. package/backend/src/agent/tools/fetch.tool.ts +128 -0
  12. package/backend/src/agent/tools/file-read.tool.ts +99 -0
  13. package/backend/src/agent/tools/index.ts +55 -0
  14. package/backend/src/agent/tools/node-repl.tool.ts +82 -0
  15. package/backend/src/agent/tools/rag.tool.ts +192 -0
  16. package/backend/src/agent/tools/shell.tool.ts +65 -0
  17. package/backend/src/app.module.ts +26 -0
  18. package/backend/src/chat/chat.controller.ts +34 -0
  19. package/backend/src/chat/chat.module.ts +12 -0
  20. package/backend/src/chat/chat.service.ts +52 -0
  21. package/backend/src/chat/dto/chat.dto.ts +12 -0
  22. package/backend/src/chat/dto/index.ts +1 -0
  23. package/backend/src/chat/index.ts +4 -0
  24. package/backend/src/config/config.controller.ts +92 -0
  25. package/backend/src/config/config.module.ts +7 -0
  26. package/backend/src/config/constants.ts +56 -0
  27. package/backend/src/config/index.ts +3 -0
  28. package/backend/src/files/files.controller.ts +87 -0
  29. package/backend/src/files/files.module.ts +7 -0
  30. package/backend/src/files/index.ts +2 -0
  31. package/backend/src/main.ts +21 -0
  32. package/backend/src/memory/index.ts +3 -0
  33. package/backend/src/memory/memory.module.ts +10 -0
  34. package/backend/src/memory/memory.service.ts +329 -0
  35. package/backend/src/memory/memory.types.ts +38 -0
  36. package/backend/src/sessions/default.json +7 -0
  37. package/backend/src/sessions/index.ts +2 -0
  38. package/backend/src/sessions/main_session.json +40 -0
  39. package/backend/src/sessions/sessions.controller.ts +25 -0
  40. package/backend/src/sessions/sessions.module.ts +9 -0
  41. package/backend/src/sessions/test.json +16 -0
  42. package/backend/src/skills/browser_search/SKILL.md +81 -0
  43. package/backend/src/skills/get_weather/SKILL.md +72 -0
  44. package/backend/src/skills/index.ts +3 -0
  45. package/backend/src/skills/skill.types.ts +27 -0
  46. package/backend/src/skills/skills.module.ts +8 -0
  47. package/backend/src/skills/skills.service.ts +139 -0
  48. package/backend/src/skills/web_search/SKILL.md +76 -0
  49. package/backend/src/workspace/AGENTS.md +47 -0
  50. package/backend/src/workspace/IDENTITY.md +32 -0
  51. package/backend/src/workspace/MEMORY.md +15 -0
  52. package/backend/src/workspace/SOUL.md +29 -0
  53. package/backend/src/workspace/USER.md +8 -0
  54. package/backend/tsconfig.build.json +4 -0
  55. package/backend/tsconfig.json +26 -0
  56. package/bin/fotric-claw.js +281 -0
  57. package/frontend/next.config.js +14 -0
  58. package/frontend/package-lock.json +5700 -0
  59. package/frontend/package.json +33 -0
  60. package/frontend/postcss.config.js +6 -0
  61. package/frontend/src/app/globals.css +41 -0
  62. package/frontend/src/app/layout.tsx +22 -0
  63. package/frontend/src/app/page.tsx +405 -0
  64. package/frontend/src/lib/api.ts +157 -0
  65. package/frontend/src/lib/utils.ts +3 -0
  66. package/frontend/tailwind.config.js +32 -0
  67. package/frontend/tsconfig.json +26 -0
  68. package/knowledge/README.md +21 -0
  69. package/package.json +49 -0
  70. package/scripts/init-skills.ts +95 -0
  71. package/storage/.gitkeep +5 -0
@@ -0,0 +1,21 @@
1
+ import { NestFactory } from '@nestjs/core';
2
+ import { AppModule } from './app.module';
3
+ import { Logger } from '@nestjs/common';
4
+
5
+ async function bootstrap() {
6
+ const logger = new Logger('FOTRIC-CLAW');
7
+ const app = await NestFactory.create(AppModule);
8
+
9
+ app.enableCors({
10
+ origin: ['http://localhost:3000', 'http://127.0.0.1:3000'],
11
+ credentials: true,
12
+ });
13
+
14
+ const port = process.env.PORT ?? 8002;
15
+ await app.listen(port);
16
+
17
+ logger.log(`[FOTRIC-CLAW] Server is running on http://localhost:${port}`);
18
+ logger.log(`[FOTRIC-CLAW] API endpoint: http://localhost:${port}/api`);
19
+ }
20
+
21
+ bootstrap();
@@ -0,0 +1,3 @@
1
+ export * from './memory.types';
2
+ export * from './memory.service';
3
+ export * from './memory.module';
@@ -0,0 +1,10 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { MemoryService } from './memory.service';
3
+ import { SkillsModule } from '../skills';
4
+
5
+ @Module({
6
+ imports: [SkillsModule],
7
+ providers: [MemoryService],
8
+ exports: [MemoryService],
9
+ })
10
+ export class MemoryModule {}
@@ -0,0 +1,329 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
4
+ import { FOTRIC_CONFIG } from '../config';
5
+ import { SkillsService } from '../skills';
6
+ import { MemoryFile, SystemPromptConfig, Session, SessionMessage } from './memory.types';
7
+
8
+ const MEMORY_FILES = [
9
+ { name: 'SKILLS_SNAPSHOT', fileName: 'SKILLS_SNAPSHOT.md', order: 1, dynamic: true },
10
+ { name: 'SOUL', fileName: 'SOUL.md', order: 2 },
11
+ { name: 'IDENTITY', fileName: 'IDENTITY.md', order: 3 },
12
+ { name: 'USER', fileName: 'USER.md', order: 4 },
13
+ { name: 'AGENTS', fileName: 'AGENTS.md', order: 5 },
14
+ { name: 'MEMORY', fileName: 'MEMORY.md', order: 6 },
15
+ ];
16
+
17
+ @Injectable()
18
+ export class MemoryService implements OnModuleInit {
19
+ private readonly logger = new Logger('FOTRIC-CLAW:Memory');
20
+ private workspaceDir: string;
21
+ private sessionsDir: string;
22
+ private memoryCache: Map<string, string> = new Map();
23
+
24
+ constructor(private readonly skillsService: SkillsService) {
25
+ this.workspaceDir = path.resolve(FOTRIC_CONFIG.PATHS.WORKSPACE_DIR);
26
+ this.sessionsDir = path.resolve(FOTRIC_CONFIG.PATHS.SESSIONS_DIR);
27
+ }
28
+
29
+ async onModuleInit() {
30
+ await this.ensureDirectories();
31
+ await this.initializeDefaultFiles();
32
+ await this.loadMemoryFiles();
33
+ }
34
+
35
+ private async ensureDirectories(): Promise<void> {
36
+ await fs.mkdir(this.workspaceDir, { recursive: true });
37
+ await fs.mkdir(this.sessionsDir, { recursive: true });
38
+ }
39
+
40
+ private async initializeDefaultFiles(): Promise<void> {
41
+ const defaultFiles: Record<string, string> = {
42
+ 'SOUL.md': `# Core Soul
43
+
44
+ You are FotricCalw, a lightweight and transparent AI Agent system.
45
+
46
+ ## Core Values
47
+ - Transparency: All operations are visible and explainable
48
+ - File-first Memory: Using human-readable files for all data
49
+ - Skills as Plugins: Extensible through markdown-based skill definitions
50
+
51
+ ## Behavior Guidelines
52
+ - Always be helpful and honest
53
+ - Explain your reasoning when asked
54
+ - Use tools appropriately to accomplish tasks
55
+ `,
56
+ 'IDENTITY.md': `# Identity
57
+
58
+ I am FotricCalw, an AI assistant with transparent memory and skill systems.
59
+
60
+ ## Capabilities
61
+ - Execute shell commands safely
62
+ - Run JavaScript code in a sandbox
63
+ - Fetch and analyze web content
64
+ - Read and write files
65
+ - Search the knowledge base
66
+
67
+ ## Limitations
68
+ - Cannot access files outside the project directory
69
+ - Cannot execute dangerous system commands
70
+ - Memory is limited to configured limits
71
+ `,
72
+ 'USER.md': `# User Profile
73
+
74
+ ## Preferences
75
+ - Language: User's preferred language
76
+ - Response style: Clear and educational
77
+
78
+ ## Notes
79
+ Add user-specific information here.
80
+ `,
81
+ 'AGENTS.md': `# Agent Behavior Guidelines
82
+
83
+ ## Skill Invocation Protocol (SKILL PROTOCOL)
84
+
85
+ You have access to a skills list (SKILLS_SNAPSHOT), which contains available capabilities and their definition file locations.
86
+
87
+ **When you want to use a skill, you MUST follow these steps:**
88
+
89
+ 1. Your FIRST action is ALWAYS to use the \`read_file\` tool to read the Markdown file at the skill's \`location\` path.
90
+ 2. Carefully read the content, steps, and examples in the file.
91
+ 3. Based on the instructions in the file, combine with your built-in Core Tools (terminal, node_repl, fetch_url) to execute the specific task.
92
+
93
+ **FORBIDDEN** to directly guess skill parameters or usage - you MUST read the file first!
94
+
95
+ ## Available Core Tools
96
+
97
+ 1. **terminal** - Execute shell commands in a sandboxed environment
98
+ 2. **node_repl** - Execute JavaScript code in a sandboxed Node.js environment
99
+ 3. **fetch_url** - Fetch content from URLs and extract text
100
+ 4. **read_file** - Read local file contents
101
+ 5. **search_knowledge_base** - Search the local knowledge base
102
+
103
+ ## Memory Operations
104
+
105
+ - All conversations are stored as session files
106
+ - Long-term memory is stored in MEMORY.md
107
+ - You can read and update memory files as needed
108
+ `,
109
+ 'MEMORY.md': `# Long-term Memory
110
+
111
+ This file stores persistent information that should be remembered across sessions.
112
+
113
+ ## Important Information
114
+
115
+ (Add important information here as you interact with users)
116
+
117
+ ## User Preferences
118
+
119
+ (Record user preferences and patterns)
120
+
121
+ ## Task History
122
+
123
+ (Keep track of important tasks and their outcomes)
124
+ `,
125
+ };
126
+
127
+ for (const [fileName, defaultContent] of Object.entries(defaultFiles)) {
128
+ const filePath = path.join(this.workspaceDir, fileName);
129
+ try {
130
+ await fs.access(filePath);
131
+ } catch {
132
+ await fs.writeFile(filePath, defaultContent, 'utf-8');
133
+ this.logger.log(`Created default file: ${fileName}`);
134
+ }
135
+ }
136
+ }
137
+
138
+ private async loadMemoryFiles(): Promise<void> {
139
+ for (const file of MEMORY_FILES) {
140
+ if (file.dynamic) continue;
141
+
142
+ const filePath = path.join(this.workspaceDir, file.fileName);
143
+ try {
144
+ const content = await fs.readFile(filePath, 'utf-8');
145
+ this.memoryCache.set(file.name, content);
146
+ } catch {
147
+ this.memoryCache.set(file.name, '');
148
+ }
149
+ }
150
+ }
151
+
152
+ async getMemoryFile(name: string): Promise<string> {
153
+ if (name === 'SKILLS_SNAPSHOT') {
154
+ return this.skillsService.generateSkillsSnapshotMarkdown();
155
+ }
156
+
157
+ const cached = this.memoryCache.get(name);
158
+ if (cached !== undefined) {
159
+ return cached;
160
+ }
161
+
162
+ const fileInfo = MEMORY_FILES.find(f => f.name === name);
163
+ if (!fileInfo) {
164
+ return '';
165
+ }
166
+
167
+ const filePath = path.join(this.workspaceDir, fileInfo.fileName);
168
+ try {
169
+ const content = await fs.readFile(filePath, 'utf-8');
170
+ this.memoryCache.set(name, content);
171
+ return content;
172
+ } catch {
173
+ return '';
174
+ }
175
+ }
176
+
177
+ async updateMemoryFile(name: string, content: string): Promise<void> {
178
+ const fileInfo = MEMORY_FILES.find(f => f.name === name);
179
+ if (!fileInfo || fileInfo.dynamic) {
180
+ throw new Error(`Cannot update memory file: ${name}`);
181
+ }
182
+
183
+ const filePath = path.join(this.workspaceDir, fileInfo.fileName);
184
+ await fs.writeFile(filePath, content, 'utf-8');
185
+ this.memoryCache.set(name, content);
186
+ this.logger.log(`Updated memory file: ${name}`);
187
+ }
188
+
189
+ async buildSystemPrompt(config: SystemPromptConfig = {}): Promise<string> {
190
+ const {
191
+ includeSkills = true,
192
+ includeSoul = true,
193
+ includeIdentity = true,
194
+ includeUser = true,
195
+ includeAgents = true,
196
+ includeMemory = true,
197
+ } = config;
198
+
199
+ const parts: string[] = [];
200
+
201
+ if (includeSkills) {
202
+ const skillsSnapshot = await this.getMemoryFile('SKILLS_SNAPSHOT');
203
+ if (skillsSnapshot) {
204
+ parts.push(`## Available Skills\n\n${skillsSnapshot}`);
205
+ }
206
+ }
207
+
208
+ if (includeSoul) {
209
+ const soul = await this.getMemoryFile('SOUL');
210
+ if (soul) {
211
+ parts.push(`## Core Soul\n\n${soul}`);
212
+ }
213
+ }
214
+
215
+ if (includeIdentity) {
216
+ const identity = await this.getMemoryFile('IDENTITY');
217
+ if (identity) {
218
+ parts.push(`## Identity\n\n${identity}`);
219
+ }
220
+ }
221
+
222
+ if (includeUser) {
223
+ const user = await this.getMemoryFile('USER');
224
+ if (user) {
225
+ parts.push(`## User Profile\n\n${user}`);
226
+ }
227
+ }
228
+
229
+ if (includeAgents) {
230
+ const agents = await this.getMemoryFile('AGENTS');
231
+ if (agents) {
232
+ parts.push(`## Agent Guidelines\n\n${agents}`);
233
+ }
234
+ }
235
+
236
+ if (includeMemory) {
237
+ const memory = await this.getMemoryFile('MEMORY');
238
+ if (memory) {
239
+ parts.push(`## Long-term Memory\n\n${memory}`);
240
+ }
241
+ }
242
+
243
+ const fullPrompt = parts.join('\n\n---\n\n');
244
+
245
+ if (fullPrompt.length > FOTRIC_CONFIG.MEMORY.MAX_CHARS) {
246
+ return fullPrompt.substring(0, FOTRIC_CONFIG.MEMORY.MAX_CHARS) + '\n\n...[truncated]';
247
+ }
248
+
249
+ return fullPrompt;
250
+ }
251
+
252
+ async getSession(sessionId: string): Promise<Session | null> {
253
+ const sessionPath = path.join(this.sessionsDir, `${sessionId}.json`);
254
+ try {
255
+ const content = await fs.readFile(sessionPath, 'utf-8');
256
+ return JSON.parse(content);
257
+ } catch {
258
+ return null;
259
+ }
260
+ }
261
+
262
+ async saveSession(session: Session): Promise<void> {
263
+ const sessionPath = path.join(this.sessionsDir, `${session.id}.json`);
264
+ session.updatedAt = new Date().toISOString();
265
+ await fs.writeFile(sessionPath, JSON.stringify(session, null, 2), 'utf-8');
266
+ }
267
+
268
+ async createSession(sessionId: string, name?: string): Promise<Session> {
269
+ const session: Session = {
270
+ id: sessionId,
271
+ name: name || `Session ${sessionId}`,
272
+ messages: [],
273
+ createdAt: new Date().toISOString(),
274
+ updatedAt: new Date().toISOString(),
275
+ };
276
+ await this.saveSession(session);
277
+ return session;
278
+ }
279
+
280
+ async addMessageToSession(sessionId: string, message: SessionMessage): Promise<Session> {
281
+ let session = await this.getSession(sessionId);
282
+ if (!session) {
283
+ session = await this.createSession(sessionId);
284
+ }
285
+ session.messages.push(message);
286
+ await this.saveSession(session);
287
+ return session;
288
+ }
289
+
290
+ async listSessions(): Promise<Array<{ id: string; name: string; updatedAt: string; messageCount: number }>> {
291
+ const files = await fs.readdir(this.sessionsDir);
292
+ const sessions: Array<{ id: string; name: string; updatedAt: string; messageCount: number }> = [];
293
+
294
+ for (const file of files) {
295
+ if (!file.endsWith('.json')) continue;
296
+
297
+ const sessionPath = path.join(this.sessionsDir, file);
298
+ try {
299
+ const content = await fs.readFile(sessionPath, 'utf-8');
300
+ const session: Session = JSON.parse(content);
301
+ sessions.push({
302
+ id: session.id,
303
+ name: session.name,
304
+ updatedAt: session.updatedAt,
305
+ messageCount: session.messages.length,
306
+ });
307
+ } catch {
308
+ continue;
309
+ }
310
+ }
311
+
312
+ return sessions.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
313
+ }
314
+
315
+ async deleteSession(sessionId: string): Promise<boolean> {
316
+ const sessionPath = path.join(this.sessionsDir, `${sessionId}.json`);
317
+ try {
318
+ await fs.unlink(sessionPath);
319
+ return true;
320
+ } catch {
321
+ return false;
322
+ }
323
+ }
324
+
325
+ async reloadMemoryFiles(): Promise<void> {
326
+ await this.loadMemoryFiles();
327
+ this.logger.log('Memory files reloaded');
328
+ }
329
+ }
@@ -0,0 +1,38 @@
1
+ export interface MemoryFile {
2
+ name: string;
3
+ path: string;
4
+ content: string;
5
+ order: number;
6
+ }
7
+
8
+ export interface SystemPromptConfig {
9
+ includeSkills?: boolean;
10
+ includeSoul?: boolean;
11
+ includeIdentity?: boolean;
12
+ includeUser?: boolean;
13
+ includeAgents?: boolean;
14
+ includeMemory?: boolean;
15
+ }
16
+
17
+ export interface SessionMessage {
18
+ role: 'user' | 'assistant' | 'system' | 'tool';
19
+ content: string;
20
+ name?: string;
21
+ toolCallId?: string;
22
+ toolCalls?: Array<{
23
+ id: string;
24
+ type: 'function';
25
+ function: {
26
+ name: string;
27
+ arguments: string;
28
+ };
29
+ }>;
30
+ }
31
+
32
+ export interface Session {
33
+ id: string;
34
+ name: string;
35
+ messages: SessionMessage[];
36
+ createdAt: string;
37
+ updatedAt: string;
38
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "id": "default",
3
+ "name": "Session default",
4
+ "messages": [],
5
+ "createdAt": "2026-02-27T14:30:17.725Z",
6
+ "updatedAt": "2026-02-27T14:30:17.726Z"
7
+ }
@@ -0,0 +1,2 @@
1
+ export * from './sessions.controller';
2
+ export * from './sessions.module';
@@ -0,0 +1,40 @@
1
+ {
2
+ "id": "main_session",
3
+ "name": "Session main_session",
4
+ "messages": [
5
+ {
6
+ "role": "user",
7
+ "content": "打开浏览器搜索人工智能"
8
+ },
9
+ {
10
+ "role": "assistant",
11
+ "content": "我已经为您打开了浏览器并搜索了人工智能。"
12
+ },
13
+ {
14
+ "role": "user",
15
+ "content": "今天上海天气怎么样"
16
+ },
17
+ {
18
+ "role": "assistant",
19
+ "content": "任务已执行完成。工具执行结果:{\n \"generationtime_ms\": 0.21731853\n}"
20
+ },
21
+ {
22
+ "role": "user",
23
+ "content": "今天上海天气怎么样"
24
+ },
25
+ {
26
+ "role": "assistant",
27
+ "content": "今天上海的天气情况如下:\n\n- 天气:多云\n- 温度:16/10℃\n- 风力:<3级\n\n接下来几天的天气预报:\n- 明天:小雨转中雨,13/9℃\n- 后天:多云转阴,12/8℃\n- 周二:阴,11/7℃\n- 周三:小雨,13/7℃\n- 周四:小雨转阴,11/6℃\n- 周五:阴,12/5℃\n\n请注意保暖并携带雨具!"
28
+ },
29
+ {
30
+ "role": "user",
31
+ "content": "打开浏览器搜索人工智能"
32
+ },
33
+ {
34
+ "role": "assistant",
35
+ "content": "我已经为您打开了浏览器并搜索了人工智能。"
36
+ }
37
+ ],
38
+ "createdAt": "2026-02-27T14:21:52.360Z",
39
+ "updatedAt": "2026-02-28T02:31:05.308Z"
40
+ }
@@ -0,0 +1,25 @@
1
+ import { Controller, Get, Delete, Param } from '@nestjs/common';
2
+ import { MemoryService } from '../memory';
3
+
4
+ @Controller('api/sessions')
5
+ export class SessionsController {
6
+ constructor(private readonly memoryService: MemoryService) {}
7
+
8
+ @Get()
9
+ async listSessions() {
10
+ const sessions = await this.memoryService.listSessions();
11
+ return { sessions };
12
+ }
13
+
14
+ @Get(':id')
15
+ async getSession(@Param('id') id: string) {
16
+ const session = await this.memoryService.getSession(id);
17
+ return { session };
18
+ }
19
+
20
+ @Delete(':id')
21
+ async deleteSession(@Param('id') id: string) {
22
+ const success = await this.memoryService.deleteSession(id);
23
+ return { success };
24
+ }
25
+ }
@@ -0,0 +1,9 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { SessionsController } from './sessions.controller';
3
+ import { MemoryModule } from '../memory';
4
+
5
+ @Module({
6
+ imports: [MemoryModule],
7
+ controllers: [SessionsController],
8
+ })
9
+ export class SessionsModule {}
@@ -0,0 +1,16 @@
1
+ {
2
+ "id": "test",
3
+ "name": "Session test",
4
+ "messages": [
5
+ {
6
+ "role": "user",
7
+ "content": "??"
8
+ },
9
+ {
10
+ "role": "assistant",
11
+ "content": "您好!看起来您输入了\"??\", 请问有什么我可以帮您的吗?如果您有任何问题或需要信息,请尽管告诉我!"
12
+ }
13
+ ],
14
+ "createdAt": "2026-02-27T14:35:06.777Z",
15
+ "updatedAt": "2026-02-27T14:35:09.468Z"
16
+ }
@@ -0,0 +1,81 @@
1
+ ---
2
+ name: browser_search
3
+ description: 调用本地浏览器打开搜索引擎进行搜索
4
+ version: 1.1.0
5
+ author: FotricCalw
6
+ tags:
7
+ - browser
8
+ - search
9
+ - web
10
+ ---
11
+
12
+ # 浏览器搜索技能
13
+
14
+ 此技能用于调用本地浏览器打开搜索引擎进行搜索。
15
+
16
+ ## 核心规则(必须遵守)
17
+
18
+ 1. **禁止返回命令文本给用户** - 不要告诉用户如何执行命令
19
+ 2. **必须直接调用 terminal 工具** - 立即执行,不要解释过程
20
+ 3. **执行完成后简单告知用户即可** - 例如"已为您打开浏览器搜索 xxx"
21
+
22
+ ## 执行流程
23
+
24
+ 当用户请求搜索时,你必须立即执行以下操作:
25
+
26
+ ### 1. 构建搜索 URL
27
+
28
+ 将用户的搜索词转换为 URL:
29
+ - 空格替换为 `+`
30
+ - 特殊字符保持原样(浏览器会自动处理)
31
+
32
+ **URL 模板:**
33
+ - 必应搜索:`https://www.bing.com/search?q={搜索词}`
34
+ - 百度搜索:`https://www.baidu.com/s?wd={搜索词}`
35
+
36
+ ### 2. 立即调用 terminal 工具
37
+
38
+ **必须使用 terminal 工具执行以下格式的命令:**
39
+
40
+ ```
41
+ start "" "URL"
42
+ ```
43
+
44
+ **调用示例:**
45
+
46
+ 用户说:"用浏览器搜索 Python 教程"
47
+
48
+ 你必须立即调用 terminal 工具:
49
+ ```
50
+ start "" "https://www.bing.com/search?q=Python+tutorial"
51
+ ```
52
+
53
+ 执行成功后回复:"已为您打开浏览器搜索 Python 教程"
54
+
55
+ ## 完整示例
56
+
57
+ | 用户输入 | terminal 工具执行的命令 | 回复用户 |
58
+ |---------|------------------------|---------|
59
+ | 用浏览器搜索 Python 教程 | `start "" "https://www.bing.com/search?q=Python+tutorial"` | 已为您打开浏览器搜索 Python 教程 |
60
+ | 搜索今天的新闻 | `start "" "https://www.bing.com/search?q=今天+新闻"` | 已为您打开浏览器搜索今天的新闻 |
61
+ | 百度搜索 北京天气 | `start "" "https://www.baidu.com/s?wd=北京天气"` | 已为您打开百度搜索北京天气 |
62
+
63
+ ## 搜索引擎选择规则
64
+
65
+ - **默认使用必应搜索**
66
+ - 当用户明确指定"百度"时使用百度搜索
67
+ - 当搜索内容明显是中文相关时,优先使用百度
68
+
69
+ ## 错误处理
70
+
71
+ 如果 `start` 命令失败,尝试:
72
+ ```
73
+ powershell -Command "Start-Process 'URL'"
74
+ ```
75
+
76
+ ## 重要提醒
77
+
78
+ - 不要输出命令让用户自己执行
79
+ - 不要解释命令的含义
80
+ - 直接调用 terminal 工具执行
81
+ - 执行完成后简洁回复即可
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: get_weather
3
+ description: 获取指定城市的实时天气信息
4
+ version: 1.0.0
5
+ author: FotricCalw
6
+ tags:
7
+ - weather
8
+ - api
9
+ ---
10
+
11
+ # 获取天气技能
12
+
13
+ 此技能用于获取指定城市的实时天气信息。
14
+
15
+ ## 使用方法
16
+
17
+ 1. 使用 `fetch_url` 工具访问天气 API
18
+ 2. 解析返回的 JSON 数据
19
+ 3. 向用户展示天气信息
20
+
21
+ ## API 端点
22
+
23
+ 使用 Open-Meteo 免费天气 API(无需 API Key):
24
+
25
+ ```
26
+ https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&current_weather=true
27
+ ```
28
+
29
+ ## 步骤
30
+
31
+ ### 步骤 1: 获取城市坐标
32
+
33
+ 首先需要获取城市的经纬度。使用 Geocoding API:
34
+
35
+ ```
36
+ https://geocoding-api.open-meteo.com/v1/search?name={city_name}&count=1
37
+ ```
38
+
39
+ 使用 `fetch_url` 工具:
40
+ ```json
41
+ {
42
+ "url": "https://geocoding-api.open-meteo.com/v1/search?name=Beijing&count=1"
43
+ }
44
+ ```
45
+
46
+ ### 步骤 2: 获取天气数据
47
+
48
+ 从 Geocoding 响应中提取 latitude 和 longitude,然后请求天气数据:
49
+
50
+ ```json
51
+ {
52
+ "url": "https://api.open-meteo.com/v1/forecast?latitude=39.9042&longitude=116.4074&current_weather=true"
53
+ }
54
+ ```
55
+
56
+ ### 步骤 3: 格式化输出
57
+
58
+ 将天气数据格式化为友好的文本格式展示给用户。
59
+
60
+ ## 示例
61
+
62
+ 用户问:"北京今天天气怎么样?"
63
+
64
+ 1. 先调用 `fetch_url` 获取北京的坐标
65
+ 2. 再调用 `fetch_url` 获取天气数据
66
+ 3. 返回格式化的天气信息
67
+
68
+ ## 注意事项
69
+
70
+ - API 是免费的,无需 API Key
71
+ - 温度单位默认为摄氏度
72
+ - 返回的风速单位为 km/h
@@ -0,0 +1,3 @@
1
+ export * from './skill.types';
2
+ export * from './skills.service';
3
+ export * from './skills.module';
@@ -0,0 +1,27 @@
1
+ export interface SkillMetadata {
2
+ name: string;
3
+ description: string;
4
+ version?: string;
5
+ author?: string;
6
+ tags?: string[];
7
+ }
8
+
9
+ export interface Skill {
10
+ name: string;
11
+ description: string;
12
+ location: string;
13
+ metadata: SkillMetadata;
14
+ content: string;
15
+ }
16
+
17
+ export interface SkillSnapshot {
18
+ name: string;
19
+ description: string;
20
+ location: string;
21
+ }
22
+
23
+ export interface ParsedSkill {
24
+ metadata: SkillMetadata;
25
+ content: string;
26
+ raw: string;
27
+ }