Merge branch 'main' of github.com:tsingliuwin/autoclaw
This commit is contained in:
commit
25cf9929be
18 changed files with 1657 additions and 56 deletions
11
.env.example
11
.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
|
||||
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=
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -1,3 +1,4 @@
|
|||
.env
|
||||
node_modules
|
||||
dist
|
||||
dist
|
||||
.vscode
|
||||
|
|
@ -6,3 +6,4 @@ install.bat
|
|||
install.sh
|
||||
GEMINI.md
|
||||
node_modules/
|
||||
.vscode
|
||||
|
|
|
|||
51
CONTRIBUTING.md
Normal file
51
CONTRIBUTING.md
Normal file
|
|
@ -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!
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
155
README.md
155
README.md
|
|
@ -1,9 +1,21 @@
|
|||
# AutoClaw 🦞
|
||||
|
||||
[](https://www.npmjs.com/package/autoclaw)
|
||||
[](https://www.npmjs.com/package/autoclaw)
|
||||
[](https://github.com/tsingliuwin/autoclaw)
|
||||
[](https://github.com/tsingliuwin/autoclaw/blob/main/LICENSE)
|
||||
[](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 <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
|
||||
<<<<<<< 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
|
||||
|
|
|
|||
641
package-lock.json
generated
641
package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
29
package.json
29
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",
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
`
|
||||
}
|
||||
];
|
||||
|
|
|
|||
135
src/index.ts
135
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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
73
src/tools/browser.ts
Normal file
73
src/tools/browser.ts
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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}`;
|
||||
|
|
|
|||
278
src/tools/image.ts
Normal file
278
src/tools/image.ts
Normal file
|
|
@ -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<void> {
|
||||
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<string> => {
|
||||
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
|
||||
};
|
||||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
69
src/tools/prompt-optimizer.ts
Normal file
69
src/tools/prompt-optimizer.ts
Normal file
|
|
@ -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}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
192
src/tools/screenshot.ts
Normal file
192
src/tools/screenshot.ts
Normal file
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Loading…
Reference in a new issue