@dotsetlabs/dotclaw 1.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/.env.example +54 -0
- package/LICENSE +21 -0
- package/README.md +111 -0
- package/config-examples/groups/global/CLAUDE.md +21 -0
- package/config-examples/groups/main/CLAUDE.md +47 -0
- package/config-examples/mount-allowlist.json +25 -0
- package/config-examples/plugin-http.json +18 -0
- package/config-examples/runtime.json +30 -0
- package/config-examples/tool-budgets.json +24 -0
- package/config-examples/tool-policy.json +51 -0
- package/container/.dockerignore +6 -0
- package/container/Dockerfile +74 -0
- package/container/agent-runner/package-lock.json +92 -0
- package/container/agent-runner/package.json +20 -0
- package/container/agent-runner/src/agent-config.ts +295 -0
- package/container/agent-runner/src/container-protocol.ts +73 -0
- package/container/agent-runner/src/daemon.ts +91 -0
- package/container/agent-runner/src/index.ts +1428 -0
- package/container/agent-runner/src/ipc.ts +321 -0
- package/container/agent-runner/src/memory.ts +336 -0
- package/container/agent-runner/src/prompt-packs.ts +341 -0
- package/container/agent-runner/src/tools.ts +1720 -0
- package/container/agent-runner/tsconfig.json +19 -0
- package/container/build.sh +23 -0
- package/container/skills/agent-browser.md +159 -0
- package/dist/admin-commands.d.ts +7 -0
- package/dist/admin-commands.d.ts.map +1 -0
- package/dist/admin-commands.js +87 -0
- package/dist/admin-commands.js.map +1 -0
- package/dist/agent-context.d.ts +42 -0
- package/dist/agent-context.d.ts.map +1 -0
- package/dist/agent-context.js +92 -0
- package/dist/agent-context.js.map +1 -0
- package/dist/agent-execution.d.ts +68 -0
- package/dist/agent-execution.d.ts.map +1 -0
- package/dist/agent-execution.js +169 -0
- package/dist/agent-execution.js.map +1 -0
- package/dist/agent-semaphore.d.ts +2 -0
- package/dist/agent-semaphore.d.ts.map +1 -0
- package/dist/agent-semaphore.js +52 -0
- package/dist/agent-semaphore.js.map +1 -0
- package/dist/behavior-config.d.ts +14 -0
- package/dist/behavior-config.d.ts.map +1 -0
- package/dist/behavior-config.js +52 -0
- package/dist/behavior-config.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +626 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +31 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -0
- package/dist/container-protocol.d.ts +72 -0
- package/dist/container-protocol.d.ts.map +1 -0
- package/dist/container-protocol.js +3 -0
- package/dist/container-protocol.js.map +1 -0
- package/dist/container-runner.d.ts +59 -0
- package/dist/container-runner.d.ts.map +1 -0
- package/dist/container-runner.js +813 -0
- package/dist/container-runner.js.map +1 -0
- package/dist/cost.d.ts +9 -0
- package/dist/cost.d.ts.map +1 -0
- package/dist/cost.js +11 -0
- package/dist/cost.js.map +1 -0
- package/dist/dashboard.d.ts +58 -0
- package/dist/dashboard.d.ts.map +1 -0
- package/dist/dashboard.js +471 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/db.d.ts +99 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +423 -0
- package/dist/db.js.map +1 -0
- package/dist/error-messages.d.ts +17 -0
- package/dist/error-messages.d.ts.map +1 -0
- package/dist/error-messages.js +109 -0
- package/dist/error-messages.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2072 -0
- package/dist/index.js.map +1 -0
- package/dist/locks.d.ts +2 -0
- package/dist/locks.d.ts.map +1 -0
- package/dist/locks.js +26 -0
- package/dist/locks.js.map +1 -0
- package/dist/logger.d.ts +4 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +15 -0
- package/dist/logger.js.map +1 -0
- package/dist/maintenance.d.ts +13 -0
- package/dist/maintenance.d.ts.map +1 -0
- package/dist/maintenance.js +151 -0
- package/dist/maintenance.js.map +1 -0
- package/dist/memory-embeddings.d.ts +13 -0
- package/dist/memory-embeddings.d.ts.map +1 -0
- package/dist/memory-embeddings.js +126 -0
- package/dist/memory-embeddings.js.map +1 -0
- package/dist/memory-recall.d.ts +8 -0
- package/dist/memory-recall.d.ts.map +1 -0
- package/dist/memory-recall.js +127 -0
- package/dist/memory-recall.js.map +1 -0
- package/dist/memory-store.d.ts +149 -0
- package/dist/memory-store.d.ts.map +1 -0
- package/dist/memory-store.js +787 -0
- package/dist/memory-store.js.map +1 -0
- package/dist/metrics.d.ts +12 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +134 -0
- package/dist/metrics.js.map +1 -0
- package/dist/model-registry.d.ts +67 -0
- package/dist/model-registry.d.ts.map +1 -0
- package/dist/model-registry.js +230 -0
- package/dist/model-registry.js.map +1 -0
- package/dist/mount-security.d.ts +37 -0
- package/dist/mount-security.d.ts.map +1 -0
- package/dist/mount-security.js +284 -0
- package/dist/mount-security.js.map +1 -0
- package/dist/paths.d.ts +80 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +149 -0
- package/dist/paths.js.map +1 -0
- package/dist/personalization.d.ts +6 -0
- package/dist/personalization.d.ts.map +1 -0
- package/dist/personalization.js +180 -0
- package/dist/personalization.js.map +1 -0
- package/dist/progress.d.ts +15 -0
- package/dist/progress.d.ts.map +1 -0
- package/dist/progress.js +92 -0
- package/dist/progress.js.map +1 -0
- package/dist/runtime-config.d.ts +227 -0
- package/dist/runtime-config.d.ts.map +1 -0
- package/dist/runtime-config.js +297 -0
- package/dist/runtime-config.js.map +1 -0
- package/dist/task-scheduler.d.ts +9 -0
- package/dist/task-scheduler.d.ts.map +1 -0
- package/dist/task-scheduler.js +195 -0
- package/dist/task-scheduler.js.map +1 -0
- package/dist/telegram-format.d.ts +3 -0
- package/dist/telegram-format.d.ts.map +1 -0
- package/dist/telegram-format.js +200 -0
- package/dist/telegram-format.js.map +1 -0
- package/dist/tool-budgets.d.ts +16 -0
- package/dist/tool-budgets.d.ts.map +1 -0
- package/dist/tool-budgets.js +83 -0
- package/dist/tool-budgets.js.map +1 -0
- package/dist/tool-policy.d.ts +18 -0
- package/dist/tool-policy.d.ts.map +1 -0
- package/dist/tool-policy.js +84 -0
- package/dist/tool-policy.js.map +1 -0
- package/dist/trace-writer.d.ts +39 -0
- package/dist/trace-writer.d.ts.map +1 -0
- package/dist/trace-writer.js +27 -0
- package/dist/trace-writer.js.map +1 -0
- package/dist/types.d.ts +81 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +30 -0
- package/dist/utils.js.map +1 -0
- package/launchd/com.dotclaw.plist +32 -0
- package/package.json +89 -0
- package/scripts/autotune.js +53 -0
- package/scripts/bootstrap.js +348 -0
- package/scripts/configure.js +200 -0
- package/scripts/doctor.js +164 -0
- package/scripts/init.js +209 -0
- package/scripts/install.sh +219 -0
- package/systemd/dotclaw.service +22 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import readline from 'readline';
|
|
5
|
+
|
|
6
|
+
// Get DOTCLAW_HOME from environment or default to ~/.dotclaw
|
|
7
|
+
const DOTCLAW_HOME = process.env.DOTCLAW_HOME || path.join(os.homedir(), '.dotclaw');
|
|
8
|
+
const CONFIG_DIR = path.join(DOTCLAW_HOME, 'config');
|
|
9
|
+
const DATA_DIR = path.join(DOTCLAW_HOME, 'data');
|
|
10
|
+
const ENV_PATH = path.join(DOTCLAW_HOME, '.env');
|
|
11
|
+
const MODEL_CONFIG_PATH = path.join(CONFIG_DIR, 'model.json');
|
|
12
|
+
const RUNTIME_CONFIG_PATH = path.join(CONFIG_DIR, 'runtime.json');
|
|
13
|
+
|
|
14
|
+
function parseEnv(content) {
|
|
15
|
+
const lines = content.split('\n');
|
|
16
|
+
const map = new Map();
|
|
17
|
+
for (const line of lines) {
|
|
18
|
+
const trimmed = line.trim();
|
|
19
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
20
|
+
const idx = trimmed.indexOf('=');
|
|
21
|
+
if (idx === -1) continue;
|
|
22
|
+
const key = trimmed.slice(0, idx).trim();
|
|
23
|
+
const value = trimmed.slice(idx + 1).trim();
|
|
24
|
+
map.set(key, value);
|
|
25
|
+
}
|
|
26
|
+
return map;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function updateEnvContent(existing, updates) {
|
|
30
|
+
const lines = existing.split('\n');
|
|
31
|
+
const keys = new Set(Object.keys(updates));
|
|
32
|
+
const output = lines.map(line => {
|
|
33
|
+
const trimmed = line.trim();
|
|
34
|
+
if (!trimmed || trimmed.startsWith('#')) return line;
|
|
35
|
+
const idx = trimmed.indexOf('=');
|
|
36
|
+
if (idx === -1) return line;
|
|
37
|
+
const key = trimmed.slice(0, idx).trim();
|
|
38
|
+
if (!keys.has(key)) return line;
|
|
39
|
+
return `${key}=${updates[key]}`;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
for (const [key, value] of Object.entries(updates)) {
|
|
43
|
+
const exists = lines.some(line => line.trim().startsWith(`${key}=`));
|
|
44
|
+
if (!exists) {
|
|
45
|
+
output.push(`${key}=${value}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return output.join('\n').replace(/\n+$/, '\n');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function mask(value) {
|
|
53
|
+
if (!value) return 'missing';
|
|
54
|
+
if (value.length <= 8) return 'set';
|
|
55
|
+
return `${value.slice(0, 4)}…${value.slice(-2)}`;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function promptForValue(rl, label, currentValue, optional = false) {
|
|
59
|
+
const suffix = optional ? ' (optional)' : '';
|
|
60
|
+
const prompt = `${label}${suffix} [current: ${mask(currentValue)}]: `;
|
|
61
|
+
return new Promise(resolve => {
|
|
62
|
+
rl.question(prompt, answer => {
|
|
63
|
+
const value = answer.trim();
|
|
64
|
+
if (!value) {
|
|
65
|
+
resolve(currentValue || '');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
resolve(value);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function loadRuntimeConfig() {
|
|
74
|
+
if (!fs.existsSync(RUNTIME_CONFIG_PATH)) return {};
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(fs.readFileSync(RUNTIME_CONFIG_PATH, 'utf-8'));
|
|
77
|
+
} catch {
|
|
78
|
+
return {};
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function saveRuntimeConfig(config) {
|
|
83
|
+
fs.mkdirSync(path.dirname(RUNTIME_CONFIG_PATH), { recursive: true });
|
|
84
|
+
fs.writeFileSync(RUNTIME_CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function main() {
|
|
88
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
|
89
|
+
fs.mkdirSync(DATA_DIR, { recursive: true });
|
|
90
|
+
|
|
91
|
+
const envContent = fs.existsSync(ENV_PATH) ? fs.readFileSync(ENV_PATH, 'utf-8') : '';
|
|
92
|
+
const envMap = parseEnv(envContent);
|
|
93
|
+
const runtimeConfig = loadRuntimeConfig();
|
|
94
|
+
|
|
95
|
+
const nonInteractive = ['1', 'true', 'yes'].includes((process.env.DOTCLAW_CONFIGURE_NONINTERACTIVE || process.env.DOTCLAW_BOOTSTRAP_NONINTERACTIVE || '').toLowerCase());
|
|
96
|
+
|
|
97
|
+
let modelConfig = {
|
|
98
|
+
model: 'moonshotai/kimi-k2.5',
|
|
99
|
+
allowlist: [],
|
|
100
|
+
updated_at: new Date().toISOString()
|
|
101
|
+
};
|
|
102
|
+
if (fs.existsSync(MODEL_CONFIG_PATH)) {
|
|
103
|
+
try {
|
|
104
|
+
modelConfig = JSON.parse(fs.readFileSync(MODEL_CONFIG_PATH, 'utf-8'));
|
|
105
|
+
} catch {
|
|
106
|
+
// keep defaults
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const runtimeAgent = runtimeConfig.agent || {};
|
|
111
|
+
const runtimeOpenrouter = runtimeAgent.openrouter || {};
|
|
112
|
+
|
|
113
|
+
let telegramToken = envMap.get('TELEGRAM_BOT_TOKEN') || '';
|
|
114
|
+
let openrouterKey = envMap.get('OPENROUTER_API_KEY') || '';
|
|
115
|
+
let openrouterModel = modelConfig.model;
|
|
116
|
+
let openrouterSiteUrl = runtimeOpenrouter.siteUrl || '';
|
|
117
|
+
let openrouterSiteName = runtimeOpenrouter.siteName || '';
|
|
118
|
+
let braveKey = envMap.get('BRAVE_SEARCH_API_KEY') || '';
|
|
119
|
+
let allowlistInput = '';
|
|
120
|
+
|
|
121
|
+
if (nonInteractive) {
|
|
122
|
+
telegramToken = process.env.TELEGRAM_BOT_TOKEN || telegramToken;
|
|
123
|
+
openrouterKey = process.env.OPENROUTER_API_KEY || openrouterKey;
|
|
124
|
+
braveKey = process.env.BRAVE_SEARCH_API_KEY || braveKey;
|
|
125
|
+
|
|
126
|
+
if (!telegramToken) {
|
|
127
|
+
console.error('TELEGRAM_BOT_TOKEN is required for non-interactive configuration.');
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
if (!openrouterKey) {
|
|
131
|
+
console.error('OPENROUTER_API_KEY is required for non-interactive configuration.');
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
136
|
+
|
|
137
|
+
telegramToken = await promptForValue(rl, 'TELEGRAM_BOT_TOKEN', telegramToken);
|
|
138
|
+
openrouterKey = await promptForValue(rl, 'OPENROUTER_API_KEY', openrouterKey);
|
|
139
|
+
openrouterModel = await promptForValue(rl, 'OPENROUTER_MODEL', openrouterModel);
|
|
140
|
+
openrouterSiteUrl = await promptForValue(rl, 'OPENROUTER_SITE_URL', openrouterSiteUrl, true);
|
|
141
|
+
openrouterSiteName = await promptForValue(rl, 'OPENROUTER_SITE_NAME', openrouterSiteName, true);
|
|
142
|
+
braveKey = await promptForValue(rl, 'BRAVE_SEARCH_API_KEY', braveKey, true);
|
|
143
|
+
|
|
144
|
+
allowlistInput = await new Promise(resolve => {
|
|
145
|
+
rl.question('Model allowlist (comma-separated, blank = allow all): ', answer => {
|
|
146
|
+
resolve(answer.trim());
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
rl.close();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const updates = {
|
|
154
|
+
TELEGRAM_BOT_TOKEN: telegramToken,
|
|
155
|
+
OPENROUTER_API_KEY: openrouterKey
|
|
156
|
+
};
|
|
157
|
+
if (braveKey) updates.BRAVE_SEARCH_API_KEY = braveKey;
|
|
158
|
+
|
|
159
|
+
const nextEnv = updateEnvContent(envContent || '', updates);
|
|
160
|
+
fs.writeFileSync(ENV_PATH, nextEnv);
|
|
161
|
+
try {
|
|
162
|
+
fs.chmodSync(ENV_PATH, 0o600);
|
|
163
|
+
} catch {
|
|
164
|
+
// best-effort
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const allowlist = allowlistInput
|
|
168
|
+
? allowlistInput.split(',').map(item => item.trim()).filter(Boolean)
|
|
169
|
+
: (Array.isArray(modelConfig.allowlist) ? modelConfig.allowlist : []);
|
|
170
|
+
|
|
171
|
+
const nextModelConfig = {
|
|
172
|
+
...modelConfig,
|
|
173
|
+
model: openrouterModel,
|
|
174
|
+
allowlist,
|
|
175
|
+
updated_at: new Date().toISOString()
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
fs.writeFileSync(MODEL_CONFIG_PATH, JSON.stringify(nextModelConfig, null, 2) + '\n');
|
|
179
|
+
|
|
180
|
+
const nextRuntimeConfig = { ...runtimeConfig };
|
|
181
|
+
if (!nextRuntimeConfig.agent) nextRuntimeConfig.agent = {};
|
|
182
|
+
if (!nextRuntimeConfig.agent.openrouter) nextRuntimeConfig.agent.openrouter = {};
|
|
183
|
+
nextRuntimeConfig.agent.openrouter.siteUrl = openrouterSiteUrl || '';
|
|
184
|
+
nextRuntimeConfig.agent.openrouter.siteName = openrouterSiteName || '';
|
|
185
|
+
|
|
186
|
+
if (!nextRuntimeConfig.host) nextRuntimeConfig.host = {};
|
|
187
|
+
if (!nextRuntimeConfig.host.memory) nextRuntimeConfig.host.memory = {};
|
|
188
|
+
if (!nextRuntimeConfig.host.memory.embeddings) nextRuntimeConfig.host.memory.embeddings = {};
|
|
189
|
+
nextRuntimeConfig.host.memory.embeddings.openrouterSiteUrl = openrouterSiteUrl || '';
|
|
190
|
+
nextRuntimeConfig.host.memory.embeddings.openrouterSiteName = openrouterSiteName || '';
|
|
191
|
+
|
|
192
|
+
saveRuntimeConfig(nextRuntimeConfig);
|
|
193
|
+
|
|
194
|
+
console.log('Configuration updated.');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
main().catch(err => {
|
|
198
|
+
console.error(err);
|
|
199
|
+
process.exit(1);
|
|
200
|
+
});
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
|
|
6
|
+
// Get DOTCLAW_HOME from environment or default to ~/.dotclaw
|
|
7
|
+
const DOTCLAW_HOME = process.env.DOTCLAW_HOME || path.join(os.homedir(), '.dotclaw');
|
|
8
|
+
const CONFIG_DIR = path.join(DOTCLAW_HOME, 'config');
|
|
9
|
+
const DATA_DIR = path.join(DOTCLAW_HOME, 'data');
|
|
10
|
+
const GROUPS_DIR = path.join(DOTCLAW_HOME, 'groups');
|
|
11
|
+
const STORE_DIR = path.join(DATA_DIR, 'store');
|
|
12
|
+
const LOGS_DIR = path.join(DOTCLAW_HOME, 'logs');
|
|
13
|
+
const TRACES_DIR = path.join(DOTCLAW_HOME, 'traces');
|
|
14
|
+
const PROMPTS_DIR = path.join(DOTCLAW_HOME, 'prompts');
|
|
15
|
+
|
|
16
|
+
function log(label, value) {
|
|
17
|
+
console.log(`${label}: ${value}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function checkDocker() {
|
|
21
|
+
try {
|
|
22
|
+
execSync('docker info', { stdio: 'pipe', timeout: 10000 });
|
|
23
|
+
log('Docker', 'OK');
|
|
24
|
+
} catch {
|
|
25
|
+
log('Docker', 'NOT RUNNING');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function checkPathAccess(label, dir) {
|
|
30
|
+
try {
|
|
31
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
32
|
+
fs.accessSync(dir, fs.constants.R_OK | fs.constants.W_OK);
|
|
33
|
+
log(label, 'read/write OK');
|
|
34
|
+
} catch (err) {
|
|
35
|
+
log(label, `permission error (${err instanceof Error ? err.message : String(err)})`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function countFiles(dir) {
|
|
40
|
+
try {
|
|
41
|
+
if (!fs.existsSync(dir)) return 0;
|
|
42
|
+
return fs.readdirSync(dir).length;
|
|
43
|
+
} catch {
|
|
44
|
+
return 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function checkSystemd(service) {
|
|
49
|
+
try {
|
|
50
|
+
const output = execSync(`systemctl is-active ${service}`, { stdio: 'pipe' }).toString().trim();
|
|
51
|
+
log(`systemd ${service}`, output);
|
|
52
|
+
} catch {
|
|
53
|
+
log(`systemd ${service}`, 'not available');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function diskSpace(dir) {
|
|
58
|
+
try {
|
|
59
|
+
const output = execSync(`df -k "${dir}"`, { stdio: 'pipe' }).toString();
|
|
60
|
+
const lines = output.trim().split('\n');
|
|
61
|
+
const last = lines[lines.length - 1];
|
|
62
|
+
const parts = last.split(/\s+/);
|
|
63
|
+
const availKb = parseInt(parts[3], 10);
|
|
64
|
+
if (Number.isFinite(availKb)) {
|
|
65
|
+
const availGb = (availKb / (1024 * 1024)).toFixed(2);
|
|
66
|
+
return `${availGb} GB available`;
|
|
67
|
+
}
|
|
68
|
+
return 'unknown';
|
|
69
|
+
} catch (err) {
|
|
70
|
+
return `error (${err instanceof Error ? err.message : String(err)})`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function loadRuntimeConfig() {
|
|
75
|
+
const runtimePath = path.join(CONFIG_DIR, 'runtime.json');
|
|
76
|
+
if (!fs.existsSync(runtimePath)) return null;
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(fs.readFileSync(runtimePath, 'utf-8'));
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
log('Node', process.version);
|
|
85
|
+
if (typeof process.getuid === 'function') {
|
|
86
|
+
log('UID', String(process.getuid()));
|
|
87
|
+
}
|
|
88
|
+
if (typeof process.getgid === 'function') {
|
|
89
|
+
log('GID', String(process.getgid()));
|
|
90
|
+
}
|
|
91
|
+
checkDocker();
|
|
92
|
+
log('DOTCLAW_HOME', DOTCLAW_HOME);
|
|
93
|
+
checkPathAccess('config/', CONFIG_DIR);
|
|
94
|
+
checkPathAccess('data/', DATA_DIR);
|
|
95
|
+
checkPathAccess('groups/', GROUPS_DIR);
|
|
96
|
+
checkPathAccess('data/store/', STORE_DIR);
|
|
97
|
+
checkPathAccess('logs/', LOGS_DIR);
|
|
98
|
+
log('Disk space', diskSpace(DOTCLAW_HOME));
|
|
99
|
+
|
|
100
|
+
const runtimeConfig = loadRuntimeConfig();
|
|
101
|
+
if (runtimeConfig) {
|
|
102
|
+
log('runtime.json', 'present');
|
|
103
|
+
const containerMode = runtimeConfig?.host?.container?.mode;
|
|
104
|
+
if (containerMode) {
|
|
105
|
+
log('Container mode', String(containerMode));
|
|
106
|
+
}
|
|
107
|
+
const maxAgents = runtimeConfig?.host?.concurrency?.maxAgents;
|
|
108
|
+
if (Number.isFinite(maxAgents)) {
|
|
109
|
+
log('Max concurrent agents', String(maxAgents));
|
|
110
|
+
}
|
|
111
|
+
const warmStart = runtimeConfig?.host?.concurrency?.warmStart;
|
|
112
|
+
if (typeof warmStart === 'boolean') {
|
|
113
|
+
log('Warm start', String(warmStart));
|
|
114
|
+
}
|
|
115
|
+
const blockPrivate = runtimeConfig?.agent?.tools?.webfetch?.blockPrivate;
|
|
116
|
+
if (typeof blockPrivate === 'boolean') {
|
|
117
|
+
log('WebFetch block private', String(blockPrivate));
|
|
118
|
+
}
|
|
119
|
+
const traceDir = runtimeConfig?.host?.trace?.dir || TRACES_DIR;
|
|
120
|
+
const promptsDir = runtimeConfig?.host?.promptPacksDir || PROMPTS_DIR;
|
|
121
|
+
log('Trace files', String(countFiles(traceDir)));
|
|
122
|
+
log('Prompt packs', String(countFiles(promptsDir)));
|
|
123
|
+
} else {
|
|
124
|
+
log('runtime.json', 'missing');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const envPath = path.join(DOTCLAW_HOME, '.env');
|
|
128
|
+
log('.env', fs.existsSync(envPath) ? 'present' : 'missing');
|
|
129
|
+
|
|
130
|
+
if (fs.existsSync(envPath)) {
|
|
131
|
+
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
132
|
+
const hasOpenRouter = envContent.includes('OPENROUTER_API_KEY=');
|
|
133
|
+
const hasBrave = envContent.includes('BRAVE_SEARCH_API_KEY=');
|
|
134
|
+
const hasTelegram = envContent.includes('TELEGRAM_BOT_TOKEN=');
|
|
135
|
+
log('TELEGRAM_BOT_TOKEN', hasTelegram ? 'set' : 'missing');
|
|
136
|
+
log('OPENROUTER_API_KEY', hasOpenRouter ? 'set' : 'missing');
|
|
137
|
+
log('BRAVE_SEARCH_API_KEY', hasBrave ? 'set (optional, enables WebSearch)' : 'missing');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
checkSystemd('dotclaw.service');
|
|
141
|
+
checkSystemd('autotune.timer');
|
|
142
|
+
|
|
143
|
+
if (typeof process.getuid === 'function' && process.getuid() === 0) {
|
|
144
|
+
log('Warning', 'Running as root. For best security, run as a non-root user.');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const modelConfigPath = path.join(CONFIG_DIR, 'model.json');
|
|
148
|
+
if (fs.existsSync(modelConfigPath)) {
|
|
149
|
+
try {
|
|
150
|
+
const modelConfig = JSON.parse(fs.readFileSync(modelConfigPath, 'utf-8'));
|
|
151
|
+
log('Model', modelConfig.model || 'missing');
|
|
152
|
+
log('Model allowlist', Array.isArray(modelConfig.allowlist) && modelConfig.allowlist.length > 0 ? modelConfig.allowlist.join(', ') : 'none (allow all)');
|
|
153
|
+
} catch (err) {
|
|
154
|
+
log('Model config', `error (${err instanceof Error ? err.message : String(err)})`);
|
|
155
|
+
}
|
|
156
|
+
} else {
|
|
157
|
+
log('Model config', 'missing');
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const behaviorConfigPath = path.join(CONFIG_DIR, 'behavior.json');
|
|
161
|
+
log('Behavior config', fs.existsSync(behaviorConfigPath) ? behaviorConfigPath : 'missing');
|
|
162
|
+
|
|
163
|
+
const memoryDbPath = path.join(STORE_DIR, 'memory.db');
|
|
164
|
+
log('Memory DB', fs.existsSync(memoryDbPath) ? 'present' : 'missing');
|
package/scripts/init.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { execSync } from 'child_process';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
|
|
10
|
+
// Get DOTCLAW_HOME from environment or default to ~/.dotclaw
|
|
11
|
+
const DOTCLAW_HOME = process.env.DOTCLAW_HOME || path.join(os.homedir(), '.dotclaw');
|
|
12
|
+
const CONFIG_DIR = path.join(DOTCLAW_HOME, 'config');
|
|
13
|
+
const DATA_DIR = path.join(DOTCLAW_HOME, 'data');
|
|
14
|
+
const STORE_DIR = path.join(DATA_DIR, 'store');
|
|
15
|
+
const GROUPS_DIR = path.join(DOTCLAW_HOME, 'groups');
|
|
16
|
+
const LOGS_DIR = path.join(DOTCLAW_HOME, 'logs');
|
|
17
|
+
const TRACES_DIR = path.join(DOTCLAW_HOME, 'traces');
|
|
18
|
+
const PROMPTS_DIR = path.join(DOTCLAW_HOME, 'prompts');
|
|
19
|
+
const ENV_PATH = path.join(DOTCLAW_HOME, '.env');
|
|
20
|
+
const MOUNT_ALLOWLIST_DIR = path.join(os.homedir(), '.config', 'dotclaw');
|
|
21
|
+
const MOUNT_ALLOWLIST_PATH = path.join(MOUNT_ALLOWLIST_DIR, 'mount-allowlist.json');
|
|
22
|
+
|
|
23
|
+
// Package root for copying example configs
|
|
24
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
25
|
+
const CONFIG_EXAMPLES_DIR = path.join(PACKAGE_ROOT, 'config-examples');
|
|
26
|
+
|
|
27
|
+
function log(message) {
|
|
28
|
+
console.log(message);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function ensureDir(dir) {
|
|
32
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function ensureFile(filePath, contents) {
|
|
36
|
+
if (!fs.existsSync(filePath)) {
|
|
37
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
38
|
+
fs.writeFileSync(filePath, contents);
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function checkDocker() {
|
|
45
|
+
try {
|
|
46
|
+
execSync('docker info', { stdio: 'pipe', timeout: 10000 });
|
|
47
|
+
log('Docker: OK');
|
|
48
|
+
} catch {
|
|
49
|
+
log('Docker: NOT RUNNING');
|
|
50
|
+
log('Start Docker Desktop (macOS) or run: sudo systemctl start docker (Linux)');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function initFiles() {
|
|
55
|
+
// Create directory structure
|
|
56
|
+
ensureDir(DOTCLAW_HOME);
|
|
57
|
+
ensureDir(CONFIG_DIR);
|
|
58
|
+
ensureDir(DATA_DIR);
|
|
59
|
+
ensureDir(STORE_DIR);
|
|
60
|
+
ensureDir(GROUPS_DIR);
|
|
61
|
+
ensureDir(path.join(GROUPS_DIR, 'main'));
|
|
62
|
+
ensureDir(path.join(GROUPS_DIR, 'global'));
|
|
63
|
+
ensureDir(LOGS_DIR);
|
|
64
|
+
ensureDir(TRACES_DIR);
|
|
65
|
+
ensureDir(PROMPTS_DIR);
|
|
66
|
+
ensureDir(MOUNT_ALLOWLIST_DIR);
|
|
67
|
+
|
|
68
|
+
// Set restrictive permissions
|
|
69
|
+
try {
|
|
70
|
+
fs.chmodSync(DOTCLAW_HOME, 0o700);
|
|
71
|
+
fs.chmodSync(CONFIG_DIR, 0o700);
|
|
72
|
+
fs.chmodSync(DATA_DIR, 0o700);
|
|
73
|
+
} catch {
|
|
74
|
+
// Best-effort; permissions may be controlled by the OS or user policy.
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Config files
|
|
78
|
+
const registeredGroupsPath = path.join(DATA_DIR, 'registered_groups.json');
|
|
79
|
+
const modelConfigPath = path.join(CONFIG_DIR, 'model.json');
|
|
80
|
+
const behaviorConfigPath = path.join(CONFIG_DIR, 'behavior.json');
|
|
81
|
+
const toolPolicyPath = path.join(CONFIG_DIR, 'tool-policy.json');
|
|
82
|
+
const toolBudgetsPath = path.join(CONFIG_DIR, 'tool-budgets.json');
|
|
83
|
+
const runtimeConfigPath = path.join(CONFIG_DIR, 'runtime.json');
|
|
84
|
+
|
|
85
|
+
// Seed CLAUDE.md templates if missing
|
|
86
|
+
const mainClaudePath = path.join(GROUPS_DIR, 'main', 'CLAUDE.md');
|
|
87
|
+
const globalClaudePath = path.join(GROUPS_DIR, 'global', 'CLAUDE.md');
|
|
88
|
+
const mainClaudeExample = path.join(CONFIG_EXAMPLES_DIR, 'groups', 'main', 'CLAUDE.md');
|
|
89
|
+
const globalClaudeExample = path.join(CONFIG_EXAMPLES_DIR, 'groups', 'global', 'CLAUDE.md');
|
|
90
|
+
if (!fs.existsSync(mainClaudePath) && fs.existsSync(mainClaudeExample)) {
|
|
91
|
+
fs.copyFileSync(mainClaudeExample, mainClaudePath);
|
|
92
|
+
}
|
|
93
|
+
if (!fs.existsSync(globalClaudePath) && fs.existsSync(globalClaudeExample)) {
|
|
94
|
+
fs.copyFileSync(globalClaudeExample, globalClaudePath);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const createdRegistered = ensureFile(registeredGroupsPath, '{}\n');
|
|
98
|
+
const createdModelConfig = ensureFile(modelConfigPath, JSON.stringify({
|
|
99
|
+
model: 'moonshotai/kimi-k2.5',
|
|
100
|
+
allowlist: [
|
|
101
|
+
'moonshotai/kimi-k2.5',
|
|
102
|
+
'openai/gpt-5-mini',
|
|
103
|
+
'openai/gpt-5-nano'
|
|
104
|
+
],
|
|
105
|
+
overrides: {
|
|
106
|
+
'moonshotai/kimi-k2.5': {
|
|
107
|
+
context_window: 32000,
|
|
108
|
+
max_output_tokens: 2048,
|
|
109
|
+
tokens_per_char: 0.6,
|
|
110
|
+
tokens_per_message: 4,
|
|
111
|
+
tokens_per_request: 50
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
updated_at: new Date().toISOString()
|
|
115
|
+
}, null, 2) + '\n');
|
|
116
|
+
const createdBehaviorConfig = ensureFile(behaviorConfigPath, JSON.stringify({
|
|
117
|
+
tool_calling_bias: 0.5,
|
|
118
|
+
memory_importance_threshold: 0.55,
|
|
119
|
+
response_style: 'balanced',
|
|
120
|
+
caution_bias: 0.5,
|
|
121
|
+
last_updated: new Date().toISOString()
|
|
122
|
+
}, null, 2) + '\n');
|
|
123
|
+
const toolPolicyExamplePath = path.join(CONFIG_EXAMPLES_DIR, 'tool-policy.json');
|
|
124
|
+
const toolPolicyPayload = fs.existsSync(toolPolicyExamplePath)
|
|
125
|
+
? fs.readFileSync(toolPolicyExamplePath, 'utf-8')
|
|
126
|
+
: JSON.stringify({ default: {} }, null, 2);
|
|
127
|
+
const createdToolPolicy = ensureFile(
|
|
128
|
+
toolPolicyPath,
|
|
129
|
+
toolPolicyPayload.endsWith('\n') ? toolPolicyPayload : toolPolicyPayload + '\n'
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
const toolBudgetsExamplePath = path.join(CONFIG_EXAMPLES_DIR, 'tool-budgets.json');
|
|
133
|
+
const toolBudgetsPayload = fs.existsSync(toolBudgetsExamplePath)
|
|
134
|
+
? fs.readFileSync(toolBudgetsExamplePath, 'utf-8')
|
|
135
|
+
: JSON.stringify({ default: { per_day: {} } }, null, 2);
|
|
136
|
+
const createdToolBudgets = ensureFile(
|
|
137
|
+
toolBudgetsPath,
|
|
138
|
+
toolBudgetsPayload.endsWith('\n') ? toolBudgetsPayload : toolBudgetsPayload + '\n'
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// Copy runtime config from examples if available
|
|
142
|
+
const runtimeExamplePath = path.join(CONFIG_EXAMPLES_DIR, 'runtime.json');
|
|
143
|
+
const runtimePayload = fs.existsSync(runtimeExamplePath)
|
|
144
|
+
? fs.readFileSync(runtimeExamplePath, 'utf-8')
|
|
145
|
+
: '{\n "host": {},\n "agent": {}\n}\n';
|
|
146
|
+
const createdRuntimeConfig = ensureFile(
|
|
147
|
+
runtimeConfigPath,
|
|
148
|
+
runtimePayload.endsWith('\n') ? runtimePayload : runtimePayload + '\n'
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
// Mount allowlist template (kept outside DotClaw home)
|
|
152
|
+
const allowlistTemplate = JSON.stringify({
|
|
153
|
+
allowedRoots: [],
|
|
154
|
+
blockedPatterns: [],
|
|
155
|
+
nonMainReadOnly: true
|
|
156
|
+
}, null, 2) + '\n';
|
|
157
|
+
const createdAllowlist = ensureFile(MOUNT_ALLOWLIST_PATH, allowlistTemplate);
|
|
158
|
+
if (createdAllowlist) {
|
|
159
|
+
try {
|
|
160
|
+
fs.chmodSync(MOUNT_ALLOWLIST_PATH, 0o600);
|
|
161
|
+
} catch {
|
|
162
|
+
// best-effort
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Environment file
|
|
167
|
+
const envSample = [
|
|
168
|
+
'# DotClaw Secrets',
|
|
169
|
+
'# These values are required for DotClaw to function.',
|
|
170
|
+
'',
|
|
171
|
+
'TELEGRAM_BOT_TOKEN=your_bot_token_here',
|
|
172
|
+
'OPENROUTER_API_KEY=your_openrouter_api_key',
|
|
173
|
+
'',
|
|
174
|
+
'# Optional: Brave Search for web search capability',
|
|
175
|
+
'BRAVE_SEARCH_API_KEY=your_brave_search_api_key'
|
|
176
|
+
].join('\n');
|
|
177
|
+
|
|
178
|
+
const createdEnv = ensureFile(ENV_PATH, envSample);
|
|
179
|
+
if (createdEnv) {
|
|
180
|
+
try {
|
|
181
|
+
fs.chmodSync(ENV_PATH, 0o600);
|
|
182
|
+
} catch {
|
|
183
|
+
// Best-effort; permissions may be controlled by the OS or user policy.
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
log(`DOTCLAW_HOME: ${DOTCLAW_HOME}`);
|
|
188
|
+
log(`registered_groups.json: ${createdRegistered ? 'created' : 'exists'}`);
|
|
189
|
+
log(`model.json: ${createdModelConfig ? 'created' : 'exists'}`);
|
|
190
|
+
log(`behavior.json: ${createdBehaviorConfig ? 'created' : 'exists'}`);
|
|
191
|
+
log(`tool-policy.json: ${createdToolPolicy ? 'created' : 'exists'}`);
|
|
192
|
+
log(`tool-budgets.json: ${createdToolBudgets ? 'created' : 'exists'}`);
|
|
193
|
+
log(`runtime.json: ${createdRuntimeConfig ? 'created' : 'exists'}`);
|
|
194
|
+
log(`.env: ${createdEnv ? 'created (edit this file)' : 'exists'}`);
|
|
195
|
+
log(`mount-allowlist.json: ${createdAllowlist ? 'created (edit to enable mounts)' : 'exists'}`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function printNextSteps() {
|
|
199
|
+
log('\nNext steps:');
|
|
200
|
+
log(`1) Edit ${ENV_PATH} with your Telegram bot token and OpenRouter API key`);
|
|
201
|
+
log(`2) (Optional) Edit ${path.join(CONFIG_DIR, 'runtime.json')} for tuning`);
|
|
202
|
+
log('3) Build the container: dotclaw build');
|
|
203
|
+
log('4) Register your chat: dotclaw register');
|
|
204
|
+
log('5) Start the service: dotclaw start');
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
checkDocker();
|
|
208
|
+
initFiles();
|
|
209
|
+
printNextSteps();
|