inspiration-agent 0.0.1

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.
@@ -0,0 +1,208 @@
1
+ const axios = require("axios");
2
+ const logger = require("../utils/logger");
3
+ const cheerio = require("cheerio");
4
+
5
+ /**
6
+ * 搜索工具类
7
+ * 提供联网搜索功能,使用搜狗搜索
8
+ */
9
+ class SearchTools {
10
+ /**
11
+ * 构造函数
12
+ */
13
+ constructor() {
14
+ this.sogouSearchUrl = "https://www.sogou.com/web";
15
+ }
16
+
17
+ /**
18
+ * 使用搜狗进行网络搜索
19
+ * @param {Object} args - 参数对象
20
+ * @param {string} args.query - 搜索查询词
21
+ * @param {number} args.maxResults - 最大结果数,默认 5
22
+ * @returns {Promise<Object>} 操作结果
23
+ */
24
+ async webSearch(args) {
25
+ try {
26
+ const { query, maxResults = 5 } = args;
27
+
28
+ if (!query) {
29
+ return {
30
+ success: false,
31
+ error: "缺少 query 参数",
32
+ message: "搜索失败: 缺少查询词",
33
+ };
34
+ }
35
+
36
+ logger.info(`\n========== 网络搜索 ==========`);
37
+ logger.info(`查询: ${query}`);
38
+ logger.info(`最大结果数: ${maxResults}`);
39
+ logger.info(`==============================\n`);
40
+
41
+ const response = await axios.get(this.sogouSearchUrl, {
42
+ params: {
43
+ query: encodeURIComponent(query),
44
+ ie: "utf8",
45
+ },
46
+ timeout: 10000,
47
+ headers: {
48
+ "User-Agent":
49
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
50
+ },
51
+ });
52
+
53
+ const $ = cheerio.load(response.data);
54
+ const results = [];
55
+
56
+ $(".results .vr-title, .results h3 a").each((index, element) => {
57
+ if (results.length >= maxResults) return false;
58
+
59
+ const $el = $(element);
60
+ const title = $el.text().trim();
61
+ const url = $el.attr("href");
62
+ const $parent = $el.closest(".vr-title, .results > div");
63
+ const snippet = $parent.find(".str_info, .str-text, .abstract").text().trim() || "";
64
+
65
+ if (title && url) {
66
+ results.push({
67
+ title: title,
68
+ snippet: snippet,
69
+ url: url,
70
+ source: "搜狗",
71
+ });
72
+ }
73
+ });
74
+
75
+ logger.info(`\n========== 搜索结果 ==========`);
76
+ logger.info(`找到 ${results.length} 条结果`);
77
+ logger.info(`============================\n`);
78
+
79
+ return {
80
+ success: true,
81
+ query: query,
82
+ count: results.length,
83
+ results: results,
84
+ message: `成功搜索 "${query}",找到 ${results.length} 条结果`,
85
+ };
86
+ } catch (error) {
87
+ logger.info(`\n========== 搜索失败 ==========`);
88
+ logger.info(`查询: ${args.query}`);
89
+ logger.info(`错误: ${error.message}`);
90
+ logger.info(`============================\n`);
91
+
92
+ return {
93
+ success: false,
94
+ error: error.message,
95
+ message: `搜索失败: ${error.message}`,
96
+ };
97
+ }
98
+ }
99
+
100
+ /**
101
+ * 获取网页内容
102
+ * @param {Object} args - 参数对象
103
+ * @param {string} args.url - 要获取的网页 URL
104
+ * @returns {Promise<Object>} 操作结果
105
+ */
106
+ async fetchWebPage(args) {
107
+ try {
108
+ const { url } = args;
109
+
110
+ if (!url) {
111
+ return {
112
+ success: false,
113
+ error: "缺少 url 参数",
114
+ message: "获取网页失败: 缺少 URL",
115
+ };
116
+ }
117
+
118
+ logger.info(`\n========== 获取网页内容 ==========`);
119
+ logger.info(`URL: ${url}`);
120
+ logger.info(`=================================\n`);
121
+
122
+ const response = await axios.get(url, {
123
+ timeout: 15000,
124
+ headers: {
125
+ "User-Agent":
126
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
127
+ },
128
+ });
129
+
130
+ const content = response.data;
131
+ const contentLength = content.length;
132
+
133
+ logger.info(`\n========== 获取成功 ==========`);
134
+ logger.info(`内容长度: ${contentLength} 字符`);
135
+ logger.info(`=============================\n`);
136
+
137
+ return {
138
+ success: true,
139
+ url: url,
140
+ content: content,
141
+ contentLength: contentLength,
142
+ message: `成功获取网页: ${url}`,
143
+ };
144
+ } catch (error) {
145
+ logger.info(`\n========== 获取失败 ==========`);
146
+ logger.info(`URL: ${args.url}`);
147
+ logger.info(`错误: ${error.message}`);
148
+ logger.info(`=============================\n`);
149
+
150
+ return {
151
+ success: false,
152
+ error: error.message,
153
+ message: `获取网页失败: ${error.message}`,
154
+ };
155
+ }
156
+ }
157
+
158
+ /**
159
+ * 获取函数定义
160
+ * 返回所有搜索工具的函数定义,用于 OpenAI 函数调用格式
161
+ * @returns {Array<Object>} 函数定义数组
162
+ */
163
+ getFunctionDefinitions() {
164
+ return [
165
+ {
166
+ type: "function",
167
+ function: {
168
+ name: "web_search",
169
+ description: "使用搜狗搜索进行网络搜索,获取相关信息",
170
+ parameters: {
171
+ type: "object",
172
+ properties: {
173
+ query: {
174
+ type: "string",
175
+ description: "搜索查询词,如 'JavaScript 教程', '最新科技新闻' 等",
176
+ },
177
+ maxResults: {
178
+ type: "integer",
179
+ description: "返回的最大结果数,默认 5",
180
+ default: 5,
181
+ },
182
+ },
183
+ required: ["query"],
184
+ },
185
+ },
186
+ },
187
+ {
188
+ type: "function",
189
+ function: {
190
+ name: "fetch_web_page",
191
+ description: "获取指定 URL 的网页内容",
192
+ parameters: {
193
+ type: "object",
194
+ properties: {
195
+ url: {
196
+ type: "string",
197
+ description: "要获取的网页 URL,必须以 http:// 或 https:// 开头",
198
+ },
199
+ },
200
+ required: ["url"],
201
+ },
202
+ },
203
+ },
204
+ ];
205
+ }
206
+ }
207
+
208
+ module.exports = SearchTools;
@@ -0,0 +1,52 @@
1
+ const winston = require("winston");
2
+ const path = require("path");
3
+ const fs = require("fs");
4
+
5
+ const logDir = path.join(__dirname, "../../logs");
6
+ if (!fs.existsSync(logDir)) {
7
+ fs.mkdirSync(logDir, { recursive: true });
8
+ }
9
+
10
+ const logger = winston.createLogger({
11
+ level: process.env.LOG_LEVEL || "info",
12
+ format: winston.format.combine(
13
+ winston.format.timestamp({
14
+ format: "YYYY-MM-DD HH:mm:ss",
15
+ }),
16
+ winston.format.errors({ stack: true }),
17
+ winston.format.splat(),
18
+ winston.format.json()
19
+ ),
20
+ defaultMeta: { service: "ai-agent" },
21
+ transports: [
22
+ new winston.transports.File({
23
+ filename: path.join(logDir, "error.log"),
24
+ level: "error",
25
+ }),
26
+ new winston.transports.File({
27
+ filename: path.join(logDir, "combined.log"),
28
+ }),
29
+ ],
30
+ });
31
+
32
+ if (process.env.NODE_ENV !== "production") {
33
+ logger.add(
34
+ new winston.transports.Console({
35
+ format: winston.format.combine(
36
+ winston.format.colorize(),
37
+ winston.format.timestamp({
38
+ format: "YYYY-MM-DD HH:mm:ss",
39
+ }),
40
+ winston.format.printf(({ level, message, timestamp, ...meta }) => {
41
+ let msg = `${timestamp} [${level}] ${message}`;
42
+ if (Object.keys(meta).length > 0) {
43
+ msg += ` ${JSON.stringify(meta)}`;
44
+ }
45
+ return msg;
46
+ })
47
+ ),
48
+ })
49
+ );
50
+ }
51
+
52
+ module.exports = logger;
@@ -0,0 +1,207 @@
1
+ /**
2
+ * 终端 Markdown 渲染器
3
+ * 使用 ANSI 转义码在终端中渲染 Markdown 格式内容
4
+ */
5
+ class TerminalMarkdownRenderer {
6
+ constructor() {
7
+ this.ansi = {
8
+ reset: "\x1b[0m",
9
+ bright: "\x1b[1m",
10
+ dim: "\x1b[2m",
11
+ underscore: "\x1b[4m",
12
+ blink: "\x1b[5m",
13
+ reverse: "\x1b[7m",
14
+ hidden: "\x1b[8m",
15
+
16
+ fgBlack: "\x1b[30m",
17
+ fgRed: "\x1b[31m",
18
+ fgGreen: "\x1b[32m",
19
+ fgYellow: "\x1b[33m",
20
+ fgBlue: "\x1b[34m",
21
+ fgMagenta: "\x1b[35m",
22
+ fgCyan: "\x1b[36m",
23
+ fgWhite: "\x1b[37m",
24
+
25
+ bgBlack: "\x1b[40m",
26
+ bgRed: "\x1b[41m",
27
+ bgGreen: "\x1b[42m",
28
+ bgYellow: "\x1b[43m",
29
+ bgBlue: "\x1b[44m",
30
+ bgMagenta: "\x1b[45m",
31
+ bgCyan: "\x1b[46m",
32
+ bgWhite: "\x1b[47m",
33
+ };
34
+
35
+ this.ansiRegex = /\x1b\[[0-9;]*m/g;
36
+ }
37
+
38
+ /**
39
+ * 计算去除 ANSI 转义码后的实际字符长度
40
+ * @param {string} text - 包含 ANSI 转义码的文本
41
+ * @returns {number} 实际字符长度
42
+ */
43
+ getVisibleLength(text) {
44
+ return text.replace(this.ansiRegex, "").length;
45
+ }
46
+
47
+ /**
48
+ * 根据可见字符长度截取文本
49
+ * 确保 ANSI 转义码不会被截断
50
+ * @param {string} text - 包含 ANSI 转义码的文本
51
+ * @param {number} visibleStart - 可见字符起始位置
52
+ * @param {number} visibleEnd - 可见字符结束位置
53
+ * @returns {string} 截取后的文本
54
+ */
55
+ sliceByVisibleLength(text, visibleStart, visibleEnd) {
56
+ let visibleCount = 0;
57
+ let result = "";
58
+ let pos = 0;
59
+
60
+ while (pos < text.length && visibleCount < visibleEnd) {
61
+ const remainingText = text.slice(pos);
62
+ const ansiMatch = remainingText.match(this.ansiRegex);
63
+
64
+ if (ansiMatch && ansiMatch.index === 0) {
65
+ const ansiCode = ansiMatch[0];
66
+ if (visibleCount >= visibleStart) {
67
+ result += ansiCode;
68
+ }
69
+ pos += ansiCode.length;
70
+ } else {
71
+ const char = Array.from(remainingText)[0];
72
+ if (visibleCount >= visibleStart) {
73
+ result += char;
74
+ }
75
+ visibleCount++;
76
+ pos += char.length;
77
+ }
78
+ }
79
+
80
+ return result;
81
+ }
82
+
83
+ /**
84
+ * 渲染 Markdown 文本
85
+ * @param {string} markdown - Markdown 格式的文本
86
+ * @returns {string} 渲染后的文本
87
+ */
88
+ render(markdown) {
89
+ if (!markdown) return "";
90
+
91
+ let result = markdown;
92
+
93
+ result = this.renderCodeBlocks(result);
94
+ result = this.renderInlineCode(result);
95
+ result = this.renderHeaders(result);
96
+ result = this.renderBold(result);
97
+ result = this.renderItalic(result);
98
+ result = this.renderLinks(result);
99
+ result = this.renderUnorderedLists(result);
100
+ result = this.renderOrderedLists(result);
101
+ result = this.renderHorizontalRules(result);
102
+ result = this.renderBlockquotes(result);
103
+
104
+ return result;
105
+ }
106
+
107
+ /**
108
+ * 渲染代码块
109
+ */
110
+ renderCodeBlocks(text) {
111
+ return text.replace(/```(\w*)\n([\s\S]*?)```/g, (match, lang, code) => {
112
+ return `\n${this.ansi.bgBlack}${this.ansi.fgGreen}${code.trim()}${this.ansi.reset}\n`;
113
+ });
114
+ }
115
+
116
+ /**
117
+ * 渲染行内代码
118
+ */
119
+ renderInlineCode(text) {
120
+ return text.replace(
121
+ /`([^`]+)`/g,
122
+ `${this.ansi.bgBlack}${this.ansi.fgCyan}$1${this.ansi.reset}`
123
+ );
124
+ }
125
+
126
+ /**
127
+ * 渲染标题
128
+ */
129
+ renderHeaders(text) {
130
+ return text
131
+ .replace(/^#### (.+)$/gm, `${this.ansi.dim}${this.ansi.fgCyan} $1${this.ansi.reset}`)
132
+ .replace(/^### (.+)$/gm, `${this.ansi.dim}${this.ansi.fgCyan} $1${this.ansi.reset}`)
133
+ .replace(/^## (.+)$/gm, `${this.ansi.bright}${this.ansi.fgBlue} $1${this.ansi.reset}`)
134
+ .replace(/^# (.+)$/gm, `${this.ansi.bright}${this.ansi.fgGreen}$1${this.ansi.reset}`);
135
+ }
136
+
137
+ /**
138
+ * 渲染粗体
139
+ */
140
+ renderBold(text) {
141
+ return text.replace(/\*\*([^*]+)\*\*/g, `${this.ansi.bright}$1${this.ansi.reset}`);
142
+ }
143
+
144
+ /**
145
+ * 渲染斜体
146
+ */
147
+ renderItalic(text) {
148
+ return text.replace(/\*([^*]+)\*/g, `${this.ansi.underscore}$1${this.ansi.reset}`);
149
+ }
150
+
151
+ /**
152
+ * 渲染链接
153
+ */
154
+ renderLinks(text) {
155
+ return text.replace(
156
+ /\[([^\]]+)\]\(([^)]+)\)/g,
157
+ `${this.ansi.fgBlue}${this.ansi.underscore}$1${this.ansi.reset} (${this.ansi.dim}$2${this.ansi.reset})`
158
+ );
159
+ }
160
+
161
+ /**
162
+ * 渲染无序列表
163
+ */
164
+ renderUnorderedLists(text) {
165
+ return text.replace(/^(\s*)- (.+)$/gm, `${this.ansi.fgYellow}$1• $2${this.ansi.reset}`);
166
+ }
167
+
168
+ /**
169
+ * 渲染有序列表
170
+ */
171
+ renderOrderedLists(text) {
172
+ return text.replace(/^(\s*)(\d+)\. (.+)$/gm, `${this.ansi.fgYellow}$1$2. $3${this.ansi.reset}`);
173
+ }
174
+
175
+ /**
176
+ * 渲染分隔线
177
+ */
178
+ renderHorizontalRules(text) {
179
+ return text.replace(/^---$/gm, `${this.ansi.dim}${"─".repeat(50)}${this.ansi.reset}`);
180
+ }
181
+
182
+ /**
183
+ * 渲染引用块
184
+ */
185
+ renderBlockquotes(text) {
186
+ return text.replace(/^> (.+)$/gm, `${this.ansi.dim}│ $1${this.ansi.reset}`);
187
+ }
188
+
189
+ /**
190
+ * 流式渲染 Markdown
191
+ * 用于逐步渲染流式输出的内容
192
+ * @param {string} chunk - 当前接收的文本块
193
+ * @param {string} buffer - 之前的缓冲区
194
+ * @returns {Object} { rendered: 渲染后的文本, buffer: 新的缓冲区 }
195
+ */
196
+ renderStream(chunk, buffer = "") {
197
+ const fullText = buffer + chunk;
198
+ const rendered = this.render(fullText);
199
+
200
+ return {
201
+ rendered: rendered,
202
+ buffer: fullText,
203
+ };
204
+ }
205
+ }
206
+
207
+ module.exports = TerminalMarkdownRenderer;
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "inspiration-agent",
3
+ "version": "0.0.1",
4
+ "description": "Personal AI Assistant with multi-channel support",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "inspiration": "./dist/cli.js"
8
+ },
9
+ "files": [
10
+ "dist/",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "node scripts/build.js",
15
+ "start": "node src/index.js",
16
+ "start:dist": "node dist/cli.js",
17
+ "format": "prettier --write \"src/**/*.js\" \"*.json\" \"*.md\"",
18
+ "prepublishOnly": "npm run build",
19
+ "test": "echo \"Error: no test specified\" && exit 1"
20
+ },
21
+ "keywords": [
22
+ "ai",
23
+ "agent",
24
+ "deepseek",
25
+ "kimi",
26
+ "zhipu",
27
+ "minimax",
28
+ "feishu",
29
+ "chat"
30
+ ],
31
+ "author": "",
32
+ "license": "MIT",
33
+ "engines": {
34
+ "node": ">=18"
35
+ },
36
+ "dependencies": {
37
+ "@larksuiteoapi/node-sdk": "^1.58.0",
38
+ "axios": "^1.13.4",
39
+ "cheerio": "^1.2.0",
40
+ "dotenv": "^17.2.3",
41
+ "node-cron": "^4.2.1",
42
+ "puppeteer": "^24.37.2",
43
+ "sharp": "^0.34.5",
44
+ "sqlite3": "^5.1.7",
45
+ "tiktoken": "^1.0.22",
46
+ "winston": "^3.19.0"
47
+ },
48
+ "devDependencies": {
49
+ "prettier": "^3.8.1"
50
+ }
51
+ }