Merge branch 'main' of github.com:tsingliuwin/autoclaw

This commit is contained in:
tsingliu 2026-02-10 16:34:30 +08:00
commit 25cf9929be
18 changed files with 1657 additions and 56 deletions

View file

@ -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
View file

@ -1,3 +1,4 @@
.env
node_modules
dist
dist
.vscode

View file

@ -6,3 +6,4 @@ install.bat
install.sh
GEMINI.md
node_modules/
.vscode

51
CONTRIBUTING.md Normal file
View 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!

View file

@ -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
View file

@ -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 <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
View file

@ -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",

View file

@ -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",

View file

@ -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.
`
}
];

View file

@ -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
View 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();
}
}
};

View file

@ -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);
}
};

View file

@ -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
View 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
};

View file

@ -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() {

View file

@ -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",

View 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
View 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();
}
}
}
};