autoclaw 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/dist/agent.js +93 -0
- package/dist/index.js +114 -0
- package/dist/tools.js +130 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 AutoClaw Contributor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# AutoClaw 🦞
|
|
2
|
+
|
|
3
|
+
AutoClaw is a lightweight AI agent CLI tool that brings the power of Large Language Models (LLMs) directly to your terminal. It allows you to interact with your file system and execute commands using natural language.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 💬 **Natural Language Interface**: Chat with your terminal to perform complex tasks.
|
|
8
|
+
- 🛡️ **Safe Execution**: All shell commands require user confirmation before execution.
|
|
9
|
+
- 📂 **File Management**: Read and write files effortlessly.
|
|
10
|
+
- 🧠 **Context Aware**: Automatically detects your OS, shell, and environment for accurate command generation.
|
|
11
|
+
- 🔌 **Model Agnostic**: Compatible with OpenAI, DeepSeek, LocalLLM, or any OpenAI-compatible API.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install -g autoclaw
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
1. **Setup**: Run the setup wizard to configure your API key.
|
|
22
|
+
```bash
|
|
23
|
+
autoclaw setup
|
|
24
|
+
```
|
|
25
|
+
2. **Run**: Start the agent.
|
|
26
|
+
```bash
|
|
27
|
+
autoclaw
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage Examples
|
|
31
|
+
|
|
32
|
+
- "List all TypeScript files in the src folder."
|
|
33
|
+
- "Create a new React component named Button in `components/Button.tsx`."
|
|
34
|
+
- "Check my disk usage and tell me which folder is the largest."
|
|
35
|
+
|
|
36
|
+
## Configuration
|
|
37
|
+
|
|
38
|
+
AutoClaw uses a `.env` file in the current working directory or creates one globally during setup.
|
|
39
|
+
|
|
40
|
+
Supported Environment Variables:
|
|
41
|
+
- `OPENAI_API_KEY`: Your API Key.
|
|
42
|
+
- `OPENAI_BASE_URL`: Custom Base URL (e.g., for LocalLLM or other providers).
|
|
43
|
+
- `OPENAI_MODEL`: Default model to use (default: `gpt-4o`).
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT
|
package/dist/agent.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import OpenAI from 'openai';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import * as os from 'os';
|
|
5
|
+
import { tools, executeTool } from './tools.js';
|
|
6
|
+
export class Agent {
|
|
7
|
+
client;
|
|
8
|
+
messages;
|
|
9
|
+
model;
|
|
10
|
+
constructor(apiKey, baseURL, model = 'gpt-4-turbo-preview') {
|
|
11
|
+
this.client = new OpenAI({
|
|
12
|
+
apiKey: apiKey,
|
|
13
|
+
baseURL: baseURL
|
|
14
|
+
});
|
|
15
|
+
this.model = model;
|
|
16
|
+
const systemInfo = `
|
|
17
|
+
System Information:
|
|
18
|
+
- OS: ${os.type()} ${os.release()} (${os.platform()})
|
|
19
|
+
- Architecture: ${os.arch()}
|
|
20
|
+
- Node.js Version: ${process.version}
|
|
21
|
+
- Current Working Directory: ${process.cwd()}
|
|
22
|
+
- User: ${os.userInfo().username}
|
|
23
|
+
- Home Directory: ${os.homedir()}
|
|
24
|
+
`;
|
|
25
|
+
this.messages = [
|
|
26
|
+
{
|
|
27
|
+
role: "system",
|
|
28
|
+
content: `You are AutoClaw, an advanced AI agent running in a CLI environment.
|
|
29
|
+
Your goal is to help the user perform tasks on their computer.
|
|
30
|
+
You have access to tools to execute shell commands and read/write files.
|
|
31
|
+
|
|
32
|
+
CONTEXT:
|
|
33
|
+
${systemInfo}
|
|
34
|
+
|
|
35
|
+
GUIDELINES:
|
|
36
|
+
1. Use the System Information above to generate platform-specific commands (e.g., use 'dir' for Windows, 'ls' for Linux).
|
|
37
|
+
2. When asked to perform a task, analyze if you need to use tools.
|
|
38
|
+
3. If you need to run a command, always use 'execute_shell_command' and provide a clear rationale.
|
|
39
|
+
4. If you need to read a file, use 'read_file'.
|
|
40
|
+
5. If you need to write a file, use 'write_file'.
|
|
41
|
+
6. Be concise in your responses.
|
|
42
|
+
7. If the user's request is ambiguous, ask for clarification.
|
|
43
|
+
8. You are running on the user's local machine. Be careful with destructive commands (rm, etc.).
|
|
44
|
+
`
|
|
45
|
+
}
|
|
46
|
+
];
|
|
47
|
+
}
|
|
48
|
+
async chat(userInput) {
|
|
49
|
+
this.messages.push({ role: "user", content: userInput });
|
|
50
|
+
let active = true;
|
|
51
|
+
while (active) {
|
|
52
|
+
const spinner = ora('Thinking...').start();
|
|
53
|
+
try {
|
|
54
|
+
const response = await this.client.chat.completions.create({
|
|
55
|
+
model: this.model,
|
|
56
|
+
messages: this.messages,
|
|
57
|
+
tools: tools,
|
|
58
|
+
tool_choice: "auto"
|
|
59
|
+
});
|
|
60
|
+
spinner.stop();
|
|
61
|
+
const message = response.choices[0].message;
|
|
62
|
+
this.messages.push(message);
|
|
63
|
+
if (message.content) {
|
|
64
|
+
console.log(chalk.blue("AutoClaw: ") + message.content);
|
|
65
|
+
}
|
|
66
|
+
if (message.tool_calls) {
|
|
67
|
+
for (const toolCall of message.tool_calls) {
|
|
68
|
+
if (toolCall.type !== 'function')
|
|
69
|
+
continue;
|
|
70
|
+
const functionName = toolCall.function.name;
|
|
71
|
+
const functionArgs = JSON.parse(toolCall.function.arguments);
|
|
72
|
+
console.log(chalk.gray(`Executing tool: ${functionName}...`));
|
|
73
|
+
const toolResult = await executeTool(functionName, functionArgs);
|
|
74
|
+
this.messages.push({
|
|
75
|
+
role: "tool",
|
|
76
|
+
tool_call_id: toolCall.id,
|
|
77
|
+
content: toolResult
|
|
78
|
+
});
|
|
79
|
+
// console.log(chalk.dim(`Tool Output: ${toolResult.slice(0, 100)}...`));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
active = false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
spinner.fail('Error during processing');
|
|
88
|
+
console.error(chalk.red(error.message));
|
|
89
|
+
active = false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import dotenv from 'dotenv';
|
|
6
|
+
import { Agent } from './agent.js';
|
|
7
|
+
import * as fs from 'fs';
|
|
8
|
+
import * as path from 'path';
|
|
9
|
+
// Load env vars
|
|
10
|
+
dotenv.config();
|
|
11
|
+
const program = new Command();
|
|
12
|
+
program
|
|
13
|
+
.name('autoclaw')
|
|
14
|
+
.description('A lightweight AI agent CLI tool')
|
|
15
|
+
.version('1.0.0');
|
|
16
|
+
program
|
|
17
|
+
.command('setup')
|
|
18
|
+
.description('Run the interactive setup wizard to configure API keys')
|
|
19
|
+
.action(async () => {
|
|
20
|
+
await runSetup();
|
|
21
|
+
});
|
|
22
|
+
program
|
|
23
|
+
.command('chat', { isDefault: true })
|
|
24
|
+
.description('Start the AI agent (default)')
|
|
25
|
+
.option('-m, --model <model>', 'Model to use')
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
await runChat(options);
|
|
28
|
+
});
|
|
29
|
+
program.parse(process.argv);
|
|
30
|
+
async function runSetup() {
|
|
31
|
+
console.log(chalk.bold.cyan("AutoClaw Setup Wizard 🦞\n"));
|
|
32
|
+
const answers = await inquirer.prompt([
|
|
33
|
+
{
|
|
34
|
+
type: 'password',
|
|
35
|
+
name: 'apiKey',
|
|
36
|
+
message: 'Enter your OpenAI API Key:',
|
|
37
|
+
mask: '*',
|
|
38
|
+
validate: (input) => input.length > 0 ? true : 'API Key cannot be empty.'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: 'input',
|
|
42
|
+
name: 'baseUrl',
|
|
43
|
+
message: 'Enter API Base URL (optional, press Enter for default):',
|
|
44
|
+
default: 'https://api.openai.com/v1'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
type: 'input',
|
|
48
|
+
name: 'model',
|
|
49
|
+
message: 'Enter default Model:',
|
|
50
|
+
default: 'gpt-4o'
|
|
51
|
+
}
|
|
52
|
+
]);
|
|
53
|
+
const envContent = `OPENAI_API_KEY=${answers.apiKey}
|
|
54
|
+
OPENAI_BASE_URL=${answers.baseUrl}
|
|
55
|
+
OPENAI_MODEL=${answers.model}
|
|
56
|
+
`;
|
|
57
|
+
const envPath = path.resolve(process.cwd(), '.env');
|
|
58
|
+
fs.writeFileSync(envPath, envContent);
|
|
59
|
+
console.log(chalk.green(`\n✅ Configuration saved to ${envPath}`));
|
|
60
|
+
console.log(chalk.cyan("You can now run 'autoclaw' to start using the agent."));
|
|
61
|
+
}
|
|
62
|
+
async function runChat(options) {
|
|
63
|
+
console.log(chalk.bold.cyan("Welcome to AutoClaw CLI 🦞"));
|
|
64
|
+
let apiKey = process.env.OPENAI_API_KEY;
|
|
65
|
+
let baseURL = process.env.OPENAI_BASE_URL;
|
|
66
|
+
// Priority: CLI arg > Env var > Default
|
|
67
|
+
const model = options.model || process.env.OPENAI_MODEL || 'gpt-4o';
|
|
68
|
+
if (!apiKey) {
|
|
69
|
+
console.log(chalk.yellow("API Key not found."));
|
|
70
|
+
const { doSetup } = await inquirer.prompt([
|
|
71
|
+
{
|
|
72
|
+
type: 'confirm',
|
|
73
|
+
name: 'doSetup',
|
|
74
|
+
message: 'Would you like to run the setup wizard now?',
|
|
75
|
+
default: true
|
|
76
|
+
}
|
|
77
|
+
]);
|
|
78
|
+
if (doSetup) {
|
|
79
|
+
await runSetup();
|
|
80
|
+
// Reload env
|
|
81
|
+
dotenv.config({ override: true });
|
|
82
|
+
apiKey = process.env.OPENAI_API_KEY;
|
|
83
|
+
baseURL = process.env.OPENAI_BASE_URL;
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
console.error(chalk.red("API Key is required to proceed."));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Double check if apiKey is present after potential setup
|
|
91
|
+
if (!apiKey) {
|
|
92
|
+
console.error(chalk.red("API Key is still missing. Exiting."));
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
const agent = new Agent(apiKey, baseURL, model);
|
|
96
|
+
console.log(chalk.green(`Agent initialized with model: ${model}`));
|
|
97
|
+
console.log(chalk.gray("Type 'exit' or 'quit' to leave."));
|
|
98
|
+
while (true) {
|
|
99
|
+
const { userInput } = await inquirer.prompt([
|
|
100
|
+
{
|
|
101
|
+
type: 'input',
|
|
102
|
+
name: 'userInput',
|
|
103
|
+
message: 'You >'
|
|
104
|
+
}
|
|
105
|
+
]);
|
|
106
|
+
if (userInput.toLowerCase() === 'exit' || userInput.toLowerCase() === 'quit') {
|
|
107
|
+
console.log(chalk.cyan("Goodbye!"));
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
if (userInput.trim() === '')
|
|
111
|
+
continue;
|
|
112
|
+
await agent.chat(userInput);
|
|
113
|
+
}
|
|
114
|
+
}
|
package/dist/tools.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { exec } from 'child_process';
|
|
2
|
+
import * as fs from 'fs/promises';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import util from 'util';
|
|
7
|
+
const execAsync = util.promisify(exec);
|
|
8
|
+
export const tools = [
|
|
9
|
+
{
|
|
10
|
+
type: "function",
|
|
11
|
+
function: {
|
|
12
|
+
name: "execute_shell_command",
|
|
13
|
+
description: "Execute a shell command on the host machine. Use this to run scripts, list files, or interact with the system.",
|
|
14
|
+
parameters: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
command: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "The shell command to execute (e.g., 'ls -la', 'npm install')."
|
|
20
|
+
},
|
|
21
|
+
rationale: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Explain why you are running this command."
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
required: ["command", "rationale"]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
type: "function",
|
|
32
|
+
function: {
|
|
33
|
+
name: "read_file",
|
|
34
|
+
description: "Read the content of a file.",
|
|
35
|
+
parameters: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {
|
|
38
|
+
path: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "The path to the file to read."
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
required: ["path"]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
type: "function",
|
|
49
|
+
function: {
|
|
50
|
+
name: "write_file",
|
|
51
|
+
description: "Write content to a file. Overwrites existing files.",
|
|
52
|
+
parameters: {
|
|
53
|
+
type: "object",
|
|
54
|
+
properties: {
|
|
55
|
+
path: {
|
|
56
|
+
type: "string",
|
|
57
|
+
description: "The path to the file to write."
|
|
58
|
+
},
|
|
59
|
+
content: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "The content to write to the file."
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
required: ["path", "content"]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
];
|
|
69
|
+
export async function executeTool(name, args) {
|
|
70
|
+
try {
|
|
71
|
+
switch (name) {
|
|
72
|
+
case 'execute_shell_command':
|
|
73
|
+
return await executeShellCommand(args.command, args.rationale);
|
|
74
|
+
case 'read_file':
|
|
75
|
+
return await readFile(args.path);
|
|
76
|
+
case 'write_file':
|
|
77
|
+
return await writeFile(args.path, args.content);
|
|
78
|
+
default:
|
|
79
|
+
return `Error: Unknown tool ${name}`;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
return `Error executing ${name}: ${error.message}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
async function executeShellCommand(command, rationale) {
|
|
87
|
+
console.log(chalk.yellow(`
|
|
88
|
+
AI wants to execute: `) + chalk.bold(command));
|
|
89
|
+
console.log(chalk.dim(`Reason: ${rationale}`));
|
|
90
|
+
const { confirm } = await inquirer.prompt([
|
|
91
|
+
{
|
|
92
|
+
type: 'confirm',
|
|
93
|
+
name: 'confirm',
|
|
94
|
+
message: 'Do you want to run this command?',
|
|
95
|
+
default: false
|
|
96
|
+
}
|
|
97
|
+
]);
|
|
98
|
+
if (!confirm) {
|
|
99
|
+
return "User denied command execution.";
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const { stdout, stderr } = await execAsync(command);
|
|
103
|
+
return stdout + (stderr ? `
|
|
104
|
+
Stderr: ${stderr}` : '');
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
return `Command failed: ${error.message}
|
|
108
|
+
Stdout: ${error.stdout}
|
|
109
|
+
Stderr: ${error.stderr}`;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function readFile(filePath) {
|
|
113
|
+
try {
|
|
114
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
115
|
+
return content;
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
return `Error reading file: ${error.message}`;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function writeFile(filePath, content) {
|
|
122
|
+
try {
|
|
123
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
124
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
125
|
+
return `Successfully wrote to ${filePath}`;
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
return `Error writing file: ${error.message}`;
|
|
129
|
+
}
|
|
130
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "autoclaw",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"autoclaw": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsc",
|
|
11
|
+
"start": "node dist/index.js",
|
|
12
|
+
"dev": "node --loader ts-node/esm src/index.ts",
|
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"package.json",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
22
|
+
"keywords": [
|
|
23
|
+
"ai",
|
|
24
|
+
"cli",
|
|
25
|
+
"agent",
|
|
26
|
+
"automation",
|
|
27
|
+
"openai",
|
|
28
|
+
"tool"
|
|
29
|
+
],
|
|
30
|
+
"author": "AutoClaw Contributor",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"description": "A lightweight AI agent CLI tool that brings the power of LLMs to your terminal.",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"chalk": "^5.6.2",
|
|
35
|
+
"commander": "^14.0.3",
|
|
36
|
+
"dotenv": "^17.2.4",
|
|
37
|
+
"inquirer": "^13.2.2",
|
|
38
|
+
"openai": "^6.18.0",
|
|
39
|
+
"ora": "^9.3.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/inquirer": "^9.0.9",
|
|
43
|
+
"@types/node": "^25.2.1",
|
|
44
|
+
"ts-node": "^10.9.2",
|
|
45
|
+
"typescript": "^5.9.3"
|
|
46
|
+
}
|
|
47
|
+
}
|