diff --git a/.env.example b/.env.example index 2097364..aab9fbf 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,12 @@ OPENAI_API_KEY=sk-your-key-here OPENAI_BASE_URL=https://api.openai.com/v1 -OPENAI_MODEL=gpt-4o \ No newline at end of file +OPENAI_MODEL=gpt-4o + +# Notifications (Optional) +# Configure Webhook URL and Security Keyword for your IM platform +FEISHU_WEBHOOK= +FEISHU_KEYWORD= +DINGTALK_WEBHOOK= +DINGTALK_KEYWORD= +WECOM_WEBHOOK= +WECOM_KEYWORD= \ No newline at end of file diff --git a/.gitignore b/.gitignore index bdfa6b8..59831f2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .env node_modules -dist \ No newline at end of file +dist +.vscode \ No newline at end of file diff --git a/.npmignore b/.npmignore index 759fd62..e825c10 100644 --- a/.npmignore +++ b/.npmignore @@ -6,3 +6,4 @@ install.bat install.sh GEMINI.md node_modules/ +.vscode diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..dcbce55 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,51 @@ +# Contributing to AutoClaw ๐Ÿฆž + +Thank you for your interest in contributing to AutoClaw! We welcome contributions from everyone. + +## Getting Started + +1. **Fork the repository**: Click the "Fork" button on GitHub. +2. **Clone your fork**: + ```bash + git clone https://github.com/YOUR_USERNAME/autoclaw.git + cd autoclaw + ``` +3. **Install dependencies**: + ```bash + npm install + ``` +4. **Create a branch**: + ```bash + git checkout -b feature/my-new-feature + ``` + +## Development Guidelines + +- **Code Style**: We use TypeScript. Please follow the existing code style (formatting, variable naming). +- **Testing**: Before submitting a PR, please run `npm run build` to ensure there are no compilation errors. +- **Commit Messages**: Write clear, descriptive commit messages. We follow the [Conventional Commits](https://www.conventionalcommits.org/) specification (e.g., `feat: add new tool`, `fix: correct typo in README`). + +## Submitting a Pull Request + +1. Push your changes to your fork: + ```bash + git push origin feature/my-new-feature + ``` +2. Go to the [original repository](https://github.com/tsingliuwin/autoclaw) and click "New Pull Request". +3. Describe your changes clearly. What problem does it solve? Why is this change necessary? +4. Wait for review! We will review your PR as soon as possible. + +## Reporting Bugs + +If you find a bug, please open an issue on GitHub with: +- A clear description of the bug. +- Steps to reproduce. +- Expected behavior. +- Screenshots or logs if applicable. + +## Feature Requests + +Have an idea for a new feature? Open an issue with the "enhancement" label and let's discuss it! + +--- +Thank you for helping make AutoClaw better! diff --git a/GEMINI.md b/GEMINI.md index 5d36c2e..c4b64c1 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -4,6 +4,8 @@ **AutoClaw** is a hyper-lightweight AI agent designed for **massive scale automation** in **headless/containerized environments**. It serves as the ideal "runtime" for executing LLM-driven tasks within Docker containers, allowing users to orchestrate thousands of agents simultaneously for complex parallel workflows. +**GitHub**: [https://github.com/tsingliuwin/autoclaw](https://github.com/tsingliuwin/autoclaw) + ## Core Philosophy - **Docker First**: Designed to run inside isolated containers (Alpine/Debian). - **Massive Scalability**: Low resource footprint enables high-concurrency swarms. diff --git a/README.md b/README.md index 779d3c6..4bc1f1d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,21 @@ # AutoClaw ๐Ÿฆž +[![NPM Version](https://img.shields.io/npm/v/autoclaw.svg?style=flat-square)](https://www.npmjs.com/package/autoclaw) +[![NPM Downloads](https://img.shields.io/npm/dm/autoclaw.svg?style=flat-square)](https://www.npmjs.com/package/autoclaw) +[![GitHub](https://img.shields.io/badge/GitHub-Repository-blue?logo=github&style=flat-square)](https://github.com/tsingliuwin/autoclaw) +[![License](https://img.shields.io/npm/l/autoclaw.svg?style=flat-square)](https://github.com/tsingliuwin/autoclaw/blob/main/LICENSE) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) + **The Engineering-First Headless Agent Framework: Stable, Scalable Automation for the Post-Vision Era.** English | [็ฎ€ไฝ“ไธญๆ–‡](./README.zh-CN.md) +--- + +๐Ÿ”— **GitHub Repository**: [https://github.com/tsingliuwin/autoclaw](https://github.com/tsingliuwin/autoclaw) + +--- + AutoClaw is a high-stability, open-source automation framework specifically engineered for **headless systems**. Unlike "screen-seeing" agents (such as OpenClaw) that rely on visual interpretation, AutoClaw is built on a foundation of precise command-driven execution. This makes it significantly more **stable**, **robust from an engineering perspective**, and **easier to scale** across complex environmentsโ€”whether it's a local server, a CI/CD pipeline, or thousands of containerized nodes. @@ -14,44 +26,94 @@ Unlike "screen-seeing" agents (such as OpenClaw) that rely on visual interpretat - ๐Ÿ›ก๏ธ **Superior Stability**: Immune to issues like UI rendering, screen resolution, or network lag that plague vision-based agents. - ๐Ÿ“ˆ **Massive Scalability**: Low resource consumption allows orchestrating thousands of instances (e.g., in K8s) for true automation swarms. - ๐Ÿ”Œ **Swarm Ready**: Stateless design allows for easy orchestration via K8s, Docker Swarm, or simple shell loops. +- ๐Ÿงฉ **Extensible Integrations**: Built-in support for Web Search (Tavily), Email (SMTP), and Notification Webhooks (Feishu, DingTalk, WeCom). ## Features - ๐Ÿ“œ **Headless Execution**: No browsers, no GUIs. Pure terminal efficiency. +<<<<<<< HEAD - ๐Ÿค– **Non-Interactive**: Intelligent flag handling (`-y`) for zero-touch automation. - ๐Ÿ“‚ **Universal Control**: From simple file I/O to complex system administration and code refactoring. - ๐Ÿง  **Context Aware**: Detects OS and container environments to optimize command strategies. +======= +- ๐Ÿค– **Non-Interactive Mode**: Intelligent flag handling (`-y`, `--no-interactive`) for zero-touch automation. +- ๐Ÿ“‚ **Universal Control**: From simple file I/O to complex system administration. +- ๐Ÿง  **Context Aware**: Detects container environments and provides accurate system time for relative date queries. +- ๐ŸŒ **Web Search**: Integrated with Tavily for real-time information retrieval. +- ๐Ÿ•’ **Time Accuracy**: Built-in tool to get precise system date and time for correct temporal context. +- ๐Ÿ“ง **Communication**: Send emails and push notifications to chat groups automatically. + +## Tech Stack +- **Runtime**: Node.js +- **Language**: TypeScript +- **Framework**: Commander.js +- **UI**: Inquirer (interactivity), Chalk (styling), Ora (spinners) +- **AI**: OpenAI SDK (Compatible with DeepSeek, LocalLLM, etc.) +>>>>>>> a5a91c879217b2677060b6f52805c3f99ffe732d ## Installation +### User Installation +Install globally via npm: ```bash npm install -g autoclaw ``` -## Updating - -To update AutoClaw to the latest version: - -```bash -npm update -g autoclaw -``` +### Development Installation +1. Clone the repository: + ```bash + git clone https://github.com/tsingliuwin/autoclaw.git + cd autoclaw + ``` +2. Install dependencies: + ```bash + npm install + ``` +3. Build the project: + ```bash + npm run build + ``` +4. Link globally (optional): + ```bash + npm link + ``` ## Quick Start -1. **Setup**: Run the setup wizard to configure your API key. +1. **Setup**: Run the interactive setup wizard to configure your API keys and integrations. ```bash autoclaw setup ``` -2. **Run**: Start the agent. +2. **Run**: Start the agent in interactive mode. ```bash autoclaw ``` -## Usage Examples +## Usage -- "List all TypeScript files in the src folder." -- "Create a new React component named Button in `components/Button.tsx`." -- "Check my disk usage and tell me which folder is the largest." +### Interactive Mode +Simply run `autoclaw` to enter the chat loop. +```bash +autoclaw +> List all TypeScript files in the src folder. +``` + +### Headless Mode (One-Shot) +Run a single command and exit. +```bash +autoclaw "Check disk usage and save the report to usage.txt" --no-interactive +``` + +### Auto-Confirm (CI/CD) +Automatically approve all tool executions (dangerous, use with caution or in sandboxes). +```bash +autoclaw "Refactor src/index.ts to use ES modules" -y +``` + +### CLI Options +- `-m, --model `: Specify the LLM model (default: `gpt-4o`). +- `-n, --no-interactive`: Exit after processing the initial query (Headless mode). +- `-y, --yes`: Auto-confirm all tool executions (e.g., shell commands). ## Configuration @@ -64,21 +126,76 @@ AutoClaw uses a hierarchical configuration system. 4. **Global Config**: (`~/.autoclaw/setting.json`) ### Supported Configuration Keys (JSON) -- `apiKey`: Your API Key. -- `baseUrl`: Custom Base URL. +- `apiKey`: Your OpenAI API Key. +- `baseUrl`: Custom Base URL (e.g., for DeepSeek or LocalLLM). - `model`: Default model to use. +- `tavilyApiKey`: API Key for Tavily Web Search. +- `smtpHost`, `smtpPort`, `smtpUser`, `smtpPass`, `smtpFrom`: SMTP Email settings. +- `feishuWebhook`, `dingtalkWebhook`, `wecomWebhook`: Notification webhooks. -### Project-Level Config (Example) +### Project-Level Config Example Create a file at `.autoclaw/setting.json`: ```json { "model": "gpt-3.5-turbo", - "baseUrl": "https://api.example.com/v1" + "baseUrl": "https://api.deepseek.com/v1" } ``` -> **โš ๏ธ Security Warning**: If you store your `apiKey` in `.autoclaw/setting.json`, make sure to add `.autoclaw/` to your `.gitignore` file to prevent leaking secrets! +> **โš ๏ธ Security Warning**: If you store your `apiKey` or secrets in `.autoclaw/setting.json`, make sure to add `.autoclaw/` to your `.gitignore` file to prevent leaking secrets! + +## Integrations + +### Web Search (Tavily) +AutoClaw can search the web if you provide a Tavily API Key during setup or in config. +- **Usage**: "Search for the latest Node.js release notes." + +### Email (SMTP) +Configure SMTP settings to let the agent send emails. +- **Usage**: "Send an email to user@example.com with the summary of the log file." + +### Notifications (Feishu/DingTalk/WeCom) +Configure webhooks to receive alerts or reports in your team chat apps. +- **Usage**: "Notify the team on Feishu that the build has finished." + +### Date & Time +Built-in utility to provide the agent with the current system time, ensuring accurate handling of relative time requests. +- **Usage**: "What's the date today?" or "Remind me to check the logs next Monday." + +## Docker Support + +### Chinese Font Issues in Screenshots +When running AutoClaw inside a Docker container (especially Alpine or Debian Slim), screenshots of Chinese websites may display text as square boxes ("tofu") due to missing fonts. Emojis (e.g., ๐Ÿ”ฅ) may also appear as squares. + +**Solution:** Install CJK (Chinese/Japanese/Korean) and Emoji fonts in your container. + +**For Debian/Ubuntu:** +```bash +apt-get update && apt-get install -y fonts-noto-cjk fonts-wqy-zenhei fonts-noto-color-emoji +``` + +**For Alpine Linux:** +```bash +apk add font-noto-cjk font-noto-emoji +``` ## License -MIT \ No newline at end of file +<<<<<<< HEAD +MIT +======= +MIT + +## Contributing + +Contributions are welcome! Please feel free to submit a Pull Request. + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + +--- +GitHub: [https://github.com/tsingliuwin/autoclaw](https://github.com/tsingliuwin/autoclaw) +>>>>>>> a5a91c879217b2677060b6f52805c3f99ffe732d diff --git a/package-lock.json b/package-lock.json index 6ba9feb..388b057 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,33 +1,75 @@ { "name": "autoclaw", - "version": "1.0.16", + "version": "1.0.38", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "autoclaw", - "version": "1.0.16", + "version": "1.0.38", "license": "MIT", "dependencies": { + "@mozilla/readability": "^0.6.0", "chalk": "^5.6.2", "commander": "^14.0.3", "dotenv": "^16.4.7", "inquirer": "^13.2.2", + "jsdom": "^28.0.0", "nodemailer": "^8.0.0", "openai": "^6.18.0", - "ora": "^9.3.0" + "ora": "^9.3.0", + "playwright": "^1.58.2" }, "bin": { "autoclaw": "dist/index.js" }, "devDependencies": { "@types/inquirer": "^9.0.9", + "@types/jsdom": "^27.0.0", "@types/node": "^25.2.1", "@types/nodemailer": "^7.0.9", "ts-node": "^10.9.2", "typescript": "^5.9.3" } }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmmirror.com/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.2", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", + "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==", + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.0.0", + "@csstools/css-color-parser": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.5" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.7.8", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/dom-selector/-/dom-selector-6.7.8.tgz", + "integrity": "sha512-stisC1nULNc9oH5lakAj8MH88ZxeGxzyWNDfbdCxvJSJIvDsHNZqYvscGTgy/ysgXWLJPt6K/4t0/GjvtKcFJQ==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.5" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmmirror.com/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "license": "MIT" + }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", @@ -41,6 +83,149 @@ "node": ">=12" } }, + "node_modules/@csstools/color-helpers": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-6.0.1.tgz", + "integrity": "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.0.0", + "resolved": "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-3.0.0.tgz", + "integrity": "sha512-q4d82GTl8BIlh/dTnVsWmxnbWJeb3kiU8eUH71UxlxnS+WIaALmtzTL8gR15PkYOexMQYVk0CO4qIG93C1IvPA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-4.0.1.tgz", + "integrity": "sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.0.1", + "@csstools/css-calc": "^3.0.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.26", + "resolved": "https://registry.npmmirror.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.26.tgz", + "integrity": "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.11.0", + "resolved": "https://registry.npmmirror.com/@exodus/bytes/-/bytes-1.11.0.tgz", + "integrity": "sha512-wO3vd8nsEHdumsXrjGO/v4p6irbg7hy9kvIeR6i2AwylZSk4HJdWgL0FNaVquW1+AweJcdvU1IEpuIWk/WaPnA==", + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, "node_modules/@inquirer/ansi": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.3.tgz", @@ -397,6 +582,15 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@mozilla/readability": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/@mozilla/readability/-/readability-0.6.0.tgz", + "integrity": "sha512-juG5VWh4qAivzTAeMzvY9xs9HY5rAcr2E4I7tiSSCokRFi7XIZCAu92ZkSTsIj1OPceCifL3cpfteP3pDT9/QQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", @@ -436,6 +630,31 @@ "rxjs": "^7.2.0" } }, + "node_modules/@types/jsdom": { + "version": "27.0.0", + "resolved": "https://registry.npmmirror.com/@types/jsdom/-/jsdom-27.0.0.tgz", + "integrity": "sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/jsdom/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmmirror.com/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/@types/node": { "version": "25.2.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz", @@ -466,6 +685,13 @@ "@types/node": "*" } }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", @@ -492,6 +718,15 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ansi-regex": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", @@ -523,6 +758,15 @@ "dev": true, "license": "MIT" }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, "node_modules/chalk": { "version": "5.6.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", @@ -593,6 +837,70 @@ "dev": true, "license": "MIT" }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "5.3.7", + "resolved": "https://registry.npmmirror.com/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "license": "MIT" + }, "node_modules/diff": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", @@ -621,6 +929,32 @@ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "license": "MIT" }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/get-east-asian-width": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", @@ -633,6 +967,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmmirror.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", @@ -687,6 +1059,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "license": "MIT" + }, "node_modules/is-unicode-supported": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", @@ -699,6 +1077,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jsdom": { + "version": "28.0.0", + "resolved": "https://registry.npmmirror.com/jsdom/-/jsdom-28.0.0.tgz", + "integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==", + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.7.6", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^5.3.7", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "undici": "^7.20.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/log-symbols": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", @@ -715,6 +1132,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lru-cache": { + "version": "11.2.5", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -722,6 +1148,12 @@ "dev": true, "license": "ISC" }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "license": "CC0-1.0" + }, "node_modules/mimic-function": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", @@ -734,6 +1166,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/mute-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", @@ -810,6 +1248,66 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmmirror.com/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmmirror.com/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmmirror.com/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -850,6 +1348,18 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/signal-exit": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", @@ -862,6 +1372,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/stdin-discarder": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.1.tgz", @@ -905,6 +1424,54 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "license": "MIT" + }, + "node_modules/tldts": { + "version": "7.0.22", + "resolved": "https://registry.npmmirror.com/tldts/-/tldts-7.0.22.tgz", + "integrity": "sha512-nqpKFC53CgopKPjT6Wfb6tpIcZXHcI6G37hesvikhx0EmUGPkZrujRyAjgnmp1SHNgpQfKVanZ+KfpANFt2Hxw==", + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.22" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.22", + "resolved": "https://registry.npmmirror.com/tldts-core/-/tldts-core-7.0.22.tgz", + "integrity": "sha512-KgbTDC5wzlL6j/x6np6wCnDSMUq4kucHNm00KXPbfNzmllCmtmvtykJHfmgdHntwIeupW04y8s1N/43S1PkQDw==", + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", @@ -969,6 +1536,15 @@ "node": ">=14.17" } }, + "node_modules/undici": { + "version": "7.21.0", + "resolved": "https://registry.npmmirror.com/undici/-/undici-7.21.0.tgz", + "integrity": "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg==", + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", @@ -983,6 +1559,50 @@ "dev": true, "license": "MIT" }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.0", + "resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-16.0.0.tgz", + "integrity": "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==", + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, "node_modules/wrap-ansi": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", @@ -1017,6 +1637,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "license": "MIT" + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index edc6bb8..4473b70 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "autoclaw", - "version": "1.0.16", + "version": "1.0.38", "type": "module", "main": "dist/index.js", "bin": { - "autoclaw": "./dist/index.js" + "autoclaw": "dist/index.js" }, "scripts": { "build": "tsc", @@ -25,22 +25,43 @@ "agent", "automation", "openai", - "tool" + "tool", + "docker", + "headless", + "devops", + "llm", + "gpt-4", + "typescript", + "orchestration", + "infrastructure", + "terminal" ], "author": "AutoClaw Contributor", "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/tsingliuwin/autoclaw.git" + }, + "bugs": { + "url": "https://github.com/tsingliuwin/autoclaw/issues" + }, + "homepage": "https://github.com/tsingliuwin/autoclaw#readme", "description": "A lightweight AI agent CLI tool that brings the power of LLMs to your terminal.", "dependencies": { + "@mozilla/readability": "^0.6.0", "chalk": "^5.6.2", "commander": "^14.0.3", "dotenv": "^16.4.7", "inquirer": "^13.2.2", + "jsdom": "^28.0.0", "nodemailer": "^8.0.0", "openai": "^6.18.0", - "ora": "^9.3.0" + "ora": "^9.3.0", + "playwright": "^1.58.2" }, "devDependencies": { "@types/inquirer": "^9.0.9", + "@types/jsdom": "^27.0.0", "@types/node": "^25.2.1", "@types/nodemailer": "^7.0.9", "ts-node": "^10.9.2", diff --git a/src/agent.ts b/src/agent.ts index 78ae434..7593138 100644 --- a/src/agent.ts +++ b/src/agent.ts @@ -27,6 +27,7 @@ System Information: - Current Working Directory: ${process.cwd()} - User: ${os.userInfo().username} - Home Directory: ${os.homedir()} +- Current Date: ${new Date().toLocaleString()} `; this.messages = [ @@ -48,6 +49,7 @@ GUIDELINES: 2. ROBUSTNESS: Use standard Linux/Unix tools found in minimal images (Alpine/Debian). 3. TOOLS: Use 'execute_shell_command' for actions, 'write_file' for code generation. 4. CLARITY: Output concise logs. You are a worker unit, not a chat bot. +5. OPTIMIZATION: When asked to generate creative content (images, stories, complex code), use 'optimize_prompt' first to ensure the best possible output quality. ` } ]; diff --git a/src/index.ts b/src/index.ts index e281dd2..974e52e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import { Agent } from './agent.js'; import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; +import * as readline from 'node:readline/promises'; import { fileURLToPath } from 'url'; // Handle Ctrl+C gracefully @@ -36,10 +37,20 @@ interface AppConfig { smtpPass?: string; smtpFrom?: string; tavilyApiKey?: string; + imageApiKey?: string; + imageBaseUrl?: string; + imageModel?: string; + imageSize?: string; + imageQuality?: string; + imageStyle?: string; + imageN?: number; autoConfirm?: boolean; feishuWebhook?: string; + feishuKeyword?: string; dingtalkWebhook?: string; + dingtalkKeyword?: string; wecomWebhook?: string; + wecomKeyword?: string; } function loadJsonConfig(filePath: string): AppConfig { @@ -81,8 +92,9 @@ program program .command('setup') .description('Run the interactive setup wizard to configure API keys') - .action(async () => { - await runSetup(); + .option('-p, --project', 'Save configuration to project-level (.autoclaw/setting.json)') + .action(async (options) => { + await runSetup(options); }); program @@ -95,11 +107,22 @@ program program.parse(process.argv); -async function runSetup() { - console.log(chalk.bold.cyan("AutoClaw Setup Wizard ๐Ÿฆž\n")); - console.log(chalk.dim(`Config will be saved to: ${GLOBAL_CONFIG_FILE}`)); +async function runSetup(options: any = {}) { + const isProject = options.project; + const targetFile = isProject ? LOCAL_CONFIG_FILE : GLOBAL_CONFIG_FILE; + const targetDir = isProject ? path.join(process.cwd(), '.autoclaw') : GLOBAL_CONFIG_DIR; - const currentConfig = loadJsonConfig(GLOBAL_CONFIG_FILE); + console.log(chalk.bold.cyan("AutoClaw Setup Wizard ๐Ÿฆž\n")); + console.log(chalk.dim(`Config will be saved to: ${targetFile}`)); + + // Load both to show current effective values as defaults + const globalConfig = loadJsonConfig(GLOBAL_CONFIG_FILE); + const localConfig = loadJsonConfig(LOCAL_CONFIG_FILE); + // If setting up Global (default), prioritize Global values for display, falling back to Local. + // If setting up Project, prioritize Project values (standard effective config). + const currentConfig = isProject + ? { ...globalConfig, ...localConfig } + : { ...localConfig, ...globalConfig }; function maskSecret(secret?: string): string { if (!secret || secret.length < 8) return '******'; @@ -132,6 +155,12 @@ async function runSetup() { message: 'Enter default Model:', default: currentConfig.model || 'gpt-4o' }, + { + type: 'confirm', + name: 'configureImage', + message: 'Do you want to configure a separate Image Generation Service (DALL-E)?', + default: !!currentConfig.imageApiKey + }, { type: 'confirm', name: 'configureEmail', @@ -155,6 +184,37 @@ async function runSetup() { // Resolve sensitive values (Keep old if empty) const finalApiKey = answers.apiKey || currentConfig.apiKey; + let imageConfig: any = {}; + if (answers.configureImage) { + const imageAnswers = await inquirer.prompt([ + { + type: 'password', + name: 'imageApiKey', + message: currentConfig.imageApiKey + ? `Enter Image Service API Key (Leave empty to keep ${maskSecret(currentConfig.imageApiKey)}, or leave empty to use main API key):` + : 'Enter Image Service API Key (Leave empty to use main API key):', + mask: '*' + }, + { + type: 'input', + name: 'imageBaseUrl', + message: 'Enter Image Service Base URL:', + default: currentConfig.imageBaseUrl || currentConfig.baseUrl || 'https://api.openai.com/v1' + }, + { + type: 'input', + name: 'imageModel', + message: 'Default Image Model:', + default: currentConfig.imageModel || 'dall-e-3' + } + ]); + imageConfig = { + imageApiKey: imageAnswers.imageApiKey || currentConfig.imageApiKey, + imageBaseUrl: imageAnswers.imageBaseUrl, + imageModel: imageAnswers.imageModel + }; + } + let emailConfig: any = {}; if (answers.configureEmail) { const emailAnswers = await inquirer.prompt([ @@ -222,6 +282,12 @@ async function runSetup() { : 'Feishu Webhook (Optional):', mask: '*' }, + { + type: 'input', + name: 'feishuKeyword', + message: 'Feishu Security Keyword (Optional):', + default: currentConfig.feishuKeyword + }, { type: 'password', name: 'dingtalkWebhook', @@ -230,6 +296,12 @@ async function runSetup() { : 'DingTalk Webhook (Optional):', mask: '*' }, + { + type: 'input', + name: 'dingtalkKeyword', + message: 'DingTalk Security Keyword (Optional):', + default: currentConfig.dingtalkKeyword + }, { type: 'password', name: 'wecomWebhook', @@ -237,12 +309,21 @@ async function runSetup() { ? `WeCom Webhook (Leave empty to keep ${maskSecret(currentConfig.wecomWebhook)}):` : 'WeCom Webhook (Optional):', mask: '*' + }, + { + type: 'input', + name: 'wecomKeyword', + message: 'WeCom Security Keyword (Optional):', + default: currentConfig.wecomKeyword } ]); notifyConfig = { feishuWebhook: notifyAnswers.feishuWebhook || currentConfig.feishuWebhook, + feishuKeyword: notifyAnswers.feishuKeyword || currentConfig.feishuKeyword, dingtalkWebhook: notifyAnswers.dingtalkWebhook || currentConfig.dingtalkWebhook, - wecomWebhook: notifyAnswers.wecomWebhook || currentConfig.wecomWebhook + dingtalkKeyword: notifyAnswers.dingtalkKeyword || currentConfig.dingtalkKeyword, + wecomWebhook: notifyAnswers.wecomWebhook || currentConfig.wecomWebhook, + wecomKeyword: notifyAnswers.wecomKeyword || currentConfig.wecomKeyword }; } @@ -250,17 +331,18 @@ async function runSetup() { apiKey: finalApiKey, baseUrl: answers.baseUrl, model: answers.model, + ...imageConfig, ...emailConfig, ...searchConfig, ...notifyConfig }; try { - if (!fs.existsSync(GLOBAL_CONFIG_DIR)) { - fs.mkdirSync(GLOBAL_CONFIG_DIR, { recursive: true }); + if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir, { recursive: true }); } - fs.writeFileSync(GLOBAL_CONFIG_FILE, JSON.stringify(newConfig, null, 2), { mode: 0o600 }); - console.log(chalk.green(`\nโœ… Configuration saved to ${GLOBAL_CONFIG_FILE}`)); + fs.writeFileSync(targetFile, JSON.stringify(newConfig, null, 2), { mode: 0o600 }); + console.log(chalk.green(`\nโœ… Configuration saved to ${targetFile}`)); console.log(chalk.cyan("You can now run 'autoclaw' to start using the agent.")); } catch (error: any) { console.error(chalk.red(`Failed to write config: ${error.message}`)); @@ -302,8 +384,11 @@ async function runChat(queryParts: string[], options: any) { if (process.env.SMTP_PASS) fullConfig.smtpPass = process.env.SMTP_PASS; if (process.env.TAVILY_API_KEY) fullConfig.tavilyApiKey = process.env.TAVILY_API_KEY; if (process.env.FEISHU_WEBHOOK) fullConfig.feishuWebhook = process.env.FEISHU_WEBHOOK; + if (process.env.FEISHU_KEYWORD) fullConfig.feishuKeyword = process.env.FEISHU_KEYWORD; if (process.env.DINGTALK_WEBHOOK) fullConfig.dingtalkWebhook = process.env.DINGTALK_WEBHOOK; + if (process.env.DINGTALK_KEYWORD) fullConfig.dingtalkKeyword = process.env.DINGTALK_KEYWORD; if (process.env.WECOM_WEBHOOK) fullConfig.wecomWebhook = process.env.WECOM_WEBHOOK; + if (process.env.WECOM_KEYWORD) fullConfig.wecomKeyword = process.env.WECOM_KEYWORD; if (!apiKey) { console.log(chalk.yellow("API Key not found.")); @@ -355,15 +440,15 @@ async function runChat(queryParts: string[], options: any) { } // Main chat loop + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: true + }); + try { while (true) { - const { userInput } = await inquirer.prompt([ - { - type: 'input', - name: 'userInput', - message: 'You >' - } - ]); + const userInput = await rl.question(chalk.green('?') + ' You > '); if (userInput.toLowerCase() === 'exit' || userInput.toLowerCase() === 'quit') { console.log(chalk.cyan("Goodbye!")); @@ -372,15 +457,21 @@ async function runChat(queryParts: string[], options: any) { if (userInput.trim() === '') continue; - await agent.chat(userInput); + rl.pause(); + try { + await agent.chat(userInput); + } finally { + rl.resume(); + } } } catch (err: any) { - // Check for Inquirer interruption error (Ctrl+C often causes this) if (err.message && (err.message.includes('User force closed') || err.message.includes('Prompt was canceled'))) { console.log(chalk.cyan("\nGoodbye!")); - process.exit(0); + } else { + console.error(chalk.red("Error in chat loop:"), err); } - throw err; // Re-throw real errors to be caught by main().catch + } finally { + rl.close(); } } diff --git a/src/tools/browser.ts b/src/tools/browser.ts new file mode 100644 index 0000000..d993833 --- /dev/null +++ b/src/tools/browser.ts @@ -0,0 +1,73 @@ +import { chromium } from 'playwright'; +import { Readability } from '@mozilla/readability'; +import { JSDOM } from 'jsdom'; +import { ToolModule } from './interface.js'; + +export const BrowserTool: ToolModule = { + name: "Web Browser", + configKeys: [], + definition: { + type: "function", + function: { + name: "read_website", + description: "Reads and extracts the main content from a website URL. Use this to summarize articles or get information from specific pages.", + parameters: { + type: "object", + properties: { + url: { + type: "string", + description: "The full URL of the website to read (e.g., https://example.com/article)." + } + }, + required: ["url"] + } + } + }, + handler: async (args: any, config: any) => { + let browser; + try { + browser = await chromium.launch({ headless: true }); + } catch (error: any) { + if (error.message.includes("Executable doesn't exist")) { + return "Error: Playwright browsers are not installed. Please run `npx playwright install chromium` to enable this feature."; + } + return `Error launching browser: ${error.message}`; + } + + const context = await browser.newContext({ + userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' + }); + const page = await context.newPage(); + + try { + console.log(`Navigating to ${args.url}...`); + await page.goto(args.url, { waitUntil: 'domcontentloaded', timeout: 30000 }); + + // Get the full HTML content + const html = await page.content(); + + // Use JSDOM to create a virtual DOM for Readability + const dom = new JSDOM(html, { url: args.url }); + const reader = new Readability(dom.window.document); + const article = reader.parse(); + + if (!article) { + // Fallback: just return body text if Readability fails + const bodyText = await page.innerText('body'); + return `Could not parse article content with Readability. Raw text content: + +${bodyText.slice(0, 5000)}... (truncated)`; + } + + return `Title: ${article.title} + +Content: +${(article.textContent || "").trim()}`; + + } catch (error: any) { + return `Error reading website: ${error.message}`; + } finally { + await browser.close(); + } + } +}; diff --git a/src/tools/core.ts b/src/tools/core.ts index 2ece8dd..1d48e0b 100644 --- a/src/tools/core.ts +++ b/src/tools/core.ts @@ -107,3 +107,28 @@ export const WriteFileTool: ToolModule = { } } }; + +export const DateTimeTool: ToolModule = { + name: "Date & Time", + definition: { + type: "function", + function: { + name: "get_current_datetime", + description: "Get the current system date and time. Use this when the user refers to relative dates (like 'today', 'next week', 'this March') to ensure accuracy.", + parameters: { + type: "object", + properties: {}, + required: [] + } + } + }, + handler: async () => { + const now = new Date(); + return JSON.stringify({ + iso: now.toISOString(), + local: now.toLocaleString(), + timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, + weekday: now.toLocaleDateString('en-US', { weekday: 'long' }) + }, null, 2); + } +}; diff --git a/src/tools/email.ts b/src/tools/email.ts index 3bf9417..f968dd4 100644 --- a/src/tools/email.ts +++ b/src/tools/email.ts @@ -8,13 +8,18 @@ export const EmailTool: ToolModule = { type: "function", function: { name: "send_email", - description: "Send an email using configured SMTP settings.", + description: "Send an email using configured SMTP settings. Can include optional file attachments.", parameters: { type: "object", properties: { to: { type: "string", description: "Recipient email address." }, subject: { type: "string", description: "Email subject." }, - body: { type: "string", description: "Email body content (text)." } + body: { type: "string", description: "Email body content (text)." }, + attachments: { + type: "array", + items: { type: "string" }, + description: "Optional list of local file paths to attach to the email." + } }, required: ["to", "subject", "body"] } @@ -37,11 +42,16 @@ export const EmailTool: ToolModule = { }, }); + const emailAttachments = args.attachments?.map((filePath: string) => ({ + path: filePath + })) || []; + const info = await transporter.sendMail({ from: config.smtpFrom || config.smtpUser, // sender address to: args.to, // list of receivers subject: args.subject, // Subject line text: args.body, // plain text body + attachments: emailAttachments }); return `Email sent successfully. Message ID: ${info.messageId}`; diff --git a/src/tools/image.ts b/src/tools/image.ts new file mode 100644 index 0000000..e9b6b1f --- /dev/null +++ b/src/tools/image.ts @@ -0,0 +1,278 @@ +import OpenAI from 'openai'; +import chalk from 'chalk'; +import * as fs from 'fs'; +import * as path from 'path'; +import { ToolModule } from './interface.js'; + +const toolDefinition = { + type: "function", + function: { + name: "generate_image", + description: "Generates or edits images using AI models (DALL-E 3/2). Supports text-to-image, image variation, and image editing. Allows control over size, resolution (quality), and model selection.", + parameters: { + type: "object", + properties: { + prompt: { + type: "string", + description: "Text description of the desired image. Required for text-to-image and edit modes." + }, + image_path: { + type: "string", + description: "Path to an existing image file (local path). Required for variation and editing modes." + }, + mask_path: { + type: "string", + description: "Path to a mask image file (local path). Optional, used only for editing." + }, + mode: { + type: "string", + enum: ["text-to-image", "variation", "edit"], + description: "Operation mode. Inferred if not provided." + }, + model: { + type: "string", + description: "The AI model to use. 'dall-e-3' for high quality (default), 'dall-e-2' for editing, or a custom model like 'doubao-seedream-4-5-251128'.", + default: "dall-e-3" + }, + n: { + type: "integer", + description: "Number of images to generate. Default is 1.", + default: 1 + }, + size: { + type: "string", + description: "Resolution/Aspect Ratio. YOU should infer the best size based on the prompt content.\n- DALL-E 3: '1024x1024' (Square), '1792x1024' (Landscape), '1024x1792' (Portrait).\n- Doubao/High-Res: MUST be >3.6M pixels. Use '2048x2048' (Square), '2560x1440' (Landscape), '1440x2560' (Portrait).", + default: "1024x1024" + }, + quality: { + type: "string", + enum: ["standard", "hd"], + description: "Image quality (DALL-E 3 only). 'hd' creates more detailed images. Default is 'standard'.", + default: "standard" + }, + style: { + type: "string", + enum: ["vivid", "natural"], + description: "Image style (DALL-E 3 only). Default is 'vivid'.", + default: "vivid" + }, + output_dir: { + type: "string", + description: "Directory to save the generated images. Defaults to current directory." + } + }, + required: [] + } + } +}; + +async function downloadImage(url: string, destPath: string): Promise { + const response = await fetch(url); + if (!response.ok) throw new Error(`Failed to download image: ${response.statusText}`); + const buffer = await response.arrayBuffer(); + fs.writeFileSync(destPath, Buffer.from(buffer)); +} + +const handler = async (args: any, config: any): Promise => { + const apiKey = config.imageApiKey || config.apiKey || process.env.OPENAI_API_KEY; + const baseURL = config.imageBaseUrl || config.baseUrl || process.env.OPENAI_BASE_URL; + + if (!apiKey) { + return "Error: Image Service API Key is missing. Please configure it in .autoclaw/setting.json (imageApiKey or apiKey)."; + } + + const client = new OpenAI({ + apiKey: apiKey, + baseURL: baseURL + }); + + const { + prompt, + image_path, + mask_path, + output_dir = "." + } = args; + + const n = args.n || config.imageN || 1; + + // Model-specific default size + let mode = args.mode; + let model = args.model; + if (config.imageModel && (!model || model === 'dall-e-3')) { + model = config.imageModel; + } + model = model || "dall-e-3"; + + let defaultSize = "1024x1024"; + if (model.toLowerCase().includes("doubao")) { + defaultSize = "2048x2048"; + } + + const size = args.size || config.imageSize || defaultSize; + const quality = args.quality || config.imageQuality || "standard"; + const style = args.style || config.imageStyle || "vivid"; + + // Infer mode if not provided + if (!mode) { + if (image_path && mask_path) mode = "edit"; + else if (image_path) mode = "variation"; + else mode = "text-to-image"; + } + + // Model-specific validations + if (mode === "text-to-image") { + // DALL-E 3 Validation + if (model === "dall-e-3") { + const validSizes = ["1024x1024", "1024x1792", "1792x1024"]; + if (!validSizes.includes(size)) { + return `Error: Invalid size '${size}' for DALL-E 3. Supported sizes are: ${validSizes.join(", ")}.`; + } + } + // DALL-E 2 Validation + else if (model === "dall-e-2") { + const validSizes = ["256x256", "512x512", "1024x1024"]; + if (!validSizes.includes(size)) { + return `Error: Invalid size '${size}' for DALL-E 2. Supported sizes are: ${validSizes.join(", ")}.`; + } + } + } else { + // Variation and Edit only support DALL-E 2 currently + if (model === "dall-e-3") { + console.log("Note: DALL-E 3 does not support variation/edit. Falling back to DALL-E 2."); + model = "dall-e-2"; + } + } + + // Resolve output directory + const resolvedOutputDir = path.resolve(process.cwd(), output_dir); + if (!fs.existsSync(resolvedOutputDir)) { + fs.mkdirSync(resolvedOutputDir, { recursive: true }); + } + + const generatedFiles: string[] = []; + + try { + if (mode === "text-to-image") { + if (!prompt) return "Error: 'prompt' is required for text-to-image mode."; + + console.log(`Generating ${n} image(s) with ${model} (${size}, ${quality})...`); + + if (model === "dall-e-3") { + for (let i = 0; i < n; i++) { + const response = await client.images.generate({ + model: "dall-e-3", + prompt: prompt, + n: 1, // DALL-E 3 constraint + size: size as any, + quality: quality as any, + style: style as any, + response_format: "url" + }); + + const imageUrl = response.data?.[0]?.url; + if (imageUrl) { + const fileName = `generated-${Date.now()}-${i + 1}.png`; + const filePath = path.join(resolvedOutputDir, fileName); + await downloadImage(imageUrl, filePath); + generatedFiles.push(filePath); + } + } + } else { + // DALL-E 2 or Custom Model + const response = await client.images.generate({ + model: model, + prompt: prompt, + n: n, + size: size as any, + response_format: "url" + }); + + const data = response.data || []; + for (let i = 0; i < data.length; i++) { + const imageUrl = data[i].url; + if (imageUrl) { + const fileName = `generated-${Date.now()}-${i + 1}.png`; + const filePath = path.join(resolvedOutputDir, fileName); + await downloadImage(imageUrl, filePath); + generatedFiles.push(filePath); + } + } + } + + } else if (mode === "variation") { + if (!image_path) return "Error: 'image_path' is required for variation mode."; + if (!fs.existsSync(image_path)) return `Error: Image file not found at ${image_path}`; + + console.log(`Generating ${n} variation(s) with ${model}...`); + + const response = await client.images.createVariation({ + image: fs.createReadStream(image_path), + n: n, + model: "dall-e-2", // Explicitly set model just in case, though it's the default/only option + size: size as any, + response_format: "url" + }); + + const data = response.data || []; + for (let i = 0; i < data.length; i++) { + const imageUrl = data[i].url; + if (imageUrl) { + const fileName = `variation-${Date.now()}-${i + 1}.png`; + const filePath = path.join(resolvedOutputDir, fileName); + await downloadImage(imageUrl, filePath); + generatedFiles.push(filePath); + } + } + + } else if (mode === "edit") { + if (!image_path) return "Error: 'image_path' is required for edit mode."; + if (!prompt) return "Error: 'prompt' is required for edit mode."; + if (!fs.existsSync(image_path)) return `Error: Image file not found at ${image_path}`; + + console.log(`Editing image with ${model}...`); + + const params: any = { + image: fs.createReadStream(image_path), + prompt: prompt, + n: n, + model: "dall-e-2", + size: size as any, + response_format: "url" + }; + + if (mask_path && fs.existsSync(mask_path)) { + params.mask = fs.createReadStream(mask_path); + } + + const response = await client.images.edit(params); + + const data = response.data || []; + for (let i = 0; i < data.length; i++) { + const imageUrl = data[i].url; + if (imageUrl) { + const fileName = `edited-${Date.now()}-${i + 1}.png`; + const filePath = path.join(resolvedOutputDir, fileName); + await downloadImage(imageUrl, filePath); + generatedFiles.push(filePath); + } + } + } else { + return `Error: Unknown mode '${mode}'.`; + } + + return `Successfully generated ${generatedFiles.length} image(s):\n${generatedFiles.join('\n')}`; + + } catch (error: any) { + console.error(chalk.red(`Image Generation Failed: ${error.message}`)); + if (error.response && error.response.data) { + console.error(chalk.dim(JSON.stringify(error.response.data))); + } + return `Error generating image: ${error.message}`; + } +}; + +export const ImageTool: ToolModule = { + name: "Image Generation", + definition: toolDefinition as any, + handler: handler +}; diff --git a/src/tools/index.ts b/src/tools/index.ts index bac7386..1333730 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,17 +1,26 @@ import { ToolModule } from './interface.js'; -import { ShellTool, ReadFileTool, WriteFileTool } from './core.js'; +import { ShellTool, ReadFileTool, WriteFileTool, DateTimeTool } from './core.js'; import { EmailTool } from './email.js'; import { SearchTool } from './search.js'; import { NotifyTool } from './notify.js'; +import { BrowserTool } from './browser.js'; +import { ScreenshotTool } from './screenshot.js'; +import { ImageTool } from './image.js'; +import { PromptOptimizerTool } from './prompt-optimizer.js'; // Central Registry of all available tools export const toolRegistry: ToolModule[] = [ ShellTool, ReadFileTool, WriteFileTool, + DateTimeTool, + PromptOptimizerTool, EmailTool, SearchTool, - NotifyTool + NotifyTool, + BrowserTool, + ScreenshotTool, + ImageTool ]; export function getToolDefinitions() { diff --git a/src/tools/notify.ts b/src/tools/notify.ts index c159ec7..a137f91 100644 --- a/src/tools/notify.ts +++ b/src/tools/notify.ts @@ -2,7 +2,11 @@ import { ToolModule } from './interface.js'; export const NotifyTool: ToolModule = { name: "Group Bot Notification", - configKeys: ["feishuWebhook", "dingtalkWebhook", "wecomWebhook"], + configKeys: [ + "feishuWebhook", "feishuKeyword", + "dingtalkWebhook", "dingtalkKeyword", + "wecomWebhook", "wecomKeyword" + ], definition: { type: "function", function: { @@ -26,14 +30,23 @@ export const NotifyTool: ToolModule = { } }, handler: async (args: any, config: any) => { - const { platform, content } = args; + let { platform, content } = args; let webhookUrl = ''; let payload = {}; + // Helper to ensure security keyword is present + const ensureKeyword = (envKey: string, configKey: string) => { + const keyword = config[configKey] || process.env[envKey]; + if (keyword && !content.includes(keyword)) { + content = `[${keyword}] ${content}`; + } + }; + // 1. Determine Webhook URL and Payload Format switch (platform) { case 'feishu': webhookUrl = config.feishuWebhook || process.env.FEISHU_WEBHOOK; + ensureKeyword('FEISHU_KEYWORD', 'feishuKeyword'); if (!webhookUrl) return "Error: Feishu Webhook URL is not configured."; payload = { msg_type: "text", @@ -43,6 +56,7 @@ export const NotifyTool: ToolModule = { case 'dingtalk': webhookUrl = config.dingtalkWebhook || process.env.DINGTALK_WEBHOOK; + ensureKeyword('DINGTALK_KEYWORD', 'dingtalkKeyword'); if (!webhookUrl) return "Error: DingTalk Webhook URL is not configured."; payload = { msgtype: "text", @@ -52,6 +66,7 @@ export const NotifyTool: ToolModule = { case 'wecom': webhookUrl = config.wecomWebhook || process.env.WECOM_WEBHOOK; + ensureKeyword('WECOM_KEYWORD', 'wecomKeyword'); if (!webhookUrl) return "Error: WeCom Webhook URL is not configured."; payload = { msgtype: "text", diff --git a/src/tools/prompt-optimizer.ts b/src/tools/prompt-optimizer.ts new file mode 100644 index 0000000..da54fb7 --- /dev/null +++ b/src/tools/prompt-optimizer.ts @@ -0,0 +1,69 @@ +import OpenAI from 'openai'; +import { ToolModule } from './interface.js'; + +export const PromptOptimizerTool: ToolModule = { + name: "Prompt Optimizer", + definition: { + type: "function", + function: { + name: "optimize_prompt", + description: "Optimize a user's raw task description or prompt to be more professional, structured, and effective. STRONGLY RECOMMENDED for creative tasks (like image generation) or complex scripts to ensure high-quality results.", + parameters: { + type: "object", + properties: { + raw_prompt: { + type: "string", + description: "The original, raw prompt or task description provided by the user." + }, + context: { + type: "string", + description: "Optional context about the goal, audience, or specific requirements (e.g., 'for an image generator', 'for a code reviewer')." + } + }, + required: ["raw_prompt"] + } + } + }, + handler: async (args: any, config: any) => { + if (!config?.apiKey) { + return "Error: OpenAI API Key is missing in the configuration. Please run 'autoclaw setup' or check your .env file."; + } + + const client = new OpenAI({ + apiKey: config.apiKey, + baseURL: config.baseUrl + }); + + const contextMsg = args.context ? `Context: ${args.context}` : "Context: General AI Assistant interaction."; + + try { + const completion = await client.chat.completions.create({ + model: config.model || 'gpt-4o', + messages: [ + { + role: "system", + content: `You are an expert Prompt Engineer. Your goal is to rewrite the user's raw prompt to be clear, precise, and highly effective for LLMs or professional communication. + +RULES: +1. Preserve the original intent. +2. Structure the prompt logically (e.g., Role, Context, Task, Constraints, Output Format). +3. Use professional and concise language. +4. Return ONLY the optimized prompt. Do not add conversational filler.` + }, + { + role: "user", + content: `Raw Prompt: "${args.raw_prompt}" + +${contextMsg} + +Please optimize this prompt.` + } + ] + }); + + return completion.choices[0].message?.content || "Error: Failed to generate optimized prompt."; + } catch (error: any) { + return `Error optimizing prompt: ${error.message}`; + } + } +}; diff --git a/src/tools/screenshot.ts b/src/tools/screenshot.ts new file mode 100644 index 0000000..d359045 --- /dev/null +++ b/src/tools/screenshot.ts @@ -0,0 +1,192 @@ +import { chromium } from 'playwright'; +import { ToolModule } from './interface.js'; +import * as fs from 'fs'; +import * as os from 'os'; +import * as child_process from 'child_process'; + +// Helper to check for common CJK and Emoji font paths on Linux +const checkLinuxFonts = () => { + if (os.platform() !== 'linux') return { cjk: true, emoji: true }; + + // Check for specific font files rather than just directories + const commonCJKFontFiles = [ + '/usr/share/fonts/noto/NotoSansCJK-Regular.ttc', // Alpine / Some Debian + '/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc', // Debian / Ubuntu + '/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc', // ZenHei + '/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc' // Arch + ]; + + const commonEmojiFontFiles = [ + '/usr/share/fonts/noto/NotoColorEmoji.ttf', + '/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf', + '/usr/share/fonts/google-noto-emoji/NotoColorEmoji.ttf' + ]; + + const hasCJK = commonCJKFontFiles.some(path => fs.existsSync(path)); + const hasEmoji = commonEmojiFontFiles.some(path => fs.existsSync(path)); + + // Also check if fc-list finds fonts (if available) - secondary check + try { + const cjkOutput = child_process.execSync('fc-list :lang=zh', { stdio: 'pipe' }).toString(); + const emojiOutput = child_process.execSync('fc-list :family=Emoji', { stdio: 'pipe' }).toString(); // Approximate check + + return { + cjk: hasCJK || cjkOutput.length > 0, + emoji: hasEmoji || emojiOutput.length > 0 + }; + } catch (e) { + // fc-list might not be installed or failed, fall back to file check + return { cjk: hasCJK, emoji: hasEmoji }; + } +}; + +const installFonts = (missing: { cjk: boolean, emoji: boolean }) => { + try { + let installCmd = ''; + if (fs.existsSync('/etc/alpine-release')) { + // Alpine + const pkgs = []; + if (!missing.cjk) pkgs.push('font-noto-cjk'); + if (!missing.emoji) pkgs.push('font-noto-emoji'); + if (pkgs.length > 0) { + installCmd = `apk add --no-cache ${pkgs.join(' ')}`; + } + } else if (fs.existsSync('/etc/debian_version')) { + // Debian/Ubuntu + const pkgs = []; + if (!missing.cjk) pkgs.push('fonts-noto-cjk', 'fonts-wqy-zenhei'); + if (!missing.emoji) pkgs.push('fonts-noto-color-emoji'); + if (pkgs.length > 0) { + // apt-get update is often needed first in clean containers + installCmd = `apt-get update && apt-get install -y ${pkgs.join(' ')}`; + } + } + + if (installCmd) { + console.log(`Creating font environment... (${installCmd})`); + console.log("This may take a few moments..."); + child_process.execSync(installCmd, { stdio: 'inherit' }); + console.log('โœ… Fonts installed successfully.'); + return true; + } + } catch (e: any) { + console.warn(`โš ๏ธ Failed to auto-install fonts: ${e.message}`); + console.warn('Please install them manually to fix "tofu" characters.'); + } + return false; +}; + +export const ScreenshotTool: ToolModule = { + name: "Screenshot Tool", + configKeys: [], + definition: { + type: "function", + function: { + name: "take_screenshot", + description: "Captures a screenshot of a specified website and saves it as an image file.", + parameters: { + type: "object", + properties: { + url: { + type: "string", + description: "The full URL of the website to capture (e.g., https://google.com)." + }, + outputPath: { + type: "string", + description: "The file path where the screenshot should be saved (e.g., 'homepage.png')." + }, + fullPage: { + type: "boolean", + description: "If true (default), takes a screenshot of the full scrollable page. Set to false for viewport only." + }, + waitTime: { + type: "number", + description: "Optional delay in seconds to wait for page resources to load before capturing (default: 1)." + } + }, + required: ["url", "outputPath"] + } + } + }, + handler: async (args: any, config: any) => { + // Check for fonts on Linux to prevent "tofu" characters + if (os.platform() === 'linux') { + const fonts = checkLinuxFonts(); + if (!fonts.cjk || !fonts.emoji) { + console.log("Missing fonts detected. Attempting to fix environment..."); + installFonts(fonts); + } + } + + let browser; + const launchOptions: any = { + headless: true, + args: ['--font-render-hinting=none'] + }; + + try { + // Try to launch system Chrome first as it usually has better font support + browser = await chromium.launch({ ...launchOptions, channel: 'chrome' }); + } catch (e) { + // Fallback to bundled Chromium + try { + browser = await chromium.launch(launchOptions); + } catch (error: any) { + if (error.message.includes("Executable doesn't exist")) { + return "Error: Playwright browsers are not installed. Please run `npx playwright install chromium` to enable this feature."; + } + return `Error launching browser: ${error.message}`; + } + } + + const context = await browser.newContext({ + userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + viewport: { width: 1280, height: 720 }, + locale: 'zh-CN', // Set locale to Chinese + deviceScaleFactor: 2 // High DPI + }); + const page = await context.newPage(); + + try { + console.log(`Navigating to ${args.url} for screenshot...`); + await page.goto(args.url, { waitUntil: 'networkidle', timeout: 30000 }); + + // Inject CSS to force common Chinese fonts + // Note: For Docker environments (Alpine/Debian), ensure fonts are installed. + // Alpine: apk add font-noto-cjk font-noto-emoji + // Debian/Ubuntu: apt-get install fonts-noto-cjk fonts-wqy-zenhei fonts-noto-color-emoji + await page.addStyleTag({ + content: ` + body, h1, h2, h3, h4, h5, h6, p, span, div, li, a, button, input, textarea { + font-family: "PingFang SC", "Heiti SC", "Microsoft YaHei", "WenQuanYi Micro Hei", "Noto Sans CJK SC", "Noto Sans SC", "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", sans-serif !important; + } + ` + }); + + // Wait for fonts to be ready + await page.evaluate(() => document.fonts.ready); + + // Additional delay for dynamic content (user specified or default 1s) + const waitTimeMs = (args.waitTime || 1) * 1000; + if (waitTimeMs > 0) { + console.log(`Waiting for ${waitTimeMs}ms...`); + await page.waitForTimeout(waitTimeMs); + } + + await page.screenshot({ + path: args.outputPath, + fullPage: args.fullPage !== false // Default to true if undefined + }); + + return `Successfully captured screenshot of ${args.url} and saved to ${args.outputPath}`; + + } catch (error: any) { + return `Error taking screenshot: ${error.message}`; + } finally { + if (browser) { + await browser.close(); + } + } + } + }; + \ No newline at end of file