@phartmann80/klaw 0.1.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 +34 -0
- package/index.js +75 -0
- package/package.json +21 -0
- package/src/agents/architect.js +47 -0
- package/src/agents/demo.js +137 -0
- package/src/agents/fixer.js +21 -0
- package/src/agents/shell.js +113 -0
- package/src/agents/writer.js +40 -0
- package/src/memory.js +21 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Paul Hartmann
|
|
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,34 @@
|
|
|
1
|
+
KLAW
|
|
2
|
+
|
|
3
|
+
Local AI runtime for running agents with your own models and APIs.
|
|
4
|
+
|
|
5
|
+
Usage
|
|
6
|
+
|
|
7
|
+
npx klaw init
|
|
8
|
+
npx klaw run "build a Next.js landing page"
|
|
9
|
+
|
|
10
|
+
How it works
|
|
11
|
+
|
|
12
|
+
KLAW executes tasks through a chain of agents. Each agent handles a specific part of the work.
|
|
13
|
+
|
|
14
|
+
Agents
|
|
15
|
+
|
|
16
|
+
Architect: Plans task steps
|
|
17
|
+
Writer: Creates and modifies files
|
|
18
|
+
Shell: Runs commands with permission prompts
|
|
19
|
+
Fixer: Handles basic errors and retries
|
|
20
|
+
|
|
21
|
+
Security
|
|
22
|
+
|
|
23
|
+
Workspace isolation by default
|
|
24
|
+
Permission prompts for shell commands
|
|
25
|
+
No file writes outside workspace root
|
|
26
|
+
|
|
27
|
+
Author
|
|
28
|
+
|
|
29
|
+
Built by Paul Hartmann
|
|
30
|
+
GitHub: @janpaul80
|
|
31
|
+
|
|
32
|
+
License
|
|
33
|
+
|
|
34
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { Command } = require('commander');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const { executeTask } = require('./src/agents/demo');
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
program.version('0.1.0');
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.command('init')
|
|
14
|
+
.description('Initialize KLAW configuration')
|
|
15
|
+
.action(() => {
|
|
16
|
+
const configDir = path.join(process.env.HOME, '.klaw');
|
|
17
|
+
if (!fs.existsSync(configDir)) {
|
|
18
|
+
fs.mkdirSync(configDir);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const config = {
|
|
22
|
+
version: '0.1.0',
|
|
23
|
+
defaultProvider: 'openai',
|
|
24
|
+
workspace: path.join(process.cwd(), 'klaw-workspace'),
|
|
25
|
+
providers: {
|
|
26
|
+
openai: { apiKey: '' },
|
|
27
|
+
anthropic: { apiKey: '' },
|
|
28
|
+
ollama: { url: 'http://localhost:11434', model: 'llama3' }
|
|
29
|
+
},
|
|
30
|
+
permissions: {
|
|
31
|
+
allowFileWrite: true,
|
|
32
|
+
allowShellCommands: false
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
fs.writeFileSync(path.join(configDir, 'config.json'), JSON.stringify(config, null, 2));
|
|
37
|
+
console.log(chalk.blue('[KLAW][SYSTEM] Configuration created'));
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
program
|
|
41
|
+
.command('run <task>')
|
|
42
|
+
.description('Execute a task via KLAW agents')
|
|
43
|
+
.option('--provider <provider>', 'AI provider to use', 'openai')
|
|
44
|
+
.action(async (task) => {
|
|
45
|
+
console.log(chalk.blue('[KLAW][ARCHITECT] Starting task: ' + task));
|
|
46
|
+
await executeTask(task);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
program
|
|
50
|
+
.command('logs')
|
|
51
|
+
.description('Show KLAW execution logs')
|
|
52
|
+
.option('--follow', 'Follow log output')
|
|
53
|
+
.option('--lines <n>', 'Number of lines to show', '50')
|
|
54
|
+
.action((options) => {
|
|
55
|
+
const logFile = 'klaw-logs.json';
|
|
56
|
+
if (!fs.existsSync(logFile)) {
|
|
57
|
+
console.log(chalk.yellow('[KLAW][SYSTEM] No logs found. Run a task first.'));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const content = fs.readFileSync(logFile, 'utf8');
|
|
62
|
+
console.log(content);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
program
|
|
66
|
+
.command('doctor')
|
|
67
|
+
.description('Check KLAW system status')
|
|
68
|
+
.action(() => {
|
|
69
|
+
console.log(chalk.blue('[KLAW][SYSTEM] Checking system status'));
|
|
70
|
+
console.log(chalk.green('[KLAW][SYSTEM] CLI initialized'));
|
|
71
|
+
console.log(chalk.green('[KLAW][SYSTEM] Provider interfaces ready'));
|
|
72
|
+
console.log(chalk.green('[KLAW][SYSTEM] Memory system active'));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@phartmann80/klaw",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local AI runtime for running agents with your own models and APIs",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"klaw": "index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo 'No test suite yet' && exit 0"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"ai",
|
|
14
|
+
"runtime",
|
|
15
|
+
"agents",
|
|
16
|
+
"local",
|
|
17
|
+
"framework"
|
|
18
|
+
],
|
|
19
|
+
"author": "Paul Hartmann",
|
|
20
|
+
"license": "MIT"
|
|
21
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
class ArchitectAgent {
|
|
2
|
+
constructor(task) {
|
|
3
|
+
this.task = task;
|
|
4
|
+
this.steps = [];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
analyze() {
|
|
8
|
+
const lower = this.task.toLowerCase();
|
|
9
|
+
this.steps = [];
|
|
10
|
+
|
|
11
|
+
if (lower.includes('next.js') || lower.includes('next app') || lower.includes('landing page')) {
|
|
12
|
+
this.steps = [
|
|
13
|
+
'Create package.json with Next.js dependencies',
|
|
14
|
+
'Generate app structure (pages/api)',
|
|
15
|
+
'Install dependencies',
|
|
16
|
+
'Start development server'
|
|
17
|
+
];
|
|
18
|
+
} else if (lower.includes('react') || lower.includes('component')) {
|
|
19
|
+
this.steps = [
|
|
20
|
+
'Create component file',
|
|
21
|
+
'Add to project structure',
|
|
22
|
+
'Install required packages'
|
|
23
|
+
];
|
|
24
|
+
} else {
|
|
25
|
+
this.steps = [
|
|
26
|
+
'Analyze task requirements',
|
|
27
|
+
'Create necessary files',
|
|
28
|
+
'Install dependencies if needed',
|
|
29
|
+
'Set up project structure'
|
|
30
|
+
];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return this.steps;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
log(reasoning) {
|
|
37
|
+
console.log(`[KLAW][ARCHITECT] ${reasoning}`);
|
|
38
|
+
appendMemory('reasoning', `ArchitectAgent: ${reasoning}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
delegate(agent, step) {
|
|
42
|
+
console.log(`[KLAW][ARCHITECT] Delegating to ${agent}: ${step}`);
|
|
43
|
+
appendMemory('action', `[${agent}] ${step}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = ArchitectAgent;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
const ArchitectAgent = require('./architect');
|
|
2
|
+
const FileWriterAgent = require('./writer');
|
|
3
|
+
const ShellAgent = require('./shell');
|
|
4
|
+
const FixerAgent = require('./fixer');
|
|
5
|
+
const { appendMemory, appendMemoryBlock } = require('./memory');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
function parseConfig() {
|
|
10
|
+
const configPath = path.join(process.env.HOME, '.klaw', 'config.json');
|
|
11
|
+
if (fs.existsSync(configPath)) {
|
|
12
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
workspace: './klaw-workspace',
|
|
16
|
+
providers: {},
|
|
17
|
+
permissions: {
|
|
18
|
+
allowFileWrite: true,
|
|
19
|
+
allowShellCommands: false
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function executeTask(task) {
|
|
25
|
+
const config = parseConfig();
|
|
26
|
+
const workspace = config.workspace || './klaw-workspace';
|
|
27
|
+
|
|
28
|
+
if (!fs.existsSync(workspace)) {
|
|
29
|
+
fs.mkdirSync(workspace, { recursive: true });
|
|
30
|
+
console.log(`[KLAW][SYSTEM] Workspace created: ${workspace}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
appendMemory('task', `Received task: ${task}`);
|
|
34
|
+
|
|
35
|
+
const architect = new ArchitectAgent(task);
|
|
36
|
+
const steps = architect.analyze();
|
|
37
|
+
architect.log(`Planning ${steps.length} steps`);
|
|
38
|
+
|
|
39
|
+
const writer = new FileWriterAgent(workspace);
|
|
40
|
+
const shell = new ShellAgent(config);
|
|
41
|
+
const fixer = new FixerAgent();
|
|
42
|
+
|
|
43
|
+
const context = {
|
|
44
|
+
workspace: workspace,
|
|
45
|
+
task: task,
|
|
46
|
+
steps: steps,
|
|
47
|
+
architect: architect,
|
|
48
|
+
writer: writer,
|
|
49
|
+
shell: shell,
|
|
50
|
+
fixer: fixer,
|
|
51
|
+
memory: { appendMemory, appendMemoryBlock }
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
await executeSteps(context, steps);
|
|
56
|
+
console.log(`[KLAW][SYSTEM] Demo complete. Workspace: ${workspace}`);
|
|
57
|
+
appendMemory('result', 'Demo completed');
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.log(`[KLAW][SYSTEM] Error: ${error.message}`);
|
|
60
|
+
appendMemory('error', error.message);
|
|
61
|
+
fixer.suggestFix(error);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function executeSteps(context, steps) {
|
|
66
|
+
for (let i = 0; i < steps.length; i++) {
|
|
67
|
+
const step = steps[i];
|
|
68
|
+
context.architect.log(`Executing step ${i + 1}/${steps.length}: ${step}`);
|
|
69
|
+
|
|
70
|
+
if (step.includes('package.json')) {
|
|
71
|
+
await createNextJsProject(context);
|
|
72
|
+
} else if (step.includes('Install dependencies')) {
|
|
73
|
+
await installDependencies(context);
|
|
74
|
+
} else if (step.includes('Start development server')) {
|
|
75
|
+
await startServer(context);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function createNextJsProject(context) {
|
|
81
|
+
const { workspace } = context;
|
|
82
|
+
|
|
83
|
+
const packageJson = {
|
|
84
|
+
name: 'klaw-demo',
|
|
85
|
+
version: '0.1.0',
|
|
86
|
+
private: true,
|
|
87
|
+
scripts: {
|
|
88
|
+
dev: 'next dev',
|
|
89
|
+
build: 'next build',
|
|
90
|
+
start: 'next start'
|
|
91
|
+
},
|
|
92
|
+
dependencies: {
|
|
93
|
+
next: '^14.0.0',
|
|
94
|
+
react: '^18.2.0',
|
|
95
|
+
'react-dom': '^18.2.0'
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
context.writer.write('package.json', JSON.stringify(packageJson, null, 2));
|
|
100
|
+
context.writer.write('pages/index.js', `export default function Home() {
|
|
101
|
+
return <div style={{ padding: '2rem', background: '#000', color: '#0f0', fontFamily: 'monospace' }}>
|
|
102
|
+
<h1>KLAW Demo</h1>
|
|
103
|
+
<p>Next.js landing page generated by KLAW</p>
|
|
104
|
+
</div>;
|
|
105
|
+
}`);
|
|
106
|
+
|
|
107
|
+
context.writer.write('next.config.js', `/** @type {import('next').NextConfig} */
|
|
108
|
+
const nextConfig = {};
|
|
109
|
+
module.exports = nextConfig;`);
|
|
110
|
+
|
|
111
|
+
context.writer.write('styles/globals.css', `html, body { margin: 0; padding: 0; }
|
|
112
|
+
body { background: #000; color: #0f0; }`);
|
|
113
|
+
|
|
114
|
+
appendMemory('files', 'Created package.json, pages/index.js, next.config.js, styles/globals.css');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function installDependencies(context) {
|
|
118
|
+
context.architect.log('Delegating to ShellAgent for npm install');
|
|
119
|
+
const result = await context.shell.run('npm install', 'Install project dependencies');
|
|
120
|
+
if (!result) {
|
|
121
|
+
context.fixer.retry('npm install', 'Install failed');
|
|
122
|
+
appendMemory('error', 'npm install failed');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function startServer(context) {
|
|
127
|
+
context.architect.log('Delegating to ShellAgent for npm run dev');
|
|
128
|
+
const result = await context.shell.run('npm run dev', 'Start development server');
|
|
129
|
+
if (result) {
|
|
130
|
+
appendMemory('success', 'Dev server started');
|
|
131
|
+
} else {
|
|
132
|
+
appendMemory('error', 'Dev server failed to start');
|
|
133
|
+
context.fixer.retry('npm run dev', 'Server startup failed');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
module.exports = { executeTask };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
class FixerAgent {
|
|
2
|
+
constructor(maxRetries = 3) {
|
|
3
|
+
this.maxRetries = maxRetries;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
retry(command, reason) {
|
|
7
|
+
console.log(`[KLAW][FIXER] Retrying: ${command}`);
|
|
8
|
+
console.log(`[KLAW][FIXER] Reason: ${reason}`);
|
|
9
|
+
// Basic retry logic - just log for v0.1
|
|
10
|
+
appendMemory('fix', `Retry attempted: ${command}`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
suggestFix(error) {
|
|
14
|
+
console.log(`[KLAW][FIXER] Suggesting fix for: ${error.message}`);
|
|
15
|
+
if (error.message.includes('ENOENT') || error.message.includes('not found')) {
|
|
16
|
+
console.log(`[KLAW][FIXER] Tip: Check if dependencies are installed`);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = FixerAgent;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const { spawn } = require('child_process');
|
|
2
|
+
const inquirer = require('inquirer');
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const { parse } = require('url');
|
|
5
|
+
|
|
6
|
+
class ShellAgent {
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.permissions = config.permissions;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async run(command, reason) {
|
|
12
|
+
console.log(`[KLAW][SHELL] Command: ${command}`);
|
|
13
|
+
console.log(`[KLAW][SHELL] Reason: ${reason}`);
|
|
14
|
+
|
|
15
|
+
if (!this.permissions.allowShellCommands) {
|
|
16
|
+
const { allow } = await inquirer.prompt([
|
|
17
|
+
{ type: 'confirm', name: 'allow', message: `[KLAW][SHELL] Allow command? ${command}` }
|
|
18
|
+
]);
|
|
19
|
+
if (!allow) {
|
|
20
|
+
console.log(`[KLAW][SHELL] Command denied`);
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
// Split command into parts for spawn
|
|
27
|
+
const parts = command.split(' ');
|
|
28
|
+
const cmd = parts[0];
|
|
29
|
+
const args = parts.slice(1);
|
|
30
|
+
|
|
31
|
+
const child = spawn(cmd, args, { stdio: 'pipe' });
|
|
32
|
+
|
|
33
|
+
let output = '';
|
|
34
|
+
let serverUrl = null;
|
|
35
|
+
|
|
36
|
+
child.stdout.on('data', (data) => {
|
|
37
|
+
const str = data.toString();
|
|
38
|
+
process.stdout.write(str); // Stream to console in real-time
|
|
39
|
+
output += str;
|
|
40
|
+
|
|
41
|
+
// Check for Next.js dev server start message
|
|
42
|
+
if (str.includes('Server running at') && str.includes('http://')) {
|
|
43
|
+
const match = str.match(/https?:\/\/[^\s]+/);
|
|
44
|
+
if (match) {
|
|
45
|
+
serverUrl = match[0];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
child.stderr.on('data', (data) => {
|
|
51
|
+
process.stderr.write(data.toString());
|
|
52
|
+
output += data.toString();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
child.on('close', (code) => {
|
|
56
|
+
if (code !== 0) {
|
|
57
|
+
reject(new Error(`Command exited with code ${code}`));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// If we detected a server URL, verify it's reachable
|
|
62
|
+
if (serverUrl) {
|
|
63
|
+
this.verifyServer(serverUrl, (err, reachable) => {
|
|
64
|
+
if (err) {
|
|
65
|
+
console.log(`[KLAW][SHELL] Warning: Could not verify server: ${err.message}`);
|
|
66
|
+
}
|
|
67
|
+
if (reachable) {
|
|
68
|
+
console.log(`[KLAW][SHELL] Server verified at ${serverUrl}`);
|
|
69
|
+
}
|
|
70
|
+
resolve(output);
|
|
71
|
+
});
|
|
72
|
+
} else {
|
|
73
|
+
resolve(output);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
child.on('error', (err) => {
|
|
78
|
+
reject(err);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
verifyServer(url, callback) {
|
|
84
|
+
const parsed = parse(url);
|
|
85
|
+
const options = {
|
|
86
|
+
hostname: parsed.hostname,
|
|
87
|
+
port: parsed.port || 80,
|
|
88
|
+
path: parsed.pathname || '/',
|
|
89
|
+
method: 'GET'
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const req = http.request(options, (res) => {
|
|
93
|
+
if (res.statusCode === 200) {
|
|
94
|
+
callback(null, true);
|
|
95
|
+
} else {
|
|
96
|
+
callback(new Error(`Status code: ${res.statusCode}`), false);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
req.on('error', (err) => {
|
|
101
|
+
callback(err, false);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
req.setTimeout(5000, () => {
|
|
105
|
+
req.destroy();
|
|
106
|
+
callback(new Error('Request timeout'), false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
req.end();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = ShellAgent;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
function isValidPath(workspaceRoot, targetPath) {
|
|
5
|
+
const resolved = path.resolve(workspaceRoot, targetPath);
|
|
6
|
+
return resolved.startsWith(path.resolve(workspaceRoot));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function backupFile(filePath) {
|
|
10
|
+
if (fs.existsSync(filePath)) {
|
|
11
|
+
const backupPath = `${filePath}.bak`;
|
|
12
|
+
fs.copyFileSync(filePath, backupPath);
|
|
13
|
+
console.log(`[KLAW][WRITER] Backup: ${filePath} -> ${backupPath}`);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class FileWriterAgent {
|
|
18
|
+
constructor(workspaceRoot) {
|
|
19
|
+
this.workspaceRoot = workspaceRoot;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
write(filePath, content) {
|
|
23
|
+
const fullPath = path.join(this.workspaceRoot, filePath);
|
|
24
|
+
|
|
25
|
+
if (!isValidPath(this.workspaceRoot, filePath)) {
|
|
26
|
+
console.log(`[KLAW][WRITER] Path outside workspace: ${filePath}`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
backupFile(fullPath);
|
|
31
|
+
|
|
32
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
33
|
+
fs.writeFileSync(fullPath, content);
|
|
34
|
+
console.log(`[KLAW][WRITER] Created: ${filePath}`);
|
|
35
|
+
|
|
36
|
+
appendMemory('file', `Created ${filePath}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = FileWriterAgent;
|
package/src/memory.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
function appendMemory(type, content) {
|
|
4
|
+
const memoryFile = './memory.md';
|
|
5
|
+
const timestamp = new Date().toISOString();
|
|
6
|
+
|
|
7
|
+
if (!fs.existsSync(memoryFile)) {
|
|
8
|
+
fs.writeFileSync(memoryFile, '# KLAW Execution Log\n\n');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const entry = `[${timestamp}] ${type.toUpperCase()}: ${content}\n\n`;
|
|
12
|
+
fs.appendFileSync(memoryFile, entry);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function appendMemoryBlock(block) {
|
|
16
|
+
const memoryFile = './memory.md';
|
|
17
|
+
const entry = `## ${block.title}\n\nAgent: ${block.agent}\nTime: ${block.timestamp}\n\nReasoning:\n${block.reasoning}\n\nActions:\n${block.actions}\n\n---\n\n`;
|
|
18
|
+
fs.appendFileSync(memoryFile, entry);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { appendMemory, appendMemoryBlock };
|