commit 0d43b4399885392460defbc37d0ac06ec258585f Author: tsingliu <410869548@qq.com> Date: Fri Feb 6 18:41:02 2026 +0800 init diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..2097364 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +OPENAI_API_KEY=sk-your-key-here +OPENAI_BASE_URL=https://api.openai.com/v1 +OPENAI_MODEL=gpt-4o \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bdfa6b8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +node_modules +dist \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..759fd62 --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +src/ +tsconfig.json +.env +.env.example +install.bat +install.sh +GEMINI.md +node_modules/ diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..5d36c2e --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,81 @@ +# Project: AutoClaw + +## Project Overview +**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. + +## Core Philosophy +- **Docker First**: Designed to run inside isolated containers (Alpine/Debian). +- **Massive Scalability**: Low resource footprint enables high-concurrency swarms. +- **Headless & Non-Interactive**: Zero GUI dependencies; optimized for CI/CD and Clusters. + +## Technology Stack +- **Runtime**: Node.js +- **Language**: TypeScript +- **Framework**: Commander.js +- **UI**: Inquirer (interactivity), Chalk (styling), Ora (spinners) +- **AI**: OpenAI SDK + +## Directory Structure +- `src/`: Source code + - `index.ts`: CLI entry point and main loop. + - `agent.ts`: Agent class handling LLM interaction and tool loop. + - `tools.ts`: Implementation of tools (Shell execution, File I/O). +- `dist/`: Compiled JavaScript files. + +## Getting Started + +### Prerequisites +- Node.js installed. +- OpenAI API Key (or compatible provider like DeepSeek, LocalLLM). + +### Installation (Development) +1. Install dependencies: + ```bash + npm install + ``` +2. Build the project: + ```bash + npm run build + ``` + +### Installation (User) +```bash +npm install -g autoclaw +``` + +### Updating +```bash +npm update -g autoclaw +``` + +### Configuration +AutoClaw uses a hierarchical configuration system. + +**Priority Order:** +1. **CLI Arguments**: (`-m`) +2. **Environment Variables**: (`.env`, System Vars) +3. **Project Config**: (`./.autoclaw/setting.json`) +4. **Global Config**: (`~/.autoclaw/setting.json`) + +**Setup:** +Run `autoclaw setup` to configure the global JSON settings. + +**Security:** +Add `.autoclaw/` to `.gitignore` if using project-level config with secrets. + +### Usage +Run the tool: +```bash +npm start +``` +Or use the CLI command if installed globally: +```bash +autoclaw +``` + +## Features +- **Natural Language Command Execution**: "List all markdown files in this folder." +- **File Management**: "Create a new file called test.txt with 'Hello World'." +- **Safety**: All shell commands require user confirmation before execution. +- **Context Aware**: Automatically detects OS and environment. \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..29663c8 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 AutoClaw Contributor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..767a14b --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +# AutoClaw šŸ¦ž + +**The Docker-Native Headless Agent for Massive Scale Automation.** + +AutoClaw is a hyper-lightweight AI agent designed to live inside **Docker containers**. Unlike heavy, GUI-dependent agents, AutoClaw is built for **headless, massive-scale concurrency**. + +You can run one instance to fix a local script, or orchestrate **10,000+ instances** in a Kubernetes cluster to refactor codebases, audit servers, or process data streams in parallel. + +## Why AutoClaw? +- 🐳 **Docker Native**: Built to run safely inside containers. Minimal footprint (Node.js/Alpine friendly). +- šŸš€ **Massive Scalability**: Text-only, headless design means you can spawn thousands of agents without consuming graphical resources. +- šŸ›”ļø **Sandbox Safety**: Ideal for running untrusted code when isolated in Docker. +- šŸ”Œ **Swarm Ready**: Stateless design allows for easy orchestration via K8s, Docker Swarm, or simple shell loops. + +## Features + +- šŸ“œ **Headless Execution**: No browsers, no GUIs. Pure terminal efficiency. +- šŸ¤– **Non-Interactive**: Intelligent flag handling (`-y`) for zero-touch automation. +- šŸ“‚ **Universal Control**: From simple file I/O to complex system administration. +- 🧠 **Context Aware**: Detects container environments to optimize command strategies. + +## Installation + +```bash +npm install -g autoclaw +``` + +## Updating + +To update AutoClaw to the latest version: + +```bash +npm update -g autoclaw +``` + +## Quick Start + +1. **Setup**: Run the setup wizard to configure your API key. + ```bash + autoclaw setup + ``` +2. **Run**: Start the agent. + ```bash + autoclaw + ``` + +## Usage Examples + +- "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." + +## Configuration + +AutoClaw uses a hierarchical configuration system. + +**Priority Order (Highest to Lowest):** +1. **CLI Arguments**: (e.g., `-m gpt-4o`) +2. **Environment Variables**: (`OPENAI_API_KEY`, `.env` file) +3. **Project Config**: (`./.autoclaw/setting.json` in current directory) +4. **Global Config**: (`~/.autoclaw/setting.json`) + +### Supported Configuration Keys (JSON) +- `apiKey`: Your API Key. +- `baseUrl`: Custom Base URL. +- `model`: Default model to use. + +### Project-Level Config (Example) +Create a file at `.autoclaw/setting.json`: +```json +{ + "model": "gpt-3.5-turbo", + "baseUrl": "https://api.example.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! + +## License + +MIT diff --git a/install.bat b/install.bat new file mode 100644 index 0000000..bed3527 --- /dev/null +++ b/install.bat @@ -0,0 +1,19 @@ +@echo off +echo Installing AutoClaw dependencies... +call npm install + +echo Building AutoClaw... +call npm run build + +echo. +echo ============================================ +echo Installation Complete! +echo ============================================ +echo. +echo To configure, run: +echo npm start -- setup +echo. +echo To use, run: +echo npm start +echo. +pause diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..e61a023 --- /dev/null +++ b/install.sh @@ -0,0 +1,18 @@ +#!/bin/bash +echo "Installing AutoClaw dependencies..." +npm install + +echo "Building AutoClaw..." +npm run build + +echo "" +echo "============================================" +echo " Installation Complete!" +echo "============================================" +echo "" +echo "To configure, run:" +echo " npm start -- setup" +echo "" +echo "To use, run:" +echo " npm start" +echo "" diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6ba9feb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1043 @@ +{ + "name": "autoclaw", + "version": "1.0.16", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "autoclaw", + "version": "1.0.16", + "license": "MIT", + "dependencies": { + "chalk": "^5.6.2", + "commander": "^14.0.3", + "dotenv": "^16.4.7", + "inquirer": "^13.2.2", + "nodemailer": "^8.0.0", + "openai": "^6.18.0", + "ora": "^9.3.0" + }, + "bin": { + "autoclaw": "dist/index.js" + }, + "devDependencies": { + "@types/inquirer": "^9.0.9", + "@types/node": "^25.2.1", + "@types/nodemailer": "^7.0.9", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" + } + }, + "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", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@inquirer/ansi": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.3.tgz", + "integrity": "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw==", + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-5.0.4.tgz", + "integrity": "sha512-DrAMU3YBGMUAp6ArwTIp/25CNDtDbxk7UjIrrtM25JVVrlVYlVzHh5HR1BDFu9JMyUoZ4ZanzeaHqNDttf3gVg==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.3", + "@inquirer/core": "^11.1.1", + "@inquirer/figures": "^2.0.3", + "@inquirer/type": "^4.0.3" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-6.0.4.tgz", + "integrity": "sha512-WdaPe7foUnoGYvXzH4jp4wH/3l+dBhZ3uwhKjXjwdrq5tEIFaANxj6zrGHxLdsIA0yKM0kFPVcEalOZXBB5ISA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.1", + "@inquirer/type": "^4.0.3" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-11.1.1.tgz", + "integrity": "sha512-hV9o15UxX46OyQAtaoMqAOxGR8RVl1aZtDx1jHbCtSJy1tBdTfKxLPKf7utsE4cRy4tcmCQ4+vdV+ca+oNxqNA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.3", + "@inquirer/figures": "^2.0.3", + "@inquirer/type": "^4.0.3", + "cli-width": "^4.1.0", + "mute-stream": "^3.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^9.0.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-5.0.4.tgz", + "integrity": "sha512-QI3Jfqcv6UO2/VJaEFONH8Im1ll++Xn/AJTBn9Xf+qx2M+H8KZAdQ5sAe2vtYlo+mLW+d7JaMJB4qWtK4BG3pw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.1", + "@inquirer/external-editor": "^2.0.3", + "@inquirer/type": "^4.0.3" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-5.0.4.tgz", + "integrity": "sha512-0I/16YwPPP0Co7a5MsomlZLpch48NzYfToyqYAOWtBmaXSB80RiNQ1J+0xx2eG+Wfxt0nHtpEWSRr6CzNVnOGg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.1", + "@inquirer/type": "^4.0.3" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-2.0.3.tgz", + "integrity": "sha512-LgyI7Agbda74/cL5MvA88iDpvdXI2KuMBCGRkbCl2Dg1vzHeOgs+s0SDcXV7b+WZJrv2+ERpWSM65Fpi9VfY3w==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-2.0.3.tgz", + "integrity": "sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==", + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + } + }, + "node_modules/@inquirer/input": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-5.0.4.tgz", + "integrity": "sha512-4B3s3jvTREDFvXWit92Yc6jF1RJMDy2VpSqKtm4We2oVU65YOh2szY5/G14h4fHlyQdpUmazU5MPCFZPRJ0AOw==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.1", + "@inquirer/type": "^4.0.3" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-4.0.4.tgz", + "integrity": "sha512-CmMp9LF5HwE+G/xWsC333TlCzYYbXMkcADkKzcawh49fg2a1ryLc7JL1NJYYt1lJ+8f4slikNjJM9TEL/AljYQ==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.1", + "@inquirer/type": "^4.0.3" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-5.0.4.tgz", + "integrity": "sha512-ZCEPyVYvHK4W4p2Gy6sTp9nqsdHQCfiPXIP9LbJVW4yCinnxL/dDDmPaEZVysGrj8vxVReRnpfS2fOeODe9zjg==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.3", + "@inquirer/core": "^11.1.1", + "@inquirer/type": "^4.0.3" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-8.2.0.tgz", + "integrity": "sha512-rqTzOprAj55a27jctS3vhvDDJzYXsr33WXTjODgVOru21NvBo9yIgLIAf7SBdSV0WERVly3dR6TWyp7ZHkvKFA==", + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^5.0.4", + "@inquirer/confirm": "^6.0.4", + "@inquirer/editor": "^5.0.4", + "@inquirer/expand": "^5.0.4", + "@inquirer/input": "^5.0.4", + "@inquirer/number": "^4.0.4", + "@inquirer/password": "^5.0.4", + "@inquirer/rawlist": "^5.2.0", + "@inquirer/search": "^4.1.0", + "@inquirer/select": "^5.0.4" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-5.2.0.tgz", + "integrity": "sha512-CciqGoOUMrFo6HxvOtU5uL8fkjCmzyeB6fG7O1vdVAZVSopUBYECOwevDBlqNLyyYmzpm2Gsn/7nLrpruy9RFg==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.1", + "@inquirer/type": "^4.0.3" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-4.1.0.tgz", + "integrity": "sha512-EAzemfiP4IFvIuWnrHpgZs9lAhWDA0GM3l9F4t4mTQ22IFtzfrk8xbkMLcAN7gmVML9O/i+Hzu8yOUyAaL6BKA==", + "license": "MIT", + "dependencies": { + "@inquirer/core": "^11.1.1", + "@inquirer/figures": "^2.0.3", + "@inquirer/type": "^4.0.3" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-5.0.4.tgz", + "integrity": "sha512-s8KoGpPYMEQ6WXc0dT9blX2NtIulMdLOO3LA1UKOiv7KFWzlJ6eLkEYTDBIi+JkyKXyn8t/CD6TinxGjyLt57g==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.3", + "@inquirer/core": "^11.1.1", + "@inquirer/figures": "^2.0.3", + "@inquirer/type": "^4.0.3" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-4.0.3.tgz", + "integrity": "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw==", + "license": "MIT", + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", + "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/inquirer": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-9.0.9.tgz", + "integrity": "sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/through": "*", + "rxjs": "^7.2.0" + } + }, + "node_modules/@types/node": { + "version": "25.2.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.1.tgz", + "integrity": "sha512-CPrnr8voK8vC6eEtyRzvMpgp3VyVRhgclonE7qYi6P9sXwYb59ucfrnmFBTaP0yUi8Gk4yZg/LlTJULGxvTNsg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/nodemailer": { + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.9.tgz", + "integrity": "sha512-vI8oF1M+8JvQhsId0Pc38BdUP2evenIIys7c7p+9OZXSPOH5c1dyINP1jT8xQ2xPuBUXmIC87s+91IZMDjH8Ow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/through": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/@types/through/-/through-0.0.33.tgz", + "integrity": "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz", + "integrity": "sha512-bXfOC4QcT1tKXGorxL3wbJm6XJPDqEnij2gQ2m7ESQuE+/z9YFIWnl/5RpTiKWbMq3EVKR4fRLJGn6DVfu0mpw==", + "license": "MIT", + "engines": { + "node": ">=18.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/diff": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", + "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/emoji-regex": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "license": "MIT" + }, + "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", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inquirer": { + "version": "13.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-13.2.2.tgz", + "integrity": "sha512-+hlN8I88JE9T3zjWHGnMhryniRDbSgFNJHJTyD2iKO5YNpMRyfghQ6wVoe+gV4ygMM4r4GzlsBxNa1g/UUZixA==", + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^2.0.3", + "@inquirer/core": "^11.1.1", + "@inquirer/prompts": "^8.2.0", + "@inquirer/type": "^4.0.3", + "mute-stream": "^3.0.0", + "run-async": "^4.0.6", + "rxjs": "^7.8.2" + }, + "engines": { + "node": ">=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz", + "integrity": "sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, + "license": "ISC" + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mute-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-3.0.0.tgz", + "integrity": "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==", + "license": "ISC", + "engines": { + "node": "^20.17.0 || >=22.9.0" + } + }, + "node_modules/nodemailer": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.0.tgz", + "integrity": "sha512-xvVJf/f0bzmNpnRIbhCp/IKxaHgJ6QynvUbLXzzMRPG3LDQr5oXkYuw4uDFyFYs8cge8agwwrJAXZsd4hhMquw==", + "license": "MIT-0", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/openai": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-6.18.0.tgz", + "integrity": "sha512-odLRYyz9rlzz6g8gKn61RM2oP5UUm428sE2zOxZqS9MzVfD5/XW8UoEjpnRkzTuScXP7ZbP/m7fC+bl8jCOZZw==", + "license": "Apache-2.0", + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/ora": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-9.3.0.tgz", + "integrity": "sha512-lBX72MWFduWEf7v7uWf5DHp9Jn5BI8bNPGuFgtXMmr2uDz2Gz2749y3am3agSDdkhHPHYmmxEGSKH85ZLGzgXw==", + "license": "MIT", + "dependencies": { + "chalk": "^5.6.2", + "cli-cursor": "^5.0.0", + "cli-spinners": "^3.2.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.1.0", + "log-symbols": "^7.0.1", + "stdin-discarder": "^0.3.1", + "string-width": "^8.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/run-async": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-4.0.6.tgz", + "integrity": "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/stdin-discarder": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.3.1.tgz", + "integrity": "sha512-reExS1kSGoElkextOcPkel4NE99S0BWxjUHQeDFnR8S993JxpPX7KU4MNmO19NXhlJp+8dmdCbKQVNgLJh2teA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..edc6bb8 --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "autoclaw", + "version": "1.0.16", + "type": "module", + "main": "dist/index.js", + "bin": { + "autoclaw": "./dist/index.js" + }, + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "node --loader ts-node/esm src/index.ts", + "test": "echo \"Error: no test specified\" && exit 1", + "prepublishOnly": "npm run build" + }, + "files": [ + "dist", + "README.md", + "package.json", + "LICENSE" + ], + "keywords": [ + "ai", + "cli", + "agent", + "automation", + "openai", + "tool" + ], + "author": "AutoClaw Contributor", + "license": "MIT", + "description": "A lightweight AI agent CLI tool that brings the power of LLMs to your terminal.", + "dependencies": { + "chalk": "^5.6.2", + "commander": "^14.0.3", + "dotenv": "^16.4.7", + "inquirer": "^13.2.2", + "nodemailer": "^8.0.0", + "openai": "^6.18.0", + "ora": "^9.3.0" + }, + "devDependencies": { + "@types/inquirer": "^9.0.9", + "@types/node": "^25.2.1", + "@types/nodemailer": "^7.0.9", + "ts-node": "^10.9.2", + "typescript": "^5.9.3" + } +} diff --git a/src/agent.ts b/src/agent.ts new file mode 100644 index 0000000..78ae434 --- /dev/null +++ b/src/agent.ts @@ -0,0 +1,109 @@ +import OpenAI from 'openai'; +import chalk from 'chalk'; +import ora from 'ora'; +import * as os from 'os'; +import * as path from 'path'; +import { getToolDefinitions, executeToolHandler } from './tools/index.js'; + +export class Agent { + private client: OpenAI; + private messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[]; + private model: string; + private config: any; + + constructor(apiKey: string, baseURL: string | undefined, model: string = 'gpt-4-turbo-preview', config: any = {}) { + this.client = new OpenAI({ + apiKey: apiKey, + baseURL: baseURL + }); + this.model = model; + this.config = config; + + const systemInfo = ` +System Information: +- OS: ${os.type()} ${os.release()} (${os.platform()}) +- Architecture: ${os.arch()} +- Node.js Version: ${process.version} +- Current Working Directory: ${process.cwd()} +- User: ${os.userInfo().username} +- Home Directory: ${os.homedir()} +`; + + this.messages = [ + { + role: "system", + content: `You are AutoClaw, a Docker-Native Autonomous Agent designed for massive scale automation. +You are likely running inside a container or headless server, possibly as one of thousands of parallel units in a swarm. + +CONTEXT: +${systemInfo} + +ENVIRONMENT CONSTRAINTS: +1. HEADLESS: No GUI available. Do not try to open browsers or apps. +2. CONTAINER-OPTIMIZED: Assume you are in a sandbox. You can be aggressive with file creation but robust with errors. +3. NON-INTERACTIVE: Always use flags to suppress prompts (e.g., 'apt-get -y', 'rm -rf'). + +GUIDELINES: +1. EFFICIENCY: Your goal is speed and success. Write scripts that just work. +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. +` + } + ]; + } + + async chat(userInput: string): Promise { + this.messages.push({ role: "user", content: userInput }); + + let active = true; + while (active) { + const spinner = ora('Thinking...').start(); + + try { + const response = await this.client.chat.completions.create({ + model: this.model, + messages: this.messages, + tools: getToolDefinitions() as any, + tool_choice: "auto" + }); + + spinner.stop(); + + const message = response.choices[0].message; + this.messages.push(message); + + if (message.content) { + console.log(chalk.blue("AutoClaw: ") + message.content); + } + + if (message.tool_calls) { + for (const toolCall of message.tool_calls) { + if (toolCall.type !== 'function') continue; + + const functionName = toolCall.function.name; + const functionArgs = JSON.parse(toolCall.function.arguments); + + console.log(chalk.gray(`Executing tool: ${functionName}...`)); + + // Pass the full config to the tool handler + const toolResult = await executeToolHandler(functionName, functionArgs, this.config); + + this.messages.push({ + role: "tool", + tool_call_id: toolCall.id, + content: toolResult + }); + } + } else { + active = false; + } + + } catch (error: any) { + spinner.fail('Error during processing'); + console.error(chalk.red(error.message)); + active = false; + } + } + } +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e281dd2 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,400 @@ +#!/usr/bin/env node +import { Command } from 'commander'; +import inquirer from 'inquirer'; +import chalk from 'chalk'; +import dotenv from 'dotenv'; +import { Agent } from './agent.js'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import { fileURLToPath } from 'url'; + +// Handle Ctrl+C gracefully +function handleExit() { + console.log(chalk.cyan("\n\nGoodbye! (Interrupted)")); + if (process.stdin.isTTY) { + process.stdin.setRawMode(false); + } + process.stdin.pause(); + process.exit(0); +} + +process.on('SIGINT', handleExit); +process.on('SIGTERM', handleExit); + +const GLOBAL_CONFIG_DIR = path.join(os.homedir(), '.autoclaw'); +const GLOBAL_CONFIG_FILE = path.join(GLOBAL_CONFIG_DIR, 'setting.json'); +const LOCAL_CONFIG_FILE = path.join(process.cwd(), '.autoclaw', 'setting.json'); + +interface AppConfig { + apiKey?: string; + baseUrl?: string; + model?: string; + smtpHost?: string; + smtpPort?: string; + smtpUser?: string; + smtpPass?: string; + smtpFrom?: string; + tavilyApiKey?: string; + autoConfirm?: boolean; + feishuWebhook?: string; + dingtalkWebhook?: string; + wecomWebhook?: string; +} + +function loadJsonConfig(filePath: string): AppConfig { + if (fs.existsSync(filePath)) { + try { + return JSON.parse(fs.readFileSync(filePath, 'utf-8')); + } catch (e) { + console.error(chalk.yellow(`Warning: Failed to parse config file at ${filePath}`)); + } + } + return {}; +} + +// Load local env vars (lowest priority of env vars, but env vars override JSON) +dotenv.config(); + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +// In dist/index.js, package.json is usually up one level in the root +const pkgPath = path.join(__dirname, '..', 'package.json'); +let version = '1.0.2'; + +try { + const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8')); + version = pkg.version; +} catch (e) { + // Fallback if package.json not found in expected location +} + +const program = new Command(); + +program + .name('autoclaw') + .description('A lightweight AI agent CLI tool') + .version(version) + .option('-m, --model ', 'Model to use') + .option('-n, --no-interactive', 'Exit after processing the initial query (Headless mode)') + .option('-y, --yes', 'Auto-confirm all tool executions (e.g., shell commands)'); + +program + .command('setup') + .description('Run the interactive setup wizard to configure API keys') + .action(async () => { + await runSetup(); + }); + +program + .command('chat [query...]', { isDefault: true }) + .description('Start the AI agent (default)') + .action(async (queryParts) => { + const options = program.opts(); + await runChat(queryParts, options); + }); + +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}`)); + + const currentConfig = loadJsonConfig(GLOBAL_CONFIG_FILE); + + function maskSecret(secret?: string): string { + if (!secret || secret.length < 8) return '******'; + return `${secret.slice(0, 3)}...${secret.slice(-4)}`; + } + + const answers = await inquirer.prompt([ + { + type: 'password', + name: 'apiKey', + message: currentConfig.apiKey + ? `Enter OpenAI API Key (Leave empty to keep ${maskSecret(currentConfig.apiKey)}):` + : 'Enter OpenAI API Key:', + mask: '*', + validate: (input) => { + if (input.length > 0) return true; + if (currentConfig.apiKey) return true; + return 'API Key cannot be empty.'; + } + }, + { + type: 'input', + name: 'baseUrl', + message: 'Enter API Base URL:', + default: currentConfig.baseUrl || 'https://api.openai.com/v1' + }, + { + type: 'input', + name: 'model', + message: 'Enter default Model:', + default: currentConfig.model || 'gpt-4o' + }, + { + type: 'confirm', + name: 'configureEmail', + message: 'Do you want to configure the Email Tool (SMTP)?', + default: !!currentConfig.smtpHost + }, + { + type: 'confirm', + name: 'configureSearch', + message: 'Do you want to configure Web Search (Tavily)?', + default: !!currentConfig.tavilyApiKey + }, + { + type: 'confirm', + name: 'configureNotify', + message: 'Do you want to configure Group Bots (Feishu/DingTalk/WeCom)?', + default: !!(currentConfig.feishuWebhook || currentConfig.dingtalkWebhook || currentConfig.wecomWebhook) + } + ]); + + // Resolve sensitive values (Keep old if empty) + const finalApiKey = answers.apiKey || currentConfig.apiKey; + + let emailConfig: any = {}; + if (answers.configureEmail) { + const emailAnswers = await inquirer.prompt([ + { + type: 'input', + name: 'smtpHost', + message: 'SMTP Host:', + default: currentConfig.smtpHost + }, + { + type: 'input', + name: 'smtpPort', + message: 'SMTP Port:', + default: currentConfig.smtpPort || '587' + }, + { + type: 'input', + name: 'smtpUser', + message: 'SMTP Username:', + default: currentConfig.smtpUser + }, + { + type: 'password', + name: 'smtpPass', + message: currentConfig.smtpPass + ? `SMTP Password (Leave empty to keep ${maskSecret(currentConfig.smtpPass)}):` + : 'SMTP Password:', + mask: '*', + validate: (input) => { return true; } + }, + { + type: 'input', + name: 'smtpFrom', + message: 'Sender Email Address (From):', + default: currentConfig.smtpFrom || currentConfig.smtpUser + } + ]); + emailConfig = { ...emailAnswers, smtpPass: emailAnswers.smtpPass || currentConfig.smtpPass }; + if (!emailConfig.smtpFrom && emailConfig.smtpUser) { emailConfig.smtpFrom = emailConfig.smtpUser; } + } + + let searchConfig: any = {}; + if (answers.configureSearch) { + const searchAnswers = await inquirer.prompt([ + { + type: 'password', + name: 'tavilyApiKey', + message: currentConfig.tavilyApiKey + ? `Tavily API Key (Leave empty to keep ${maskSecret(currentConfig.tavilyApiKey)}):` + : 'Tavily API Key (Free at tavily.com):', + mask: '*' + } + ]); + searchConfig = { tavilyApiKey: searchAnswers.tavilyApiKey || currentConfig.tavilyApiKey }; + } + + let notifyConfig: any = {}; + if (answers.configureNotify) { + const notifyAnswers = await inquirer.prompt([ + { + type: 'password', + name: 'feishuWebhook', + message: currentConfig.feishuWebhook + ? `Feishu Webhook (Leave empty to keep ${maskSecret(currentConfig.feishuWebhook)}):` + : 'Feishu Webhook (Optional):', + mask: '*' + }, + { + type: 'password', + name: 'dingtalkWebhook', + message: currentConfig.dingtalkWebhook + ? `DingTalk Webhook (Leave empty to keep ${maskSecret(currentConfig.dingtalkWebhook)}):` + : 'DingTalk Webhook (Optional):', + mask: '*' + }, + { + type: 'password', + name: 'wecomWebhook', + message: currentConfig.wecomWebhook + ? `WeCom Webhook (Leave empty to keep ${maskSecret(currentConfig.wecomWebhook)}):` + : 'WeCom Webhook (Optional):', + mask: '*' + } + ]); + notifyConfig = { + feishuWebhook: notifyAnswers.feishuWebhook || currentConfig.feishuWebhook, + dingtalkWebhook: notifyAnswers.dingtalkWebhook || currentConfig.dingtalkWebhook, + wecomWebhook: notifyAnswers.wecomWebhook || currentConfig.wecomWebhook + }; + } + + const newConfig: AppConfig = { + apiKey: finalApiKey, + baseUrl: answers.baseUrl, + model: answers.model, + ...emailConfig, + ...searchConfig, + ...notifyConfig + }; + + try { + if (!fs.existsSync(GLOBAL_CONFIG_DIR)) { + fs.mkdirSync(GLOBAL_CONFIG_DIR, { 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}`)); + 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}`)); + } +} + +async function runChat(queryParts: string[], options: any) { + if (options.interactive) { + console.log(chalk.bold.cyan("Welcome to AutoClaw CLI šŸ¦ž")); + } + + const initialQuery = queryParts.join(' '); + + // 1. Load Global JSON + const globalConfig = loadJsonConfig(GLOBAL_CONFIG_FILE); + + // 2. Load Local JSON (Project Level) + const localConfig = loadJsonConfig(LOCAL_CONFIG_FILE); + if (Object.keys(localConfig).length > 0 && options.interactive) { + console.log(chalk.dim(`Loaded project config from ${LOCAL_CONFIG_FILE}`)); + } + + // 3. Merge Configs for Tool Usage + // Priority: Local > Global + const fullConfig = { ...globalConfig, ...localConfig }; + + // 4. Resolve Env Vars (CLI > Env > Config) + let apiKey = process.env.OPENAI_API_KEY || fullConfig.apiKey; + let baseURL = process.env.OPENAI_BASE_URL || fullConfig.baseUrl; + let model = options.model || process.env.OPENAI_MODEL || fullConfig.model || 'gpt-4o'; + + // Inject Runtime Flags + fullConfig.autoConfirm = options.yes; + + // Inject Env vars + if (process.env.SMTP_HOST) fullConfig.smtpHost = process.env.SMTP_HOST; + if (process.env.SMTP_PORT) fullConfig.smtpPort = process.env.SMTP_PORT; + if (process.env.SMTP_User) fullConfig.smtpUser = process.env.SMTP_USER; + 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.DINGTALK_WEBHOOK) fullConfig.dingtalkWebhook = process.env.DINGTALK_WEBHOOK; + if (process.env.WECOM_WEBHOOK) fullConfig.wecomWebhook = process.env.WECOM_WEBHOOK; + + if (!apiKey) { + console.log(chalk.yellow("API Key not found.")); + const { doSetup } = await inquirer.prompt([ + { + type: 'confirm', + name: 'doSetup', + message: 'Would you like to run the setup wizard now?', + default: true + } + ]); + + if (doSetup) { + await runSetup(); + const newConfig = loadJsonConfig(GLOBAL_CONFIG_FILE); + apiKey = newConfig.apiKey; + baseURL = newConfig.baseUrl; + model = options.model || newConfig.model || 'gpt-4o'; + Object.assign(fullConfig, newConfig); + } else { + console.error(chalk.red("API Key is required to proceed.")); + process.exit(1); + } + } + + if (!apiKey) { + console.error(chalk.red("API Key is still missing. Exiting.")); + process.exit(1); + } + + const agent = new Agent(apiKey!, baseURL, model, fullConfig); + + if (options.interactive) { + console.log(chalk.green(`Agent initialized with model: ${model}`)); + console.log(chalk.gray("Type 'exit' or 'quit' to leave.")); + } + + // Handle initial query if present + if (initialQuery) { + if (options.interactive) { + console.log(chalk.blue("\nProcessing initial request: ") + chalk.bold(initialQuery)); + } + await agent.chat(initialQuery); + + // Headless mode exit + if (!options.interactive) { + process.exit(0); + } + } + + // Main chat loop + try { + while (true) { + const { userInput } = await inquirer.prompt([ + { + type: 'input', + name: 'userInput', + message: 'You >' + } + ]); + + if (userInput.toLowerCase() === 'exit' || userInput.toLowerCase() === 'quit') { + console.log(chalk.cyan("Goodbye!")); + break; + } + + if (userInput.trim() === '') continue; + + await agent.chat(userInput); + } + } 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); + } + throw err; // Re-throw real errors to be caught by main().catch + } +} + +// Global error handler +main().catch(err => { + if (err.message && (err.message.includes('User force closed') || err.message.includes('Prompt was canceled'))) { + console.log(chalk.cyan("\nGoodbye!")); + process.exit(0); + } + console.error(chalk.red("Fatal Error:"), err); + process.exit(1); +}); + +async function main() { + // Just a wrapper to keep the promise chain clean if needed, + // but currently logic is triggered by program.parse() +} diff --git a/src/tools/core.ts b/src/tools/core.ts new file mode 100644 index 0000000..2ece8dd --- /dev/null +++ b/src/tools/core.ts @@ -0,0 +1,109 @@ +import { exec } from 'child_process'; +import * as fs from 'fs/promises'; +import * as path from 'path'; +import inquirer from 'inquirer'; +import chalk from 'chalk'; +import util from 'util'; +import { ToolModule } from './interface.js'; + +const execAsync = util.promisify(exec); + +export const ShellTool: ToolModule = { + name: "Shell Execution", + definition: { + type: "function", + function: { + name: "execute_shell_command", + description: "Execute a shell command on the host machine. Use this to run scripts, list files, or interact with the system.", + parameters: { + type: "object", + properties: { + command: { type: "string", description: "The shell command to execute." }, + rationale: { type: "string", description: "Explain why you are running this command." } + }, + required: ["command", "rationale"] + } + } + }, + handler: async (args: any, config: any) => { + console.log(chalk.yellow(`\nAI wants to execute: `) + chalk.bold(args.command)); + console.log(chalk.dim(`Reason: ${args.rationale}`)); + + // Check for auto-confirm flag + if (!config?.autoConfirm) { + const { confirm } = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirm', + message: 'Do you want to run this command?', + default: false + } + ]); + + if (!confirm) return "User denied command execution."; + } else { + console.log(chalk.gray("(Auto-confirming command execution due to --yes flag)")); + } + + try { + const { stdout, stderr } = await execAsync(args.command); + return stdout + (stderr ? `\nStderr: ${stderr}` : ''); + } catch (error: any) { + return `Command failed: ${error.message}\nStdout: ${error.stdout}\nStderr: ${error.stderr}`; + } + } +}; + +export const ReadFileTool: ToolModule = { + name: "File Reader", + definition: { + type: "function", + function: { + name: "read_file", + description: "Read the content of a file.", + parameters: { + type: "object", + properties: { + path: { type: "string", description: "The path to the file to read." } + }, + required: ["path"] + } + } + }, + handler: async (args: any) => { + try { + const content = await fs.readFile(args.path, 'utf-8'); + return content; + } catch (error: any) { + return `Error reading file: ${error.message}`; + } + } +}; + +export const WriteFileTool: ToolModule = { + name: "File Writer", + definition: { + type: "function", + function: { + name: "write_file", + description: "Write content to a file. Overwrites existing files.", + parameters: { + type: "object", + properties: { + path: { type: "string", description: "The path to the file to write." }, + content: { type: "string", description: "The content to write." } + }, + required: ["path", "content"] + } + } + }, + handler: async (args: any) => { + try { + await fs.mkdir(path.dirname(args.path), { recursive: true }); + await fs.writeFile(args.path, args.content, 'utf-8'); + return `Successfully wrote to ${args.path}`; + } catch (error: any) { + return `Error writing file: ${error.message}`; + } + } +}; diff --git a/src/tools/email.ts b/src/tools/email.ts new file mode 100644 index 0000000..3bf9417 --- /dev/null +++ b/src/tools/email.ts @@ -0,0 +1,55 @@ +import nodemailer from 'nodemailer'; +import { ToolModule } from './interface.js'; + +export const EmailTool: ToolModule = { + name: "Email Service", + configKeys: ["smtpHost", "smtpPort", "smtpUser", "smtpPass", "smtpFrom"], + definition: { + type: "function", + function: { + name: "send_email", + description: "Send an email using configured SMTP settings.", + 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)." } + }, + required: ["to", "subject", "body"] + } + } + }, + handler: async (args: any, config: any) => { + // Validate config + if (!config?.smtpHost || !config?.smtpUser || !config?.smtpPass) { + return "Error: Email tool is not configured. Please run 'autoclaw setup' to configure SMTP settings."; + } + + try { + const transporter = nodemailer.createTransport({ + host: config.smtpHost, + port: parseInt(config.smtpPort || '587'), + secure: parseInt(config.smtpPort) === 465, // true for 465, false for other ports + auth: { + user: config.smtpUser, + pass: config.smtpPass, + }, + }); + + 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 + }); + + return `Email sent successfully. Message ID: ${info.messageId}`; + } catch (error: any) { + // Return detailed error info for debugging + const code = error.code ? `[Code: ${error.code}] ` : ''; + const response = error.response ? ` (Server Response: ${error.response})` : ''; + return `Failed to send email: ${code}${error.message}${response}`; + } + } +}; diff --git a/src/tools/index.ts b/src/tools/index.ts new file mode 100644 index 0000000..bac7386 --- /dev/null +++ b/src/tools/index.ts @@ -0,0 +1,28 @@ +import { ToolModule } from './interface.js'; +import { ShellTool, ReadFileTool, WriteFileTool } from './core.js'; +import { EmailTool } from './email.js'; +import { SearchTool } from './search.js'; +import { NotifyTool } from './notify.js'; + +// Central Registry of all available tools +export const toolRegistry: ToolModule[] = [ + ShellTool, + ReadFileTool, + WriteFileTool, + EmailTool, + SearchTool, + NotifyTool +]; + +export function getToolDefinitions() { + return toolRegistry.map(t => t.definition); +} + +export async function executeToolHandler(name: string, args: any, fullConfig: any): Promise { + const tool = toolRegistry.find(t => t.definition.function.name === name); + if (!tool) { + return `Error: Tool ${name} not found.`; + } + + return await tool.handler(args, fullConfig); +} diff --git a/src/tools/interface.ts b/src/tools/interface.ts new file mode 100644 index 0000000..c3e85b8 --- /dev/null +++ b/src/tools/interface.ts @@ -0,0 +1,19 @@ +export interface ToolDefinition { + type: "function"; + function: { + name: "execute_shell_command" | "read_file" | "write_file" | "send_email" | string; + description: string; + parameters: { + type: "object"; + properties: Record; + required: string[]; + }; + }; +} + +export interface ToolModule { + name: string; // Display name for setup (e.g., "Email Service") + configKeys?: string[]; // Keys needed in setting.json (e.g., ["smtpHost", "smtpUser"]) + definition: ToolDefinition; // OpenAI Tool Definition + handler: (args: any, config?: any) => Promise; // Implementation +} diff --git a/src/tools/notify.ts b/src/tools/notify.ts new file mode 100644 index 0000000..c159ec7 --- /dev/null +++ b/src/tools/notify.ts @@ -0,0 +1,90 @@ +import { ToolModule } from './interface.js'; + +export const NotifyTool: ToolModule = { + name: "Group Bot Notification", + configKeys: ["feishuWebhook", "dingtalkWebhook", "wecomWebhook"], + definition: { + type: "function", + function: { + name: "send_notification", + description: "Send a text message to an Instant Messaging (IM) Group Bot (Feishu/Lark, DingTalk, WeCom).", + parameters: { + type: "object", + properties: { + platform: { + type: "string", + enum: ["feishu", "dingtalk", "wecom"], + description: "The target platform." + }, + content: { + type: "string", + description: "The text content to send." + } + }, + required: ["platform", "content"] + } + } + }, + handler: async (args: any, config: any) => { + const { platform, content } = args; + let webhookUrl = ''; + let payload = {}; + + // 1. Determine Webhook URL and Payload Format + switch (platform) { + case 'feishu': + webhookUrl = config.feishuWebhook || process.env.FEISHU_WEBHOOK; + if (!webhookUrl) return "Error: Feishu Webhook URL is not configured."; + payload = { + msg_type: "text", + content: { text: content } + }; + break; + + case 'dingtalk': + webhookUrl = config.dingtalkWebhook || process.env.DINGTALK_WEBHOOK; + if (!webhookUrl) return "Error: DingTalk Webhook URL is not configured."; + payload = { + msgtype: "text", + text: { content: content } + }; + break; + + case 'wecom': + webhookUrl = config.wecomWebhook || process.env.WECOM_WEBHOOK; + if (!webhookUrl) return "Error: WeCom Webhook URL is not configured."; + payload = { + msgtype: "text", + text: { content: content } + }; + break; + + default: + return `Error: Unknown platform '${platform}'. Supported: feishu, dingtalk, wecom.`; + } + + // 2. Send Request + try { + const response = await fetch(webhookUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }); + + const result: any = await response.json(); + + // Platform specific success checks + // Feishu: code 0 + // DingTalk: errcode 0 + // WeCom: errcode 0 + if (result.code === 0 || result.errcode === 0) { + return `Notification sent to ${platform} successfully.`; + } else { + return `Failed to send to ${platform}. API Response: ${JSON.stringify(result)}`; + } + + } catch (error: any) { + return `Network error sending notification: ${error.message}`; + } + } +}; diff --git a/src/tools/search.ts b/src/tools/search.ts new file mode 100644 index 0000000..cbc2705 --- /dev/null +++ b/src/tools/search.ts @@ -0,0 +1,87 @@ +import { ToolModule } from './interface.js'; + +export const SearchTool: ToolModule = { + name: "Web Search (Tavily)", + configKeys: ["tavilyApiKey"], + definition: { + type: "function", + function: { + name: "web_search", + description: "Search the web for real-time information. Returns a summary of search results.", + parameters: { + type: "object", + properties: { + query: { + type: "string", + description: "The search query (e.g., 'latest openclaw news', 'nodejs documentation')." + }, + depth: { + type: "string", + enum: ["basic", "advanced"], + description: "Search depth. 'basic' is faster, 'advanced' scrapes more content." + } + }, + required: ["query"] + } + } + }, + handler: async (args: any, config: any) => { + const apiKey = config.tavilyApiKey || process.env.TAVILY_API_KEY; + + if (!apiKey) { + return "Error: Tavily API Key is missing. Please run 'autoclaw setup' to configure it, or set TAVILY_API_KEY env var. Get a free key at https://tavily.com"; + } + + try { + const response = await fetch("https://api.tavily.com/search", { + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({ + api_key: apiKey, + query: args.query, + search_depth: args.depth || "basic", + include_answer: true, + include_images: false, + max_results: 5 + }) + }); + + if (!response.ok) { + const errText = await response.text(); + return `Search API Error: ${response.status} - ${errText}`; + } + + const data: any = await response.json(); + + // Format the results beautifully for the LLM + let output = `Search Results for "${args.query}": + +`; + + if (data.answer) { + output += `šŸ’” **Direct Answer**: ${data.answer} + +`; + } + + if (data.results && Array.isArray(data.results)) { + data.results.forEach((result: any, index: number) => { + output += `### ${index + 1}. ${result.title} +`; + output += `šŸ”— ${result.url} +`; + output += `šŸ“ ${result.content} + +`; + }); + } + + return output; + + } catch (error: any) { + return `Failed to perform web search: ${error.message}`; + } + } +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..891cff9 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} \ No newline at end of file