openclaw-agent-builder 0.0.1
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/README.md +75 -0
- package/bin/cli.js +30 -0
- package/dist/assets/index-J39606WI.css +1 -0
- package/dist/assets/index-yzWCTaaY.js +228 -0
- package/dist/index.html +13 -0
- package/package.json +46 -0
- package/server/generators/agents.js +87 -0
- package/server/generators/bootstrap.js +42 -0
- package/server/generators/identity.js +18 -0
- package/server/generators/index.js +81 -0
- package/server/generators/memory.js +58 -0
- package/server/generators/routing.js +51 -0
- package/server/generators/soul.js +53 -0
- package/server/generators/team.js +58 -0
- package/server/generators/tools.js +42 -0
- package/server/generators/user.js +27 -0
- package/server/index.js +56 -0
- package/server/routes/agentChat.js +168 -0
- package/server/routes/capabilities.js +195 -0
- package/server/routes/channel.js +161 -0
- package/server/routes/chat.js +150 -0
- package/server/routes/config.js +130 -0
- package/server/routes/files.js +78 -0
- package/server/routes/generate.js +17 -0
- package/server/routes/preflight.js +114 -0
- package/server/routes/validate.js +30 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
function expandHome(p) {
|
|
7
|
+
return p && p.startsWith('~') ? path.join(os.homedir(), p.slice(1)) : (p || '');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const router = Router();
|
|
11
|
+
const CONFIG_PATH = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
12
|
+
|
|
13
|
+
function readConfig() {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
|
|
16
|
+
} catch {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function writeConfig(cfg) {
|
|
22
|
+
fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
|
|
23
|
+
fs.writeFileSync(CONFIG_PATH, JSON.stringify(cfg, null, 2), 'utf8');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
router.get('/openclaw-config', (_req, res) => {
|
|
27
|
+
res.json(readConfig());
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// GET /api/install-context — detect whether this is a fresh install
|
|
31
|
+
router.get('/install-context', (_req, res) => {
|
|
32
|
+
const cfg = readConfig();
|
|
33
|
+
const defaultWsRaw = cfg.agents?.defaults?.workspace || '~/.openclaw/workspace';
|
|
34
|
+
const defaultWs = expandHome(defaultWsRaw);
|
|
35
|
+
const soulExists = fs.existsSync(path.join(defaultWs, 'SOUL.md'));
|
|
36
|
+
const namedAgents = (cfg.agents?.list || []).filter(a => a.workspace);
|
|
37
|
+
res.json({
|
|
38
|
+
isFreshInstall: !soulExists,
|
|
39
|
+
defaultWorkspace: defaultWsRaw,
|
|
40
|
+
existingAgentCount: namedAgents.length,
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Resolve the actual workspace path for an agent, respecting existing config.
|
|
45
|
+
// Falls back to ~/.openclaw/workspace-<id> for new agents.
|
|
46
|
+
router.get('/resolve-workspace/:agentId', (req, res) => {
|
|
47
|
+
const { agentId } = req.params;
|
|
48
|
+
const cfg = readConfig();
|
|
49
|
+
const home = os.homedir();
|
|
50
|
+
|
|
51
|
+
// Check agents.list for an existing entry with explicit workspace
|
|
52
|
+
const existing = (cfg.agents?.list || []).find(a => a.id === agentId);
|
|
53
|
+
if (existing?.workspace) {
|
|
54
|
+
const resolved = existing.workspace.replace(/^~/, home);
|
|
55
|
+
return res.json({ workspace: existing.workspace, resolved, existing: true });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// If this agent has no explicit workspace, it inherits agents.defaults.workspace
|
|
59
|
+
// (the default agent pattern — workspace has no -<id> suffix)
|
|
60
|
+
const defaultWs = cfg.agents?.defaults?.workspace || '~/.openclaw/workspace';
|
|
61
|
+
const isDefaultAgent = existing && !existing.workspace;
|
|
62
|
+
if (isDefaultAgent) {
|
|
63
|
+
const resolved = defaultWs.replace(/^~/, home);
|
|
64
|
+
return res.json({ workspace: defaultWs, resolved, existing: true, inheritedDefault: true });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// New agent — use workspace-<id> convention
|
|
68
|
+
const workspace = `~/.openclaw/workspace-${agentId}`;
|
|
69
|
+
res.json({ workspace, resolved: path.join(home, `.openclaw/workspace-${agentId}`), existing: false });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
router.patch('/openclaw-config', (req, res) => {
|
|
73
|
+
try {
|
|
74
|
+
const { agents: newAgents, bindings: newBindings, defaultAgentId, isMainAgent } = req.body;
|
|
75
|
+
const cfg = readConfig();
|
|
76
|
+
|
|
77
|
+
// Backup first
|
|
78
|
+
if (fs.existsSync(CONFIG_PATH)) {
|
|
79
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
80
|
+
fs.copyFileSync(CONFIG_PATH, `${CONFIG_PATH}.bak-${ts}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (!isMainAgent) {
|
|
84
|
+
// Named agent — upsert into agents.list
|
|
85
|
+
if (!cfg.agents) cfg.agents = {};
|
|
86
|
+
if (!cfg.agents.list) cfg.agents.list = [];
|
|
87
|
+
|
|
88
|
+
if (newAgents && newAgents.list) {
|
|
89
|
+
for (const newAgent of newAgents.list) {
|
|
90
|
+
const idx = cfg.agents.list.findIndex(a => a.id === newAgent.id);
|
|
91
|
+
if (idx >= 0) {
|
|
92
|
+
if (newAgent.workspace) cfg.agents.list[idx].workspace = newAgent.workspace;
|
|
93
|
+
} else {
|
|
94
|
+
cfg.agents.list.push(newAgent);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Ensure exactly one default
|
|
100
|
+
if (defaultAgentId) {
|
|
101
|
+
for (const agent of cfg.agents.list) {
|
|
102
|
+
if (agent.default !== undefined) delete agent.default;
|
|
103
|
+
}
|
|
104
|
+
const defAgent = cfg.agents.list.find(a => a.id === defaultAgentId);
|
|
105
|
+
if (defAgent) defAgent.default = true;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// isMainAgent === true: the main agent already IS the default via agents.defaults —
|
|
109
|
+
// no agents.list entry needed. Just handle bindings below.
|
|
110
|
+
|
|
111
|
+
// Merge bindings (top-level) — prepend new, deduplicate
|
|
112
|
+
if (!cfg.bindings) cfg.bindings = [];
|
|
113
|
+
if (newBindings) {
|
|
114
|
+
for (const nb of newBindings) {
|
|
115
|
+
const key = nb.agentId + JSON.stringify(nb.match);
|
|
116
|
+
const exists = cfg.bindings.some(b => b.agentId + JSON.stringify(b.match) === key);
|
|
117
|
+
if (!exists) {
|
|
118
|
+
cfg.bindings.unshift(nb);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
writeConfig(cfg);
|
|
124
|
+
res.json({ ok: true });
|
|
125
|
+
} catch (err) {
|
|
126
|
+
res.status(500).json({ error: err.message });
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
export default router;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
const router = Router();
|
|
7
|
+
|
|
8
|
+
function expandHome(p) {
|
|
9
|
+
if (p.startsWith('~')) return path.join(os.homedir(), p.slice(1));
|
|
10
|
+
return p;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readOpenClawConfig() {
|
|
14
|
+
try {
|
|
15
|
+
return JSON.parse(fs.readFileSync(path.join(os.homedir(), '.openclaw', 'openclaw.json'), 'utf8'));
|
|
16
|
+
} catch { return {}; }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function resolveAgentWorkspace(agentId, cfg) {
|
|
20
|
+
const existing = (cfg.agents?.list || []).find(a => a.id === agentId);
|
|
21
|
+
if (existing?.workspace) return existing.workspace;
|
|
22
|
+
// Agent exists but has no explicit workspace — uses agents.defaults.workspace
|
|
23
|
+
if (existing) return cfg.agents?.defaults?.workspace || '~/.openclaw/workspace';
|
|
24
|
+
// New agent
|
|
25
|
+
return `~/.openclaw/workspace-${agentId}`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
router.get('/check-paths', (req, res) => {
|
|
29
|
+
const agents = (req.query.agents || '').split(',').filter(Boolean);
|
|
30
|
+
const cfg = readOpenClawConfig();
|
|
31
|
+
const conflicts = agents.map(agentId => {
|
|
32
|
+
const ws = resolveAgentWorkspace(agentId, cfg);
|
|
33
|
+
const p = expandHome(ws);
|
|
34
|
+
return { agentId, path: p, exists: fs.existsSync(p) };
|
|
35
|
+
});
|
|
36
|
+
res.json({ conflicts });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
router.post('/write', async (req, res) => {
|
|
40
|
+
const { files, basePath, force = false } = req.body;
|
|
41
|
+
const written = [], skipped = [], errors = [];
|
|
42
|
+
|
|
43
|
+
for (const file of files) {
|
|
44
|
+
try {
|
|
45
|
+
let filePath = file.path;
|
|
46
|
+
// Resolve relative to basePath or ~/.openclaw
|
|
47
|
+
if (!path.isAbsolute(filePath)) {
|
|
48
|
+
const base = basePath ? expandHome(basePath) : expandHome('~/.openclaw');
|
|
49
|
+
filePath = path.join(base, filePath);
|
|
50
|
+
} else {
|
|
51
|
+
filePath = expandHome(filePath);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const dir = path.dirname(filePath);
|
|
55
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
56
|
+
|
|
57
|
+
if (fs.existsSync(filePath) && !force) {
|
|
58
|
+
skipped.push(filePath);
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (fs.existsSync(filePath) && force) {
|
|
63
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
64
|
+
const bakPath = `${filePath}.bak-${ts}`;
|
|
65
|
+
fs.copyFileSync(filePath, bakPath);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fs.writeFileSync(filePath, file.content, 'utf8');
|
|
69
|
+
written.push(filePath);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
errors.push({ path: file.path, error: err.message });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
res.json({ written, skipped, errors });
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
export default router;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { generateFiles } from '../generators/index.js';
|
|
3
|
+
|
|
4
|
+
const router = Router();
|
|
5
|
+
|
|
6
|
+
router.post('/', async (req, res) => {
|
|
7
|
+
try {
|
|
8
|
+
const { teamSpec } = req.body;
|
|
9
|
+
if (!teamSpec) return res.status(400).json({ error: 'teamSpec required' });
|
|
10
|
+
const files = generateFiles(teamSpec);
|
|
11
|
+
res.json({ files, errors: [] });
|
|
12
|
+
} catch (err) {
|
|
13
|
+
res.status(500).json({ error: err.message, files: [], errors: [err.message] });
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export default router;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { spawn, execSync } from 'child_process';
|
|
3
|
+
import { existsSync } from 'fs';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
const router = Router();
|
|
8
|
+
|
|
9
|
+
// Build an env with extended PATH covering common node/npm install locations
|
|
10
|
+
function extendedEnv() {
|
|
11
|
+
const home = os.homedir();
|
|
12
|
+
const extra = [
|
|
13
|
+
process.env.NVM_BIN, // nvm active bin (already in PATH but be explicit)
|
|
14
|
+
'/usr/local/bin',
|
|
15
|
+
'/opt/homebrew/bin',
|
|
16
|
+
'/opt/homebrew/sbin',
|
|
17
|
+
`${home}/.npm-global/bin`,
|
|
18
|
+
`${home}/.volta/bin`,
|
|
19
|
+
`${home}/.fnm/current/bin`,
|
|
20
|
+
'/usr/bin',
|
|
21
|
+
'/bin',
|
|
22
|
+
].filter(Boolean);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
...process.env,
|
|
26
|
+
PATH: [...extra, process.env.PATH || ''].join(':'),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function tryGetVersion(env) {
|
|
31
|
+
// Try direct command
|
|
32
|
+
try {
|
|
33
|
+
return execSync('openclaw --version', { timeout: 5000, shell: true, env }).toString().trim();
|
|
34
|
+
} catch {}
|
|
35
|
+
|
|
36
|
+
// Try resolving via `which` first, then running the resolved path
|
|
37
|
+
try {
|
|
38
|
+
const bin = execSync('which openclaw', { timeout: 3000, shell: true, env }).toString().trim();
|
|
39
|
+
if (bin) return execSync(`"${bin}" --version`, { timeout: 5000 }).toString().trim();
|
|
40
|
+
} catch {}
|
|
41
|
+
|
|
42
|
+
// Try common absolute paths
|
|
43
|
+
const home = os.homedir();
|
|
44
|
+
const candidates = [
|
|
45
|
+
'/usr/local/bin/openclaw',
|
|
46
|
+
'/opt/homebrew/bin/openclaw',
|
|
47
|
+
`${home}/.npm-global/bin/openclaw`,
|
|
48
|
+
`${home}/.volta/bin/openclaw`,
|
|
49
|
+
];
|
|
50
|
+
for (const p of candidates) {
|
|
51
|
+
if (existsSync(p)) {
|
|
52
|
+
try { return execSync(`"${p}" --version`, { timeout: 5000 }).toString().trim(); } catch {}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if openclaw is installed and get its version
|
|
60
|
+
router.get('/preflight', (_req, res) => {
|
|
61
|
+
const env = extendedEnv();
|
|
62
|
+
const version = tryGetVersion(env);
|
|
63
|
+
|
|
64
|
+
if (version !== null) {
|
|
65
|
+
return res.json({ installed: true, version });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Fallback: if ~/.openclaw/openclaw.json exists, the CLI is installed even if we can't find it in PATH
|
|
69
|
+
const configPath = path.join(os.homedir(), '.openclaw', 'openclaw.json');
|
|
70
|
+
if (existsSync(configPath)) {
|
|
71
|
+
return res.json({ installed: true, version: '(version unknown)' });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
res.json({ installed: false, version: null });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Run npm install -g openclaw and stream output via SSE
|
|
78
|
+
router.post('/preflight/install', (req, res) => {
|
|
79
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
80
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
81
|
+
res.setHeader('Connection', 'keep-alive');
|
|
82
|
+
|
|
83
|
+
function send(type, data) {
|
|
84
|
+
res.write(`data: ${JSON.stringify({ type, data })}\n\n`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
send('log', 'Running: npm install -g openclaw\n');
|
|
88
|
+
|
|
89
|
+
const proc = spawn('npm', ['install', '-g', 'openclaw'], {
|
|
90
|
+
shell: true,
|
|
91
|
+
env: extendedEnv(),
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
proc.stdout.on('data', d => send('log', d.toString()));
|
|
95
|
+
proc.stderr.on('data', d => send('log', d.toString()));
|
|
96
|
+
|
|
97
|
+
proc.on('error', err => {
|
|
98
|
+
send('error', err.message);
|
|
99
|
+
res.end();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
proc.on('close', code => {
|
|
103
|
+
if (code === 0) {
|
|
104
|
+
const env = extendedEnv();
|
|
105
|
+
const version = tryGetVersion(env);
|
|
106
|
+
send('done', version || '(installed)');
|
|
107
|
+
} else {
|
|
108
|
+
send('error', `Install exited with code ${code}`);
|
|
109
|
+
}
|
|
110
|
+
res.end();
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
export default router;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import { spawn } from 'child_process';
|
|
3
|
+
|
|
4
|
+
const router = Router();
|
|
5
|
+
|
|
6
|
+
router.post('/validate', (_req, res) => {
|
|
7
|
+
const proc = spawn('openclaw', ['doctor'], { shell: true });
|
|
8
|
+
let output = '';
|
|
9
|
+
|
|
10
|
+
proc.stdout.on('data', d => { output += d.toString(); });
|
|
11
|
+
proc.stderr.on('data', d => { output += d.toString(); });
|
|
12
|
+
|
|
13
|
+
proc.on('error', err => {
|
|
14
|
+
if (err.code === 'ENOENT') {
|
|
15
|
+
res.json({
|
|
16
|
+
exitCode: 127,
|
|
17
|
+
output: 'openclaw not found in PATH. Install from https://openclaw.ai',
|
|
18
|
+
passed: false,
|
|
19
|
+
});
|
|
20
|
+
} else {
|
|
21
|
+
res.json({ exitCode: 1, output: err.message, passed: false });
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
proc.on('close', code => {
|
|
26
|
+
res.json({ exitCode: code, output, passed: code === 0 });
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export default router;
|