From 0d43b4399885392460defbc37d0ac06ec258585f Mon Sep 17 00:00:00 2001 From: tsingliu <410869548@qq.com> Date: Fri, 6 Feb 2026 18:41:02 +0800 Subject: [PATCH] init --- .env.example | 3 + .gitignore | 3 + .npmignore | 8 + GEMINI.md | 81 ++++ LICENSE | 21 + README.md | 81 ++++ install.bat | 19 + install.sh | 18 + package-lock.json | 1043 ++++++++++++++++++++++++++++++++++++++++ package.json | 49 ++ src/agent.ts | 109 +++++ src/index.ts | 400 +++++++++++++++ src/tools/core.ts | 109 +++++ src/tools/email.ts | 55 +++ src/tools/index.ts | 28 ++ src/tools/interface.ts | 19 + src/tools/notify.ts | 90 ++++ src/tools/search.ts | 87 ++++ tsconfig.json | 15 + 19 files changed, 2238 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 .npmignore create mode 100644 GEMINI.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 install.bat create mode 100644 install.sh create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/agent.ts create mode 100644 src/index.ts create mode 100644 src/tools/core.ts create mode 100644 src/tools/email.ts create mode 100644 src/tools/index.ts create mode 100644 src/tools/interface.ts create mode 100644 src/tools/notify.ts create mode 100644 src/tools/search.ts create mode 100644 tsconfig.json 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