@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,32 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
3
|
+
<plist version="1.0">
|
|
4
|
+
<dict>
|
|
5
|
+
<key>Label</key>
|
|
6
|
+
<string>com.dotclaw</string>
|
|
7
|
+
<key>ProgramArguments</key>
|
|
8
|
+
<array>
|
|
9
|
+
<string>{{NODE_PATH}}</string>
|
|
10
|
+
<string>{{PROJECT_ROOT}}/dist/index.js</string>
|
|
11
|
+
</array>
|
|
12
|
+
<key>WorkingDirectory</key>
|
|
13
|
+
<string>{{PROJECT_ROOT}}</string>
|
|
14
|
+
<key>RunAtLoad</key>
|
|
15
|
+
<true/>
|
|
16
|
+
<key>KeepAlive</key>
|
|
17
|
+
<true/>
|
|
18
|
+
<key>EnvironmentVariables</key>
|
|
19
|
+
<dict>
|
|
20
|
+
<key>PATH</key>
|
|
21
|
+
<string>{{HOME}}/.local/bin:/usr/local/bin:/usr/bin:/bin</string>
|
|
22
|
+
<key>HOME</key>
|
|
23
|
+
<string>{{HOME}}</string>
|
|
24
|
+
<key>DOTCLAW_HOME</key>
|
|
25
|
+
<string>{{DOTCLAW_HOME}}</string>
|
|
26
|
+
</dict>
|
|
27
|
+
<key>StandardOutPath</key>
|
|
28
|
+
<string>{{HOME}}/.dotclaw/logs/dotclaw.log</string>
|
|
29
|
+
<key>StandardErrorPath</key>
|
|
30
|
+
<string>{{HOME}}/.dotclaw/logs/dotclaw.error.log</string>
|
|
31
|
+
</dict>
|
|
32
|
+
</plist>
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dotsetlabs/dotclaw",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Personal OpenRouter-based assistant. Lightweight, secure, customizable.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dotclaw": "./dist/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "git+https://github.com/dotsetlabs/dotclaw.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"ai",
|
|
16
|
+
"assistant",
|
|
17
|
+
"openrouter",
|
|
18
|
+
"telegram",
|
|
19
|
+
"agent",
|
|
20
|
+
"llm"
|
|
21
|
+
],
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"container/Dockerfile",
|
|
25
|
+
"container/build.sh",
|
|
26
|
+
"container/.dockerignore",
|
|
27
|
+
"container/skills",
|
|
28
|
+
"container/agent-runner/package.json",
|
|
29
|
+
"container/agent-runner/package-lock.json",
|
|
30
|
+
"container/agent-runner/tsconfig.json",
|
|
31
|
+
"container/agent-runner/src",
|
|
32
|
+
"scripts/init.js",
|
|
33
|
+
"scripts/bootstrap.js",
|
|
34
|
+
"scripts/configure.js",
|
|
35
|
+
"scripts/doctor.js",
|
|
36
|
+
"scripts/autotune.js",
|
|
37
|
+
"scripts/install.sh",
|
|
38
|
+
"config-examples",
|
|
39
|
+
"launchd",
|
|
40
|
+
"systemd",
|
|
41
|
+
".env.example"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"build": "tsc",
|
|
45
|
+
"start": "node dist/index.js",
|
|
46
|
+
"dev": "tsx src/index.ts",
|
|
47
|
+
"typecheck": "tsc --noEmit",
|
|
48
|
+
"init": "node scripts/init.js",
|
|
49
|
+
"bootstrap": "node scripts/bootstrap.js",
|
|
50
|
+
"configure": "node scripts/configure.js",
|
|
51
|
+
"doctor": "node scripts/doctor.js",
|
|
52
|
+
"autotune": "node scripts/autotune.js",
|
|
53
|
+
"lint": "eslint . --max-warnings=0",
|
|
54
|
+
"test": "npm run build && node --test test/**/*.test.js && npm run test:agent-runner",
|
|
55
|
+
"test:agent-runner": "node scripts/test-agent-runner.js",
|
|
56
|
+
"prepublishOnly": "npm run build && npm run lint && npm test",
|
|
57
|
+
"docs:dev": "vitepress dev docs",
|
|
58
|
+
"docs:build": "vitepress build docs",
|
|
59
|
+
"docs:preview": "vitepress preview docs"
|
|
60
|
+
},
|
|
61
|
+
"dependencies": {
|
|
62
|
+
"@dotsetlabs/autotune": "^1.1.0",
|
|
63
|
+
"better-sqlite3": "^11.8.1",
|
|
64
|
+
"cron-parser": "^5.5.0",
|
|
65
|
+
"dotenv": "^17.2.3",
|
|
66
|
+
"pino": "^9.6.0",
|
|
67
|
+
"pino-pretty": "^13.0.0",
|
|
68
|
+
"prom-client": "^15.1.3",
|
|
69
|
+
"telegraf": "^4.16.3",
|
|
70
|
+
"zod": "^4.3.6"
|
|
71
|
+
},
|
|
72
|
+
"devDependencies": {
|
|
73
|
+
"@eslint/js": "^9.19.0",
|
|
74
|
+
"@types/better-sqlite3": "^7.6.12",
|
|
75
|
+
"@types/node": "^22.10.0",
|
|
76
|
+
"eslint": "^9.19.0",
|
|
77
|
+
"globals": "^15.14.0",
|
|
78
|
+
"tsx": "^4.19.0",
|
|
79
|
+
"typescript": "^5.7.0",
|
|
80
|
+
"typescript-eslint": "^8.23.0",
|
|
81
|
+
"vitepress": "^1.2.0"
|
|
82
|
+
},
|
|
83
|
+
"engines": {
|
|
84
|
+
"node": ">=20"
|
|
85
|
+
},
|
|
86
|
+
"publishConfig": {
|
|
87
|
+
"access": "public"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import os from 'os';
|
|
6
|
+
import { runOnce } from '@dotsetlabs/autotune';
|
|
7
|
+
|
|
8
|
+
// Get DOTCLAW_HOME from environment or default to ~/.dotclaw
|
|
9
|
+
const DOTCLAW_HOME = process.env.DOTCLAW_HOME || path.join(os.homedir(), '.dotclaw');
|
|
10
|
+
const CONFIG_DIR = path.join(DOTCLAW_HOME, 'config');
|
|
11
|
+
const DATA_DIR = path.join(DOTCLAW_HOME, 'data');
|
|
12
|
+
const TRACES_DIR = path.join(DOTCLAW_HOME, 'traces');
|
|
13
|
+
const PROMPTS_DIR = path.join(DOTCLAW_HOME, 'prompts');
|
|
14
|
+
|
|
15
|
+
function setDefaultEnv(key, value) {
|
|
16
|
+
if (!process.env[key] && value !== undefined && value !== null) {
|
|
17
|
+
process.env[key] = String(value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function loadRuntimeConfig() {
|
|
22
|
+
const runtimePath = path.join(CONFIG_DIR, 'runtime.json');
|
|
23
|
+
if (!fs.existsSync(runtimePath)) return null;
|
|
24
|
+
try {
|
|
25
|
+
return JSON.parse(fs.readFileSync(runtimePath, 'utf-8'));
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function main() {
|
|
32
|
+
const runtime = loadRuntimeConfig();
|
|
33
|
+
if (runtime) {
|
|
34
|
+
setDefaultEnv('AUTOTUNE_TRACE_DIR', runtime.host?.trace?.dir || TRACES_DIR);
|
|
35
|
+
setDefaultEnv('AUTOTUNE_OUTPUT_DIR', runtime.host?.promptPacksDir || PROMPTS_DIR);
|
|
36
|
+
setDefaultEnv('AUTOTUNE_CANARY_FRACTION', runtime.agent?.promptPacks?.canaryRate);
|
|
37
|
+
} else {
|
|
38
|
+
setDefaultEnv('AUTOTUNE_TRACE_DIR', TRACES_DIR);
|
|
39
|
+
setDefaultEnv('AUTOTUNE_OUTPUT_DIR', PROMPTS_DIR);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setDefaultEnv('AUTOTUNE_BEHAVIOR_CONFIG_PATH', path.join(CONFIG_DIR, 'behavior.json'));
|
|
43
|
+
setDefaultEnv('AUTOTUNE_BEHAVIOR_REPORT_DIR', DATA_DIR);
|
|
44
|
+
setDefaultEnv('AUTOTUNE_BEHAVIOR_ENABLED', '1');
|
|
45
|
+
|
|
46
|
+
await runOnce();
|
|
47
|
+
console.log('Autotune complete.');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
main().catch(err => {
|
|
51
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
52
|
+
process.exit(1);
|
|
53
|
+
});
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import { spawnSync } from 'child_process';
|
|
5
|
+
import readline from 'readline';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
// Get DOTCLAW_HOME from environment or default to ~/.dotclaw
|
|
12
|
+
const DOTCLAW_HOME = process.env.DOTCLAW_HOME || path.join(os.homedir(), '.dotclaw');
|
|
13
|
+
const CONFIG_DIR = path.join(DOTCLAW_HOME, 'config');
|
|
14
|
+
const DATA_DIR = path.join(DOTCLAW_HOME, 'data');
|
|
15
|
+
const GROUPS_DIR = path.join(DOTCLAW_HOME, 'groups');
|
|
16
|
+
const IPC_DIR = path.join(DATA_DIR, 'ipc');
|
|
17
|
+
const SESSIONS_DIR = path.join(DATA_DIR, 'sessions');
|
|
18
|
+
const REGISTERED_GROUPS = path.join(DATA_DIR, 'registered_groups.json');
|
|
19
|
+
|
|
20
|
+
// Package root for container build script and script paths
|
|
21
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..');
|
|
22
|
+
const SCRIPTS_DIR = path.join(PACKAGE_ROOT, 'scripts');
|
|
23
|
+
|
|
24
|
+
function runScript(scriptName) {
|
|
25
|
+
const scriptPath = path.join(SCRIPTS_DIR, scriptName);
|
|
26
|
+
const result = spawnSync('node', [scriptPath], {
|
|
27
|
+
stdio: 'inherit',
|
|
28
|
+
env: { ...process.env, DOTCLAW_HOME }
|
|
29
|
+
});
|
|
30
|
+
if (result.status !== 0) {
|
|
31
|
+
process.exit(result.status || 1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function loadJson(filePath, fallback) {
|
|
36
|
+
try {
|
|
37
|
+
if (fs.existsSync(filePath)) {
|
|
38
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
// ignore
|
|
42
|
+
}
|
|
43
|
+
return fallback;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function saveJson(filePath, data) {
|
|
47
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
48
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isSafeFolder(folder) {
|
|
52
|
+
return /^[a-z0-9-]+$/.test(folder);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function parseEnv(filePath) {
|
|
56
|
+
if (!fs.existsSync(filePath)) return new Map();
|
|
57
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
58
|
+
const lines = content.split('\n');
|
|
59
|
+
const map = new Map();
|
|
60
|
+
for (const line of lines) {
|
|
61
|
+
const trimmed = line.trim();
|
|
62
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
63
|
+
const idx = trimmed.indexOf('=');
|
|
64
|
+
if (idx === -1) continue;
|
|
65
|
+
const key = trimmed.slice(0, idx).trim();
|
|
66
|
+
const value = trimmed.slice(idx + 1).trim();
|
|
67
|
+
map.set(key, value);
|
|
68
|
+
}
|
|
69
|
+
return map;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function filterEnv(envMap) {
|
|
73
|
+
const allowedVars = new Set([
|
|
74
|
+
'OPENROUTER_API_KEY',
|
|
75
|
+
'BRAVE_SEARCH_API_KEY'
|
|
76
|
+
]);
|
|
77
|
+
|
|
78
|
+
const filtered = new Map();
|
|
79
|
+
for (const [key, value] of envMap.entries()) {
|
|
80
|
+
if (allowedVars.has(key)) {
|
|
81
|
+
filtered.set(key, value);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return filtered;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function loadRuntimeConfig() {
|
|
88
|
+
const runtimePath = path.join(CONFIG_DIR, 'runtime.json');
|
|
89
|
+
if (!fs.existsSync(runtimePath)) return {};
|
|
90
|
+
try {
|
|
91
|
+
return JSON.parse(fs.readFileSync(runtimePath, 'utf-8'));
|
|
92
|
+
} catch {
|
|
93
|
+
return {};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function runSelfCheck({
|
|
98
|
+
image,
|
|
99
|
+
envVars,
|
|
100
|
+
groupFolder,
|
|
101
|
+
modelOverride,
|
|
102
|
+
containerConfig
|
|
103
|
+
}) {
|
|
104
|
+
const uid = containerConfig.runUid || (typeof process.getuid === 'function' ? String(process.getuid()) : '');
|
|
105
|
+
const gid = containerConfig.runGid || (typeof process.getgid === 'function' ? String(process.getgid()) : '');
|
|
106
|
+
const pidsLimit = containerConfig.pidsLimit || 256;
|
|
107
|
+
const memory = containerConfig.memory || '';
|
|
108
|
+
const cpus = containerConfig.cpus || '';
|
|
109
|
+
const readOnlyRoot = !!containerConfig.readOnlyRoot;
|
|
110
|
+
const tmpfsSize = containerConfig.tmpfsSize || '64m';
|
|
111
|
+
|
|
112
|
+
const groupDir = path.join(GROUPS_DIR, groupFolder);
|
|
113
|
+
const sessionDir = path.join(SESSIONS_DIR, groupFolder, 'openrouter');
|
|
114
|
+
const ipcDir = path.join(IPC_DIR, groupFolder);
|
|
115
|
+
const messagesDir = path.join(ipcDir, 'messages');
|
|
116
|
+
const tasksDir = path.join(ipcDir, 'tasks');
|
|
117
|
+
fs.mkdirSync(groupDir, { recursive: true });
|
|
118
|
+
fs.mkdirSync(sessionDir, { recursive: true });
|
|
119
|
+
fs.mkdirSync(messagesDir, { recursive: true });
|
|
120
|
+
fs.mkdirSync(tasksDir, { recursive: true });
|
|
121
|
+
|
|
122
|
+
const args = ['run', '-i', '--rm'];
|
|
123
|
+
args.push('--cap-drop=ALL');
|
|
124
|
+
args.push('--security-opt=no-new-privileges');
|
|
125
|
+
args.push(`--pids-limit=${pidsLimit}`);
|
|
126
|
+
if (uid) {
|
|
127
|
+
args.push('--user', gid ? `${uid}:${gid}` : uid);
|
|
128
|
+
}
|
|
129
|
+
args.push('--env', 'HOME=/tmp');
|
|
130
|
+
if (memory) args.push(`--memory=${memory}`);
|
|
131
|
+
if (cpus) args.push(`--cpus=${cpus}`);
|
|
132
|
+
if (readOnlyRoot) {
|
|
133
|
+
const tmpfsOptions = ['rw', 'noexec', 'nosuid', `size=${tmpfsSize}`];
|
|
134
|
+
if (uid) tmpfsOptions.push(`uid=${uid}`);
|
|
135
|
+
if (gid) tmpfsOptions.push(`gid=${gid}`);
|
|
136
|
+
args.push('--read-only');
|
|
137
|
+
args.push('--tmpfs', `/tmp:${tmpfsOptions.join(',')}`);
|
|
138
|
+
args.push('--tmpfs', `/home/node:${tmpfsOptions.join(',')}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
args.push('-v', `${groupDir}:/workspace/group`);
|
|
142
|
+
args.push('-v', `${sessionDir}:/workspace/session`);
|
|
143
|
+
args.push('-v', `${ipcDir}:/workspace/ipc`);
|
|
144
|
+
if (fs.existsSync(CONFIG_DIR)) {
|
|
145
|
+
args.push('-v', `${CONFIG_DIR}:/workspace/config:ro`);
|
|
146
|
+
}
|
|
147
|
+
if (fs.existsSync(DATA_DIR)) {
|
|
148
|
+
args.push('-v', `${DATA_DIR}:/workspace/data:ro`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
for (const [key, value] of envVars.entries()) {
|
|
152
|
+
if (!value) continue;
|
|
153
|
+
args.push('--env', `${key}=${value}`);
|
|
154
|
+
}
|
|
155
|
+
args.push('--env', 'DOTCLAW_SELF_CHECK=1');
|
|
156
|
+
|
|
157
|
+
args.push(image);
|
|
158
|
+
|
|
159
|
+
const input = JSON.stringify({
|
|
160
|
+
prompt: 'self-check',
|
|
161
|
+
groupFolder,
|
|
162
|
+
chatJid: 'self-check',
|
|
163
|
+
isMain: true,
|
|
164
|
+
modelOverride
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
const result = spawnSync('docker', args, {
|
|
168
|
+
input,
|
|
169
|
+
encoding: 'utf-8'
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
const stdout = result.stdout || '';
|
|
173
|
+
const stderr = result.stderr || '';
|
|
174
|
+
const startMarker = '---DOTCLAW_OUTPUT_START---';
|
|
175
|
+
const endMarker = '---DOTCLAW_OUTPUT_END---';
|
|
176
|
+
const startIdx = stdout.indexOf(startMarker);
|
|
177
|
+
const endIdx = stdout.indexOf(endMarker);
|
|
178
|
+
if (startIdx === -1 || endIdx === -1) {
|
|
179
|
+
console.log('Self-check output could not be parsed.');
|
|
180
|
+
if (stderr.trim()) console.log(stderr.trim());
|
|
181
|
+
if (stdout.trim()) console.log(stdout.trim());
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const jsonText = stdout.slice(startIdx + startMarker.length, endIdx).trim();
|
|
186
|
+
try {
|
|
187
|
+
const payload = JSON.parse(jsonText);
|
|
188
|
+
if (payload.status !== 'success') {
|
|
189
|
+
console.log('Self-check reported error:', payload.error || 'unknown');
|
|
190
|
+
if (stderr.trim()) console.log(stderr.trim());
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
console.log(payload.result || 'Self-check passed.');
|
|
194
|
+
return true;
|
|
195
|
+
} catch (err) {
|
|
196
|
+
console.log('Self-check output parse error:', err instanceof Error ? err.message : String(err));
|
|
197
|
+
if (stderr.trim()) console.log(stderr.trim());
|
|
198
|
+
return false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function prompt(question, defaultValue = '') {
|
|
203
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
204
|
+
return new Promise(resolve => {
|
|
205
|
+
rl.question(`${question}${defaultValue ? ` [${defaultValue}]` : ''}: `, answer => {
|
|
206
|
+
rl.close();
|
|
207
|
+
const value = answer.trim();
|
|
208
|
+
resolve(value || defaultValue);
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function parseBoolEnv(value, fallback = false) {
|
|
214
|
+
if (value === undefined || value === null || value === '') return fallback;
|
|
215
|
+
const normalized = String(value).toLowerCase().trim();
|
|
216
|
+
return ['1', 'true', 'yes', 'on'].includes(normalized);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function requireEnv(name) {
|
|
220
|
+
const value = process.env[name];
|
|
221
|
+
if (!value) {
|
|
222
|
+
console.error(`Missing required env var: ${name}`);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
return value;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function main() {
|
|
229
|
+
console.log('DotClaw bootstrap starting...\n');
|
|
230
|
+
|
|
231
|
+
runScript('init.js');
|
|
232
|
+
|
|
233
|
+
const nonInteractive = parseBoolEnv(process.env.DOTCLAW_BOOTSTRAP_NONINTERACTIVE, false);
|
|
234
|
+
if (nonInteractive) {
|
|
235
|
+
process.env.DOTCLAW_CONFIGURE_NONINTERACTIVE = '1';
|
|
236
|
+
}
|
|
237
|
+
runScript('configure.js');
|
|
238
|
+
|
|
239
|
+
if (typeof process.getuid === 'function' && process.getuid() === 0) {
|
|
240
|
+
console.log('Warning: You are running bootstrap as root. For best security, run as a non-root user.');
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
fs.accessSync(DATA_DIR, fs.constants.W_OK);
|
|
245
|
+
fs.accessSync(GROUPS_DIR, fs.constants.W_OK);
|
|
246
|
+
} catch {
|
|
247
|
+
console.log(`Warning: ${DATA_DIR} or ${GROUPS_DIR} is not writable by the current user.`);
|
|
248
|
+
console.log(`If you encounter permission errors, run: sudo chown -R $USER ${DOTCLAW_HOME}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
console.log('\nNow register your main chat.');
|
|
252
|
+
console.log('If you do not know your Telegram chat ID, use @userinfobot or @get_id_bot in Telegram.\n');
|
|
253
|
+
|
|
254
|
+
let chatId = '';
|
|
255
|
+
let name = 'main';
|
|
256
|
+
let folder = 'main';
|
|
257
|
+
|
|
258
|
+
if (nonInteractive) {
|
|
259
|
+
chatId = requireEnv('DOTCLAW_BOOTSTRAP_CHAT_ID');
|
|
260
|
+
name = process.env.DOTCLAW_BOOTSTRAP_GROUP_NAME || 'main';
|
|
261
|
+
folder = process.env.DOTCLAW_BOOTSTRAP_GROUP_FOLDER || 'main';
|
|
262
|
+
if (!isSafeFolder(folder)) {
|
|
263
|
+
console.error('DOTCLAW_BOOTSTRAP_GROUP_FOLDER must be lowercase letters, numbers, and hyphens only.');
|
|
264
|
+
process.exit(1);
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
chatId = await prompt('Telegram chat ID');
|
|
268
|
+
if (!chatId) {
|
|
269
|
+
console.error('Chat ID is required to register the main group.');
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
name = await prompt('Group name', 'main');
|
|
273
|
+
folder = await prompt('Folder name (lowercase, hyphens)', 'main');
|
|
274
|
+
while (!isSafeFolder(folder)) {
|
|
275
|
+
console.log('Folder name must be lowercase letters, numbers, and hyphens only.');
|
|
276
|
+
folder = await prompt('Folder name (lowercase, hyphens)', 'main');
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const groups = loadJson(REGISTERED_GROUPS, {});
|
|
280
|
+
groups[String(chatId)] = {
|
|
281
|
+
name,
|
|
282
|
+
folder,
|
|
283
|
+
added_at: new Date().toISOString()
|
|
284
|
+
};
|
|
285
|
+
saveJson(REGISTERED_GROUPS, groups);
|
|
286
|
+
|
|
287
|
+
console.log('\nMain group registered.');
|
|
288
|
+
let buildNow = 'yes';
|
|
289
|
+
if (nonInteractive) {
|
|
290
|
+
buildNow = parseBoolEnv(process.env.DOTCLAW_BOOTSTRAP_BUILD, true) ? 'yes' : 'no';
|
|
291
|
+
} else {
|
|
292
|
+
buildNow = await prompt('Build the container now? (yes/no)', 'yes');
|
|
293
|
+
}
|
|
294
|
+
if (buildNow.toLowerCase().startsWith('y')) {
|
|
295
|
+
const buildScript = path.join(PACKAGE_ROOT, 'container', 'build.sh');
|
|
296
|
+
const result = spawnSync(buildScript, { stdio: 'inherit', shell: true });
|
|
297
|
+
if (result.status !== 0) {
|
|
298
|
+
console.log(`Container build failed. You can retry with: ${buildScript}`);
|
|
299
|
+
}
|
|
300
|
+
} else {
|
|
301
|
+
console.log('Skipped container build.');
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const envMap = parseEnv(path.join(DOTCLAW_HOME, '.env'));
|
|
305
|
+
const filteredEnv = filterEnv(envMap);
|
|
306
|
+
const runtimeConfig = loadRuntimeConfig();
|
|
307
|
+
const containerConfig = runtimeConfig?.host?.container || {};
|
|
308
|
+
const image = containerConfig.image || 'dotclaw-agent:latest';
|
|
309
|
+
|
|
310
|
+
const modelConfig = loadJson(path.join(CONFIG_DIR, 'model.json'), {});
|
|
311
|
+
const modelOverride = typeof modelConfig.model === 'string' && modelConfig.model.trim()
|
|
312
|
+
? modelConfig.model.trim()
|
|
313
|
+
: 'moonshotai/kimi-k2.5';
|
|
314
|
+
|
|
315
|
+
if (!filteredEnv.get('OPENROUTER_API_KEY')) {
|
|
316
|
+
console.log('Self-check skipped: OPENROUTER_API_KEY is not set in .env.');
|
|
317
|
+
} else {
|
|
318
|
+
let runCheck = 'yes';
|
|
319
|
+
if (nonInteractive) {
|
|
320
|
+
runCheck = parseBoolEnv(process.env.DOTCLAW_BOOTSTRAP_SELF_CHECK, true) ? 'yes' : 'no';
|
|
321
|
+
} else {
|
|
322
|
+
runCheck = await prompt('Run container self-check now? (yes/no)', 'yes');
|
|
323
|
+
}
|
|
324
|
+
if (runCheck.toLowerCase().startsWith('y')) {
|
|
325
|
+
const ok = runSelfCheck({
|
|
326
|
+
image,
|
|
327
|
+
envVars: filteredEnv,
|
|
328
|
+
groupFolder: folder,
|
|
329
|
+
modelOverride,
|
|
330
|
+
containerConfig
|
|
331
|
+
});
|
|
332
|
+
if (!ok) {
|
|
333
|
+
console.log('Self-check failed. Fix issues before starting the app.');
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
console.log('Skipped self-check.');
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
console.log('\nNext: start the app.');
|
|
342
|
+
console.log(' dotclaw start');
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
main().catch(err => {
|
|
346
|
+
console.error(err);
|
|
347
|
+
process.exit(1);
|
|
348
|
+
});
|