inspiration-agent 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -186,6 +186,19 @@ inspiration stop # 停止服务
186
186
  |--------|------|--------|
187
187
  | `COMMAND_WHITELIST_ENABLED` | 启用命令白名单 | `true` |
188
188
 
189
+ ### 浏览器配置
190
+
191
+ | 配置项 | 说明 | 默认值 |
192
+ |--------|------|--------|
193
+ | `BROWSER_EXECUTABLE_PATH` | 浏览器可执行文件路径 | macOS Chrome |
194
+
195
+ 不同系统默认值:
196
+ - **macOS**: `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`
197
+ - **Linux**: `/usr/bin/google-chrome`
198
+ - **Windows**: `C:\Program Files\Google\Chrome\Application\chrome.exe`
199
+
200
+ > 安装时会自动跳过 Chromium 下载,使用系统已安装的 Chrome 浏览器。
201
+
189
202
  ## 工具详解
190
203
 
191
204
  ### 文件工具
package/dist/.env.example CHANGED
@@ -51,3 +51,10 @@ COMMAND_WHITELIST_ENABLED=true
51
51
  # 数据库配置
52
52
  # 必填项,默认值: ./data/ai_agent.db
53
53
  DATABASE_PATH=./data/ai_agent.db
54
+
55
+ # 浏览器配置
56
+ # 非必填项,浏览器可执行文件路径
57
+ # macOS 默认值: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
58
+ # Linux 默认值: /usr/bin/google-chrome
59
+ # Windows 默认值: C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe
60
+ BROWSER_EXECUTABLE_PATH=/Applications/Google Chrome.app/Contents/MacOS/Google Chrome
package/dist/README.md CHANGED
@@ -186,6 +186,19 @@ inspiration stop # 停止服务
186
186
  |--------|------|--------|
187
187
  | `COMMAND_WHITELIST_ENABLED` | 启用命令白名单 | `true` |
188
188
 
189
+ ### 浏览器配置
190
+
191
+ | 配置项 | 说明 | 默认值 |
192
+ |--------|------|--------|
193
+ | `BROWSER_EXECUTABLE_PATH` | 浏览器可执行文件路径 | macOS Chrome |
194
+
195
+ 不同系统默认值:
196
+ - **macOS**: `/Applications/Google Chrome.app/Contents/MacOS/Google Chrome`
197
+ - **Linux**: `/usr/bin/google-chrome`
198
+ - **Windows**: `C:\Program Files\Google\Chrome\Application\chrome.exe`
199
+
200
+ > 安装时会自动跳过 Chromium 下载,使用系统已安装的 Chrome 浏览器。
201
+
189
202
  ## 工具详解
190
203
 
191
204
  ### 文件工具
package/dist/cli.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- const{spawn:spawn,exec:exec,fork:fork}=require("child_process"),fs=require("fs"),path=require("path"),os=require("os"),readline=require("readline"),net=require("net"),PACKAGE_JSON=require("../package.json"),PID_FILE=path.join(__dirname,"..","inspiration.pid"),CONFIG_DIR=path.join(os.homedir(),".inspiration"),ENV_FILE=path.join(CONFIG_DIR,".env");function parseArgs(){const n=process.argv.slice(2),e=n[0]||"help",o={};for(let e=1;e<n.length;e++)if(n[e].startsWith("-")){const t=n[e].replace(/^-+/,"");n[e+1]&&!n[e+1].startsWith("-")?(o[t]=n[e+1],e++):o[t]=!0}return{command:e,flags:o,args:n}}function ensureConfigDir(){fs.existsSync(CONFIG_DIR)||fs.mkdirSync(CONFIG_DIR,{recursive:!0})}function getPid(){if(fs.existsSync(PID_FILE)){const n=parseInt(fs.readFileSync(PID_FILE,"utf-8").trim(),10);if(Number.isInteger(n))try{return process.kill(n,0),n}catch(n){return fs.unlinkSync(PID_FILE),null}}return null}function removePid(){fs.existsSync(PID_FILE)&&fs.unlinkSync(PID_FILE)}function isRunning(){return null!==getPid()}function getEnvPath(){if(fs.existsSync(ENV_FILE))return ENV_FILE;const n=path.join(process.cwd(),".env");return fs.existsSync(n)?n:null}function startDaemon(){return new Promise((n,e)=>{const o=path.join(__dirname,"index.js"),t=getEnvPath(),s=path.join(__dirname,"..","logs");fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0});const i=path.join(s,"daemon.log"),r=fs.openSync(i,"a"),a={...process.env};t&&(a.DOTENV_CONFIG_PATH=t);const c=spawn(process.execPath,[o],{detached:!0,stdio:["ignore",r,r],env:a});c.unref(),c.on("error",n=>{e(n)});const l=Date.now(),checkPid=()=>{const o=getPid();o?n(o):Date.now()-l>1e4?e(new Error(`启动超时,请检查日志: ${i}`)):setTimeout(checkPid,100)};setTimeout(checkPid,300)})}function startForeground(){return new Promise((n,e)=>{const o=path.join(__dirname,"index.js"),t=getEnvPath(),s={...process.env};t&&(s.DOTENV_CONFIG_PATH=t);const i=spawn(process.execPath,[o],{stdio:"inherit",env:s});i.on("error",n=>{e(n)}),i.on("exit",e=>{n(e)})})}function stopProcess(){return new Promise(n=>{const e=getPid();if(e)try{process.kill(e,"SIGTERM");let o=0;const checkStopped=()=>{try{process.kill(e,0),o++,o>20?(process.kill(e,"SIGKILL"),removePid(),n(!0)):setTimeout(checkStopped,100)}catch(e){removePid(),n(!0)}};setTimeout(checkStopped,100)}catch(e){removePid(),n(!1)}else n(!1)})}function showHelp(){console.log(`\ninspiration-agent - Personal AI Assistant\n\n用法:\n inspiration <command> [options]\n\n命令:\n start 启动服务\n -d, --daemon 后台运行\n stop 停止服务\n restart 重启服务\n status 查看服务状态\n version 查看版本号\n onboard 配置向导\n cli 打开命令行交互(需要服务运行中)\n help 显示帮助信息\n\n示例:\n inspiration start # 前台启动\n inspiration start -d # 后台启动\n inspiration onboard # 运行配置向导\n inspiration status # 查看状态\n inspiration cli # 进入交互模式\n\n配置文件:\n ${ENV_FILE}\n\n更多信息:\n https://github.com/your-repo/inspiration-agent\n`)}function showVersion(){console.log(`inspiration-agent v${PACKAGE_JSON.version}`)}function showStatus(){const n=getPid();n?console.log(`\n✅ inspiration-agent 运行中 (PID: ${n})\n`):(console.log("\n❌ inspiration-agent 未运行"),console.log(" 使用 'inspiration start' 启动服务\n"))}async function runOnboard(){console.log("\n🔧 inspiration-agent 配置向导\n"),ensureConfigDir();const n=fs.existsSync(ENV_FILE)?fs.readFileSync(ENV_FILE,"utf-8"):"",getExistingValue=e=>{const o=n.match(new RegExp(`^${e}=(.*)$`,"m"));return o?o[1]:""},e=readline.createInterface({input:process.stdin,output:process.stdout}),question=(n,o="",t=!1)=>new Promise(s=>{const i=o?`${n} [${o}]: `:t?`${n} (必填): `:`${n}: `;e.question(i,e=>{const i=e.trim();!t||i||o?s(i||o):(console.log("此项为必填项,请输入有效值"),question(n,o,t).then(s))})});let o="";try{console.log("========== LLM 提供商配置 ==========\n");const n=["deepseek","kimi","zhipu","minimax"];let e=await question("LLM 提供商 (deepseek/kimi/zhipu/minimax)",getExistingValue("LLM_PROVIDER")||"deepseek",!0);for(;!n.includes(e.toLowerCase());)console.log("无效的提供商,请选择: deepseek, kimi, zhipu, minimax"),e=await question("LLM 提供商 (deepseek/kimi/zhipu/minimax)",getExistingValue("LLM_PROVIDER")||"deepseek",!0);if(o+=`LLM_PROVIDER=${e}\n\n`,"deepseek"===e){console.log("\nDeepSeek 配置:");const n=await question("API Key",getExistingValue("DEEPSEEK_API_KEY"),!0),e=await question("API URL",getExistingValue("DEEPSEEK_API_URL")||"https://api.deepseek.com/v1/chat/completions"),t=await question("模型名称",getExistingValue("DEEPSEEK_MODEL")||"deepseek-chat");o+=`DEEPSEEK_API_KEY=${n}\n`,o+=`DEEPSEEK_API_URL=${e}\n`,o+=`DEEPSEEK_MODEL=${t}\n\n`}else if("kimi"===e){console.log("\nKimi (月之暗面) 配置:");const n=await question("API Key",getExistingValue("KIMI_API_KEY"),!0),e=await question("API URL",getExistingValue("KIMI_API_URL")||"https://api.moonshot.cn/v1/chat/completions"),t=await question("模型名称",getExistingValue("KIMI_MODEL")||"moonshot-v1-8k");o+=`KIMI_API_KEY=${n}\n`,o+=`KIMI_API_URL=${e}\n`,o+=`KIMI_MODEL=${t}\n\n`}else if("zhipu"===e){console.log("\n智谱 GLM 配置:");const n=await question("API Key",getExistingValue("ZHIPU_API_KEY"),!0),e=await question("API URL",getExistingValue("ZHIPU_API_URL")||"https://open.bigmodel.cn/api/paas/v4/chat/completions"),t=await question("模型名称",getExistingValue("ZHIPU_MODEL")||"glm-4-flash");o+=`ZHIPU_API_KEY=${n}\n`,o+=`ZHIPU_API_URL=${e}\n`,o+=`ZHIPU_MODEL=${t}\n\n`}else if("minimax"===e){console.log("\nMiniMax 配置:");const n=await question("API Key",getExistingValue("MINIMAX_API_KEY"),!0),e=await question("Group ID",getExistingValue("MINIMAX_GROUP_ID"),!0),t=await question("API URL",getExistingValue("MINIMAX_API_URL")||"https://api.minimax.chat/v1/chat/completions"),s=await question("模型名称",getExistingValue("MINIMAX_MODEL")||"abab6.5s-chat");o+=`MINIMAX_API_KEY=${n}\n`,o+=`MINIMAX_GROUP_ID=${e}\n`,o+=`MINIMAX_API_URL=${t}\n`,o+=`MINIMAX_MODEL=${s}\n\n`}console.log("========== 飞书应用配置 ==========\n");const t=await question("飞书 App ID",getExistingValue("FEISHU_APP_ID"),!0),s=await question("飞书 App Secret",getExistingValue("FEISHU_APP_SECRET"),!0),i=await question("飞书 Verification Token",getExistingValue("FEISHU_VERIFICATION_TOKEN"),!0),r=await question("飞书 Encrypt Key (可选)",getExistingValue("FEISHU_ENCRYPT_KEY")),a=await question("飞书默认 Chat ID (可选)",getExistingValue("FEISHU_DEFAULT_CHAT_ID"));o+=`FEISHU_APP_ID=${t}\n`,o+=`FEISHU_APP_SECRET=${s}\n`,o+=`FEISHU_VERIFICATION_TOKEN=${i}\n`,r&&(o+=`FEISHU_ENCRYPT_KEY=${r}\n`),a&&(o+=`FEISHU_DEFAULT_CHAT_ID=${a}\n\n`),console.log("========== 服务配置 ==========\n");const c=await question("服务端口",getExistingValue("PORT")||"3000");let l=await question("启用流式输出 (true/false)",getExistingValue("ENABLE_STREAM")||"true");for(;!["true","false"].includes(l.toLowerCase());)console.log("请输入 true 或 false"),l=await question("启用流式输出 (true/false)",getExistingValue("ENABLE_STREAM")||"true");o+=`PORT=${c}\n`,o+=`ENABLE_STREAM=${l}\n\n`,console.log("========== 命令安全配置 ==========\n");let I=await question("启用命令白名单 (true/false)",getExistingValue("COMMAND_WHITELIST_ENABLED")||"true");for(;!["true","false"].includes(I.toLowerCase());)console.log("请输入 true 或 false"),I=await question("启用命令白名单 (true/false)",getExistingValue("COMMAND_WHITELIST_ENABLED")||"true");o+=`COMMAND_WHITELIST_ENABLED=${I}\n`,o+="\n",console.log("========== 数据库配置 ==========\n");o+=`DATABASE_PATH=${await question("数据库路径",getExistingValue("DATABASE_PATH")||"./data/ai_agent.db")}\n`,fs.writeFileSync(ENV_FILE,o),console.log("\n✅ 配置已保存到:",ENV_FILE),console.log("\n现在可以使用 'inspiration start' 启动服务\n")}finally{e.close()}}const SOCKET_FILE=path.join(__dirname,"..","inspiration.sock");function sendIPC(n){return new Promise((e,o)=>{if(!fs.existsSync(SOCKET_FILE))return void o(new Error("服务未运行"));const t=net.createConnection(SOCKET_FILE,()=>{t.write(JSON.stringify(n)+"\n")});let s="",i=!1;const r=setTimeout(()=>{i||(t.destroy(),o(new Error("连接超时")))},3e5);t.on("data",n=>{s+=n.toString()}),t.on("end",()=>{i=!0,clearTimeout(r);try{const n=JSON.parse(s.trim());e(n)}catch(n){o(new Error("解析响应失败"))}}),t.on("error",n=>{i=!0,clearTimeout(r),o(new Error(`连接失败: ${n.message}`))})})}async function runCli(){if(!getPid())return console.log("\n❌ inspiration-agent 未运行"),void console.log(" 请先使用 'inspiration start' 启动服务\n");console.log("\n💬 inspiration-agent 命令行交互"),console.log("输入消息与 AI 交互,输入 'exit' 或 'quit' 退出\n");const n=new(require("./utils/markdown-renderer")),e=readline.createInterface({input:process.stdin,output:process.stdout}),question=n=>new Promise(o=>{e.question(n,o)});try{for(;;){const o=await question("你: ");if(o.trim()){if("exit"===o.toLowerCase()||"quit"===o.toLowerCase()){console.log("\n再见!\n");break}if(o.startsWith("/"))try{const n=await sendIPC({type:"command",content:o});"response"===n.type?("object"==typeof n.content?console.log("\n"+JSON.stringify(n.content,null,2)+"\n"):console.log(`\n${n.content}\n`),"/shutdown"===o.trim().toLowerCase()&&(console.log("CLI 已关闭\n"),e.close(),process.exit(0))):"error"===n.type&&console.error(`\n错误: ${n.message}\n`)}catch(n){console.error(`\n错误: ${n.message}\n`)}else try{const e=await sendIPC({type:"message",content:o});if("response"===e.type){const o=n.render(e.content);console.log(`\nAI:\n${o}\n`)}else"error"===e.type&&console.error(`\n错误: ${e.message}\n`)}catch(n){console.error(`\n错误: ${n.message}\n`)}}}}finally{e.close()}}async function main(){const{command:n,flags:e}=parseArgs();switch(n){case"start":if(isRunning()){console.log("\n⚠️ inspiration-agent 已在运行中"),console.log(" 使用 'inspiration status' 查看状态\n");break}if(e.d||e.daemon){console.log("\n🚀 启动 inspiration-agent (后台模式)...");try{const n=await startDaemon();console.log(`✅ 服务已启动 (PID: ${n})\n`)}catch(n){console.error(`❌ 启动失败: ${n.message}\n`),process.exit(1)}}else console.log("\n🚀 启动 inspiration-agent...\n"),await startForeground();break;case"stop":await stopProcess()?console.log("\n✅ inspiration-agent 已停止\n"):console.log("\n⚠️ inspiration-agent 未运行\n");break;case"restart":if(console.log("\n🔄 重启 inspiration-agent..."),await stopProcess(),await new Promise(n=>setTimeout(n,1e3)),e.d||e.daemon){const n=await startDaemon();console.log(`✅ 服务已重启 (PID: ${n})\n`)}else await startForeground();break;case"status":showStatus(),console.log();break;case"version":case"-v":case"--version":showVersion();break;case"onboard":await runOnboard();break;case"cli":await runCli();break;default:showHelp()}}main().catch(n=>{console.error(`错误: ${n.message}`),process.exit(1)});
2
+ const{spawn:spawn,exec:exec,fork:fork}=require("child_process"),fs=require("fs"),path=require("path"),os=require("os"),readline=require("readline"),net=require("net"),PACKAGE_JSON=require("../package.json"),PID_FILE=path.join(__dirname,"..","inspiration.pid"),CONFIG_DIR=path.join(os.homedir(),".inspiration"),ENV_FILE=path.join(CONFIG_DIR,".env");function parseArgs(){const n=process.argv.slice(2),e=n[0]||"help",o={};for(let e=1;e<n.length;e++)if(n[e].startsWith("-")){const t=n[e].replace(/^-+/,"");n[e+1]&&!n[e+1].startsWith("-")?(o[t]=n[e+1],e++):o[t]=!0}return{command:e,flags:o,args:n}}function ensureConfigDir(){fs.existsSync(CONFIG_DIR)||fs.mkdirSync(CONFIG_DIR,{recursive:!0})}function getPid(){if(fs.existsSync(PID_FILE)){const n=parseInt(fs.readFileSync(PID_FILE,"utf-8").trim(),10);if(Number.isInteger(n))try{return process.kill(n,0),n}catch(n){return fs.unlinkSync(PID_FILE),null}}return null}function removePid(){fs.existsSync(PID_FILE)&&fs.unlinkSync(PID_FILE)}function isRunning(){return null!==getPid()}function getEnvPath(){if(fs.existsSync(ENV_FILE))return ENV_FILE;const n=path.join(process.cwd(),".env");return fs.existsSync(n)?n:null}function startDaemon(){return new Promise((n,e)=>{const o=path.join(__dirname,"index.js"),t=getEnvPath(),s=path.join(__dirname,"..","logs");fs.existsSync(s)||fs.mkdirSync(s,{recursive:!0});const i=path.join(s,"daemon.log"),r=fs.openSync(i,"a"),a={...process.env};t&&(a.DOTENV_CONFIG_PATH=t);const c=spawn(process.execPath,[o],{detached:!0,stdio:["ignore",r,r],env:a});c.unref(),c.on("error",n=>{e(n)});const l=Date.now(),checkPid=()=>{const o=getPid();o?n(o):Date.now()-l>1e4?e(new Error(`启动超时,请检查日志: ${i}`)):setTimeout(checkPid,100)};setTimeout(checkPid,300)})}function startForeground(){return new Promise((n,e)=>{const o=path.join(__dirname,"index.js"),t=getEnvPath(),s={...process.env};t&&(s.DOTENV_CONFIG_PATH=t);const i=spawn(process.execPath,[o],{stdio:"inherit",env:s});i.on("error",n=>{e(n)}),i.on("exit",e=>{n(e)})})}function stopProcess(){return new Promise(n=>{const e=getPid();if(e)try{process.kill(e,"SIGTERM");let o=0;const checkStopped=()=>{try{process.kill(e,0),o++,o>20?(process.kill(e,"SIGKILL"),removePid(),n(!0)):setTimeout(checkStopped,100)}catch(e){removePid(),n(!0)}};setTimeout(checkStopped,100)}catch(e){removePid(),n(!1)}else n(!1)})}function showHelp(){console.log(`\ninspiration-agent - Personal AI Assistant\n\n用法:\n inspiration <command> [options]\n\n命令:\n start 启动服务\n -d, --daemon 后台运行\n stop 停止服务\n restart 重启服务\n status 查看服务状态\n version 查看版本号\n onboard 配置向导\n cli 打开命令行交互(需要服务运行中)\n help 显示帮助信息\n\n示例:\n inspiration start # 前台启动\n inspiration start -d # 后台启动\n inspiration onboard # 运行配置向导\n inspiration status # 查看状态\n inspiration cli # 进入交互模式\n\n配置文件:\n ${ENV_FILE}\n\n更多信息:\n https://github.com/your-repo/inspiration-agent\n`)}function showVersion(){console.log(`inspiration-agent v${PACKAGE_JSON.version}`)}function showStatus(){const n=getPid();n?console.log(`\n✅ inspiration-agent 运行中 (PID: ${n})\n`):(console.log("\n❌ inspiration-agent 未运行"),console.log(" 使用 'inspiration start' 启动服务\n"))}async function runOnboard(){console.log("\n🔧 inspiration-agent 配置向导\n"),ensureConfigDir();const n=fs.existsSync(ENV_FILE)?fs.readFileSync(ENV_FILE,"utf-8"):"",getExistingValue=e=>{const o=n.match(new RegExp(`^${e}=(.*)$`,"m"));return o?o[1]:""},e=readline.createInterface({input:process.stdin,output:process.stdout}),question=(n,o="",t=!1)=>new Promise(s=>{const i=o?`${n} [${o}]: `:t?`${n} (必填): `:`${n}: `;e.question(i,e=>{const i=e.trim();!t||i||o?s(i||o):(console.log("此项为必填项,请输入有效值"),question(n,o,t).then(s))})});let o="";try{console.log("========== LLM 提供商配置 ==========\n");const n=["deepseek","kimi","zhipu","minimax"];let e=await question("LLM 提供商 (deepseek/kimi/zhipu/minimax)",getExistingValue("LLM_PROVIDER")||"deepseek",!0);for(;!n.includes(e.toLowerCase());)console.log("无效的提供商,请选择: deepseek, kimi, zhipu, minimax"),e=await question("LLM 提供商 (deepseek/kimi/zhipu/minimax)",getExistingValue("LLM_PROVIDER")||"deepseek",!0);if(o+=`LLM_PROVIDER=${e}\n\n`,"deepseek"===e){console.log("\nDeepSeek 配置:");const n=await question("API Key",getExistingValue("DEEPSEEK_API_KEY"),!0),e=await question("API URL",getExistingValue("DEEPSEEK_API_URL")||"https://api.deepseek.com/v1/chat/completions"),t=await question("模型名称",getExistingValue("DEEPSEEK_MODEL")||"deepseek-chat");o+=`DEEPSEEK_API_KEY=${n}\n`,o+=`DEEPSEEK_API_URL=${e}\n`,o+=`DEEPSEEK_MODEL=${t}\n\n`}else if("kimi"===e){console.log("\nKimi (月之暗面) 配置:");const n=await question("API Key",getExistingValue("KIMI_API_KEY"),!0),e=await question("API URL",getExistingValue("KIMI_API_URL")||"https://api.moonshot.cn/v1/chat/completions"),t=await question("模型名称",getExistingValue("KIMI_MODEL")||"moonshot-v1-8k");o+=`KIMI_API_KEY=${n}\n`,o+=`KIMI_API_URL=${e}\n`,o+=`KIMI_MODEL=${t}\n\n`}else if("zhipu"===e){console.log("\n智谱 GLM 配置:");const n=await question("API Key",getExistingValue("ZHIPU_API_KEY"),!0),e=await question("API URL",getExistingValue("ZHIPU_API_URL")||"https://open.bigmodel.cn/api/paas/v4/chat/completions"),t=await question("模型名称",getExistingValue("ZHIPU_MODEL")||"glm-4-flash");o+=`ZHIPU_API_KEY=${n}\n`,o+=`ZHIPU_API_URL=${e}\n`,o+=`ZHIPU_MODEL=${t}\n\n`}else if("minimax"===e){console.log("\nMiniMax 配置:");const n=await question("API Key",getExistingValue("MINIMAX_API_KEY"),!0),e=await question("Group ID",getExistingValue("MINIMAX_GROUP_ID"),!0),t=await question("API URL",getExistingValue("MINIMAX_API_URL")||"https://api.minimax.chat/v1/chat/completions"),s=await question("模型名称",getExistingValue("MINIMAX_MODEL")||"abab6.5s-chat");o+=`MINIMAX_API_KEY=${n}\n`,o+=`MINIMAX_GROUP_ID=${e}\n`,o+=`MINIMAX_API_URL=${t}\n`,o+=`MINIMAX_MODEL=${s}\n\n`}console.log("========== 飞书应用配置 ==========\n");const t=await question("飞书 App ID",getExistingValue("FEISHU_APP_ID"),!0),s=await question("飞书 App Secret",getExistingValue("FEISHU_APP_SECRET"),!0),i=await question("飞书 Verification Token",getExistingValue("FEISHU_VERIFICATION_TOKEN"),!0),r=await question("飞书 Encrypt Key (可选)",getExistingValue("FEISHU_ENCRYPT_KEY")),a=await question("飞书默认 Chat ID (可选)",getExistingValue("FEISHU_DEFAULT_CHAT_ID"));o+=`FEISHU_APP_ID=${t}\n`,o+=`FEISHU_APP_SECRET=${s}\n`,o+=`FEISHU_VERIFICATION_TOKEN=${i}\n`,r&&(o+=`FEISHU_ENCRYPT_KEY=${r}\n`),a&&(o+=`FEISHU_DEFAULT_CHAT_ID=${a}\n\n`),console.log("========== 服务配置 ==========\n");const c=await question("服务端口",getExistingValue("PORT")||"3000");let l=await question("启用流式输出 (true/false)",getExistingValue("ENABLE_STREAM")||"true");for(;!["true","false"].includes(l.toLowerCase());)console.log("请输入 true 或 false"),l=await question("启用流式输出 (true/false)",getExistingValue("ENABLE_STREAM")||"true");o+=`PORT=${c}\n`,o+=`ENABLE_STREAM=${l}\n\n`,console.log("========== 命令安全配置 ==========\n");let I=await question("启用命令白名单 (true/false)",getExistingValue("COMMAND_WHITELIST_ENABLED")||"true");for(;!["true","false"].includes(I.toLowerCase());)console.log("请输入 true 或 false"),I=await question("启用命令白名单 (true/false)",getExistingValue("COMMAND_WHITELIST_ENABLED")||"true");o+=`COMMAND_WHITELIST_ENABLED=${I}\n`,o+="\n",console.log("========== 数据库配置 ==========\n");o+=`DATABASE_PATH=${await question("数据库路径",getExistingValue("DATABASE_PATH")||"./data/ai_agent.db")}\n\n`,console.log("========== 浏览器配置 ==========\n");o+=`BROWSER_EXECUTABLE_PATH=${await question("浏览器路径 (留空使用默认 Chrome)",getExistingValue("BROWSER_EXECUTABLE_PATH")||"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome")}\n`,fs.writeFileSync(ENV_FILE,o),console.log("\n✅ 配置已保存到:",ENV_FILE),console.log("\n现在可以使用 'inspiration start' 启动服务\n")}finally{e.close()}}const SOCKET_FILE=path.join(__dirname,"..","inspiration.sock");function sendIPC(n){return new Promise((e,o)=>{if(!fs.existsSync(SOCKET_FILE))return void o(new Error("服务未运行"));const t=net.createConnection(SOCKET_FILE,()=>{t.write(JSON.stringify(n)+"\n")});let s="",i=!1;const r=setTimeout(()=>{i||(t.destroy(),o(new Error("连接超时")))},3e5);t.on("data",n=>{s+=n.toString()}),t.on("end",()=>{i=!0,clearTimeout(r);try{const n=JSON.parse(s.trim());e(n)}catch(n){o(new Error("解析响应失败"))}}),t.on("error",n=>{i=!0,clearTimeout(r),o(new Error(`连接失败: ${n.message}`))})})}async function runCli(){if(!getPid())return console.log("\n❌ inspiration-agent 未运行"),void console.log(" 请先使用 'inspiration start' 启动服务\n");console.log("\n💬 inspiration-agent 命令行交互"),console.log("输入消息与 AI 交互,输入 'exit' 或 'quit' 退出\n");const n=new(require("./utils/markdown-renderer")),e=readline.createInterface({input:process.stdin,output:process.stdout}),question=n=>new Promise(o=>{e.question(n,o)});try{for(;;){const o=await question("你: ");if(o.trim()){if("exit"===o.toLowerCase()||"quit"===o.toLowerCase()){console.log("\n再见!\n");break}if(o.startsWith("/"))try{const n=await sendIPC({type:"command",content:o});"response"===n.type?("object"==typeof n.content?console.log("\n"+JSON.stringify(n.content,null,2)+"\n"):console.log(`\n${n.content}\n`),"/shutdown"===o.trim().toLowerCase()&&(console.log("CLI 已关闭\n"),e.close(),process.exit(0))):"error"===n.type&&console.error(`\n错误: ${n.message}\n`)}catch(n){console.error(`\n错误: ${n.message}\n`)}else try{const e=await sendIPC({type:"message",content:o});if("response"===e.type){const o=n.render(e.content);console.log(`\nAI:\n${o}\n`)}else"error"===e.type&&console.error(`\n错误: ${e.message}\n`)}catch(n){console.error(`\n错误: ${n.message}\n`)}}}}finally{e.close()}}async function main(){const{command:n,flags:e}=parseArgs();switch(n){case"start":if(isRunning()){console.log("\n⚠️ inspiration-agent 已在运行中"),console.log(" 使用 'inspiration status' 查看状态\n");break}if(e.d||e.daemon){console.log("\n🚀 启动 inspiration-agent (后台模式)...");try{const n=await startDaemon();console.log(`✅ 服务已启动 (PID: ${n})\n`)}catch(n){console.error(`❌ 启动失败: ${n.message}\n`),process.exit(1)}}else console.log("\n🚀 启动 inspiration-agent...\n"),await startForeground();break;case"stop":await stopProcess()?console.log("\n✅ inspiration-agent 已停止\n"):console.log("\n⚠️ inspiration-agent 未运行\n");break;case"restart":if(console.log("\n🔄 重启 inspiration-agent..."),await stopProcess(),await new Promise(n=>setTimeout(n,1e3)),e.d||e.daemon){const n=await startDaemon();console.log(`✅ 服务已重启 (PID: ${n})\n`)}else await startForeground();break;case"status":showStatus(),console.log();break;case"version":case"-v":case"--version":showVersion();break;case"onboard":await runOnboard();break;case"cli":await runCli();break;default:showHelp()}}main().catch(n=>{console.error(`错误: ${n.message}`),process.exit(1)});
@@ -1 +1 @@
1
- const puppeteer=require("puppeteer"),logger=require("../utils/logger"),sharp=require("sharp"),fs=require("fs").promises;class BrowserTools{constructor(){this.browser=null,this.page=null}async launchBrowser(e){try{const{headless:t=!0}=e;return logger.info("\n========== 启动浏览器 =========="),logger.info(`无头模式: ${t}`),logger.info("================================\n"),this.browser?(logger.info("\n========== 浏览器已在运行 ==========\n"),{success:!0,headless:t,message:"浏览器已在运行"}):(this.browser=await puppeteer.launch({headless:!!t&&"new",args:["--no-sandbox","--disable-setuid-sandbox","--disable-blink-features=AutomationControlled","--disable-dev-shm-usage","--disable-gpu","--no-first-run","--no-default-browser-check","--disable-background-timer-throttling","--disable-backgrounding-occluded-windows","--disable-renderer-backgrounding","--disable-features=TranslateUI","--disable-ipc-flooding-protection","--window-size=1920,1080"],executablePath:"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",ignoreDefaultArgs:["--enable-automation"]}),this.page=await this.browser.newPage(),await this.page.setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"),await this.page.setViewport({width:1920,height:1080}),await this.page.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>!1}),Object.defineProperty(navigator,"plugins",{get:()=>[1,2,3,4,5]}),Object.defineProperty(navigator,"languages",{get:()=>["zh-CN","zh","en"]}),Object.defineProperty(navigator,"platform",{get:()=>"MacIntel"}),window.chrome={runtime:{}},Object.defineProperty(navigator,"permissions",{get:()=>({query:()=>Promise.resolve({state:"granted"})})});const e=window.navigator.permissions.query;window.navigator.permissions.query=t=>"notifications"===t.name?Promise.resolve({state:Notification.permission}):e(t),Object.defineProperty(navigator,"hardwareConcurrency",{get:()=>8}),Object.defineProperty(navigator,"deviceMemory",{get:()=>8}),Object.defineProperty(navigator,"maxTouchPoints",{get:()=>0}),window.outerHeight=window.innerHeight,window.outerWidth=window.innerWidth;const t=HTMLCanvasElement.prototype.getContext;HTMLCanvasElement.prototype.getContext=function(e){const r=t.call(this,e);if("2d"===e){const e=r.getImageData;r.getImageData=function(){return e.apply(this,arguments)}}return r}}),logger.info("\n========== 浏览器启动成功 =========="),logger.info("====================================\n"),{success:!0,headless:t,message:"浏览器启动成功"})}catch(e){return logger.info("\n========== 浏览器启动失败 =========="),logger.info(`错误: ${e.message}`),logger.info("====================================\n"),{success:!1,error:e.message,message:`浏览器启动失败: ${e.message}`}}}async ensureBrowser(){if(!this.browser){return(await this.launchBrowser({})).success}return!0}async navigateTo(e){try{const{url:t,timeout:r=3e4}=e;if(await this.ensureBrowser(),!t)return{success:!1,error:"缺少 url 参数",message:"导航失败: 缺少 URL"};logger.info("\n========== 导航到页面 =========="),logger.info(`URL: ${t}`),logger.info(`超时: ${r}ms`),logger.info("================================\n"),await this.page.goto(t,{waitUntil:"networkidle2",timeout:r});const s=await this.page.title(),o=this.page.url();return logger.info("\n========== 导航成功 =========="),logger.info(`页面标题: ${s}`),logger.info(`页面 URL: ${o}`),logger.info("=============================\n"),{success:!0,url:o,title:s,message:`成功导航到: ${t}`}}catch(t){return logger.info("\n========== 导航失败 =========="),logger.info(`URL: ${e.url}`),logger.info(`错误: ${t.message}`),logger.info("=============================\n"),{success:!1,error:t.message,message:`导航失败: ${t.message}`}}}async clickElement(e){try{const{selector:t,timeout:r=5e3}=e;return await this.ensureBrowser(),t?(logger.info("\n========== 点击元素 =========="),logger.info(`选择器: ${t}`),logger.info(`超时: ${r}ms`),logger.info("=============================\n"),await this.page.waitForSelector(t,{timeout:r}),await this.page.click(t),logger.info("\n========== 点击成功 ==========\n"),{success:!0,selector:t,message:`成功点击元素: ${t}`}):{success:!1,error:"缺少 selector 参数",message:"点击失败: 缺少选择器"}}catch(t){return logger.info("\n========== 点击失败 =========="),logger.info(`选择器: ${e.selector}`),logger.info(`错误: ${t.message}`),logger.info("=============================\n"),{success:!1,error:t.message,message:`点击失败: ${t.message}`}}}async fillInput(e){try{const{selector:t,value:r,timeout:s=5e3}=e;return await this.ensureBrowser(),t&&void 0!==r?(logger.info("\n========== 填写输入框 =========="),logger.info(`选择器: ${t}`),logger.info(`值: ${r}`),logger.info(`超时: ${s}ms`),logger.info("================================\n"),await this.page.waitForSelector(t,{timeout:s}),await this.page.type(t,r),logger.info("\n========== 填写成功 ==========\n"),{success:!0,selector:t,value:r,message:`成功填写输入框: ${t}`}):{success:!1,error:"缺少 selector 或 value 参数",message:"填写失败: 缺少必要参数"}}catch(t){return logger.info("\n========== 填写失败 =========="),logger.info(`选择器: ${e.selector}`),logger.info(`错误: ${t.message}`),logger.info("=============================\n"),{success:!1,error:t.message,message:`填写失败: ${t.message}`}}}async getPageContent(e){try{const{selector:t}=e;let r;if(await this.ensureBrowser(),logger.info("\n========== 获取页面内容 =========="),logger.info(`选择器: ${t||"整个页面"}`),logger.info("==================================\n"),t){const e=await this.page.$(t);if(!e)return{success:!1,error:"元素未找到",message:`未找到元素: ${t}`};r=await this.page.evaluate(e=>e.textContent,e)}else r=await this.page.evaluate(()=>document.body.textContent);const s=await this.page.title(),o=this.page.url();return logger.info("\n========== 获取成功 =========="),logger.info(`内容长度: ${r.length} 字符`),logger.info("=============================\n"),{success:!0,url:o,title:s,content:r,selector:t,message:"成功获取页面内容"}}catch(e){return logger.info("\n========== 获取失败 =========="),logger.info(`错误: ${e.message}`),logger.info("=============================\n"),{success:!1,error:e.message,message:`获取页面内容失败: ${e.message}`}}}async takeScreenshot(e){try{const{path:t,selector:r,quality:s=80,maxWidth:o,maxHeight:n}=e;let i;if(await this.ensureBrowser(),logger.info("\n========== 截图 =========="),logger.info(`路径: ${t||"不保存"}`),logger.info(`选择器: ${r||"整个页面"}`),logger.info(`压缩质量: ${s}`),logger.info("最大尺寸: "+(o?o+"x"+n:"不限制")),logger.info("===========================\n"),r){const e=await this.page.$(r);if(!e)return{success:!1,error:"元素未找到",message:`未找到元素: ${r}`};i=await e.screenshot({encoding:"base64"})}else i=await this.page.screenshot({encoding:"base64",fullPage:!0});if(t){const e=t.replace(/(\.[^.]+)$/,"_temp$1");await this.page.screenshot({path:e,fullPage:!r});let i=sharp(e);(o||n)&&(i=i.resize(o||null,n||null,{fit:"inside",withoutEnlargement:!0}));const a=t.split(".").pop().toLowerCase();i="jpg"===a||"jpeg"===a?i.jpeg({quality:s}):"png"===a?i.png({quality:Math.round(s/10)}):"webp"===a?i.webp({quality:s}):i.jpeg({quality:s}),await i.toFile(t);const g=await fs.stat(e),c=await fs.stat(t);await fs.unlink(e),logger.info("\n========== 压缩完成 =========="),logger.info(`原始大小: ${g.size} 字节`),logger.info(`压缩后: ${c.size} 字节`),logger.info(`压缩率: ${(100*(1-c.size/g.size)).toFixed(2)}%`),logger.info("=============================\n")}return logger.info("\n========== 截图成功 ==========\n"),{success:!0,path:t,screenshot:i,selector:r,message:"成功截图"+(t?`,保存到: ${t}`:"")}}catch(e){return logger.info("\n========== 截图失败 =========="),logger.info(`错误: ${e.message}`),logger.info("=============================\n"),{success:!1,error:e.message,message:`截图失败: ${e.message}`}}}async waitForElement(e){try{const{selector:t,timeout:r=3e4}=e;return await this.ensureBrowser(),t?(logger.info("\n========== 等待元素 =========="),logger.info(`选择器: ${t}`),logger.info(`超时: ${r}ms`),logger.info("===========================\n"),await this.page.waitForSelector(t,{timeout:r}),logger.info("\n========== 元素已出现 ==========\n"),{success:!0,selector:t,message:`元素已出现: ${t}`}):{success:!1,error:"缺少 selector 参数",message:"等待失败: 缺少选择器"}}catch(t){return logger.info("\n========== 等待超时 =========="),logger.info(`选择器: ${e.selector}`),logger.info(`错误: ${t.message}`),logger.info("=============================\n"),{success:!1,error:t.message,message:`等待元素超时: ${t.message}`}}}async executeScript(e){try{const{script:t}=e;if(await this.ensureBrowser(),!t)return{success:!1,error:"缺少 script 参数",message:"执行失败: 缺少脚本"};logger.info("\n========== 执行脚本 =========="),logger.info(`脚本: ${t.substring(0,100)}...`),logger.info("=============================\n");const r=await this.page.evaluate(t);return logger.info("\n========== 执行成功 ==========\n"),{success:!0,result:r,message:"脚本执行成功"}}catch(e){return logger.info("\n========== 执行失败 =========="),logger.info(`错误: ${e.message}`),logger.info("=============================\n"),{success:!1,error:e.message,message:`脚本执行失败: ${e.message}`}}}async closeBrowser(e){try{return this.browser?(logger.info("\n========== 关闭浏览器 ==========\n"),await this.browser.close(),this.browser=null,this.page=null,logger.info("\n========== 浏览器已关闭 ==========\n"),{success:!0,message:"浏览器已关闭"}):{success:!1,error:"浏览器未启动",message:"浏览器未运行"}}catch(e){return logger.info("\n========== 关闭失败 =========="),logger.info(`错误: ${e.message}`),logger.info("=============================\n"),{success:!1,error:e.message,message:`关闭浏览器失败: ${e.message}`}}}getFunctionDefinitions(){return[{type:"function",function:{name:"launch_browser",description:"启动浏览器实例(如果已启动则复用现有实例),支持多轮对话保持浏览器状态",parameters:{type:"object",properties:{headless:{type:"boolean",description:"是否使用无头模式(不显示浏览器界面),默认 true",default:!0}}}}},{type:"function",function:{name:"navigate_to",description:"导航到指定的 URL(如果浏览器未启动会自动启动)",parameters:{type:"object",properties:{url:{type:"string",description:"要访问的网页 URL,必须以 http:// 或 https:// 开头"},timeout:{type:"integer",description:"页面加载超时时间(毫秒),默认 30000",default:3e4}},required:["url"]}}},{type:"function",function:{name:"click_element",description:"点击页面上的元素(如果浏览器未启动会自动启动)",parameters:{type:"object",properties:{selector:{type:"string",description:"CSS 选择器,如 '#submit-btn', '.login-button', 'input[type=\"submit\"]'"},timeout:{type:"integer",description:"等待元素出现的超时时间(毫秒),默认 5000",default:5e3}},required:["selector"]}}},{type:"function",function:{name:"fill_input",description:"在输入框中填写内容(如果浏览器未启动会自动启动)",parameters:{type:"object",properties:{selector:{type:"string",description:"CSS 选择器,如 '#username', 'input[name=\"email\"]'"},value:{type:"string",description:"要输入的文本内容"},timeout:{type:"integer",description:"等待输入框出现的超时时间(毫秒),默认 5000",default:5e3}},required:["selector","value"]}}},{type:"function",function:{name:"get_page_content",description:"获取页面文本内容(如果浏览器未启动会自动启动)",parameters:{type:"object",properties:{selector:{type:"string",description:"CSS 选择器,可选,如果不提供则获取整个页面的内容"}}}}},{type:"function",function:{name:"wait_for_element",description:"等待页面元素出现(如果浏览器未启动会自动启动)",parameters:{type:"object",properties:{selector:{type:"string",description:"CSS 选择器,如 '#loading', '.content'"},timeout:{type:"integer",description:"等待超时时间(毫秒),默认 30000",default:3e4}},required:["selector"]}}},{type:"function",function:{name:"execute_script",description:"在页面中执行 JavaScript 代码(如果浏览器未启动会自动启动)",parameters:{type:"object",properties:{script:{type:"string",description:"要执行的 JavaScript 代码,如 'document.title' 或 'window.scrollTo(0, 1000)'"}},required:["script"]}}},{type:"function",function:{name:"close_browser",description:"关闭浏览器实例",parameters:{type:"object",properties:{}}}}]}}module.exports=BrowserTools;
1
+ const puppeteer=require("puppeteer"),logger=require("../utils/logger"),sharp=require("sharp"),fs=require("fs").promises;class BrowserTools{constructor(){this.browser=null,this.page=null}async launchBrowser(e){try{const{headless:t=!0}=e;return logger.info("\n========== 启动浏览器 =========="),logger.info(`无头模式: ${t}`),logger.info("================================\n"),this.browser?(logger.info("\n========== 浏览器已在运行 ==========\n"),{success:!0,headless:t,message:"浏览器已在运行"}):(this.browser=await puppeteer.launch({headless:!!t&&"new",args:["--no-sandbox","--disable-setuid-sandbox","--disable-blink-features=AutomationControlled","--disable-dev-shm-usage","--disable-gpu","--no-first-run","--no-default-browser-check","--disable-background-timer-throttling","--disable-backgrounding-occluded-windows","--disable-renderer-backgrounding","--disable-features=TranslateUI","--disable-ipc-flooding-protection","--window-size=1920,1080"],executablePath:process.env.BROWSER_EXECUTABLE_PATH||"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",ignoreDefaultArgs:["--enable-automation"]}),this.page=await this.browser.newPage(),await this.page.setUserAgent("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36"),await this.page.setViewport({width:1920,height:1080}),await this.page.evaluateOnNewDocument(()=>{Object.defineProperty(navigator,"webdriver",{get:()=>!1}),Object.defineProperty(navigator,"plugins",{get:()=>[1,2,3,4,5]}),Object.defineProperty(navigator,"languages",{get:()=>["zh-CN","zh","en"]}),Object.defineProperty(navigator,"platform",{get:()=>"MacIntel"}),window.chrome={runtime:{}},Object.defineProperty(navigator,"permissions",{get:()=>({query:()=>Promise.resolve({state:"granted"})})});const e=window.navigator.permissions.query;window.navigator.permissions.query=t=>"notifications"===t.name?Promise.resolve({state:Notification.permission}):e(t),Object.defineProperty(navigator,"hardwareConcurrency",{get:()=>8}),Object.defineProperty(navigator,"deviceMemory",{get:()=>8}),Object.defineProperty(navigator,"maxTouchPoints",{get:()=>0}),window.outerHeight=window.innerHeight,window.outerWidth=window.innerWidth;const t=HTMLCanvasElement.prototype.getContext;HTMLCanvasElement.prototype.getContext=function(e){const r=t.call(this,e);if("2d"===e){const e=r.getImageData;r.getImageData=function(){return e.apply(this,arguments)}}return r}}),logger.info("\n========== 浏览器启动成功 =========="),logger.info("====================================\n"),{success:!0,headless:t,message:"浏览器启动成功"})}catch(e){return logger.info("\n========== 浏览器启动失败 =========="),logger.info(`错误: ${e.message}`),logger.info("====================================\n"),{success:!1,error:e.message,message:`浏览器启动失败: ${e.message}`}}}async ensureBrowser(){if(!this.browser){return(await this.launchBrowser({})).success}return!0}async navigateTo(e){try{const{url:t,timeout:r=3e4}=e;if(await this.ensureBrowser(),!t)return{success:!1,error:"缺少 url 参数",message:"导航失败: 缺少 URL"};logger.info("\n========== 导航到页面 =========="),logger.info(`URL: ${t}`),logger.info(`超时: ${r}ms`),logger.info("================================\n"),await this.page.goto(t,{waitUntil:"networkidle2",timeout:r});const s=await this.page.title(),o=this.page.url();return logger.info("\n========== 导航成功 =========="),logger.info(`页面标题: ${s}`),logger.info(`页面 URL: ${o}`),logger.info("=============================\n"),{success:!0,url:o,title:s,message:`成功导航到: ${t}`}}catch(t){return logger.info("\n========== 导航失败 =========="),logger.info(`URL: ${e.url}`),logger.info(`错误: ${t.message}`),logger.info("=============================\n"),{success:!1,error:t.message,message:`导航失败: ${t.message}`}}}async clickElement(e){try{const{selector:t,timeout:r=5e3}=e;return await this.ensureBrowser(),t?(logger.info("\n========== 点击元素 =========="),logger.info(`选择器: ${t}`),logger.info(`超时: ${r}ms`),logger.info("=============================\n"),await this.page.waitForSelector(t,{timeout:r}),await this.page.click(t),logger.info("\n========== 点击成功 ==========\n"),{success:!0,selector:t,message:`成功点击元素: ${t}`}):{success:!1,error:"缺少 selector 参数",message:"点击失败: 缺少选择器"}}catch(t){return logger.info("\n========== 点击失败 =========="),logger.info(`选择器: ${e.selector}`),logger.info(`错误: ${t.message}`),logger.info("=============================\n"),{success:!1,error:t.message,message:`点击失败: ${t.message}`}}}async fillInput(e){try{const{selector:t,value:r,timeout:s=5e3}=e;return await this.ensureBrowser(),t&&void 0!==r?(logger.info("\n========== 填写输入框 =========="),logger.info(`选择器: ${t}`),logger.info(`值: ${r}`),logger.info(`超时: ${s}ms`),logger.info("================================\n"),await this.page.waitForSelector(t,{timeout:s}),await this.page.type(t,r),logger.info("\n========== 填写成功 ==========\n"),{success:!0,selector:t,value:r,message:`成功填写输入框: ${t}`}):{success:!1,error:"缺少 selector 或 value 参数",message:"填写失败: 缺少必要参数"}}catch(t){return logger.info("\n========== 填写失败 =========="),logger.info(`选择器: ${e.selector}`),logger.info(`错误: ${t.message}`),logger.info("=============================\n"),{success:!1,error:t.message,message:`填写失败: ${t.message}`}}}async getPageContent(e){try{const{selector:t}=e;let r;if(await this.ensureBrowser(),logger.info("\n========== 获取页面内容 =========="),logger.info(`选择器: ${t||"整个页面"}`),logger.info("==================================\n"),t){const e=await this.page.$(t);if(!e)return{success:!1,error:"元素未找到",message:`未找到元素: ${t}`};r=await this.page.evaluate(e=>e.textContent,e)}else r=await this.page.evaluate(()=>document.body.textContent);const s=await this.page.title(),o=this.page.url();return logger.info("\n========== 获取成功 =========="),logger.info(`内容长度: ${r.length} 字符`),logger.info("=============================\n"),{success:!0,url:o,title:s,content:r,selector:t,message:"成功获取页面内容"}}catch(e){return logger.info("\n========== 获取失败 =========="),logger.info(`错误: ${e.message}`),logger.info("=============================\n"),{success:!1,error:e.message,message:`获取页面内容失败: ${e.message}`}}}async takeScreenshot(e){try{const{path:t,selector:r,quality:s=80,maxWidth:o,maxHeight:n}=e;let i;if(await this.ensureBrowser(),logger.info("\n========== 截图 =========="),logger.info(`路径: ${t||"不保存"}`),logger.info(`选择器: ${r||"整个页面"}`),logger.info(`压缩质量: ${s}`),logger.info("最大尺寸: "+(o?o+"x"+n:"不限制")),logger.info("===========================\n"),r){const e=await this.page.$(r);if(!e)return{success:!1,error:"元素未找到",message:`未找到元素: ${r}`};i=await e.screenshot({encoding:"base64"})}else i=await this.page.screenshot({encoding:"base64",fullPage:!0});if(t){const e=t.replace(/(\.[^.]+)$/,"_temp$1");await this.page.screenshot({path:e,fullPage:!r});let i=sharp(e);(o||n)&&(i=i.resize(o||null,n||null,{fit:"inside",withoutEnlargement:!0}));const a=t.split(".").pop().toLowerCase();i="jpg"===a||"jpeg"===a?i.jpeg({quality:s}):"png"===a?i.png({quality:Math.round(s/10)}):"webp"===a?i.webp({quality:s}):i.jpeg({quality:s}),await i.toFile(t);const g=await fs.stat(e),c=await fs.stat(t);await fs.unlink(e),logger.info("\n========== 压缩完成 =========="),logger.info(`原始大小: ${g.size} 字节`),logger.info(`压缩后: ${c.size} 字节`),logger.info(`压缩率: ${(100*(1-c.size/g.size)).toFixed(2)}%`),logger.info("=============================\n")}return logger.info("\n========== 截图成功 ==========\n"),{success:!0,path:t,screenshot:i,selector:r,message:"成功截图"+(t?`,保存到: ${t}`:"")}}catch(e){return logger.info("\n========== 截图失败 =========="),logger.info(`错误: ${e.message}`),logger.info("=============================\n"),{success:!1,error:e.message,message:`截图失败: ${e.message}`}}}async waitForElement(e){try{const{selector:t,timeout:r=3e4}=e;return await this.ensureBrowser(),t?(logger.info("\n========== 等待元素 =========="),logger.info(`选择器: ${t}`),logger.info(`超时: ${r}ms`),logger.info("===========================\n"),await this.page.waitForSelector(t,{timeout:r}),logger.info("\n========== 元素已出现 ==========\n"),{success:!0,selector:t,message:`元素已出现: ${t}`}):{success:!1,error:"缺少 selector 参数",message:"等待失败: 缺少选择器"}}catch(t){return logger.info("\n========== 等待超时 =========="),logger.info(`选择器: ${e.selector}`),logger.info(`错误: ${t.message}`),logger.info("=============================\n"),{success:!1,error:t.message,message:`等待元素超时: ${t.message}`}}}async executeScript(e){try{const{script:t}=e;if(await this.ensureBrowser(),!t)return{success:!1,error:"缺少 script 参数",message:"执行失败: 缺少脚本"};logger.info("\n========== 执行脚本 =========="),logger.info(`脚本: ${t.substring(0,100)}...`),logger.info("=============================\n");const r=await this.page.evaluate(t);return logger.info("\n========== 执行成功 ==========\n"),{success:!0,result:r,message:"脚本执行成功"}}catch(e){return logger.info("\n========== 执行失败 =========="),logger.info(`错误: ${e.message}`),logger.info("=============================\n"),{success:!1,error:e.message,message:`脚本执行失败: ${e.message}`}}}async closeBrowser(e){try{return this.browser?(logger.info("\n========== 关闭浏览器 ==========\n"),await this.browser.close(),this.browser=null,this.page=null,logger.info("\n========== 浏览器已关闭 ==========\n"),{success:!0,message:"浏览器已关闭"}):{success:!1,error:"浏览器未启动",message:"浏览器未运行"}}catch(e){return logger.info("\n========== 关闭失败 =========="),logger.info(`错误: ${e.message}`),logger.info("=============================\n"),{success:!1,error:e.message,message:`关闭浏览器失败: ${e.message}`}}}getFunctionDefinitions(){return[{type:"function",function:{name:"launch_browser",description:"启动浏览器实例(如果已启动则复用现有实例),支持多轮对话保持浏览器状态",parameters:{type:"object",properties:{headless:{type:"boolean",description:"是否使用无头模式(不显示浏览器界面),默认 true",default:!0}}}}},{type:"function",function:{name:"navigate_to",description:"导航到指定的 URL(如果浏览器未启动会自动启动)",parameters:{type:"object",properties:{url:{type:"string",description:"要访问的网页 URL,必须以 http:// 或 https:// 开头"},timeout:{type:"integer",description:"页面加载超时时间(毫秒),默认 30000",default:3e4}},required:["url"]}}},{type:"function",function:{name:"click_element",description:"点击页面上的元素(如果浏览器未启动会自动启动)",parameters:{type:"object",properties:{selector:{type:"string",description:"CSS 选择器,如 '#submit-btn', '.login-button', 'input[type=\"submit\"]'"},timeout:{type:"integer",description:"等待元素出现的超时时间(毫秒),默认 5000",default:5e3}},required:["selector"]}}},{type:"function",function:{name:"fill_input",description:"在输入框中填写内容(如果浏览器未启动会自动启动)",parameters:{type:"object",properties:{selector:{type:"string",description:"CSS 选择器,如 '#username', 'input[name=\"email\"]'"},value:{type:"string",description:"要输入的文本内容"},timeout:{type:"integer",description:"等待输入框出现的超时时间(毫秒),默认 5000",default:5e3}},required:["selector","value"]}}},{type:"function",function:{name:"get_page_content",description:"获取页面文本内容(如果浏览器未启动会自动启动)",parameters:{type:"object",properties:{selector:{type:"string",description:"CSS 选择器,可选,如果不提供则获取整个页面的内容"}}}}},{type:"function",function:{name:"wait_for_element",description:"等待页面元素出现(如果浏览器未启动会自动启动)",parameters:{type:"object",properties:{selector:{type:"string",description:"CSS 选择器,如 '#loading', '.content'"},timeout:{type:"integer",description:"等待超时时间(毫秒),默认 30000",default:3e4}},required:["selector"]}}},{type:"function",function:{name:"execute_script",description:"在页面中执行 JavaScript 代码(如果浏览器未启动会自动启动)",parameters:{type:"object",properties:{script:{type:"string",description:"要执行的 JavaScript 代码,如 'document.title' 或 'window.scrollTo(0, 1000)'"}},required:["script"]}}},{type:"function",function:{name:"close_browser",description:"关闭浏览器实例",parameters:{type:"object",properties:{}}}}]}}module.exports=BrowserTools;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inspiration-agent",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Personal AI Assistant with multi-channel support",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -34,6 +34,9 @@
34
34
  "engines": {
35
35
  "node": ">=18"
36
36
  },
37
+ "puppeteer": {
38
+ "skipDownload": true
39
+ },
37
40
  "dependencies": {
38
41
  "@larksuiteoapi/node-sdk": "^1.58.0",
39
42
  "axios": "^1.13.4",